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;
use serde_json::Value;

use crate::app_transaction::AppTransaction;
use crate::error::StoreKitError;
use crate::ffi;
use crate::private::{
    decode_base64, decode_base64_urlsafe, error_from_status, parse_optional_json_ptr,
};
use crate::verification_result::VerificationResult;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppReceipt {
    pub path: String,
    pub data: Vec<u8>,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct ReceiptValidator;

impl ReceiptValidator {
    pub fn current_receipt() -> Result<Option<AppReceipt>, StoreKitError> {
        let mut receipt_json = core::ptr::null_mut();
        let mut error_message = core::ptr::null_mut();
        let status = unsafe { ffi::sk_receipt_json(&mut receipt_json, &mut error_message) };
        if status != ffi::status::OK {
            return Err(unsafe { error_from_status(status, error_message) });
        }

        unsafe { parse_optional_json_ptr::<AppReceiptPayload>(receipt_json, "receipt") }
            .and_then(|payload| payload.map(AppReceiptPayload::into_receipt).transpose())
    }

    pub fn shared_app_transaction() -> Result<VerificationResult<AppTransaction>, StoreKitError> {
        AppTransaction::shared()
    }

    pub fn refresh_app_transaction() -> Result<VerificationResult<AppTransaction>, StoreKitError> {
        AppTransaction::refresh()
    }

    pub fn extract_unverified_payload(jws: &str) -> Result<Value, StoreKitError> {
        let mut segments = jws.split('.');
        let _header = segments.next().ok_or_else(|| {
            StoreKitError::InvalidArgument("JWS is missing a header segment".to_owned())
        })?;
        let payload = segments.next().ok_or_else(|| {
            StoreKitError::InvalidArgument("JWS is missing a payload segment".to_owned())
        })?;
        let _signature = segments.next().ok_or_else(|| {
            StoreKitError::InvalidArgument("JWS is missing a signature segment".to_owned())
        })?;
        if segments.next().is_some() {
            return Err(StoreKitError::InvalidArgument(
                "JWS contains too many segments".to_owned(),
            ));
        }
        let payload_bytes = decode_base64_urlsafe(payload, "JWS payload")?;
        serde_json::from_slice::<Value>(&payload_bytes).map_err(|error| {
            StoreKitError::InvalidArgument(format!("failed to decode JWS payload JSON: {error}"))
        })
    }
}

#[derive(Debug, Deserialize)]
struct AppReceiptPayload {
    path: String,
    #[serde(rename = "dataBase64")]
    data_base64: String,
}

impl AppReceiptPayload {
    fn into_receipt(self) -> Result<AppReceipt, StoreKitError> {
        Ok(AppReceipt {
            path: self.path,
            data: decode_base64(&self.data_base64, "receipt data")?,
        })
    }
}