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