Skip to main content

storekit/
subscription_info.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};
10use crate::renewal_info::{RenewalInfo, RenewalInfoPayload};
11use crate::renewal_state::RenewalState;
12use crate::subscription::{
13    SubscriptionOffer, SubscriptionOfferPayload, SubscriptionPeriod, SubscriptionPeriodPayload,
14};
15use crate::transaction::{Transaction, TransactionPayload};
16use crate::verification_result::{VerificationResult, VerificationResultPayload};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct SubscriptionInfo {
20    pub introductory_offer: Option<SubscriptionOffer>,
21    pub promotional_offers: Vec<SubscriptionOffer>,
22    pub win_back_offers: Vec<SubscriptionOffer>,
23    pub subscription_group_id: String,
24    pub subscription_period: SubscriptionPeriod,
25    pub group_level: Option<i64>,
26    pub group_display_name: Option<String>,
27}
28
29impl SubscriptionInfo {
30    pub fn is_eligible_for_intro_offer(&self) -> Result<bool, StoreKitError> {
31        Self::is_eligible_for_intro_offer_for(&self.subscription_group_id)
32    }
33
34    pub fn is_eligible_for_intro_offer_for(group_id: &str) -> Result<bool, StoreKitError> {
35        let group_id = cstring_from_str(group_id, "subscription group id")?;
36        let mut raw_value = 0;
37        let mut error_message = ptr::null_mut();
38        let status = unsafe {
39            ffi::sk_subscription_info_is_eligible_for_intro_offer(
40                group_id.as_ptr(),
41                &mut raw_value,
42                &mut error_message,
43            )
44        };
45        if status == ffi::status::OK {
46            Ok(raw_value != 0)
47        } else {
48            Err(unsafe { error_from_status(status, error_message) })
49        }
50    }
51
52    pub fn status(&self) -> Result<Vec<SubscriptionStatus>, StoreKitError> {
53        Self::status_for(&self.subscription_group_id)
54    }
55
56    pub fn status_for(group_id: &str) -> Result<Vec<SubscriptionStatus>, StoreKitError> {
57        let group_id = cstring_from_str(group_id, "subscription group id")?;
58        let mut statuses_json = ptr::null_mut();
59        let mut error_message = ptr::null_mut();
60        let status = unsafe {
61            ffi::sk_subscription_info_statuses_json(
62                group_id.as_ptr(),
63                &mut statuses_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 payloads = unsafe {
71            parse_json_ptr::<Vec<SubscriptionStatusPayload>>(statuses_json, "subscription statuses")
72        }?;
73        payloads
74            .into_iter()
75            .map(SubscriptionStatusPayload::into_subscription_status)
76            .collect::<Result<Vec<_>, _>>()
77    }
78
79    pub fn status_for_transaction(
80        transaction_id: u64,
81    ) -> Result<Option<SubscriptionStatus>, StoreKitError> {
82        let transaction_id = cstring_from_str(&transaction_id.to_string(), "transaction id")?;
83        let mut status_json = ptr::null_mut();
84        let mut error_message = ptr::null_mut();
85        let status = unsafe {
86            ffi::sk_subscription_info_status_for_transaction(
87                transaction_id.as_ptr(),
88                &mut status_json,
89                &mut error_message,
90            )
91        };
92        if status != ffi::status::OK {
93            return Err(unsafe { error_from_status(status, error_message) });
94        }
95        unsafe {
96            parse_optional_json_ptr::<SubscriptionStatusPayload>(
97                status_json,
98                "subscription status for transaction",
99            )
100        }
101        .and_then(|payload| {
102            payload
103                .map(SubscriptionStatusPayload::into_subscription_status)
104                .transpose()
105        })
106    }
107}
108
109#[derive(Debug, Clone)]
110pub struct SubscriptionStatus {
111    pub state: RenewalState,
112    pub transaction: VerificationResult<Transaction>,
113    pub renewal_info: VerificationResult<RenewalInfo>,
114}
115
116#[derive(Debug, Deserialize)]
117pub(crate) struct SubscriptionInfoPayload {
118    #[serde(rename = "introductoryOffer")]
119    introductory_offer: Option<SubscriptionOfferPayload>,
120    #[serde(rename = "promotionalOffers")]
121    promotional_offers: Vec<SubscriptionOfferPayload>,
122    #[serde(rename = "winBackOffers")]
123    win_back_offers: Vec<SubscriptionOfferPayload>,
124    #[serde(rename = "subscriptionGroupID")]
125    subscription_group_id: String,
126    #[serde(rename = "subscriptionPeriod")]
127    subscription_period: SubscriptionPeriodPayload,
128    #[serde(rename = "groupLevel")]
129    group_level: Option<i64>,
130    #[serde(rename = "groupDisplayName")]
131    group_display_name: Option<String>,
132}
133
134impl SubscriptionInfoPayload {
135    pub(crate) fn into_subscription_info(self) -> SubscriptionInfo {
136        SubscriptionInfo {
137            introductory_offer: self
138                .introductory_offer
139                .map(SubscriptionOfferPayload::into_subscription_offer),
140            promotional_offers: self
141                .promotional_offers
142                .into_iter()
143                .map(SubscriptionOfferPayload::into_subscription_offer)
144                .collect(),
145            win_back_offers: self
146                .win_back_offers
147                .into_iter()
148                .map(SubscriptionOfferPayload::into_subscription_offer)
149                .collect(),
150            subscription_group_id: self.subscription_group_id,
151            subscription_period: self.subscription_period.into_subscription_period(),
152            group_level: self.group_level,
153            group_display_name: self.group_display_name,
154        }
155    }
156}
157
158#[derive(Debug, Deserialize)]
159pub(crate) struct SubscriptionStatusPayload {
160    state: String,
161    transaction: VerificationResultPayload<TransactionPayload>,
162    #[serde(rename = "renewalInfo")]
163    renewal_info: VerificationResultPayload<RenewalInfoPayload>,
164}
165
166impl SubscriptionStatusPayload {
167    pub(crate) fn into_subscription_status(self) -> Result<SubscriptionStatus, StoreKitError> {
168        Ok(SubscriptionStatus {
169            state: RenewalState::from_raw(self.state),
170            transaction: self
171                .transaction
172                .into_result(Transaction::from_snapshot_payload)?,
173            renewal_info: self
174                .renewal_info
175                .into_result(|payload| Ok(payload.into_renewal_info()))?,
176        })
177    }
178}