Skip to main content

storekit/
external_purchase.rs

1use core::ptr;
2
3use serde::Deserialize;
4
5use crate::error::StoreKitError;
6use crate::ffi;
7use crate::private::{
8    cstring_from_str, error_from_status, parse_json_ptr, parse_optional_json_ptr,
9};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12/// Represents the result returned by the `StoreKit` external purchase notice sheet.
13pub enum ExternalPurchaseNoticeResult {
14    /// The `StoreKit` flow continued.
15    Continued,
16    /// The person cancelled the `StoreKit` flow.
17    Cancelled,
18    /// The notice continued and `StoreKit` returned an external purchase token.
19    ContinuedWithExternalPurchaseToken {
20        /// Token returned by `StoreKit`.
21        token: String,
22    },
23    /// Preserves an unrecognized `StoreKit` case.
24    Unknown(String),
25}
26
27impl ExternalPurchaseNoticeResult {
28    /// Returns the raw `StoreKit` string for this notice result.
29    pub fn as_str(&self) -> &str {
30        match self {
31            Self::Continued => "continued",
32            Self::Cancelled => "cancelled",
33            Self::ContinuedWithExternalPurchaseToken { .. } => "continuedWithExternalPurchaseToken",
34            Self::Unknown(value) => value.as_str(),
35        }
36    }
37}
38
39#[derive(Debug, Clone, Copy, Default)]
40/// Helpers backed by `StoreKit` external purchase APIs.
41pub struct ExternalPurchase;
42
43impl ExternalPurchase {
44    /// Returns whether `StoreKit` can present the external purchase notice sheet.
45    pub fn can_present() -> Result<bool, StoreKitError> {
46        let mut raw_value = 0;
47        let mut error_message = ptr::null_mut();
48        let status =
49            unsafe { ffi::sk_external_purchase_can_present(&mut raw_value, &mut error_message) };
50        if status == ffi::status::OK {
51            Ok(raw_value != 0)
52        } else {
53            Err(unsafe { error_from_status(status, error_message) })
54        }
55    }
56
57    /// Presents the `StoreKit` external purchase notice sheet.
58    pub fn present_notice_sheet() -> Result<ExternalPurchaseNoticeResult, StoreKitError> {
59        let mut result_json = ptr::null_mut();
60        let mut error_message = ptr::null_mut();
61        let status = unsafe {
62            ffi::sk_external_purchase_present_notice_result_json(
63                &mut result_json,
64                &mut error_message,
65            )
66        };
67        if status != ffi::status::OK {
68            return Err(unsafe { error_from_status(status, error_message) });
69        }
70        let payload = unsafe {
71            parse_json_ptr::<ExternalPurchaseNoticeResultPayload>(
72                result_json,
73                "external purchase notice result",
74            )
75        }?;
76        Ok(payload.into_notice_result())
77    }
78}
79
80#[derive(Debug, Clone, Copy, Default)]
81/// Helpers backed by `StoreKit` external purchase link APIs.
82pub struct ExternalPurchaseLink;
83
84impl ExternalPurchaseLink {
85    /// Returns whether `StoreKit` can open the external purchase link.
86    pub fn can_open() -> Result<bool, StoreKitError> {
87        let mut raw_value = 0;
88        let mut error_message = ptr::null_mut();
89        let status =
90            unsafe { ffi::sk_external_purchase_link_can_open(&mut raw_value, &mut error_message) };
91        if status == ffi::status::OK {
92            Ok(raw_value != 0)
93        } else {
94            Err(unsafe { error_from_status(status, error_message) })
95        }
96    }
97
98    /// Returns the eligible external purchase URLs reported by `StoreKit`.
99    pub fn eligible_urls() -> Result<Option<Vec<String>>, StoreKitError> {
100        let mut urls_json = ptr::null_mut();
101        let mut error_message = ptr::null_mut();
102        let status = unsafe {
103            ffi::sk_external_purchase_link_eligible_urls_json(&mut urls_json, &mut error_message)
104        };
105        if status != ffi::status::OK {
106            return Err(unsafe { error_from_status(status, error_message) });
107        }
108        unsafe {
109            parse_optional_json_ptr::<Vec<String>>(urls_json, "eligible external purchase URLs")
110        }
111    }
112
113    /// Opens the default `StoreKit` external purchase link.
114    pub fn open() -> Result<(), StoreKitError> {
115        let mut error_message = ptr::null_mut();
116        let status = unsafe { ffi::sk_external_purchase_link_open(&mut error_message) };
117        if status == ffi::status::OK {
118            Ok(())
119        } else {
120            Err(unsafe { error_from_status(status, error_message) })
121        }
122    }
123
124    /// Opens the supplied `StoreKit`-eligible external purchase URL.
125    pub fn open_url(url: &str) -> Result<(), StoreKitError> {
126        let url = cstring_from_str(url, "external purchase URL")?;
127        let mut error_message = ptr::null_mut();
128        let status =
129            unsafe { ffi::sk_external_purchase_link_open_url(url.as_ptr(), &mut error_message) };
130        if status == ffi::status::OK {
131            Ok(())
132        } else {
133            Err(unsafe { error_from_status(status, error_message) })
134        }
135    }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139/// Represents the notice type passed to `StoreKit` external purchase custom link APIs.
140pub enum ExternalPurchaseCustomLinkNoticeType {
141    /// Represents the `WithinApp` `StoreKit` case.
142    WithinApp,
143    /// Represents the `Browser` `StoreKit` case.
144    Browser,
145    /// Preserves an unrecognized `StoreKit` case.
146    Unknown(i64),
147}
148
149impl ExternalPurchaseCustomLinkNoticeType {
150    /// Returns the raw `StoreKit` integer for this notice type.
151    pub const fn as_raw(&self) -> i64 {
152        match self {
153            Self::WithinApp => 0,
154            Self::Browser => 1,
155            Self::Unknown(value) => *value,
156        }
157    }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
161/// Represents the result returned by `StoreKit` external purchase custom link UI.
162pub enum ExternalPurchaseCustomLinkNoticeResult {
163    /// The person cancelled the `StoreKit` flow.
164    Cancelled,
165    /// The `StoreKit` flow continued.
166    Continued,
167    /// Preserves an unrecognized `StoreKit` case.
168    Unknown(String),
169}
170
171impl ExternalPurchaseCustomLinkNoticeResult {
172    /// Returns the raw `StoreKit` string for this custom-link notice result.
173    pub fn as_str(&self) -> &str {
174        match self {
175            Self::Cancelled => "cancelled",
176            Self::Continued => "continued",
177            Self::Unknown(value) => value.as_str(),
178        }
179    }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
183/// Wraps a token returned by `StoreKit` external purchase custom link APIs.
184pub struct ExternalPurchaseCustomLinkToken {
185    /// Value returned by `StoreKit`.
186    pub value: String,
187}
188
189#[derive(Debug, Clone, Copy, Default)]
190/// Helpers backed by `StoreKit` external purchase custom link APIs.
191pub struct ExternalPurchaseCustomLink;
192
193impl ExternalPurchaseCustomLink {
194    /// Returns whether `StoreKit` reports that the custom link flow is eligible.
195    pub fn is_eligible() -> Result<bool, StoreKitError> {
196        let mut raw_value = 0;
197        let mut error_message = ptr::null_mut();
198        let status = unsafe {
199            ffi::sk_external_purchase_custom_link_is_eligible(&mut raw_value, &mut error_message)
200        };
201        if status == ffi::status::OK {
202            Ok(raw_value != 0)
203        } else {
204            Err(unsafe { error_from_status(status, error_message) })
205        }
206    }
207
208    /// Presents the `StoreKit` custom-link notice UI.
209    pub fn show_notice(
210        notice_type: ExternalPurchaseCustomLinkNoticeType,
211    ) -> Result<ExternalPurchaseCustomLinkNoticeResult, StoreKitError> {
212        let raw_notice_type = i32::try_from(notice_type.as_raw()).map_err(|_| {
213            StoreKitError::InvalidArgument(format!(
214                "external purchase custom link notice type '{}' does not fit in i32",
215                notice_type.as_raw()
216            ))
217        })?;
218        let mut result_json = ptr::null_mut();
219        let mut error_message = ptr::null_mut();
220        let status = unsafe {
221            ffi::sk_external_purchase_custom_link_show_notice_result_json(
222                raw_notice_type,
223                &mut result_json,
224                &mut error_message,
225            )
226        };
227        if status != ffi::status::OK {
228            return Err(unsafe { error_from_status(status, error_message) });
229        }
230        let payload = unsafe {
231            parse_json_ptr::<ExternalPurchaseCustomLinkNoticeResultPayload>(
232                result_json,
233                "external purchase custom link notice result",
234            )
235        }?;
236        Ok(payload.into_notice_result())
237    }
238
239    /// Fetches a custom-link token from `StoreKit`.
240    pub fn token(
241        token_type: &str,
242    ) -> Result<Option<ExternalPurchaseCustomLinkToken>, StoreKitError> {
243        let token_type = cstring_from_str(token_type, "external purchase custom link token type")?;
244        let mut token_json = ptr::null_mut();
245        let mut error_message = ptr::null_mut();
246        let status = unsafe {
247            ffi::sk_external_purchase_custom_link_token_json(
248                token_type.as_ptr(),
249                &mut token_json,
250                &mut error_message,
251            )
252        };
253        if status != ffi::status::OK {
254            return Err(unsafe { error_from_status(status, error_message) });
255        }
256        unsafe {
257            parse_optional_json_ptr::<ExternalPurchaseCustomLinkTokenPayload>(
258                token_json,
259                "external purchase custom link token",
260            )
261        }
262        .map(|payload| {
263            payload.map(|payload| ExternalPurchaseCustomLinkToken {
264                value: payload.value,
265            })
266        })
267    }
268}
269
270#[derive(Debug, Deserialize)]
271struct ExternalPurchaseNoticeResultPayload {
272    kind: String,
273    token: Option<String>,
274}
275
276impl ExternalPurchaseNoticeResultPayload {
277    fn into_notice_result(self) -> ExternalPurchaseNoticeResult {
278        match self.kind.as_str() {
279            "continued" => ExternalPurchaseNoticeResult::Continued,
280            "cancelled" => ExternalPurchaseNoticeResult::Cancelled,
281            "continuedWithExternalPurchaseToken" => {
282                ExternalPurchaseNoticeResult::ContinuedWithExternalPurchaseToken {
283                    token: self.token.unwrap_or_default(),
284                }
285            }
286            other => ExternalPurchaseNoticeResult::Unknown(other.to_owned()),
287        }
288    }
289}
290
291#[derive(Debug, Deserialize)]
292struct ExternalPurchaseCustomLinkNoticeResultPayload {
293    kind: String,
294}
295
296impl ExternalPurchaseCustomLinkNoticeResultPayload {
297    fn into_notice_result(self) -> ExternalPurchaseCustomLinkNoticeResult {
298        match self.kind.as_str() {
299            "cancelled" => ExternalPurchaseCustomLinkNoticeResult::Cancelled,
300            "continued" => ExternalPurchaseCustomLinkNoticeResult::Continued,
301            other => ExternalPurchaseCustomLinkNoticeResult::Unknown(other.to_owned()),
302        }
303    }
304}
305
306#[derive(Debug, Deserialize)]
307struct ExternalPurchaseCustomLinkTokenPayload {
308    value: String,
309}