From c7237d056fecb65db0713dcefda956599fa40913 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 13 May 2024 11:17:14 +0200 Subject: [PATCH 1/5] feat: Add support for App Store Server API v1.11 and App Store Server Notifications v2.11 --- .../signedConsumptionRequestNotification.json | 16 ++++++++ src/api_client.rs | 3 ++ src/primitives/consumption_request.rs | 7 ++++ src/primitives/consumption_request_reason.rs | 18 +++++++++ src/primitives/data.rs | 7 ++++ src/primitives/error_payload.rs | 6 +++ src/primitives/mod.rs | 2 + src/primitives/refund_preference.rs | 14 +++++++ src/signed_data_verifier.rs | 38 +++++++++++++++++++ 9 files changed, 111 insertions(+) create mode 100644 assets/signedConsumptionRequestNotification.json create mode 100644 src/primitives/consumption_request_reason.rs create mode 100644 src/primitives/refund_preference.rs diff --git a/assets/signedConsumptionRequestNotification.json b/assets/signedConsumptionRequestNotification.json new file mode 100644 index 0000000..3c5b01f --- /dev/null +++ b/assets/signedConsumptionRequestNotification.json @@ -0,0 +1,16 @@ +{ + "notificationType": "CONSUMPTION_REQUEST", + "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", + "data": { + "environment": "LocalTesting", + "appAppleId": 41234, + "bundleId": "com.example", + "bundleVersion": "1.2.3", + "signedTransactionInfo": "signed_transaction_info_value", + "signedRenewalInfo": "signed_renewal_info_value", + "status": 1, + "consumptionRequestReason": "UNINTENDED_PURCHASE" + }, + "version": "2.0", + "signedDate": 1698148900000 +} diff --git a/src/api_client.rs b/src/api_client.rs index 0b2cde4..403e7d7 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -597,6 +597,7 @@ mod tests { use crate::primitives::order_lookup_status::OrderLookupStatus; use crate::primitives::platform::Platform; use crate::primitives::play_time::PlayTime; + use crate::primitives::refund_preference::RefundPreference; use crate::primitives::send_attempt_item::SendAttemptItem; use crate::primitives::send_attempt_result::SendAttemptResult; use crate::primitives::subscription_group_identifier_item::SubscriptionGroupIdentifierItem; @@ -912,6 +913,7 @@ mod tests { assert_eq!(6, decoded_json["lifetimeDollarsRefunded"].as_i64().unwrap()); assert_eq!(7, decoded_json["lifetimeDollarsPurchased"].as_i64().unwrap()); assert_eq!(4, decoded_json["userStatus"].as_i64().unwrap()); + assert_eq!(3, decoded_json["refundPreference"].as_i64().unwrap()); })); let consumption_request = ConsumptionRequest { @@ -926,6 +928,7 @@ mod tests { lifetime_dollars_refunded: LifetimeDollarsRefunded::OneThousandDollarsToOneThousandNineHundredNinetyNineDollarsAndNinetyNineCents.into(), lifetime_dollars_purchased: LifetimeDollarsPurchased::TwoThousandDollarsOrGreater.into(), user_status: UserStatus::LimitedAccess.into(), + refund_preference: RefundPreference::NoPreference.into(), }; let _ = client.send_consumption_data("49571273", &consumption_request).await.unwrap(); diff --git a/src/primitives/consumption_request.rs b/src/primitives/consumption_request.rs index e3ae310..79c31eb 100644 --- a/src/primitives/consumption_request.rs +++ b/src/primitives/consumption_request.rs @@ -8,6 +8,7 @@ use crate::primitives::play_time::PlayTime; use crate::primitives::user_status::UserStatus; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::primitives::refund_preference::RefundPreference; /// The request body containing consumption information. /// @@ -78,4 +79,10 @@ pub struct ConsumptionRequest { /// [userStatus](https://developer.apple.com/documentation/appstoreserverapi/userstatus) #[serde(rename = "userStatus")] pub user_status: Option, + + /// A value that indicates your preference, based on your operational logic, as to whether Apple should grant the refund. + /// + /// [refundPreference](https://developer.apple.com/documentation/appstoreserverapi/refundpreference) + #[serde(rename = "refundPreference")] + pub refund_preference: Option, } diff --git a/src/primitives/consumption_request_reason.rs b/src/primitives/consumption_request_reason.rs new file mode 100644 index 0000000..2219e8d --- /dev/null +++ b/src/primitives/consumption_request_reason.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +/// The customer-provided reason for a refund request. +/// +/// [consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason) +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ConsumptionRequestReason { + #[serde(rename = "UNINTENDED_PURCHASE")] + UnintendedPurchase, + #[serde(rename = "FULFILLMENT_ISSUE")] + FulfillmentIssue, + #[serde(rename = "UNSATISFIED_WITH_PURCHASE")] + UnsatisfiedWithPurchase, + #[serde(rename = "LEGAL")] + Legal, + #[serde(rename = "OTHER")] + Other, +} diff --git a/src/primitives/data.rs b/src/primitives/data.rs index eeda2d8..7fb0b2b 100644 --- a/src/primitives/data.rs +++ b/src/primitives/data.rs @@ -1,6 +1,7 @@ use crate::primitives::environment::Environment; use crate::primitives::status::Status; use serde::{Deserialize, Serialize}; +use crate::primitives::consumption_request_reason::ConsumptionRequestReason; /// The app metadata and the signed renewal and transaction information. /// @@ -47,4 +48,10 @@ pub struct Data { /// [JWSRenewalInfo](https://developer.apple.com/documentation/appstoreservernotifications/status) #[serde(rename = "status")] pub status: Option, + + /// The reason the customer requested the refund. + /// + /// [consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason) + #[serde(rename = "consumptionRequestReason")] + pub consumption_request_reason: Option, } diff --git a/src/primitives/error_payload.rs b/src/primitives/error_payload.rs index c4b6092..0422074 100644 --- a/src/primitives/error_payload.rs +++ b/src/primitives/error_payload.rs @@ -159,8 +159,14 @@ pub enum APIError { /// An error that indicates the transaction is not consumable. /// [Documentation](https://developer.apple.com/documentation/appstoreserverapi/transactionnotconsumableerror) + #[deprecated(since="2.1.0")] InvalidTransactionNotConsumable = 4000043, + /// An error that indicates the transaction identifier represents an unsupported in-app purchase type. + /// + /// [InvalidTransactionTypeNotSupportedError](https://developer.apple.com/documentation/appstoreserverapi/invalidtransactiontypenotsupportederror) + InvalidTransactionTypeNotSupported = 4000047, + /// An error that indicates the subscription doesn't qualify for a renewal-date extension due to its subscription state. /// [Documentation](https://developer.apple.com/documentation/appstoreserverapi/subscriptionextensionineligibleerror) SubscriptionExtensionIneligible = 4030004, diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index ca71142..c91fe42 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -52,3 +52,5 @@ pub mod transaction_info_response; pub mod transaction_reason; pub mod user_status; pub mod external_purchase_token; +pub mod consumption_request_reason; +pub mod refund_preference; diff --git a/src/primitives/refund_preference.rs b/src/primitives/refund_preference.rs new file mode 100644 index 0000000..fb73ace --- /dev/null +++ b/src/primitives/refund_preference.rs @@ -0,0 +1,14 @@ +use serde_repr::{Deserialize_repr, Serialize_repr}; + +/// A value that indicates your preferred outcome for the refund request. +/// +/// [refundPreference](https://developer.apple.com/documentation/appstoreserverapi/refundpreference) +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum RefundPreference { + Undeclared = 0, + PreferGrant = 1, + PreferDecline = 2, + NoPreference = 3, +} + diff --git a/src/signed_data_verifier.rs b/src/signed_data_verifier.rs index 895a679..c676b60 100644 --- a/src/signed_data_verifier.rs +++ b/src/signed_data_verifier.rs @@ -318,6 +318,8 @@ mod tests { use ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING; use serde_json::{Map, Value}; use std::fs; + use chrono::TimeZone; + use crate::primitives::consumption_request_reason::ConsumptionRequestReason; const ROOT_CA_BASE64_ENCODED: &str = "MIIBgjCCASmgAwIBAgIJALUc5ALiH5pbMAoGCCqGSM49BAMDMDYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDdXBlcnRpbm8wHhcNMjMwMTA1MjEzMDIyWhcNMzMwMTAyMjEzMDIyWjA2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ3VwZXJ0aW5vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc+/Bl+gospo6tf9Z7io5tdKdrlN1YdVnqEhEDXDShzdAJPQijamXIMHf8xWWTa1zgoYTxOKpbuJtDplz1XriTaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDRwAwRAIgemWQXnMAdTad2JDJWng9U4uBBL5mA7WI05H7oH7c6iQCIHiRqMjNfzUAyiu9h6rOU/K+iTR0I/3Y/NSWsXHX+acc"; @@ -902,6 +904,7 @@ mod tests { .expect("Expect signed_renewal_info") ); assert_eq!(Status::Active, data.status.expect("Expect status")); + assert!(data.consumption_request_reason.is_none()); } else { panic!("Data field is expected to be present in the notification"); } @@ -910,6 +913,41 @@ mod tests { } } + #[test] + fn test_consumption_request_notification_decoding() { + let signed_notification = create_signed_data_from_json("assets/signedConsumptionRequestNotification.json"); + + let signed_data_verifier = get_default_signed_data_verifier(); + + match signed_data_verifier.verify_and_decode_notification(&signed_notification) { + Ok(notification) => { + assert_eq!(NotificationTypeV2::ConsumptionRequest, notification.notification_type); + assert!(notification.subtype.is_none()); + assert_eq!("002e14d5-51f5-4503-b5a8-c3a1af68eb20", notification.notification_uuid); + assert_eq!("2.0", notification.version.unwrap()); + assert_eq!(1698148900, notification.signed_date.unwrap().timestamp()); + assert!(notification.data.is_some()); + assert!(notification.summary.is_none()); + assert!(notification.external_purchase_token.is_none()); + + if let Some(data) = notification.data { + assert_eq!(Environment::LocalTesting, data.environment.unwrap()); + assert_eq!(41234, data.app_apple_id.unwrap()); + assert_eq!("com.example", data.bundle_id.unwrap()); + assert_eq!("1.2.3", data.bundle_version.unwrap()); + assert_eq!("signed_transaction_info_value", data.signed_transaction_info.unwrap()); + assert_eq!("signed_renewal_info_value", data.signed_renewal_info.unwrap()); + assert_eq!(Status::Active, data.status.unwrap()); + assert_eq!(ConsumptionRequestReason::UnintendedPurchase, data.consumption_request_reason.unwrap()); + } + } + Err(err) => panic!( + "Failed to verify and decode consumption request notification: {:?}", + err + ), + } + } + #[test] fn test_summary_notification_decoding() { let signed_summary_notification = From bcbf92011d55a453578cee784430c52f044e8133 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 13 May 2024 11:19:48 +0200 Subject: [PATCH 2/5] chore: Update deps --- Cargo.toml | 16 ++++++++-------- src/signed_data_verifier.rs | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b77e670..ddeff2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,20 +20,20 @@ ring = "0.17.8" pem = "3.0.3" # Serialization -serde = { version = "1.0.197", features = ["derive"] } -serde_json = { version = "1.0.115" } -serde_with = { version = "3.7.0", features = ["chrono"] } -serde_repr = "0.1.18" +serde = { version = "1.0.201", features = ["derive"] } +serde_json = { version = "1.0.117" } +serde_with = { version = "3.8.1", features = ["chrono"] } +serde_repr = "0.1.19" uuid = { version = "1.8.0", features = ["serde", "v4"] } -chrono = { version = "0.4.37", features = ["serde"] } -base64 = "0.22.0" +chrono = { version = "0.4.38", features = ["serde"] } +base64 = "0.22.1" asn1-rs = { version = "0.6.1", optional = true } # Networking -reqwest = { version = "0.12.2", features = ["json"], optional = true } +reqwest = { version = "0.12.4", features = ["json"], optional = true } # Utils -thiserror = "1.0.58" +thiserror = "1.0.60" # Tools regex = { version = "1.10.4", optional = true } diff --git a/src/signed_data_verifier.rs b/src/signed_data_verifier.rs index c676b60..388e63b 100644 --- a/src/signed_data_verifier.rs +++ b/src/signed_data_verifier.rs @@ -318,7 +318,6 @@ mod tests { use ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING; use serde_json::{Map, Value}; use std::fs; - use chrono::TimeZone; use crate::primitives::consumption_request_reason::ConsumptionRequestReason; const ROOT_CA_BASE64_ENCODED: &str = "MIIBgjCCASmgAwIBAgIJALUc5ALiH5pbMAoGCCqGSM49BAMDMDYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDdXBlcnRpbm8wHhcNMjMwMTA1MjEzMDIyWhcNMzMwMTAyMjEzMDIyWjA2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ3VwZXJ0aW5vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc+/Bl+gospo6tf9Z7io5tdKdrlN1YdVnqEhEDXDShzdAJPQijamXIMHf8xWWTa1zgoYTxOKpbuJtDplz1XriTaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDRwAwRAIgemWQXnMAdTad2JDJWng9U4uBBL5mA7WI05H7oH7c6iQCIHiRqMjNfzUAyiu9h6rOU/K+iTR0I/3Y/NSWsXHX+acc"; From 66651452bbe0e2e466ff3cc7ef09185c0bc8c4ce Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 13 May 2024 11:25:53 +0200 Subject: [PATCH 3/5] chore: Add URL links to docs for items that are a List --- src/primitives/notification_history_response.rs | 2 ++ src/primitives/notification_type_v2.rs | 4 ++-- src/primitives/order_lookup_response.rs | 2 ++ src/primitives/refund_history_response.rs | 2 ++ src/primitives/subscription_group_identifier_item.rs | 2 ++ src/primitives/subtype.rs | 4 ++-- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/primitives/notification_history_response.rs b/src/primitives/notification_history_response.rs index 5ecaba0..f4acc6f 100644 --- a/src/primitives/notification_history_response.rs +++ b/src/primitives/notification_history_response.rs @@ -20,5 +20,7 @@ pub struct NotificationHistoryResponse { /// An array of App Store server notification history records. #[serde(rename = "notificationHistory")] + /// + ///[notificationHistoryResponseItem](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponseitem) pub notification_history: Option>, } diff --git a/src/primitives/notification_type_v2.rs b/src/primitives/notification_type_v2.rs index 42cae5b..3901580 100644 --- a/src/primitives/notification_type_v2.rs +++ b/src/primitives/notification_type_v2.rs @@ -1,8 +1,8 @@ use serde::{Deserialize, Serialize}; -/// A notification type value that App Store Server Notifications V2 uses. +/// The type that describes the in-app purchase or external purchase event for which the App Store sends the version 2 notification. /// -/// [notificationType](https://developer.apple.com/documentation/appstoreserverapi/notificationtype) +/// [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype) #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] pub enum NotificationTypeV2 { #[serde(rename = "SUBSCRIBED")] diff --git a/src/primitives/order_lookup_response.rs b/src/primitives/order_lookup_response.rs index 88829f8..25c2530 100644 --- a/src/primitives/order_lookup_response.rs +++ b/src/primitives/order_lookup_response.rs @@ -13,6 +13,8 @@ pub struct OrderLookupResponse { pub status: OrderLookupStatus, /// An array of in-app purchase transactions that are part of the order, signed by Apple, in JSON Web Signature format. + /// + /// [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) #[serde(rename = "signedTransactions")] pub signed_transactions: Vec, } diff --git a/src/primitives/refund_history_response.rs b/src/primitives/refund_history_response.rs index f93d078..32d5c44 100644 --- a/src/primitives/refund_history_response.rs +++ b/src/primitives/refund_history_response.rs @@ -6,6 +6,8 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] pub struct RefundHistoryResponse { /// 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. + /// + /// [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) #[serde(rename = "signedTransactions")] pub signed_transactions: Vec, diff --git a/src/primitives/subscription_group_identifier_item.rs b/src/primitives/subscription_group_identifier_item.rs index 7536be1..a1f8dbd 100644 --- a/src/primitives/subscription_group_identifier_item.rs +++ b/src/primitives/subscription_group_identifier_item.rs @@ -13,6 +13,8 @@ pub struct SubscriptionGroupIdentifierItem { pub subscription_group_identifier: Option, /// 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. + /// + /// [lastTransactionsItem](https://developer.apple.com/documentation/appstoreserverapi/lasttransactionsitem) #[serde(rename = "lastTransactions")] pub last_transactions: Option>, } diff --git a/src/primitives/subtype.rs b/src/primitives/subtype.rs index 2ec7a28..8aef6d8 100644 --- a/src/primitives/subtype.rs +++ b/src/primitives/subtype.rs @@ -1,8 +1,8 @@ use serde::{Deserialize, Serialize}; -/// A notification subtype value that App Store Server Notifications 2 uses. +/// A string that provides details about select notification types in version 2. /// -/// [Subtype](https://developer.apple.com/documentation/appstoreserverapi/notificationsubtype) +/// [subtype](https://developer.apple.com/documentation/appstoreservernotifications/subtype) #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)] pub enum Subtype { #[serde(rename = "INITIAL_BUY")] From 67d776d15e75ed1a05c3e1f2d4d20d438f4cceb3 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 13 May 2024 11:37:02 +0200 Subject: [PATCH 4/5] feat: Use placeholder URL when LOCAL_TESTING environment option is provided --- src/api_client.rs | 22 +++++++++++----------- src/primitives/environment.rs | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/api_client.rs b/src/api_client.rs index 403e7d7..2d3c59d 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -610,7 +610,7 @@ mod tests { async fn test_extend_renewal_date_for_all_active_subscribers() { let client = app_store_server_api_client_with_body_from_file("assets/models/extendRenewalDateForAllActiveSubscribersResponse.json", StatusCode::OK, Some(|req, body| { assert_eq!(Method::POST, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/extend/mass", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/subscriptions/extend/mass", req.url().as_str()); let decoded_json: HashMap<&str, Value> = serde_json::from_slice(body.unwrap()).unwrap(); assert_eq!(45, decoded_json.get("extendByDays").unwrap().as_u64().unwrap()); @@ -636,7 +636,7 @@ mod tests { async fn test_extend_subscription_renewal_date() { let client = app_store_server_api_client_with_body_from_file("assets/models/extendSubscriptionRenewalDateResponse.json", StatusCode::OK, Some(|req, body| { assert_eq!(Method::PUT, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/extend/4124214", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/subscriptions/extend/4124214", req.url().as_str()); let decoded_json: HashMap<&str, Value> = serde_json::from_slice(body.unwrap()).unwrap(); assert_eq!(45, decoded_json.get("extendByDays").unwrap().as_u64().unwrap()); @@ -661,7 +661,7 @@ mod tests { async fn test_get_all_subscription_statuses() { let client = app_store_server_api_client_with_body_from_file("assets/models/getAllSubscriptionStatusesResponse.json", StatusCode::OK, Some(|req, _body| { assert_eq!(Method::GET, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/4321?status=2&status=1", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/subscriptions/4321?status=2&status=1", req.url().as_str()); assert!(req.body().is_none()); })); @@ -709,7 +709,7 @@ mod tests { async fn test_get_refund_history() { let client = app_store_server_api_client_with_body_from_file("assets/models/getRefundHistoryResponse.json", StatusCode::OK, Some(|req, _body| { assert_eq!(Method::GET, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v2/refund/lookup/555555?revision=revision_input", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v2/refund/lookup/555555?revision=revision_input", req.url().as_str()); assert!(req.body().is_none()); })); @@ -724,7 +724,7 @@ mod tests { async fn test_get_status_of_subscription_renewal_date_extensions() { let client = app_store_server_api_client_with_body_from_file("assets/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json", StatusCode::OK, Some(|req, _body| { assert_eq!(Method::GET, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/extend/mass/20fba8a0-2b80-4a7d-a17f-85c1854727f8/com.example.product", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/subscriptions/extend/mass/20fba8a0-2b80-4a7d-a17f-85c1854727f8/com.example.product", req.url().as_str()); assert!(req.body().is_none()); })); @@ -741,7 +741,7 @@ mod tests { async fn test_get_test_notification_status() { let client = app_store_server_api_client_with_body_from_file("assets/models/getTestNotificationStatusResponse.json", StatusCode::OK, Some(|req, _body| { assert_eq!(Method::GET, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/test/8cd2974c-f905-492a-bf9a-b2f47c791d19", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/notifications/test/8cd2974c-f905-492a-bf9a-b2f47c791d19", req.url().as_str()); assert!(req.body().is_none()); })); @@ -765,7 +765,7 @@ mod tests { async fn test_get_notification_history() { let client = app_store_server_api_client_with_body_from_file("assets/models/getNotificationHistoryResponse.json", StatusCode::OK, Some(|req, body| { assert_eq!(Method::POST, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/history?paginationToken=a036bc0e-52b8-4bee-82fc-8c24cb6715d6", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/notifications/history?paginationToken=a036bc0e-52b8-4bee-82fc-8c24cb6715d6", req.url().as_str()); let decoded_json: HashMap<&str, Value> = serde_json::from_slice(body.unwrap()).unwrap(); assert_eq!(1698148900000, decoded_json["startDate"].as_i64().unwrap()); @@ -862,7 +862,7 @@ mod tests { async fn test_get_transaction_info() { let client = app_store_server_api_client_with_body_from_file("assets/models/transactionInfoResponse.json", StatusCode::OK, Some(|req, _body| { assert_eq!(Method::GET, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/1234", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/transactions/1234", req.url().as_str()); assert!(req.body().is_none()); })); @@ -874,7 +874,7 @@ mod tests { async fn test_look_up_order_id() { let client = app_store_server_api_client_with_body_from_file("assets/models/lookupOrderIdResponse.json", StatusCode::OK, Some(|req, _body| { assert_eq!(Method::GET, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/lookup/W002182", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/lookup/W002182", req.url().as_str()); assert!(req.body().is_none()); })); @@ -887,7 +887,7 @@ mod tests { async fn test_request_test_notification() { let client = app_store_server_api_client_with_body_from_file("assets/models/requestTestNotificationResponse.json", StatusCode::OK, Some(|req, _body| { assert_eq!(Method::POST, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/test", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/notifications/test", req.url().as_str()); assert!(req.body().is_none()); })); @@ -899,7 +899,7 @@ mod tests { async fn test_send_consumption_data() { let client = app_store_server_api_client("".into(), StatusCode::OK, Some(|req, body| { assert_eq!(Method::PUT, req.method()); - assert_eq!("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/consumption/49571273", req.url().as_str()); + assert_eq!("https://local-testing-base-url/inApps/v1/transactions/consumption/49571273", req.url().as_str()); assert_eq!("application/json", req.headers().get("Content-Type").unwrap().to_str().unwrap()); let decoded_json: HashMap = serde_json::from_slice(body.unwrap()).unwrap(); assert_eq!(true, decoded_json["customerConsented"].as_bool().unwrap()); diff --git a/src/primitives/environment.rs b/src/primitives/environment.rs index 6eb5722..10af8ef 100644 --- a/src/primitives/environment.rs +++ b/src/primitives/environment.rs @@ -9,7 +9,7 @@ pub enum Environment { #[serde(rename = "Xcode")] Xcode, #[serde(rename = "LocalTesting")] - LocalTesting, + LocalTesting, // Used for unit testing #[serde(other)] Unknown } @@ -19,6 +19,7 @@ impl Environment { match self { Environment::Production => "https://api.storekit.itunes.apple.com".to_string(), Environment::Sandbox => "https://api.storekit-sandbox.itunes.apple.com".to_string(), + Environment::LocalTesting => "https://local-testing-base-url".to_string(), _ => "https://api.storekit-sandbox.itunes.apple.com".to_string(), } } From 0fec8fa7ede88cb79194786467d896be5f50f4c1 Mon Sep 17 00:00:00 2001 From: tikhop Date: Mon, 13 May 2024 11:38:24 +0200 Subject: [PATCH 5/5] chore: Prepare Release v2.1.0 --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ddeff2b..67070d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "app-store-server-library" description = "The Rust server library for the App Store Server API and App Store Server Notifications" -version = "2.0.0" +version = "2.1.0" repository = "https://github.com/namecare/app-store-server-library-rust" homepage = "https://github.com/namecare/app-store-server-library-rust" authors = ["tkhp", "namecare"] diff --git a/README.md b/README.md index f100d6b..992ee9e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Specify `app-store-server-library` in your project's `Cargo.toml` file, under th ```rust [dependencies] -app-store-server-library = { version = "2.0.0", features = ["receipt-utility", "api-client"] } +app-store-server-library = { version = "2.1.0", features = ["receipt-utility", "api-client"] } ``` Check [crates.io](https://crates.io/crates/app-store-server-library) for the latest version number.