1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Default, Deserialize, Serialize)]
4#[serde(rename_all = "camelCase")]
5pub struct InitializeResponse {
6 pub success: bool,
7}
8
9#[derive(Debug, Deserialize, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct GetProductsRequest {
12 pub product_ids: Vec<String>,
13 #[serde(default = "default_product_type")]
14 pub product_type: String,
15}
16
17fn default_product_type() -> String {
18 "subs".to_string()
19}
20
21#[derive(Debug, Clone, Deserialize, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct PricingPhase {
24 pub formatted_price: String,
25 pub price_currency_code: String,
26 pub price_amount_micros: i64,
27 pub billing_period: String,
28 pub billing_cycle_count: i32,
29 pub recurrence_mode: i32,
30}
31
32#[derive(Debug, Clone, Deserialize, Serialize)]
33#[serde(rename_all = "camelCase")]
34pub struct SubscriptionOffer {
35 pub offer_token: String,
36 pub base_plan_id: String,
37 pub offer_id: Option<String>,
38 pub pricing_phases: Vec<PricingPhase>,
39}
40
41#[derive(Debug, Clone, Deserialize, Serialize)]
42#[serde(rename_all = "camelCase")]
43pub struct Product {
44 pub product_id: String,
45 pub title: String,
46 pub description: String,
47 pub product_type: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub formatted_price: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub price_currency_code: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub price_amount_micros: Option<i64>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub subscription_offer_details: Option<Vec<SubscriptionOffer>>,
56}
57
58#[derive(Debug, Clone, Deserialize, Serialize)]
59#[serde(rename_all = "camelCase")]
60pub struct GetProductsResponse {
61 pub products: Vec<Product>,
62}
63
64#[derive(Debug, Clone, Deserialize, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub struct PurchaseOptions {
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub offer_token: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub obfuscated_account_id: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub obfuscated_profile_id: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub app_account_token: Option<String>,
75}
76
77#[derive(Debug, Deserialize, Serialize)]
78#[serde(rename_all = "camelCase")]
79pub struct PurchaseRequest {
80 pub product_id: String,
81 #[serde(default = "default_product_type")]
82 pub product_type: String,
83 #[serde(flatten)]
84 pub options: Option<PurchaseOptions>,
85}
86
87#[derive(Debug, Clone, Deserialize, Serialize)]
88#[serde(rename_all = "camelCase")]
89pub struct Purchase {
90 pub order_id: Option<String>,
91 pub package_name: String,
92 pub product_id: String,
93 pub purchase_time: i64,
94 pub purchase_token: String,
95 pub purchase_state: PurchaseStateValue,
96 pub is_auto_renewing: bool,
97 pub is_acknowledged: bool,
98 pub original_json: String,
99 pub signature: String,
100 pub original_id: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub jws_representation: Option<String>,
103}
104
105#[derive(Debug, Clone, Deserialize, Serialize)]
106#[serde(rename_all = "camelCase")]
107pub struct RestorePurchasesRequest {
108 #[serde(default = "default_product_type")]
109 pub product_type: String,
110}
111
112#[derive(Debug, Clone, Deserialize, Serialize)]
113#[serde(rename_all = "camelCase")]
114pub struct RestorePurchasesResponse {
115 pub purchases: Vec<Purchase>,
116}
117
118#[derive(Debug, Clone, Deserialize, Serialize)]
119#[serde(rename_all = "camelCase")]
120pub struct PurchaseHistoryRecord {
121 pub product_id: String,
122 pub purchase_time: i64,
123 pub purchase_token: String,
124 pub quantity: i32,
125 pub original_json: String,
126 pub signature: String,
127}
128
129#[derive(Debug, Clone, Deserialize, Serialize)]
130#[serde(rename_all = "camelCase")]
131pub struct GetPurchaseHistoryResponse {
132 pub history: Vec<PurchaseHistoryRecord>,
133}
134
135#[derive(Debug, Deserialize, Serialize)]
136#[serde(rename_all = "camelCase")]
137pub struct AcknowledgePurchaseRequest {
138 pub purchase_token: String,
139}
140
141#[derive(Debug, Clone, Deserialize, Serialize)]
142#[serde(rename_all = "camelCase")]
143pub struct AcknowledgePurchaseResponse {
144 pub success: bool,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq)]
149pub enum PurchaseStateValue {
150 Purchased = 0,
151 Canceled = 1,
152 Pending = 2,
153}
154
155impl Serialize for PurchaseStateValue {
156 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157 where
158 S: serde::Serializer,
159 {
160 serializer.serialize_i32(*self as i32)
161 }
162}
163
164impl<'de> Deserialize<'de> for PurchaseStateValue {
165 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
166 where
167 D: serde::Deserializer<'de>,
168 {
169 let value = i32::deserialize(deserializer)?;
170 match value {
171 0 => Ok(PurchaseStateValue::Purchased),
172 1 => Ok(PurchaseStateValue::Canceled),
173 2 => Ok(PurchaseStateValue::Pending),
174 _ => Err(serde::de::Error::custom(format!(
175 "Invalid purchase state: {value}"
176 ))),
177 }
178 }
179}
180
181#[derive(Debug, Deserialize, Serialize)]
182#[serde(rename_all = "camelCase")]
183pub struct GetProductStatusRequest {
184 pub product_id: String,
185 #[serde(default = "default_product_type")]
186 pub product_type: String,
187}
188
189#[derive(Debug, Clone, Deserialize, Serialize)]
190#[serde(rename_all = "camelCase")]
191pub struct ProductStatus {
192 pub product_id: String,
193 pub is_owned: bool,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub purchase_state: Option<PurchaseStateValue>,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub purchase_time: Option<i64>,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub expiration_time: Option<i64>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub is_auto_renewing: Option<bool>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub is_acknowledged: Option<bool>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub purchase_token: Option<String>,
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_default_product_type() {
214 assert_eq!(default_product_type(), "subs");
215 }
216
217 #[test]
218 fn test_purchase_state_value_serialize() {
219 assert_eq!(
220 serde_json::to_string(&PurchaseStateValue::Purchased)
221 .expect("Failed to serialize Purchased state"),
222 "0"
223 );
224 assert_eq!(
225 serde_json::to_string(&PurchaseStateValue::Canceled)
226 .expect("Failed to serialize Canceled state"),
227 "1"
228 );
229 assert_eq!(
230 serde_json::to_string(&PurchaseStateValue::Pending)
231 .expect("Failed to serialize Pending state"),
232 "2"
233 );
234 }
235
236 #[test]
237 fn test_purchase_state_value_deserialize() {
238 assert_eq!(
239 serde_json::from_str::<PurchaseStateValue>("0")
240 .expect("Failed to deserialize Purchased state"),
241 PurchaseStateValue::Purchased
242 );
243 assert_eq!(
244 serde_json::from_str::<PurchaseStateValue>("1")
245 .expect("Failed to deserialize Canceled state"),
246 PurchaseStateValue::Canceled
247 );
248 assert_eq!(
249 serde_json::from_str::<PurchaseStateValue>("2")
250 .expect("Failed to deserialize Pending state"),
251 PurchaseStateValue::Pending
252 );
253 }
254
255 #[test]
256 fn test_purchase_state_value_deserialize_invalid() {
257 let result = serde_json::from_str::<PurchaseStateValue>("3");
258 assert!(result.is_err());
259 let err = result
260 .expect_err("Expected error for invalid state")
261 .to_string();
262 assert!(err.contains("Invalid purchase state: 3"));
263 }
264
265 #[test]
266 fn test_purchase_state_value_roundtrip() {
267 for state in [
268 PurchaseStateValue::Purchased,
269 PurchaseStateValue::Canceled,
270 PurchaseStateValue::Pending,
271 ] {
272 let serialized =
273 serde_json::to_string(&state).expect("Failed to serialize PurchaseStateValue");
274 let deserialized: PurchaseStateValue = serde_json::from_str(&serialized)
275 .expect("Failed to deserialize PurchaseStateValue");
276 assert_eq!(state, deserialized);
277 }
278 }
279
280 #[test]
281 fn test_initialize_response_default() {
282 let response = InitializeResponse::default();
283 assert!(!response.success);
284 }
285
286 #[test]
287 fn test_initialize_response_serde() {
288 let response = InitializeResponse { success: true };
289 let json =
290 serde_json::to_string(&response).expect("Failed to serialize InitializeResponse");
291 assert_eq!(json, r#"{"success":true}"#);
292
293 let deserialized: InitializeResponse =
294 serde_json::from_str(&json).expect("Failed to deserialize InitializeResponse");
295 assert!(deserialized.success);
296 }
297
298 #[test]
299 fn test_get_products_request_default_product_type() {
300 let json = r#"{"productIds":["product1","product2"]}"#;
301 let request: GetProductsRequest =
302 serde_json::from_str(json).expect("Failed to deserialize GetProductsRequest");
303 assert_eq!(request.product_ids, vec!["product1", "product2"]);
304 assert_eq!(request.product_type, "subs");
305 }
306
307 #[test]
308 fn test_get_products_request_explicit_product_type() {
309 let json = r#"{"productIds":["product1"],"productType":"inapp"}"#;
310 let request: GetProductsRequest =
311 serde_json::from_str(json).expect("Failed to deserialize GetProductsRequest");
312 assert_eq!(request.product_type, "inapp");
313 }
314
315 #[test]
316 fn test_product_optional_fields_skip_serializing() {
317 let product = Product {
318 product_id: "test".to_string(),
319 title: "Test Product".to_string(),
320 description: "A test product".to_string(),
321 product_type: "inapp".to_string(),
322 formatted_price: None,
323 price_currency_code: None,
324 price_amount_micros: None,
325 subscription_offer_details: None,
326 };
327 let json = serde_json::to_string(&product).expect("Failed to serialize Product");
328 assert!(!json.contains("formattedPrice"));
329 assert!(!json.contains("priceCurrencyCode"));
330 assert!(!json.contains("priceAmountMicros"));
331 assert!(!json.contains("subscriptionOfferDetails"));
332 }
333
334 #[test]
335 fn test_product_with_optional_fields() {
336 let product = Product {
337 product_id: "test".to_string(),
338 title: "Test Product".to_string(),
339 description: "A test product".to_string(),
340 product_type: "inapp".to_string(),
341 formatted_price: Some("$9.99".to_string()),
342 price_currency_code: Some("USD".to_string()),
343 price_amount_micros: Some(9990000),
344 subscription_offer_details: None,
345 };
346 let json = serde_json::to_string(&product).expect("Failed to serialize Product");
347 assert!(json.contains(r#""formattedPrice":"$9.99""#));
348 assert!(json.contains(r#""priceCurrencyCode":"USD""#));
349 assert!(json.contains(r#""priceAmountMicros":9990000"#));
350 }
351
352 #[test]
353 fn test_purchase_serde_roundtrip() {
354 let purchase = Purchase {
355 order_id: Some("order123".to_string()),
356 package_name: "com.example.app".to_string(),
357 product_id: "product1".to_string(),
358 purchase_time: 1700000000000,
359 purchase_token: "token123".to_string(),
360 purchase_state: PurchaseStateValue::Purchased,
361 is_auto_renewing: true,
362 is_acknowledged: false,
363 original_json: "{}".to_string(),
364 signature: "sig".to_string(),
365 original_id: None,
366 jws_representation: Some("test_jws".to_string()),
367 };
368
369 let json = serde_json::to_string(&purchase).expect("Failed to serialize Purchase");
370 let deserialized: Purchase =
371 serde_json::from_str(&json).expect("Failed to deserialize Purchase");
372
373 assert_eq!(deserialized.order_id, purchase.order_id);
374 assert_eq!(deserialized.product_id, purchase.product_id);
375 assert_eq!(deserialized.purchase_time, purchase.purchase_time);
376 assert_eq!(deserialized.purchase_state, purchase.purchase_state);
377 assert_eq!(deserialized.is_auto_renewing, purchase.is_auto_renewing);
378 }
379
380 #[test]
381 fn test_pricing_phase_serde() {
382 let phase = PricingPhase {
383 formatted_price: "$4.99".to_string(),
384 price_currency_code: "USD".to_string(),
385 price_amount_micros: 4990000,
386 billing_period: "P1M".to_string(),
387 billing_cycle_count: 1,
388 recurrence_mode: 1,
389 };
390
391 let json = serde_json::to_string(&phase).expect("Failed to serialize PricingPhase");
392 assert!(json.contains(r#""formattedPrice":"$4.99""#));
393 assert!(json.contains(r#""billingPeriod":"P1M""#));
394
395 let deserialized: PricingPhase =
396 serde_json::from_str(&json).expect("Failed to deserialize PricingPhase");
397 assert_eq!(deserialized.price_amount_micros, 4990000);
398 }
399
400 #[test]
401 fn test_subscription_offer_serde() {
402 let offer = SubscriptionOffer {
403 offer_token: "token123".to_string(),
404 base_plan_id: "base_plan".to_string(),
405 offer_id: Some("offer1".to_string()),
406 pricing_phases: vec![PricingPhase {
407 formatted_price: "$9.99".to_string(),
408 price_currency_code: "USD".to_string(),
409 price_amount_micros: 9990000,
410 billing_period: "P1M".to_string(),
411 billing_cycle_count: 0,
412 recurrence_mode: 1,
413 }],
414 };
415
416 let json = serde_json::to_string(&offer).expect("Failed to serialize SubscriptionOffer");
417 let deserialized: SubscriptionOffer =
418 serde_json::from_str(&json).expect("Failed to deserialize SubscriptionOffer");
419 assert_eq!(deserialized.offer_token, "token123");
420 assert_eq!(deserialized.pricing_phases.len(), 1);
421 }
422
423 #[test]
424 fn test_purchase_options_flatten() {
425 let json = r#"{"productId":"prod1","offerToken":"token","obfuscatedAccountId":"acc123"}"#;
426 let request: PurchaseRequest =
427 serde_json::from_str(json).expect("Failed to deserialize PurchaseRequest");
428
429 assert_eq!(request.product_id, "prod1");
430 assert_eq!(request.product_type, "subs"); let opts = request
432 .options
433 .expect("Expected PurchaseOptions to be present");
434 assert_eq!(opts.offer_token, Some("token".to_string()));
435 assert_eq!(opts.obfuscated_account_id, Some("acc123".to_string()));
436 }
437
438 #[test]
439 fn test_restore_purchases_request_default() {
440 let json = r#"{}"#;
441 let request: RestorePurchasesRequest =
442 serde_json::from_str(json).expect("Failed to deserialize RestorePurchasesRequest");
443 assert_eq!(request.product_type, "subs");
444 }
445
446 #[test]
447 fn test_product_status_optional_fields() {
448 let status = ProductStatus {
449 product_id: "prod1".to_string(),
450 is_owned: false,
451 purchase_state: None,
452 purchase_time: None,
453 expiration_time: None,
454 is_auto_renewing: None,
455 is_acknowledged: None,
456 purchase_token: None,
457 };
458
459 let json = serde_json::to_string(&status).expect("Failed to serialize ProductStatus");
460 assert!(!json.contains("purchaseState"));
462 assert!(!json.contains("purchaseTime"));
463 assert!(!json.contains("expirationTime"));
464 }
465
466 #[test]
467 fn test_product_status_with_values() {
468 let status = ProductStatus {
469 product_id: "prod1".to_string(),
470 is_owned: true,
471 purchase_state: Some(PurchaseStateValue::Purchased),
472 purchase_time: Some(1700000000000),
473 expiration_time: Some(1703000000000),
474 is_auto_renewing: Some(true),
475 is_acknowledged: Some(true),
476 purchase_token: Some("token123".to_string()),
477 };
478
479 let json = serde_json::to_string(&status).expect("Failed to serialize ProductStatus");
480 assert!(json.contains(r#""isOwned":true"#));
481 assert!(json.contains(r#""purchaseState":0"#));
482 assert!(json.contains(r#""isAutoRenewing":true"#));
483 }
484
485 #[test]
486 fn test_acknowledge_purchase_request_serde() {
487 let request = AcknowledgePurchaseRequest {
488 purchase_token: "token123".to_string(),
489 };
490 let json = serde_json::to_string(&request)
491 .expect("Failed to serialize AcknowledgePurchaseRequest");
492 assert_eq!(json, r#"{"purchaseToken":"token123"}"#);
493 }
494
495 #[test]
496 fn test_get_product_status_request_serde() {
497 let json = r#"{"productId":"prod1"}"#;
498 let request: GetProductStatusRequest =
499 serde_json::from_str(json).expect("Failed to deserialize GetProductStatusRequest");
500 assert_eq!(request.product_id, "prod1");
501 assert_eq!(request.product_type, "subs"); }
503
504 #[test]
505 fn test_purchase_history_record_serde() {
506 let record = PurchaseHistoryRecord {
507 product_id: "prod1".to_string(),
508 purchase_time: 1700000000000,
509 purchase_token: "token".to_string(),
510 quantity: 1,
511 original_json: "{}".to_string(),
512 signature: "sig".to_string(),
513 };
514
515 let json =
516 serde_json::to_string(&record).expect("Failed to serialize PurchaseHistoryRecord");
517 let deserialized: PurchaseHistoryRecord =
518 serde_json::from_str(&json).expect("Failed to deserialize PurchaseHistoryRecord");
519 assert_eq!(deserialized.quantity, 1);
520 }
521}