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: u64 = Self::get_usage_helper(&mut tree)?
290                .iter()
291                .map(|a| a.size_bytes)
292                .sum();
293
294            if usage > FREE_TIER_USAGE_SIZE {
295                debug!("Cannot downgrade user to free since they are over the data cap");
296                return Err(ClientError(CancelSubscriptionError::UsageIsOverFreeTierDataCap));
297            }
298        }
299
300        match account.billing_info.billing_platform {
301            None => {
302                return Err(internal!(
303                    "A user somehow has premium tier usage, but no billing information on redis. public_key: {:?}",
304                    context.public_key
305                ));
306            }
307            Some(BillingPlatform::GooglePlay(ref mut info)) => {
308                debug!("Canceling google play subscription of user");
309
310                if let GooglePlayAccountState::Canceled = &info.account_state {
311                    return Err(ClientError(CancelSubscriptionError::AlreadyCanceled));
312                }
313
314                self.google_play_client
315                    .cancel_subscription(&self.config, &info.purchase_token)
316                    .await?;
317
318                info.account_state = GooglePlayAccountState::Canceled;
319                debug!("Successfully canceled google play subscription of user");
320            }
321            Some(BillingPlatform::Stripe(ref mut info)) => {
322                debug!("Canceling stripe subscription of user");
323
324                self.stripe_client
325                    .cancel_subscription(&info.subscription_id.parse()?)
326                    .await
327                    .map_err::<ServerError<CancelSubscriptionError>, _>(|err| {
328                        internal!("{:?}", err)
329                    })?;
330
331                info.account_state = StripeAccountState::Canceled;
332
333                debug!("Successfully canceled stripe subscription");
334            }
335            Some(BillingPlatform::AppStore(_)) => {
336                return Err(ClientError(CancelSubscriptionError::CannotCancelForAppStore));
337            }
338        }
339
340        self.release_subscription_profile::<CancelSubscriptionError>(context.public_key, account)
341            .await?;
342
343        Ok(CancelSubscriptionResponse {})
344    }
345
346    pub async fn admin_set_user_tier(
347        &self, context: RequestContext<AdminSetUserTierRequest>,
348    ) -> Result<AdminSetUserTierResponse, ServerError<AdminSetUserTierError>> {
349        let request = &context.request;
350
351        {
352            let db = self.index_db.lock().await;
353
354            if !Self::is_admin::<AdminSetUserTierError>(
355                &db,
356                &context.public_key,
357                &self.config.admin.admins,
358            )? {
359                return Err(ClientError(AdminSetUserTierError::NotPermissioned));
360            }
361        }
362
363        let public_key = self
364            .index_db
365            .lock()
366            .await
367            .usernames
368            .get()
369            .get(&request.username)
370            .ok_or(ClientError(AdminSetUserTierError::UserNotFound))?
371            .0;
372        let mut account = self.lock_subscription_profile(&public_key).await?;
373
374        let billing_config = &self.config.billing;
375
376        account.billing_info.billing_platform = match &request.info {
377            AdminSetUserTierInfo::Stripe {
378                customer_id,
379                customer_name,
380                payment_method_id,
381                last_4,
382                subscription_id,
383                expiration_time,
384                account_state,
385            } => Some(BillingPlatform::Stripe(StripeUserInfo {
386                customer_id: customer_id.to_string(),
387                customer_name: *customer_name,
388                price_id: billing_config.stripe.premium_price_id.to_string(),
389                payment_method_id: payment_method_id.to_string(),
390                last_4: last_4.to_string(),
391                subscription_id: subscription_id.to_string(),
392                expiration_time: *expiration_time,
393                account_state: account_state.clone(),
394            })),
395            AdminSetUserTierInfo::GooglePlay { purchase_token, expiration_time, account_state } => {
396                Some(BillingPlatform::GooglePlay(GooglePlayUserInfo {
397                    purchase_token: purchase_token.clone(),
398                    subscription_product_id: billing_config
399                        .google
400                        .premium_subscription_product_id
401                        .to_string(),
402                    subscription_offer_id: billing_config
403                        .google
404                        .premium_subscription_offer_id
405                        .to_string(),
406                    expiration_time: *expiration_time,
407                    account_state: account_state.clone(),
408                }))
409            }
410            AdminSetUserTierInfo::AppStore {
411                account_token,
412                original_transaction_id,
413                expiration_time,
414                account_state,
415            } => Some(BillingPlatform::AppStore(AppStoreUserInfo {
416                account_token: account_token.to_string(),
417                original_transaction_id: original_transaction_id.to_string(),
418                subscription_product_id: billing_config.apple.subscription_product_id.to_string(),
419                expiration_time: *expiration_time,
420                account_state: account_state.clone(),
421            })),
422            AdminSetUserTierInfo::Free => None,
423        };
424
425        account.username = request.username.clone();
426
427        self.release_subscription_profile::<AdminSetUserTierError>(public_key, account)
428            .await?;
429
430        Ok(AdminSetUserTierResponse {})
431    }
432
433    async fn save_subscription_profile<
434        T: Debug,
435        F: Fn(&mut Account) -> Result<(), ServerError<T>>,
436    >(
437        &self, public_key: &PublicKey, update_subscription_profile: F,
438    ) -> Result<(), ServerError<T>> {
439        let millis_between_lock_attempts = self.config.billing.time_between_lock_attempts;
440        loop {
441            match self.lock_subscription_profile(public_key).await {
442                Ok(ref mut sub_profile) => {
443                    update_subscription_profile(sub_profile)?;
444                    self.release_subscription_profile(*public_key, sub_profile.clone())
445                        .await?;
446                    break;
447                }
448                Err(ClientError(ExistingRequestPending)) => {
449                    tokio::time::sleep(millis_between_lock_attempts).await;
450                    continue;
451                }
452                Err(err) => {
453                    return Err(internal!("Cannot get billing lock in webhooks: {:#?}", err));
454                }
455            }
456        }
457
458        Ok(())
459    }
460
461    pub async fn stripe_webhooks(
462        &self, request_body: Bytes, stripe_sig: HeaderValue,
463    ) -> Result<(), ServerError<StripeWebhookError>> {
464        let event = self.verify_request_and_get_event(&request_body, stripe_sig)?;
465
466        let event_type = event.type_;
467        debug!(?event_type, "Verified stripe request");
468
469        match (&event.type_, &event.data.object) {
470            (stripe::EventType::InvoicePaymentFailed, stripe::EventObject::Invoice(invoice)) => {
471                if let Some(stripe::InvoiceBillingReason::SubscriptionCycle) =
472                    invoice.billing_reason
473                {
474                    let public_key = self.get_public_key_from_invoice(invoice).await?;
475                    let owner = Owner(public_key);
476
477                    debug!(
478                        ?owner,
479                        "User's tier is being reduced due to failed renewal payment in stripe"
480                    );
481
482                    self.save_subscription_profile(&public_key, |account| {
483                        if let Some(BillingPlatform::Stripe(ref mut info)) =
484                            account.billing_info.billing_platform
485                        {
486                            info.account_state = StripeAccountState::InvoiceFailed;
487
488                            Ok(())
489                        } else {
490                            Err(internal!(
491                                "Cannot get any billing info for user. public_key: {:?}",
492                                public_key
493                            ))
494                        }
495                    })
496                    .await?;
497                }
498            }
499            (stripe::EventType::InvoicePaid, stripe::EventObject::Invoice(invoice)) => {
500                if let Some(stripe::InvoiceBillingReason::SubscriptionCycle) =
501                    invoice.billing_reason
502                {
503                    let public_key = self.get_public_key_from_invoice(invoice).await?;
504                    let owner = Owner(public_key);
505
506                    debug!(
507                        ?owner,
508                        "User's subscription period_end is being changed after successful renewal",
509                    );
510
511                    let subscription_period_end = match &invoice.subscription {
512                        Some(stripe::Expandable::Object(subscription)) => {
513                            subscription.current_period_end
514                        }
515                        Some(stripe::Expandable::Id(subscription_id)) => {
516                            self.stripe_client
517                                .get_subscription(subscription_id)
518                                .await
519                                .map_err::<ServerError<StripeWebhookError>, _>(|err| {
520                                    internal!("{:?}", err)
521                                })?
522                                .current_period_end
523                        }
524                        None => {
525                            return Err(internal!(
526                                "The subscription should be included in this invoice: {:?}",
527                                invoice
528                            ));
529                        }
530                    };
531
532                    self.save_subscription_profile(&public_key, |account| {
533                        if let Some(BillingPlatform::Stripe(ref mut info)) =
534                            account.billing_info.billing_platform
535                        {
536                            info.account_state = StripeAccountState::Ok;
537                            info.expiration_time = subscription_period_end as u64;
538
539                            Ok(())
540                        } else {
541                            Err(internal!(
542                                "Cannot get any billing info for user. public_key: {:?}",
543                                public_key
544                            ))
545                        }
546                    })
547                    .await?;
548                }
549            }
550            (_, _) => {
551                return Err(internal!("Unexpected stripe event: {:?}", event.type_));
552            }
553        }
554
555        Ok(())
556    }
557
558    pub async fn google_play_notification_webhooks(
559        &self, request_body: Bytes, query_parameters: HashMap<String, String>,
560    ) -> Result<(), ServerError<GooglePlayWebhookError>> {
561        let notification = self
562            .verify_request_and_get_notification(request_body, query_parameters)
563            .await?;
564
565        if let Some(sub_notif) = notification.subscription_notification {
566            debug!(?sub_notif, "Notification is for a subscription");
567
568            let subscription = self
569                .google_play_client
570                .get_subscription(&self.config, &sub_notif.purchase_token)
571                .await
572                .map_err(|e| internal!("{:#?}", e))?;
573
574            let notification_type = sub_notif.notification_type();
575            if let NotificationType::SubscriptionPurchased = notification_type {
576                return Ok(());
577            }
578
579            let public_key = self
580                .get_public_key_from_subnotif(&sub_notif, &subscription, &notification_type)
581                .await?;
582            let owner = Owner(public_key);
583
584            debug!(
585                ?owner,
586                ?notification_type,
587                "Updating google play user's subscription profile to match new subscription state"
588            );
589
590            self.save_subscription_profile(&public_key, |account| {
591                if let Some(BillingPlatform::GooglePlay(ref mut info)) =
592                    account.billing_info.billing_platform
593                {
594                    match notification_type {
595                        NotificationType::SubscriptionRecovered
596                        | NotificationType::SubscriptionRestarted
597                        | NotificationType::SubscriptionRenewed => {
598                            info.account_state = GooglePlayAccountState::Ok;
599                            info.expiration_time = Self::get_subscription_period_end(
600                                &subscription,
601                                &notification_type,
602                                public_key,
603                            )?;
604                        }
605                        NotificationType::SubscriptionInGracePeriod => {
606                            info.account_state = GooglePlayAccountState::GracePeriod;
607                        }
608                        NotificationType::SubscriptionOnHold => {
609                            info.account_state = GooglePlayAccountState::OnHold;
610                            info.expiration_time = Self::get_subscription_period_end(
611                                &subscription,
612                                &notification_type,
613                                public_key,
614                            )?;
615                        }
616                        NotificationType::SubscriptionExpired
617                        | NotificationType::SubscriptionRevoked => {
618                            if info.purchase_token == sub_notif.purchase_token {
619                                account.billing_info.billing_platform = None
620                            } else {
621                                let old_purchase_token = &sub_notif.purchase_token;
622                                let new_purchase_token = &info.purchase_token;
623                                debug!(
624                                ?old_purchase_token,
625                                ?new_purchase_token,
626                                "Expired or revoked subscription was tied to an old purchase_token"
627                            );
628                            }
629                        }
630                        NotificationType::SubscriptionCanceled => {
631                            info.account_state = GooglePlayAccountState::Canceled;
632                            let cancellation_reason = &subscription.cancel_survey_result;
633                            let owner = Owner(public_key);
634                            debug!(?cancellation_reason, ?owner, "Subscription cancelled");
635                        }
636                        NotificationType::SubscriptionPriceChangeConfirmed
637                        | NotificationType::SubscriptionDeferred
638                        | NotificationType::SubscriptionPaused
639                        | NotificationType::SubscriptionPausedScheduleChanged
640                        | NotificationType::SubscriptionPurchased => {
641                            return Err(internal!(
642                                "Unexpected subscription notification: {:?}, public_key: {:?}",
643                                notification_type,
644                                public_key
645                            ))
646                        }
647                        NotificationType::Unknown => {
648                            return Err(internal!(
649                                "Unknown subscription change. public_key: {:?}",
650                                public_key
651                            ))
652                        }
653                    }
654
655                    Ok(())
656                } else {
657                    Err(internal!(
658                        "Cannot get any billing info for user. public_key: {:?}",
659                        public_key
660                    ))
661                }
662            })
663            .await?;
664        }
665
666        if let Some(test_notif) = notification.test_notification {
667            let version = &test_notif.version;
668            debug!(?version, "Test notification");
669        }
670
671        if let Some(otp_notif) = notification.one_time_product_notification {
672            return Err(internal!(
673                "Received a one time product notification although there are no registered one time products. one_time_product_notification: {:?}",
674                otp_notif
675            ));
676        }
677
678        Ok(())
679    }
680
681    pub async fn app_store_notification_webhook(
682        &self, body: Bytes,
683    ) -> Result<(), ServerError<AppStoreNotificationError>> {
684        let resp = Self::decode_verify_notification(&self.config.billing.apple, &body)?;
685
686        if let NotificationChange::Subscribed = resp.notification_type {
687            return Ok(());
688        } else if let NotificationChange::Test = resp.notification_type {
689            debug!(?resp, "This is a test notification.");
690            return Ok(());
691        }
692
693        let trans = Self::decode_verify_transaction(
694            &self.config.billing.apple,
695            &resp
696                .clone()
697                .data
698                .encoded_transaction_info
699                .ok_or(ClientError(AppStoreNotificationError::InvalidJWS))?,
700        )?;
701        let public_key = self.get_public_key_from_tx(&trans).await?;
702
703        let owner = Owner(public_key);
704        let maybe_username = &self
705            .index_db
706            .lock()
707            .await
708            .accounts
709            .get()
710            .get(&owner)
711            .map(|acc| acc.username.clone());
712
713        info!(
714            ?owner,
715            ?maybe_username,
716            ?resp.notification_type,
717            ?resp.subtype,
718            "Updating app store user's subscription profile to match new subscription state"
719        );
720
721        self.save_subscription_profile(&public_key, |account| {
722            if let Some(BillingPlatform::AppStore(ref mut info)) =
723                account.billing_info.billing_platform
724            {
725                match resp.notification_type {
726                    NotificationChange::DidFailToRenew => {
727                        info.account_state = if let Some(Subtype::GracePeriod) = resp.subtype {
728                            AppStoreAccountState::GracePeriod
729                        } else {
730                            AppStoreAccountState::FailedToRenew
731                        };
732                    }
733                    NotificationChange::Expired => {
734                        info.account_state = AppStoreAccountState::Expired;
735
736                        match resp.subtype {
737                            Some(Subtype::BillingRetry) => {
738                                info!(
739                                    ?owner,
740                                    ?resp,
741                                    "Subscription failed to renew due to billing issues."
742                                );
743                            }
744                            Some(Subtype::Voluntary) => {
745                                info!(?owner, ?resp, "Subscription cancelled");
746                            }
747                            _ => {
748                                return Err(internal!(
749                            "Unexpected subtype: {:?}, notification_type {:?}, public_key: {:?}",
750                            resp.subtype,
751                            resp.notification_type,
752                            public_key
753                        ))
754                            }
755                        }
756                    }
757                    NotificationChange::GracePeriodExpired => {
758                        info.account_state = AppStoreAccountState::Expired
759                    }
760                    NotificationChange::Refund => {
761                        info!(?resp, "A user has requested a refund.");
762                    }
763                    NotificationChange::RefundDeclined => {
764                        info!(?resp, "A user's refund request has been denied.");
765                    }
766                    NotificationChange::DidChangeRenewalStatus => match resp.subtype {
767                        Some(Subtype::AutoRenewEnabled) => {
768                            info.account_state = AppStoreAccountState::Ok;
769                            info.expiration_time = trans.expires_date;
770                        }
771                        Some(Subtype::AutoRenewDisabled) => {}
772                        _ => {
773                            return Err(internal!(
774                            "Unexpected subtype: {:?}, notification_type {:?}, public_key: {:?}",
775                            resp.subtype,
776                            resp.notification_type,
777                            public_key
778                        ))
779                        }
780                    },
781                    NotificationChange::RenewalExtended => {
782                        info.expiration_time = trans.expires_date
783                    }
784                    NotificationChange::DidRenew => {
785                        if let Some(Subtype::BillingRecovery) = resp.subtype {
786                            info.account_state = AppStoreAccountState::Ok;
787                        }
788                        info.expiration_time = trans.expires_date
789                    }
790                    NotificationChange::ConsumptionRequest
791                    | NotificationChange::Subscribed
792                    | NotificationChange::DidChangeRenewalPref
793                    | NotificationChange::OfferRedeemed
794                    | NotificationChange::PriceIncrease
795                    | NotificationChange::Revoke
796                    | NotificationChange::Test => {
797                        return Err(internal!(
798                            "Unexpected notification change: {:?} {:?}, public_key: {:?}",
799                            resp.notification_type,
800                            resp.subtype,
801                            public_key
802                        ))
803                    }
804                }
805
806                Ok(())
807            } else {
808                Err(internal!("Cannot get any billing info for user. public_key: {:?}", public_key))
809            }
810        })
811        .await?;
812
813        Ok(())
814    }
815}
816
817pub fn stringify_public_key(pk: &PublicKey) -> String {
818    base64::encode(pk.serialize_compressed())
819}
820
821#[derive(Debug)]
822pub enum StripeWebhookError {
823    VerificationError(String),
824    InvalidHeader(String),
825    InvalidBody(String),
826    ParseError(String),
827}
828
829#[derive(Debug)]
830pub enum LockBillingWorkflowError {
831    UserNotFound,
832    ExistingRequestPending,
833}
834
835#[derive(Debug)]
836pub enum GooglePlayWebhookError {
837    InvalidToken,
838    CannotRetrieveData,
839    CannotDecodePubSubData(DecodeError),
840    CannotRetrieveUserInfo,
841    CannotRetrievePublicKey,
842    CannotParseTime,
843}
844
845#[derive(Debug)]
846pub enum AppStoreNotificationError {
847    InvalidJWS,
848}