1use core::ptr;
2
3use serde::{Deserialize, Serialize, Serializer};
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 enum BillingPlanType {
21 Monthly,
23 UpFront,
25 Unknown(String),
27}
28
29impl BillingPlanType {
30 pub fn as_str(&self) -> &str {
32 match self {
33 Self::Monthly => "monthly",
34 Self::UpFront => "upFront",
35 Self::Unknown(value) => value.as_str(),
36 }
37 }
38
39 pub(crate) fn from_raw(raw: String) -> Self {
40 match raw.as_str() {
41 "monthly" => Self::Monthly,
42 "upFront" => Self::UpFront,
43 _ => Self::Unknown(raw),
44 }
45 }
46}
47
48impl Serialize for BillingPlanType {
49 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50 where
51 S: Serializer,
52 {
53 serializer.serialize_str(self.as_str())
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct SubscriptionCommitmentInfo {
60 pub price: String,
62 pub display_price: String,
64 pub period: SubscriptionPeriod,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct SubscriptionPricingTerms {
71 pub billing_price: String,
73 pub billing_display_price: String,
75 pub billing_period: SubscriptionPeriod,
77 pub billing_plan_type: BillingPlanType,
79 pub commitment_info: SubscriptionCommitmentInfo,
81 pub subscription_offers: Vec<SubscriptionOffer>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct SubscriptionInfo {
88 pub introductory_offer: Option<SubscriptionOffer>,
90 pub promotional_offers: Vec<SubscriptionOffer>,
92 pub win_back_offers: Vec<SubscriptionOffer>,
94 pub subscription_group_id: String,
96 pub subscription_period: SubscriptionPeriod,
98 pub pricing_terms: Vec<SubscriptionPricingTerms>,
100 pub group_level: Option<i64>,
102 pub group_display_name: Option<String>,
104}
105
106impl SubscriptionInfo {
107 pub fn is_eligible_for_intro_offer(&self) -> Result<bool, StoreKitError> {
109 Self::is_eligible_for_intro_offer_for(&self.subscription_group_id)
110 }
111
112 pub fn is_eligible_for_intro_offer_for(group_id: &str) -> Result<bool, StoreKitError> {
114 let group_id = cstring_from_str(group_id, "subscription group id")?;
115 let mut raw_value = 0;
116 let mut error_message = ptr::null_mut();
117 let status = unsafe {
118 ffi::sk_subscription_info_is_eligible_for_intro_offer(
119 group_id.as_ptr(),
120 &mut raw_value,
121 &mut error_message,
122 )
123 };
124 if status == ffi::status::OK {
125 Ok(raw_value != 0)
126 } else {
127 Err(unsafe { error_from_status(status, error_message) })
128 }
129 }
130
131 pub fn status(&self) -> Result<Vec<SubscriptionStatus>, StoreKitError> {
133 Self::status_for(&self.subscription_group_id)
134 }
135
136 pub fn status_for(group_id: &str) -> Result<Vec<SubscriptionStatus>, StoreKitError> {
138 let group_id = cstring_from_str(group_id, "subscription group id")?;
139 let mut statuses_json = ptr::null_mut();
140 let mut error_message = ptr::null_mut();
141 let status = unsafe {
142 ffi::sk_subscription_info_statuses_json(
143 group_id.as_ptr(),
144 &mut statuses_json,
145 &mut error_message,
146 )
147 };
148 if status != ffi::status::OK {
149 return Err(unsafe { error_from_status(status, error_message) });
150 }
151 let payloads = unsafe {
152 parse_json_ptr::<Vec<SubscriptionStatusPayload>>(statuses_json, "subscription statuses")
153 }?;
154 payloads
155 .into_iter()
156 .map(SubscriptionStatusPayload::into_subscription_status)
157 .collect::<Result<Vec<_>, _>>()
158 }
159
160 pub fn status_for_transaction(
162 transaction_id: u64,
163 ) -> Result<Option<SubscriptionStatus>, StoreKitError> {
164 let transaction_id = cstring_from_str(&transaction_id.to_string(), "transaction id")?;
165 let mut status_json = ptr::null_mut();
166 let mut error_message = ptr::null_mut();
167 let status = unsafe {
168 ffi::sk_subscription_info_status_for_transaction(
169 transaction_id.as_ptr(),
170 &mut status_json,
171 &mut error_message,
172 )
173 };
174 if status != ffi::status::OK {
175 return Err(unsafe { error_from_status(status, error_message) });
176 }
177 unsafe {
178 parse_optional_json_ptr::<SubscriptionStatusPayload>(
179 status_json,
180 "subscription status for transaction",
181 )
182 }
183 .and_then(|payload| {
184 payload
185 .map(SubscriptionStatusPayload::into_subscription_status)
186 .transpose()
187 })
188 }
189}
190
191#[derive(Debug, Clone)]
192pub struct SubscriptionStatus {
194 pub state: RenewalState,
196 pub transaction: VerificationResult<Transaction>,
198 pub renewal_info: VerificationResult<RenewalInfo>,
200}
201
202#[derive(Debug, Deserialize)]
203pub(crate) struct SubscriptionInfoPayload {
204 #[serde(rename = "introductoryOffer")]
205 introductory_offer: Option<SubscriptionOfferPayload>,
206 #[serde(rename = "promotionalOffers")]
207 promotional_offers: Vec<SubscriptionOfferPayload>,
208 #[serde(rename = "winBackOffers")]
209 win_back_offers: Vec<SubscriptionOfferPayload>,
210 #[serde(rename = "subscriptionGroupID")]
211 subscription_group_id: String,
212 #[serde(rename = "subscriptionPeriod")]
213 subscription_period: SubscriptionPeriodPayload,
214 #[serde(default, rename = "pricingTerms")]
215 pricing_terms: Vec<SubscriptionPricingTermsPayload>,
216 #[serde(rename = "groupLevel")]
217 group_level: Option<i64>,
218 #[serde(rename = "groupDisplayName")]
219 group_display_name: Option<String>,
220}
221
222#[derive(Debug, Deserialize)]
223pub(crate) struct SubscriptionCommitmentInfoPayload {
224 price: String,
225 #[serde(rename = "displayPrice")]
226 display_price: String,
227 period: SubscriptionPeriodPayload,
228}
229
230impl SubscriptionCommitmentInfoPayload {
231 pub(crate) fn into_subscription_commitment_info(self) -> SubscriptionCommitmentInfo {
232 SubscriptionCommitmentInfo {
233 price: self.price,
234 display_price: self.display_price,
235 period: self.period.into_subscription_period(),
236 }
237 }
238}
239
240#[derive(Debug, Deserialize)]
241pub(crate) struct SubscriptionPricingTermsPayload {
242 #[serde(rename = "billingPrice")]
243 billing_price: String,
244 #[serde(rename = "billingDisplayPrice")]
245 billing_display_price: String,
246 #[serde(rename = "billingPeriod")]
247 billing_period: SubscriptionPeriodPayload,
248 #[serde(rename = "billingPlanType")]
249 billing_plan_type: String,
250 #[serde(rename = "commitmentInfo")]
251 commitment_info: SubscriptionCommitmentInfoPayload,
252 #[serde(default, rename = "subscriptionOffers")]
253 subscription_offers: Vec<SubscriptionOfferPayload>,
254}
255
256impl SubscriptionPricingTermsPayload {
257 pub(crate) fn into_subscription_pricing_terms(self) -> SubscriptionPricingTerms {
258 SubscriptionPricingTerms {
259 billing_price: self.billing_price,
260 billing_display_price: self.billing_display_price,
261 billing_period: self.billing_period.into_subscription_period(),
262 billing_plan_type: BillingPlanType::from_raw(self.billing_plan_type),
263 commitment_info: self.commitment_info.into_subscription_commitment_info(),
264 subscription_offers: self
265 .subscription_offers
266 .into_iter()
267 .map(SubscriptionOfferPayload::into_subscription_offer)
268 .collect(),
269 }
270 }
271}
272
273impl SubscriptionInfoPayload {
274 pub(crate) fn into_subscription_info(self) -> SubscriptionInfo {
275 SubscriptionInfo {
276 introductory_offer: self
277 .introductory_offer
278 .map(SubscriptionOfferPayload::into_subscription_offer),
279 promotional_offers: self
280 .promotional_offers
281 .into_iter()
282 .map(SubscriptionOfferPayload::into_subscription_offer)
283 .collect(),
284 win_back_offers: self
285 .win_back_offers
286 .into_iter()
287 .map(SubscriptionOfferPayload::into_subscription_offer)
288 .collect(),
289 subscription_group_id: self.subscription_group_id,
290 subscription_period: self.subscription_period.into_subscription_period(),
291 pricing_terms: self
292 .pricing_terms
293 .into_iter()
294 .map(SubscriptionPricingTermsPayload::into_subscription_pricing_terms)
295 .collect(),
296 group_level: self.group_level,
297 group_display_name: self.group_display_name,
298 }
299 }
300}
301
302#[derive(Debug, Deserialize)]
303pub(crate) struct SubscriptionStatusPayload {
304 state: String,
305 transaction: VerificationResultPayload<TransactionPayload>,
306 #[serde(rename = "renewalInfo")]
307 renewal_info: VerificationResultPayload<RenewalInfoPayload>,
308}
309
310impl SubscriptionStatusPayload {
311 pub(crate) fn into_subscription_status(self) -> Result<SubscriptionStatus, StoreKitError> {
312 Ok(SubscriptionStatus {
313 state: RenewalState::from_raw(self.state),
314 transaction: self
315 .transaction
316 .into_result(Transaction::from_snapshot_payload)?,
317 renewal_info: self
318 .renewal_info
319 .into_result(|payload| Ok(payload.into_renewal_info()))?,
320 })
321 }
322}