app_store_server_library/
signed_data_verifier.rs

1use base64::engine::general_purpose::STANDARD;
2use base64::{DecodeError, Engine};
3
4use crate::chain_verifier::{verify_chain, ChainVerifierError};
5use crate::primitives::app_transaction::AppTransaction;
6use crate::primitives::environment::Environment;
7use crate::primitives::jws_renewal_info_decoded_payload::JWSRenewalInfoDecodedPayload;
8use crate::primitives::jws_transaction_decoded_payload::JWSTransactionDecodedPayload;
9use crate::primitives::response_body_v2_decoded_payload::ResponseBodyV2DecodedPayload;
10use crate::utils::{base64_url_to_base64, StringExt};
11use jsonwebtoken::{Algorithm, DecodingKey, Validation};
12use serde::de::DeserializeOwned;
13
14#[derive(thiserror::Error, Debug, PartialEq)]
15pub enum SignedDataVerifierError {
16    #[error("VerificationFailure")]
17    VerificationFailure,
18
19    #[error("InvalidAppIdentifier")]
20    InvalidAppIdentifier,
21
22    #[error("InvalidEnvironment")]
23    InvalidEnvironment,
24
25    #[error("InternalChainVerifierError")]
26    InternalChainVerifierError(#[from] ChainVerifierError),
27
28    #[error("InternalDecodeError: [{0}]")]
29    InternalDecodeError(#[from] DecodeError),
30
31    #[error("InternalJWTError: [{0}]")]
32    InternalJWTError(#[from] jsonwebtoken::errors::Error),
33}
34
35/// A verifier for signed data, commonly used for verifying and decoding
36/// signed Apple server notifications and transactions.
37pub struct SignedDataVerifier {
38    root_certificates: Vec<Vec<u8>>,
39    environment: Environment,
40    bundle_id: String,
41    app_apple_id: Option<i64>,
42}
43
44impl SignedDataVerifier {
45    /// Creates a new `SignedDataVerifier` instance with the specified parameters.
46    ///
47    /// # Arguments
48    ///
49    /// * `root_certificates` - A vector of DER-encoded root certificates used for verification.
50    /// * `environment` - The environment (e.g., `Environment::PRODUCTION` or `Environment::SANDBOX`).
51    /// * `bundle_id` - The bundle ID associated with the application.
52    /// * `app_apple_id` - An optional Apple ID associated with the application.
53    ///
54    /// # Returns
55    ///
56    /// A new `SignedDataVerifier` instance.
57    pub fn new(
58        root_certificates: Vec<Vec<u8>>,
59        environment: Environment,
60        bundle_id: String,
61        app_apple_id: Option<i64>,
62    ) -> Self {
63        return SignedDataVerifier {
64            root_certificates,
65            environment,
66            bundle_id,
67            app_apple_id,
68        };
69    }
70}
71
72impl SignedDataVerifier {
73    /// Verifies and decodes a signed renewal info.
74    ///
75    /// This method takes a signed renewal info string, verifies its authenticity and
76    /// integrity, and returns the decoded payload as a `JWSRenewalInfoDecodedPayload`
77    /// if the verification is successful.
78    ///
79    /// # Arguments
80    ///
81    /// * `signed_renewal_info` - The signed renewal info string to verify and decode.
82    ///
83    /// # Returns
84    ///
85    /// - `Ok(JWSRenewalInfoDecodedPayload)` if verification and decoding are successful.
86    /// - `Err(SignedDataVerifierError)` if verification or decoding fails, with error details.
87    pub fn verify_and_decode_renewal_info(
88        &self,
89        signed_renewal_info: &str,
90    ) -> Result<JWSRenewalInfoDecodedPayload, SignedDataVerifierError> {
91        Ok(self.decode_signed_object(signed_renewal_info)?)
92    }
93
94    /// Verifies and decodes a signed transaction.
95    ///
96    /// This method takes a signed transaction string, verifies its authenticity and
97    /// integrity, and returns the decoded payload as a `JWSTransactionDecodedPayload`
98    /// if the verification is successful.
99    ///
100    /// # Arguments
101    ///
102    /// * `signed_transaction` - The signed transaction string to verify and decode.
103    ///
104    /// # Returns
105    ///
106    /// - `Ok(JWSTransactionDecodedPayload)` if verification and decoding are successful.
107    /// - `Err(SignedDataVerifierError)` if verification or decoding fails, with error details.
108    pub fn verify_and_decode_signed_transaction(
109        &self,
110        signed_transaction: &str,
111    ) -> Result<JWSTransactionDecodedPayload, SignedDataVerifierError> {
112        let decoded_signed_tx: JWSTransactionDecodedPayload =
113            self.decode_signed_object(signed_transaction)?;
114
115        if decoded_signed_tx.bundle_id.as_ref() != Some(&self.bundle_id) {
116            return Err(SignedDataVerifierError::InvalidAppIdentifier);
117        }
118
119        if decoded_signed_tx.environment.as_ref() != Some(&self.environment) {
120            return Err(SignedDataVerifierError::InvalidEnvironment);
121        }
122
123        Ok(decoded_signed_tx)
124    }
125
126    /// Verifies and decodes a signed notification.
127    ///
128    /// This method takes a signed notification string, verifies its authenticity and
129    /// integrity, and returns the decoded payload as a `ResponseBodyV2DecodedPayload`
130    /// if the verification is successful.
131    ///
132    /// # Arguments
133    ///
134    /// * `signed_payload` - The signed notification string to verify and decode.
135    ///
136    /// # Returns
137    ///
138    /// - `Ok(ResponseBodyV2DecodedPayload)` if verification and decoding are successful.
139    /// - `Err(SignedDataVerifierError)` if verification or decoding fails, with error details.
140    pub fn verify_and_decode_notification(
141        &self,
142        signed_payload: &str,
143    ) -> Result<ResponseBodyV2DecodedPayload, SignedDataVerifierError> {
144        let decoded_signed_notification: ResponseBodyV2DecodedPayload =
145            self.decode_signed_object(signed_payload)?;
146
147        let bundle_id;
148        let app_apple_id;
149        let environment;
150
151        if let Some(data) = &decoded_signed_notification.data {
152            bundle_id = data.bundle_id.clone();
153            app_apple_id = data.app_apple_id.clone();
154            environment = data.environment.clone();
155        } else if let Some(summary) = &decoded_signed_notification.summary {
156            bundle_id = summary.bundle_id.clone();
157            app_apple_id = summary.app_apple_id.clone();
158            environment = summary.environment.clone();
159        } else if let Some(external_purchase_token) = &decoded_signed_notification.external_purchase_token {
160            bundle_id = external_purchase_token.bundle_id.clone();
161            app_apple_id = external_purchase_token.app_apple_id.clone();
162
163            if let Some(external_purchase_id) = &external_purchase_token.external_purchase_id {
164                if external_purchase_id.starts_with("SANDBOX") {
165                    environment = Some(Environment::Sandbox)
166                } else {
167                    environment = Some(Environment::Production)
168                }
169            } else {
170                environment = Some(Environment::Production)
171            }
172        } else {
173            bundle_id = None;
174            app_apple_id = None;
175            environment = None;
176        }
177
178        self.verify_notification_app_identifier_and_environment(bundle_id, app_apple_id, environment)?;
179
180        Ok(decoded_signed_notification)
181    }
182
183    fn verify_notification_app_identifier_and_environment(
184        &self,
185        bundle_id: Option<String>,
186        app_apple_id: Option<i64>,
187        environment: Option<Environment>,
188    ) -> Result<(), SignedDataVerifierError> {
189        if let Some(bundle_id) = bundle_id {
190            if bundle_id != self.bundle_id {
191                return Err(SignedDataVerifierError::InvalidAppIdentifier);
192            }
193        }
194
195        if self.environment == Environment::Production && self.app_apple_id != app_apple_id {
196            return Err(SignedDataVerifierError::InvalidAppIdentifier);
197        }
198
199        if let Some(environment) = environment {
200            if self.environment != Environment::LocalTesting && self.environment != environment {
201                return Err(SignedDataVerifierError::InvalidEnvironment);
202            }
203        }
204
205        Ok(())
206    }
207
208    /// Verifies and decodes a signed notification.
209    ///
210    /// This method takes a signed notification string, verifies its authenticity and
211    /// integrity, and returns the decoded payload as a `ResponseBodyV2DecodedPayload`
212    /// if the verification is successful.
213    ///
214    /// # Arguments
215    ///
216    /// * `signed_payload` - The signed notification string to verify and decode.
217    ///
218    /// # Returns
219    ///
220    /// - `Ok(ResponseBodyV2DecodedPayload)` if verification and decoding are successful.
221    /// - `Err(SignedDataVerifierError)` if verification or decoding fails, with error details.
222    pub fn verify_and_decode_app_transaction(
223        &self,
224        signed_app_transaction: &str,
225    ) -> Result<AppTransaction, SignedDataVerifierError> {
226        let decoded_app_transaction: AppTransaction =
227            self.decode_signed_object(signed_app_transaction)?;
228
229        if decoded_app_transaction.bundle_id.as_ref() != Some(&self.bundle_id) {
230            return Err(SignedDataVerifierError::InvalidAppIdentifier);
231        }
232
233        if decoded_app_transaction.receipt_type.as_ref() != Some(&self.environment) {
234            return Err(SignedDataVerifierError::InvalidEnvironment);
235        }
236
237        Ok(decoded_app_transaction)
238    }
239
240    /// Private method used for decoding a signed object (internal use).
241    fn decode_signed_object<T: DeserializeOwned>(
242        &self,
243        signed_obj: &str,
244    ) -> Result<T, SignedDataVerifierError> {
245        // Data is not signed by the App Store, and verification should be skipped
246        // The environment MUST be checked in the public method calling this
247        if self.environment == Environment::Xcode || self.environment == Environment::LocalTesting {
248            const EXPECTED_JWT_SEGMENTS: usize = 3;
249
250            let body_segments: Vec<&str> = signed_obj.split('.').collect();
251
252            if body_segments.len() != EXPECTED_JWT_SEGMENTS {
253                return Err(SignedDataVerifierError::VerificationFailure);
254            }
255
256            let _ = jsonwebtoken::decode_header(&signed_obj)?;
257            let body_data = base64_url_to_base64(body_segments[1]);
258
259            let decoded_body = match STANDARD.decode(body_data) {
260                Ok(decoded_body) => match serde_json::from_slice(&decoded_body) {
261                    Ok(decoded) => decoded,
262                    Err(_) => return Err(SignedDataVerifierError::VerificationFailure),
263                },
264                Err(_) => return Err(SignedDataVerifierError::VerificationFailure),
265            };
266
267            return Ok(decoded_body);
268        }
269
270        let header = jsonwebtoken::decode_header(signed_obj)?;
271
272        let Some(x5c) = header.x5c else {
273            return Err(SignedDataVerifierError::VerificationFailure);
274        };
275
276        if x5c.is_empty() {
277            return Err(SignedDataVerifierError::VerificationFailure);
278        }
279
280        let x5c: Result<Vec<Vec<u8>>, DecodeError> = x5c.iter().map(|c| c.as_der_bytes()).collect();
281        let chain = x5c?;
282
283        if header.alg != Algorithm::ES256 {
284            return Err(SignedDataVerifierError::VerificationFailure);
285        }
286
287        let pub_key = verify_chain(&chain, &self.root_certificates, None)?;
288        let pub_key = &pub_key[pub_key.len() - 65..];
289
290        let decoding_key = DecodingKey::from_ec_der(pub_key);
291        let claims: [&str; 0] = [];
292
293        let mut validator = Validation::new(Algorithm::ES256);
294        validator.validate_exp = false;
295        validator.set_required_spec_claims(&claims);
296
297        let payload = jsonwebtoken::decode::<T>(signed_obj, &decoding_key, &validator)
298            .expect("Expect Payload");
299        return Ok(payload.claims);
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306    use crate::primitives::auto_renew_status::AutoRenewStatus;
307    use crate::primitives::expiration_intent::ExpirationIntent;
308    use crate::primitives::in_app_ownership_type::InAppOwnershipType;
309    use crate::primitives::notification_type_v2::NotificationTypeV2;
310    use crate::primitives::offer_discount_type::OfferDiscountType;
311    use crate::primitives::offer_type::OfferType;
312    use crate::primitives::price_increase_status::PriceIncreaseStatus;
313    use crate::primitives::product_type::ProductType;
314    use crate::primitives::purchase_platform::PurchasePlatform;
315    use crate::primitives::revocation_reason::RevocationReason;
316    use crate::primitives::status::Status;
317    use crate::primitives::subtype::Subtype;
318    use crate::primitives::transaction_reason::TransactionReason;
319    use ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING;
320    use serde_json::{Map, Value};
321    use std::fs;
322    use crate::primitives::consumption_request_reason::ConsumptionRequestReason;
323
324    const ROOT_CA_BASE64_ENCODED: &str = "MIIBgjCCASmgAwIBAgIJALUc5ALiH5pbMAoGCCqGSM49BAMDMDYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDdXBlcnRpbm8wHhcNMjMwMTA1MjEzMDIyWhcNMzMwMTAyMjEzMDIyWjA2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ3VwZXJ0aW5vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc+/Bl+gospo6tf9Z7io5tdKdrlN1YdVnqEhEDXDShzdAJPQijamXIMHf8xWWTa1zgoYTxOKpbuJtDplz1XriTaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDRwAwRAIgemWQXnMAdTad2JDJWng9U4uBBL5mA7WI05H7oH7c6iQCIHiRqMjNfzUAyiu9h6rOU/K+iTR0I/3Y/NSWsXHX+acc";
325
326    const TEST_NOTIFICATION: &str = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImFwcEFwcGxlSWQiOjEyMzQsImVudmlyb25tZW50IjoiU2FuZGJveCIsImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsInNpZ25lZERhdGUiOjE2ODEzMTQzMjQwMDAsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.VVXYwuNm2Y3XsOUva-BozqatRCsDuykA7xIe_CCRw6aIAAxJ1nb2sw871jfZ6dcgNhUuhoZ93hfbc1v_5zB7Og";
327    const MISSING_X5C_HEADER_CLAIM: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1Y3dyb25nIjpbIk1JSUJvRENDQVVhZ0F3SUJBZ0lCRERBS0JnZ3Foa2pPUFFRREF6QkZNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1CNFhEVEl6TURFd05USXhNekV6TkZvWERUTXpNREV3TVRJeE16RXpORm93UFRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eERUQUxCZ05WQkFvTUJFeGxZV1l3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRpdFlIRWFZVnVjOGc5QWpUT3dFck12R3lQeWtQYStwdXZUSThoSlRIWlpETEdhczJxWDErRXJ4Z1FUSmdWWHY3Nm5tTGhoUkpIK2oyNUFpQUk4aUdzb3k4d0xUQUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVFCZ29xaGtpRzkyTmtCZ3NCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05JQURCRkFpQlg0YytUMEZwNW5KNVFSQ2xSZnU1UFNCeVJ2TlB0dWFUc2swdlBCM1dBSUFJaEFOZ2FhdUFqL1lQOXMwQWtFaHlKaHhRTy82UTJ6b3VaK0gxQ0lPZWhuTXpRIiwiTUlJQm56Q0NBVVdnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQXpBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1CNFhEVEl6TURFd05USXhNekV3TlZvWERUTXpNREV3TVRJeE16RXdOVm93UlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eEZUQVRCZ05WQkFvTURFbHVkR1Z5YldWa2FXRjBaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCQlVONVY5cktqZlJpTUFJb2pFQTBBdjVNcDBvRitPMGNMNGd6clRGMTc4aW5VSHVnajdFdDQ2TnJrUTdoS2dNVm5qb2dxNDVRMXJNcytjTUhWTklMV3FqTlRBek1BOEdBMVVkRXdRSU1BWUJBZjhDQVFBd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJBR0NpcUdTSWIzWTJRR0FnRUVBZ1VBTUFvR0NDcUdTTTQ5QkFNREEwZ0FNRVVDSVFDbXNJS1lzNDF1bGxzc0hYNHJWdmVVVDBaN0lzNS9oTEsxbEZQVHR1bjNoQUlnYzIrMlJHNStnTmNGVmNzK1hKZUVsNEdaK29qbDNST09tbGwreWU3ZHluUT0iLCJNSUlCZ2pDQ0FTbWdBd0lCQWdJSkFMVWM1QUxpSDVwYk1Bb0dDQ3FHU000OUJBTURNRFl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh3SGhjTk1qTXdNVEExTWpFek1ESXlXaGNOTXpNd01UQXlNakV6TURJeVdqQTJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVjKy9CbCtnb3NwbzZ0ZjlaN2lvNXRkS2RybE4xWWRWbnFFaEVEWERTaHpkQUpQUWlqYW1YSU1IZjh4V1dUYTF6Z29ZVHhPS3BidUp0RHBsejFYcmlUYU1nTUI0d0RBWURWUjBUQkFVd0F3RUIvekFPQmdOVkhROEJBZjhFQkFNQ0FRWXdDZ1lJS29aSXpqMEVBd01EUndBd1JBSWdlbVdRWG5NQWRUYWQySkRKV25nOVU0dUJCTDVtQTdXSTA1SDdvSDdjNmlRQ0lIaVJxTWpOZnpVQXlpdTloNnJPVS9LK2lUUjBJLzNZL05TV3NYSFgrYWNjIl19.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.1TFhjDR4WwQJNgizVGYXz3WE3ajxTdH1wKLQQ71MtrkadSxxOo3yPo_6L9Z03unIU7YK-NRNzSIb5bh5WqTprQ";
328    const WRONG_BUNDLE_ID: &str = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUud3JvbmcifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.WWE31hTB_mcv2O_lf-xI-MNY3d8txc0MzpqFx4QnYDfFIxB95Lo2Fm3r46YSjLLdL7xCWdEJrJP5bHgRCejAGg";
329    const RENEWAL_INFO: &str = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJzaWduZWREYXRlIjoxNjcyOTU2MTU0MDAwfQ.FbK2OL-t6l4892W7fzWyus_g9mIl2CzWLbVt7Kgcnt6zzVulF8bzovgpe0v_y490blROGixy8KDoe2dSU53-Xw";
330    const TRANSACTION_INFO: &str = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlIiwic2lnbmVkRGF0ZSI6MTY3Mjk1NjE1NDAwMH0.PnHWpeIJZ8f2Q218NSGLo_aR0IBEJvC6PxmxKXh-qfYTrZccx2suGl223OSNAX78e4Ylf2yJCG2N-FfU-NIhZQ";
331    const XCODE_BUNDLE_ID: &str = "com.example.naturelab.backyardbirds.example";
332
333    #[test]
334    fn test_app_store_server_notification_decoding() {
335        let verifier = get_signed_data_verifier(Environment::Sandbox, "com.example", None);
336        let notification = verifier
337            .verify_and_decode_notification(TEST_NOTIFICATION)
338            .unwrap();
339        assert_eq!(notification.notification_type, NotificationTypeV2::Test);
340    }
341
342    #[test]
343    fn test_app_store_server_notification_decoding_production() {
344        let verifier = get_signed_data_verifier(Environment::Production, "com.example", None);
345        let error = verifier
346            .verify_and_decode_notification(TEST_NOTIFICATION)
347            .err()
348            .unwrap();
349
350        assert_eq!(error, SignedDataVerifierError::InvalidEnvironment);
351    }
352
353    #[test]
354    fn test_missing_x5c_header() {
355        let verifier = get_signed_data_verifier(Environment::Sandbox, "com.example", None);
356        let result = verifier.verify_and_decode_notification(MISSING_X5C_HEADER_CLAIM);
357        assert_eq!(
358            result.err().unwrap(),
359            SignedDataVerifierError::VerificationFailure
360        );
361    }
362
363    #[test]
364    fn test_wrong_bundle_id_for_server_notification() {
365        let verifier = get_signed_data_verifier(Environment::Sandbox, "com.example", None);
366        let result = verifier.verify_and_decode_notification(WRONG_BUNDLE_ID);
367        assert_eq!(
368            result.err().unwrap(),
369            SignedDataVerifierError::InvalidAppIdentifier
370        );
371    }
372
373    #[test]
374    fn test_wrong_app_apple_id_for_server_notification() {
375        let verifier = get_signed_data_verifier(Environment::Production, "com.example", Some(1235));
376        let result = verifier.verify_and_decode_notification(TEST_NOTIFICATION);
377        assert_eq!(
378            result.err().unwrap(),
379            SignedDataVerifierError::InvalidAppIdentifier
380        );
381    }
382
383    #[test]
384    fn test_renewal_info_decoding() {
385        let verifier = get_signed_data_verifier(Environment::Sandbox, "com.example", None);
386        let renewal_info = verifier
387            .verify_and_decode_renewal_info(RENEWAL_INFO)
388            .unwrap();
389        assert_eq!(renewal_info.environment, Some(Environment::Sandbox));
390        // TODO: Implement TestingUtility to generate signed data from json
391        // assert_eq!(
392        //     "USD",
393        //     renewal_info.currency.as_deref().expect("Expect currency")
394        // );
395        // assert_eq!(
396        //     OfferDiscountType::PayAsYouGo,
397        //     renewal_info
398        //         .offer_discount_type
399        //         .expect("Expect offer_discount_type")
400        // );
401        // assert_eq!(
402        //     vec!["eligible1", "eligible2"],
403        //     renewal_info.eligible_win_back_offer_ids.unwrap()
404        // );
405    }
406
407    #[test]
408    fn test_external_purchase_token_notification_decoding() {
409        let signed_notification =
410            create_signed_data_from_json("resources/models/signedExternalPurchaseTokenNotification.json");
411
412        let signed_data_verifier = get_signed_data_verifier(Environment::LocalTesting, "com.example", Some(55555));
413
414        match signed_data_verifier.verify_and_decode_notification(&signed_notification) {
415            Ok(notification) => {
416
417                assert_eq!(NotificationTypeV2::ExternalPurchaseToken, notification.notification_type);
418                assert_eq!(Subtype::Unreported, notification.subtype.expect("Expect subtype"));
419                assert_eq!("002e14d5-51f5-4503-b5a8-c3a1af68eb20", &notification.notification_uuid);
420                assert_eq!("2.0", &notification.version.expect("Expect version"));
421                assert_eq!(
422                    1698148900,
423                    notification.signed_date.expect("Expect signed_date").timestamp()
424                );
425                assert!(notification.data.is_none());
426                assert!(notification.summary.is_none());
427                assert!(notification.external_purchase_token.is_some());
428
429                if let Some(external_purchase_token) = notification.external_purchase_token {
430                    assert_eq!("b2158121-7af9-49d4-9561-1f588205523e", &external_purchase_token.external_purchase_id.expect("Expect external_purchase_id"));
431                    assert_eq!(1698148950, external_purchase_token.token_creation_date.unwrap().timestamp());
432                    assert_eq!(55555, external_purchase_token.app_apple_id.unwrap());
433                    assert_eq!("com.example", &external_purchase_token.bundle_id.unwrap());
434                } else {
435                    panic!("External purchase token is expected to be Some, but it was None");
436                }
437            }
438            Err(err) => {
439                panic!("Failed to verify and decode app transaction: {:?}", err)
440            }
441        }
442    }
443
444    #[test]
445    fn test_external_purchase_token_sanbox_notification_decoding() {
446        let signed_notification =
447            create_signed_data_from_json("resources/models/signedExternalPurchaseTokenSandboxNotification.json");
448
449        let signed_data_verifier = get_signed_data_verifier(Environment::LocalTesting, "com.example", Some(55555));
450
451        match signed_data_verifier.verify_and_decode_notification(&signed_notification) {
452            Ok(notification) => {
453
454                assert_eq!(NotificationTypeV2::ExternalPurchaseToken, notification.notification_type);
455                assert_eq!(Subtype::Unreported, notification.subtype.expect("Expect subtype"));
456                assert_eq!("002e14d5-51f5-4503-b5a8-c3a1af68eb20", &notification.notification_uuid);
457                assert_eq!("2.0", &notification.version.expect("Expect version"));
458                assert_eq!(
459                    1698148900,
460                    notification.signed_date.expect("Expect signed_date").timestamp()
461                );
462                assert!(notification.data.is_none());
463                assert!(notification.summary.is_none());
464                assert!(notification.external_purchase_token.is_some());
465
466                if let Some(external_purchase_token) = notification.external_purchase_token {
467                    assert_eq!("SANDBOX_b2158121-7af9-49d4-9561-1f588205523e", &external_purchase_token.external_purchase_id.expect("Expect external_purchase_id"));
468                    assert_eq!(1698148950, external_purchase_token.token_creation_date.unwrap().timestamp());
469                    assert_eq!(55555, external_purchase_token.app_apple_id.unwrap());
470                    assert_eq!("com.example", &external_purchase_token.bundle_id.unwrap());
471                } else {
472                    panic!("External purchase token is expected to be Some, but it was None");
473                }
474            }
475            Err(err) => {
476                panic!("Failed to verify and decode app transaction: {:?}", err)
477            }
478        }
479    }
480
481    #[test]
482    fn test_transaction_info_decoding() {
483        let verifier = get_signed_data_verifier(Environment::Sandbox, "com.example", None);
484        let notification = verifier
485            .verify_and_decode_signed_transaction(TRANSACTION_INFO)
486            .unwrap();
487        assert_eq!(notification.environment, Some(Environment::Sandbox));
488    }
489
490    #[test]
491    fn test_malformed_jwt_with_too_many_parts() {
492        let verifier = get_signed_data_verifier(Environment::Sandbox, "com.example", None);
493        let result = verifier.verify_and_decode_notification("a.b.c.d");
494        assert!(result
495            .err()
496            .unwrap()
497            .to_string()
498            .contains("InternalJWTError"));
499    }
500
501    #[test]
502    fn test_malformed_jwt_with_malformed_data() {
503        let verifier = get_signed_data_verifier(Environment::Sandbox, "com.example", None);
504        let result = verifier.verify_and_decode_notification("a.b.c");
505        assert!(result
506            .err()
507            .unwrap()
508            .to_string()
509            .contains("InternalJWTError"));
510    }
511
512    fn get_signed_data_verifier(
513        environment: Environment,
514        bundle_id: &str,
515        app_apple_id: Option<i64>,
516    ) -> SignedDataVerifier {
517        let verifier = SignedDataVerifier::new(
518            vec![ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap()],
519            environment,
520            bundle_id.to_string(),
521            app_apple_id.or(Some(1234)),
522        );
523
524        verifier
525    }
526
527    #[test]
528    fn test_decoded_payloads_app_transaction_decoding() {
529        let signed_app_transaction = create_signed_data_from_json("resources/models/appTransaction.json");
530
531        let signed_data_verifier = get_default_signed_data_verifier();
532
533        match signed_data_verifier.verify_and_decode_app_transaction(&signed_app_transaction) {
534            Ok(app_transaction) => {
535                assert_eq!(
536                    Some(&Environment::LocalTesting),
537                    app_transaction.receipt_type.as_ref()
538                );
539                assert_eq!(
540                    531412,
541                    app_transaction.app_apple_id.expect("Expect app_apple_id")
542                );
543                assert_eq!(
544                    "com.example",
545                    app_transaction.bundle_id.expect("Expect bundle_id")
546                );
547                assert_eq!(
548                    "1.2.3",
549                    app_transaction
550                        .application_version
551                        .expect("Expect application_version")
552                );
553                assert_eq!(
554                    512,
555                    app_transaction
556                        .version_external_identifier
557                        .expect("Expect version_external_identifier")
558                );
559                assert_eq!(
560                    1698148900,
561                    app_transaction
562                        .receipt_creation_date
563                        .expect("Expect receipt_creation_date")
564                        .timestamp()
565                );
566                assert_eq!(
567                    1698148800,
568                    app_transaction
569                        .original_purchase_date
570                        .expect("Expect original_purchase_date")
571                        .timestamp()
572                );
573                assert_eq!(
574                    "1.1.2",
575                    app_transaction
576                        .original_application_version
577                        .expect("Expect original_application_version")
578                );
579                assert_eq!(
580                    "device_verification_value",
581                    app_transaction
582                        .device_verification
583                        .expect("Expect device_verification")
584                );
585                assert_eq!(
586                    "48ccfa42-7431-4f22-9908-7e88983e105a",
587                    app_transaction
588                        .device_verification_nonce
589                        .expect("Expect device_verification_nonce")
590                        .to_string()
591                );
592                assert_eq!(
593                    1698148700,
594                    app_transaction
595                        .preorder_date
596                        .expect("Expect preorder_date")
597                        .timestamp()
598                );
599                assert_eq!(
600                    "71134",
601                    app_transaction
602                        .app_transaction_id
603                        .expect("Expect app_transaction_id")
604                        .to_string()
605                );
606                assert_eq!(
607                    PurchasePlatform::IOS,
608                    app_transaction
609                        .original_platform
610                        .expect("Expect original_platform")
611                );
612            }
613            Err(err) => panic!("Failed to verify and decode app transaction: {:?}", err),
614        }
615    }
616
617    #[test]
618    fn test_decoded_payloads_transaction_decoding() {
619        let signed_transaction = create_signed_data_from_json("resources/models/signedTransaction.json");
620
621        let signed_data_verifier = get_default_signed_data_verifier();
622
623        match signed_data_verifier.verify_and_decode_signed_transaction(&signed_transaction) {
624            Ok(transaction) => {
625                assert_eq!(
626                    "12345",
627                    transaction
628                        .original_transaction_id
629                        .as_deref()
630                        .expect("Expect original_transaction_id")
631                );
632                assert_eq!(
633                    "23456",
634                    transaction
635                        .transaction_id
636                        .as_deref()
637                        .expect("Expect transaction_id")
638                );
639                assert_eq!(
640                    "34343",
641                    transaction
642                        .web_order_line_item_id
643                        .as_deref()
644                        .expect("Expect web_order_line_item_id")
645                );
646                assert_eq!(
647                    "com.example",
648                    transaction.bundle_id.as_deref().expect("Expect bundle_id")
649                );
650                assert_eq!(
651                    "com.example.product",
652                    transaction
653                        .product_id
654                        .as_deref()
655                        .expect("Expect product_id")
656                );
657                assert_eq!(
658                    "55555",
659                    transaction
660                        .subscription_group_identifier
661                        .as_deref()
662                        .expect("Expect subscription_group_identifier")
663                );
664                assert_eq!(
665                    1698148800,
666                    transaction
667                        .original_purchase_date
668                        .expect("Expect original_purchase_date")
669                        .timestamp()
670                );
671                assert_eq!(
672                    1698148900,
673                    transaction
674                        .purchase_date
675                        .expect("Expect purchase_date")
676                        .timestamp()
677                );
678                assert_eq!(
679                    1698148950,
680                    transaction
681                        .revocation_date
682                        .expect("Expect revocation_date")
683                        .timestamp()
684                );
685                assert_eq!(
686                    1698149000,
687                    transaction
688                        .expires_date
689                        .expect("Expect expires_date")
690                        .timestamp()
691                );
692                assert_eq!(1, transaction.quantity.expect("Expect quantity"));
693                assert_eq!(
694                    ProductType::AutoRenewableSubscription,
695                    transaction.r#type.expect("Expect type")
696                );
697                assert_eq!(
698                    "7e3fb20b-4cdb-47cc-936d-99d65f608138",
699                    transaction
700                        .app_account_token
701                        .expect("Expect app_account_token")
702                        .to_string()
703                );
704                assert_eq!(
705                    InAppOwnershipType::Purchased,
706                    transaction
707                        .in_app_ownership_type
708                        .expect("Expect in_app_ownership_type")
709                );
710                assert_eq!(
711                    1698148900,
712                    transaction
713                        .signed_date
714                        .expect("Expect signed_date")
715                        .timestamp()
716                );
717                assert_eq!(
718                    RevocationReason::RefundedDueToIssue,
719                    transaction
720                        .revocation_reason
721                        .expect("Expect revocation_reason")
722                );
723                assert_eq!(
724                    "abc.123",
725                    transaction
726                        .offer_identifier
727                        .as_deref()
728                        .expect("Expect offer_identifier")
729                );
730                assert!(transaction.is_upgraded.unwrap_or_default());
731                assert_eq!(
732                    OfferType::IntroductoryOffer,
733                    transaction.offer_type.expect("Expect offer_type")
734                );
735                assert_eq!(
736                    "USA",
737                    transaction
738                        .storefront
739                        .as_deref()
740                        .expect("Expect storefront")
741                );
742                assert_eq!(
743                    "143441",
744                    transaction
745                        .storefront_id
746                        .as_deref()
747                        .expect("Expect storefront_id")
748                );
749                assert_eq!(
750                    TransactionReason::Purchase,
751                    transaction
752                        .transaction_reason
753                        .expect("Expect transaction_reason")
754                );
755                assert_eq!(
756                    Environment::LocalTesting,
757                    transaction.environment.expect("Expect environment")
758                );
759                assert_eq!(10990, transaction.price.expect("Expect price"));
760                assert_eq!(
761                    "USD",
762                    transaction.currency.as_deref().expect("Expect currency")
763                );
764                assert_eq!(
765                    OfferDiscountType::PayAsYouGo,
766                    transaction
767                        .offer_discount_type
768                        .expect("Expect offer_discount_type")
769                );
770                assert_eq!(
771                    "71134",
772                    transaction
773                        .app_transaction_id
774                        .expect("Expect app_transaction_id")
775                        .to_string()
776                );
777                assert_eq!(
778                    "P1Y",
779                    transaction
780                        .offer_period
781                        .expect("Expect offer_period")
782                        .to_string()
783                );
784            }
785            Err(err) => panic!("Failed to verify and decode signed transaction: {:?}", err),
786        }
787    }
788
789    #[test]
790    fn test_decoded_payloads_renewal_info_decoding() {
791        let signed_renewal_info = create_signed_data_from_json("resources/models/signedRenewalInfo.json");
792
793        let signed_data_verifier = get_default_signed_data_verifier();
794
795        match signed_data_verifier.verify_and_decode_renewal_info(&signed_renewal_info) {
796            Ok(renewal_info) => {
797                assert_eq!(
798                    ExpirationIntent::CustomerCancelled,
799                    renewal_info
800                        .expiration_intent
801                        .expect("Expect expiration_intent")
802                );
803                assert_eq!(
804                    "12345",
805                    renewal_info
806                        .original_transaction_id
807                        .as_deref()
808                        .expect("Expect original_transaction_id")
809                );
810                assert_eq!(
811                    "com.example.product.2",
812                    renewal_info
813                        .auto_renew_product_id
814                        .as_deref()
815                        .expect("Expect auto_renew_product_id")
816                );
817                assert_eq!(
818                    "com.example.product",
819                    renewal_info
820                        .product_id
821                        .as_deref()
822                        .expect("Expect product_id")
823                );
824                assert_eq!(
825                    AutoRenewStatus::On,
826                    renewal_info
827                        .auto_renew_status
828                        .expect("Expect auto_renew_status")
829                );
830                assert!(renewal_info.is_in_billing_retry_period.unwrap_or_default());
831                assert_eq!(
832                    PriceIncreaseStatus::CustomerHasNotResponded,
833                    renewal_info
834                        .price_increase_status
835                        .expect("Expect price_increase_status")
836                );
837                assert_eq!(
838                    1698148900,
839                    renewal_info
840                        .grace_period_expires_date
841                        .expect("Expect grace_period_expires_date")
842                        .timestamp()
843                );
844                assert_eq!(
845                    OfferType::PromotionalOffer,
846                    renewal_info.offer_type.expect("Expect offer_type")
847                );
848                assert_eq!(
849                    "abc.123",
850                    renewal_info
851                        .offer_identifier
852                        .as_deref()
853                        .expect("Expect offer_identifier")
854                );
855                assert_eq!(
856                    1698148800,
857                    renewal_info
858                        .signed_date
859                        .expect("Expect signed_date")
860                        .timestamp()
861                );
862                assert_eq!(
863                    Environment::LocalTesting,
864                    renewal_info.environment.expect("Expect environment")
865                );
866                assert_eq!(
867                    1698148800,
868                    renewal_info
869                        .recent_subscription_start_date
870                        .expect("Expect recent_subscription_start_date")
871                        .timestamp()
872                );
873                assert_eq!(
874                    1698148850,
875                    renewal_info
876                        .renewal_date
877                        .expect("Expect renewal_date")
878                        .timestamp()
879                );
880                assert_eq!(
881                    "71134",
882                    renewal_info
883                        .app_transaction_id
884                        .expect("Expect app_transaction_id")
885                        .to_string()
886                );
887                assert_eq!(
888                    "P1Y",
889                    renewal_info
890                        .offer_period
891                        .expect("Expect offer_period")
892                        .to_string()
893                );
894                assert_eq!(
895                    "7e3fb20b-4cdb-47cc-936d-99d65f608138",
896                    renewal_info
897                        .app_account_token
898                        .expect("Expect app_account_token")
899                        .to_string()
900                );
901            }
902            Err(err) => panic!("Failed to verify and decode renewal info: {:?}", err),
903        }
904    }
905
906    #[test]
907    fn test_decoded_payloads_notification_decoding() {
908        let signed_notification = create_signed_data_from_json("resources/models/signedNotification.json");
909
910        let signed_data_verifier = get_default_signed_data_verifier();
911
912        match signed_data_verifier.verify_and_decode_notification(&signed_notification) {
913            Ok(notification) => {
914                assert_eq!(
915                    NotificationTypeV2::Subscribed,
916                    notification.notification_type
917                );
918                assert_eq!(
919                    Subtype::InitialBuy,
920                    notification.subtype.expect("Expect subtype")
921                );
922                assert_eq!(
923                    "002e14d5-51f5-4503-b5a8-c3a1af68eb20",
924                    notification.notification_uuid
925                );
926                assert_eq!(
927                    "2.0",
928                    notification.version.as_deref().expect("Expect version")
929                );
930                assert_eq!(
931                    1698148900,
932                    notification
933                        .signed_date
934                        .expect("Expect signed_date")
935                        .timestamp()
936                );
937                assert!(notification.data.is_some());
938                assert!(notification.summary.is_none());
939                assert!(notification.external_purchase_token.is_none());
940
941                if let Some(data) = notification.data {
942                    assert_eq!(
943                        Environment::LocalTesting,
944                        data.environment.expect("Expect environment")
945                    );
946                    assert_eq!(41234, data.app_apple_id.expect("Expect app_apple_id"));
947                    assert_eq!(
948                        "com.example",
949                        data.bundle_id.as_deref().expect("Expect bundle_id")
950                    );
951                    assert_eq!(
952                        "1.2.3",
953                        data.bundle_version
954                            .as_deref()
955                            .expect("Expect bundle_version")
956                    );
957                    assert_eq!(
958                        "signed_transaction_info_value",
959                        data.signed_transaction_info
960                            .as_deref()
961                            .expect("Expect signed_transaction_info")
962                    );
963                    assert_eq!(
964                        "signed_renewal_info_value",
965                        data.signed_renewal_info
966                            .as_deref()
967                            .expect("Expect signed_renewal_info")
968                    );
969                    assert_eq!(Status::Active, data.status.expect("Expect status"));
970                    assert!(data.consumption_request_reason.is_none());
971                } else {
972                    panic!("Data field is expected to be present in the notification");
973                }
974            }
975            Err(err) => panic!("Failed to verify and decode notification: {:?}", err),
976        }
977    }
978
979    #[test]
980    fn test_consumption_request_notification_decoding() {
981        let signed_notification = create_signed_data_from_json("resources/models/signedConsumptionRequestNotification.json");
982
983        let signed_data_verifier = get_default_signed_data_verifier();
984
985        match signed_data_verifier.verify_and_decode_notification(&signed_notification) {
986            Ok(notification) => {
987                assert_eq!(NotificationTypeV2::ConsumptionRequest, notification.notification_type);
988                assert!(notification.subtype.is_none());
989                assert_eq!("002e14d5-51f5-4503-b5a8-c3a1af68eb20", notification.notification_uuid);
990                assert_eq!("2.0", notification.version.unwrap());
991                assert_eq!(1698148900, notification.signed_date.unwrap().timestamp());
992                assert!(notification.data.is_some());
993                assert!(notification.summary.is_none());
994                assert!(notification.external_purchase_token.is_none());
995
996                if let Some(data) = notification.data {
997                    assert_eq!(Environment::LocalTesting, data.environment.unwrap());
998                    assert_eq!(41234, data.app_apple_id.unwrap());
999                    assert_eq!("com.example", data.bundle_id.unwrap());
1000                    assert_eq!("1.2.3", data.bundle_version.unwrap());
1001                    assert_eq!("signed_transaction_info_value", data.signed_transaction_info.unwrap());
1002                    assert_eq!("signed_renewal_info_value", data.signed_renewal_info.unwrap());
1003                    assert_eq!(Status::Active, data.status.unwrap());
1004                    assert_eq!(ConsumptionRequestReason::UnintendedPurchase, data.consumption_request_reason.unwrap());
1005                }
1006            }
1007            Err(err) => panic!(
1008                "Failed to verify and decode consumption request notification: {:?}",
1009                err
1010            ),
1011        }
1012    }
1013
1014    #[test]
1015    fn test_summary_notification_decoding() {
1016        let signed_summary_notification =
1017            create_signed_data_from_json("resources/models/signedSummaryNotification.json");
1018
1019        let signed_data_verifier = get_default_signed_data_verifier();
1020
1021        match signed_data_verifier.verify_and_decode_notification(&signed_summary_notification) {
1022            Ok(notification) => {
1023                assert_eq!(
1024                    NotificationTypeV2::RenewalExtension,
1025                    notification.notification_type
1026                );
1027                assert_eq!(
1028                    Subtype::Summary,
1029                    notification.subtype.expect("Expect subtype")
1030                );
1031                assert_eq!(
1032                    "002e14d5-51f5-4503-b5a8-c3a1af68eb20",
1033                    notification.notification_uuid
1034                );
1035                assert_eq!(
1036                    "2.0",
1037                    notification.version.as_deref().expect("Expect version")
1038                );
1039                assert_eq!(
1040                    1698148900,
1041                    notification
1042                        .signed_date
1043                        .expect("Expect signed_date")
1044                        .timestamp()
1045                );
1046                assert!(notification.data.is_none());
1047                assert!(notification.summary.is_some());
1048                assert!(notification.external_purchase_token.is_none());
1049
1050                if let Some(summary) = notification.summary {
1051                    assert_eq!(
1052                        Environment::LocalTesting,
1053                        summary.environment.expect("Expect environment")
1054                    );
1055                    assert_eq!(41234, summary.app_apple_id.expect("Expect app_apple_id"));
1056                    assert_eq!(
1057                        "com.example",
1058                        summary.bundle_id.as_deref().expect("Expect bundle_id")
1059                    );
1060                    assert_eq!(
1061                        "com.example.product",
1062                        summary.product_id.as_deref().expect("Expect product_id")
1063                    );
1064                    assert_eq!(
1065                        "efb27071-45a4-4aca-9854-2a1e9146f265",
1066                        summary.request_identifier
1067                    );
1068                    assert_eq!(vec!["CAN", "USA", "MEX"], summary.storefront_country_codes);
1069                    assert_eq!(5, summary.succeeded_count);
1070                    assert_eq!(2, summary.failed_count);
1071                } else {
1072                    panic!("Summary field is expected to be present in the notification");
1073                }
1074            }
1075            Err(err) => panic!(
1076                "Failed to verify and decode summary notification: {:?}",
1077                err
1078            ),
1079        }
1080    }
1081
1082    #[test]
1083    fn test_xcode_signed_app_transaction() {
1084        let verifier = get_signed_data_verifier(Environment::Xcode, XCODE_BUNDLE_ID, None);
1085        let encoded_app_transaction = fs::read_to_string("resources/xcode/xcode-signed-app-transaction").expect("Failed to read file");
1086
1087        if let Ok(app_transaction) = verifier.verify_and_decode_app_transaction(&encoded_app_transaction) {
1088            assert_eq!(XCODE_BUNDLE_ID, app_transaction.bundle_id.as_deref().expect("Expect bundle_id"));
1089            assert_eq!("1", app_transaction.application_version.as_deref().expect("Expect application_version"));
1090            assert_eq!(None, app_transaction.version_external_identifier);
1091            assert_eq!(-62135769600000, app_transaction.original_purchase_date.expect("Expect value").timestamp_millis());
1092            assert_eq!("1", app_transaction.original_application_version.as_deref().expect("Expect original_application_version"));
1093            assert_eq!("cYUsXc53EbYc0pOeXG5d6/31LGHeVGf84sqSN0OrJi5u/j2H89WWKgS8N0hMsMlf", app_transaction.device_verification.as_deref().expect("Expect device_verification"));
1094            assert_eq!("48c8b92d-ce0d-4229-bedf-e61b4f9cfc92", app_transaction.device_verification_nonce.expect("Expect device_verification_nonce").to_string());
1095            assert_eq!(None, app_transaction.preorder_date);
1096            assert_eq!(Environment::Xcode, app_transaction.receipt_type.unwrap());
1097        } else {
1098            panic!("Failed to verify and decode app transaction");
1099        }
1100    }
1101
1102    #[test]
1103    fn test_xcode_signed_transaction() {
1104        let verifier = get_signed_data_verifier(Environment::Xcode, XCODE_BUNDLE_ID, None);
1105        let encoded_app_transaction = fs::read_to_string("resources/xcode/xcode-signed-transaction").expect("Failed to read file");
1106
1107        if let Ok(transaction) = verifier.verify_and_decode_signed_transaction(&encoded_app_transaction) {
1108            assert_eq!("0", transaction.original_transaction_id.as_deref().expect("Expect original_transaction_id"));
1109            assert_eq!("0", transaction.transaction_id.as_deref().expect("Expect transaction_id"));
1110            assert_eq!("0", transaction.web_order_line_item_id.as_deref().expect("Expect web_order_line_item_id"));
1111            assert_eq!(XCODE_BUNDLE_ID, transaction.bundle_id.as_deref().expect("Expect bundle_id"));
1112            assert_eq!("pass.premium", transaction.product_id.as_deref().expect("Expect product_id"));
1113            assert_eq!("6F3A93AB", transaction.subscription_group_identifier.as_deref().expect("Expect subscription_group_identifier"));
1114            assert_eq!(1697679936049, transaction.purchase_date.unwrap().timestamp_millis());
1115            assert_eq!(1697679936049, transaction.original_purchase_date.unwrap().timestamp_millis());
1116            assert_eq!(1700358336049, transaction.expires_date.unwrap().timestamp_millis());
1117            assert_eq!(1, transaction.quantity.expect("Expect quantity"));
1118            assert_eq!(ProductType::AutoRenewableSubscription, transaction.r#type.expect("Expect type"));
1119            assert_eq!(None, transaction.app_account_token);
1120            assert_eq!(InAppOwnershipType::Purchased, transaction.in_app_ownership_type.expect("Expect in_app_ownership_type"));
1121            assert_eq!(1697679936056, transaction.signed_date.unwrap().timestamp_millis());
1122            assert_eq!(None, transaction.revocation_reason);
1123            assert_eq!(None, transaction.revocation_date);
1124            assert!(!transaction.is_upgraded.unwrap_or(false));
1125            assert_eq!(OfferType::IntroductoryOffer, transaction.offer_type.expect("Expect offer_type"));
1126            assert_eq!(None, transaction.offer_identifier);
1127            assert_eq!(Environment::Xcode, transaction.environment.expect("Expect environment"));
1128            assert_eq!("USA", transaction.storefront.expect("Expect storefront"));
1129            assert_eq!("143441", transaction.storefront_id.as_deref().expect("Expect storefront_id"));
1130            assert_eq!(TransactionReason::Purchase, transaction.transaction_reason.expect("Expect transaction_reason"));
1131        } else {
1132            panic!("Failed to verify and decode signed transaction");
1133        }
1134    }
1135
1136    #[test]
1137    fn test_xcode_signed_renewal_info() {
1138        let verifier = get_signed_data_verifier(Environment::Xcode, XCODE_BUNDLE_ID, None);
1139        let encoded_renewal_info = fs::read_to_string("resources/xcode/xcode-signed-renewal-info").expect("Failed to read file");
1140
1141        if let Ok(renewal_info) = verifier.verify_and_decode_renewal_info(&encoded_renewal_info) {
1142            assert_eq!(None, renewal_info.expiration_intent);
1143            assert_eq!("0", renewal_info.original_transaction_id.as_deref().expect("Expect original_transaction_id"));
1144            assert_eq!("pass.premium", renewal_info.auto_renew_product_id.as_deref().expect("Expect auto_renew_product_id"));
1145            assert_eq!("pass.premium", renewal_info.product_id.as_deref().expect("Expect product_id"));
1146            assert_eq!(AutoRenewStatus::On, renewal_info.auto_renew_status.expect("Expect auto_renew_status"));
1147            assert_eq!(None, renewal_info.is_in_billing_retry_period);
1148            assert_eq!(None, renewal_info.price_increase_status);
1149            assert_eq!(None, renewal_info.grace_period_expires_date);
1150            assert_eq!(None, renewal_info.offer_type);
1151            assert_eq!(None, renewal_info.offer_identifier);
1152            assert_eq!(1697679936711, renewal_info.signed_date.unwrap().timestamp_millis());
1153            assert_eq!(Environment::Xcode, renewal_info.environment.expect("Expect environment"));
1154            assert_eq!(1697679936049, renewal_info.recent_subscription_start_date.unwrap().timestamp_millis());
1155            assert_eq!(1700358336049, renewal_info.renewal_date.unwrap().timestamp_millis());
1156        } else {
1157            panic!("Failed to verify and decode signed renewal info");
1158        }
1159    }
1160
1161    #[test]
1162    fn test_xcode_signed_app_transaction_with_production_environment() {
1163        let verifier = get_signed_data_verifier(Environment::Production, XCODE_BUNDLE_ID, None);
1164        let encoded_app_transaction = fs::read_to_string("resources/xcode/xcode-signed-app-transaction").expect("Failed to read file");
1165
1166        if let Err(_) = verifier.verify_and_decode_app_transaction(&encoded_app_transaction) {
1167            return;
1168        }
1169        panic!("Expected VerificationException, but no exception was raised");
1170    }
1171
1172    fn get_default_signed_data_verifier() -> SignedDataVerifier {
1173        return get_signed_data_verifier(Environment::LocalTesting, "com.example", None);
1174    }
1175
1176    fn create_signed_data_from_json(path: &str) -> String {
1177        let json_payload = fs::read_to_string(path).expect("Failed to read JSON file");
1178        let json: Map<String, Value> =
1179            serde_json::from_str(json_payload.as_str()).expect("Expect JSON");
1180
1181        let header = jsonwebtoken::Header::new(Algorithm::ES256);
1182        let private_key = generate_p256_private_key();
1183        let key = jsonwebtoken::EncodingKey::from_ec_der(private_key.as_ref());
1184        let payload = jsonwebtoken::encode(&header, &json, &key).expect("Failed to encode JWT");
1185        payload
1186    }
1187
1188    fn generate_p256_private_key() -> Vec<u8> {
1189        let rng = ring::rand::SystemRandom::new();
1190        let private_key =
1191            ring::signature::EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng)
1192                .expect("Failed to generate private key");
1193
1194        private_key.as_ref().to_vec()
1195    }
1196}