storekit/
receipt_validator.rs1use 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}