Skip to main content

storekit/
purchase_option.rs

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