Skip to main content

storekit/
purchase_intent.rs

1use core::ffi::c_void;
2use core::ptr;
3use std::ptr::NonNull;
4use std::time::Duration;
5
6use serde::Deserialize;
7
8use crate::error::StoreKitError;
9use crate::ffi;
10use crate::private::{duration_to_timeout_ms, error_from_status, parse_json_ptr};
11use crate::product::{Product, ProductPayload};
12use crate::subscription::{SubscriptionOffer, SubscriptionOfferPayload};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct PurchaseIntent {
16    pub product: Product,
17    pub offer: Option<SubscriptionOffer>,
18}
19
20impl PurchaseIntent {
21    pub fn id(&self) -> &str {
22        &self.product.id
23    }
24
25    pub fn intents() -> Result<PurchaseIntentStream, StoreKitError> {
26        PurchaseIntentStream::new()
27    }
28}
29
30#[derive(Debug)]
31pub struct PurchaseIntentStream {
32    handle: NonNull<c_void>,
33    finished: bool,
34}
35
36impl Drop for PurchaseIntentStream {
37    fn drop(&mut self) {
38        unsafe { ffi::sk_purchase_intent_stream_release(self.handle.as_ptr()) };
39    }
40}
41
42impl PurchaseIntentStream {
43    fn new() -> Result<Self, StoreKitError> {
44        let mut error_message = ptr::null_mut();
45        let handle = unsafe { ffi::sk_purchase_intent_stream_create(&mut error_message) };
46        let handle = NonNull::new(handle)
47            .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
48        Ok(Self {
49            handle,
50            finished: false,
51        })
52    }
53
54    pub const fn is_finished(&self) -> bool {
55        self.finished
56    }
57
58    #[allow(clippy::should_implement_trait)]
59    pub fn next(&mut self) -> Result<Option<PurchaseIntent>, StoreKitError> {
60        self.next_timeout(Duration::from_secs(30))
61    }
62
63    pub fn next_timeout(&mut self, timeout: Duration) -> Result<Option<PurchaseIntent>, StoreKitError> {
64        let mut payload_json = ptr::null_mut();
65        let mut error_message = ptr::null_mut();
66        let status = unsafe {
67            ffi::sk_purchase_intent_stream_next(
68                self.handle.as_ptr(),
69                duration_to_timeout_ms(timeout),
70                &mut payload_json,
71                &mut error_message,
72            )
73        };
74
75        match status {
76            ffi::status::OK => {
77                let payload = unsafe {
78                    parse_json_ptr::<PurchaseIntentPayload>(payload_json, "purchase intent")
79                }?;
80                payload.into_purchase_intent().map(Some)
81            }
82            ffi::status::END_OF_STREAM => {
83                self.finished = true;
84                Ok(None)
85            }
86            ffi::status::TIMED_OUT => Ok(None),
87            _ => Err(unsafe { error_from_status(status, error_message) }),
88        }
89    }
90}
91
92#[derive(Debug, Deserialize)]
93struct PurchaseIntentPayload {
94    product: ProductPayload,
95    offer: Option<SubscriptionOfferPayload>,
96}
97
98impl PurchaseIntentPayload {
99    fn into_purchase_intent(self) -> Result<PurchaseIntent, StoreKitError> {
100        Ok(PurchaseIntent {
101            product: self.product.into_product()?,
102            offer: self.offer.map(SubscriptionOfferPayload::into_subscription_offer),
103        })
104    }
105}