Skip to main content

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}