diff --git a/index.ts b/index.ts index 551efa5..d0571be 100644 --- a/index.ts +++ b/index.ts @@ -288,11 +288,12 @@ export class AppStoreServerAPIClient { * * @param transactionId The identifier of a transaction that belongs to the customer, and which may be an original transaction identifier. * @param revision A token you provide to get the next set of up to 20 transactions. All responses include a revision token. Note: For requests that use the revision token, include the same query parameters from the initial request. Use the revision token from the previous HistoryResponse. + * @param version The version of the Get Transaction History endpoint to use. V2 is recommended. * @return A response that contains the customer’s transaction history for an app. * @throws APIException If a response was returned indicating the request could not be processed * {@link https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history Get Transaction History} */ - public async getTransactionHistory(transactionId: string, revision: string | null, transactionHistoryRequest: TransactionHistoryRequest): Promise { + public async getTransactionHistory(transactionId: string, revision: string | null, transactionHistoryRequest: TransactionHistoryRequest, version: GetTransactionHistoryVersion = GetTransactionHistoryVersion.V1): Promise { const queryParameters: { [key: string]: string[]} = {} if (revision != null) { queryParameters["revision"] = [revision]; @@ -321,7 +322,7 @@ export class AppStoreServerAPIClient { if (transactionHistoryRequest.revoked !== undefined) { queryParameters["revoked"] = [transactionHistoryRequest.revoked.toString()]; } - return await this.makeRequest("/inApps/v1/history/" + transactionId, "GET", queryParameters, null, new HistoryResponseValidator()); + return await this.makeRequest("/inApps/" + version + "/history/" + transactionId, "GET", queryParameters, null, new HistoryResponseValidator()); } /** @@ -794,4 +795,12 @@ export enum APIError { * {@link https://developer.apple.com/documentation/appstoreserverapi/generalinternalretryableerror GeneralInternalRetryableError} */ GENERAL_INTERNAL_RETRYABLE = 5000001, -} \ No newline at end of file +} + +export enum GetTransactionHistoryVersion { + /** + * @deprecated + */ + V1 = "v1", + V2 = "v2", +} diff --git a/models/JWSRenewalInfoDecodedPayload.ts b/models/JWSRenewalInfoDecodedPayload.ts index af9e552..878217f 100644 --- a/models/JWSRenewalInfoDecodedPayload.ts +++ b/models/JWSRenewalInfoDecodedPayload.ts @@ -4,6 +4,7 @@ import { AutoRenewStatus, AutoRenewStatusValidator } from "./AutoRenewStatus" import { DecodedSignedData } from "./DecodedSignedData" import { Environment, EnvironmentValidator } from "./Environment" import { ExpirationIntent, ExpirationIntentValidator } from "./ExpirationIntent" +import { OfferDiscountType, OfferDiscountTypeValidator } from "./OfferDiscountType" import { OfferType, OfferTypeValidator } from "./OfferType" import { PriceIncreaseStatus, PriceIncreaseStatusValidator } from "./PriceIncreaseStatus" import { Validator } from "./Validator" @@ -112,6 +113,27 @@ export interface JWSRenewalInfoDecodedPayload extends DecodedSignedData { * {@link https://developer.apple.com/documentation/appstoreserverapi/renewaldate renewalDate} **/ renewalDate?: number + + /** + * The currency code for the renewalPrice of the subscription. + * + * {@link https://developer.apple.com/documentation/appstoreserverapi/currency currency} + **/ + currency?: string + + /** + * The renewal price, in milliunits, of the auto-renewable subscription that renews at the next billing period. + * + * {@link https://developer.apple.com/documentation/appstoreserverapi/renewalprice renewalPrice} + **/ + renewalPrice?: number + + /** + * The payment mode of the discount offer. + * + * {@link https://developer.apple.com/documentation/appstoreserverapi/offerdiscounttype offerDiscountType} + **/ + offerDiscountType?: OfferDiscountType | string } @@ -121,6 +143,7 @@ export class JWSRenewalInfoDecodedPayloadValidator implements Validator { expect(expectedNotificationHistory).toStrictEqual(notificationHistoryResponse.notificationHistory) }) - it('calls getTransactionHistory', async () => { + it('calls getTransactionHistory V1', async () => { const client = getClientWithBody("tests/resources/models/transactionHistoryResponse.json", (path: string, parsedQueryParameters: URLSearchParams, method: string, stringBody: string | undefined, headers: { [key: string]: string; }) => { expect("GET").toBe(method) expect("/inApps/v1/history/1234").toBe(path) @@ -315,7 +315,7 @@ describe('The api client ', () => { subscriptionGroupIdentifiers: ["sub_group_id", "sub_group_id_2"] } - const historyResponse = await client.getTransactionHistory("1234", "revision_input", request); + const historyResponse = await client.getTransactionHistory("1234", "revision_input", request, GetTransactionHistoryVersion.V1); expect(historyResponse).toBeTruthy() expect("revision_output").toBe(historyResponse.revision) @@ -326,6 +326,44 @@ describe('The api client ', () => { expect(["signed_transaction_value", "signed_transaction_value2"]).toStrictEqual(historyResponse.signedTransactions) }) + it('calls getTransactionHistory V2', async () => { + const client = getClientWithBody("tests/resources/models/transactionHistoryResponse.json", (path: string, parsedQueryParameters: URLSearchParams, method: string, stringBody: string | undefined, headers: { [key: string]: string; }) => { + expect("GET").toBe(method) + expect("/inApps/v2/history/1234").toBe(path) + expect("revision_input").toBe(parsedQueryParameters.get("revision")) + expect("123455").toBe(parsedQueryParameters.get("startDate")) + expect("123456").toBe(parsedQueryParameters.get("endDate")) + expect(["com.example.1", "com.example.2"]).toStrictEqual(parsedQueryParameters.getAll("productId")) + expect(["CONSUMABLE", "AUTO_RENEWABLE"]).toStrictEqual(parsedQueryParameters.getAll("productType")) + expect("ASCENDING").toBe(parsedQueryParameters.get("sort")) + expect(["sub_group_id", "sub_group_id_2"]).toStrictEqual(parsedQueryParameters.getAll("subscriptionGroupIdentifier")) + expect("FAMILY_SHARED").toBe(parsedQueryParameters.get("inAppOwnershipType")) + expect("false").toBe(parsedQueryParameters.get("revoked")) + expect(stringBody).toBeUndefined() + }); + + const request: TransactionHistoryRequest = { + sort: Order.ASCENDING, + productTypes: [ProductType.CONSUMABLE, ProductType.AUTO_RENEWABLE], + endDate: 123456, + startDate: 123455, + revoked: false, + inAppOwnershipType: InAppOwnershipType.FAMILY_SHARED, + productIds: ["com.example.1", "com.example.2"], + subscriptionGroupIdentifiers: ["sub_group_id", "sub_group_id_2"] + } + + const historyResponse = await client.getTransactionHistory("1234", "revision_input", request, GetTransactionHistoryVersion.V2); + + expect(historyResponse).toBeTruthy() + expect("revision_output").toBe(historyResponse.revision) + expect(historyResponse.hasMore).toBe(true) + expect("com.example").toBe(historyResponse.bundleId) + expect(323232).toBe(historyResponse.appAppleId) + expect(Environment.LOCAL_TESTING).toBe(historyResponse.environment) + expect(["signed_transaction_value", "signed_transaction_value2"]).toStrictEqual(historyResponse.signedTransactions) + }) + it('calls getTransactionInfo', async () => { const client = getClientWithBody("tests/resources/models/transactionInfoResponse.json", (path: string, parsedQueryParameters: URLSearchParams, method: string, stringBody: string | undefined, headers: { [key: string]: string; }) => { expect("GET").toBe(method) @@ -481,7 +519,7 @@ describe('The api client ', () => { subscriptionGroupIdentifiers: ["sub_group_id", "sub_group_id_2"] } - const historyResponse = await client.getTransactionHistory("1234", "revision_input", request); + const historyResponse = await client.getTransactionHistory("1234", "revision_input", request, GetTransactionHistoryVersion.V2); expect(historyResponse.environment).toBe("LocalTestingxxx") }) diff --git a/tests/unit-tests/transaction_decoding.test.ts b/tests/unit-tests/transaction_decoding.test.ts index a522e79..90a6b60 100644 --- a/tests/unit-tests/transaction_decoding.test.ts +++ b/tests/unit-tests/transaction_decoding.test.ts @@ -14,6 +14,7 @@ import { RevocationReason } from "../../models/RevocationReason"; import { TransactionReason } from "../../models/TransactionReason"; import { Type } from "../../models/Type"; import { ConsumptionRequestReason } from "../../models/ConsumptionRequestReason"; +import { OfferDiscountType } from "../../models/OfferDiscountType"; describe('Testing decoding of signed data', () => { @@ -53,6 +54,9 @@ describe('Testing decoding of signed data', () => { expect(Environment.LOCAL_TESTING).toBe(renewalInfo.environment) expect(1698148800000).toBe(renewalInfo.recentSubscriptionStartDate) expect(1698148850000).toBe(renewalInfo.renewalDate) + expect(9990).toBe(renewalInfo.renewalPrice) + expect("USD").toBe(renewalInfo.currency) + expect(OfferDiscountType.PAY_AS_YOU_GO).toBe(renewalInfo.offerDiscountType) }) it('should decode a transaction info', async () => { const signedTransaction = createSignedDataFromJson("tests/resources/models/signedTransaction.json") @@ -82,6 +86,9 @@ describe('Testing decoding of signed data', () => { expect("143441").toBe(transaction.storefrontId) expect(TransactionReason.PURCHASE).toBe(transaction.transactionReason) expect(Environment.LOCAL_TESTING).toBe(transaction.environment) + expect(10990).toBe(transaction.price) + expect("USD").toBe(transaction.currency) + expect(OfferDiscountType.PAY_AS_YOU_GO).toBe(transaction.offerDiscountType) }) it('should decode a signed notification', async () => { const signedNotification = createSignedDataFromJson("tests/resources/models/signedNotification.json")