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