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