Skip to main content

lockbook_server_lib/billing/
google_play_client.rs

1use crate::config::Config;
2use async_trait::async_trait;
3use google_androidpublisher3::api::{
4    SubscriptionPurchase, SubscriptionPurchasesAcknowledgeRequest,
5};
6use google_androidpublisher3::hyper::StatusCode;
7use google_androidpublisher3::hyper::client::HttpConnector;
8use google_androidpublisher3::hyper_rustls::HttpsConnector;
9use google_androidpublisher3::{AndroidPublisher, Error, hyper, hyper_rustls, oauth2};
10
11const PACKAGE_NAME: &str = "app.lockbook";
12
13pub async fn get_google_play_client(
14    service_account_key: &Option<String>,
15) -> AndroidPublisher<HttpsConnector<HttpConnector>> {
16    let auth = match service_account_key {
17        Some(key) => {
18            let service_account_key: oauth2::ServiceAccountKey =
19                oauth2::parse_service_account_key(key).unwrap();
20
21            oauth2::ServiceAccountAuthenticator::builder(service_account_key)
22                .build()
23                .await
24                .unwrap()
25        }
26        None => {
27            // creating dummy AndroidPublisher since no service account was provided
28            oauth2::InstalledFlowAuthenticator::builder(
29                oauth2::ApplicationSecret::default(),
30                oauth2::InstalledFlowReturnMethod::HTTPRedirect,
31            )
32            .build()
33            .await
34            .unwrap()
35        }
36    };
37
38    let client = hyper::Client::builder().build(
39        hyper_rustls::HttpsConnectorBuilder::with_native_roots(Default::default())
40            .https_or_http()
41            .enable_http1()
42            .enable_http2()
43            .build(),
44    );
45
46    AndroidPublisher::new(client, auth)
47}
48
49#[derive(Debug)]
50pub enum SimpleGCPError {
51    PurchaseTokenNotFound,
52    Unexpected(String),
53}
54
55impl From<Error> for SimpleGCPError {
56    fn from(err: Error) -> Self {
57        match err {
58            Error::Failure(err) => {
59                if err.status() == StatusCode::NOT_FOUND {
60                    Self::PurchaseTokenNotFound
61                } else {
62                    Self::Unexpected(format!("{err:#?}"))
63                }
64            }
65            _ => Self::Unexpected(format!("{err:#?}")),
66        }
67    }
68}
69
70#[async_trait]
71pub trait GooglePlayClient: Send + Sync + Clone + 'static {
72    async fn acknowledge_subscription(
73        &self, config: &Config, purchase_token: &str,
74    ) -> Result<(), SimpleGCPError>;
75
76    async fn cancel_subscription(
77        &self, config: &Config, purchase_token: &str,
78    ) -> Result<(), SimpleGCPError>;
79
80    async fn get_subscription(
81        &self, config: &Config, purchase_token: &str,
82    ) -> Result<SubscriptionPurchase, SimpleGCPError>;
83}
84
85#[async_trait]
86impl GooglePlayClient for AndroidPublisher<HttpsConnector<HttpConnector>> {
87    async fn acknowledge_subscription(
88        &self, config: &Config, purchase_token: &str,
89    ) -> Result<(), SimpleGCPError> {
90        let subscription_id = &config.billing.google.premium_subscription_product_id;
91        self.purchases()
92            .subscriptions_acknowledge(
93                SubscriptionPurchasesAcknowledgeRequest { developer_payload: None },
94                PACKAGE_NAME,
95                subscription_id,
96                purchase_token,
97            )
98            .doit()
99            .await?;
100
101        Ok(())
102    }
103
104    async fn cancel_subscription(
105        &self, config: &Config, purchase_token: &str,
106    ) -> Result<(), SimpleGCPError> {
107        let subscription_id = &config.billing.google.premium_subscription_product_id;
108        self.purchases()
109            .subscriptions_cancel(PACKAGE_NAME, subscription_id, purchase_token)
110            .doit()
111            .await?;
112
113        Ok(())
114    }
115
116    async fn get_subscription(
117        &self, config: &Config, purchase_token: &str,
118    ) -> Result<SubscriptionPurchase, SimpleGCPError> {
119        let subscription_id = &config.billing.google.premium_subscription_product_id;
120        Ok(self
121            .purchases()
122            .subscriptions_get(PACKAGE_NAME, subscription_id, purchase_token)
123            .doit()
124            .await?
125            .1)
126    }
127}