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