use core::ptr;
use serde::{Deserialize, Serialize, Serializer};
use crate::error::StoreKitError;
use crate::ffi;
use crate::private::{
cstring_from_str, error_from_status, parse_json_ptr, parse_optional_json_ptr,
};
use crate::renewal_info::{RenewalInfo, RenewalInfoPayload};
use crate::renewal_state::RenewalState;
use crate::subscription::{
SubscriptionOffer, SubscriptionOfferPayload, SubscriptionPeriod, SubscriptionPeriodPayload,
};
use crate::transaction::{Transaction, TransactionPayload};
use crate::verification_result::{VerificationResult, VerificationResultPayload};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BillingPlanType {
Monthly,
UpFront,
Unknown(String),
}
impl BillingPlanType {
pub fn as_str(&self) -> &str {
match self {
Self::Monthly => "monthly",
Self::UpFront => "upFront",
Self::Unknown(value) => value.as_str(),
}
}
pub(crate) fn from_raw(raw: String) -> Self {
match raw.as_str() {
"monthly" => Self::Monthly,
"upFront" => Self::UpFront,
_ => Self::Unknown(raw),
}
}
}
impl Serialize for BillingPlanType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubscriptionCommitmentInfo {
pub price: String,
pub display_price: String,
pub period: SubscriptionPeriod,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubscriptionPricingTerms {
pub billing_price: String,
pub billing_display_price: String,
pub billing_period: SubscriptionPeriod,
pub billing_plan_type: BillingPlanType,
pub commitment_info: SubscriptionCommitmentInfo,
pub subscription_offers: Vec<SubscriptionOffer>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubscriptionInfo {
pub introductory_offer: Option<SubscriptionOffer>,
pub promotional_offers: Vec<SubscriptionOffer>,
pub win_back_offers: Vec<SubscriptionOffer>,
pub subscription_group_id: String,
pub subscription_period: SubscriptionPeriod,
pub pricing_terms: Vec<SubscriptionPricingTerms>,
pub group_level: Option<i64>,
pub group_display_name: Option<String>,
}
impl SubscriptionInfo {
pub fn is_eligible_for_intro_offer(&self) -> Result<bool, StoreKitError> {
Self::is_eligible_for_intro_offer_for(&self.subscription_group_id)
}
pub fn is_eligible_for_intro_offer_for(group_id: &str) -> Result<bool, StoreKitError> {
let group_id = cstring_from_str(group_id, "subscription group id")?;
let mut raw_value = 0;
let mut error_message = ptr::null_mut();
let status = unsafe {
ffi::sk_subscription_info_is_eligible_for_intro_offer(
group_id.as_ptr(),
&mut raw_value,
&mut error_message,
)
};
if status == ffi::status::OK {
Ok(raw_value != 0)
} else {
Err(unsafe { error_from_status(status, error_message) })
}
}
pub fn status(&self) -> Result<Vec<SubscriptionStatus>, StoreKitError> {
Self::status_for(&self.subscription_group_id)
}
pub fn status_for(group_id: &str) -> Result<Vec<SubscriptionStatus>, StoreKitError> {
let group_id = cstring_from_str(group_id, "subscription group id")?;
let mut statuses_json = ptr::null_mut();
let mut error_message = ptr::null_mut();
let status = unsafe {
ffi::sk_subscription_info_statuses_json(
group_id.as_ptr(),
&mut statuses_json,
&mut error_message,
)
};
if status != ffi::status::OK {
return Err(unsafe { error_from_status(status, error_message) });
}
let payloads = unsafe {
parse_json_ptr::<Vec<SubscriptionStatusPayload>>(statuses_json, "subscription statuses")
}?;
payloads
.into_iter()
.map(SubscriptionStatusPayload::into_subscription_status)
.collect::<Result<Vec<_>, _>>()
}
pub fn status_for_transaction(
transaction_id: u64,
) -> Result<Option<SubscriptionStatus>, StoreKitError> {
let transaction_id = cstring_from_str(&transaction_id.to_string(), "transaction id")?;
let mut status_json = ptr::null_mut();
let mut error_message = ptr::null_mut();
let status = unsafe {
ffi::sk_subscription_info_status_for_transaction(
transaction_id.as_ptr(),
&mut status_json,
&mut error_message,
)
};
if status != ffi::status::OK {
return Err(unsafe { error_from_status(status, error_message) });
}
unsafe {
parse_optional_json_ptr::<SubscriptionStatusPayload>(
status_json,
"subscription status for transaction",
)
}
.and_then(|payload| {
payload
.map(SubscriptionStatusPayload::into_subscription_status)
.transpose()
})
}
}
#[derive(Debug, Clone)]
pub struct SubscriptionStatus {
pub state: RenewalState,
pub transaction: VerificationResult<Transaction>,
pub renewal_info: VerificationResult<RenewalInfo>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct SubscriptionInfoPayload {
#[serde(rename = "introductoryOffer")]
introductory_offer: Option<SubscriptionOfferPayload>,
#[serde(rename = "promotionalOffers")]
promotional_offers: Vec<SubscriptionOfferPayload>,
#[serde(rename = "winBackOffers")]
win_back_offers: Vec<SubscriptionOfferPayload>,
#[serde(rename = "subscriptionGroupID")]
subscription_group_id: String,
#[serde(rename = "subscriptionPeriod")]
subscription_period: SubscriptionPeriodPayload,
#[serde(default, rename = "pricingTerms")]
pricing_terms: Vec<SubscriptionPricingTermsPayload>,
#[serde(rename = "groupLevel")]
group_level: Option<i64>,
#[serde(rename = "groupDisplayName")]
group_display_name: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct SubscriptionCommitmentInfoPayload {
price: String,
#[serde(rename = "displayPrice")]
display_price: String,
period: SubscriptionPeriodPayload,
}
impl SubscriptionCommitmentInfoPayload {
pub(crate) fn into_subscription_commitment_info(self) -> SubscriptionCommitmentInfo {
SubscriptionCommitmentInfo {
price: self.price,
display_price: self.display_price,
period: self.period.into_subscription_period(),
}
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct SubscriptionPricingTermsPayload {
#[serde(rename = "billingPrice")]
billing_price: String,
#[serde(rename = "billingDisplayPrice")]
billing_display_price: String,
#[serde(rename = "billingPeriod")]
billing_period: SubscriptionPeriodPayload,
#[serde(rename = "billingPlanType")]
billing_plan_type: String,
#[serde(rename = "commitmentInfo")]
commitment_info: SubscriptionCommitmentInfoPayload,
#[serde(default, rename = "subscriptionOffers")]
subscription_offers: Vec<SubscriptionOfferPayload>,
}
impl SubscriptionPricingTermsPayload {
pub(crate) fn into_subscription_pricing_terms(self) -> SubscriptionPricingTerms {
SubscriptionPricingTerms {
billing_price: self.billing_price,
billing_display_price: self.billing_display_price,
billing_period: self.billing_period.into_subscription_period(),
billing_plan_type: BillingPlanType::from_raw(self.billing_plan_type),
commitment_info: self.commitment_info.into_subscription_commitment_info(),
subscription_offers: self
.subscription_offers
.into_iter()
.map(SubscriptionOfferPayload::into_subscription_offer)
.collect(),
}
}
}
impl SubscriptionInfoPayload {
pub(crate) fn into_subscription_info(self) -> SubscriptionInfo {
SubscriptionInfo {
introductory_offer: self
.introductory_offer
.map(SubscriptionOfferPayload::into_subscription_offer),
promotional_offers: self
.promotional_offers
.into_iter()
.map(SubscriptionOfferPayload::into_subscription_offer)
.collect(),
win_back_offers: self
.win_back_offers
.into_iter()
.map(SubscriptionOfferPayload::into_subscription_offer)
.collect(),
subscription_group_id: self.subscription_group_id,
subscription_period: self.subscription_period.into_subscription_period(),
pricing_terms: self
.pricing_terms
.into_iter()
.map(SubscriptionPricingTermsPayload::into_subscription_pricing_terms)
.collect(),
group_level: self.group_level,
group_display_name: self.group_display_name,
}
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct SubscriptionStatusPayload {
state: String,
transaction: VerificationResultPayload<TransactionPayload>,
#[serde(rename = "renewalInfo")]
renewal_info: VerificationResultPayload<RenewalInfoPayload>,
}
impl SubscriptionStatusPayload {
pub(crate) fn into_subscription_status(self) -> Result<SubscriptionStatus, StoreKitError> {
Ok(SubscriptionStatus {
state: RenewalState::from_raw(self.state),
transaction: self
.transaction
.into_result(Transaction::from_snapshot_payload)?,
renewal_info: self
.renewal_info
.into_result(|payload| Ok(payload.into_renewal_info()))?,
})
}
}