storekit/purchase_option.rs
1use serde::{Deserialize, Serialize};
2
3use crate::error::StoreKitError;
4use crate::transaction::{Transaction, TransactionPayload};
5use crate::verification_result::{VerificationResult, VerificationResultPayload};
6
7#[derive(Debug, Clone, PartialEq, Serialize)]
8#[serde(tag = "kind", rename_all = "camelCase")]
9/// Represents options passed to `StoreKit.Product.purchase(options:)`.
10pub enum PurchaseOption {
11 /// Represents the `AppAccountToken` `StoreKit` case.
12 AppAccountToken {
13 /// App account token reported by `StoreKit`.
14 app_account_token: String,
15 },
16 /// Represents the `Quantity` `StoreKit` case.
17 Quantity {
18 /// Value forwarded to `StoreKit` for `quantity`.
19 quantity: i64,
20 },
21 /// Represents the `SimulatesAskToBuyInSandbox` `StoreKit` case.
22 SimulatesAskToBuyInSandbox {
23 /// Value forwarded to `StoreKit` for `simulate_ask_to_buy_in_sandbox`.
24 simulate_ask_to_buy_in_sandbox: bool,
25 },
26 /// Represents the `CustomString` `StoreKit` case.
27 CustomString {
28 /// Value forwarded to `StoreKit` for `key`.
29 key: String,
30 /// Value returned by `StoreKit`.
31 value: String,
32 },
33 /// Represents the `CustomNumber` `StoreKit` case.
34 CustomNumber {
35 /// Value forwarded to `StoreKit` for `key`.
36 key: String,
37 /// Value returned by `StoreKit`.
38 value: f64,
39 },
40 /// Represents the `CustomBool` `StoreKit` case.
41 CustomBool {
42 /// Value forwarded to `StoreKit` for `key`.
43 key: String,
44 /// Value returned by `StoreKit`.
45 value: bool,
46 },
47 /// Represents the `CustomData` `StoreKit` case.
48 CustomData {
49 /// Value forwarded to `StoreKit` for `key`.
50 key: String,
51 /// Value forwarded to `StoreKit` for `value_base64`.
52 value_base64: String,
53 },
54 /// Represents the `PromotionalOfferSignature` `StoreKit` case.
55 PromotionalOfferSignature {
56 /// Value forwarded to `StoreKit` for `offer_id`.
57 offer_id: String,
58 /// Value forwarded to `StoreKit` for `key_id`.
59 key_id: String,
60 /// Value forwarded to `StoreKit` for `nonce`.
61 nonce: String,
62 /// Value forwarded to `StoreKit` for `signature_base64`.
63 signature_base64: String,
64 /// Value forwarded to `StoreKit` for `timestamp`.
65 timestamp: i64,
66 },
67 /// Represents the `PromotionalOfferCompactJws` `StoreKit` case.
68 PromotionalOfferCompactJws {
69 /// Value forwarded to `StoreKit` for `offer_id`.
70 offer_id: String,
71 /// Value forwarded to `StoreKit` for `compact_jws`.
72 compact_jws: String,
73 },
74 /// Represents the `IntroductoryOfferEligibility` `StoreKit` case.
75 IntroductoryOfferEligibility {
76 /// Value forwarded to `StoreKit` for `compact_jws`.
77 compact_jws: String,
78 },
79 /// Represents the `WinBackOffer` `StoreKit` case.
80 WinBackOffer {
81 /// Value forwarded to `StoreKit` for `offer_id`.
82 offer_id: String,
83 },
84 /// Represents the `OnStorefrontChange` `StoreKit` case.
85 OnStorefrontChange {
86 /// Value forwarded to `StoreKit` for `should_continue_purchase`.
87 should_continue_purchase: bool,
88 },
89}
90
91#[allow(clippy::large_enum_variant)]
92#[derive(Debug)]
93/// Represents the result returned by `StoreKit.Product.purchase(options:)`.
94pub enum PurchaseResult {
95 /// The `StoreKit` operation succeeded.
96 Success(VerificationResult<Transaction>),
97 /// The person cancelled the `StoreKit` flow.
98 UserCancelled,
99 /// The `StoreKit` flow is pending further action.
100 Pending,
101}
102
103#[allow(clippy::unsafe_derive_deserialize)]
104#[derive(Debug, Deserialize)]
105pub(crate) struct PurchaseResultPayload {
106 kind: String,
107 #[serde(rename = "verificationResult")]
108 verification_result: Option<VerificationResultPayload<TransactionPayload>>,
109}
110
111impl PurchaseResultPayload {
112 pub(crate) fn into_purchase_result(
113 self,
114 transaction_handle: *mut core::ffi::c_void,
115 ) -> Result<PurchaseResult, StoreKitError> {
116 match self.kind.as_str() {
117 "success" => {
118 let verification_result = self.verification_result.ok_or_else(|| {
119 StoreKitError::Unknown(
120 "StoreKit reported a successful purchase without a verification result"
121 .to_owned(),
122 )
123 })?;
124 let transaction = verification_result.into_result(|payload| {
125 Transaction::from_raw_parts(transaction_handle, payload)
126 })?;
127 Ok(PurchaseResult::Success(transaction))
128 }
129 "userCancelled" => {
130 if !transaction_handle.is_null() {
131 unsafe { crate::ffi::sk_transaction_release(transaction_handle) };
132 }
133 Ok(PurchaseResult::UserCancelled)
134 }
135 "pending" => {
136 if !transaction_handle.is_null() {
137 unsafe { crate::ffi::sk_transaction_release(transaction_handle) };
138 }
139 Ok(PurchaseResult::Pending)
140 }
141 other => {
142 if !transaction_handle.is_null() {
143 unsafe { crate::ffi::sk_transaction_release(transaction_handle) };
144 }
145 Err(StoreKitError::Unknown(format!(
146 "StoreKit returned an unknown purchase result kind '{other}'"
147 )))
148 }
149 }
150 }
151}