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}