Skip to main content

lockbook_server_lib/billing/
google_play_service.rs

1use crate::billing::billing_service::GooglePlayWebhookError;
2use crate::billing::google_play_model::{
3    DeveloperNotification, NotificationType, PubSubNotification, SubscriptionNotification,
4};
5use crate::document_service::DocumentService;
6use crate::{ClientError, ServerError, ServerState};
7use google_androidpublisher3::api::SubscriptionPurchase;
8use google_androidpublisher3::hyper::body::Bytes;
9use lb_rs::model::api::UnixTimeMillis;
10use libsecp256k1::PublicKey;
11use std::collections::HashMap;
12use tracing::*;
13
14use super::app_store_client::AppStoreClient;
15use super::google_play_client::GooglePlayClient;
16use super::stripe_client::StripeClient;
17
18impl<S, A, G, D> ServerState<S, A, G, D>
19where
20    S: StripeClient,
21    A: AppStoreClient,
22    G: GooglePlayClient,
23    D: DocumentService,
24{
25    pub async fn get_public_key_from_subnotif(
26        &self, sub_notif: &SubscriptionNotification, subscription: &SubscriptionPurchase,
27        notification_type: &NotificationType,
28    ) -> Result<PublicKey, ServerError<GooglePlayWebhookError>> {
29        let account_id = &subscription
30            .obfuscated_external_account_id
31            .clone()
32            .ok_or_else(|| {
33                internal!("There should be an account id attached to a purchase: {:?}", sub_notif)
34            })?;
35
36        info!(
37            ?notification_type,
38            ?account_id,
39            "Retrieved full subscription info for notification event",
40        );
41
42        let public_key: PublicKey = self
43            .index_db
44            .lock()
45            .await
46            .google_play_ids
47            .get()
48            .get(account_id)
49            .ok_or_else(|| {
50                internal!("There is no public_key related to this account_id: {:?}", account_id)
51            })?
52            .0;
53
54        Ok(public_key)
55    }
56
57    pub fn get_subscription_period_end(
58        subscription: &SubscriptionPurchase, notification_type: &NotificationType,
59        public_key: PublicKey,
60    ) -> Result<UnixTimeMillis, ServerError<GooglePlayWebhookError>> {
61        UnixTimeMillis::try_from(subscription
62        .expiry_time_millis
63        .ok_or_else(|| internal!("Cannot get expiration time of a recovered subscription. public_key {:?}, subscription notification type: {:?}", public_key, notification_type))?)
64        .map_err(|e| internal!("Cannot parse millis into int: {:?}", e))
65    }
66
67    pub async fn verify_request_and_get_notification(
68        &self, request_body: Bytes, query_parameters: HashMap<String, String>,
69    ) -> Result<DeveloperNotification, ServerError<GooglePlayWebhookError>> {
70        if !constant_time_eq::constant_time_eq(
71            query_parameters
72                .get("token")
73                .ok_or(ClientError(GooglePlayWebhookError::InvalidToken))?
74                .as_bytes(),
75            self.config.billing.google.pubsub_token.as_bytes(),
76        ) {
77            return Err(ClientError(GooglePlayWebhookError::InvalidToken));
78        }
79
80        info!("Parsing pubsub notification and extracting the developer notification");
81
82        let pubsub_notif = serde_json::from_slice::<PubSubNotification>(&request_body)?;
83        let data = base64::decode(pubsub_notif.message.data)
84            .map_err(|e| ClientError(GooglePlayWebhookError::CannotDecodePubSubData(e)))?;
85
86        Ok(serde_json::from_slice::<DeveloperNotification>(&data)?)
87    }
88}