storekit-rs 0.2.1

Safe Rust bindings for Apple's StoreKit framework — in-app purchases and transaction streams on macOS
Documentation
use serde::{Deserialize, Serialize};

use crate::error::StoreKitError;
use crate::transaction::{Transaction, TransactionPayload};
use crate::verification_result::{VerificationResult, VerificationResultPayload};

#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum PurchaseOption {
    AppAccountToken {
        app_account_token: String,
    },
    Quantity {
        quantity: i64,
    },
    SimulatesAskToBuyInSandbox {
        simulate_ask_to_buy_in_sandbox: bool,
    },
    CustomString {
        key: String,
        value: String,
    },
    CustomNumber {
        key: String,
        value: f64,
    },
    CustomBool {
        key: String,
        value: bool,
    },
    CustomData {
        key: String,
        value_base64: String,
    },
    PromotionalOfferSignature {
        offer_id: String,
        key_id: String,
        nonce: String,
        signature_base64: String,
        timestamp: i64,
    },
    PromotionalOfferCompactJws {
        offer_id: String,
        compact_jws: String,
    },
    IntroductoryOfferEligibility {
        compact_jws: String,
    },
    WinBackOffer {
        offer_id: String,
    },
    OnStorefrontChange {
        should_continue_purchase: bool,
    },
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum PurchaseResult {
    Success(VerificationResult<Transaction>),
    UserCancelled,
    Pending,
}

#[allow(clippy::unsafe_derive_deserialize)]
#[derive(Debug, Deserialize)]
pub(crate) struct PurchaseResultPayload {
    kind: String,
    #[serde(rename = "verificationResult")]
    verification_result: Option<VerificationResultPayload<TransactionPayload>>,
}

impl PurchaseResultPayload {
    pub(crate) fn into_purchase_result(
        self,
        transaction_handle: *mut core::ffi::c_void,
    ) -> Result<PurchaseResult, StoreKitError> {
        match self.kind.as_str() {
            "success" => {
                let verification_result = self.verification_result.ok_or_else(|| {
                    StoreKitError::Unknown(
                        "StoreKit reported a successful purchase without a verification result"
                            .to_owned(),
                    )
                })?;
                let transaction = verification_result.into_result(|payload| {
                    Transaction::from_raw_parts(transaction_handle, payload)
                })?;
                Ok(PurchaseResult::Success(transaction))
            }
            "userCancelled" => {
                if !transaction_handle.is_null() {
                    unsafe { crate::ffi::sk_transaction_release(transaction_handle) };
                }
                Ok(PurchaseResult::UserCancelled)
            }
            "pending" => {
                if !transaction_handle.is_null() {
                    unsafe { crate::ffi::sk_transaction_release(transaction_handle) };
                }
                Ok(PurchaseResult::Pending)
            }
            other => {
                if !transaction_handle.is_null() {
                    unsafe { crate::ffi::sk_transaction_release(transaction_handle) };
                }
                Err(StoreKitError::Unknown(format!(
                    "StoreKit returned an unknown purchase result kind '{other}'"
                )))
            }
        }
    }
}