Skip to content

Commit

Permalink
Merge pull request #53 from namecare/develop
Browse files Browse the repository at this point in the history
chore: Release v2.1.0
  • Loading branch information
tikhop authored May 13, 2024
2 parents 7442492 + 0fec8fa commit ea2ba1d
Show file tree
Hide file tree
Showing 18 changed files with 145 additions and 26 deletions.
18 changes: 9 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions assets/signedConsumptionRequestNotification.json
Original file line number Diff line number Diff line change
@@ -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
}
25 changes: 14 additions & 11 deletions src/api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -609,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());
Expand All @@ -635,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());
Expand All @@ -660,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());
}));

Expand Down Expand Up @@ -708,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());
}));

Expand All @@ -723,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());
}));

Expand All @@ -740,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());
}));

Expand All @@ -764,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());
Expand Down Expand Up @@ -861,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());
}));

Expand All @@ -873,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());
}));

Expand All @@ -886,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());
}));

Expand All @@ -898,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<String, Value> = serde_json::from_slice(body.unwrap()).unwrap();
assert_eq!(true, decoded_json["customerConsented"].as_bool().unwrap());
Expand All @@ -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 {
Expand All @@ -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();
Expand Down
7 changes: 7 additions & 0 deletions src/primitives/consumption_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -78,4 +79,10 @@ pub struct ConsumptionRequest {
/// [userStatus](https://developer.apple.com/documentation/appstoreserverapi/userstatus)
#[serde(rename = "userStatus")]
pub user_status: Option<UserStatus>,

/// 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<RefundPreference>,
}
18 changes: 18 additions & 0 deletions src/primitives/consumption_request_reason.rs
Original file line number Diff line number Diff line change
@@ -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,
}
7 changes: 7 additions & 0 deletions src/primitives/data.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -47,4 +48,10 @@ pub struct Data {
/// [JWSRenewalInfo](https://developer.apple.com/documentation/appstoreservernotifications/status)
#[serde(rename = "status")]
pub status: Option<Status>,

/// The reason the customer requested the refund.
///
/// [consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason)
#[serde(rename = "consumptionRequestReason")]
pub consumption_request_reason: Option<ConsumptionRequestReason>,
}
3 changes: 2 additions & 1 deletion src/primitives/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub enum Environment {
#[serde(rename = "Xcode")]
Xcode,
#[serde(rename = "LocalTesting")]
LocalTesting,
LocalTesting, // Used for unit testing
#[serde(other)]
Unknown
}
Expand All @@ -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(),
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/primitives/error_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 2 additions & 0 deletions src/primitives/notification_history_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<NotificationHistoryResponseItem>>,
}
4 changes: 2 additions & 2 deletions src/primitives/notification_type_v2.rs
Original file line number Diff line number Diff line change
@@ -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")]
Expand Down
2 changes: 2 additions & 0 deletions src/primitives/order_lookup_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}
2 changes: 2 additions & 0 deletions src/primitives/refund_history_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,

Expand Down
14 changes: 14 additions & 0 deletions src/primitives/refund_preference.rs
Original file line number Diff line number Diff line change
@@ -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,
}

2 changes: 2 additions & 0 deletions src/primitives/subscription_group_identifier_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub struct SubscriptionGroupIdentifierItem {
pub subscription_group_identifier: Option<String>,

/// 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<Vec<LastTransactionsItem>>,
}
4 changes: 2 additions & 2 deletions src/primitives/subtype.rs
Original file line number Diff line number Diff line change
@@ -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")]
Expand Down
Loading

0 comments on commit ea2ba1d

Please sign in to comment.