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