storekit/
purchase_intent.rs1use 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 {
17 pub product: Product,
19 pub offer: Option<SubscriptionOffer>,
21}
22
23impl PurchaseIntent {
24 pub fn id(&self) -> &str {
26 &self.product.id
27 }
28
29 pub fn intents() -> Result<PurchaseIntentStream, StoreKitError> {
31 PurchaseIntentStream::new()
32 }
33}
34
35#[derive(Debug)]
36pub struct PurchaseIntentStream {
38 handle: NonNull<c_void>,
39 finished: bool,
40}
41
42impl Drop for PurchaseIntentStream {
43 fn drop(&mut self) {
44 unsafe { ffi::sk_purchase_intent_stream_release(self.handle.as_ptr()) };
45 }
46}
47
48impl PurchaseIntentStream {
49 fn new() -> Result<Self, StoreKitError> {
50 let mut error_message = ptr::null_mut();
51 let handle = unsafe { ffi::sk_purchase_intent_stream_create(&mut error_message) };
52 let handle = NonNull::new(handle)
53 .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
54 Ok(Self {
55 handle,
56 finished: false,
57 })
58 }
59
60 pub const fn is_finished(&self) -> bool {
62 self.finished
63 }
64
65 #[allow(clippy::should_implement_trait)]
66 pub fn next(&mut self) -> Result<Option<PurchaseIntent>, StoreKitError> {
68 self.next_timeout(Duration::from_secs(30))
69 }
70
71 pub fn next_timeout(
73 &mut self,
74 timeout: Duration,
75 ) -> Result<Option<PurchaseIntent>, StoreKitError> {
76 let mut payload_json = ptr::null_mut();
77 let mut error_message = ptr::null_mut();
78 let status = unsafe {
79 ffi::sk_purchase_intent_stream_next(
80 self.handle.as_ptr(),
81 duration_to_timeout_ms(timeout),
82 &mut payload_json,
83 &mut error_message,
84 )
85 };
86
87 match status {
88 ffi::status::OK => {
89 let payload = unsafe {
90 parse_json_ptr::<PurchaseIntentPayload>(payload_json, "purchase intent")
91 }?;
92 payload.into_purchase_intent().map(Some)
93 }
94 ffi::status::END_OF_STREAM => {
95 self.finished = true;
96 Ok(None)
97 }
98 ffi::status::TIMED_OUT => Ok(None),
99 _ => Err(unsafe { error_from_status(status, error_message) }),
100 }
101 }
102}
103
104#[derive(Debug, Deserialize)]
105struct PurchaseIntentPayload {
106 product: ProductPayload,
107 offer: Option<SubscriptionOfferPayload>,
108}
109
110impl PurchaseIntentPayload {
111 fn into_purchase_intent(self) -> Result<PurchaseIntent, StoreKitError> {
112 Ok(PurchaseIntent {
113 product: self.product.into_product()?,
114 offer: self
115 .offer
116 .map(SubscriptionOfferPayload::into_subscription_offer),
117 })
118 }
119}