Skip to main content

lockbook_server_lib/billing/
billing_service.rs

1use crate::ServerError::ClientError;
2use crate::billing::app_store_model::{NotificationChange, Subtype};
3use crate::billing::billing_model::{
4    AppStoreUserInfo, BillingPlatform, GooglePlayUserInfo, StripeUserInfo,
5};
6use crate::billing::billing_service::LockBillingWorkflowError::{
7    ExistingRequestPending, UserNotFound,
8};
9use crate::billing::google_play_model::NotificationType;
10use crate::document_service::DocumentService;
11use crate::schema::Account;
12use crate::{RequestContext, ServerError, ServerState};
13use base64::DecodeError;
14use db_rs::Db;
15use lb_rs::model::api::{
16    AdminSetUserTierError, AdminSetUserTierInfo, AdminSetUserTierRequest, AdminSetUserTierResponse,
17    AppStoreAccountState, CancelSubscriptionError, CancelSubscriptionRequest,
18    CancelSubscriptionResponse, FREE_TIER_USAGE_SIZE, GetSubscriptionInfoError,
19    GetSubscriptionInfoRequest, GetSubscriptionInfoResponse, GooglePlayAccountState,
20    PaymentPlatform, StripeAccountState, SubscriptionInfo, UpgradeAccountAppStoreError,
21    UpgradeAccountAppStoreRequest, UpgradeAccountAppStoreResponse, UpgradeAccountGooglePlayError,
22    UpgradeAccountGooglePlayRequest, UpgradeAccountGooglePlayResponse, UpgradeAccountStripeError,
23    UpgradeAccountStripeRequest, UpgradeAccountStripeResponse,
24};
25use lb_rs::model::clock::get_time;
26use lb_rs::model::file_metadata::Owner;
27use lb_rs::model::server_tree::ServerTree;
28use lb_rs::model::tree_like::TreeLike;
29use libsecp256k1::PublicKey;
30use std::collections::HashMap;
31use std::fmt::Debug;
32use std::ops::DerefMut;
33use tracing::*;
34use warp::http::HeaderValue;
35use warp::hyper::body::Bytes;
36
37use super::app_store_client::AppStoreClient;
38use super::google_play_client::GooglePlayClient;
39use super::stripe_client::StripeClient;
40
41impl<S, A, G, D> ServerState<S, A, G, D>
42where
43    S: StripeClient,
44    A: AppStoreClient,
45    G: GooglePlayClient,
46    D: DocumentService,
47{
48    async fn lock_subscription_profile(
49        &self, public_key: &PublicKey,
50    ) -> Result<Account, ServerError<LockBillingWorkflowError>> {
51        let owner = Owner(*public_key);
52        let mut db = self.index_db.lock().await;
53        let tx = db.begin_transaction()?;
54        let mut account = db
55            .accounts
56            .get()
57            .get(&owner)
58            .ok_or(ClientError(UserNotFound))?
59            .clone();
60
61        let current_time = get_time().0 as u64;
62
63        if current_time - account.billing_info.last_in_payment_flow
64            < self.config.billing.millis_between_user_payment_flows
65        {
66            warn!(
67                ?owner,
68                "User/Webhook is already in payment flow, or not enough time that has elapsed since a failed attempt"
69            );
70
71            return Err(ClientError(ExistingRequestPending));
72        }
73
74        account.billing_info.last_in_payment_flow = current_time;
75        db.accounts.insert(owner, account.clone())?;
76
77        debug!(?owner, "User successfully entered payment flow");
78
79        tx.drop_safely()?;
80        Ok(account)
81    }
82
83    async fn release_subscription_profile<T: Debug>(
84        &self, public_key: PublicKey, mut account: Account,
85    ) -> Result<(), ServerError<T>> {
86        account.billing_info.last_in_payment_flow = 0;
87        self.index_db
88            .lock()
89            .await
90            .accounts
91            .insert(Owner(public_key), account)?;
92        Ok(())
93    }
94
95    pub async fn upgrade_account_app_store(
96        &self, context: RequestContext<UpgradeAccountAppStoreRequest>,
97    ) -> Result<UpgradeAccountAppStoreResponse, ServerError<UpgradeAccountAppStoreError>> {
98        let request = &context.request;
99
100        let mut account = self.lock_subscription_profile(&context.public_key).await?;
101
102        debug!("Upgrading the account of a user through app store billing");
103
104        {
105            let db = self.index_db.lock().await;
106            if db
107                .app_store_ids
108                .get()
109                .get(&request.app_account_token)
110                .is_some()
111            {
112                return Err(ClientError(UpgradeAccountAppStoreError::AppStoreAccountAlreadyLinked));
113            }
114        }
115
116        if account.billing_info.is_premium() {
117            return Err(ClientError(UpgradeAccountAppStoreError::AlreadyPremium));
118        }
119
120        let (expires, account_state) = Self::verify_details(
121            &self.app_store_client,
122            &self.config.billing.apple,
123            &request.app_account_token,
124            &request.original_transaction_id,
125        )
126        .await?;
127
128        debug!("Successfully verified app store subscription");
129
130        account.billing_info.billing_platform = Some(BillingPlatform::AppStore(AppStoreUserInfo {
131            account_token: request.app_account_token.clone(),
132            original_transaction_id: request.original_transaction_id.clone(),
133            subscription_product_id: self.config.billing.apple.subscription_product_id.clone(),
134            expiration_time: expires,
135            account_state,
136        }));
137
138        self.index_db
139            .lock()
140            .await
141            .app_store_ids
142            .insert(request.app_account_token.clone(), Owner(context.public_key))?;
143
144        self.release_subscription_profile::<UpgradeAccountAppStoreError>(
145            context.public_key,
146            account,
147        )
148        .await?;
149
150        Ok(UpgradeAccountAppStoreResponse {})
151    }
152
153    pub async fn upgrade_account_google_play(
154        &self, context: RequestContext<UpgradeAccountGooglePlayRequest>,
155    ) -> Result<UpgradeAccountGooglePlayResponse, ServerError<UpgradeAccountGooglePlayError>> {
156        let request = &context.request;
157
158        let mut account = self.lock_subscription_profile(&context.public_key).await?;
159
160        if account.billing_info.is_premium() {
161            return Err(ClientError(UpgradeAccountGooglePlayError::AlreadyPremium));
162        }
163
164        debug!("Upgrading the account of a user through google play billing");
165
166        self.google_play_client
167            .acknowledge_subscription(&self.config, &request.purchase_token)
168            .await?;
169
170        debug!("Acknowledged a user's google play subscription");
171
172        let expiry_info = self
173            .google_play_client
174            .get_subscription(&self.config, &request.purchase_token)
175            .await?;
176
177        account.billing_info.billing_platform = Some(BillingPlatform::new_play_sub(
178            &self.config,
179            &request.purchase_token,
180            expiry_info,
181        )?);
182
183        self.index_db
184            .lock()
185            .await
186            .google_play_ids
187            .insert(request.account_id.clone(), Owner(context.public_key))?;
188
189        self.release_subscription_profile::<UpgradeAccountGooglePlayError>(
190            context.public_key,
191            account,
192        )
193        .await?;
194
195        debug!("Successfully upgraded a user through a google play subscription. public_key");
196
197        Ok(UpgradeAccountGooglePlayResponse {})
198    }
199
200    pub async fn upgrade_account_stripe(
201        &self, context: RequestContext<UpgradeAccountStripeRequest>,
202    ) -> Result<UpgradeAccountStripeResponse, ServerError<UpgradeAccountStripeError>> {
203        let request = &context.request;
204
205        debug!("Attempting to upgrade the account tier of to premium");
206
207        let mut account = self.lock_subscription_profile(&context.public_key).await?;
208
209        if account.billing_info.is_premium() {
210            return Err(ClientError(UpgradeAccountStripeError::AlreadyPremium));
211        }
212
213        let maybe_user_info = account
214            .billing_info
215            .billing_platform
216            .and_then(|info| match info {
217                BillingPlatform::Stripe(stripe_info) => Some(stripe_info),
218                _ => None,
219            });
220
221        let user_info = self
222            .create_subscription(&context.public_key, &request.account_tier, maybe_user_info)
223            .await?;
224
225        account.billing_info.billing_platform = Some(BillingPlatform::Stripe(user_info));
226        self.release_subscription_profile::<UpgradeAccountStripeError>(context.public_key, account)
227            .await?;
228
229        debug!("Successfully upgraded the account tier of from free to premium");
230
231        Ok(UpgradeAccountStripeResponse {})
232    }
233
234    pub async fn get_subscription_info(
235        &self, context: RequestContext<GetSubscriptionInfoRequest>,
236    ) -> Result<GetSubscriptionInfoResponse, ServerError<GetSubscriptionInfoError>> {
237        let platform = self
238            .index_db
239            .lock()
240            .await
241            .accounts
242            .get()
243            .get(&Owner(context.public_key))
244            .ok_or(ClientError(GetSubscriptionInfoError::UserNotFound))?
245            .billing_info
246            .billing_platform
247            .clone();
248
249        let subscription_info = platform.map(|info| match info {
250            BillingPlatform::Stripe(info) => SubscriptionInfo {
251                payment_platform: PaymentPlatform::Stripe { card_last_4_digits: info.last_4 },
252                period_end: info.expiration_time,
253            },
254            BillingPlatform::GooglePlay(info) => SubscriptionInfo {
255                payment_platform: PaymentPlatform::GooglePlay { account_state: info.account_state },
256                period_end: info.expiration_time,
257            },
258            BillingPlatform::AppStore(info) => SubscriptionInfo {
259                payment_platform: PaymentPlatform::AppStore { account_state: info.account_state },
260                period_end: info.expiration_time,
261            },
262        });
263
264        Ok(GetSubscriptionInfoResponse { subscription_info })
265    }
266
267    pub async fn cancel_subscription(
268        &self, context: RequestContext<CancelSubscriptionRequest>,
269    ) -> Result<CancelSubscriptionResponse, ServerError<CancelSubscriptionError>> {
270        let mut account = self.lock_subscription_profile(&context.public_key).await?;
271
272        if account.billing_info.data_cap() == FREE_TIER_USAGE_SIZE {
273            return Err(ClientError(CancelSubscriptionError::NotPremium));
274        }
275
276        {
277            let mut lock = self.index_db.lock().await;
278            let db = lock.deref_mut();
279
280            let mut tree = ServerTree::new(
281                Owner(context.public_key),
282                &mut db.owned_files,
283                &mut db.shared_files,
284                &mut db.file_children,
285                &mut db.metas,
286            )?
287            .to_lazy();
288
289            let usage = tree.calculate_usage(Owner(context.public_key))?;
290
291            if usage > FREE_TIER_USAGE_SIZE {
292                debug!("Cannot downgrade user to free since they are over the data cap");
293                return Err(ClientError(CancelSubscriptionError::UsageIsOverFreeTierDataCap));
294            }
295        }
296
297        match account.billing_info.billing_platform {
298            None => {
299                return Err(internal!(
300                    "A user somehow has premium tier usage, but no billing information on redis. public_key: {:?}",
301                    context.public_key
302                ));
303            }
304            Some(BillingPlatform::GooglePlay(ref mut info)) => {
305                debug!("Canceling google play subscription of user");
306
307                if let GooglePlayAccountState::Canceled = &info.account_state {
308                    return Err(ClientError(CancelSubscriptionError::AlreadyCanceled));
309                }
310
311                self.google_play_client
312                    .cancel_subscription(&self.config, &info.purchase_token)
313                    .await?;
314
315                info.account_state = GooglePlayAccountState::Canceled;
316                debug!("Successfully canceled google play subscription of user");
317            }
318            Some(BillingPlatform::Stripe(ref mut info)) => {
319                debug!("Canceling stripe subscription of user");
320
321                self.stripe_client
322                    .cancel_subscription(&info.subscription_id.parse()?)
323                    .await
324                    .map_err::<ServerError<CancelSubscriptionError>, _>(|err| {
325                        internal!("{:?}", err)
326                    })?;
327
328                info.account_state = StripeAccountState::Canceled;
329
330                debug!("Successfully canceled stripe subscription");
331            }
332            Some(BillingPlatform::AppStore(_)) => {
333                return Err(ClientError(CancelSubscriptionError::CannotCancelForAppStore));
334            }
335        }
336
337        self.release_subscription_profile::<CancelSubscriptionError>(context.public_key, account)
338            .await?;
339
340        Ok(CancelSubscriptionResponse {})
341    }
342
343    pub async fn admin_set_user_tier(
344        &self, context: RequestContext<AdminSetUserTierRequest>,
345    ) -> Result<AdminSetUserTierResponse, ServerError<AdminSetUserTierError>> {
346        let request = &context.request;
347
348        {
349            let db = self.index_db.lock().await;
350
351            if !Self::is_admin::<AdminSetUserTierError>(
352                &db,
353                &context.public_key,
354                &self.config.admin.admins,
355            )? {
356                return Err(ClientError(AdminSetUserTierError::NotPermissioned));
357            }
358        }
359
360        let public_key = self
361            .index_db
362            .lock()
363            .await
364            .usernames
365            .get()
366            .get(&request.username)
367            .ok_or(ClientError(AdminSetUserTierError::UserNotFound))?
368            .0;
369        let mut account = self.lock_subscription_profile(&public_key).await?;
370
371        let billing_config = &self.config.billing;
372
373        account.billing_info.billing_platform = match &request.info {
374            AdminSetUserTierInfo::Stripe {
375                customer_id,
376                customer_name,
377                payment_method_id,
378                last_4,
379                subscription_id,
380                expiration_time,
381                account_state,
382            } => Some(BillingPlatform::Stripe(StripeUserInfo {
383                customer_id: customer_id.to_string(),
384                customer_name: *customer_name,
385                price_id: billing_config.stripe.premium_price_id.to_string(),
386                payment_method_id: payment_method_id.to_string(),
387                last_4: last_4.to_string(),
388                subscription_id: subscription_id.to_string(),
389                expiration_time: *expiration_time,
390                account_state: account_state.clone(),
391            })),
392            AdminSetUserTierInfo::GooglePlay { purchase_token, expiration_time, account_state } => {
393                Some(BillingPlatform::GooglePlay(GooglePlayUserInfo {
394                    purchase_token: purchase_token.clone(),
395                    subscription_product_id: billing_config
396                        .google
397                        .premium_subscription_product_id
398                        .to_string(),
399                    subscription_offer_id: billing_config
400                        .google
401                        .premium_subscription_offer_id
402                        .to_string(),
403                    expiration_time: *expiration_time,
404                    account_state: account_state.clone(),
405                }))
406            }
407            AdminSetUserTierInfo::AppStore {
408                account_token,
409                original_transaction_id,
410                expiration_time,
411                account_state,
412            } => Some(BillingPlatform::AppStore(AppStoreUserInfo {
413                account_token: account_token.to_string(),
414                original_transaction_id: original_transaction_id.to_string(),
415                subscription_product_id: billing_config.apple.subscription_product_id.to_string(),
416                expiration_time: *expiration_time,
417                account_state: account_state.clone(),
418            })),
419            AdminSetUserTierInfo::Free => None,
420        };
421
422        account.username = request.username.clone();
423
424        self.release_subscription_profile::<AdminSetUserTierError>(public_key, account)
425            .await?;
426
427        Ok(AdminSetUserTierResponse {})
428    }
429
430    async fn save_subscription_profile<
431        T: Debug,
432        F: Fn(&mut Account) -> Result<(), ServerError<T>>,
433    >(
434        &self, public_key: &PublicKey, update_subscription_profile: F,
435    ) -> Result<(), ServerError<T>> {
436        let millis_between_lock_attempts = self.config.billing.time_between_lock_attempts;
437        loop {
438            match self.lock_subscription_profile(public_key).await {
439                Ok(ref mut sub_profile) => {
440                    update_subscription_profile(sub_profile)?;
441                    self.release_subscription_profile(*public_key, sub_profile.clone())
442                        .await?;
443                    break;
444                }
445                Err(ClientError(ExistingRequestPending)) => {
446                    tokio::time::sleep(millis_between_lock_attempts).await;
447                    continue;
448                }
449                Err(err) => {
450                    return Err(internal!("Cannot get billing lock in webhooks: {:#?}", err));
451                }
452            }
453        }
454
455        Ok(())
456    }
457
458    pub async fn stripe_webhooks(
459        &self, request_body: Bytes, stripe_sig: HeaderValue,
460    ) -> Result<(), ServerError<StripeWebhookError>> {
461        let event = self.verify_request_and_get_event(&request_body, stripe_sig)?;
462
463        let event_type = event.type_;
464        debug!(?event_type, "Verified stripe request");
465
466        match (&event.type_, &event.data.object) {
467            (stripe::EventType::InvoicePaymentFailed, stripe::EventObject::Invoice(invoice)) => {
468                if let Some(stripe::InvoiceBillingReason::SubscriptionCycle) =
469                    invoice.billing_reason
470                {
471                    let public_key = self.get_public_key_from_invoice(invoice).await?;
472                    let owner = Owner(public_key);
473
474                    debug!(
475                        ?owner,
476                        "User's tier is being reduced due to failed renewal payment in stripe"
477                    );
478
479                    self.save_subscription_profile(&public_key, |account| {
480                        if let Some(BillingPlatform::Stripe(ref mut info)) =
481                            account.billing_info.billing_platform
482                        {
483                            info.account_state = StripeAccountState::InvoiceFailed;
484
485                            Ok(())
486                        } else {
487                            Err(internal!(
488                                "Cannot get any billing info for user. public_key: {:?}",
489                                public_key
490                            ))
491                        }
492                    })
493                    .await?;
494                }
495            }
496            (stripe::EventType::InvoicePaid, stripe::EventObject::Invoice(invoice)) => {
497                if let Some(stripe::InvoiceBillingReason::SubscriptionCycle) =
498                    invoice.billing_reason
499                {
500                    let public_key = self.get_public_key_from_invoice(invoice).await?;
501                    let owner = Owner(public_key);
502
503                    debug!(
504                        ?owner,
505                        "User's subscription period_end is being changed after successful renewal",
506                    );
507
508                    let subscription_period_end = match &invoice.subscription {
509                        Some(stripe::Expandable::Object(subscription)) => {
510                            subscription.current_period_end
511                        }
512                        Some(stripe::Expandable::Id(subscription_id)) => {
513                            self.stripe_client
514                                .get_subscription(subscription_id)
515                                .await
516                                .map_err::<ServerError<StripeWebhookError>, _>(|err| {
517                                    internal!("{:?}", err)
518                                })?
519                                .current_period_end
520                        }
521                        None => {
522                            return Err(internal!(
523                                "The subscription should be included in this invoice: {:?}",
524                                invoice
525                            ));
526                        }
527                    };
528
529                    self.save_subscription_profile(&public_key, |account| {
530                        if let Some(BillingPlatform::Stripe(ref mut info)) =
531                            account.billing_info.billing_platform
532                        {
533                            info.account_state = StripeAccountState::Ok;
534                            info.expiration_time = subscription_period_end as u64;
535
536                            Ok(())
537                        } else {
538                            Err(internal!(
539                                "Cannot get any billing info for user. public_key: {:?}",
540                                public_key
541                            ))
542                        }
543                    })
544                    .await?;
545                }
546            }
547            (_, _) => {
548                return Err(internal!("Unexpected stripe event: {:?}", event.type_));
549            }
550        }
551
552        Ok(())
553    }
554
555    pub async fn google_play_notification_webhooks(
556        &self, request_body: Bytes, query_parameters: HashMap<String, String>,
557    ) -> Result<(), ServerError<GooglePlayWebhookError>> {
558        let notification = self
559            .verify_request_and_get_notification(request_body, query_parameters)
560            .await?;
561
562        if let Some(sub_notif) = notification.subscription_notification {
563            debug!(?sub_notif, "Notification is for a subscription");
564
565            let subscription = self
566                .google_play_client
567                .get_subscription(&self.config, &sub_notif.purchase_token)
568                .await
569                .map_err(|e| internal!("{:#?}", e))?;
570
571            let notification_type = sub_notif.notification_type();
572            if let NotificationType::SubscriptionPurchased = notification_type {
573                return Ok(());
574            }
575
576            let public_key = self
577                .get_public_key_from_subnotif(&sub_notif, &subscription, &notification_type)
578                .await?;
579            let owner = Owner(public_key);
580
581            debug!(
582                ?owner,
583                ?notification_type,
584                "Updating google play user's subscription profile to match new subscription state"
585            );
586
587            self.save_subscription_profile(&public_key, |account| {
588                if let Some(BillingPlatform::GooglePlay(ref mut info)) =
589                    account.billing_info.billing_platform
590                {
591                    match notification_type {
592                        NotificationType::SubscriptionRecovered
593                        | NotificationType::SubscriptionRestarted
594                        | NotificationType::SubscriptionRenewed => {
595                            info.account_state = GooglePlayAccountState::Ok;
596                            info.expiration_time = Self::get_subscription_period_end(
597                                &subscription,
598                                &notification_type,
599                                public_key,
600                            )?;
601                        }
602                        NotificationType::SubscriptionInGracePeriod => {
603                            info.account_state = GooglePlayAccountState::GracePeriod;
604                        }
605                        NotificationType::SubscriptionOnHold => {
606                            info.account_state = GooglePlayAccountState::OnHold;
607                            info.expiration_time = Self::get_subscription_period_end(
608                                &subscription,
609                                &notification_type,
610                                public_key,
611                            )?;
612                        }
613                        NotificationType::SubscriptionExpired
614                        | NotificationType::SubscriptionRevoked => {
615                            if info.purchase_token == sub_notif.purchase_token {
616                                account.billing_info.billing_platform = None
617                            } else {
618                                let old_purchase_token = &sub_notif.purchase_token;
619                                let new_purchase_token = &info.purchase_token;
620                                debug!(
621                                ?old_purchase_token,
622                                ?new_purchase_token,
623                                "Expired or revoked subscription was tied to an old purchase_token"
624                            );
625                            }
626                        }
627                        NotificationType::SubscriptionCanceled => {
628                            info.account_state = GooglePlayAccountState::Canceled;
629                            let cancellation_reason = &subscription.cancel_survey_result;
630                            let owner = Owner(public_key);
631                            debug!(?cancellation_reason, ?owner, "Subscription cancelled");
632                        }
633                        NotificationType::SubscriptionPriceChangeConfirmed
634                        | NotificationType::SubscriptionDeferred
635                        | NotificationType::SubscriptionPaused
636                        | NotificationType::SubscriptionPausedScheduleChanged
637                        | NotificationType::SubscriptionPurchased => {
638                            return Err(internal!(
639                                "Unexpected subscription notification: {:?}, public_key: {:?}",
640                                notification_type,
641                                public_key
642                            ))
643                        }
644                        NotificationType::Unknown => {
645                            return Err(internal!(
646                                "Unknown subscription change. public_key: {:?}",
647                                public_key
648                            ))
649                        }
650                    }
651
652                    Ok(())
653                } else {
654                    Err(internal!(
655                        "Cannot get any billing info for user. public_key: {:?}",
656                        public_key
657                    ))
658                }
659            })
660            .await?;
661        }
662
663        if let Some(test_notif) = notification.test_notification {
664            let version = &test_notif.version;
665            debug!(?version, "Test notification");
666        }
667
668        if let Some(otp_notif) = notification.one_time_product_notification {
669            return Err(internal!(
670                "Received a one time product notification although there are no registered one time products. one_time_product_notification: {:?}",
671                otp_notif
672            ));
673        }
674
675        Ok(())
676    }
677
678    pub async fn app_store_notification_webhook(
679        &self, body: Bytes,
680    ) -> Result<(), ServerError<AppStoreNotificationError>> {
681        let resp = Self::decode_verify_notification(&self.config.billing.apple, &body)?;
682
683        if let NotificationChange::Subscribed = resp.notification_type {
684            return Ok(());
685        } else if let NotificationChange::Test = resp.notification_type {
686            debug!(?resp, "This is a test notification.");
687            return Ok(());
688        }
689
690        let trans = Self::decode_verify_transaction(
691            &self.config.billing.apple,
692            &resp
693                .clone()
694                .data
695                .encoded_transaction_info
696                .ok_or(ClientError(AppStoreNotificationError::InvalidJWS))?,
697        )?;
698        let public_key = self.get_public_key_from_tx(&trans).await?;
699
700        let owner = Owner(public_key);
701        let maybe_username = &self
702            .index_db
703            .lock()
704            .await
705            .accounts
706            .get()
707            .get(&owner)
708            .map(|acc| acc.username.clone());
709
710        info!(
711            ?owner,
712            ?maybe_username,
713            ?resp.notification_type,
714            ?resp.subtype,
715            "Updating app store user's subscription profile to match new subscription state"
716        );
717
718        self.save_subscription_profile(&public_key, |account| {
719            if let Some(BillingPlatform::AppStore(ref mut info)) =
720                account.billing_info.billing_platform
721            {
722                match resp.notification_type {
723                    NotificationChange::DidFailToRenew => {
724                        info.account_state = if let Some(Subtype::GracePeriod) = resp.subtype {
725                            AppStoreAccountState::GracePeriod
726                        } else {
727                            AppStoreAccountState::FailedToRenew
728                        };
729                    }
730                    NotificationChange::Expired => {
731                        info.account_state = AppStoreAccountState::Expired;
732
733                        match resp.subtype {
734                            Some(Subtype::BillingRetry) => {
735                                info!(
736                                    ?owner,
737                                    ?resp,
738                                    "Subscription failed to renew due to billing issues."
739                                );
740                            }
741                            Some(Subtype::Voluntary) => {
742                                info!(?owner, ?resp, "Subscription cancelled");
743                            }
744                            _ => {
745                                return Err(internal!(
746                            "Unexpected subtype: {:?}, notification_type {:?}, public_key: {:?}",
747                            resp.subtype,
748                            resp.notification_type,
749                            public_key
750                        ))
751                            }
752                        }
753                    }
754                    NotificationChange::GracePeriodExpired => {
755                        info.account_state = AppStoreAccountState::Expired
756                    }
757                    NotificationChange::Refund => {
758                        info!(?resp, "A user has requested a refund.");
759                    }
760                    NotificationChange::RefundDeclined => {
761                        info!(?resp, "A user's refund request has been denied.");
762                    }
763                    NotificationChange::DidChangeRenewalStatus => match resp.subtype {
764                        Some(Subtype::AutoRenewEnabled) => {
765                            info.account_state = AppStoreAccountState::Ok;
766                            info.expiration_time = trans.expires_date;
767                        }
768                        Some(Subtype::AutoRenewDisabled) => {}
769                        _ => {
770                            return Err(internal!(
771                            "Unexpected subtype: {:?}, notification_type {:?}, public_key: {:?}",
772                            resp.subtype,
773                            resp.notification_type,
774                            public_key
775                        ))
776                        }
777                    },
778                    NotificationChange::RenewalExtended => {
779                        info.expiration_time = trans.expires_date
780                    }
781                    NotificationChange::DidRenew => {
782                        if let Some(Subtype::BillingRecovery) = resp.subtype {
783                            info.account_state = AppStoreAccountState::Ok;
784                        }
785                        info.expiration_time = trans.expires_date
786                    }
787                    NotificationChange::ConsumptionRequest
788                    | NotificationChange::Subscribed
789                    | NotificationChange::DidChangeRenewalPref
790                    | NotificationChange::OfferRedeemed
791                    | NotificationChange::PriceIncrease
792                    | NotificationChange::Revoke
793                    | NotificationChange::Test => {
794                        return Err(internal!(
795                            "Unexpected notification change: {:?} {:?}, public_key: {:?}",
796                            resp.notification_type,
797                            resp.subtype,
798                            public_key
799                        ))
800                    }
801                }
802
803                Ok(())
804            } else {
805                Err(internal!("Cannot get any billing info for user. public_key: {:?}", public_key))
806            }
807        })
808        .await?;
809
810        Ok(())
811    }
812}
813
814pub fn stringify_public_key(pk: &PublicKey) -> String {
815    base64::encode(pk.serialize_compressed())
816}
817
818#[derive(Debug)]
819pub enum StripeWebhookError {
820    VerificationError(String),
821    InvalidHeader(String),
822    InvalidBody(String),
823    ParseError(String),
824}
825
826#[derive(Debug)]
827pub enum LockBillingWorkflowError {
828    UserNotFound,
829    ExistingRequestPending,
830}
831
832#[derive(Debug)]
833pub enum GooglePlayWebhookError {
834    InvalidToken,
835    CannotRetrieveData,
836    CannotDecodePubSubData(DecodeError),
837    CannotRetrieveUserInfo,
838    CannotRetrievePublicKey,
839    CannotParseTime,
840}
841
842#[derive(Debug)]
843pub enum AppStoreNotificationError {
844    InvalidJWS,
845}