├── src ├── x509 │ └── mod.rs ├── api_client │ ├── reqwest_transport │ │ └── mod.rs │ ├── mod.rs │ ├── api │ │ └── mod.rs │ └── transport.rs ├── asn1 │ └── mod.rs ├── primitives │ ├── advanced_commerce │ │ ├── in_app_request.rs │ │ ├── in_app_request_version.rs │ │ ├── refund_type.rs │ │ ├── reason.rs │ │ ├── request_refund_response.rs │ │ ├── subscription_cancel_response.rs │ │ ├── subscription_revoke_response.rs │ │ ├── subscription_migrate_response.rs │ │ ├── in_app_request_operation.rs │ │ ├── subscription_change_metadata_response.rs │ │ ├── subscription_price_change_response.rs │ │ ├── effective.rs │ │ ├── refund_reason.rs │ │ ├── refund.rs │ │ ├── subscription_reactivate_item.rs │ │ ├── period.rs │ │ ├── subscription_modify_remove_item.rs │ │ ├── offer_reason.rs │ │ ├── subscription_price_change_item.rs │ │ ├── base_response.rs │ │ ├── descriptors.rs │ │ ├── subscription_modify_period_change.rs │ │ ├── subscription_migrate_descriptors.rs │ │ ├── offer_period.rs │ │ ├── one_time_charge_item.rs │ │ ├── subscription_migrate_item.rs │ │ ├── subscription_migrate_renewal_item.rs │ │ ├── offer.rs │ │ ├── subscription_cancel_request.rs │ │ ├── request_refund_item.rs │ │ ├── request_info.rs │ │ ├── request_refund_request.rs │ │ ├── mod.rs │ │ ├── subscription_modify_descriptors.rs │ │ ├── subscription_change_metadata_descriptors.rs │ │ ├── subscription_create_item.rs │ │ ├── subscription_reactivate_in_app_request.rs │ │ ├── one_time_charge_create_request.rs │ │ ├── subscription_price_change_request.rs │ │ ├── subscription_modify_add_item.rs │ │ ├── subscription_change_metadata_item.rs │ │ ├── subscription_revoke_request.rs │ │ ├── subscription_create_request.rs │ │ └── subscription_modify_change_item.rs │ ├── transaction_reason.rs │ ├── user_status.rs │ ├── auto_renew_status.rs │ ├── platform.rs │ ├── offer_type.rs │ ├── revocation_reason.rs │ ├── order_lookup_status.rs │ ├── retention_messaging │ │ ├── image_state.rs │ │ ├── message_state.rs │ │ ├── mod.rs │ │ ├── message.rs │ │ ├── realtime_request_body.rs │ │ ├── default_configuration_request.rs │ │ ├── get_image_list_response.rs │ │ ├── get_message_list_response.rs │ │ ├── get_image_list_response_item.rs │ │ ├── get_message_list_response_item.rs │ │ ├── alternate_product.rs │ │ ├── realtime_response_body.rs │ │ ├── promotional_offer.rs │ │ ├── upload_message_image.rs │ │ ├── decoded_realtime_request_body.rs │ │ └── upload_message_request_body.rs │ ├── advanced_commerce_renewal_item.rs │ ├── refund_preference.rs │ ├── extend_reason_code.rs │ ├── consumption_status.rs │ ├── price_increase_status.rs │ ├── expiration_intent.rs │ ├── purchase_platform.rs │ ├── offer_discount_type.rs │ ├── transaction_info_response.rs │ ├── play_time.rs │ ├── product_type.rs │ ├── account_tenure.rs │ ├── delivery_status.rs │ ├── response_body_v2.rs │ ├── consumption_request_reason.rs │ ├── send_test_notification_response.rs │ ├── token_type.rs │ ├── in_app_ownership_type.rs │ ├── mass_extend_renewal_date_response.rs │ ├── status.rs │ ├── app_transaction_info_response.rs │ ├── environment.rs │ ├── lifetime_dollars_purchased.rs │ ├── advanced_commerce_renewal_info.rs │ ├── lifetime_dollars_refunded.rs │ ├── order_lookup_response.rs │ ├── send_attempt_result.rs │ ├── first_send_attempt_result.rs │ ├── check_test_notification_response.rs │ ├── notification_history_response_item.rs │ ├── refund_history_response.rs │ ├── subscription_group_identifier_item.rs │ ├── send_attempt_item.rs │ ├── extend_renewal_date_request.rs │ ├── notification_history_response.rs │ ├── status_response.rs │ ├── subtype.rs │ ├── advanced_commerce_transaction_item.rs │ ├── last_transactions_item.rs │ ├── notification_type_v2.rs │ ├── extend_renewal_date_response.rs │ ├── advanced_commerce_transaction_info.rs │ ├── update_app_account_token_request.rs │ ├── mass_extend_renewal_date_request.rs │ ├── history_response.rs │ ├── mass_extend_renewal_date_status_response.rs │ ├── mod.rs │ ├── external_purchase_token.rs │ ├── summary.rs │ ├── data.rs │ ├── transaction_history_request.rs │ ├── response_body_v2_decoded_payload.rs │ └── notification_history_request.rs ├── lib.rs └── utils.rs ├── .github ├── FUNDING.yml ├── workflows │ └── rust.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── tests ├── resources │ ├── models │ │ ├── apiUnknownError.json │ │ ├── transactionInfoResponse.json │ │ ├── apiException.json │ │ ├── appTransactionInfoResponse.json │ │ ├── apiTooManyRequestsException.json │ │ ├── invalidTransactionIdError.json │ │ ├── requestTestNotificationResponse.json │ │ ├── transactionIdNotFoundError.json │ │ ├── extendRenewalDateForAllActiveSubscribersResponse.json │ │ ├── appTransactionDoesNotExistError.json │ │ ├── lookupOrderIdResponse.json │ │ ├── invalidAppAccountTokenUUIDError.json │ │ ├── familyTransactionNotSupportedError.json │ │ ├── transactionIdNotOriginalTransactionId.json │ │ ├── extendSubscriptionRenewalDateResponse.json │ │ ├── getImageListResponse.json │ │ ├── getMessageListResponse.json │ │ ├── getRefundHistoryResponse.json │ │ ├── getStatusOfSubscriptionRenewalDateExtensionsResponse.json │ │ ├── requestRefundResponse.json │ │ ├── subscriptionCancelResponse.json │ │ ├── subscriptionMigrateResponse.json │ │ ├── subscriptionRevokeResponse.json │ │ ├── subscriptionChangeMetadataResponse.json │ │ ├── subscriptionPriceChangeResponse.json │ │ ├── decodedRealtimeRequest.json │ │ ├── transactionHistoryResponse.json │ │ ├── getTestNotificationStatusResponse.json │ │ ├── transactionHistoryResponseWithMalformedEnvironment.json │ │ ├── transactionHistoryResponseWithMalformedAppAppleId.json │ │ ├── signedExternalPurchaseTokenNotification.json │ │ ├── signedExternalPurchaseTokenSandboxNotification.json │ │ ├── signedNotification.json │ │ ├── appTransaction.json │ │ ├── signedConsumptionRequestNotification.json │ │ ├── signedSummaryNotification.json │ │ ├── getNotificationHistoryResponse.json │ │ ├── signedRenewalInfo.json │ │ ├── signedTransaction.json │ │ └── getAllSubscriptionStatusesResponse.json │ ├── certs │ │ ├── testCA.der │ │ ├── testSigningKey.p8 │ │ └── SubscriptionKey_L256SYR32L.p8 │ ├── mock_signed_data │ │ ├── legacyTransaction │ │ ├── transactionInfo │ │ ├── renewalInfo │ │ ├── wrongBundleId │ │ ├── missingX5CHeaderClaim │ │ └── testNotification │ └── xcode │ │ ├── xcode-signed-renewal-info │ │ ├── xcode-signed-app-transaction │ │ ├── xcode-signed-transaction │ │ ├── xcode-app-receipt-empty │ │ └── xcode-app-receipt-with-transaction ├── chain_verifier_ocsp.rs ├── promotional_offer_signature_creator.rs ├── common │ └── transport_mock.rs └── receipt_utility.rs ├── LICENSE ├── .gitignore └── Cargo.toml /src/x509/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod x509; 2 | -------------------------------------------------------------------------------- /src/api_client/reqwest_transport/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod reqwest_http_transport; 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [tikhop] 4 | -------------------------------------------------------------------------------- /src/asn1/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "receipt-utility", feature = "ocsp"))] 2 | pub mod asn1_basics; -------------------------------------------------------------------------------- /tests/resources/models/apiUnknownError.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 9990000, 3 | "errorMessage": "Testing error." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/transactionInfoResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "signed_transaction_info_value" 3 | } -------------------------------------------------------------------------------- /tests/resources/models/apiException.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 5000000, 3 | "errorMessage": "An unknown error occurred." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/appTransactionInfoResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedAppTransactionInfo": "signed_app_transaction_info_value" 3 | } -------------------------------------------------------------------------------- /tests/resources/certs/testCA.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namecare/app-store-server-library-rust/HEAD/tests/resources/certs/testCA.der -------------------------------------------------------------------------------- /tests/resources/models/apiTooManyRequestsException.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4290000, 3 | "errorMessage": "Rate limit exceeded." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/invalidTransactionIdError.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4000006, 3 | "errorMessage": "Invalid transaction id." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/requestTestNotificationResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "testNotificationToken": "ce3af791-365e-4c60-841b-1674b43c1609" 3 | } -------------------------------------------------------------------------------- /tests/resources/models/transactionIdNotFoundError.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4040010, 3 | "errorMessage": "Transaction id not found." 4 | } -------------------------------------------------------------------------------- /tests/resources/mock_signed_data/legacyTransaction: -------------------------------------------------------------------------------- 1 | ewoicHVyY2hhc2UtaW5mbyIgPSAiZXdvaWRISmhibk5oWTNScGIyNHRhV1FpSUQwZ0lqTXpPVGt6TXprNUlqc0tmUW89IjsKfQo= -------------------------------------------------------------------------------- /tests/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestIdentifier": "758883e8-151b-47b7-abd0-60c4d804c2f5" 3 | } -------------------------------------------------------------------------------- /tests/resources/models/appTransactionDoesNotExistError.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4040019, 3 | "errorMessage": "No AppTransaction exists for the customer." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/lookupOrderIdResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 1, 3 | "signedTransactions": [ 4 | "signed_transaction_one", 5 | "signed_transaction_two" 6 | ] 7 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/in_app_request.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | /// Trait for Advanced Commerce in-app requests 4 | pub trait AdvancedCommerceInAppRequest: Serialize {} -------------------------------------------------------------------------------- /tests/resources/models/invalidAppAccountTokenUUIDError.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4000183, 3 | "errorMessage": "Invalid request. The app account token field must be a valid UUID." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/familyTransactionNotSupportedError.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4000185, 3 | "errorMessage": "Invalid request. Family Sharing transactions aren't supported by this endpoint." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/transactionIdNotOriginalTransactionId.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4000187, 3 | "errorMessage": "Invalid request. The transaction ID provided is not an original transaction ID." 4 | } -------------------------------------------------------------------------------- /tests/resources/models/extendSubscriptionRenewalDateResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "originalTransactionId": "2312412", 3 | "webOrderLineItemId": "9993", 4 | "success": true, 5 | "effectiveDate": 1698148900000 6 | } -------------------------------------------------------------------------------- /tests/resources/models/getImageListResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "imageIdentifiers": [ 3 | { 4 | "imageIdentifier": "a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890", 5 | "imageState": "APPROVED" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /tests/resources/models/getMessageListResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "messageIdentifiers": [ 3 | { 4 | "messageIdentifier": "a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890", 5 | "messageState": "APPROVED" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /src/api_client/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "api-client-reqwest", feature = "api-client-reqwest-native-tls"))] 2 | pub mod reqwest_transport; 3 | 4 | pub mod transport; 5 | pub mod error; 6 | pub mod api_client; 7 | pub mod api; -------------------------------------------------------------------------------- /tests/resources/models/getRefundHistoryResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactions": [ 3 | "signed_transaction_one", 4 | "signed_transaction_two" 5 | ], 6 | "revision": "revision_output", 7 | "hasMore": true 8 | } -------------------------------------------------------------------------------- /src/api_client/api/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "api-client")] 2 | pub mod app_store_server_api; 3 | 4 | #[cfg(feature = "api-client")] 5 | pub mod advanced_commerce_api; 6 | 7 | #[cfg(feature = "api-client")] 8 | pub mod retention_messaging_api; -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/in_app_request_version.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 4 | pub enum InAppRequestVersion { 5 | #[serde(rename = "1")] 6 | V1, 7 | } -------------------------------------------------------------------------------- /tests/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestIdentifier": "20fba8a0-2b80-4a7d-a17f-85c1854727f8", 3 | "complete": true, 4 | "completeDate": 1698148900000, 5 | "succeededCount": 30, 6 | "failedCount": 2 7 | } -------------------------------------------------------------------------------- /tests/resources/certs/testSigningKey.p8: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgSpP55ELdXswj9JRZ 3 | APRwtTfS4CNRqpKIs+28rNHiPAqhRANCAASs8nLES7b+goKslppNVOurf0MonZdw 4 | 3pb6TxS8Z/5j+UNY1sWK1ChxpuwNS9I3R50cfdQo/lA9PPhw6XIg8ytd 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /src/primitives/transaction_reason.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 4 | pub enum TransactionReason { 5 | #[serde(rename = "PURCHASE")] 6 | Purchase, 7 | #[serde(rename = "RENEWAL")] 8 | Renewal, 9 | } 10 | -------------------------------------------------------------------------------- /tests/resources/models/requestRefundResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoidGVzdF90cmFuc2FjdGlvbl9pZCJ9.test_signature", 3 | "signedRenewalInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZW5ld2FsSWQiOiJ0ZXN0X3JlbmV3YWxfaWQifQ.test_signature" 4 | } -------------------------------------------------------------------------------- /tests/resources/certs/SubscriptionKey_L256SYR32L.p8: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgtbQNyFBW6TEqa8nt 3 | lg25TdOSm5eEhvDt+1i884eLA52gCgYIKoZIzj0DAQehRANCAATTzFA+pUAk83x/ 4 | Ys/xY1Mm11YzN1kTy62X4KKvTbp6Nsi5l/QcFWhrt//+iQWgJcUJE8jXpUTRhIMz 5 | rFSv8oF6 6 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/resources/models/subscriptionCancelResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoidGVzdF90cmFuc2FjdGlvbl9pZCJ9.test_signature", 3 | "signedRenewalInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZW5ld2FsSWQiOiJ0ZXN0X3JlbmV3YWxfaWQifQ.test_signature" 4 | } -------------------------------------------------------------------------------- /tests/resources/models/subscriptionMigrateResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoidGVzdF90cmFuc2FjdGlvbl9pZCJ9.test_signature", 3 | "signedRenewalInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZW5ld2FsSWQiOiJ0ZXN0X3JlbmV3YWxfaWQifQ.test_signature" 4 | } -------------------------------------------------------------------------------- /tests/resources/models/subscriptionRevokeResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoidGVzdF90cmFuc2FjdGlvbl9pZCJ9.test_signature", 3 | "signedRenewalInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZW5ld2FsSWQiOiJ0ZXN0X3JlbmV3YWxfaWQifQ.test_signature" 4 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/refund_type.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The type of refund. 4 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 5 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 6 | pub enum RefundType { 7 | Full, 8 | Prorated, 9 | Custom, 10 | } -------------------------------------------------------------------------------- /tests/resources/models/subscriptionChangeMetadataResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoidGVzdF90cmFuc2FjdGlvbl9pZCJ9.test_signature", 3 | "signedRenewalInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZW5ld2FsSWQiOiJ0ZXN0X3JlbmV3YWxfaWQifQ.test_signature" 4 | } -------------------------------------------------------------------------------- /tests/resources/models/subscriptionPriceChangeResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoidGVzdF90cmFuc2FjdGlvbl9pZCJ9.test_signature", 3 | "signedRenewalInfo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZW5ld2FsSWQiOiJ0ZXN0X3JlbmV3YWxfaWQifQ.test_signature" 4 | } -------------------------------------------------------------------------------- /tests/resources/models/decodedRealtimeRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "originalTransactionId": "99371282", 3 | "appAppleId": 531412, 4 | "productId": "com.example.product", 5 | "userLocale": "en-US", 6 | "requestIdentifier": "3db5c98d-8acf-4e29-831e-8e1f82f9f6e9", 7 | "environment": "LocalTesting", 8 | "signedDate": 1698148900000 9 | } -------------------------------------------------------------------------------- /tests/resources/models/transactionHistoryResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "revision": "revision_output", 3 | "hasMore": true, 4 | "bundleId": "com.example", 5 | "appAppleId": 323232, 6 | "environment": "LocalTesting", 7 | "signedTransactions": [ 8 | "signed_transaction_value", 9 | "signed_transaction_value2" 10 | ] 11 | } -------------------------------------------------------------------------------- /src/primitives/user_status.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 4 | #[repr(u8)] 5 | pub enum UserStatus { 6 | Undeclared = 0, 7 | Active = 1, 8 | Suspended = 2, 9 | Terminated = 3, 10 | LimitedAccess = 4, 11 | } 12 | -------------------------------------------------------------------------------- /tests/resources/models/getTestNotificationStatusResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedPayload": "signed_payload", 3 | "sendAttempts": [ 4 | { 5 | "attemptDate": 1698148900000, 6 | "sendAttemptResult": "NO_RESPONSE" 7 | }, { 8 | "attemptDate": 1698148950000, 9 | "sendAttemptResult": "SUCCESS" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/reason.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The reason for the Advanced Commerce request. 4 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 5 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 6 | pub enum Reason { 7 | Upgrade, 8 | Downgrade, 9 | ApplyOffer, 10 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/request_refund_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::base_response::AdvancedCommerceResponse; 2 | 3 | /// The response data for a refund request. 4 | /// 5 | /// [RequestRefundResponse](https://developer.apple.com/documentation/advancedcommerceapi/requestrefundresponse) 6 | pub type RequestRefundResponse = AdvancedCommerceResponse; -------------------------------------------------------------------------------- /tests/resources/models/transactionHistoryResponseWithMalformedEnvironment.json: -------------------------------------------------------------------------------- 1 | { 2 | "revision": "revision_output", 3 | "hasMore": true, 4 | "bundleId": "com.example", 5 | "appAppleId": 323232, 6 | "environment": "LocalTestingxxx", 7 | "signedTransactions": [ 8 | "signed_transaction_value", 9 | "signed_transaction_value2" 10 | ] 11 | } -------------------------------------------------------------------------------- /tests/resources/models/transactionHistoryResponseWithMalformedAppAppleId.json: -------------------------------------------------------------------------------- 1 | { 2 | "revision": "revision_output", 3 | "hasMore": true, 4 | "bundleId": "com.example", 5 | "appAppleId": "blue", 6 | "environment": "LocalTesting", 7 | "signedTransactions": [ 8 | "signed_transaction_value", 9 | "signed_transaction_value2" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_cancel_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::base_response::AdvancedCommerceResponse; 2 | 3 | /// The response data for a subscription cancellation request. 4 | /// 5 | /// [SubscriptionCancelResponse](https://developer.apple.com/documentation/advancedcommerceapi/subscriptioncancelresponse) 6 | pub type SubscriptionCancelResponse = AdvancedCommerceResponse; -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_revoke_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::base_response::AdvancedCommerceResponse; 2 | 3 | /// The response body for a successful revoke-subscription request. 4 | /// 5 | /// [SubscriptionRevokeResponse](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionrevokeresponse) 6 | pub type SubscriptionRevokeResponse = AdvancedCommerceResponse; -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_migrate_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::base_response::AdvancedCommerceResponse; 2 | 3 | /// The response body for a successful migrate-subscription request. 4 | /// 5 | /// [SubscriptionMigrateResponse](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmigrateresponse) 6 | pub type SubscriptionMigrateResponse = AdvancedCommerceResponse; -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/in_app_request_operation.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The type of request operation. 4 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 5 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 6 | pub enum InAppRequestOperation { 7 | CreateSubscription, 8 | CreateOneTimeCharge, 9 | ModifySubscription, 10 | ReactivateSubscription, 11 | } -------------------------------------------------------------------------------- /src/primitives/auto_renew_status.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The renewal status for an auto-renewable subscription. 4 | /// 5 | /// [autoRenewStatus](https://developer.apple.com/documentation/appstoreserverapi/autorenewstatus) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum AutoRenewStatus { 9 | Off = 0, 10 | On = 1, 11 | } 12 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_change_metadata_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::base_response::AdvancedCommerceResponse; 2 | 3 | /// The response data for a subscription metadata change request. 4 | /// 5 | /// [SubscriptionChangeMetadataResponse](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionchangemetadataresponse) 6 | pub type SubscriptionChangeMetadataResponse = AdvancedCommerceResponse; -------------------------------------------------------------------------------- /src/primitives/platform.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The platform on which the customer consumed the in-app purchase. 4 | /// 5 | /// [platform](https://developer.apple.com/documentation/appstoreserverapi/platform) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum Platform { 9 | Undeclared = 0, 10 | Apple = 1, 11 | NonApple = 2, 12 | } 13 | -------------------------------------------------------------------------------- /src/primitives/offer_type.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The type of offer. 4 | /// 5 | /// [offerType](https://developer.apple.com/documentation/appstoreserverapi/offertype) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum OfferType { 9 | IntroductoryOffer = 1, 10 | PromotionalOffer = 2, 11 | OfferCode = 3, 12 | WinBackOffer = 4, 13 | } 14 | -------------------------------------------------------------------------------- /src/primitives/revocation_reason.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The reason for a refunded transaction. 4 | /// 5 | /// [revocationReason](https://developer.apple.com/documentation/appstoreserverapi/revocationreason) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum RevocationReason { 9 | RefundedDueToIssue = 1, 10 | RefundedForOtherReason = 0, 11 | } 12 | -------------------------------------------------------------------------------- /src/primitives/order_lookup_status.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// A value that indicates whether the order ID in the request is valid for your app. 4 | /// 5 | /// [OrderLookupStatus](https://developer.apple.com/documentation/appstoreserverapi/orderlookupstatus) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum OrderLookupStatus { 9 | Valid = 0, 10 | Invalid = 1, 11 | } 12 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_price_change_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::base_response::AdvancedCommerceResponse; 2 | 3 | /// A response that contains signed JWS renewal and JWS transaction information after a subscription price change request. 4 | /// 5 | /// [SubscriptionPriceChangeResponse](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionpricechangeresponse) 6 | pub type SubscriptionPriceChangeResponse = AdvancedCommerceResponse; -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod chain_verifier; 2 | pub mod jws_signature_creator; 3 | pub mod primitives; 4 | pub mod promotional_offer_signature_creator; 5 | pub mod signed_data_verifier; 6 | pub mod utils; 7 | mod x509; 8 | 9 | #[cfg(any(feature = "receipt-utility", feature = "ocsp"))] 10 | mod asn1; 11 | #[cfg(feature = "receipt-utility")] 12 | pub mod receipt_utility; 13 | 14 | #[cfg(feature = "api-client")] 15 | pub mod api_client; 16 | 17 | #[cfg(feature = "ocsp")] 18 | mod chain_verifier_ocsp; 19 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/effective.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A string value that indicates when a requested change to an auto-renewable subscription goes into effect. 4 | /// 5 | /// [effective](https://developer.apple.com/documentation/advancedcommerceapi/effective) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 8 | pub enum Effective { 9 | Immediately, 10 | NextBillCycle, 11 | } -------------------------------------------------------------------------------- /src/primitives/retention_messaging/image_state.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The approval state of an image. 4 | /// 5 | /// [imageState](https://developer.apple.com/documentation/retentionmessaging/imagestate) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum ImageState { 8 | #[serde(rename = "PENDING")] 9 | Pending, 10 | #[serde(rename = "APPROVED")] 11 | Approved, 12 | #[serde(rename = "REJECTED")] 13 | Rejected, 14 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce_renewal_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::primitives::advanced_commerce::offer::Offer; 3 | 4 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct AdvancedCommerceRenewalItem { 7 | #[serde(rename = "SKU")] 8 | pub sku: String, 9 | 10 | pub description: String, 11 | 12 | pub display_name: String, 13 | 14 | pub offer: Offer, 15 | 16 | pub price: i64, 17 | } -------------------------------------------------------------------------------- /src/primitives/retention_messaging/message_state.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The approval state of the message. 4 | /// 5 | /// [messageState](https://developer.apple.com/documentation/retentionmessaging/messagestate) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum MessageState { 8 | #[serde(rename = "PENDING")] 9 | Pending, 10 | #[serde(rename = "APPROVED")] 11 | Approved, 12 | #[serde(rename = "REJECTED")] 13 | Rejected, 14 | } -------------------------------------------------------------------------------- /tests/resources/models/signedExternalPurchaseTokenNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "EXTERNAL_PURCHASE_TOKEN", 3 | "subtype": "UNREPORTED", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "version": "2.0", 6 | "signedDate": 1698148900000, 7 | "externalPurchaseToken": { 8 | "externalPurchaseId": "b2158121-7af9-49d4-9561-1f588205523e", 9 | "tokenCreationDate": 1698148950000, 10 | "appAppleId": 55555, 11 | "bundleId": "com.example" 12 | } 13 | } -------------------------------------------------------------------------------- /src/primitives/refund_preference.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// A value that indicates your preferred outcome for the refund request. 4 | /// 5 | /// [refundPreference](https://developer.apple.com/documentation/appstoreserverapi/refundpreference) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)] 7 | #[repr(u8)] 8 | pub enum RefundPreference { 9 | Undeclared = 0, 10 | PreferGrant = 1, 11 | PreferDecline = 2, 12 | NoPreference = 3, 13 | } 14 | -------------------------------------------------------------------------------- /tests/resources/models/signedExternalPurchaseTokenSandboxNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "EXTERNAL_PURCHASE_TOKEN", 3 | "subtype": "UNREPORTED", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "version": "2.0", 6 | "signedDate": 1698148900000, 7 | "externalPurchaseToken": { 8 | "externalPurchaseId": "SANDBOX_b2158121-7af9-49d4-9561-1f588205523e", 9 | "tokenCreationDate": 1698148950000, 10 | "appAppleId": 55555, 11 | "bundleId": "com.example" 12 | } 13 | } -------------------------------------------------------------------------------- /src/primitives/extend_reason_code.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The code that represents the reason for the subscription-renewal-date extension. 4 | /// 5 | /// [extendReasonCode](https://developer.apple.com/documentation/appstoreserverapi/extendreasoncode) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum ExtendReasonCode { 9 | Undeclared = 0, 10 | CustomerSatisfaction = 1, 11 | Other = 2, 12 | ServiceIssueOrOutage = 3, 13 | } 14 | -------------------------------------------------------------------------------- /src/primitives/consumption_status.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// A value that indicates the extent to which the customer consumed the in-app purchase. 4 | /// 5 | /// [consumptionStatus](https://developer.apple.com/documentation/appstoreserverapi/consumptionstatus) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum ConsumptionStatus { 9 | Undeclared = 0, 10 | NotConsumed = 1, 11 | PartiallyConsumed = 2, 12 | FullyConsumed = 3, 13 | } 14 | -------------------------------------------------------------------------------- /tests/resources/models/signedNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "SUBSCRIBED", 3 | "subtype": "INITIAL_BUY", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "data": { 6 | "environment": "LocalTesting", 7 | "appAppleId": 41234, 8 | "bundleId": "com.example", 9 | "bundleVersion": "1.2.3", 10 | "signedTransactionInfo": "signed_transaction_info_value", 11 | "signedRenewalInfo": "signed_renewal_info_value", 12 | "status": 1 13 | }, 14 | "version": "2.0", 15 | "signedDate": 1698148900000 16 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/refund_reason.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The reason to request a refund. 4 | /// [RefundReason](https://developer.apple.com/documentation/advancedcommerceapi/refundreason) 5 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 6 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 7 | pub enum RefundReason { 8 | UnintendedPurchase, 9 | FulfillmentIssue, 10 | UnsatisfiedWithPurchase, 11 | Legal, 12 | Other, 13 | ModifyItemsRefund, 14 | SimulateRefundDecline, 15 | } -------------------------------------------------------------------------------- /src/primitives/price_increase_status.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The status that indicates whether an auto-renewable subscription is subject to a price increase. 4 | /// 5 | /// [PriceIncreaseStatus](https://developer.apple.com/documentation/appstoreserverapi/priceincreasestatus) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum PriceIncreaseStatus { 9 | CustomerHasNotResponded = 0, 10 | CustomerConsentedOrWasNotifiedWithoutNeedingConsent = 1, 11 | } 12 | -------------------------------------------------------------------------------- /src/primitives/expiration_intent.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The reason an auto-renewable subscription expired. 4 | /// 5 | /// [expirationIntent](https://developer.apple.com/documentation/appstoreserverapi/expirationintent) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum ExpirationIntent { 9 | CustomerCancelled = 1, 10 | BillingError = 2, 11 | CustomerDidNotConsentToPriceIncrease = 3, 12 | ProductNotAvailable = 4, 13 | Other = 5, 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose --features api-client,receipt-utility 23 | - name: Run tests (Only ocsp) 24 | run: cargo test --verbose --features ocsp 25 | -------------------------------------------------------------------------------- /tests/resources/models/appTransaction.json: -------------------------------------------------------------------------------- 1 | { 2 | "receiptType": "LocalTesting", 3 | "appAppleId": 531412, 4 | "bundleId": "com.example", 5 | "applicationVersion": "1.2.3", 6 | "versionExternalIdentifier": 512, 7 | "receiptCreationDate": 1698148900000, 8 | "originalPurchaseDate": 1698148800000, 9 | "originalApplicationVersion": "1.1.2", 10 | "deviceVerification": "device_verification_value", 11 | "deviceVerificationNonce": "48ccfa42-7431-4f22-9908-7e88983e105a", 12 | "preorderDate": 1698148700000, 13 | "appTransactionId": "71134", 14 | "originalPlatform": "iOS" 15 | } 16 | -------------------------------------------------------------------------------- /src/primitives/purchase_platform.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Values that represent Apple platforms. 4 | /// 5 | /// [PurchasePlatform](https://developer.apple.com/documentation/storekit/appstore/platform) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum PurchasePlatform { 8 | #[serde(rename = "iOS")] 9 | IOS, 10 | #[serde(rename = "macOS")] 11 | MacOs, 12 | #[serde(rename = "tvOS")] 13 | TvOs, 14 | #[serde(rename = "visionOS")] 15 | VisionOs, 16 | #[serde(rename = "watchOS")] 17 | WatchOs, 18 | } 19 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alternate_product; 2 | pub mod decoded_realtime_request_body; 3 | pub mod default_configuration_request; 4 | pub mod get_image_list_response; 5 | pub mod get_image_list_response_item; 6 | pub mod get_message_list_response; 7 | pub mod get_message_list_response_item; 8 | pub mod image_state; 9 | pub mod message; 10 | pub mod message_state; 11 | pub mod promotional_offer; 12 | pub mod promotional_offer_signature_v1; 13 | pub mod realtime_request_body; 14 | pub mod realtime_response_body; 15 | pub mod upload_message_image; 16 | pub mod upload_message_request_body; -------------------------------------------------------------------------------- /src/primitives/offer_discount_type.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The payment mode for a discount offer on an In-App Purchase. 4 | /// 5 | /// [offerDiscountType](https://developer.apple.com/documentation/appstoreserverapi/offerdiscounttype) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 7 | pub enum OfferDiscountType { 8 | #[serde(rename = "FREE_TRIAL")] 9 | FreeTrial, 10 | #[serde(rename = "PAY_AS_YOU_GO")] 11 | PayAsYouGo, 12 | #[serde(rename = "PAY_UP_FRONT")] 13 | PayUpFront, 14 | #[serde(rename = "ONE_TIME")] 15 | OneTime, 16 | } 17 | -------------------------------------------------------------------------------- /src/primitives/transaction_info_response.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A response that contains signed transaction information for a single transaction. 4 | /// 5 | /// [TransactionInfoResponse](https://developer.apple.com/documentation/appstoreserverapi/transactioninforesponse) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub struct TransactionInfoResponse { 8 | /// A customer’s in-app purchase transaction, signed by Apple, in JSON Web Signature (JWS) format. 9 | #[serde(rename = "signedTransactionInfo")] 10 | pub signed_transaction_info: Option, 11 | } 12 | -------------------------------------------------------------------------------- /tests/resources/models/signedConsumptionRequestNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "CONSUMPTION_REQUEST", 3 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 4 | "data": { 5 | "environment": "LocalTesting", 6 | "appAppleId": 41234, 7 | "bundleId": "com.example", 8 | "bundleVersion": "1.2.3", 9 | "signedTransactionInfo": "signed_transaction_info_value", 10 | "signedRenewalInfo": "signed_renewal_info_value", 11 | "status": 1, 12 | "consumptionRequestReason": "UNINTENDED_PURCHASE" 13 | }, 14 | "version": "2.0", 15 | "signedDate": 1698148900000 16 | } -------------------------------------------------------------------------------- /src/primitives/play_time.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// A value that indicates the amount of time that the customer used the app. 4 | /// 5 | /// [playTime](https://developer.apple.com/documentation/appstoreserverapi/playtime) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum PlayTime { 9 | Undeclared = 0, 10 | ZeroToFiveMinutes = 1, 11 | FiveToSixtyMinutes = 2, 12 | OneToSixHours = 3, 13 | SixHoursToTwentyFourHours = 4, 14 | OneDayToFourDays = 5, 15 | FourDaysToSixteenDays = 6, 16 | OverSixteenDays = 7, 17 | } 18 | -------------------------------------------------------------------------------- /src/primitives/product_type.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The type of in-app purchase products you can offer in your app. 4 | /// 5 | /// [ProductType](https://developer.apple.com/documentation/appstoreserverapi/type) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum ProductType { 8 | #[serde(rename = "Auto-Renewable Subscription")] 9 | AutoRenewableSubscription, 10 | #[serde(rename = "Non-Consumable")] 11 | NonConsumable, 12 | #[serde(rename = "Consumable")] 13 | Consumable, 14 | #[serde(rename = "Non-Renewing Subscription")] 15 | NonRenewingSubscription, 16 | } 17 | -------------------------------------------------------------------------------- /tests/resources/models/signedSummaryNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "RENEWAL_EXTENSION", 3 | "subtype": "SUMMARY", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "version": "2.0", 6 | "signedDate": 1698148900000, 7 | "summary": { 8 | "environment": "LocalTesting", 9 | "appAppleId": 41234, 10 | "bundleId": "com.example", 11 | "productId": "com.example.product", 12 | "requestIdentifier": "efb27071-45a4-4aca-9854-2a1e9146f265", 13 | "storefrontCountryCodes": [ 14 | "CAN", 15 | "USA", 16 | "MEX" 17 | ], 18 | "succeededCount": 5, 19 | "failedCount": 2 20 | } 21 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/refund.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | use crate::primitives::advanced_commerce::refund_reason::RefundReason; 4 | use crate::primitives::advanced_commerce::refund_type::RefundType; 5 | 6 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] 7 | #[serde_with::serde_as] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct Refund { 10 | pub refund_amount: i64, 11 | 12 | #[serde_as(as = "TimestampMilliSeconds")] 13 | pub refund_date: DateTime, 14 | 15 | pub refund_reason: RefundReason, 16 | 17 | pub refund_type: RefundType, 18 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_reactivate_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// An item for reactivating Advanced Commerce subscriptions. 4 | /// 5 | /// [SubscriptionReactivateItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionreactivateitem) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct SubscriptionReactivateItem { 9 | /// The SKU identifier for the item. 10 | /// 11 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 12 | #[serde(rename = "SKU")] 13 | pub sku: String, 14 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/period.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The duration of a single cycle of an auto-renewable subscription. 4 | /// 5 | /// [period](https://developer.apple.com/documentation/advancedcommerceapi/period) 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] 7 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 8 | pub enum Period { 9 | /// One week period 10 | P1W, 11 | /// One months period 12 | P1M, 13 | /// Two months period 14 | P2M, 15 | /// Three months period 16 | P3M, 17 | /// Six months period 18 | P6M, 19 | /// One year period 20 | P1Y, 21 | } -------------------------------------------------------------------------------- /src/primitives/account_tenure.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The age of the customer’s account. 4 | /// 5 | /// [accountTenure](https://developer.apple.com/documentation/appstoreserverapi/accounttenure) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum AccountTenure { 9 | Undeclared = 0, 10 | ZeroToThreeDays = 1, 11 | ThreeDaysToTenDays = 2, 12 | TenDaysToThirtyDays = 3, 13 | ThirtyDaysToNinetyDays = 4, 14 | NinetyDaysToOneHundredEightyDays = 5, 15 | OneHundredEightyDaysToThreeHundredSixtyFiveDays = 6, 16 | GreaterThanThreeHundredSixtyFiveDays = 7, 17 | } 18 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_modify_remove_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// An item for removing from Advanced Commerce subscription modifications. 4 | /// 5 | /// [SubscriptionModifyRemoveItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifyremoveitem) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct SubscriptionModifyRemoveItem { 9 | /// The SKU identifier for the item. 10 | /// 11 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 12 | #[serde(rename = "SKU")] 13 | pub sku: String 14 | } -------------------------------------------------------------------------------- /src/primitives/retention_messaging/message.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | /// A message identifier you provide in a real-time response to your Get Retention Message endpoint. 5 | /// 6 | /// [message](https://developer.apple.com/documentation/retentionmessaging/message) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct Message { 9 | /// The identifier of the message to display to the customer. 10 | /// 11 | /// [messageIdentifier](https://developer.apple.com/documentation/retentionmessaging/messageidentifier) 12 | #[serde(rename = "messageIdentifier")] 13 | pub message_identifier: Option, 14 | } -------------------------------------------------------------------------------- /src/primitives/delivery_status.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// A value that indicates whether the app successfully delivered an in-app purchase that works properly. 4 | /// 5 | /// [deliveryStatus](https://developer.apple.com/documentation/appstoreserverapi/deliverystatus) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum DeliveryStatus { 9 | DeliveredAndWorkingProperly = 0, 10 | DidNotDeliverDueToQualityIssue = 1, 11 | DeliveredWrongItem = 2, 12 | DidNotDeliverDueToServerOutage = 3, 13 | DidNotDeliverDueToIngameCurrencyChange = 4, 14 | DidNotDeliverForOtherReason = 5, 15 | } 16 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/realtime_request_body.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The request body the App Store server sends to your Get Retention Message endpoint. 4 | /// 5 | /// [RealtimeRequestBody](https://developer.apple.com/documentation/retentionmessaging/realtimerequestbody) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub struct RealtimeRequestBody { 8 | /// The payload in JSON Web Signature (JWS) format, signed by the App Store. 9 | /// 10 | /// [signedPayload](https://developer.apple.com/documentation/retentionmessaging/signedpayload) 11 | #[serde(rename = "signedPayload")] 12 | pub signed_payload: Option, 13 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/offer_reason.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The reason for the offer. 4 | /// 5 | /// [Offer](https://developer.apple.com/documentation/advancedcommerceapi/offer) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 8 | pub enum OfferReason { 9 | Acquisition, 10 | WinBack, 11 | Retention, 12 | } 13 | 14 | impl OfferReason { 15 | pub fn as_str(&self) -> &str { 16 | match self { 17 | OfferReason::Acquisition => "ACQUISITION", 18 | OfferReason::WinBack => "WIN_BACK", 19 | OfferReason::Retention => "RETENTION", 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/primitives/response_body_v2.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The response body the App Store sends in a version 2 server notification. 4 | /// 5 | /// [responseBodyV2](https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub struct ResponseBodyV2 { 8 | /// A cryptographically signed payload, in JSON Web Signature (JWS) format, containing the response body for a version 2 notification. 9 | /// 10 | /// [signedPayload](https://developer.apple.com/documentation/appstoreservernotifications/signedpayload) 11 | #[serde(rename = "signedPayload")] 12 | pub signed_payload: Option, 13 | } 14 | -------------------------------------------------------------------------------- /src/primitives/consumption_request_reason.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The customer-provided reason for a refund request. 4 | /// 5 | /// [consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 7 | pub enum ConsumptionRequestReason { 8 | #[serde(rename = "UNINTENDED_PURCHASE")] 9 | UnintendedPurchase, 10 | #[serde(rename = "FULFILLMENT_ISSUE")] 11 | FulfillmentIssue, 12 | #[serde(rename = "UNSATISFIED_WITH_PURCHASE")] 13 | UnsatisfiedWithPurchase, 14 | #[serde(rename = "LEGAL")] 15 | Legal, 16 | #[serde(rename = "OTHER")] 17 | Other, 18 | } 19 | -------------------------------------------------------------------------------- /src/primitives/send_test_notification_response.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A response that contains the test notification token. 4 | /// 5 | /// [SendTestNotificationResponse](https://developer.apple.com/documentation/appstoreserverapi/sendtestnotificationresponse) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub struct SendTestNotificationResponse { 8 | /// A unique identifier for a notification test that the App Store server sends to your server. 9 | /// 10 | /// [testNotificationToken](https://developer.apple.com/documentation/appstoreserverapi/testnotificationtoken) 11 | #[serde(rename = "testNotificationToken")] 12 | pub test_notification_token: Option, 13 | } 14 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/default_configuration_request.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | /// The request body that contains the default configuration information. 5 | /// 6 | /// [DefaultConfigurationRequest](https://developer.apple.com/documentation/retentionmessaging/defaultconfigurationrequest) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct DefaultConfigurationRequest { 9 | /// The message identifier of the message to configure as a default message. 10 | /// 11 | /// [messageIdentifier](https://developer.apple.com/documentation/retentionmessaging/messageidentifier) 12 | #[serde(rename = "messageIdentifier")] 13 | pub message_identifier: Option, 14 | } -------------------------------------------------------------------------------- /src/primitives/token_type.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The type of an external purchase custom link token. 4 | /// The token type field is present only for custom link tokens. 5 | /// For more information on tokens, see Receiving and decoding external purchase tokens. 6 | /// 7 | /// [tokenType](https://developer.apple.com/documentation/appstoreservernotifications/tokentype) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 9 | pub enum TokenType { 10 | /// A token type that indicates an initial acquisition. 11 | #[serde(rename = "ACQUISITION")] 12 | Acquisition, 13 | 14 | /// A token type that indicates usage of App Store services. 15 | #[serde(rename = "SERVICES")] 16 | Services, 17 | } 18 | -------------------------------------------------------------------------------- /tests/resources/models/getNotificationHistoryResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "paginationToken": "57715481-805a-4283-8499-1c19b5d6b20a", 3 | "hasMore": true, 4 | "notificationHistory": [ 5 | { 6 | "sendAttempts": [ 7 | { 8 | "attemptDate": 1698148900000, 9 | "sendAttemptResult": "NO_RESPONSE" 10 | }, { 11 | "attemptDate": 1698148950000, 12 | "sendAttemptResult": "SUCCESS" 13 | } 14 | ], 15 | "signedPayload": "signed_payload_one" 16 | }, 17 | { 18 | "sendAttempts": [ 19 | { 20 | "attemptDate": 1698148800000, 21 | "sendAttemptResult": "CIRCULAR_REDIRECT" 22 | } 23 | ], 24 | "signedPayload": "signed_payload_two" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /src/primitives/in_app_ownership_type.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The relationship of the user with the family-shared purchase to which they have access. 4 | /// 5 | /// [inAppOwnershipType](https://developer.apple.com/documentation/appstoreserverapi/inappownershiptype) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum InAppOwnershipType { 8 | #[serde(rename = "FAMILY_SHARED")] 9 | FamilyShared, 10 | #[serde(rename = "PURCHASED")] 11 | Purchased, 12 | } 13 | 14 | impl InAppOwnershipType { 15 | pub fn raw_value(&self) -> &str { 16 | match self { 17 | InAppOwnershipType::FamilyShared => "FAMILY_SHARED", 18 | InAppOwnershipType::Purchased => "PURCHASED", 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/primitives/mass_extend_renewal_date_response.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A response that indicates the server successfully received the subscription-renewal-date extension request. 4 | /// 5 | /// [MassExtendRenewalDateResponse](https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldateresponse) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub struct MassExtendRenewalDateResponse { 8 | /// A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 9 | /// 10 | /// [requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 11 | #[serde(rename = "requestIdentifier")] 12 | pub request_identifier: String, 13 | } 14 | -------------------------------------------------------------------------------- /src/primitives/status.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// The status of an auto-renewable subscription. 4 | /// 5 | /// [status](https://developer.apple.com/documentation/appstoreserverapi/status) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum Status { 9 | Active = 1, 10 | Expired = 2, 11 | BillingRetry = 3, 12 | BillingGracePeriod = 4, 13 | Revoked = 5, 14 | } 15 | 16 | impl Status { 17 | pub fn raw_value(&self) -> u8 { 18 | match &self { 19 | Status::Active => 1, 20 | Status::Expired => 2, 21 | Status::BillingRetry => 3, 22 | Status::BillingGracePeriod => 4, 23 | Status::Revoked => 5, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/primitives/app_transaction_info_response.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Apple Inc. Licensed under MIT License. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A response that contains signed app transaction information for a customer. 6 | /// 7 | /// [AppTransactionInfoResponse](https://developer.apple.com/documentation/appstoreserverapi/apptransactioninforesponse) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 9 | pub struct AppTransactionInfoResponse { 10 | /// A customer's app transaction information, signed by Apple, in JSON Web Signature (JWS) format. 11 | /// 12 | /// [JWSAppTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwsapptransaction) 13 | #[serde(rename = "signedAppTransactionInfo")] 14 | pub signed_app_transaction_info: Option, 15 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Expected Behavior 13 | 14 | 15 | ## Current Behavior 16 | 17 | 18 | ## Possible Solution 19 | 20 | 21 | ## Alternatives Considered 22 | 23 | 24 | ## Additional Context 25 | 26 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/get_image_list_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::retention_messaging::get_image_list_response_item::GetImageListResponseItem; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A response that contains status information for all images. 5 | /// 6 | /// [GetImageListResponse](https://developer.apple.com/documentation/retentionmessaging/getimagelistresponse) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct GetImageListResponse { 9 | /// An array of all image identifiers and their image state. 10 | /// 11 | /// [GetImageListResponseItem](https://developer.apple.com/documentation/retentionmessaging/getimagelistresponseitem) 12 | #[serde(rename = "imageIdentifiers")] 13 | pub image_identifiers: Option>, 14 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_price_change_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// An item for Advanced Commerce subscription price changes. 4 | /// 5 | /// [SubscriptionPriceChangeItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionpricechangeitem) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct SubscriptionPriceChangeItem { 9 | /// The SKU identifier for the item. 10 | /// 11 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 12 | #[serde(rename = "SKU")] 13 | pub sku: String, 14 | 15 | /// The new price in milliunits. 16 | /// 17 | /// [Price](https://developer.apple.com/documentation/advancedcommerceapi/price) 18 | pub price: i64, 19 | } 20 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/get_message_list_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::retention_messaging::get_message_list_response_item::GetMessageListResponseItem; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A response that contains status information for all messages. 5 | /// 6 | /// [GetMessageListResponse](https://developer.apple.com/documentation/retentionmessaging/getmessagelistresponse) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct GetMessageListResponse { 9 | /// An array of all message identifiers and their message state. 10 | /// 11 | /// [messageIdentifiers](https://developer.apple.com/documentation/retentionmessaging/getmessagelistresponseitem) 12 | #[serde(rename = "messageIdentifiers")] 13 | pub message_identifiers: Option>, 14 | } -------------------------------------------------------------------------------- /tests/chain_verifier_ocsp.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use app_store_server_library::chain_verifier::{ChainVerifier, ChainVerifierError}; 4 | use app_store_server_library::utils::StringExt; 5 | use common::*; 6 | 7 | extern crate base64; 8 | 9 | #[test] 10 | fn test_apple_chain_is_valid_with_ocsp() -> Result<(), ChainVerifierError> { 11 | let root = REAL_APPLE_ROOT_BASE64_ENCODED 12 | .as_der_bytes() 13 | .unwrap(); 14 | let leaf = REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED 15 | .as_der_bytes() 16 | .unwrap(); 17 | let intermediate = REAL_APPLE_INTERMEDIATE_BASE64_ENCODED 18 | .as_der_bytes() 19 | .unwrap(); 20 | 21 | let verifier = ChainVerifier::new(vec![root]); 22 | let _public_key = verifier.verify(&leaf, &intermediate, Some(EFFECTIVE_DATE)).unwrap(); 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /tests/promotional_offer_signature_creator.rs: -------------------------------------------------------------------------------- 1 | use app_store_server_library::promotional_offer_signature_creator::PromotionalOfferSignatureCreator; 2 | 3 | #[test] 4 | fn test_promotional_offer_signature_creator() { 5 | let private_key = include_str!("../tests/resources/certs/testSigningKey.p8"); 6 | let creator = PromotionalOfferSignatureCreator::new( 7 | private_key, 8 | "L256SYR32L".to_string(), 9 | "com.test.app".to_string(), 10 | ) 11 | .unwrap(); 12 | let r = creator 13 | .create_signature( 14 | "com.test.product", 15 | "com.test.offer", 16 | uuid::Uuid::new_v4() 17 | .to_string() 18 | .as_str(), 19 | &uuid::Uuid::new_v4(), 20 | 12345, 21 | ) 22 | .unwrap(); 23 | 24 | assert!(!r.is_empty()) 25 | } 26 | -------------------------------------------------------------------------------- /tests/resources/models/signedRenewalInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "expirationIntent": 1, 3 | "originalTransactionId": "12345", 4 | "autoRenewProductId": "com.example.product.2", 5 | "productId": "com.example.product", 6 | "autoRenewStatus": 1, 7 | "isInBillingRetryPeriod": true, 8 | "priceIncreaseStatus": 0, 9 | "gracePeriodExpiresDate": 1698148900000, 10 | "offerType": 2, 11 | "offerIdentifier": "abc.123", 12 | "signedDate": 1698148800000, 13 | "environment": "LocalTesting", 14 | "recentSubscriptionStartDate": 1698148800000, 15 | "renewalDate": 1698148850000, 16 | "renewalPrice": 9990, 17 | "currency": "USD", 18 | "offerDiscountType": "PAY_AS_YOU_GO", 19 | "eligibleWinBackOfferIds": [ 20 | "eligible1", 21 | "eligible2" 22 | ], 23 | "appTransactionId": "71134", 24 | "offerPeriod": "P1Y", 25 | "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138" 26 | } 27 | -------------------------------------------------------------------------------- /src/primitives/environment.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 4 | pub enum Environment { 5 | #[serde(rename = "Sandbox")] 6 | Sandbox, 7 | #[serde(rename = "Production")] 8 | Production, 9 | #[serde(rename = "Xcode")] 10 | Xcode, 11 | #[serde(rename = "LocalTesting")] 12 | LocalTesting, // Used for unit testing 13 | } 14 | 15 | impl Environment { 16 | pub fn base_url(&self) -> String { 17 | match self { 18 | Environment::Production => "https://api.storekit.itunes.apple.com".to_string(), 19 | Environment::Sandbox => "https://api.storekit-sandbox.itunes.apple.com".to_string(), 20 | Environment::LocalTesting => "https://local-testing-base-url".to_string(), 21 | _ => "https://api.storekit-sandbox.itunes.apple.com".to_string(), 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/primitives/lifetime_dollars_purchased.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// A value that indicates the total amount, in USD, of in-app purchases the customer has made in your app, across all platforms. 4 | /// 5 | /// [lifetimeDollarsPurchased](https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarspurchased) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum LifetimeDollarsPurchased { 9 | Undeclared = 0, 10 | ZeroDollars = 1, 11 | OneCentToFortyNineDollarsAndNinetyNineCents = 2, 12 | FiftyDollarsToNinetyNineDollarsAndNinetyNineCents = 3, 13 | OneHundredDollarsToFourHundredNinetyNineDollarsAndNinetyNineCents = 4, 14 | FiveHundredDollarsToNineHundredNinetyNineDollarsAndNinetyNineCents = 5, 15 | OneThousandDollarsToOneThousandNineHundredNinetyNineDollarsAndNinetyNineCents = 6, 16 | TwoThousandDollarsOrGreater = 7, 17 | } 18 | -------------------------------------------------------------------------------- /src/api_client/transport.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum TransportError { 6 | #[error("Serialization error: {0}")] 7 | Serialization(#[from] serde_json::Error), 8 | 9 | #[error("Invalid HTTP method")] 10 | InvalidMethod, 11 | 12 | #[error("Invalid status code: {0}")] 13 | InvalidStatusCode(#[from] http::status::InvalidStatusCode), 14 | 15 | #[error("Request failed: {0}")] 16 | RequestFailed(String), 17 | 18 | #[error("Network error: {0}")] 19 | NetworkError(String), 20 | 21 | #[error("Invalid response: {0}")] 22 | InvalidResponse(String), 23 | 24 | #[error("Timeout error")] 25 | Timeout, 26 | 27 | #[error("Other error: {0}")] 28 | Other(String), 29 | } 30 | 31 | pub trait Transport: Send + Sync { 32 | fn send(&self, req: http::Request>) -> impl Future>, TransportError>> + Send; 33 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce_renewal_info.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::primitives::advanced_commerce::descriptors::Descriptors; 3 | use crate::primitives::advanced_commerce::period::Period; 4 | use crate::primitives::advanced_commerce_renewal_item::AdvancedCommerceRenewalItem; 5 | 6 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct AdvancedCommerceRenewalInfo { 9 | /// advancedCommerceConsistencyToken 10 | pub consistency_token: String, 11 | 12 | /// advancedCommerceDescriptors 13 | pub descriptors: Descriptors, 14 | 15 | /// advancedCommerceRenewalItems 16 | pub items: Vec, 17 | 18 | /// advancedCommercePeriod 19 | pub period: Period, 20 | 21 | /// advancedCommerceRequestReferenceId 22 | pub request_reference_id: String, 23 | 24 | /// advancedCommerceTaxCode 25 | pub tax_code: String, 26 | } -------------------------------------------------------------------------------- /src/primitives/lifetime_dollars_refunded.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::{Deserialize_repr, Serialize_repr}; 2 | 3 | /// A value that indicates the dollar amount of refunds the customer has received in your app, since purchasing the app, across all platforms. 4 | /// 5 | /// [lifetimeDollarsRefunded](https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarsrefunded) 6 | #[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Hash, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub enum LifetimeDollarsRefunded { 9 | Undeclared = 0, 10 | ZeroDollars = 1, 11 | OneCentToFortyNineDollarsAndNinetyNineCents = 2, 12 | FiftyDollarsToNinetyNineDollarsAndNinetyNineCents = 3, 13 | OneHundredDollarsToFourHundredNinetyNineDollarsAndNinetyNineCents = 4, 14 | FiveHundredDollarsToNineHundredNinetyNineDollarsAndNinetyNineCents = 5, 15 | OneThousandDollarsToOneThousandNineHundredNinetyNineDollarsAndNinetyNineCents = 6, 16 | TwoThousandDollarsOrGreater = 7, 17 | } 18 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/base_response.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The base response body for an Advanced Commerce request. 4 | /// 5 | /// [SubscriptionRevokeResponse](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionrevokeresponse) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct AdvancedCommerceResponse { 9 | /// Subscription renewal information signed by the App Store, in JSON Web Signature (JWS) format. 10 | /// 11 | /// [signedRenewalInfo](https://developer.apple.com/documentation/advancedcommerceapi/jwsrenewalinfo) 12 | pub signed_renewal_info: String, 13 | 14 | /// Transaction information signed by the App Store, in JWS Compact Serialization format. 15 | /// 16 | /// [signedTransactionInfo](https://developer.apple.com/documentation/advancedcommerceapi/jwstransaction) 17 | pub signed_transaction_info: String, 18 | } -------------------------------------------------------------------------------- /src/primitives/retention_messaging/get_image_list_response_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::retention_messaging::image_state::ImageState; 2 | use serde::{Deserialize, Serialize}; 3 | use uuid::Uuid; 4 | 5 | /// An image identifier and state information for an image. 6 | /// 7 | /// [GetImageListResponseItem](https://developer.apple.com/documentation/retentionmessaging/getimagelistresponseitem) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 9 | pub struct GetImageListResponseItem { 10 | /// The identifier of the image. 11 | /// 12 | /// [imageIdentifier](https://developer.apple.com/documentation/retentionmessaging/imageidentifier) 13 | #[serde(rename = "imageIdentifier")] 14 | pub image_identifier: Option, 15 | 16 | /// The current state of the image. 17 | /// 18 | /// [imageState](https://developer.apple.com/documentation/retentionmessaging/imagestate) 19 | #[serde(rename = "imageState")] 20 | pub image_state: Option, 21 | } -------------------------------------------------------------------------------- /src/primitives/retention_messaging/get_message_list_response_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::retention_messaging::message_state::MessageState; 2 | use serde::{Deserialize, Serialize}; 3 | use uuid::Uuid; 4 | 5 | /// A message identifier and status information for a message. 6 | /// 7 | /// [GetMessageListResponseItem](https://developer.apple.com/documentation/retentionmessaging/getmessagelistresponseitem) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 9 | pub struct GetMessageListResponseItem { 10 | /// The identifier of the message. 11 | /// 12 | /// [messageIdentifier](https://developer.apple.com/documentation/retentionmessaging/messageidentifier) 13 | #[serde(rename = "messageIdentifier")] 14 | pub message_identifier: Option, 15 | 16 | /// The current state of the message. 17 | /// 18 | /// [messageState](https://developer.apple.com/documentation/retentionmessaging/messagestate) 19 | #[serde(rename = "messageState")] 20 | pub message_state: Option, 21 | } -------------------------------------------------------------------------------- /tests/resources/models/signedTransaction.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactionId":"23456", 3 | "originalTransactionId":"12345", 4 | "webOrderLineItemId":"34343", 5 | "bundleId":"com.example", 6 | "productId":"com.example.product", 7 | "subscriptionGroupIdentifier":"55555", 8 | "purchaseDate":1698148900000, 9 | "originalPurchaseDate":1698148800000, 10 | "expiresDate":1698149000000, 11 | "quantity":1, 12 | "type":"Auto-Renewable Subscription", 13 | "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138", 14 | "inAppOwnershipType":"PURCHASED", 15 | "signedDate":1698148900000, 16 | "revocationReason": 1, 17 | "revocationDate": 1698148950000, 18 | "isUpgraded": true, 19 | "offerType":1, 20 | "offerIdentifier": "abc.123", 21 | "environment":"LocalTesting", 22 | "transactionReason":"PURCHASE", 23 | "storefront":"USA", 24 | "storefrontId":"143441", 25 | "price": 10990, 26 | "currency": "USD", 27 | "offerDiscountType": "PAY_AS_YOU_GO", 28 | "appTransactionId": "71134", 29 | "offerPeriod": "P1Y" 30 | } 31 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/alternate_product.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | /// A switch-plan message and product ID you provide in a real-time response to your Get Retention Message endpoint. 5 | /// 6 | /// [alternateProduct](https://developer.apple.com/documentation/retentionmessaging/alternateproduct) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct AlternateProduct { 9 | /// The message identifier of the text to display in the switch-plan retention message. 10 | /// 11 | /// [messageIdentifier](https://developer.apple.com/documentation/retentionmessaging/messageidentifier) 12 | #[serde(rename = "messageIdentifier")] 13 | pub message_identifier: Option, 14 | 15 | /// The product identifier of the subscription the retention message suggests for your customer to switch to. 16 | /// 17 | /// [productId](https://developer.apple.com/documentation/retentionmessaging/productid) 18 | #[serde(rename = "productId")] 19 | pub product_id: Option, 20 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/descriptors.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The description and display name of the subscription to migrate to that you manage. 4 | /// 5 | /// [Descriptors](https://developer.apple.com/documentation/advancedcommerceapi/descriptors) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct Descriptors { 9 | /// A string you provide that describes a SKU. 10 | /// 11 | /// [Description](https://developer.apple.com/documentation/appstoreserverapi/description) 12 | pub description: String, 13 | 14 | /// A string with a product name that you can localize and is suitable for display to customers. 15 | /// 16 | /// [DisplayName](https://developer.apple.com/documentation/appstoreserverapi/displayname) 17 | pub display_name: String, 18 | } 19 | 20 | impl Descriptors { 21 | pub fn new(description: String, display_name: String) -> Self { 22 | Self { 23 | description, 24 | display_name, 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/primitives/order_lookup_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::order_lookup_status::OrderLookupStatus; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A response that includes the order lookup status and an array of signed transactions for the in-app purchases in the order. 5 | /// 6 | /// [OrderLookupResponse](https://developer.apple.com/documentation/appstoreserverapi/orderlookupresponse) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct OrderLookupResponse { 9 | /// The status that indicates whether the order ID is valid. 10 | /// 11 | /// [OrderLookupStatus](https://developer.apple.com/documentation/appstoreserverapi/orderlookupstatus) 12 | #[serde(rename = "status")] 13 | pub status: OrderLookupStatus, 14 | 15 | /// An array of in-app purchase transactions that are part of the order, signed by Apple, in JSON Web Signature format. 16 | /// 17 | /// [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 18 | #[serde(rename = "signedTransactions")] 19 | pub signed_transactions: Vec, 20 | } 21 | -------------------------------------------------------------------------------- /tests/resources/models/getAllSubscriptionStatusesResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "environment": "LocalTesting", 3 | "bundleId": "com.example", 4 | "appAppleId": 5454545, 5 | "data": [ 6 | { 7 | "subscriptionGroupIdentifier": "sub_group_one", 8 | "lastTransactions": [ 9 | { 10 | "status": 1, 11 | "originalTransactionId": "3749183", 12 | "signedTransactionInfo": "signed_transaction_one", 13 | "signedRenewalInfo": "signed_renewal_one" 14 | }, 15 | { 16 | "status": 5, 17 | "originalTransactionId": "5314314134", 18 | "signedTransactionInfo": "signed_transaction_two", 19 | "signedRenewalInfo": "signed_renewal_two" 20 | } 21 | ] 22 | }, 23 | { 24 | "subscriptionGroupIdentifier": "sub_group_two", 25 | "lastTransactions": [ 26 | { 27 | "status": 2, 28 | "originalTransactionId": "3413453", 29 | "signedTransactionInfo": "signed_transaction_three", 30 | "signedRenewalInfo": "signed_renewal_three" 31 | } 32 | ] 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Namecare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_modify_period_change.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::period::Period; 2 | use chrono::{DateTime, Utc}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_with::formats::Flexible; 5 | use serde_with::TimestampMilliSeconds; 6 | 7 | /// A period change for Advanced Commerce subscription modifications. 8 | /// 9 | /// [SubscriptionModifyPeriodChange](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifyperiodchange) 10 | #[serde_with::serde_as] 11 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct SubscriptionModifyPeriodChange { 14 | /// The new period for the subscription. 15 | /// 16 | /// [Period](https://developer.apple.com/documentation/advancedcommerceapi/period) 17 | pub period: Period, 18 | 19 | /// The effective date for the period change. 20 | /// 21 | /// [Effective Date](https://developer.apple.com/documentation/advancedcommerceapi/effectivedate) 22 | #[serde_as(as = "TimestampMilliSeconds")] 23 | pub effective: DateTime, 24 | } -------------------------------------------------------------------------------- /src/primitives/send_attempt_result.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The success or error information the App Store server records when it attempts to send an App Store server notification to your server. 4 | /// 5 | /// [sendAttemptResult](https://developer.apple.com/documentation/appstoreserverapi/sendattemptresult) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum SendAttemptResult { 8 | #[serde(rename = "SUCCESS")] 9 | Success, 10 | #[serde(rename = "TIMED_OUT")] 11 | TimedOut, 12 | #[serde(rename = "TLS_ISSUE")] 13 | TlsIssue, 14 | #[serde(rename = "CIRCULAR_REDIRECT")] 15 | CircularRedirect, 16 | #[serde(rename = "NO_RESPONSE")] 17 | NoResponse, 18 | #[serde(rename = "SOCKET_ISSUE")] 19 | SocketIssue, 20 | #[serde(rename = "UNSUPPORTED_CHARSET")] 21 | UnsupportedCharset, 22 | #[serde(rename = "INVALID_RESPONSE")] 23 | InvalidResponse, 24 | #[serde(rename = "PREMATURE_CLOSE")] 25 | PrematureClose, 26 | #[serde(rename = "UNSUCCESSFUL_HTTP_RESPONSE_CODE")] 27 | UnsuccessfulHttpResponseCode, 28 | #[serde(rename = "OTHER")] 29 | Other, 30 | } 31 | -------------------------------------------------------------------------------- /src/primitives/first_send_attempt_result.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// An error or result that the App Store server receives when attempting to send an App Store server notification to your server. 4 | /// 5 | /// [firstSendAttemptResult](https://developer.apple.com/documentation/appstoreserverapi/firstsendattemptresult) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum FirstSendAttemptResult { 8 | #[serde(rename = "SUCCESS")] 9 | Success, 10 | #[serde(rename = "TIMED_OUT")] 11 | TimedOut, 12 | #[serde(rename = "TLS_ISSUE")] 13 | TlsIssue, 14 | #[serde(rename = "CIRCULAR_REDIRECT")] 15 | CircularRedirect, 16 | #[serde(rename = "NO_RESPONSE")] 17 | NoResponse, 18 | #[serde(rename = "SOCKET_ISSUE")] 19 | SocketIssue, 20 | #[serde(rename = "UNSUPPORTED_CHARSET")] 21 | UnsuportedCharset, 22 | #[serde(rename = "INVALID_RESPONSE")] 23 | InvalidResponse, 24 | #[serde(rename = "PREMATURE_CLOSE")] 25 | PrematureClose, 26 | #[serde(rename = "UNSUCCESSFUL_HTTP_RESPONSE_CODE")] 27 | UnsuccessfulHttpResponseCode, 28 | #[serde(rename = "OTHER")] 29 | Other, 30 | } 31 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_migrate_descriptors.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The display name and description of a subscription to migrate to. 4 | /// 5 | /// [SubscriptionMigrateDescriptors](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmigratedescriptors) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct SubscriptionMigrateDescriptors { 9 | /// The description of the subscription to migrate to. This string doesn't display to customers. 10 | /// 11 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 12 | pub description: String, 13 | 14 | /// The display name of the subscription to migrate to. This string displays to customers. 15 | /// 16 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 17 | pub display_name: String, 18 | } 19 | 20 | impl SubscriptionMigrateDescriptors { 21 | pub fn new(description: String, display_name: String) -> Self { 22 | Self { 23 | description, 24 | display_name, 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Expected Behavior 13 | 14 | 15 | ## Current Behavior 16 | 17 | 18 | ## Possible Solution 19 | 20 | 21 | ## Steps to Reproduce (for bugs) 22 | 23 | 24 | 1. 25 | 2. 26 | 3. 27 | 4. 28 | 29 | ## Context 30 | 31 | 32 | 33 | ## Your Environment 34 | 35 | * Version used: 36 | * Environment name and version (device, simalator): 37 | * Operating System and version (iOS, osx): 38 | * Link to your project: 39 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/offer_period.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The period of the offer. 4 | /// 5 | /// [Offer](https://developer.apple.com/documentation/advancedcommerceapi/offer) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum OfferPeriod { 8 | #[serde(rename = "P3D")] 9 | P3d, 10 | #[serde(rename = "P1W")] 11 | P1w, 12 | #[serde(rename = "P2W")] 13 | P2w, 14 | #[serde(rename = "P1M")] 15 | P1m, 16 | #[serde(rename = "P2M")] 17 | P2m, 18 | #[serde(rename = "P3M")] 19 | P3m, 20 | #[serde(rename = "P6M")] 21 | P6m, 22 | #[serde(rename = "P9M")] 23 | P9m, 24 | #[serde(rename = "P1Y")] 25 | P1y, 26 | } 27 | 28 | impl OfferPeriod { 29 | pub fn as_str(&self) -> &str { 30 | match self { 31 | OfferPeriod::P3d => "P3D", 32 | OfferPeriod::P1w => "P1W", 33 | OfferPeriod::P2w => "P2W", 34 | OfferPeriod::P1m => "P1M", 35 | OfferPeriod::P2m => "P2M", 36 | OfferPeriod::P3m => "P3M", 37 | OfferPeriod::P6m => "P6M", 38 | OfferPeriod::P9m => "P9M", 39 | OfferPeriod::P1y => "P1Y", 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/one_time_charge_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The details of a one-time charge product, including its display name, price, SKU, and metadata. 4 | /// 5 | /// [OneTimeChargeItem](https://developer.apple.com/documentation/advancedcommerceapi/onetimechargeitem) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct OneTimeChargeItem { 9 | /// The stock keeping unit (SKU) for the product. 10 | #[serde(rename = "SKU")] 11 | pub sku: String, 12 | 13 | /// The description of the product. 14 | pub description: String, 15 | 16 | /// The display name for the product. 17 | pub display_name: String, 18 | 19 | /// The price, in milliunits of the currency, of the one-time charge product. 20 | /// 21 | /// [Price](https://developer.apple.com/documentation/advancedcommerceapi/price) 22 | pub price: i64, 23 | } 24 | 25 | impl OneTimeChargeItem { 26 | pub fn new(sku: String, description: String, display_name: String, price: i64) -> Self { 27 | Self { 28 | sku, 29 | description, 30 | display_name, 31 | price, 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/primitives/check_test_notification_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::send_attempt_item::SendAttemptItem; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A response that contains the contents of the test notification sent by the App Store server and the result from your server. 5 | /// 6 | /// [CheckTestNotificationResponse](https://developer.apple.com/documentation/appstoreserverapi/checktestnotificationresponse) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 8 | pub struct CheckTestNotificationResponse { 9 | /// A cryptographically signed payload, in JSON Web Signature (JWS) format, containing the response body for a version 2 notification. 10 | /// 11 | /// [signedPayload](https://developer.apple.com/documentation/appstoreservernotifications/signedpayload) 12 | #[serde(rename = "signedPayload")] 13 | pub signed_payload: Option, 14 | 15 | /// An array of information the App Store server records for its attempts to send the TEST notification to your server. The array may contain a maximum of six sendAttemptItem objects. 16 | /// 17 | /// [sendAttemptItem](https://developer.apple.com/documentation/appstoreserverapi/sendattemptitem) 18 | #[serde(rename = "sendAttempts")] 19 | pub send_attempts: Option>, 20 | } 21 | -------------------------------------------------------------------------------- /src/primitives/notification_history_response_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::send_attempt_item::SendAttemptItem; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The App Store server notification history record, including the signed notification payload and the result of the server’s first send attempt. 5 | /// 6 | /// [notificationHistoryResponseItem](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponseitem) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq)] 8 | pub struct NotificationHistoryResponseItem { 9 | /// A cryptographically signed payload, in JSON Web Signature (JWS) format, containing the response body for a version 2 notification. 10 | /// 11 | /// [signedPayload](https://developer.apple.com/documentation/appstoreservernotifications/signedpayload) 12 | #[serde(rename = "signedPayload")] 13 | pub signed_payload: Option, 14 | 15 | /// An array of information the App Store server records for its attempts to send a notification to your server. The maximum number of entries in the array is six. 16 | /// 17 | /// [sendAttemptItem](https://developer.apple.com/documentation/appstoreserverapi/sendattemptitem) 18 | #[serde(rename = "sendAttempts")] 19 | pub send_attempts: Option>, 20 | } 21 | -------------------------------------------------------------------------------- /src/primitives/refund_history_response.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A response that contains an array of signed JSON Web Signature (JWS) refunded transactions, and paging information. 4 | /// 5 | /// [RefundHistoryResponse](https://developer.apple.com/documentation/appstoreserverapi/refundhistoryresponse) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub struct RefundHistoryResponse { 8 | /// A list of up to 20 JWS transactions, or an empty array if the customer hasn't received any refunds in your app. The transactions are sorted in ascending order by revocationDate. 9 | /// 10 | /// [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 11 | #[serde(rename = "signedTransactions")] 12 | pub signed_transactions: Vec, 13 | 14 | /// A token you use in a query to request the next set of transactions for the customer. 15 | /// 16 | /// [revision](https://developer.apple.com/documentation/appstoreserverapi/revision) 17 | pub revision: String, 18 | 19 | /// A Boolean value indicating whether the App Store has more transaction data. 20 | /// 21 | /// [hasMore](https://developer.apple.com/documentation/appstoreserverapi/hasmore) 22 | #[serde(rename = "hasMore")] 23 | pub has_more: bool, 24 | } 25 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_migrate_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// An item for migrating Advanced Commerce subscriptions. 4 | /// 5 | /// [SubscriptionMigrateItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmigrateitem) 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct SubscriptionMigrateItem { 9 | /// The SKU identifier for the item. 10 | /// 11 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 12 | #[serde(rename = "SKU")] 13 | pub sku: String, 14 | 15 | /// The description of the item. 16 | /// 17 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 18 | pub description: String, 19 | 20 | /// The display name of the item. 21 | /// 22 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 23 | pub display_name: String, 24 | } 25 | 26 | impl SubscriptionMigrateItem { 27 | pub fn new( 28 | sku: String, 29 | description: String, 30 | display_name: String, 31 | ) -> Self { 32 | Self { 33 | sku, 34 | description, 35 | display_name, 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/primitives/subscription_group_identifier_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::last_transactions_item::LastTransactionsItem; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Information for auto-renewable subscriptions, including signed transaction information and signed renewal information, for one subscription group. 5 | /// 6 | /// [SubscriptionGroupIdentifierItem](https://developer.apple.com/documentation/appstoreserverapi/subscriptiongroupidentifieritem) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq)] 8 | pub struct SubscriptionGroupIdentifierItem { 9 | /// The identifier of the subscription group that the subscription belongs to. 10 | /// 11 | /// [subscriptionGroupIdentifier](https://developer.apple.com/documentation/appstoreserverapi/subscriptiongroupidentifier) 12 | #[serde(rename = "subscriptionGroupIdentifier")] 13 | pub subscription_group_identifier: Option, 14 | 15 | /// An array of the most recent App Store-signed transaction information and App Store-signed renewal information for all auto-renewable subscriptions in the subscription group. 16 | /// 17 | /// [lastTransactionsItem](https://developer.apple.com/documentation/appstoreserverapi/lasttransactionsitem) 18 | #[serde(rename = "lastTransactions")] 19 | pub last_transactions: Option>, 20 | } 21 | -------------------------------------------------------------------------------- /src/primitives/send_attempt_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::send_attempt_result::SendAttemptResult; 2 | use chrono::{DateTime, Utc}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_with::formats::Flexible; 5 | use serde_with::TimestampMilliSeconds; 6 | /// The success or error information and the date the App Store server records when it attempts to send a server notification to your server. 7 | /// 8 | /// [sendAttemptItem](https://developer.apple.com/documentation/appstoreserverapi/sendattemptitem) 9 | #[serde_with::serde_as] 10 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq)] 11 | pub struct SendAttemptItem { 12 | /// The date the App Store server attempts to send a notification. 13 | /// 14 | /// [attemptDate](https://developer.apple.com/documentation/appstoreservernotifications/attemptdate) 15 | #[serde(rename = "attemptDate")] 16 | #[serde_as(as = "Option>")] 17 | pub attempt_date: Option>, 18 | 19 | /// The success or error information the App Store server records when it attempts to send an App Store server notification to your server. 20 | /// 21 | /// [sendAttemptResult](https://developer.apple.com/documentation/appstoreserverapi/sendattemptresult) 22 | #[serde(rename = "sendAttemptResult")] 23 | pub send_attempt_result: Option, 24 | } 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Issue being fixed or feature implemented 5 | 6 | 7 | 8 | 9 | ## What was done? 10 | 11 | 12 | 13 | ## How Has This Been Tested? 14 | 15 | 16 | 17 | 18 | 19 | ## Breaking Changes 20 | 21 | 22 | 23 | 24 | ## Checklist: 25 | 26 | - [ ] I have performed a self-review of my own code 27 | - [ ] I have commented my code, particularly in hard-to-understand areas 28 | - [ ] I have added or updated relevant unit/integration/functional/e2e tests 29 | - [ ] I have made corresponding changes to the documentation 30 | 31 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_migrate_renewal_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// An item for migrating Advanced Commerce subscription renewals. 4 | /// 5 | /// [SubscriptionMigrateRenewalItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmigraterenewalitem) 6 | #[serde_with::serde_as] 7 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct SubscriptionMigrateRenewalItem { 10 | /// The SKU identifier for the item. 11 | /// 12 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 13 | #[serde(rename = "SKU")] 14 | pub sku: String, 15 | 16 | /// The description of the item. 17 | /// 18 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 19 | pub description: String, 20 | 21 | /// The display name of the item. 22 | /// 23 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 24 | pub display_name: String, 25 | } 26 | 27 | impl SubscriptionMigrateRenewalItem { 28 | pub fn new( 29 | sku: String, 30 | description: String, 31 | display_name: String, 32 | ) -> Self { 33 | Self { 34 | sku, 35 | description, 36 | display_name, 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/primitives/extend_renewal_date_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::extend_reason_code::ExtendReasonCode; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The request body that contains subscription-renewal-extension data for an individual subscription. 5 | /// 6 | /// [ExtendRenewalDateRequest](https://developer.apple.com/documentation/appstoreserverapi/extendrenewaldaterequest) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 8 | pub struct ExtendRenewalDateRequest { 9 | /// The number of days to extend the subscription renewal date. 10 | /// 11 | /// [extendByDays](https://developer.apple.com/documentation/appstoreserverapi/extendbydays) 12 | /// 13 | /// maximum: 90 14 | #[serde(rename = "extendByDays")] 15 | pub extend_by_days: Option, 16 | 17 | /// The reason code for the subscription date extension. 18 | /// 19 | /// [extendReasonCode](https://developer.apple.com/documentation/appstoreserverapi/extendreasoncode) 20 | #[serde(rename = "extendReasonCode")] 21 | pub extend_reason_code: Option, 22 | 23 | /// A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 24 | /// 25 | /// [requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 26 | #[serde(rename = "requestIdentifier")] 27 | pub request_identifier: Option, 28 | } 29 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/offer.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::offer_period::OfferPeriod; 2 | use crate::primitives::advanced_commerce::offer_reason::OfferReason; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A discount offer for an auto-renewable subscription. 6 | /// 7 | /// [Offer](https://developer.apple.com/documentation/advancedcommerceapi/offer) 8 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct Offer { 11 | /// The period of the offer. 12 | /// 13 | /// [Period](https://developer.apple.com/documentation/advancedcommerceapi/period) 14 | pub period: OfferPeriod, 15 | 16 | /// The number of periods the offer is active. 17 | /// Minimum: 1, Maximum: 12 18 | pub period_count: i32, 19 | 20 | /// The offer price, in milliunits. 21 | /// 22 | /// [Price](https://developer.apple.com/documentation/advancedcommerceapi/price) 23 | pub price: i64, 24 | 25 | /// The reason for the offer. 26 | /// 27 | /// [Reason](https://developer.apple.com/documentation/advancedcommerceapi/reason) 28 | pub reason: OfferReason, 29 | } 30 | 31 | impl Offer { 32 | pub fn new(period: OfferPeriod, period_count: i32, price: i64, reason: OfferReason) -> Self { 33 | Self { 34 | period, 35 | period_count, 36 | price, 37 | reason, 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/primitives/notification_history_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::notification_history_response_item::NotificationHistoryResponseItem; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A response that contains the App Store Server Notifications history for your app. 5 | /// 6 | /// [NotificationHistoryResponse](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponse) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 8 | pub struct NotificationHistoryResponse { 9 | /// A pagination token that you return to the endpoint on a subsequent call to receive the next set of results. 10 | /// 11 | /// [paginationToken](https://developer.apple.com/documentation/appstoreserverapi/paginationtoken) 12 | #[serde(rename = "paginationToken")] 13 | pub pagination_token: Option, 14 | 15 | /// A Boolean value indicating whether the App Store has more transaction data. 16 | /// 17 | /// [hasMore](https://developer.apple.com/documentation/appstoreserverapi/hasmore) 18 | #[serde(rename = "hasMore")] 19 | pub has_more: Option, 20 | 21 | /// An array of App Store server notification history records. 22 | #[serde(rename = "notificationHistory")] 23 | /// 24 | ///[notificationHistoryResponseItem](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponseitem) 25 | pub notification_history: Option>, 26 | } 27 | -------------------------------------------------------------------------------- /src/primitives/status_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::environment::Environment; 2 | use crate::primitives::subscription_group_identifier_item::SubscriptionGroupIdentifierItem; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The response that contains status information for all of a customer’s auto-renewable subscriptions in your app. 6 | /// 7 | /// [StatusResponse](https://developer.apple.com/documentation/appstoreserverapi/statusresponse) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 9 | pub struct StatusResponse { 10 | /// The server environment, sandbox or production, in which the App Store generated the response. 11 | /// 12 | /// [environment](https://developer.apple.com/documentation/appstoreserverapi/environment) 13 | pub environment: Option, 14 | 15 | /// The bundle identifier of an app. 16 | /// 17 | /// [bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 18 | #[serde(rename = "bundleId")] 19 | pub bundle_id: String, 20 | 21 | /// The unique identifier of an app in the App Store. 22 | /// 23 | /// [appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 24 | #[serde(rename = "appAppleId")] 25 | pub app_apple_id: Option, 26 | 27 | /// An array of information for auto-renewable subscriptions, including App Store-signed transaction information and App Store-signed renewal information. 28 | pub data: Vec, 29 | } 30 | -------------------------------------------------------------------------------- /tests/resources/xcode/xcode-signed-renewal-info: -------------------------------------------------------------------------------- 1 | eyJraWQiOiJBcHBsZV9YY29kZV9LZXkiLCJ0eXAiOiJKV1QiLCJ4NWMiOlsiTUlJQnpEQ0NBWEdnQXdJQkFnSUJBVEFLQmdncWhrak9QUVFEQWpCSU1TSXdJQVlEVlFRREV4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1TSXdJQVlEVlFRS0V4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1CNFhEVEl6TVRBeE9UQXhORFV6TmxvWERUSTBNVEF4T0RBeE5EVXpObG93U0RFaU1DQUdBMVVFQXhNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRFaU1DQUdBMVVFQ2hNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQktYRVFnWWpDb3VQdFRzdEdyS3BZOEk1M25IN3JiREhuY0lMR25vZ1NBdWxJSTNzXC91Zk0wZzlEYzNCY3I0OTdBVWd6R1R2V3Bpd0p4cGVCMzcxTmdWK2pUREJLTUJJR0ExVWRFd0VCXC93UUlNQVlCQWY4Q0FRQXdKQVlEVlIwUkJCMHdHNEVaVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEFPQmdOVkhROEJBZjhFQkFNQ0I0QXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTVp2VllKNjRDRitoMmZtc213dnpBY2VQcklEMTNycElKR0JFVytXZ3BwdEFpRUF4V2l5NCtUMXp0MzdWc3UwdmI2WXVtMCtOTHREcUhsSzZycE1jdjZKZm5BPSJdLCJhbGciOiJFUzI1NiJ9.eyJkZXZpY2VWZXJpZmljYXRpb24iOiJ1K1cxb1FUcXZGSE9RK1pCZTRRMHhQTUMyOGtxRUZ2YmJzRVBwTEtEVlJGdjFHSkdlZ21yTkhWb09ZTU9QdmIyIiwicHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIiwiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiIzNDM5OTE5ZS04N2M5LTQ3YjYtYWVlZS0yODIzZjdhOWQzYzMiLCJyZW5ld2FsRGF0ZSI6MTcwMDM1ODMzNjA0OS43Mjk3LCJvcmlnaW5hbFRyYW5zYWN0aW9uSWQiOiIwIiwicmVjZW50U3Vic2NyaXB0aW9uU3RhcnREYXRlIjoxNjk3Njc5OTM2MDQ5LjcyOTcsImF1dG9SZW5ld1N0YXR1cyI6MSwic2lnbmVkRGF0ZSI6MTY5NzY3OTkzNjcxMS4wNzQ3LCJlbnZpcm9ubWVudCI6Ilhjb2RlIiwiYXV0b1JlbmV3UHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIn0.WnT3aB9Lwjbr0ICUGn_5CdglzedVd7eOkrqirhcWFvwJZzN1FajuMV6gFEbgD82aL0Ix6HGZcwkNDlVNLvYOEQ -------------------------------------------------------------------------------- /tests/resources/xcode/xcode-signed-app-transaction: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQnpEQ0NBWEdnQXdJQkFnSUJBVEFLQmdncWhrak9QUVFEQWpCSU1TSXdJQVlEVlFRREV4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1TSXdJQVlEVlFRS0V4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1CNFhEVEl6TVRBeE9UQXhORFV6TmxvWERUSTBNVEF4T0RBeE5EVXpObG93U0RFaU1DQUdBMVVFQXhNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRFaU1DQUdBMVVFQ2hNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQktYRVFnWWpDb3VQdFRzdEdyS3BZOEk1M25IN3JiREhuY0lMR25vZ1NBdWxJSTNzXC91Zk0wZzlEYzNCY3I0OTdBVWd6R1R2V3Bpd0p4cGVCMzcxTmdWK2pUREJLTUJJR0ExVWRFd0VCXC93UUlNQVlCQWY4Q0FRQXdKQVlEVlIwUkJCMHdHNEVaVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEFPQmdOVkhROEJBZjhFQkFNQ0I0QXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTVp2VllKNjRDRitoMmZtc213dnpBY2VQcklEMTNycElKR0JFVytXZ3BwdEFpRUF4V2l5NCtUMXp0MzdWc3UwdmI2WXVtMCtOTHREcUhsSzZycE1jdjZKZm5BPSJdLCJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkFwcGxlX1hjb2RlX0tleSJ9.eyJidW5kbGVJZCI6ImNvbS5leGFtcGxlLm5hdHVyZWxhYi5iYWNreWFyZGJpcmRzLmV4YW1wbGUiLCJhcHBsaWNhdGlvblZlcnNpb24iOiIxIiwiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiI0OGM4YjkyZC1jZTBkLTQyMjktYmVkZi1lNjFiNGY5Y2ZjOTIiLCJyZWNlaXB0VHlwZSI6Ilhjb2RlIiwicmVjZWlwdENyZWF0aW9uRGF0ZSI6MTY5NzY4MDEyMjI1Ny40NDcsImRldmljZVZlcmlmaWNhdGlvbiI6ImNZVXNYYzUzRWJZYzBwT2VYRzVkNlwvMzFMR0hlVkdmODRzcVNOME9ySmk1dVwvajJIODlXV0tnUzhOMGhNc01sZiIsInJlcXVlc3REYXRlIjoxNjk3NjgwMTIyMjU3LjQ0Nywib3JpZ2luYWxBcHBsaWNhdGlvblZlcnNpb24iOiIxIiwib3JpZ2luYWxQdXJjaGFzZURhdGUiOi02MjEzNTc2OTYwMDAwMH0.Dpdk_VsO2MUCevwyS407alJpPc1Nq_UIP9EiDHaQBxlyi35NFnsKUVNuFNcGWrGRCCImnb4QGBKHfQC2i4sPCg -------------------------------------------------------------------------------- /src/primitives/subtype.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A string that provides details about select notification types in version 2. 4 | /// 5 | /// [subtype](https://developer.apple.com/documentation/appstoreservernotifications/subtype) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum Subtype { 8 | #[serde(rename = "INITIAL_BUY")] 9 | InitialBuy, 10 | #[serde(rename = "RESUBSCRIBE")] 11 | Resubscribe, 12 | #[serde(rename = "DOWNGRADE")] 13 | Downgrade, 14 | #[serde(rename = "UPGRADE")] 15 | Upgrade, 16 | #[serde(rename = "AUTO_RENEW_ENABLED")] 17 | AutoRenewEnabled, 18 | #[serde(rename = "AUTO_RENEW_DISABLED")] 19 | AutoRenewDisabled, 20 | #[serde(rename = "VOLUNTARY")] 21 | Voluntary, 22 | #[serde(rename = "BILLING_RETRY")] 23 | BillingRetry, 24 | #[serde(rename = "PRICE_INCREASE")] 25 | PriceIncrease, 26 | #[serde(rename = "GRACE_PERIOD")] 27 | GracePeriod, 28 | #[serde(rename = "PENDING")] 29 | Pending, 30 | #[serde(rename = "ACCEPTED")] 31 | Accepted, 32 | #[serde(rename = "BILLING_RECOVERY")] 33 | BillingRecovery, 34 | #[serde(rename = "PRODUCT_NOT_FOR_SALE")] 35 | ProductNotForSale, 36 | #[serde(rename = "SUMMARY")] 37 | Summary, 38 | #[serde(rename = "FAILURE")] 39 | Failure, 40 | #[serde(rename = "UNREPORTED")] 41 | Unreported, 42 | #[serde(rename = "ACTIVE_TOKEN_REMINDER")] 43 | ActiveTokenReminder, 44 | #[serde(rename = "CREATED")] 45 | Created, 46 | } 47 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_cancel_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::request_info::RequestInfo; 2 | use serde::{Deserialize, Serialize}; 3 | use uuid::Uuid; 4 | 5 | /// The request data your app provides to cancel an auto-renewable subscription. 6 | /// 7 | /// [SubscriptionCancelRequest](https://developer.apple.com/documentation/advancedcommerceapi/subscriptioncancelrequest) 8 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct SubscriptionCancelRequest { 11 | /// The metadata to include in server requests. 12 | /// 13 | /// [requestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 14 | pub request_info: RequestInfo, 15 | 16 | /// The storefront for the transaction. 17 | /// 18 | /// [storefront](https://developer.apple.com/documentation/advancedcommerceapi/onetimechargecreaterequest) 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub storefront: Option, 21 | } 22 | 23 | impl SubscriptionCancelRequest { 24 | pub fn new(request_reference_id: Uuid) -> Self { 25 | Self { 26 | request_info: RequestInfo::new(request_reference_id), 27 | storefront: None, 28 | } 29 | } 30 | 31 | pub fn with_storefront(mut self, storefront: String) -> Self { 32 | self.storefront = Some(storefront); 33 | self 34 | } 35 | 36 | pub fn with_request_info(mut self, request_info: RequestInfo) -> Self { 37 | self.request_info = request_info; 38 | self 39 | } 40 | } -------------------------------------------------------------------------------- /src/primitives/retention_messaging/realtime_response_body.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::retention_messaging::alternate_product::AlternateProduct; 2 | use crate::primitives::retention_messaging::message::Message; 3 | use crate::primitives::retention_messaging::promotional_offer::PromotionalOffer; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// A response you provide to choose, in real time, a retention message the system displays to the customer. 7 | /// 8 | /// [RealtimeResponseBody](https://developer.apple.com/documentation/retentionmessaging/realtimeresponsebody) 9 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 10 | pub struct RealtimeResponseBody { 11 | /// A retention message that's text-based and can include an optional image. 12 | /// 13 | /// [message](https://developer.apple.com/documentation/retentionmessaging/message) 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub message: Option, 16 | 17 | /// A retention message with a switch-plan option. 18 | /// 19 | /// [alternateProduct](https://developer.apple.com/documentation/retentionmessaging/alternateproduct) 20 | #[serde(rename = "alternateProduct", skip_serializing_if = "Option::is_none")] 21 | pub alternate_product: Option, 22 | 23 | /// A retention message that includes a promotional offer. 24 | /// 25 | /// [promotionalOffer](https://developer.apple.com/documentation/retentionmessaging/promotionaloffer) 26 | #[serde(rename = "promotionalOffer", skip_serializing_if = "Option::is_none")] 27 | pub promotional_offer: Option, 28 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce_transaction_item.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | use crate::primitives::advanced_commerce::offer::Offer; 4 | use crate::primitives::advanced_commerce::refund::Refund; 5 | 6 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] 7 | #[serde_with::serde_as] 8 | #[serde(rename_all = "camelCase")] 9 | /// [AdvancedCommerceTransactionItem](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetransactionitem) 10 | pub struct AdvancedCommerceTransactionItem { 11 | /// The SKU of the item. 12 | /// 13 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 14 | #[serde(rename = "SKU")] 15 | pub sku: String, 16 | 17 | /// The new description for the item. 18 | /// 19 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 20 | pub description: String, 21 | 22 | /// The display name for the item. 23 | /// 24 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 25 | pub display_name: String, 26 | 27 | /// An offer for the item. 28 | /// 29 | /// [Offer](https://developer.apple.com/documentation/advancedcommerceapi/offer) 30 | pub offer: Offer, 31 | 32 | /// The price in milliunits. 33 | /// 34 | /// [Price](https://developer.apple.com/documentation/advancedcommerceapi/price) 35 | pub price: i64, 36 | 37 | pub refunds: Vec, 38 | 39 | #[serde_as(as = "TimestampMilliSeconds")] 40 | pub revocation_date: DateTime, 41 | } 42 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/promotional_offer.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::retention_messaging::promotional_offer_signature_v1::PromotionalOfferSignatureV1; 2 | use serde::{Deserialize, Serialize}; 3 | use uuid::Uuid; 4 | 5 | /// A promotional offer and message you provide in a real-time response to your Get Retention Message endpoint. 6 | /// 7 | /// [promotionalOffer](https://developer.apple.com/documentation/retentionmessaging/promotionaloffer) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 9 | pub struct PromotionalOffer { 10 | /// The identifier of the message to display to the customer, along with the promotional offer. 11 | /// 12 | /// [messageIdentifier](https://developer.apple.com/documentation/retentionmessaging/messageidentifier) 13 | #[serde(rename = "messageIdentifier", skip_serializing_if = "Option::is_none")] 14 | pub message_identifier: Option, 15 | 16 | /// The promotional offer signature in V2 format. 17 | /// 18 | /// [promotionalOfferSignatureV2](https://developer.apple.com/documentation/retentionmessaging/promotionaloffersignaturev2) 19 | #[serde(rename = "promotionalOfferSignatureV2", skip_serializing_if = "Option::is_none")] 20 | pub promotional_offer_signature_v2: Option, 21 | 22 | /// The promotional offer signature in V1 format. 23 | /// 24 | /// [promotionalOfferSignatureV1](https://developer.apple.com/documentation/retentionmessaging/promotionaloffersignaturev1) 25 | #[serde(rename = "promotionalOfferSignatureV1", skip_serializing_if = "Option::is_none")] 26 | pub promotional_offer_signature_v1: Option, 27 | } -------------------------------------------------------------------------------- /src/primitives/last_transactions_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::status::Status; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The most recent App Store-signed transaction information and App Store-signed renewal information for an auto-renewable subscription. 5 | /// 6 | /// [lastTransactionsItem](https://developer.apple.com/documentation/appstoreserverapi/lasttransactionsitem) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq)] 8 | pub struct LastTransactionsItem { 9 | /// The status of the auto-renewable subscription. 10 | /// 11 | /// [status](https://developer.apple.com/documentation/appstoreserverapi/status) 12 | #[serde(rename = "status")] 13 | pub status: Option, 14 | 15 | /// The original transaction identifier of a purchase. 16 | /// 17 | /// [originalTransactionId](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid) 18 | #[serde(rename = "originalTransactionId")] 19 | pub original_transaction_id: Option, 20 | 21 | /// Transaction information signed by the App Store, in JSON Web Signature (JWS) format. 22 | /// 23 | /// [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 24 | #[serde(rename = "signedTransactionInfo")] 25 | pub signed_transaction_info: Option, 26 | 27 | /// Subscription renewal information, signed by the App Store, in JSON Web Signature (JWS) format. 28 | /// 29 | /// [JWSRenewalInfo](https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfo) 30 | #[serde(rename = "signedRenewalInfo")] 31 | pub signed_renewal_info: Option, 32 | } 33 | -------------------------------------------------------------------------------- /tests/common/transport_mock.rs: -------------------------------------------------------------------------------- 1 | use app_store_server_library::api_client::transport::{Transport, TransportError}; 2 | use http::StatusCode; 3 | 4 | pub type RequestVerifier = Box>, &Vec) -> () + Send + Sync>; 5 | 6 | pub struct MockTransport { 7 | response_body: String, 8 | status_code: StatusCode, 9 | request_verifier: Option, 10 | } 11 | 12 | impl MockTransport { 13 | pub fn new(body: String, status: StatusCode, verifier: Option) -> Self { 14 | Self { 15 | response_body: body, 16 | status_code: status, 17 | request_verifier: verifier, 18 | } 19 | } 20 | } 21 | 22 | impl Transport for MockTransport { 23 | async fn send(&self, req: http::Request>) -> Result>, TransportError> { 24 | let (parts, body) = req.into_parts(); 25 | 26 | // Call the verifier if present 27 | if let Some(ref verifier) = self.request_verifier { 28 | verifier( 29 | &http::Request::from_parts(parts.clone(), body.clone()), 30 | &body, 31 | ); 32 | } 33 | 34 | // Get the response body 35 | let body_str = self.response_body.clone(); 36 | let response_body = body_str.into_bytes(); 37 | 38 | // Build the response 39 | let response = http::Response::builder() 40 | .status(self.status_code) 41 | .header("Content-Type", "application/json") 42 | .body(response_body) 43 | .map_err(|e| TransportError::InvalidResponse(e.to_string()))?; 44 | 45 | Ok(response) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/request_refund_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::refund_reason::RefundReason; 2 | use serde::{Deserialize, Serialize}; 3 | use crate::primitives::advanced_commerce::refund_type::RefundType; 4 | 5 | /// The data your app provides to request a refund for an item. 6 | /// 7 | /// [RequestRefundItem](https://developer.apple.com/documentation/advancedcommerceapi/requestrefunditem) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct RequestRefundItem { 11 | /// The SKU identifier for the item to refund. 12 | /// 13 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 14 | #[serde(rename = "SKU")] 15 | pub sku: String, 16 | 17 | /// A refund amount, in milliunits of the currency. 18 | /// 19 | /// [RefundAmount](https://developer.apple.com/documentation/advancedcommerceapi/refundamount) 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub refund_amount: Option, 22 | 23 | /// The reason for the refund. 24 | /// 25 | /// [RefundReason](https://developer.apple.com/documentation/advancedcommerceapi/refundreason) 26 | pub refund_reason: RefundReason, 27 | 28 | /// The type of refund. Possible values: FULL, PRORATED, CUSTOM. 29 | /// 30 | /// [RefundType](https://developer.apple.com/documentation/advancedcommerceapi/refundtype) 31 | pub refund_type: RefundType, 32 | 33 | /// A Boolean value that indicates whether to revoke the item. 34 | /// 35 | /// [Revoke](https://developer.apple.com/documentation/advancedcommerceapi/revoke) 36 | pub revoke: bool, 37 | } -------------------------------------------------------------------------------- /src/primitives/notification_type_v2.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The type that describes the in-app purchase or external purchase event for which the App Store sends the version 2 notification. 4 | /// 5 | /// [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype) 6 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 7 | pub enum NotificationTypeV2 { 8 | #[serde(rename = "SUBSCRIBED")] 9 | Subscribed, 10 | #[serde(rename = "DID_CHANGE_RENEWAL_PREF")] 11 | DidChangeRenewalPref, 12 | #[serde(rename = "DID_CHANGE_RENEWAL_STATUS")] 13 | DidChangeRenewalStatus, 14 | #[serde(rename = "OFFER_REDEEMED")] 15 | OfferRedeemed, 16 | #[serde(rename = "DID_RENEW")] 17 | DidRenew, 18 | #[serde(rename = "EXPIRED")] 19 | Expired, 20 | #[serde(rename = "DID_FAIL_TO_RENEW")] 21 | DidFailToRenew, 22 | #[serde(rename = "GRACE_PERIOD_EXPIRED")] 23 | GracePeriodExpired, 24 | #[serde(rename = "PRICE_INCREASE")] 25 | PriceIncrease, 26 | #[serde(rename = "REFUND")] 27 | Refund, 28 | #[serde(rename = "REFUND_DECLINED")] 29 | RefundDeclined, 30 | #[serde(rename = "CONSUMPTION_REQUEST")] 31 | ConsumptionRequest, 32 | #[serde(rename = "RENEWAL_EXTENDED")] 33 | RenewalExtended, 34 | #[serde(rename = "REVOKE")] 35 | Revoke, 36 | #[serde(rename = "TEST")] 37 | Test, 38 | #[serde(rename = "RENEWAL_EXTENSION")] 39 | RenewalExtension, 40 | #[serde(rename = "REFUND_REVERSED")] 41 | RefundReversed, 42 | #[serde(rename = "EXTERNAL_PURCHASE_TOKEN")] 43 | ExternalPurchaseToken, 44 | #[serde(rename = "ONE_TIME_CHARGE")] 45 | OneTimeCharge, 46 | } 47 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/request_info.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | /// The metadata to include in Advanced Commerce server requests. 5 | /// 6 | /// [RequestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct RequestInfo { 10 | /// The app account token for the request. 11 | /// 12 | /// [App Account Token](https://developer.apple.com/documentation/advancedcommerceapi/appaccounttoken) 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub app_account_token: Option, 15 | 16 | /// The consistency token for the request. 17 | /// 18 | /// [Consistency Token](https://developer.apple.com/documentation/advancedcommerceapi/consistencytoken) 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub consistency_token: Option, 21 | 22 | /// The request reference identifier. 23 | /// 24 | /// [Request Reference ID](https://developer.apple.com/documentation/advancedcommerceapi/requestreferenceid) 25 | pub request_reference_id: Uuid, 26 | } 27 | 28 | impl RequestInfo { 29 | pub fn new(request_reference_id: Uuid) -> Self { 30 | Self { 31 | app_account_token: None, 32 | consistency_token: None, 33 | request_reference_id, 34 | } 35 | } 36 | 37 | pub fn with_app_account_token(mut self, token: Uuid) -> Self { 38 | self.app_account_token = Some(token); 39 | self 40 | } 41 | 42 | pub fn with_consistency_token(mut self, token: String) -> Self { 43 | self.consistency_token = Some(token); 44 | self 45 | } 46 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/request_refund_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::request_info::RequestInfo; 2 | use crate::primitives::advanced_commerce::request_refund_item::RequestRefundItem; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The request data your app provides to request refunds for items. 6 | /// 7 | /// [RequestRefundRequest](https://developer.apple.com/documentation/advancedcommerceapi/requestrefundrequest) 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct RequestRefundRequest { 11 | /// The metadata to include in server requests. 12 | /// 13 | /// [requestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 14 | pub request_info: RequestInfo, 15 | 16 | /// The currency of the refund amount. 17 | /// 18 | /// [currency](https://developer.apple.com/documentation/advancedcommerceapi/currency) 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub currency: Option, 21 | 22 | /// The list of items to request refunds for. 23 | /// 24 | /// [RequestRefundItem](https://developer.apple.com/documentation/advancedcommerceapi/requestrefunditem) 25 | pub items: Vec, 26 | 27 | /// A Boolean value that indicates the refund risking preference. 28 | /// 29 | /// [RefundRiskingPreference](https://developer.apple.com/documentation/advancedcommerceapi/refundriskingpreference) 30 | pub refund_risking_preference: bool, 31 | 32 | /// The storefront for the transaction. 33 | /// 34 | /// [storefront](https://developer.apple.com/documentation/advancedcommerceapi/storefront) 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub storefront: Option, 37 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/mod.rs: -------------------------------------------------------------------------------- 1 | // Core enums and types 2 | pub mod effective; 3 | pub mod offer; 4 | pub mod offer_period; 5 | pub mod offer_reason; 6 | pub mod period; 7 | pub mod reason; 8 | pub mod refund_reason; 9 | pub mod refund_type; 10 | pub mod request_info; 11 | pub mod validation_utils; 12 | 13 | pub mod one_time_charge_create_request; 14 | pub mod one_time_charge_item; 15 | 16 | pub mod request_refund_item; 17 | pub mod request_refund_request; 18 | pub mod request_refund_response; 19 | 20 | pub mod descriptors; 21 | pub mod subscription_modify_descriptors; 22 | pub mod subscription_cancel_request; 23 | pub mod subscription_cancel_response; 24 | pub mod subscription_change_metadata_descriptors; 25 | pub mod subscription_change_metadata_item; 26 | pub mod subscription_change_metadata_request; 27 | pub mod subscription_change_metadata_response; 28 | pub mod subscription_create_item; 29 | pub mod subscription_create_request; 30 | pub mod subscription_migrate_descriptors; 31 | pub mod subscription_migrate_item; 32 | pub mod subscription_migrate_renewal_item; 33 | pub mod subscription_migrate_request; 34 | pub mod subscription_modify_add_item; 35 | pub mod subscription_modify_change_item; 36 | pub mod subscription_modify_in_app_request; 37 | pub mod subscription_modify_period_change; 38 | pub mod subscription_modify_remove_item; 39 | pub mod subscription_price_change_item; 40 | pub mod subscription_price_change_request; 41 | pub mod subscription_price_change_response; 42 | pub mod subscription_reactivate_in_app_request; 43 | pub mod subscription_reactivate_item; 44 | pub mod subscription_revoke_request; 45 | pub mod subscription_revoke_response; 46 | pub mod subscription_migrate_response; 47 | pub mod in_app_request_operation; 48 | pub mod in_app_request_version; 49 | pub mod in_app_request; 50 | pub mod base_response; 51 | pub mod refund; 52 | -------------------------------------------------------------------------------- /src/primitives/extend_renewal_date_response.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_with::formats::Flexible; 4 | use serde_with::TimestampMilliSeconds; 5 | 6 | /// A response that indicates whether an individual renewal-date extension succeeded, and related details. 7 | /// 8 | /// [ExtendRenewalDateResponse](https://developer.apple.com/documentation/appstoreserverapi/extendrenewaldateresponse) 9 | #[serde_with::serde_as] 10 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 11 | pub struct ExtendRenewalDateResponse { 12 | /// The original transaction identifier of a purchase. 13 | /// 14 | /// [originalTransactionId](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid) 15 | #[serde(rename = "originalTransactionId")] 16 | pub original_transaction_id: Option, 17 | 18 | /// The unique identifier of subscription-purchase events across devices, including renewals. 19 | /// 20 | /// [webOrderLineItemId](https://developer.apple.com/documentation/appstoreserverapi/weborderlineitemid) 21 | #[serde(rename = "webOrderLineItemId")] 22 | pub web_order_line_item_id: Option, 23 | 24 | /// A Boolean value that indicates whether the subscription-renewal-date extension succeeded. 25 | /// 26 | /// [success](https://developer.apple.com/documentation/appstoreserverapi/success) 27 | #[serde(rename = "success")] 28 | pub success: Option, 29 | 30 | /// The new subscription expiration date for a subscription-renewal extension. 31 | /// 32 | /// [effectiveDate](https://developer.apple.com/documentation/appstoreserverapi/effectivedate) 33 | #[serde(rename = "effectiveDate")] 34 | #[serde_as(as = "Option>")] 35 | pub effective_date: Option>, 36 | } 37 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce_transaction_info.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::primitives::advanced_commerce::descriptors::Descriptors; 3 | use crate::primitives::advanced_commerce::period::Period; 4 | use crate::primitives::advanced_commerce_transaction_item::AdvancedCommerceTransactionItem; 5 | 6 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] 7 | #[serde(rename_all = "camelCase")] 8 | /// [AdvancedCommerceTransactionInfo](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetransactioninfo) 9 | pub struct AdvancedCommerceTransactionInfo { 10 | 11 | /// [descriptors](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercedescriptors) 12 | pub descriptors: Descriptors, 13 | 14 | /// [estimatedTax](https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceestimatedtax) 15 | pub estimated_tax: i64, 16 | 17 | /// [items](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetransactionitems) 18 | pub items: Vec, 19 | 20 | /// [period](https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceperiod) 21 | pub period: Period, 22 | 23 | /// [requestReferenceId](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerequestreferenceid) 24 | pub request_reference_id: String, 25 | 26 | /// [taxCode](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetaxcode) 27 | pub tax_code: String, 28 | 29 | /// [taxExclusivePrice](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetaxexclusiveprice) 30 | pub tax_exclusive_price: i64, 31 | 32 | /// [taxRate](https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetaxrate) 33 | pub tax_rate: String, 34 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_modify_descriptors.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::effective::Effective; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The data your app provides to change the description and display name of an auto-renewable subscription. 5 | /// 6 | /// [SubscriptionModifyDescriptors](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifydescriptors) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct SubscriptionModifyDescriptors { 10 | /// The description of the subscription. 11 | /// 12 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub description: Option, 15 | 16 | /// The display name of the subscription. 17 | /// 18 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub display_name: Option, 21 | 22 | /// When the modification takes effect. 23 | /// 24 | /// [Effective](https://developer.apple.com/documentation/advancedcommerceapi/effective) 25 | pub effective: Effective, 26 | } 27 | 28 | impl SubscriptionModifyDescriptors { 29 | pub fn new(effective: Effective) -> Self { 30 | Self { 31 | description: None, 32 | display_name: None, 33 | effective, 34 | } 35 | } 36 | 37 | pub fn with_description(mut self, description: String) -> Self { 38 | self.description = Some(description); 39 | self 40 | } 41 | 42 | pub fn with_display_name(mut self, display_name: String) -> Self { 43 | self.display_name = Some(display_name); 44 | self 45 | } 46 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_change_metadata_descriptors.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::effective::Effective; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Descriptors for the metadata changes of a subscription. 5 | /// 6 | /// [SubscriptionChangeMetadataDescriptors](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionchangemetadatadescriptors) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct SubscriptionChangeMetadataDescriptors { 10 | /// The new description for the subscription. 11 | /// 12 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub description: Option, 15 | 16 | /// The new display name for the subscription. 17 | /// 18 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub display_name: Option, 21 | 22 | /// The string that determines when the metadata change goes into effect. 23 | /// 24 | /// [Effective](https://developer.apple.com/documentation/advancedcommerceapi/effective) 25 | pub effective: Effective, 26 | } 27 | 28 | impl SubscriptionChangeMetadataDescriptors { 29 | pub fn new(effective: Effective) -> Self { 30 | Self { 31 | description: None, 32 | display_name: None, 33 | effective, 34 | } 35 | } 36 | 37 | pub fn with_description(mut self, description: String) -> Self { 38 | self.description = Some(description); 39 | self 40 | } 41 | 42 | pub fn with_display_name(mut self, display_name: String) -> Self { 43 | self.display_name = Some(display_name); 44 | self 45 | } 46 | } -------------------------------------------------------------------------------- /tests/resources/xcode/xcode-signed-transaction: -------------------------------------------------------------------------------- 1 | eyJraWQiOiJBcHBsZV9YY29kZV9LZXkiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlCekRDQ0FYR2dBd0lCQWdJQkFUQUtCZ2dxaGtqT1BRUURBakJJTVNJd0lBWURWUVFERXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTVNJd0lBWURWUVFLRXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTUI0WERUSXpNVEF4T1RBeE5EVXpObG9YRFRJME1UQXhPREF4TkRVek5sb3dTREVpTUNBR0ExVUVBeE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEVpTUNBR0ExVUVDaE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCS1hFUWdZakNvdVB0VHN0R3JLcFk4STUzbkg3cmJESG5jSUxHbm9nU0F1bElJM3NcL3VmTTBnOURjM0JjcjQ5N0FVZ3pHVHZXcGl3SnhwZUIzNzFOZ1YralREQktNQklHQTFVZEV3RUJcL3dRSU1BWUJBZjhDQVFBd0pBWURWUjBSQkIwd0c0RVpVM1J2Y21WTGFYUWdWR1Z6ZEdsdVp5QnBiaUJZWTI5a1pUQU9CZ05WSFE4QkFmOEVCQU1DQjRBd0NnWUlLb1pJemowRUF3SURTUUF3UmdJaEFNWnZWWUo2NENGK2gyZm1zbXd2ekFjZVBySUQxM3JwSUpHQkVXK1dncHB0QWlFQXhXaXk0K1QxenQzN1ZzdTB2YjZZdW0wK05MdERxSGxLNnJwTWN2NkpmbkE9Il19.eyJpbkFwcE93bmVyc2hpcFR5cGUiOiJQVVJDSEFTRUQiLCJwdXJjaGFzZURhdGUiOjE2OTc2Nzk5MzYwNDkuNzI5Nywic3Vic2NyaXB0aW9uR3JvdXBJZGVudGlmaWVyIjoiNkYzQTkzQUIiLCJzaWduZWREYXRlIjoxNjk3Njc5OTM2MDU2LjQ4NSwib3JpZ2luYWxQdXJjaGFzZURhdGUiOjE2OTc2Nzk5MzYwNDkuNzI5NywiaXNVcGdyYWRlZCI6ZmFsc2UsImRldmljZVZlcmlmaWNhdGlvbiI6InNHRG5wZytvemI4dXdEU3VDRFoyb1ZabzFDS3JiQjh1alI4VnhDeGh5a1J3eUJJSzZ4NlhDeUVSbTh5V3J6RTgiLCJvZmZlclR5cGUiOjEsInF1YW50aXR5IjoxLCJ0cmFuc2FjdGlvbklkIjoiMCIsInR5cGUiOiJBdXRvLVJlbmV3YWJsZSBTdWJzY3JpcHRpb24iLCJ0cmFuc2FjdGlvblJlYXNvbiI6IlBVUkNIQVNFIiwicHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIiwiZXhwaXJlc0RhdGUiOjE3MDAzNTgzMzYwNDkuNzI5NywiZW52aXJvbm1lbnQiOiJYY29kZSIsInN0b3JlZnJvbnRJZCI6IjE0MzQ0MSIsIm9yaWdpbmFsVHJhbnNhY3Rpb25JZCI6IjAiLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlLm5hdHVyZWxhYi5iYWNreWFyZGJpcmRzLmV4YW1wbGUiLCJkZXZpY2VWZXJpZmljYXRpb25Ob25jZSI6IjdlZGVhODdkLTk4ZjAtNDJkMC05NjgyLTQ5Y2E4MTAyMmY3MyIsIndlYk9yZGVyTGluZUl0ZW1JZCI6IjAiLCJzdG9yZWZyb250IjoiVVNBIn0.rkJYnvujStteRkMHhoIR2ThmNFnyKcx5XxIakXYdh-1oKtEVEU5zQAiONaLDpBDO5JhLLrTbfp7LS5tMiqmgHw -------------------------------------------------------------------------------- /src/primitives/update_app_account_token_request.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | /// The request body for the Set App Account Token endpoint. 5 | /// 6 | /// # References 7 | /// [UpdateAppAccountTokenRequest](https://developer.apple.com/documentation/appstoreserverapi/updateappaccounttokenrequest) 8 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct UpdateAppAccountTokenRequest { 11 | /// The UUID that an app optionally generates to map a customer's in-app purchase with its resulting App Store transaction. 12 | /// 13 | /// # References 14 | /// [appAccountToken](https://developer.apple.com/documentation/appstoreserverapi/appaccounttoken) 15 | pub app_account_token: Uuid, 16 | } 17 | 18 | impl UpdateAppAccountTokenRequest { 19 | /// Creates a new UpdateAppAccountTokenRequest with the specified app account token. 20 | pub fn new(app_account_token: Uuid) -> Self { 21 | Self { app_account_token } 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | use serde_json; 29 | 30 | #[test] 31 | fn test_serialization() { 32 | let token = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); 33 | let request = UpdateAppAccountTokenRequest::new(token); 34 | 35 | let json = serde_json::to_string(&request).unwrap(); 36 | let expected = r#"{"appAccountToken":"550e8400-e29b-41d4-a716-446655440000"}"#; 37 | assert_eq!(json, expected); 38 | } 39 | 40 | #[test] 41 | fn test_deserialization() { 42 | let json = r#"{"appAccountToken":"550e8400-e29b-41d4-a716-446655440000"}"#; 43 | let request: UpdateAppAccountTokenRequest = serde_json::from_str(json).unwrap(); 44 | 45 | assert_eq!( 46 | request.app_account_token.to_string(), 47 | "550e8400-e29b-41d4-a716-446655440000" 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_create_item.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::offer::Offer; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The data that describes a subscription item. 5 | /// 6 | /// [SubscriptionCreateItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptioncreateitem) 7 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct SubscriptionCreateItem { 10 | /// The item's product identifier, which you define. 11 | /// 12 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 13 | #[serde(rename = "SKU")] 14 | pub sku: String, 15 | 16 | /// A description of the product. 17 | /// 18 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 19 | pub description: String, 20 | 21 | /// The product name, suitable for display to customers. 22 | /// 23 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 24 | pub display_name: String, 25 | 26 | /// The price in milliunits. 27 | /// 28 | /// [Price](https://developer.apple.com/documentation/advancedcommerceapi/price) 29 | pub price: i64, 30 | 31 | /// An offer for the subscription. 32 | /// 33 | /// [Offer](https://developer.apple.com/documentation/advancedcommerceapi/offer) 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub offer: Option, 36 | } 37 | 38 | impl SubscriptionCreateItem { 39 | pub fn new( 40 | sku: String, 41 | description: String, 42 | display_name: String, 43 | price: i64, 44 | ) -> Self { 45 | Self { 46 | sku, 47 | description, 48 | display_name, 49 | price, 50 | offer: None, 51 | } 52 | } 53 | 54 | pub fn with_offer(mut self, offer: Offer) -> Self { 55 | self.offer = Some(offer); 56 | self 57 | } 58 | } -------------------------------------------------------------------------------- /src/primitives/retention_messaging/upload_message_image.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | const MAXIMUM_ALT_TEXT_LENGTH: usize = 150; 5 | 6 | /// The definition of an image with its alternative text. 7 | /// 8 | /// [UploadMessageImage](https://developer.apple.com/documentation/retentionmessaging/uploadmessageimage) 9 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 10 | pub struct UploadMessageImage { 11 | /// The unique identifier of an image. 12 | /// 13 | /// [imageIdentifier](https://developer.apple.com/documentation/retentionmessaging/imageidentifier) 14 | #[serde(rename = "imageIdentifier")] 15 | pub image_identifier: Uuid, 16 | 17 | /// The alternative text you provide for the corresponding image. 18 | /// 19 | /// [altText](https://developer.apple.com/documentation/retentionmessaging/alttext) 20 | #[serde(rename = "altText")] 21 | pub alt_text: String, 22 | } 23 | 24 | impl UploadMessageImage { 25 | /// Creates a new UploadMessageImage with validation. 26 | /// 27 | /// # Errors 28 | /// 29 | /// Returns `ValidationError::AltTextTooLong` if alt_text exceeds 150 characters. 30 | pub fn new(image_identifier: Uuid, alt_text: String) -> Result { 31 | if alt_text.len() > MAXIMUM_ALT_TEXT_LENGTH { 32 | return Err(ValidationError::AltTextTooLong); 33 | } 34 | Ok(Self { 35 | image_identifier, 36 | alt_text, 37 | }) 38 | } 39 | } 40 | 41 | #[derive(Debug, Clone, PartialEq, Eq)] 42 | pub enum ValidationError { 43 | AltTextTooLong, 44 | } 45 | 46 | impl std::fmt::Display for ValidationError { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | match self { 49 | ValidationError::AltTextTooLong => { 50 | write!(f, "Alt text exceeds maximum length of {} characters", MAXIMUM_ALT_TEXT_LENGTH) 51 | } 52 | } 53 | } 54 | } 55 | 56 | impl std::error::Error for ValidationError {} -------------------------------------------------------------------------------- /src/primitives/mass_extend_renewal_date_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::extend_reason_code::ExtendReasonCode; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The request body that contains subscription-renewal-extension data to apply for all eligible active subscribers. 5 | /// 6 | /// [MassExtendRenewalDateRequest](https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldaterequest) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct MassExtendRenewalDateRequest { 9 | /// The number of days to extend the subscription renewal date. 10 | /// 11 | /// [extendByDays](https://developer.apple.com/documentation/appstoreserverapi/extendbydays) 12 | /// maximum: 90 13 | #[serde(rename = "extendByDays")] 14 | pub extend_by_days: i32, 15 | 16 | /// The reason code for the subscription-renewal-date extension. 17 | /// 18 | /// [extendReasonCode](https://developer.apple.com/documentation/appstoreserverapi/extendreasoncode) 19 | #[serde(rename = "extendReasonCode")] 20 | pub extend_reason_code: ExtendReasonCode, 21 | 22 | /// A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 23 | /// 24 | /// [requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 25 | #[serde(rename = "requestIdentifier")] 26 | pub request_identifier: String, 27 | 28 | /// A list of storefront country codes you provide to limit the storefronts for a subscription-renewal-date extension. 29 | /// 30 | /// [storefrontCountryCodes](https://developer.apple.com/documentation/appstoreserverapi/storefrontcountrycodes) 31 | #[serde(rename = "storefrontCountryCodes")] 32 | pub storefront_country_codes: Vec, 33 | 34 | /// The unique identifier for the product, that you create in App Store Connect. 35 | /// 36 | /// [productId](https://developer.apple.com/documentation/appstoreserverapi/productid) 37 | #[serde(rename = "productId")] 38 | pub product_id: String, 39 | } 40 | -------------------------------------------------------------------------------- /src/primitives/history_response.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::environment::Environment; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A response that contains the customer’s transaction history for an app. 5 | /// 6 | /// [HistoryResponse](https://developer.apple.com/documentation/appstoreserverapi/historyresponse) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 8 | pub struct HistoryResponse { 9 | /// A token you use in a query to request the next set of transactions for the customer. 10 | /// 11 | /// [revision](https://developer.apple.com/documentation/appstoreserverapi/revision) 12 | #[serde(rename = "revision")] 13 | pub revision: Option, 14 | 15 | /// A Boolean value indicating whether the App Store has more transaction data. 16 | /// 17 | /// [hasMore](https://developer.apple.com/documentation/appstoreserverapi/hasmore) 18 | #[serde(rename = "hasMore")] 19 | pub has_more: Option, 20 | 21 | /// The bundle identifier of an app. 22 | /// 23 | /// [bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 24 | #[serde(rename = "bundleId")] 25 | pub bundle_id: Option, 26 | 27 | /// The unique identifier of an app in the App Store. 28 | /// 29 | /// [appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 30 | #[serde(rename = "appAppleId")] 31 | pub app_apple_id: Option, 32 | 33 | /// The server environment in which you’re making the request, whether sandbox or production. 34 | /// 35 | /// [environment](https://developer.apple.com/documentation/appstoreserverapi/environment) 36 | #[serde(rename = "environment")] 37 | pub environment: Option, 38 | 39 | /// An array of in-app purchase transactions for the customer, signed by Apple, in JSON Web Signature format. 40 | /// 41 | /// [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 42 | #[serde(rename = "signedTransactions")] 43 | pub signed_transactions: Option>, 44 | } 45 | -------------------------------------------------------------------------------- /tests/resources/xcode/xcode-app-receipt-empty: -------------------------------------------------------------------------------- 1 | MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSABIHhMYHeMA8CAQACAQEEBwwFWGNvZGUwCwIBAQIBAQQDAgEAMDUCAQICAQEELQwrY29tLmV4YW1wbGUubmF0dXJlbGFiLmJhY2t5YXJkYmlyZHMuZXhhbXBsZTALAgEDAgEBBAMMATEwEAIBBAIBAQQI0bz+zwQAAAAwHAIBBQIBAQQU4nEwK24WxZhKi0PSGTYgWoXOIqMwCgIBCAIBAQQCFgAwHgIBDAIBAQQWFhQyMDIzLTEwLTE5VDAxOjE4OjU0WjAeAgEVAgEBBBYWFDQwMDEtMDEtMDFUMDA6MDA6MDBaAAAAAAAAoIIDeDCCA3QwggJcoAMCAQICAQEwDQYJKoZIhvcNAQELBQAwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MB4XDTIwMDQwMTE3NTIzNVoXDTQwMDMyNzE3NTIzNVowXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23+QPCxzD9uXJkuTuwr4oSE+yGHZJMheH3U+2pPbMRqRgLm/5QzLPLsORGIm+gQptknnb+Ab5g1ozSVuw3YI9UoLrnp0PMSpC7PPYg/7tLz324ReKOtHDfHti6z1n7AJOKNue8smUAoa4YnRcnYLOUzLT27As1+3lbq5qF1KdKvvb0GlfgmNuj09zXBX2O3v1dp3yJMEHO8JiHhlzoHyjXLnBxpuJhL3MrENuziQawbE/A3llVDNkci6JfRYyYzhcdtKRfMtGZYDVoGmRO51d1tTz3isXbo+X1ArXCmM3cLXKhffIrTX5Hior6htp8HaaC1mzM8pC1As48L75l8SwQIDAQABozswOTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIChDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAsgDgPPHo6WK9wNYdQJ5XuTiQd3ZS0qhLcG64Z5n7s4pVn+8dKLhfKtFznzVHN7tG03YQ8vBp7M1imXH5YIqESDjEvYtnJbmrbDNlrdjCmnhID+nMwScNxs9kPG2AWTOMyjYGKhEbjUnOCP9mwEcoS+tawSsJViylqgkDezIx3OiFeEjOwMUSEWoPDK4vBcpvemR/ICx15kyxEtP94x9eDX24WNegfOR/Y6uXmivDKtjQsuHVWg05G29nKKkSg9aHeG2ZvV6zCuCYzvbqw45taeu3QIE9hz1wUdHEXY2l3H9qWBreYHY3Uuz/rBldDBUvig/1icjXKx0e7CuRBac9TzGCAY8wggGLAgEBMGQwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0AgEBMA0GCWCGSAFlAwQCAQUAMA0GCSqGSIb3DQEBCwUABIIBAIjP3bmY+TrOM0e8n7PeH3OEies1+spNT1n8om4424n/NyIJ9XRyj1QGxshxh6p2BQuUQV8mkWKpHYQJqPobVEcl72ndbHSfzkH2vM57jy/2bCopLt+zWQl0QMA9iKEB3G075wgyD6lcSveZnER/4J6E9+tO6O3R2YFVziwL2UmNR1XgfOhKyNwCfSV1CyVVoSUkkZI7fJ1S6Pce2nLKM1pf+oCWr5vAySd9E4givt/YagGJF+3RHZMEcrqHnnP8kQKi99xnXcIfYyK6VMD9uBb2+4N7MCRDhoY/8+vX9I75paW0UicS6MwacJPueNxLaAboOP4nFSlYhEhZuLiZrdIAAAAAAAA= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/**/workspace.xml 2 | .idea/**/tasks.xml 3 | .idea/**/usage.statistics.xml 4 | .idea/**/dictionaries 5 | .idea/**/shelf 6 | 7 | # AWS User-specific 8 | .idea/**/aws.xml 9 | 10 | # Generated files 11 | .idea/**/contentModel.xml 12 | 13 | # Sensitive or high-churn files 14 | .idea/**/dataSources/ 15 | .idea/**/dataSources.ids 16 | .idea/**/dataSources.local.xml 17 | .idea/**/sqlDataSources.xml 18 | .idea/**/dynamic.xml 19 | .idea/**/uiDesigner.xml 20 | .idea/**/dbnavigator.xml 21 | 22 | # Gradle 23 | .idea/**/gradle.xml 24 | .idea/**/libraries 25 | 26 | # Gradle and Maven with auto-import 27 | # When using Gradle or Maven with auto-import, you should exclude module files, 28 | # since they will be recreated, and may cause churn. Uncomment if using 29 | # auto-import. 30 | # .idea/artifacts 31 | # .idea/compiler.xml 32 | # .idea/jarRepositories.xml 33 | # .idea/modules.xml 34 | # .idea/*.iml 35 | # .idea/modules 36 | # *.iml 37 | # *.ipr 38 | 39 | # CMake 40 | cmake-build-*/ 41 | 42 | # Mongo Explorer plugin 43 | .idea/**/mongoSettings.xml 44 | 45 | # File-based project format 46 | *.iws 47 | 48 | # IntelliJ 49 | out/ 50 | 51 | # mpeltonen/sbt-idea plugin 52 | .idea_modules/ 53 | 54 | # JIRA plugin 55 | atlassian-ide-plugin.xml 56 | 57 | # Cursive Clojure plugin 58 | .idea/replstate.xml 59 | 60 | # SonarLint plugin 61 | .idea/sonarlint/ 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | 72 | # Android studio 3.1+ serialized cache file 73 | .idea/caches/build_file_checksums.ser 74 | 75 | ### CLion+all Patch ### 76 | # Ignore everything but code style settings and run configurations 77 | # that are supposed to be shared within teams. 78 | 79 | .idea/* 80 | 81 | !.idea/codeStyles 82 | !.idea/runConfigurations 83 | 84 | ### Rust ### 85 | # Generated by Cargo 86 | # will have compiled files and executables 87 | debug/ 88 | target/ 89 | 90 | Cargo.lock 91 | # These are backup files generated by rustfmt 92 | **/*.rs.bk 93 | 94 | .env 95 | .claude 96 | CLAUDE.md 97 | rustfmt.toml 98 | -------------------------------------------------------------------------------- /tests/receipt_utility.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "receipt-utility")] 2 | 3 | use app_store_server_library::receipt_utility::{ 4 | extract_transaction_id_from_app_receipt, extract_transaction_id_from_transaction_receipt, 5 | }; 6 | use std::fs; 7 | 8 | const APP_RECEIPT_EXPECTED_TRANSACTION_ID: &str = "0"; 9 | const TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID: &str = "33993399"; 10 | 11 | #[test] 12 | fn test_xcode_app_receipt_extraction_with_no_transactions() { 13 | let receipt = fs::read_to_string("tests/resources/xcode/xcode-app-receipt-empty").expect("Failed to read file"); 14 | let extracted_transaction_id = extract_transaction_id_from_app_receipt(&receipt); 15 | 16 | assert!(extracted_transaction_id 17 | .expect("Expect Result") 18 | .is_none()); 19 | } 20 | 21 | #[test] 22 | fn test_xcode_app_receipt_extraction_with_transactions() { 23 | let receipt = 24 | fs::read_to_string("tests/resources/xcode/xcode-app-receipt-with-transaction").expect("Failed to read file"); 25 | let extracted_transaction_id = extract_transaction_id_from_app_receipt(&receipt); 26 | 27 | assert_eq!( 28 | Some(APP_RECEIPT_EXPECTED_TRANSACTION_ID), 29 | extracted_transaction_id 30 | .expect("Expect Result") 31 | .as_deref() 32 | ); 33 | } 34 | 35 | #[test] 36 | fn test_transaction_receipt_extraction() { 37 | let receipt = fs::read_to_string("tests/resources/mock_signed_data/legacyTransaction").expect("Failed to read file"); 38 | let extracted_transaction_id = extract_transaction_id_from_transaction_receipt(&receipt); 39 | 40 | assert_eq!( 41 | Some(TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID), 42 | extracted_transaction_id 43 | .expect("Expect Result") 44 | .as_deref() 45 | ); 46 | } 47 | 48 | #[test] 49 | fn test_extract_transaction_id_from_app_receipt() { 50 | let receipt = fs::read_to_string("tests/resources/xcode/xcode-app-receipt-legacy").expect("Failed to read file"); 51 | let extracted_transaction_id = extract_transaction_id_from_app_receipt(&receipt); 52 | assert_eq!( 53 | Some("2000000909538865"), 54 | extracted_transaction_id 55 | .expect("Expect Result") 56 | .as_deref() 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_reactivate_in_app_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::request_info::RequestInfo; 2 | use crate::primitives::advanced_commerce::subscription_reactivate_item::SubscriptionReactivateItem; 3 | use serde::{Deserialize, Serialize}; 4 | use crate::primitives::advanced_commerce::in_app_request::AdvancedCommerceInAppRequest; 5 | use crate::primitives::advanced_commerce::in_app_request_operation::InAppRequestOperation; 6 | use crate::primitives::advanced_commerce::in_app_request_version::InAppRequestVersion; 7 | 8 | /// The metadata your app provides to reactivate an auto-renewable subscription. 9 | /// 10 | /// [SubscriptionReactivateInAppRequest](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionreactivateinapprequest) 11 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct SubscriptionReactivateInAppRequest { 14 | /// The operation type for this request. 15 | /// Value: REACTIVATE_SUBSCRIPTION 16 | pub operation: InAppRequestOperation, 17 | 18 | /// The version of this request. 19 | pub version: InAppRequestVersion, 20 | 21 | /// The details of the reactivation items. 22 | /// 23 | /// [SubscriptionReactivateItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionreactivateitem) 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | pub items: Option>, 26 | 27 | /// The metadata to include in server requests. 28 | /// 29 | /// [requestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 30 | pub request_info: RequestInfo, 31 | 32 | /// The storefront for the transaction. 33 | /// 34 | /// [storefront](https://developer.apple.com/documentation/advancedcommerceapi/storefront) 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub storefront: Option, 37 | 38 | /// The original transaction ID of the subscription. 39 | /// 40 | /// [transactionId](https://developer.apple.com/documentation/advancedcommerceapi/transactionid) 41 | pub transaction_id: String, 42 | } 43 | 44 | impl AdvancedCommerceInAppRequest for SubscriptionReactivateInAppRequest {} -------------------------------------------------------------------------------- /tests/resources/xcode/xcode-app-receipt-with-transaction: -------------------------------------------------------------------------------- 1 | MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSABIIBdjGCAXIwDwIBAAIBAQQHDAVYY29kZTALAgEBAgEBBAMCAQAwNQIBAgIBAQQtDCtjb20uZXhhbXBsZS5uYXR1cmVsYWIuYmFja3lhcmRiaXJkcy5leGFtcGxlMAsCAQMCAQEEAwwBMTAQAgEEAgEBBAjyv/X7DwAAADAcAgEFAgEBBBQWU6vLoHZxeVVlaOg/UEG2OOKahTAKAgEIAgEBBAIWADAeAgEMAgEBBBYWFDIwMjMtMTAtMTlUMDE6NDU6NDBaMIGRAgERAgEBBIGIMYGFMAwCAgalAgEBBAMCAQEwFwICBqYCAQEEDgwMcGFzcy5wcmVtaXVtMAwCAganAgEBBAMMATAwHwICBqgCAQEEFhYUMjAyMy0xMC0xOVQwMTo0NTozNlowHwICBqwCAQEEFhYUMjAyMy0xMS0xOVQwMTo0NTozNlowDAICBrcCAQEEAwIBATAeAgEVAgEBBBYWFDQwMDEtMDEtMDFUMDA6MDA6MDBaAAAAAAAAoIIDeDCCA3QwggJcoAMCAQICAQEwDQYJKoZIhvcNAQELBQAwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MB4XDTIwMDQwMTE3NTIzNVoXDTQwMDMyNzE3NTIzNVowXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23+QPCxzD9uXJkuTuwr4oSE+yGHZJMheH3U+2pPbMRqRgLm/5QzLPLsORGIm+gQptknnb+Ab5g1ozSVuw3YI9UoLrnp0PMSpC7PPYg/7tLz324ReKOtHDfHti6z1n7AJOKNue8smUAoa4YnRcnYLOUzLT27As1+3lbq5qF1KdKvvb0GlfgmNuj09zXBX2O3v1dp3yJMEHO8JiHhlzoHyjXLnBxpuJhL3MrENuziQawbE/A3llVDNkci6JfRYyYzhcdtKRfMtGZYDVoGmRO51d1tTz3isXbo+X1ArXCmM3cLXKhffIrTX5Hior6htp8HaaC1mzM8pC1As48L75l8SwQIDAQABozswOTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIChDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAsgDgPPHo6WK9wNYdQJ5XuTiQd3ZS0qhLcG64Z5n7s4pVn+8dKLhfKtFznzVHN7tG03YQ8vBp7M1imXH5YIqESDjEvYtnJbmrbDNlrdjCmnhID+nMwScNxs9kPG2AWTOMyjYGKhEbjUnOCP9mwEcoS+tawSsJViylqgkDezIx3OiFeEjOwMUSEWoPDK4vBcpvemR/ICx15kyxEtP94x9eDX24WNegfOR/Y6uXmivDKtjQsuHVWg05G29nKKkSg9aHeG2ZvV6zCuCYzvbqw45taeu3QIE9hz1wUdHEXY2l3H9qWBreYHY3Uuz/rBldDBUvig/1icjXKx0e7CuRBac9TzGCAY8wggGLAgEBMGQwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0AgEBMA0GCWCGSAFlAwQCAQUAMA0GCSqGSIb3DQEBCwUABIIBAMNY9TpOCg59NnKdDA6Xc4D74lEaa+YwQqD/z8ajAGxpw3efoQRvx8Q1qR6IVs9BcRYGyJmsFrau19QeSIRjjqaxhV8ZbRFenWp0Yps6OCPVHw94Ej3AstAL/8WIArBM1OS6OZJESJdQz5xpwavWLGm1rU2730glMdHzHfm2h0wNp/0BKV0ugV9SRQN4RsyAMNS+rCO1mtSDI6nx8E+dEVMIa4mUg+yhXRlg6KzdzKWnr9vDtRVmhdq0ANfP+jfvncsyC+d/c3cAsXOK066hKFwYWTKaRZ7M2eXus5TcU83/aaovHyKVyKKCRnKuP7VPt9d5eWLSg/7v2ctHJtjmhqsAAAAAAAA= -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/one_time_charge_create_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::one_time_charge_item::OneTimeChargeItem; 2 | use crate::primitives::advanced_commerce::request_info::RequestInfo; 3 | use serde::{Deserialize, Serialize}; 4 | use crate::primitives::advanced_commerce::in_app_request::AdvancedCommerceInAppRequest; 5 | use crate::primitives::advanced_commerce::in_app_request_operation::InAppRequestOperation; 6 | use crate::primitives::advanced_commerce::in_app_request_version::InAppRequestVersion; 7 | 8 | /// The request data your app provides when a customer purchases a one-time-charge product. 9 | /// 10 | /// [OneTimeChargeCreateRequest](https://developer.apple.com/documentation/advancedcommerceapi/onetimechargecreaterequest) 11 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct OneTimeChargeCreateRequest { 14 | /// The operation type for this request. 15 | /// Value: CREATE_ONE_TIME_CHARGE 16 | pub operation: InAppRequestOperation, 17 | 18 | /// The version of this request. 19 | pub version: InAppRequestVersion, 20 | 21 | /// The metadata to include in server requests. 22 | /// 23 | /// [requestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 24 | pub request_info: RequestInfo, 25 | 26 | /// The currency of the price of the product. 27 | /// 28 | /// [currency](https://developer.apple.com/documentation/advancedcommerceapi/currency) 29 | pub currency: String, 30 | 31 | /// The details of the product for purchase. 32 | /// 33 | /// [OneTimeChargeItem](https://developer.apple.com/documentation/advancedcommerceapi/onetimechargeitem) 34 | pub item: OneTimeChargeItem, 35 | 36 | /// The storefront for the transaction. 37 | /// 38 | /// [storefront](https://developer.apple.com/documentation/advancedcommerceapi/storefront) 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub storefront: Option, 41 | 42 | /// The tax code for this product. 43 | /// 44 | /// [taxCode](https://developer.apple.com/documentation/advancedcommerceapi/version) 45 | pub tax_code: String, 46 | } 47 | 48 | impl AdvancedCommerceInAppRequest for OneTimeChargeCreateRequest {} 49 | -------------------------------------------------------------------------------- /src/primitives/mass_extend_renewal_date_status_response.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_with::formats::Flexible; 4 | use serde_with::TimestampMilliSeconds; 5 | 6 | /// A response that indicates the current status of a request to extend the subscription renewal date to all eligible subscribers. 7 | /// 8 | /// [MassExtendRenewalDateStatusResponse](https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldatestatusresponse) 9 | #[serde_with::serde_as] 10 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 11 | pub struct MassExtendRenewalDateStatusResponse { 12 | /// A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 13 | /// 14 | /// [requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 15 | #[serde(rename = "requestIdentifier")] 16 | pub request_identifier: Option, 17 | 18 | /// A Boolean value that indicates whether the App Store completed the request to extend a subscription renewal date to active subscribers. 19 | /// 20 | /// [complete](https://developer.apple.com/documentation/appstoreserverapi/complete) 21 | pub complete: Option, 22 | 23 | /// The UNIX time, in milliseconds, that the App Store completes a request to extend a subscription renewal date for eligible subscribers. 24 | /// 25 | /// [completeDate](https://developer.apple.com/documentation/appstoreserverapi/completedate) 26 | #[serde(rename = "completeDate")] 27 | #[serde_as(as = "Option>")] 28 | pub complete_date: Option>, 29 | 30 | /// The count of subscriptions that successfully receive a subscription-renewal-date extension. 31 | /// 32 | /// [succeededCount](https://developer.apple.com/documentation/appstoreserverapi/succeededcount) 33 | #[serde(rename = "succeededCount")] 34 | pub succeeded_count: Option, 35 | 36 | /// The count of subscriptions that fail to receive a subscription-renewal-date extension. 37 | /// 38 | /// [failedCount](https://developer.apple.com/documentation/appstoreserverapi/failedcount) 39 | #[serde(rename = "failedCount")] 40 | pub failed_count: Option, 41 | } 42 | -------------------------------------------------------------------------------- /src/primitives/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account_tenure; 2 | pub mod app_transaction; 3 | pub mod app_transaction_info_response; 4 | pub mod auto_renew_status; 5 | pub mod check_test_notification_response; 6 | pub mod consumption_request; 7 | pub mod consumption_request_reason; 8 | pub mod consumption_status; 9 | pub mod data; 10 | pub mod delivery_status; 11 | pub mod environment; 12 | pub mod expiration_intent; 13 | pub mod extend_reason_code; 14 | pub mod extend_renewal_date_request; 15 | pub mod extend_renewal_date_response; 16 | pub mod external_purchase_token; 17 | pub mod first_send_attempt_result; 18 | pub mod history_response; 19 | pub mod in_app_ownership_type; 20 | pub mod jws_renewal_info_decoded_payload; 21 | pub mod jws_transaction_decoded_payload; 22 | pub mod last_transactions_item; 23 | pub mod lifetime_dollars_purchased; 24 | pub mod lifetime_dollars_refunded; 25 | pub mod mass_extend_renewal_date_request; 26 | pub mod mass_extend_renewal_date_response; 27 | pub mod mass_extend_renewal_date_status_response; 28 | pub mod notification_history_request; 29 | pub mod notification_history_response; 30 | pub mod notification_history_response_item; 31 | pub mod notification_type_v2; 32 | pub mod offer_discount_type; 33 | pub mod offer_type; 34 | pub mod order_lookup_response; 35 | pub mod order_lookup_status; 36 | pub mod platform; 37 | pub mod play_time; 38 | pub mod price_increase_status; 39 | pub mod product_type; 40 | pub mod purchase_platform; 41 | pub mod refund_history_response; 42 | pub mod refund_preference; 43 | pub mod response_body_v2; 44 | pub mod response_body_v2_decoded_payload; 45 | pub mod revocation_reason; 46 | pub mod send_attempt_item; 47 | pub mod send_attempt_result; 48 | pub mod send_test_notification_response; 49 | pub mod status; 50 | pub mod status_response; 51 | pub mod subscription_group_identifier_item; 52 | pub mod subtype; 53 | pub mod summary; 54 | pub mod token_type; 55 | pub mod transaction_history_request; 56 | pub mod transaction_info_response; 57 | pub mod transaction_reason; 58 | pub mod update_app_account_token_request; 59 | pub mod user_status; 60 | pub mod advanced_commerce; 61 | pub mod advanced_commerce_transaction_item; 62 | pub mod advanced_commerce_transaction_info; 63 | pub mod advanced_commerce_renewal_info; 64 | pub mod advanced_commerce_renewal_item; 65 | pub mod retention_messaging; 66 | mod serde_ext; 67 | -------------------------------------------------------------------------------- /tests/resources/mock_signed_data/transactionInfo: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlIiwic2lnbmVkRGF0ZSI6MTY3Mjk1NjE1NDAwMH0.PnHWpeIJZ8f2Q218NSGLo_aR0IBEJvC6PxmxKXh-qfYTrZccx2suGl223OSNAX78e4Ylf2yJCG2N-FfU-NIhZQ -------------------------------------------------------------------------------- /tests/resources/mock_signed_data/renewalInfo: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJzaWduZWREYXRlIjoxNjcyOTU2MTU0MDAwfQ.FbK2OL-t6l4892W7fzWyus_g9mIl2CzWLbVt7Kgcnt6zzVulF8bzovgpe0v_y490blROGixy8KDoe2dSU53-Xw -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_price_change_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::request_info::RequestInfo; 2 | use crate::primitives::advanced_commerce::subscription_price_change_item::SubscriptionPriceChangeItem; 3 | use serde::{Deserialize, Serialize}; 4 | use uuid::Uuid; 5 | 6 | /// The metadata your app provides to change the price of an auto-renewable subscription. 7 | /// 8 | /// [SubscriptionPriceChangeRequest](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionpricechangerequest) 9 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 10 | #[serde(rename_all = "camelCase")] 11 | pub struct SubscriptionPriceChangeRequest { 12 | /// The currency of the price of the product. 13 | /// 14 | /// [currency](https://developer.apple.com/documentation/advancedcommerceapi/currency) 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub currency: Option, 17 | 18 | /// The details of the price change items. 19 | /// 20 | /// [SubscriptionPriceChangeItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionpricechangeitem) 21 | pub items: Vec, 22 | 23 | /// The metadata to include in server requests. 24 | /// 25 | /// [requestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 26 | pub request_info: RequestInfo, 27 | 28 | /// The storefront for the transaction. 29 | /// 30 | /// [storefront](https://developer.apple.com/documentation/advancedcommerceapi/onetimechargecreaterequest) 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub storefront: Option, 33 | } 34 | 35 | impl SubscriptionPriceChangeRequest { 36 | pub fn new( 37 | currency: String, 38 | items: Vec, 39 | request_reference_id: Uuid, 40 | ) -> Self { 41 | Self { 42 | currency: Some(currency), 43 | items: items, 44 | request_info: RequestInfo::new(request_reference_id), 45 | storefront: None, 46 | } 47 | } 48 | 49 | pub fn with_storefront(mut self, storefront: String) -> Self { 50 | self.storefront = Some(storefront); 51 | self 52 | } 53 | 54 | pub fn with_request_info(mut self, request_info: RequestInfo) -> Self { 55 | self.request_info = request_info; 56 | self 57 | } 58 | } -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use base64::engine::general_purpose::STANDARD; 2 | use base64::{DecodeError, Engine}; 3 | 4 | /// Converts a base64URL-encoded string to a standard base64-encoded string. 5 | /// 6 | /// Replaces '-' with '+' and '_' with '/', and adds padding if needed. 7 | /// 8 | /// # Examples 9 | /// 10 | /// ```ignore 11 | /// let encoded_string = "aGVsbG8gd29ybGQh"; 12 | /// let result = base64_url_to_base64(encoded_string); 13 | /// assert_eq!(result, "aGVsbG8gd29ybGQh=="); 14 | /// ``` 15 | pub(crate) fn base64_url_to_base64(encoded_string: &str) -> String { 16 | let replaced_string = encoded_string 17 | .replace('-', "+") 18 | .replace('_', "/"); 19 | 20 | if replaced_string.len() % 4 != 0 { 21 | return replaced_string.clone() + &"=".repeat(4 - replaced_string.len() % 4); 22 | } 23 | 24 | replaced_string 25 | } 26 | 27 | /// A trait for extending the functionality of Rust strings. 28 | pub trait StringExt { 29 | /// Converts the string into a DER-encoded byte vector. 30 | /// 31 | /// This method attempts to parse the string as a DER-encoded byte sequence 32 | /// and returns the result as a `Vec`. If the parsing fails, it returns 33 | /// a `DecodeError`. 34 | /// 35 | /// # Errors 36 | /// 37 | /// If the string cannot be successfully parsed as DER-encoded bytes, this 38 | /// method returns a `DecodeError` indicating the reason for the failure. 39 | /// 40 | fn as_der_bytes(&self) -> Result, DecodeError>; 41 | } 42 | 43 | impl StringExt for String { 44 | fn as_der_bytes(&self) -> Result, DecodeError> { 45 | STANDARD.decode(self) 46 | } 47 | } 48 | 49 | impl StringExt for &str { 50 | fn as_der_bytes(&self) -> Result, DecodeError> { 51 | STANDARD.decode(self) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn test_base64_url_to_base64() { 61 | // Test with a base64URL-encoded string 62 | let encoded_string = "aGVsbG8gd29ybGQh"; 63 | let result = base64_url_to_base64(encoded_string); 64 | assert_eq!(result, "aGVsbG8gd29ybGQh"); 65 | 66 | // Test with a base64URL-encoded string requiring padding 67 | let encoded_string_padding = "aGVsbG8gd29ybz"; 68 | let result_padding = base64_url_to_base64(encoded_string_padding); 69 | assert_eq!(result_padding, "aGVsbG8gd29ybz=="); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/resources/mock_signed_data/wrongBundleId: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUud3JvbmcifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.WWE31hTB_mcv2O_lf-xI-MNY3d8txc0MzpqFx4QnYDfFIxB95Lo2Fm3r46YSjLLdL7xCWdEJrJP5bHgRCejAGg -------------------------------------------------------------------------------- /tests/resources/mock_signed_data/missingX5CHeaderClaim: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1Y3dyb25nIjpbIk1JSUJvRENDQVVhZ0F3SUJBZ0lCRERBS0JnZ3Foa2pPUFFRREF6QkZNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1CNFhEVEl6TURFd05USXhNekV6TkZvWERUTXpNREV3TVRJeE16RXpORm93UFRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eERUQUxCZ05WQkFvTUJFeGxZV1l3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRpdFlIRWFZVnVjOGc5QWpUT3dFck12R3lQeWtQYStwdXZUSThoSlRIWlpETEdhczJxWDErRXJ4Z1FUSmdWWHY3Nm5tTGhoUkpIK2oyNUFpQUk4aUdzb3k4d0xUQUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVFCZ29xaGtpRzkyTmtCZ3NCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05JQURCRkFpQlg0YytUMEZwNW5KNVFSQ2xSZnU1UFNCeVJ2TlB0dWFUc2swdlBCM1dBSUFJaEFOZ2FhdUFqL1lQOXMwQWtFaHlKaHhRTy82UTJ6b3VaK0gxQ0lPZWhuTXpRIiwiTUlJQm56Q0NBVVdnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQXpBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1CNFhEVEl6TURFd05USXhNekV3TlZvWERUTXpNREV3TVRJeE16RXdOVm93UlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eEZUQVRCZ05WQkFvTURFbHVkR1Z5YldWa2FXRjBaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCQlVONVY5cktqZlJpTUFJb2pFQTBBdjVNcDBvRitPMGNMNGd6clRGMTc4aW5VSHVnajdFdDQ2TnJrUTdoS2dNVm5qb2dxNDVRMXJNcytjTUhWTklMV3FqTlRBek1BOEdBMVVkRXdRSU1BWUJBZjhDQVFBd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJBR0NpcUdTSWIzWTJRR0FnRUVBZ1VBTUFvR0NDcUdTTTQ5QkFNREEwZ0FNRVVDSVFDbXNJS1lzNDF1bGxzc0hYNHJWdmVVVDBaN0lzNS9oTEsxbEZQVHR1bjNoQUlnYzIrMlJHNStnTmNGVmNzK1hKZUVsNEdaK29qbDNST09tbGwreWU3ZHluUT0iLCJNSUlCZ2pDQ0FTbWdBd0lCQWdJSkFMVWM1QUxpSDVwYk1Bb0dDQ3FHU000OUJBTURNRFl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh3SGhjTk1qTXdNVEExTWpFek1ESXlXaGNOTXpNd01UQXlNakV6TURJeVdqQTJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVjKy9CbCtnb3NwbzZ0ZjlaN2lvNXRkS2RybE4xWWRWbnFFaEVEWERTaHpkQUpQUWlqYW1YSU1IZjh4V1dUYTF6Z29ZVHhPS3BidUp0RHBsejFYcmlUYU1nTUI0d0RBWURWUjBUQkFVd0F3RUIvekFPQmdOVkhROEJBZjhFQkFNQ0FRWXdDZ1lJS29aSXpqMEVBd01EUndBd1JBSWdlbVdRWG5NQWRUYWQySkRKV25nOVU0dUJCTDVtQTdXSTA1SDdvSDdjNmlRQ0lIaVJxTWpOZnpVQXlpdTloNnJPVS9LK2lUUjBJLzNZL05TV3NYSFgrYWNjIl19.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.1TFhjDR4WwQJNgizVGYXz3WE3ajxTdH1wKLQQ71MtrkadSxxOo3yPo_6L9Z03unIU7YK-NRNzSIb5bh5WqTprQ -------------------------------------------------------------------------------- /tests/resources/mock_signed_data/testNotification: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImFwcEFwcGxlSWQiOjEyMzQsImVudmlyb25tZW50IjoiU2FuZGJveCIsImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsInNpZ25lZERhdGUiOjE2ODEzMTQzMjQwMDAsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.VVXYwuNm2Y3XsOUva-BozqatRCsDuykA7xIe_CCRw6aIAAxJ1nb2sw871jfZ6dcgNhUuhoZ93hfbc1v_5zB7Og -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_modify_add_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::primitives::advanced_commerce::offer::Offer; 3 | 4 | /// The data your app provides to add items when it makes changes to an auto-renewable subscription. 5 | /// 6 | /// [SubscriptionModifyAddItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifyadditem) 7 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct SubscriptionModifyAddItem { 10 | /// The item's product identifier. 11 | /// 12 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 13 | #[serde(rename = "SKU")] 14 | pub sku: String, 15 | 16 | /// The description of the item. 17 | /// 18 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 19 | pub description: String, 20 | 21 | /// The display name of the item. 22 | /// 23 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 24 | pub display_name: String, 25 | 26 | /// The price in milliunits. 27 | /// 28 | /// [Price](https://developer.apple.com/documentation/advancedcommerceapi/price) 29 | pub price: i64, 30 | 31 | /// An offer for the item. 32 | /// 33 | /// [Offer](https://developer.apple.com/documentation/advancedcommerceapi/offer) 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub offer: Option, 36 | 37 | /// The prorated price for the item. 38 | /// 39 | /// [ProratedPrice](https://developer.apple.com/documentation/advancedcommerceapi/proratedprice) 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub prorated_price: Option, 42 | } 43 | 44 | impl SubscriptionModifyAddItem { 45 | pub fn new( 46 | sku: String, 47 | description: String, 48 | display_name: String, 49 | price: i64, 50 | ) -> Self { 51 | Self { 52 | sku, 53 | description, 54 | display_name, 55 | price, 56 | offer: None, 57 | prorated_price: None, 58 | } 59 | } 60 | 61 | pub fn with_offer(mut self, offer: Offer) -> Self { 62 | self.offer = Some(offer); 63 | self 64 | } 65 | 66 | pub fn with_prorated_price(mut self, prorated_price: i64) -> Self { 67 | self.prorated_price = Some(prorated_price); 68 | self 69 | } 70 | } -------------------------------------------------------------------------------- /src/primitives/external_purchase_token.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::token_type::TokenType; 2 | use chrono::{DateTime, Utc}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_with::formats::Flexible; 5 | use serde_with::TimestampMilliSeconds; 6 | 7 | /// The payload data that contains an external purchase token. 8 | /// 9 | /// [externalPurchaseToken](https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken) 10 | #[serde_with::serde_as] 11 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 12 | pub struct ExternalPurchaseToken { 13 | /// The field of an external purchase token that uniquely identifies the token. 14 | /// 15 | /// [externalPurchaseId](https://developer.apple.com/documentation/appstoreservernotifications/externalpurchaseid) 16 | #[serde(rename = "externalPurchaseId")] 17 | pub external_purchase_id: Option, 18 | 19 | /// The field of an external purchase token that contains the UNIX date, in milliseconds, 20 | /// when the system created the token. 21 | /// 22 | /// [tokenCreationDate](https://developer.apple.com/documentation/appstoreservernotifications/tokencreationdate) 23 | #[serde(rename = "tokenCreationDate")] 24 | #[serde_as(as = "Option>")] 25 | pub token_creation_date: Option>, 26 | 27 | /// The unique identifier of an app in the App Store. 28 | /// 29 | /// [appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 30 | #[serde(rename = "appAppleId")] 31 | pub app_apple_id: Option, 32 | 33 | /// The bundle identifier of an app. 34 | /// 35 | /// [bundleId](https://developer.apple.com/documentation/appstoreservernotifications/bundleid) 36 | #[serde(rename = "bundleId")] 37 | pub bundle_id: Option, 38 | 39 | /// The UNIX time, in milliseconds, when a token expires. This field is present only for custom link tokens. 40 | /// 41 | /// [tokenExpirationDate](https://developer.apple.com/documentation/appstoreservernotifications/tokenexpirationdate) 42 | #[serde(rename = "tokenExpirationDate")] 43 | #[serde_as(as = "Option>")] 44 | pub token_expiration_date: Option>, 45 | 46 | /// The type of an external purchase custom link token. 47 | /// 48 | /// [tokenType](https://developer.apple.com/documentation/appstoreservernotifications/tokentype) 49 | #[serde(rename = "tokenType")] 50 | pub token_type: Option, 51 | } 52 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/decoded_realtime_request_body.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::environment::Environment; 2 | use chrono::{DateTime, Utc}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_with::formats::Flexible; 5 | use serde_with::TimestampMilliSeconds; 6 | use uuid::Uuid; 7 | 8 | /// The decoded request body the App Store sends to your server to request a real-time retention message. 9 | /// 10 | /// [DecodedRealtimeRequestBody](https://developer.apple.com/documentation/retentionmessaging/decodedrealtimerequestbody) 11 | #[serde_with::serde_as] 12 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 13 | pub struct DecodedRealtimeRequestBody { 14 | /// The original transaction identifier of the customer's subscription. 15 | /// 16 | /// [originalTransactionId](https://developer.apple.com/documentation/retentionmessaging/originaltransactionid) 17 | #[serde(rename = "originalTransactionId")] 18 | pub original_transaction_id: String, 19 | 20 | /// The unique identifier of the app in the App Store. 21 | /// 22 | /// [appAppleId](https://developer.apple.com/documentation/retentionmessaging/appappleid) 23 | #[serde(rename = "appAppleId")] 24 | pub app_apple_id: i64, 25 | 26 | /// The unique identifier of the auto-renewable subscription. 27 | /// 28 | /// [productId](https://developer.apple.com/documentation/retentionmessaging/productid) 29 | #[serde(rename = "productId")] 30 | pub product_id: String, 31 | 32 | /// The device's locale. 33 | /// 34 | /// [locale](https://developer.apple.com/documentation/retentionmessaging/locale) 35 | #[serde(rename = "userLocale")] 36 | pub user_locale: String, 37 | 38 | /// A UUID the App Store server creates to uniquely identify each request. 39 | /// 40 | /// [requestIdentifier](https://developer.apple.com/documentation/retentionmessaging/requestidentifier) 41 | #[serde(rename = "requestIdentifier")] 42 | pub request_identifier: Uuid, 43 | 44 | /// The UNIX time, in milliseconds, that the App Store signed the JSON Web Signature (JWS) data. 45 | /// 46 | /// [signedDate](https://developer.apple.com/documentation/retentionmessaging/signeddate) 47 | #[serde(rename = "signedDate")] 48 | #[serde_as(as = "TimestampMilliSeconds")] 49 | pub signed_date: DateTime, 50 | 51 | /// The server environment, either sandbox or production. 52 | /// 53 | /// [environment](https://developer.apple.com/documentation/retentionmessaging/environment) 54 | pub environment: Environment, 55 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_change_metadata_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::primitives::advanced_commerce::effective::Effective; 3 | 4 | /// The metadata to change for an item, specifically its SKU, description, and display name. 5 | /// 6 | /// [SubscriptionChangeMetadataItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionchangemetadataitem) 7 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct SubscriptionChangeMetadataItem { 10 | /// The new SKU of the item. 11 | /// 12 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 13 | #[serde(skip_serializing_if = "Option::is_none", rename = "SKU")] 14 | pub sku: Option, 15 | 16 | /// The original SKU of the item. 17 | /// 18 | /// [currentSKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 19 | #[serde(rename = "currentSKU")] 20 | pub current_sku: String, 21 | 22 | /// The new description for the item. 23 | /// 24 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub description: Option, 27 | 28 | /// The new display name for the item. 29 | /// 30 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub display_name: Option, 33 | 34 | /// The string that determines when the metadata change goes into effect. 35 | /// 36 | /// [Effective](https://developer.apple.com/documentation/advancedcommerceapi/effective) 37 | pub effective: Effective, 38 | } 39 | 40 | impl SubscriptionChangeMetadataItem { 41 | pub fn new( 42 | current_sku: String, 43 | effective: Effective, 44 | ) -> Self { 45 | Self { 46 | sku: None, 47 | current_sku, 48 | description: None, 49 | display_name: None, 50 | effective, 51 | } 52 | } 53 | 54 | pub fn with_sku(mut self, sku: String) -> Self { 55 | self.sku = Some(sku); 56 | self 57 | } 58 | 59 | pub fn with_description(mut self, description: String) -> Self { 60 | self.description = Some(description); 61 | self 62 | } 63 | 64 | pub fn with_display_name(mut self, display_name: String) -> Self { 65 | self.display_name = Some(display_name); 66 | self 67 | } 68 | } -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_revoke_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::request_info::RequestInfo; 2 | use crate::primitives::advanced_commerce::refund_reason::RefundReason; 3 | use serde::{Deserialize, Serialize}; 4 | use uuid::Uuid; 5 | use crate::primitives::advanced_commerce::refund_type::RefundType; 6 | 7 | /// The request data your app provides to revoke an auto-renewable subscription. 8 | /// 9 | /// [SubscriptionRevokeRequest](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionrevokerequest) 10 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 11 | #[serde(rename_all = "camelCase")] 12 | pub struct SubscriptionRevokeRequest { 13 | /// The metadata to include in server requests. 14 | /// 15 | /// [requestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 16 | pub request_info: RequestInfo, 17 | 18 | /// The reason for the refund. 19 | /// 20 | /// [refundReason](https://developer.apple.com/documentation/advancedcommerceapi/refundreason) 21 | pub refund_reason: RefundReason, 22 | 23 | /// The refund risking preference. 24 | /// 25 | /// [refundRiskingPreference](https://developer.apple.com/documentation/advancedcommerceapi/refundriskingpreference) 26 | pub refund_risking_preference: String, 27 | 28 | /// The type of refund. 29 | /// Possible Values: FULL, PRORATED 30 | /// 31 | /// [refundType](https://developer.apple.com/documentation/advancedcommerceapi/refundtype) 32 | pub refund_type: RefundType, 33 | 34 | /// The storefront for the transaction. 35 | /// 36 | /// [storefront](https://developer.apple.com/documentation/advancedcommerceapi/storefront) 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub storefront: Option, 39 | } 40 | 41 | impl SubscriptionRevokeRequest { 42 | pub fn new( 43 | request_reference_id: Uuid, 44 | refund_reason: RefundReason, 45 | refund_risking_preference: String, 46 | refund_type: RefundType, 47 | ) -> Self { 48 | Self { 49 | request_info: RequestInfo::new(request_reference_id), 50 | refund_reason, 51 | refund_risking_preference, 52 | refund_type, 53 | storefront: None, 54 | } 55 | } 56 | 57 | pub fn with_storefront(mut self, storefront: String) -> Self { 58 | self.storefront = Some(storefront); 59 | self 60 | } 61 | 62 | pub fn with_request_info(mut self, request_info: RequestInfo) -> Self { 63 | self.request_info = request_info; 64 | self 65 | } 66 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app-store-server-library" 3 | description = "The Rust server library for the App Store Server API, App Store Server Notifications and Advanced Commerce API" 4 | version = "4.1.3" 5 | repository = "https://github.com/namecare/app-store-server-library-rust" 6 | homepage = "https://github.com/namecare/app-store-server-library-rust" 7 | authors = ["tkhp", "namecare"] 8 | license = "MIT" 9 | readme = "README.md" 10 | documentation = "https://docs.rs/app-store-server-library" 11 | keywords = ["apple", "appstore", "appstore-server-api", "appstore-receipt", "advanced-commerce"] 12 | edition = "2021" 13 | 14 | [dependencies] 15 | 16 | # Cryptography 17 | jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] } 18 | ring = "0.17.14" 19 | pem-rfc7468 = "1.0.0" 20 | der = { version = "0.7.10", features = ["alloc", "oid"] } 21 | x509-cert = { version = "0.2.5", features = ["std"] } 22 | x509-ocsp = { version = "0.2.1" , optional = true } 23 | const-oid = { version = "0.9.6" } 24 | spki = { version = "0.7.3" } 25 | sha1 = { version = "0.10.6" } 26 | 27 | # Serialization 28 | serde = { version = "1.0.228", features = ["derive"] } 29 | serde_json = { version = "1.0.145" } 30 | serde_with = { version = "3.16.0", features = ["chrono"] } 31 | serde_repr = "0.1.20" 32 | uuid = { version = "1.18.1", features = ["serde", "v4"] } 33 | chrono = { version = "0.4.42", features = ["serde"] } 34 | base64 = "0.22.1" 35 | 36 | # Networking 37 | reqwest = { version = "0.12.24", default-features = false, features = ["json"], optional = true } 38 | http = { version = "1.3.1", optional = true } 39 | 40 | # Utils 41 | thiserror = "2.0.17" 42 | 43 | # Tools 44 | regex = { version = "1.12.2", optional = true } 45 | 46 | [dev-dependencies] 47 | 48 | tokio = { version = "1.48.0", features = ["test-util", "macros"] } 49 | jsonwebtoken = { version = "10.2.0", features = ["use_pem"] } 50 | 51 | [features] 52 | api-client = ["dep:http"] 53 | api-client-reqwest = ["api-client", "dep:reqwest", "reqwest/rustls-tls-native-roots"] 54 | api-client-reqwest-native-tls = ["api-client", "dep:reqwest", "reqwest/default-tls"] 55 | receipt-utility = ["dep:regex"] 56 | ocsp = ["dep:x509-ocsp", "dep:reqwest", "reqwest/blocking"] 57 | 58 | [[test]] 59 | name = "ass_api_client" 60 | path = "tests/ass_api_client.rs" 61 | required-features = ["api-client"] 62 | 63 | [[test]] 64 | name = "ac_api_client" 65 | path = "tests/ac_api_client.rs" 66 | required-features = ["api-client"] 67 | 68 | [[test]] 69 | name = "rm_api_client" 70 | path = "tests/rm_api_client.rs" 71 | required-features = ["api-client"] 72 | 73 | [[test]] 74 | name = "chain_verifier_ocsp" 75 | path = "tests/chain_verifier_ocsp.rs" 76 | required-features = ["ocsp"] -------------------------------------------------------------------------------- /src/primitives/summary.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::environment::Environment; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The payload data for a subscription-renewal-date extension notification. 5 | /// 6 | /// [Summary](https://developer.apple.com/documentation/appstoreservernotifications/summary) 7 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 8 | pub struct Summary { 9 | /// The server environment that the notification applies to, either sandbox or production. 10 | /// 11 | /// [environment](https://developer.apple.com/documentation/appstoreservernotifications/environment) 12 | pub environment: Option, 13 | 14 | /// The unique identifier of an app in the App Store. 15 | /// 16 | /// [appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 17 | #[serde(rename = "appAppleId")] 18 | pub app_apple_id: Option, 19 | 20 | /// The bundle identifier of an app. 21 | /// 22 | /// [bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 23 | #[serde(rename = "bundleId")] 24 | pub bundle_id: Option, 25 | 26 | /// The unique identifier for the product, that you create in App Store Connect. 27 | /// 28 | /// [productId](https://developer.apple.com/documentation/appstoreserverapi/productid) 29 | #[serde(rename = "productId")] 30 | pub product_id: Option, 31 | 32 | /// A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 33 | /// 34 | /// [requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 35 | #[serde(rename = "requestIdentifier")] 36 | pub request_identifier: String, 37 | 38 | /// A list of storefront country codes you provide to limit the storefronts for a subscription-renewal-date extension. 39 | /// 40 | /// [storefrontCountryCodes](https://developer.apple.com/documentation/appstoreserverapi/storefrontcountrycodes) 41 | #[serde(rename = "storefrontCountryCodes")] 42 | pub storefront_country_codes: Vec, 43 | 44 | /// The count of subscriptions that successfully receive a subscription-renewal-date extension. 45 | /// 46 | /// [succeededCount](https://developer.apple.com/documentation/appstoreserverapi/succeededcount) 47 | #[serde(rename = "succeededCount")] 48 | pub succeeded_count: i64, 49 | 50 | /// The count of subscriptions that fail to receive a subscription-renewal-date extension. 51 | /// 52 | /// [failedCount](https://developer.apple.com/documentation/appstoreserverapi/failedcount) 53 | #[serde(rename = "failedCount")] 54 | pub failed_count: i64, 55 | } 56 | -------------------------------------------------------------------------------- /src/primitives/data.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::consumption_request_reason::ConsumptionRequestReason; 2 | use crate::primitives::environment::Environment; 3 | use crate::primitives::status::Status; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// The app metadata and the signed renewal and transaction information. 7 | /// 8 | /// [data](https://developer.apple.com/documentation/appstoreservernotifications/data) 9 | #[derive(Debug, Clone, Deserialize, Serialize, Hash)] 10 | pub struct Data { 11 | /// The server environment that the notification applies to, either sandbox or production. 12 | /// 13 | /// [environment](https://developer.apple.com/documentation/appstoreservernotifications/environment) 14 | pub environment: Option, 15 | 16 | /// The unique identifier of an app in the App Store. 17 | /// 18 | /// [appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 19 | #[serde(rename = "appAppleId")] 20 | pub app_apple_id: Option, 21 | 22 | /// The bundle identifier of an app. 23 | /// 24 | /// [bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 25 | #[serde(rename = "bundleId")] 26 | pub bundle_id: Option, 27 | 28 | /// The version of the build that identifies an iteration of the bundle. 29 | /// 30 | /// [bundleVersion](https://developer.apple.com/documentation/appstoreservernotifications/bundleversion) 31 | #[serde(rename = "bundleVersion")] 32 | pub bundle_version: Option, 33 | 34 | /// Transaction information signed by the App Store, in JSON Web Signature (JWS) format. 35 | /// 36 | /// [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 37 | #[serde(rename = "signedTransactionInfo")] 38 | pub signed_transaction_info: Option, 39 | 40 | /// Subscription renewal information, signed by the App Store, in JSON Web Signature (JWS) format. 41 | /// 42 | /// [JWSRenewalInfo](https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfo) 43 | #[serde(rename = "signedRenewalInfo")] 44 | pub signed_renewal_info: Option, 45 | 46 | /// The status of an auto-renewable subscription at the time the App Store signs the notification. 47 | /// 48 | /// [JWSRenewalInfo](https://developer.apple.com/documentation/appstoreservernotifications/status) 49 | #[serde(rename = "status")] 50 | pub status: Option, 51 | 52 | /// The reason the customer requested the refund. 53 | /// 54 | /// [consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason) 55 | #[serde(rename = "consumptionRequestReason")] 56 | pub consumption_request_reason: Option, 57 | } 58 | -------------------------------------------------------------------------------- /src/primitives/retention_messaging/upload_message_request_body.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::retention_messaging::upload_message_image::UploadMessageImage; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | const MAXIMUM_HEADER_LENGTH: usize = 66; 5 | const MAXIMUM_BODY_LENGTH: usize = 144; 6 | 7 | /// The request body for uploading a message, which includes the message text and an optional image reference. 8 | /// 9 | /// [UploadMessageRequestBody](https://developer.apple.com/documentation/retentionmessaging/uploadmessagerequestbody) 10 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 11 | pub struct UploadMessageRequestBody { 12 | /// The header text of the retention message that the system displays to customers. 13 | /// 14 | /// [header](https://developer.apple.com/documentation/retentionmessaging/header) 15 | pub header: String, 16 | 17 | /// The body text of the retention message that the system displays to customers. 18 | /// 19 | /// [body](https://developer.apple.com/documentation/retentionmessaging/body) 20 | pub body: String, 21 | 22 | /// The optional image identifier and its alternative text to appear as part of a text-based message with an image. 23 | /// 24 | /// [UploadMessageImage](https://developer.apple.com/documentation/retentionmessaging/uploadmessageimage) 25 | pub image: Option, 26 | } 27 | 28 | impl UploadMessageRequestBody { 29 | /// Creates a new UploadMessageRequestBody with validation. 30 | /// 31 | /// # Errors 32 | /// 33 | /// Returns `ValidationError::HeaderTooLong` if header exceeds 66 characters. 34 | /// Returns `ValidationError::BodyTooLong` if body exceeds 144 characters. 35 | pub fn new(header: String, body: String, image: Option) -> Result { 36 | if header.len() > MAXIMUM_HEADER_LENGTH { 37 | return Err(ValidationError::HeaderTooLong); 38 | } 39 | if body.len() > MAXIMUM_BODY_LENGTH { 40 | return Err(ValidationError::BodyTooLong); 41 | } 42 | Ok(Self { header, body, image }) 43 | } 44 | } 45 | 46 | #[derive(Debug, Clone, PartialEq, Eq)] 47 | pub enum ValidationError { 48 | HeaderTooLong, 49 | BodyTooLong, 50 | } 51 | 52 | impl std::fmt::Display for ValidationError { 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 54 | match self { 55 | ValidationError::HeaderTooLong => { 56 | write!(f, "Header exceeds maximum length of {} characters", MAXIMUM_HEADER_LENGTH) 57 | } 58 | ValidationError::BodyTooLong => { 59 | write!(f, "Body exceeds maximum length of {} characters", MAXIMUM_BODY_LENGTH) 60 | } 61 | } 62 | } 63 | } 64 | 65 | impl std::error::Error for ValidationError {} -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_create_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::advanced_commerce::descriptors::Descriptors; 2 | use crate::primitives::advanced_commerce::period::Period; 3 | use crate::primitives::advanced_commerce::request_info::RequestInfo; 4 | use crate::primitives::advanced_commerce::subscription_create_item::SubscriptionCreateItem; 5 | use serde::{Deserialize, Serialize}; 6 | use crate::primitives::advanced_commerce::in_app_request::AdvancedCommerceInAppRequest; 7 | use crate::primitives::advanced_commerce::in_app_request_operation::InAppRequestOperation; 8 | use crate::primitives::advanced_commerce::in_app_request_version::InAppRequestVersion; 9 | 10 | /// The metadata your app provides when a customer purchases an auto-renewable subscription. 11 | /// 12 | /// [SubscriptionCreateRequest](https://developer.apple.com/documentation/advancedcommerceapi/subscriptioncreaterequest) 13 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct SubscriptionCreateRequest { 16 | /// The operation type for this request. 17 | /// Value: CREATE_SUBSCRIPTION 18 | pub operation: InAppRequestOperation, 19 | 20 | /// The version of this request. 21 | pub version: InAppRequestVersion, 22 | 23 | /// The currency of the price of the product. 24 | /// 25 | /// [currency](https://developer.apple.com/documentation/advancedcommerceapi/currency) 26 | pub currency: String, 27 | 28 | /// The display name and description of a subscription product. 29 | /// 30 | /// [Descriptors](https://developer.apple.com/documentation/advancedcommerceapi/descriptors) 31 | pub descriptors: Descriptors, 32 | 33 | /// The details of the subscription product for purchase. 34 | /// 35 | /// [SubscriptionCreateItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptioncreateitem) 36 | pub items: Vec, 37 | 38 | /// The duration of a single cycle of an auto-renewable subscription. 39 | /// 40 | /// [period](https://developer.apple.com/documentation/advancedcommerceapi/period) 41 | pub period: Period, 42 | 43 | /// The identifier of a previous transaction for the subscription. 44 | /// 45 | /// [transactionId](https://developer.apple.com/documentation/advancedcommerceapi/transactionid) 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub previous_transaction_id: Option, 48 | 49 | /// The metadata to include in server requests. 50 | /// 51 | /// [requestInfo](https://developer.apple.com/documentation/advancedcommerceapi/requestinfo) 52 | pub request_info: RequestInfo, 53 | 54 | /// The storefront for the transaction. 55 | /// 56 | /// [storefront](https://developer.apple.com/documentation/advancedcommerceapi/onetimechargecreaterequest) 57 | #[serde(skip_serializing_if = "Option::is_none")] 58 | pub storefront: Option, 59 | 60 | /// The tax code for this product. 61 | /// 62 | /// [taxCode](https://developer.apple.com/documentation/advancedcommerceapi/onetimechargecreaterequest) 63 | pub tax_code: String, 64 | } 65 | 66 | impl AdvancedCommerceInAppRequest for SubscriptionCreateRequest {} -------------------------------------------------------------------------------- /src/primitives/transaction_history_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::in_app_ownership_type::InAppOwnershipType; 2 | use chrono::{DateTime, Utc}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_with::formats::Flexible; 5 | use serde_with::TimestampMilliSeconds; 6 | 7 | #[serde_with::serde_as] 8 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 9 | pub struct TransactionHistoryRequest { 10 | /// An optional start date of the timespan for the transaction history records you’re requesting. 11 | #[serde(rename = "startDate")] 12 | #[serde_as(as = "Option>")] 13 | pub start_date: Option>, 14 | 15 | /// An optional end date of the timespan for the transaction history records you’re requesting. 16 | #[serde(rename = "endDate")] 17 | #[serde_as(as = "Option>")] 18 | pub end_date: Option>, 19 | 20 | /// An optional filter that indicates the product identifier to include in the transaction history. 21 | #[serde(rename = "productIds")] 22 | pub product_ids: Option>, 23 | 24 | /// An optional filter that indicates the product type to include in the transaction history. 25 | #[serde(rename = "productTypes")] 26 | pub product_types: Option>, 27 | 28 | /// An optional sort order for the transaction history records. 29 | pub sort: Option, 30 | 31 | /// An optional filter that indicates the subscription group identifier to include in the transaction history. 32 | #[serde(rename = "subscriptionGroupIdentifiers")] 33 | pub subscription_group_identifiers: Option>, 34 | 35 | /// An optional filter that limits the transaction history by the in-app ownership type. 36 | #[serde(rename = "inAppOwnershipType")] 37 | pub in_app_ownership_type: Option, 38 | 39 | /// An optional Boolean value that indicates whether the response includes only revoked transactions. 40 | pub revoked: Option, 41 | } 42 | 43 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 44 | pub enum ProductType { 45 | #[serde(rename = "AUTO_RENEWABLE")] 46 | AutoRenewable, 47 | #[serde(rename = "NON_RENEWABLE")] 48 | NonRenewable, 49 | #[serde(rename = "CONSUMABLE")] 50 | Consumable, 51 | #[serde(rename = "NON_CONSUMABLE")] 52 | NonConsumable, 53 | } 54 | 55 | impl ProductType { 56 | pub fn raw_value(&self) -> &str { 57 | match self { 58 | ProductType::AutoRenewable => "AUTO_RENEWABLE", 59 | ProductType::NonRenewable => "NON_RENEWABLE", 60 | ProductType::Consumable => "CONSUMABLE", 61 | ProductType::NonConsumable => "NON_CONSUMABLE", 62 | } 63 | } 64 | } 65 | 66 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 67 | pub enum Order { 68 | #[serde(rename = "ASCENDING")] 69 | Ascending, 70 | #[serde(rename = "DESCENDING")] 71 | Descending, 72 | } 73 | 74 | impl Order { 75 | pub fn raw_value(&self) -> &str { 76 | match self { 77 | Order::Ascending => "ASCENDING", 78 | Order::Descending => "DESCENDING", 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/primitives/advanced_commerce/subscription_modify_change_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::primitives::advanced_commerce::offer::Offer; 3 | use crate::primitives::advanced_commerce::effective::Effective; 4 | 5 | /// The data your app provides to change an item of an auto-renewable subscription. 6 | /// 7 | /// [SubscriptionModifyChangeItem](https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifychangeitem) 8 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct SubscriptionModifyChangeItem { 11 | /// The new SKU identifier for the item. 12 | /// 13 | /// [SKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 14 | #[serde(rename = "SKU")] 15 | pub sku: String, 16 | 17 | /// The original SKU of the item. 18 | /// 19 | /// [currentSKU](https://developer.apple.com/documentation/advancedcommerceapi/sku) 20 | #[serde(rename = "currentSKU")] 21 | pub current_sku: String, 22 | 23 | /// The description of the item. 24 | /// 25 | /// [Description](https://developer.apple.com/documentation/advancedcommerceapi/description) 26 | pub description: String, 27 | 28 | /// The display name of the item. 29 | /// 30 | /// [Display Name](https://developer.apple.com/documentation/advancedcommerceapi/displayname) 31 | pub display_name: String, 32 | 33 | /// When the change takes effect. 34 | /// 35 | /// [Effective](https://developer.apple.com/documentation/advancedcommerceapi/effective) 36 | pub effective: Effective, 37 | 38 | /// The price in milliunits. 39 | /// 40 | /// [Price](https://developer.apple.com/documentation/advancedcommerceapi/price) 41 | pub price: i64, 42 | 43 | /// The reason for the change. 44 | /// Possible Values: UPGRADE, DOWNGRADE, APPLY_OFFER 45 | /// 46 | /// [Reason](https://developer.apple.com/documentation/advancedcommerceapi/reason) 47 | pub reason: String, 48 | 49 | /// An offer for the item. 50 | /// 51 | /// [Offer](https://developer.apple.com/documentation/advancedcommerceapi/offer) 52 | #[serde(skip_serializing_if = "Option::is_none")] 53 | pub offer: Option, 54 | 55 | /// The prorated price for the item. 56 | /// 57 | /// [ProratedPrice](https://developer.apple.com/documentation/advancedcommerceapi/proratedprice) 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub prorated_price: Option, 60 | } 61 | 62 | impl SubscriptionModifyChangeItem { 63 | pub fn new( 64 | sku: String, 65 | current_sku: String, 66 | description: String, 67 | display_name: String, 68 | effective: Effective, 69 | price: i64, 70 | reason: String, 71 | ) -> Self { 72 | Self { 73 | sku, 74 | current_sku, 75 | description, 76 | display_name, 77 | effective, 78 | price, 79 | reason, 80 | offer: None, 81 | prorated_price: None, 82 | } 83 | } 84 | 85 | pub fn with_offer(mut self, offer: Offer) -> Self { 86 | self.offer = Some(offer); 87 | self 88 | } 89 | 90 | pub fn with_prorated_price(mut self, prorated_price: i64) -> Self { 91 | self.prorated_price = Some(prorated_price); 92 | self 93 | } 94 | } -------------------------------------------------------------------------------- /src/primitives/response_body_v2_decoded_payload.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::data::Data; 2 | use crate::primitives::external_purchase_token::ExternalPurchaseToken; 3 | use crate::primitives::notification_type_v2::NotificationTypeV2; 4 | use crate::primitives::subtype::Subtype; 5 | use crate::primitives::summary::Summary; 6 | use ::chrono::{DateTime, Utc}; 7 | use serde_with::formats::Flexible; 8 | use serde_with::TimestampMilliSeconds; 9 | 10 | /// A decoded payload containing the version 2 notification data. 11 | /// 12 | /// [responseBodyV2DecodedPayload](https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2decodedpayload) 13 | #[serde_with::serde_as] 14 | #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Hash)] 15 | pub struct ResponseBodyV2DecodedPayload { 16 | /// The in-app purchase event for which the App Store sends this version 2 notification. 17 | /// 18 | /// [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype) 19 | #[serde(rename = "notificationType")] 20 | pub notification_type: NotificationTypeV2, 21 | 22 | /// Additional information that identifies the notification event. 23 | /// The subtype field is present only for specific version 2 notifications. 24 | /// 25 | /// [subtype](https://developer.apple.com/documentation/appstoreservernotifications/subtype) 26 | pub subtype: Option, 27 | 28 | /// A unique identifier for the notification. 29 | /// 30 | /// [notificationUUID](https://developer.apple.com/documentation/appstoreservernotifications/notificationuuid) 31 | #[serde(rename = "notificationUUID")] 32 | pub notification_uuid: String, 33 | 34 | /// The object that contains the app metadata and signed renewal and transaction information. 35 | /// The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields. 36 | /// 37 | /// [data](https://developer.apple.com/documentation/appstoreservernotifications/data) 38 | pub data: Option, 39 | 40 | /// A string that indicates the notification’s App Store Server Notifications version number. 41 | /// 42 | /// [version](https://developer.apple.com/documentation/appstoreservernotifications/version) 43 | pub version: Option, 44 | 45 | /// The UNIX time, in milliseconds, that the App Store signed the JSON Web Signature data. 46 | /// 47 | /// [signedDate](https://developer.apple.com/documentation/appstoreserverapi/signeddate) 48 | #[serde(rename = "signedDate")] 49 | #[serde_as(as = "Option>")] 50 | pub signed_date: Option>, 51 | 52 | /// The summary data that appears when the App Store server completes your request to extend a subscription renewal date for eligible subscribers. 53 | /// The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields. 54 | /// 55 | /// [summary](https://developer.apple.com/documentation/appstoreservernotifications/summary) 56 | pub summary: Option, 57 | 58 | /// This field appears when the notificationType is EXTERNAL_PURCHASE_TOKEN. 59 | /// The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields. 60 | /// 61 | /// [externalPurchaseToken](https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken) 62 | #[serde(rename = "externalPurchaseToken")] 63 | pub external_purchase_token: Option, 64 | } 65 | -------------------------------------------------------------------------------- /src/primitives/notification_history_request.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::notification_type_v2::NotificationTypeV2; 2 | use crate::primitives::subtype::Subtype; 3 | use chrono::{DateTime, Utc}; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_with::formats::Flexible; 6 | use serde_with::TimestampMilliSeconds; 7 | 8 | /// The request body for notification history. 9 | /// 10 | /// [NotificationHistoryRequest](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryrequest) 11 | #[serde_with::serde_as] 12 | #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] 13 | pub struct NotificationHistoryRequest { 14 | /// The start date of the timespan for the requested App Store Server Notification history records. 15 | /// The startDate needs to precede the endDate. Choose a startDate that’s within the past 180 days from the current date. 16 | /// 17 | /// [startDate](https://developer.apple.com/documentation/appstoreserverapi/startdate) 18 | #[serde(rename = "startDate")] 19 | #[serde_as(as = "Option>")] 20 | pub start_date: Option>, 21 | 22 | /// The end date of the timespan for the requested App Store Server Notification history records. 23 | /// Choose an endDate that’s later than the startDate. If you choose an endDate in the future, the endpoint automatically uses the current date as the endDate. 24 | /// 25 | /// [endDate](https://developer.apple.com/documentation/appstoreserverapi/enddate) 26 | #[serde(rename = "endDate")] 27 | #[serde_as(as = "Option>")] 28 | pub end_date: Option>, 29 | 30 | /// A notification type. Provide this field to limit the notification history records to those with this one notification type. 31 | /// For a list of notifications types, see notificationType. 32 | /// Include either the transactionId or the notificationType in your query, but not both. 33 | /// 34 | /// [notificationType](https://developer.apple.com/documentation/appstoreserverapi/notificationtype) 35 | #[serde(rename = "notificationType")] 36 | pub notification_type: Option, 37 | 38 | /// A notification subtype. Provide this field to limit the notification history records to those with this one notification subtype. 39 | /// For a list of subtypes, see subtype. If you specify a notificationSubtype, you need to also specify its related notificationType. 40 | /// 41 | /// [notificationSubtype](https://developer.apple.com/documentation/appstoreserverapi/notificationsubtype) 42 | #[serde(rename = "notificationSubtype")] 43 | pub notification_subtype: Option, 44 | 45 | /// The transaction identifier, which may be an original transaction identifier, of any transaction belonging to the customer. 46 | /// Provide this field to limit the notification history request to this one customer. 47 | /// Include either the transactionId or the notificationType in your query, but not both. 48 | /// 49 | /// [transactionId](https://developer.apple.com/documentation/appstoreserverapi/transactionid) 50 | #[serde(rename = "transactionId")] 51 | pub transaction_id: Option, 52 | 53 | /// A Boolean value you set to true to request only the notifications that haven’t reached your server successfully. 54 | /// The response also includes notifications that the App Store server is currently retrying to send to your server. 55 | /// 56 | /// [onlyFailures](https://developer.apple.com/documentation/appstoreserverapi/onlyfailures) 57 | #[serde(rename = "onlyFailures")] 58 | pub only_failures: Option, 59 | } 60 | --------------------------------------------------------------------------------