Skip to content

Commit c14c783

Browse files
Merge pull request #344 from riyazpanjwani/main
Adding support for Get App Transaction Info endpoint
2 parents 6c5b8ad + d6059e9 commit c14c783

File tree

7 files changed

+133
-0
lines changed

7 files changed

+133
-0
lines changed

index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export { SignedDataVerifier, VerificationException, VerificationStatus } from '.
2727
export { ReceiptUtility } from './receipt_utility'
2828
export { AccountTenure } from "./models/AccountTenure"
2929
export { AlternateProduct } from './models/AlternateProduct'
30+
export { AppTransactionInfoResponse } from './models/AppTransactionInfoResponse';
3031
export { AutoRenewStatus } from './models/AutoRenewStatus'
3132
export { CheckTestNotificationResponse } from './models/CheckTestNotificationResponse'
3233
export { ConsumptionRequest } from './models/ConsumptionRequest'
@@ -100,6 +101,7 @@ export { AppTransaction } from './models/AppTransaction'
100101
export { ExternalPurchaseToken } from './models/ExternalPurchaseToken'
101102

102103
import jsonwebtoken = require('jsonwebtoken');
104+
import { AppTransactionInfoResponse, AppTransactionInfoResponseValidator } from './models/AppTransactionInfoResponse';
103105
import { NotificationHistoryRequest } from './models/NotificationHistoryRequest';
104106
import { NotificationHistoryResponse, NotificationHistoryResponseValidator } from './models/NotificationHistoryResponse';
105107
import { URLSearchParams } from 'url';
@@ -508,6 +510,18 @@ export class AppStoreServerAPIClient {
508510
await this.makeRequest("/inApps/v1/messaging/default/" + productId + "/" + locale, "DELETE", {}, null, null, undefined);
509511
}
510512

513+
/**
514+
* Get a customer's app transaction information for your app.
515+
*
516+
* @param transactionId Any originalTransactionId, transactionId or appTransactionId that belongs to the customer for your app.
517+
* @return A response that contains signed app transaction information for a customer.
518+
* @throws APIException If a response was returned indicating the request could not be processed
519+
* {@link https://developer.apple.com/documentation/appstoreserverapi/get-app-transaction-info Get App Transaction Info}
520+
*/
521+
public async getAppTransactionInfo(transactionId: string): Promise<AppTransactionInfoResponse> {
522+
return await this.makeRequest("/inApps/v1/transactions/appTransactions/" + transactionId, "GET", {}, null, new AppTransactionInfoResponseValidator(), undefined);
523+
}
524+
511525
private createBearerToken(): string {
512526
const payload = {
513527
bid: this.bundleId
@@ -1023,6 +1037,13 @@ export enum APIError {
10231037
*/
10241038
MESSAGE_NOT_FOUND = 4040015,
10251039

1040+
/**
1041+
* An error response that indicates an app transaction doesn’t exist for the specified customer.
1042+
*
1043+
* {@link https://developer.apple.com/documentation/appstoreserverapi/apptransactiondoesnotexisterror AppTransactionDoesNotExistError}
1044+
*/
1045+
APP_TRANSACTION_DOES_NOT_EXIST_ERROR = 4040019,
1046+
10261047
/**
10271048
* An error that indicates the image identifier already exists.
10281049
*
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2025 Apple Inc. Licensed under MIT License.
2+
3+
import { Validator } from "./Validator"
4+
5+
/**
6+
* A response that contains signed app transaction information for a customer.
7+
*
8+
* {@link https://developer.apple.com/documentation/appstoreserverapi/apptransactioninforesponse AppTransactionInfoResponse}
9+
*/
10+
export interface AppTransactionInfoResponse {
11+
/**
12+
* A customer’s app transaction information, signed by Apple, in JSON Web Signature (JWS) format.
13+
*
14+
* {@link https://developer.apple.com/documentation/appstoreserverapi/jwsapptransaction JWSAppTransaction}
15+
**/
16+
signedAppTransactionInfo?: string
17+
}
18+
19+
export class AppTransactionInfoResponseValidator implements Validator<AppTransactionInfoResponse> {
20+
validate(obj: any): obj is AppTransactionInfoResponse {
21+
if ((typeof obj['signedAppTransactionInfo'] !== 'undefined') && !(typeof obj['signedAppTransactionInfo'] === "string" || obj['signedAppTransactionInfo'] instanceof String)) {
22+
return false
23+
}
24+
return true
25+
}
26+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"errorCode": 4040019,
3+
"errorMessage": "No AppTransaction exists for the customer."
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"signedAppTransactionInfo": "signed_app_transaction_info_value"
3+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"errorCode": 4000006,
3+
"errorMessage": "Invalid transaction id."
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"errorCode": 4040010,
3+
"errorMessage": "Transaction id not found."
4+
}

tests/unit-tests/api_client.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,4 +801,75 @@ describe('The api client ', () => {
801801

802802
await client.deleteDefaultMessage("com.example.product", "en-US")
803803
})
804+
805+
it('calls getAppTransactionInfo', async () => {
806+
const client = getClientWithBody("tests/resources/models/appTransactionInfoResponse.json", (path: string, parsedQueryParameters: URLSearchParams, method: string, requestBody: string | Buffer | undefined, headers: { [key: string]: string; }) => {
807+
expect("GET").toBe(method)
808+
expect("/inApps/v1/transactions/appTransactions/1234").toBe(path)
809+
expect(parsedQueryParameters.entries.length).toBe(0)
810+
expect(requestBody).toBeUndefined()
811+
});
812+
813+
const appTransactionInfoResponse = await client.getAppTransactionInfo("1234");
814+
815+
expect(appTransactionInfoResponse).toBeTruthy()
816+
expect("signed_app_transaction_info_value").toBe(appTransactionInfoResponse.signedAppTransactionInfo)
817+
})
818+
819+
it('calls getAppTransactionInfo but receives invalid transaction id error', async () => {
820+
const client = getClientWithBody("tests/resources/models/invalidTransactionIdError.json", (path: string, parsedQueryParameters: URLSearchParams, method: string, requestBody: string | Buffer | undefined, headers: { [key: string]: string; }) => {
821+
expect("GET").toBe(method)
822+
expect("/inApps/v1/transactions/appTransactions/invalid_id").toBe(path)
823+
expect(parsedQueryParameters.entries.length).toBe(0)
824+
expect(requestBody).toBeUndefined()
825+
}, 400);
826+
827+
try {
828+
const appTransactionInfoResponse = await client.getAppTransactionInfo("invalid_id");
829+
fail('this test call is expected to throw')
830+
} catch (e) {
831+
let error = e as APIException
832+
expect(error.httpStatusCode).toBe(400)
833+
expect(error.apiError).toBe(APIError.INVALID_TRANSACTION_ID)
834+
expect(error.errorMessage).toBe("Invalid transaction id.")
835+
}
836+
})
837+
838+
it('calls getAppTransactionInfo but receives app transaction does not exist error', async () => {
839+
const client = getClientWithBody("tests/resources/models/appTransactionDoesNotExistError.json", (path: string, parsedQueryParameters: URLSearchParams, method: string, requestBody: string | Buffer | undefined, headers: { [key: string]: string; }) => {
840+
expect("GET").toBe(method)
841+
expect("/inApps/v1/transactions/appTransactions/5678").toBe(path)
842+
expect(parsedQueryParameters.entries.length).toBe(0)
843+
expect(requestBody).toBeUndefined()
844+
}, 404);
845+
846+
try {
847+
const appTransactionInfoResponse = await client.getAppTransactionInfo("5678");
848+
fail('this test call is expected to throw')
849+
} catch (e) {
850+
let error = e as APIException
851+
expect(error.httpStatusCode).toBe(404)
852+
expect(error.apiError).toBe(APIError.APP_TRANSACTION_DOES_NOT_EXIST_ERROR)
853+
expect(error.errorMessage).toBe("No AppTransaction exists for the customer.")
854+
}
855+
})
856+
857+
it('calls getAppTransactionInfo but receives transaction id not found error', async () => {
858+
const client = getClientWithBody("tests/resources/models/transactionIdNotFoundError.json", (path: string, parsedQueryParameters: URLSearchParams, method: string, requestBody: string | Buffer | undefined, headers: { [key: string]: string; }) => {
859+
expect("GET").toBe(method)
860+
expect("/inApps/v1/transactions/appTransactions/9999").toBe(path)
861+
expect(parsedQueryParameters.entries.length).toBe(0)
862+
expect(requestBody).toBeUndefined()
863+
}, 404);
864+
865+
try {
866+
const appTransactionInfoResponse = await client.getAppTransactionInfo("9999");
867+
fail('this test call is expected to throw')
868+
} catch (e) {
869+
let error = e as APIException
870+
expect(error.httpStatusCode).toBe(404)
871+
expect(error.apiError).toBe(APIError.TRANSACTION_ID_NOT_FOUND)
872+
expect(error.errorMessage).toBe("Transaction id not found.")
873+
}
874+
})
804875
})

0 commit comments

Comments
 (0)