Skip to main content

storekit/
receipt_validator.rs

1use serde::Deserialize;
2use serde_json::Value;
3
4use crate::app_transaction::AppTransaction;
5use crate::error::StoreKitError;
6use crate::ffi;
7use crate::private::{
8    decode_base64, decode_base64_urlsafe, error_from_status, parse_optional_json_ptr,
9};
10use crate::verification_result::VerificationResult;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct AppReceipt {
14    pub path: String,
15    pub data: Vec<u8>,
16}
17
18#[derive(Debug, Clone, Copy, Default)]
19pub struct ReceiptValidator;
20
21impl ReceiptValidator {
22    pub fn current_receipt() -> Result<Option<AppReceipt>, StoreKitError> {
23        let mut receipt_json = core::ptr::null_mut();
24        let mut error_message = core::ptr::null_mut();
25        let status = unsafe { ffi::sk_receipt_json(&mut receipt_json, &mut error_message) };
26        if status != ffi::status::OK {
27            return Err(unsafe { error_from_status(status, error_message) });
28        }
29
30        unsafe { parse_optional_json_ptr::<AppReceiptPayload>(receipt_json, "receipt") }
31            .and_then(|payload| payload.map(AppReceiptPayload::into_receipt).transpose())
32    }
33
34    pub fn shared_app_transaction() -> Result<VerificationResult<AppTransaction>, StoreKitError> {
35        AppTransaction::shared()
36    }
37
38    pub fn refresh_app_transaction() -> Result<VerificationResult<AppTransaction>, StoreKitError> {
39        AppTransaction::refresh()
40    }
41
42    pub fn extract_unverified_payload(jws: &str) -> Result<Value, StoreKitError> {
43        let mut segments = jws.split('.');
44        let _header = segments.next().ok_or_else(|| {
45            StoreKitError::InvalidArgument("JWS is missing a header segment".to_owned())
46        })?;
47        let payload = segments.next().ok_or_else(|| {
48            StoreKitError::InvalidArgument("JWS is missing a payload segment".to_owned())
49        })?;
50        let _signature = segments.next().ok_or_else(|| {
51            StoreKitError::InvalidArgument("JWS is missing a signature segment".to_owned())
52        })?;
53        if segments.next().is_some() {
54            return Err(StoreKitError::InvalidArgument(
55                "JWS contains too many segments".to_owned(),
56            ));
57        }
58        let payload_bytes = decode_base64_urlsafe(payload, "JWS payload")?;
59        serde_json::from_slice::<Value>(&payload_bytes).map_err(|error| {
60            StoreKitError::InvalidArgument(format!("failed to decode JWS payload JSON: {error}"))
61        })
62    }
63}
64
65#[derive(Debug, Deserialize)]
66struct AppReceiptPayload {
67    path: String,
68    #[serde(rename = "dataBase64")]
69    data_base64: String,
70}
71
72impl AppReceiptPayload {
73    fn into_receipt(self) -> Result<AppReceipt, StoreKitError> {
74        Ok(AppReceipt {
75            path: self.path,
76            data: decode_base64(&self.data_base64, "receipt data")?,
77        })
78    }
79}