1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#![allow(clippy::module_name_repetitions)]
use super::{error, error::Result, PurchaseResponse, UnityPurchaseReceipt};
use chrono::Utc;
use hyper::{body, Body, Client, Request};
use hyper_tls::HttpsConnector;
use serde::{de::Error, Deserialize, Serialize};
use yup_oauth2::{ServiceAccountAuthenticator, ServiceAccountKey};
#[derive(Default, Serialize, Deserialize)]
pub struct GoogleResponse {
#[serde(rename = "expiryTimeMillis")]
pub expiry_time: String,
#[serde(rename = "priceCurrencyCode")]
pub price_currency_code: String,
#[serde(rename = "priceAmountMicros")]
pub price_amount_micros: String,
#[serde(rename = "orderId")]
pub order_id: String,
}
#[derive(Serialize, Deserialize)]
pub struct GooglePlayData {
pub json: String,
pub signature: String,
#[serde(rename = "skuDetails")]
pub sku_details: String,
}
impl GooglePlayData {
pub fn from(payload: &str) -> Result<Self> {
Ok(serde_json::from_str(payload)?)
}
pub fn get_uri(&self) -> Result<String> {
let parameters: GooglePlayDataJson = serde_json::from_str(&self.json)?;
log::debug!(
"google purchase/receipt params, package: {}, productId: {}, token: {}",
¶meters.package_name,
¶meters.product_id,
¶meters.token
);
Ok(format!(
"https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{}/purchases/subscriptions/{}/tokens/{}",
parameters.package_name, parameters.product_id, parameters.token
))
}
pub fn get_sku_details(&self) -> Result<SkuDetails> {
Ok(serde_json::from_str(&self.sku_details)?)
}
}
#[derive(Deserialize)]
pub struct SkuDetails {
#[serde(rename = "type")]
pub sku_type: String,
}
#[derive(Serialize, Deserialize)]
pub struct GooglePlayDataJson {
#[serde(rename = "packageName")]
pub package_name: String,
#[serde(rename = "productId")]
pub product_id: String,
#[serde(rename = "purchaseToken")]
pub token: String,
pub acknowledged: bool,
#[serde(rename = "autoRenewing")]
pub auto_renewing: bool,
#[serde(rename = "purchaseTime")]
pub purchase_time: i64,
#[serde(rename = "orderId")]
pub order_id: String,
#[serde(rename = "purchaseState")]
pub purchase_state: i64,
}
pub async fn get_google_receipt_data<S: AsRef<[u8]> + Send>(
receipt: &UnityPurchaseReceipt,
secret: S,
) -> Result<GoogleResponse> {
let data = GooglePlayData::from(&receipt.payload)?;
let uri = data.get_uri()?;
let service_account_key = get_service_account_key(secret)?;
get_google_receipt_data_with_uri(Some(&service_account_key), uri).await
}
pub async fn get_google_receipt_data_with_uri(
service_account_key: Option<&ServiceAccountKey>,
uri: String,
) -> Result<GoogleResponse> {
let https = HttpsConnector::new();
let client = Client::builder().build::<_, hyper::Body>(https);
log::debug!(
"validate google parameters, service_account_key: {}, uri: {}",
service_account_key.map_or(&"key not set".to_string(), |key| &key.client_email),
uri.clone()
);
let req = if let Some(key) = service_account_key {
let authenticator = ServiceAccountAuthenticator::builder(key.clone())
.build()
.await?;
let scopes = &["https://www.googleapis.com/auth/androidpublisher"];
let auth_token = authenticator.token(scopes).await?;
Request::builder()
.method("GET")
.header(
"Authorization",
format!("Bearer {}", auth_token.as_str()).as_str(),
)
.uri(uri)
.body(Body::empty())
} else {
Request::builder()
.method("GET")
.uri(format!("{}/test", uri).as_str())
.body(Body::empty())
}?;
let response = client.request(req).await?;
let buf = body::to_bytes(response).await?;
let string = String::from_utf8(buf.to_vec())?.replace("\n", "");
log::debug!("Google response: {}", &string);
serde_json::from_slice(&buf).map_err(|err| {
error::Error::SerdeError(serde_json::Error::custom(format!(
"Failed to deserialize google response. Was the service account key set? Error message: {}", err)
))
})
}
pub fn validate_google_subscription(response: &GoogleResponse) -> Result<PurchaseResponse> {
let expiry_time = response.expiry_time.parse::<i64>()?;
let now = Utc::now().timestamp_millis();
let valid = expiry_time > now;
log::info!("google receipt verification, valid: {}, now: {}, order_id: {}, expiry_time: {}, price_currency_code: {}, price_amount_micros: {}",
valid,
now,
response.order_id,
response.expiry_time,
response.price_currency_code,
response.price_amount_micros
);
Ok(PurchaseResponse { valid })
}
pub fn get_service_account_key<S: AsRef<[u8]>>(secret: S) -> Result<ServiceAccountKey> {
Ok(serde_json::from_slice(secret.as_ref())?)
}