app-store-server-library 6.0.0

The Rust server library for the App Store Server API, App Store Server Notifications and Advanced Commerce API
Documentation

Apple App Store Server Rust Library

Build+test Documentation Crates.io Coverage

The Rust server library for the App Store Server API, App Store Server Notifications, the Retention Messaging API, and Advanced Commerce API.

Requirements

  • Rust 1.88.0 or later

Installation

Specify app-store-server-library in your project's Cargo.toml file, under the [dependencies] section:

[dependencies]
app-store-server-library = { version = "6.0.0", features = ["aws_lc", "receipt-utility", "api-client-reqwest"] }

Feature Flags

There are no default features.

Crypto backends (pick one)

  • aws_lc - aws-lc-rs backend. FIPS-friendly, needs a C build toolchain.
  • ring - ring backend.
  • rust_crypto - Pure-Rust backend (p256, p384, rsa, sha2). No C toolchain required.

The backend is also forwarded to the x509-validator dependency used for certificate chain verification, so the whole crate ends up on a single crypto stack.

API client

  • api-client - Enables the App Store Server / Advanced Commerce / Retention Messaging API clients. Bring your own HTTP client by implementing the Transport trait.
  • api-client-reqwest - api-client plus the reqwest transport, using rustls for TLS.
  • api-client-reqwest-native-tls - Same, but using the platform's native-tls.

The reqwest transport requires a TLS backend; enabling reqwest without one is a compile error rather than a runtime connection failure.

Tools

  • receipt-utility - Enables receipt processing and transaction ID extraction

Check crates.io for the latest version number.

Supported API Versions

API Version
App Store Server API 1.21
Retention Messaging API 1.5
Advanced Commerce API 1.2

Obtaining an In-App Purchase key from App Store Connect

To use the App Store Server API or create promotional offer signatures, a signing key downloaded from App Store Connect is required. To obtain this key, you must have the Admin role. Go to Users and Access > Integrations > In-App Purchase. Here you can create and manage keys, as well as find your Issuer ID. When using a key, you'll need the Key ID and the Issuer ID as well.

Obtaining Apple Root Certificates

Download and store the root certificates found in the Apple Root Certificates section of the Apple PKI site. Provide these certificates as an array to a SignedDataVerifier to allow verifying the signed data comes from Apple.

Usage

API Usage

The library ships three clients, matching the three APIs. All of them take a signing key as raw PEM bytes, return a Result from new, and are generic over the Transport trait — ReqwestHttpTransport is provided when the api-client-reqwest feature is on, but any HTTP client works.

App Store Server API

// NOTE: .unwrap() used for example purposes only

use app_store_server_library::app_store_server_api_client::AppStoreServerApiClient;
use app_store_server_library::api_client::reqwest_transport::reqwest_http_transport::ReqwestHttpTransport;
use app_store_server_library::models::app_store_environment::Environment;

#[tokio::main]
async fn main() {
    let issuer_id = "99b16628-15e4-4668-972b-eeff55eeff55";
    let key_id = "ABCDEFGHIJ";
    let bundle_id = "com.example";
    let signing_key = std::fs::read("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8").unwrap(); // Adjust the path accordingly
    let environment = Environment::Sandbox;
    let transport = ReqwestHttpTransport::new(); // You can use any http client, but you must implement `Transport` trait for it.
    let client = AppStoreServerApiClient::new(signing_key, key_id, issuer_id, bundle_id, environment, transport).unwrap();

    match client.request_test_notification().await {
        Ok(response) => {
            println!("{}", response.test_notification_token);
        }
        Err(err) => {
            println!("{:?}", err);
        }
    }
}

The Retention Messaging API methods (message list, image upload, default configuration, performance tests, realtime URL) live on this same AppStoreServerApiClient, matching the Swift library's layout.

Advanced Commerce Server API

// NOTE: .unwrap() used for example purposes only

use app_store_server_library::advanced_commerce_api_client::AdvancedCommerceApiClient;
use app_store_server_library::api_client::reqwest_transport::reqwest_http_transport::ReqwestHttpTransport;
use app_store_server_library::models::app_store_environment::Environment;

#[tokio::main]
async fn main() {
    let issuer_id = "99b16628-15e4-4668-972b-eeff55eeff55";
    let key_id = "ABCDEFGHIJ";
    let bundle_id = "com.example";
    let signing_key = std::fs::read("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8").unwrap(); // Adjust the path accordingly
    let environment = Environment::Sandbox;
    let transport = ReqwestHttpTransport::new(); // You can use any http client, but you must implement `Transport` trait for it.
    let client = AdvancedCommerceApiClient::new(signing_key, key_id, issuer_id, bundle_id, environment, transport).unwrap();

    let transaction_id = "txId";
    let subscription_cancel_request = AdvancedCommerceSubscriptionCancelRequest { /* .. */ };
    match client.cancel_subscription(transaction_id, &subscription_cancel_request).await {
        Ok(response) => {
            println!("{}", response.signed_renewal_info);
            println!("{}", response.signed_transaction_info);
        }
        Err(err) => {
            println!("{:?}", err);
        }
    }
}

Verification Usage

// NOTE: .unwrap() used for example purposes only

let root_cert = "apple-root-cert-in-base-base64-format"; // https://www.apple.com/certificateauthority/AppleRootCA-G3.cer
let root_cert_der = root_cert.as_der_bytes().unwrap(); // Use `base64` crate to decode base64 string into bytes 

let verifier = SignedDataVerifier::new(
    vec![root_cert_der], // Vector of root certificates
    Environment::Sandbox, // Environment
    "app.superapp.apple".to_string(), // Bundle id
    Some(12345678), // App id — required in Production
    false, // enable_online_checks
).unwrap();

let payload = "signed-payload";
let decoded_payload = verifier.verify_and_decode_notification(payload).unwrap();

Certificate chain verification is delegated to the x509-validator crate, which runs on the same crypto backend you selected above.

Receipt Usage

let receipt = "MI..";
let transaction_id = extract_transaction_id_from_app_receipt(receipt);

Note: To extract transaction id from app/tx receipt, receipt-utility feature must be enabled.

Promotional Offer Signature Creation

V1 Signature Creation

// NOTE: .unwrap() used for example purposes only

use app_store_server_library::promotional_offer_signature_creator::PromotionalOfferSignatureCreator;

let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = PromotionalOfferSignatureCreator::new(private_key, "L256SYR32L".to_string(), "com.test.app".to_string()).unwrap();

let nonce = uuid::Uuid::new_v4();
let timestamp = chrono::Utc::now().timestamp_millis();
let signature: String = creator.create_signature(
    "com.test.product",
    "com.test.offer", 
    uuid::Uuid::new_v4().to_string().as_str(), // app account token
    &nonce,
    timestamp
).unwrap();

V2 Signature Creation

// NOTE: .unwrap() used for example purposes only

use app_store_server_library::jws_signature_creator::PromotionalOfferV2SignatureCreator;

let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = PromotionalOfferV2SignatureCreator::new(
    private_key, 
    "L256SYR32L".to_string(),     // Key ID
    "issuer_id".to_string(),       // Issuer ID
    "com.test.app".to_string()     // Bundle ID
).unwrap();

let signature: String = creator.create_signature(
    "com.test.product",             // Product ID
    "com.test.offer",               // Offer identifier
    Some("transaction_id".to_string()) // Optional transaction ID
).unwrap();

Introductory Offer Eligibility Signature Creation

// NOTE: .unwrap() used for example purposes only

use app_store_server_library::jws_signature_creator::IntroductoryOfferEligibilitySignatureCreator;

let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = IntroductoryOfferEligibilitySignatureCreator::new(
    private_key,
    "L256SYR32L".to_string(),      // Key ID
    "issuer_id".to_string(),       // Issuer ID
    "com.test.app".to_string()     // Bundle ID
).unwrap();

let signature: String = creator.create_signature(
    "com.test.product",            // Product ID
    true,                          // Allow introductory offer
    "transaction_id"               // Transaction ID
).unwrap();

Advanced Commerce Signature Creation

Prepare request object:

  • Receive request object from the client.
  • Or create request from the server side.

Supported request objects (any type implementing AdvancedCommerceInAppRequest): AdvancedCommerceOneTimeChargeCreateRequest, AdvancedCommerceSubscriptionCreateRequest, AdvancedCommerceSubscriptionModifyInAppRequest or AdvancedCommerceSubscriptionReactivateInAppRequest.

// NOTE: .unwrap() used for example purposes only

use app_store_server_library::jws_signature_creator::AdvancedCommerceInAppSignatureCreator;

let request_object = ... // Receive from client side or create on server side 
let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = AdvancedCommerceInAppSignatureCreator::new(
    private_key, 
    "L256SYR32L".to_string(),     // Key ID
    "issuer_id".to_string(),       // Issuer ID
    "com.test.app".to_string()     // Bundle ID
).unwrap();

let signature: String = creator.create_signature(
    advanced_commerce_in_app_request: &request_object
).unwrap();

Documentation

References

Benchmarks

Two crates, in bench/:

License

app-store-server-library is distributed under the following two licenses:

  • Apache License version 2.0.
  • MIT license.

These are included as LICENSE-APACHE and LICENSE-MIT respectively.
You may use this software under the terms of any of these licenses, at your option.