lockbook_server_lib/billing/
google_play_service.rs1use 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}