storekit-rs 0.3.1

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

use crate::error::{StoreKitError, VerificationErrorPayload, VerificationFailure};
use crate::private::decode_base64;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerificationMetadata {
    pub jws_representation: String,
    pub header_data: Vec<u8>,
    pub payload_data: Vec<u8>,
    pub signature_data: Vec<u8>,
    pub signed_data: Vec<u8>,
    pub signed_date: String,
    pub device_verification: Vec<u8>,
    pub device_verification_nonce: String,
}

impl VerificationMetadata {
    pub fn jws_representation(&self) -> &str {
        &self.jws_representation
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VerificationResult<T> {
    Verified {
        payload: T,
        metadata: VerificationMetadata,
    },
    Unverified {
        payload: T,
        metadata: VerificationMetadata,
        failure: VerificationFailure,
    },
}

impl<T> VerificationResult<T> {
    pub const fn is_verified(&self) -> bool {
        matches!(self, Self::Verified { .. })
    }

    pub const fn payload(&self) -> &T {
        match self {
            Self::Verified { payload, .. } | Self::Unverified { payload, .. } => payload,
        }
    }

    pub fn into_payload(self) -> T {
        match self {
            Self::Verified { payload, .. } | Self::Unverified { payload, .. } => payload,
        }
    }

    pub const fn metadata(&self) -> &VerificationMetadata {
        match self {
            Self::Verified { metadata, .. } | Self::Unverified { metadata, .. } => metadata,
        }
    }

    pub const fn verification_failure(&self) -> Option<&VerificationFailure> {
        match self {
            Self::Verified { .. } => None,
            Self::Unverified { failure, .. } => Some(failure),
        }
    }

    pub fn into_parts(self) -> (T, VerificationMetadata, Option<VerificationFailure>) {
        match self {
            Self::Verified { payload, metadata } => (payload, metadata, None),
            Self::Unverified {
                payload,
                metadata,
                failure,
            } => (payload, metadata, Some(failure)),
        }
    }

    pub fn jws_representation(&self) -> &str {
        self.metadata().jws_representation()
    }
}

#[derive(Debug, Deserialize)]
pub(crate) struct VerificationMetadataPayload {
    #[serde(rename = "jwsRepresentation")]
    jws_representation: String,
    #[serde(rename = "headerDataBase64")]
    header_data_base64: String,
    #[serde(rename = "payloadDataBase64")]
    payload_data_base64: String,
    #[serde(rename = "signatureDataBase64")]
    signature_data_base64: String,
    #[serde(rename = "signedDataBase64")]
    signed_data_base64: String,
    #[serde(rename = "signedDate")]
    signed_date: String,
    #[serde(rename = "deviceVerificationBase64")]
    device_verification_base64: String,
    #[serde(rename = "deviceVerificationNonce")]
    device_verification_nonce: String,
}

impl VerificationMetadataPayload {
    fn into_metadata(self) -> Result<VerificationMetadata, StoreKitError> {
        Ok(VerificationMetadata {
            jws_representation: self.jws_representation,
            header_data: decode_base64(&self.header_data_base64, "verification header data")?,
            payload_data: decode_base64(&self.payload_data_base64, "verification payload data")?,
            signature_data: decode_base64(
                &self.signature_data_base64,
                "verification signature data",
            )?,
            signed_data: decode_base64(&self.signed_data_base64, "verification signed data")?,
            signed_date: self.signed_date,
            device_verification: decode_base64(
                &self.device_verification_base64,
                "verification device data",
            )?,
            device_verification_nonce: self.device_verification_nonce,
        })
    }
}

#[derive(Debug, Deserialize)]
pub(crate) struct VerificationResultPayload<T> {
    kind: String,
    payload: T,
    metadata: VerificationMetadataPayload,
    #[serde(rename = "verificationError")]
    verification_error: Option<VerificationErrorPayload>,
}

impl<T> VerificationResultPayload<T> {
    pub(crate) fn into_result<U, F>(
        self,
        map_payload: F,
    ) -> Result<VerificationResult<U>, StoreKitError>
    where
        F: FnOnce(T) -> Result<U, StoreKitError>,
    {
        let payload = map_payload(self.payload)?;
        let metadata = self.metadata.into_metadata()?;
        match self.kind.as_str() {
            "verified" => Ok(VerificationResult::Verified { payload, metadata }),
            "unverified" => {
                let failure = self
                    .verification_error
                    .map(VerificationFailure::from_payload)
                    .ok_or_else(|| {
                        StoreKitError::Unknown(
                            "StoreKit returned an unverified result without a verification error"
                                .to_owned(),
                        )
                    })?;
                Ok(VerificationResult::Unverified {
                    payload,
                    metadata,
                    failure,
                })
            }
            other => Err(StoreKitError::Unknown(format!(
                "StoreKit returned an unknown verification result kind '{other}'"
            ))),
        }
    }
}