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