### Install App Store Server API Client Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Installs the app-store-server-api package using npm. This is the first step to using the client library in a Node.js project. ```bash npm install app-store-server-api ``` -------------------------------- ### Get Subscription Statuses Source: https://context7.com/agisboye/app-store-server-api/llms.txt Check the current status of all subscriptions associated with a transaction. Returns subscription status, renewal info, and the latest transaction for each subscription group. ```APIDOC ## GET /subscription/statuses ### Description Check the current status of all subscriptions associated with a transaction. Returns subscription status, renewal info, and the latest transaction for each subscription group. ### Method GET ### Endpoint /subscription/statuses ### Query Parameters - **originalTransactionId** (string) - Required - The original transaction identifier for the subscription. - **status** (array of SubscriptionStatus) - Optional - Filters the results to only include subscriptions with the specified statuses. ### Request Example ```typescript const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const originalTransactionId = "1000000123456789" const response = await api.getSubscriptionStatuses(originalTransactionId, { status: [SubscriptionStatus.Active, SubscriptionStatus.InBillingGracePeriod] }) ``` ### Response #### Success Response (200) - **data** (array) - An array of subscription group objects, each containing `subscriptionGroupIdentifier` and `lastTransactions`. - **subscriptionGroupIdentifier** (string) - The identifier for the subscription group. - **lastTransactions** (array) - An array of the latest transactions for the subscription group. - **status** (SubscriptionStatus) - The status of the subscription. - **signedTransactionInfo** (string) - The signed transaction information. - **signedRenewalInfo** (string) - The signed renewal information. #### Response Example ```json { "data": [ { "subscriptionGroupIdentifier": "1234567890", "lastTransactions": [ { "status": 1, // Active "signedTransactionInfo": "eyJhbGciOiJSUzI1NiIsImtpZCI6I...", "signedRenewalInfo": "eyJhbGciOiJSUzI1NiIsImtpZCI6I..." } ] } ] } ``` ``` -------------------------------- ### Get Transaction History (TypeScript) Source: https://context7.com/agisboye/app-store-server-api/llms.txt Retrieves a customer's transaction history, with options for filtering by product type, date range, and sort order. Results are paginated. The signed transactions can be decoded and verified using helper functions. ```typescript import { AppStoreServerAPI, Environment, decodeTransactions, ProductTypeParameter, SortParameter, TransactionHistoryVersion } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const originalTransactionId = "1000000123456789" // Basic usage - get all transaction history const response = await api.getTransactionHistory(originalTransactionId) // Decode and verify the signed transactions const transactions = await decodeTransactions(response.signedTransactions) for (const transaction of transactions) { console.log({ transactionId: transaction.transactionId, productId: transaction.productId, purchaseDate: new Date(transaction.purchaseDate), expiresDate: transaction.expiresDate ? new Date(transaction.expiresDate) : null, type: transaction.type, price: transaction.price, currency: transaction.currency }) } // Handle pagination if (response.hasMore) { const nextPage = await api.getTransactionHistory(originalTransactionId, { revision: response.revision }) // Process next page... } // Advanced filtering options const filteredResponse = await api.getTransactionHistory( originalTransactionId, { productType: ProductTypeParameter.AutoRenewable, sort: SortParameter.Descending, startDate: Date.now() - 365 * 24 * 60 * 60 * 1000, // Last year endDate: Date.now() }, TransactionHistoryVersion.v2 // Use v2 API ) ``` -------------------------------- ### Get Notification History Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Fetches the history of App Store Server Notifications within a specified date range. It supports pagination to retrieve all historical notifications. ```javascript // Start and end date are required. // The earliest supported start date is June 6th (the start of WWDC 2022). const response = await api.getNotificationHistory({ startDate: 1654466400000, // June 6th 2022 endDate: new Date().getTime() }) // Check if there are more items. if (response.hasMore) { // Use history.paginationToken to fetch additional items. } ``` -------------------------------- ### Get Subscription Status and Decode Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Retrieves the subscription status for a given original transaction ID and decodes the transaction and renewal information. This is useful for understanding the current state of a user's subscription. ```javascript const { decodeTransaction, decodeRenewalInfo } = require("app-store-server-api") // or // import { decodeTransaction, decodeRenewalInfo } from "app-store-server-api" const response = await api.getSubscriptionStatuses(originalTransactionId) // Find the transaction you're looking for const item = response.data[0].lastTransactions.find(item => item.originalTransactionId === originalTransactionId) const transactionInfo = await decodeTransaction(item.signedTransactionInfo) const renewalInfo = await decodeRenewalInfo(item.signedRenewalInfo) ``` -------------------------------- ### Get Test Notification Status with App Store Server API (TypeScript) Source: https://context7.com/agisboye/app-store-server-api/llms.txt Checks the delivery status of a previously triggered test notification. Requires the test notification token. Returns an array of send attempts with their dates and results. ```typescript import { AppStoreServerAPI, Environment, SendAttemptResult } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const testNotificationToken = "ae0e2185-a3c6-47e4-b41a-6ef4bc86314e_1656062546521" const response = await api.getTestNotificationStatus(testNotificationToken) for (const attempt of response.sendAttempts) { console.log({ attemptDate: new Date(attempt.attemptDate), result: SendAttemptResult[attempt.sendAttemptResult] }) } ``` -------------------------------- ### Get Transaction Info (TypeScript) Source: https://context7.com/agisboye/app-store-server-api/llms.txt Retrieves detailed information for a specific transaction using its transaction ID. The response contains a signed transaction info that can be decoded using the `decodeTransaction` helper function. ```typescript import { AppStoreServerAPI, Environment, decodeTransaction } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const transactionId = "1000000123456789" const response = await api.getTransactionInfo(transactionId) const transaction = await decodeTransaction(response.signedTransactionInfo) console.log({ originalTransactionId: transaction.originalTransactionId, bundleId: transaction.bundleId, productId: transaction.productId, purchaseDate: new Date(transaction.purchaseDate), originalPurchaseDate: new Date(transaction.originalPurchaseDate), quantity: transaction.quantity, type: transaction.type, inAppOwnershipType: transaction.inAppOwnershipType, environment: transaction.environment }) ``` -------------------------------- ### Get Test Notification Status Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Retrieves the status of a previously requested test notification using its unique token. This helps in verifying if the test notification was successfully sent. ```javascript const response = await api.getTestNotificationStatus("ae0e2185-a3c6-47e4-b41a-6ef4bc86314e_1656062546521") ``` -------------------------------- ### Get Notification History with App Store Server API (TypeScript) Source: https://context7.com/agisboye/app-store-server-api/llms.txt Retrieves the history of App Store Server Notifications sent to your server within a specified date range. Supports filtering by notification type and showing only failed deliveries. Handles pagination for retrieving all available history. ```typescript import { AppStoreServerAPI, Environment, NotificationType, NotificationSubtype } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) // Get all notifications from the last 30 days const response = await api.getNotificationHistory({ startDate: Date.now() - 30 * 24 * 60 * 60 * 1000, endDate: Date.now() }) for (const item of response.notificationHistory) { console.log({ signedPayload: item.signedPayload, sendAttempts: item.sendAttempts.map(a => ({ attemptDate: new Date(a.attemptDate), result: a.sendAttemptResult })) }) } // Handle pagination if (response.hasMore) { const nextPage = await api.getNotificationHistory( { startDate: Date.now() - 30 * 24 * 60 * 60 * 1000, endDate: Date.now() }, { paginationToken: response.paginationToken } ) } // Filter by notification type const refundNotifications = await api.getNotificationHistory({ startDate: Date.now() - 30 * 24 * 60 * 60 * 1000, endDate: Date.now(), notificationType: NotificationType.Refund, onlyFailures: true // Only show failed deliveries }) ``` -------------------------------- ### Get Subscription Statuses using App Store Server API Source: https://context7.com/agisboye/app-store-server-api/llms.txt Retrieves the current status of all subscriptions for a given transaction. It returns subscription status, renewal information, and the latest transaction for each subscription group. This function requires the App Store Server API client, transaction ID, and optionally can filter by subscription status. ```typescript import { AppStoreServerAPI, Environment, decodeTransaction, decodeRenewalInfo, SubscriptionStatus, AutoRenewStatus } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const originalTransactionId = "1000000123456789" const response = await api.getSubscriptionStatuses(originalTransactionId) for (const subscriptionGroup of response.data) { console.log(`Subscription Group: ${subscriptionGroup.subscriptionGroupIdentifier}`) for (const item of subscriptionGroup.lastTransactions) { const transaction = await decodeTransaction(item.signedTransactionInfo) const renewalInfo = await decodeRenewalInfo(item.signedRenewalInfo) console.log({ status: SubscriptionStatus[item.status], // Active, Expired, InBillingRetry, etc. productId: transaction.productId, expiresDate: transaction.expiresDate ? new Date(transaction.expiresDate) : null, autoRenewStatus: renewalInfo.autoRenewStatus === AutoRenewStatus.On ? 'On' : 'Off', autoRenewProductId: renewalInfo.autoRenewProductId, renewalDate: new Date(renewalInfo.renewalDate), isInBillingRetryPeriod: renewalInfo.isInBillingRetryPeriod, gracePeriodExpiresDate: renewalInfo.gracePeriodExpiresDate ? new Date(renewalInfo.gracePeriodExpiresDate) : null }) } } // Filter by subscription status const activeOnly = await api.getSubscriptionStatuses(originalTransactionId, { status: [SubscriptionStatus.Active, SubscriptionStatus.InBillingGracePeriod] }) ``` -------------------------------- ### Decode App Store Server Notifications (JavaScript) Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Decodes App Store Server Notifications (version 2) and verifies their signature. It handles both data and summary payloads, allowing for type checking using provided guards. Ensure the 'app-store-server-api' library is installed. ```javascript import { decodeNotificationPayload, isDecodedNotificationDataPayload, isDecodedNotificationSummaryPayload } from "app-store-server-api" // signedPayload is the body sent by Apple const payload = await decodeNotificationPayload(signedPayload) // You might want to check that the bundle ID matches that of your app if (payload.data.bundleId === APP_BUNDLE_ID) { // Handle the notification... } // Notifications can contain either a data field or a summary field but never both. // Use the provided type guards to determine which is present. if (isDecodedNotificationDataPayload(payload)) { // payload is of type DecodedNotificationDataPayload } if (isDecodedNotificationSummaryPayload(payload)) { // payload is of type DecodedNotificationSummaryPayload } ``` -------------------------------- ### Create App Store Server API Client Instance Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Demonstrates how to create an instance of the AppStoreServerAPI client. It requires private key, key ID, issuer ID, app bundle ID, and the environment (e.g., Production). The private key should be loaded securely. ```javascript const { AppStoreServerAPI, Environment } = require("app-store-server-api") // or // import { AppStoreServerAPI, Environment } from "app-store-server-api" const KEY = `-----BEGIN PRIVATE KEY----- MHcCAQEEIPWH5lyoG7Wbzv71ntF6jNvFwwJLKYmPWN/KBD4qJfMcoAoGCCqGSM49 AwEHoUQDQgAEMOlUa/hmyAPU/RUBds6xzDO8QNrTFhFwzm8E4wxDnSAx8R9WOMnD cVGdtnbLFIdLk8g4S7oAfV/gGILKuc+Vqw== -----END PRIVATE KEY----- ` const KEY_ID = "ABCD123456" const ISSUER_ID = "91fa5999-7b54-4363-a2a8-265363fa6cbe" const APP_BUNDLE_ID = "com.yourcompany.app" const api = new AppStoreServerAPI( KEY, KEY_ID, ISSUER_ID, APP_BUNDLE_ID, Environment.Production ) ``` -------------------------------- ### Initialize App Store Server API Client (TypeScript) Source: https://context7.com/agisboye/app-store-server-api/llms.txt Initializes the AppStoreServerAPI client for either production or sandbox environments. Requires App Store Connect credentials including a private key, key ID, issuer ID, and app bundle ID. The client manages JWT token generation and renewal automatically. ```typescript import { AppStoreServerAPI, Environment, decodeTransactions, decodeTransaction, decodeRenewalInfo } from "app-store-server-api" // Your private key from App Store Connect (PEM-encoded PKCS8 format) const KEY = `-----BEGIN PRIVATE KEY----- MHcCAQEEIPWH5lyoG7Wbzv71ntF6jNvFwwJLKYmPWN/KBD4qJfMcoAoGCCqGSM49 AwEHoUQDQgAEMOlUa/hmyAPU/RUBds6xzDO8QNrTFhFwzm8E4wxDnSAx8R9WOMnD cVGdtnbLFIdLk8g4S7oAfV/gGILKuc+Vqw== -----END PRIVATE KEY----- ` const KEY_ID = "ABCD123456" // Key ID from App Store Connect const ISSUER_ID = "91fa5999-7b54-4363-a2a8-265363fa6cbe" // Issuer ID const APP_BUNDLE_ID = "com.yourcompany.app" // Create client for production const api = new AppStoreServerAPI( KEY, KEY_ID, ISSUER_ID, APP_BUNDLE_ID, Environment.Production ) // Or for sandbox testing const sandboxApi = new AppStoreServerAPI( KEY, KEY_ID, ISSUER_ID, APP_BUNDLE_ID, Environment.Sandbox ) ``` -------------------------------- ### Fetch Transaction History with Filters Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Fetches transaction history with advanced filtering and sorting options. This allows for more specific data retrieval based on product type and sort order. ```javascript // Import parameter types import { ProductTypeParameter, SortParameter } from "app-store-server-api" const response = await api.getTransactionHistory(originalTransactionId, { productType: ProductTypeParameter.AutoRenewable, sort: SortParameter.Descending, }) ``` -------------------------------- ### Verify Xcode StoreKit Test Transactions with Custom Certificate Source: https://context7.com/agisboye/app-store-server-api/llms.txt This snippet demonstrates how to verify StoreKit transactions using a custom root certificate fingerprint, essential for testing in Xcode. It imports necessary functions and constants from the 'app-store-server-api' library and shows how to select the appropriate fingerprint based on the environment. ```typescript import { decodeTransactions, decodeTransaction, APPLE_ROOT_CA_G3_FINGERPRINT } from "app-store-server-api" // Get your Xcode root certificate fingerprint by exporting from Xcode // and running: openssl x509 -fingerprint -sha256 -in certificate.cer const XCODE_ROOT_FINGERPRINT = "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99" // Choose fingerprint based on environment const fingerprint = process.env.NODE_ENV === "production" ? APPLE_ROOT_CA_G3_FINGERPRINT : XCODE_ROOT_FINGERPRINT // Decode with custom fingerprint const transactions = await decodeTransactions(signedTransactions, fingerprint) const transaction = await decodeTransaction(signedTransaction, fingerprint) ``` -------------------------------- ### Request Test Notification Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Requests a test notification from the App Store Server API. The response includes a token that identifies the notification to be sent. ```javascript const response = await api.requestTestNotification() // response.testNotificationToken identifies the notification that will be sent. ``` -------------------------------- ### Verify Xcode StoreKit Test Transactions Source: https://context7.com/agisboye/app-store-server-api/llms.txt This section details how to verify StoreKit transactions when testing in Xcode using a custom root certificate fingerprint. ```APIDOC ## Verify Xcode StoreKit Test Transactions When testing with StoreKit in Xcode, use a custom root certificate fingerprint for verification. ### Description This snippet demonstrates how to decode StoreKit transactions using a custom certificate fingerprint, specifically for Xcode testing environments. ### Method N/A (Client-side function) ### Endpoint N/A (Client-side function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```typescript import { decodeTransactions, decodeTransaction, APPLE_ROOT_CA_G3_FINGERPRINT } from "app-store-server-api" // Get your Xcode root certificate fingerprint by exporting from Xcode // and running: openssl x509 -fingerprint -sha256 -in certificate.cer const XCODE_ROOT_FINGERPRINT = "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99" // Choose fingerprint based on environment const fingerprint = process.env.NODE_ENV === "production" ? APPLE_ROOT_CA_G3_FINGERPRINT : XCODE_ROOT_FINGERPRINT // Decode with custom fingerprint const transactions = await decodeTransactions(signedTransactions, fingerprint) const transaction = await decodeTransaction(signedTransaction, fingerprint) ``` ### Response #### Success Response (200) Decoded transaction objects. #### Response Example ```json { "example": "decoded transaction object" } ``` ``` -------------------------------- ### Error Handling Source: https://context7.com/agisboye/app-store-server-api/llms.txt This section covers how to handle API errors, including rate limiting and retryable errors, when interacting with the App Store Server API. ```APIDOC ## Error Handling Handle API errors including rate limiting and retryable errors. ### Description This snippet provides a robust error handling mechanism for API calls, specifically addressing `AppStoreError` and `CertificateValidationError`, including strategies for rate limiting and retrying operations. ### Method N/A (Client-side function) ### Endpoint N/A (Client-side function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```typescript import { AppStoreServerAPI, Environment, AppStoreError, CertificateValidationError } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) async function fetchWithRetry(transactionId: string, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await api.getTransactionHistory(transactionId) } catch (error) { if (error instanceof AppStoreError) { console.log(`Error code: ${error.errorCode}`) console.log(`Message: ${error.message}`) // Handle rate limiting if (error.isRateLimitExceeded && error.retryAfter) { console.log(`Rate limited. Retrying after ${error.retryAfter} seconds`) await new Promise(resolve => setTimeout(resolve, error.retryAfter! * 1000)) continue } // Handle retryable errors if (error.isRetryable) { console.log("Retryable error, attempting again...") await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1))) continue } // Non-retryable error throw error } if (error instanceof CertificateValidationError) { console.log("Certificate validation failed - possible tampering detected") throw error } throw error } } throw new Error("Max retries exceeded") } ``` ### Response #### Success Response (200) Returns the result of the API call (e.g., transaction history) or throws an error if retries are exhausted. #### Response Example ```json { "example": "API response data or error message" } ``` ``` -------------------------------- ### Look Up Order Source: https://context7.com/agisboye/app-store-server-api/llms.txt Validate an order ID and retrieve all associated transactions. Useful for verifying purchases from customer receipts or order confirmations. ```APIDOC ## GET /order/{orderId} ### Description Validate an order ID and retrieve all associated transactions. Useful for verifying purchases from customer receipts or order confirmations. ### Method GET ### Endpoint /order/{orderId} ### Path Parameters - **orderId** (string) - Required - The order identifier to look up. ### Response #### Success Response (200) - **status** (OrderLookupStatus) - The status of the order lookup. - **signedTransactions** (string) - A JWS string containing transaction information if the order is valid. #### Response Example ```json { "status": "VALID", "signedTransactions": "eyJhbGciOiJSUzI1NiIsImtpZCI6I..." } ``` #### Error Response (404) - **status** (OrderLookupStatus) - The status will be 'INVALID'. #### Error Response Example ```json { "status": "INVALID" } ``` ``` -------------------------------- ### Fetch Transaction History and Decode Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Retrieves the transaction history for a given original transaction ID and decodes the signed transactions. The response may contain more items, requiring subsequent calls using the revision. ```javascript const { decodeTransactions } = require("app-store-server-api") // or // import { decodeTransactions } from "app-store-server-api" const response = await api.getTransactionHistory(originalTransactionId) // Decoding not only reveals the contents of the transactions but also verifies that they were signed by Apple. const transactions = await decodeTransactions(response.signedTransactions) for (let transaction of transactions) { // Do something with your transactions... } // The response contains at most 20 entries. You can check to see if there are more. if (response.hasMore) { const nextResponse = await api.getTransactionHistory(originalTransactionId, { revision: response.revision }) // ... } ``` -------------------------------- ### Send Consumption Information Source: https://context7.com/agisboye/app-store-server-api/llms.txt Provide consumption data to Apple when responding to a refund request for a consumable in-app purchase. ```APIDOC ## POST /inApps/v1/transactions/{transactionId}/consumption ### Description Provide consumption data to Apple when responding to a refund request for a consumable in-app purchase. ### Method POST ### Endpoint /inApps/v1/transactions/{transactionId}/consumption ### Path Parameters - **transactionId** (string) - Required - The transaction identifier for the consumable in-app purchase. ### Request Body - **accountTenure** (AccountTenure) - The tenure of the customer's account. - **appAccountToken** (string) - The app-specific account identifier for the user. - **consumptionStatus** (ConsumptionStatus) - The status of the consumption. - **customerConsented** (boolean) - Indicates if the customer consented to the refund. - **deliveryStatus** (DeliveryStatus) - The status of the in-app purchase delivery. - **lifetimeDollarsPurchased** (LifetimeDollarsPurchased) - The total amount of dollars purchased lifetime. - **lifetimeDollarsRefunded** (LifetimeDollarsRefunded) - The total amount of dollars refunded lifetime. - **platform** (Platform) - The platform of the purchase. - **playTime** (PlayTime) - The amount of time the user has spent playing the game. - **refundPreference** (RefundPreference) - The user's preference for refunds. - **sampleContentProvided** (boolean) - Indicates if sample content was provided. - **userStatus** (UserStatus) - The status of the user's account. ### Request Example ```typescript const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const transactionId = "1000000123456789" await api.sendConsumptionInformation(transactionId, { accountTenure: AccountTenure.Between90_180Days, appAccountToken: "user-account-token", consumptionStatus: ConsumptionStatus.PartiallyConsumed, customerConsented: true, deliveryStatus: DeliveryStatus.DeliveredAndWorkingProperly, lifetimeDollarsPurchased: LifetimeDollarsPurchased.Between_50_And_99_99USD, lifetimeDollarsRefunded: LifetimeDollarsRefunded.ZeroUSD, platform: Platform.Apple, playTime: PlayTime.Between6_24Hours, refundPreference: RefundPreference.PreferAppleDeclinesRefund, sampleContentProvided: true, userStatus: UserStatus.AccountIsActive }) ``` ### Response #### Success Response (200) This endpoint does not return a response body on success. A 200 OK status code indicates the consumption information was successfully sent. ``` -------------------------------- ### Verify Xcode Signed Transactions (JavaScript) Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Verifies transactions signed by Xcode's local certificate authority during StoreKit testing. Requires exporting Xcode's root certificate and obtaining its SHA256 fingerprint. The function dynamically selects between Apple's production fingerprint and a local one based on the environment. ```javascript import { decodeTransactions, APPLE_ROOT_CA_G3_FINGERPRINT } from "app-store-server-api" const LOCAL_ROOT_FINGERPRINT = "AA:BB:CC:DD:" const fingerprint = (process.env.NODE_ENV === "production") ? APPLE_ROOT_CA_G3_FINGERPRINT : LOCAL_ROOT_FINGERPRINT const transactions = await decodeTransactions(response.signedTransactions, fingerprint) ``` -------------------------------- ### Extend Subscription Renewal Date Source: https://context7.com/agisboye/app-store-server-api/llms.txt Extend a customer's subscription renewal date as a gesture of goodwill or compensation. ```APIDOC ## POST /extend/receipt ### Description Extend a customer's subscription renewal date as a gesture of goodwill or compensation. ### Method POST ### Endpoint /extend/receipt ### Request Body - **originalTransactionId** (string) - Required - The original transaction identifier for the subscription. - **extendByDays** (integer) - Required - The number of days to extend the subscription by. - **extendReasonCode** (ExtendReasonCode) - Required - The reason for extending the subscription. - **requestIdentifier** (string) - Optional - Your unique identifier for this request. ### Request Example ```typescript const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const originalTransactionId = "1000000123456789" const response = await api.extendSubscriptionRenewalDate(originalTransactionId, { extendByDays: 7, extendReasonCode: ExtendReasonCode.CUSTOMER_SATISFACTION, requestIdentifier: "unique-request-id-12345" }) ``` ### Response #### Success Response (200) - **success** (boolean) - Indicates if the extension was successful. - **effectiveDate** (integer) - The Unix timestamp when the extension becomes effective. - **originalTransactionId** (string) - The original transaction identifier. #### Response Example ```json { "success": true, "effectiveDate": 1678886400000, "originalTransactionId": "1000000123456789" } ``` ``` -------------------------------- ### Trigger Test Notification with App Store Server API (TypeScript) Source: https://context7.com/agisboye/app-store-server-api/llms.txt Triggers a test notification from Apple to verify your server's notification endpoint. Requires authentication credentials and bundle ID. Returns a test notification token used for checking delivery status. ```typescript import { AppStoreServerAPI, Environment } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const response = await api.requestTestNotification() console.log(`Test notification token: ${response.testNotificationToken}`) // Use this token to check delivery status ``` -------------------------------- ### Lookup Order and Decode Transactions Source: https://github.com/agisboye/app-store-server-api/blob/main/README.md Looks up an order by its ID and decodes the associated transactions if the order is valid. This is useful for verifying purchase details. ```javascript const { OrderLookupStatus, decodeTransactions } = require("app-store-server-api") // or // import { OrderLookupStatus, decodeTransactions } from "app-store-server-api" const response = await api.lookupOrder(orderId) if (response.status === OrderLookupStatus.Valid) { const transactions = await decodeTransactions(response.signedTransactions) /// ... } ``` -------------------------------- ### Send Consumption Information using App Store Server API Source: https://context7.com/agisboye/app-store-server-api/llms.txt Provides consumption data to Apple when responding to a refund request for a consumable in-app purchase. This function requires the App Store Server API client, the transaction ID, and an object containing various consumption details such as account tenure, consumption status, and lifetime dollars purchased/refunded. ```typescript import { AppStoreServerAPI, Environment, AccountTenure, ConsumptionStatus, DeliveryStatus, LifetimeDollarsPurchased, LifetimeDollarsRefunded, Platform, PlayTime, RefundPreference, UserStatus } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const transactionId = "1000000123456789" await api.sendConsumptionInformation(transactionId, { accountTenure: AccountTenure.Between90_180Days, appAccountToken: "user-account-token", consumptionStatus: ConsumptionStatus.PartiallyConsumed, customerConsented: true, deliveryStatus: DeliveryStatus.DeliveredAndWorkingProperly, lifetimeDollarsPurchased: LifetimeDollarsPurchased.Between_50_And_99_99USD, lifetimeDollarsRefunded: LifetimeDollarsRefunded.ZeroUSD, platform: Platform.Apple, playTime: PlayTime.Between6_24Hours, refundPreference: RefundPreference.PreferAppleDeclinesRefund, sampleContentProvided: true, userStatus: UserStatus.AccountIsActive }) ``` -------------------------------- ### Handle API Errors with Retry Logic in App Store Server API Source: https://context7.com/agisboye/app-store-server-api/llms.txt This code illustrates robust error handling for the App Store Server API, including strategies for rate limiting and other retryable errors. It initializes the API client and defines a function to fetch transaction history with automatic retries and specific error checks for AppStoreError and CertificateValidationError. ```typescript import { AppStoreServerAPI, Environment, AppStoreError, CertificateValidationError } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) async function fetchWithRetry(transactionId: string, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await api.getTransactionHistory(transactionId) } catch (error) { if (error instanceof AppStoreError) { console.log(`Error code: ${error.errorCode}`) console.log(`Message: ${error.message}`) // Handle rate limiting if (error.isRateLimitExceeded && error.retryAfter) { console.log(`Rate limited. Retrying after ${error.retryAfter} seconds`) await new Promise(resolve => setTimeout(resolve, error.retryAfter! * 1000)) continue } // Handle retryable errors if (error.isRetryable) { console.log("Retryable error, attempting again...") await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1))) continue } // Non-retryable error throw error } if (error instanceof CertificateValidationError) { console.log("Certificate validation failed - possible tampering detected") throw error } throw error } } throw new Error("Max retries exceeded") } ``` -------------------------------- ### Look Up Order using App Store Server API Source: https://context7.com/agisboye/app-store-server-api/llms.txt Validates an order ID and retrieves all associated transactions. This is useful for verifying purchases from customer receipts or order confirmations. The function requires the App Store Server API client and the order ID. It returns a status indicating if the order is valid and the signed transactions. ```typescript import { AppStoreServerAPI, Environment, decodeTransactions, OrderLookupStatus } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const orderId = "MN8TK2L9VX" // Order ID from customer receipt const response = await api.lookupOrder(orderId) if (response.status === OrderLookupStatus.Valid) { const transactions = await decodeTransactions(response.signedTransactions) for (const transaction of transactions) { console.log({ transactionId: transaction.transactionId, productId: transaction.productId, purchaseDate: new Date(transaction.purchaseDate), price: transaction.price, currency: transaction.currency }) } } else { console.log("Invalid order ID") } ``` -------------------------------- ### Extend Subscription Renewal Date using App Store Server API Source: https://context7.com/agisboye/app-store-server-api/llms.txt Extends a customer's subscription renewal date, typically used as a gesture of goodwill or compensation. This function requires the App Store Server API client, the original transaction ID, and an object containing the extension details (days, reason code, request identifier). It returns a success status and the new effective date. ```typescript import { AppStoreServerAPI, Environment, ExtendReasonCode } from "app-store-server-api" const api = new AppStoreServerAPI(KEY, KEY_ID, ISSUER_ID, BUNDLE_ID, Environment.Production) const originalTransactionId = "1000000123456789" const response = await api.extendSubscriptionRenewalDate(originalTransactionId, { extendByDays: 7, extendReasonCode: ExtendReasonCode.CUSTOMER_SATISFACTION, requestIdentifier: "unique-request-id-12345" // Your unique identifier for this request }) console.log({ success: response.success, effectiveDate: new Date(response.effectiveDate), originalTransactionId: response.originalTransactionId }) ``` -------------------------------- ### Decode Server Notifications with App Store Server API (TypeScript) Source: https://context7.com/agisboye/app-store-server-api/llms.txt Decodes and verifies App Store Server Notifications V2 received at your webhook endpoint. Handles both data and summary payload types, including decoding transaction and renewal information, and processing various notification types and subtypes. ```typescript import { decodeNotificationPayload, decodeTransaction, decodeRenewalInfo, isDecodedNotificationDataPayload, isDecodedNotificationSummaryPayload, NotificationType, NotificationSubtype } from "app-store-server-api" // In your webhook handler async function handleNotification(signedPayload: string) { // Decode and verify the notification const payload = await decodeNotificationPayload(signedPayload) console.log({ notificationType: payload.notificationType, subtype: payload.subtype, notificationUUID: payload.notificationUUID, signedDate: new Date(payload.signedDate) }) // Handle data notifications (most notification types) if (isDecodedNotificationDataPayload(payload)) { const data = payload.data console.log(`Bundle ID: ${data.bundleId}`) console.log(`Environment: ${data.environment}`) // Decode the transaction const transaction = await decodeTransaction(data.signedTransactionInfo) console.log(`Product: ${transaction.productId}`) console.log(`Transaction ID: ${transaction.transactionId}`) // For subscription notifications, decode renewal info if (data.signedRenewalInfo) { const renewalInfo = await decodeRenewalInfo(data.signedRenewalInfo) console.log(`Auto-renew product: ${renewalInfo.autoRenewProductId}`) } // Handle specific notification types switch (payload.notificationType) { case NotificationType.Subscribed: if (payload.subtype === NotificationSubtype.InitialBuy) { console.log("New subscription!") } else if (payload.subtype === NotificationSubtype.Resubscribe) { console.log("Resubscribed!") } break case NotificationType.DidRenew: console.log("Subscription renewed") break case NotificationType.Expired: console.log("Subscription expired") break case NotificationType.Refund: console.log("Purchase refunded") break } } // Handle summary notifications (for mass extend renewal date operations) if (isDecodedNotificationSummaryPayload(payload)) { const summary = payload.summary console.log({ requestIdentifier: summary.requestIdentifier, productId: summary.productId, succeededCount: summary.succeededCount, failedCount: summary.failedCount }) } } ```