Skip to content

Commit 37d3880

Browse files
Merge pull request #89 from riyazpanjwani/main
Add support for Set App Account Token endpoint
2 parents 5bd4298 + b9caf35 commit 37d3880

File tree

6 files changed

+144
-0
lines changed

6 files changed

+144
-0
lines changed

Sources/AppStoreServerLibrary/AppStoreServerAPIClient.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,16 @@ public class AppStoreServerAPIClient {
325325
public func sendConsumptionData(transactionId: String, consumptionRequest: ConsumptionRequest) async -> APIResult<Void> {
326326
return await makeRequestWithoutResponseBody(path: "/inApps/v1/transactions/consumption/" + transactionId, method: .PUT, queryParameters: [:], body: consumptionRequest)
327327
}
328+
329+
///Sets the app account token value for a purchase the customer makes outside your app, or updates its value in an existing transaction.
330+
///
331+
///- Parameter originalTransactionId: The original transaction identifier of the transaction to receive the app account token update.
332+
///- Parameter updateAppAccountTokenRequest : The request body that contains a valid app account token value.
333+
///- Returns: Success, or information about the failure
334+
///[Set App Account Token](https://developer.apple.com/documentation/appstoreserverapi/set-app-account-token)
335+
public func setAppAccountToken(originalTransactionId: String, updateAppAccountTokenRequest: UpdateAppAccountTokenRequest) async -> APIResult<Void> {
336+
return await makeRequestWithoutResponseBody(path: "/inApps/v1/transactions/" + originalTransactionId + "/appAccountToken", method: .PUT, queryParameters: [:], body: updateAppAccountTokenRequest)
337+
}
328338

329339
internal struct AppStoreServerAPIJWT: JWTPayload, Equatable {
330340
var exp: ExpirationClaim
@@ -552,6 +562,21 @@ public enum APIError: Int64 {
552562
///[AppTransactionIdNotSupportedError](https://developer.apple.com/documentation/appstoreserverapi/apptransactionidnotsupportederror)
553563
case appTransactionIdNotSupported = 4000048
554564

565+
///An error that indicates the app account token value is not a valid UUID.
566+
///
567+
///[InvalidAppAccountTokenUUIDError](https://developer.apple.com/documentation/appstoreserverapi/invalidappaccounttokenuuiderror)
568+
case invalidAppAccountTokenUUID = 4000183
569+
570+
///An error that indicates the transaction is for a product the customer obtains through Family Sharing, which the endpoint doesn’t support.
571+
///
572+
///[FamilyTransactionNotSupportedError](https://developer.apple.com/documentation/appstoreserverapi/familytransactionnotsupportederror)
573+
case familyTransactionNotSupported = 4000185
574+
575+
///An error that indicates the endpoint expects an original transaction identifier.
576+
///
577+
///[TransactionIdIsNotOriginalTransactionIdError](https://developer.apple.com/documentation/appstoreserverapi/transactionidisnotoriginaltransactioniderror)
578+
case transactionIdNotOriginalTransactionId = 4000187
579+
555580
///An error that indicates the subscription doesn't qualify for a renewal-date extension due to its subscription state.
556581
///
557582
///[SubscriptionExtensionIneligibleError](https://developer.apple.com/documentation/appstoreserverapi/subscriptionextensionineligibleerror)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2025 Apple Inc. Licensed under MIT License.
2+
3+
import Foundation
4+
5+
///The request body that contains an app account token value.
6+
///
7+
///[UpdateAppAccountTokenRequest](https://developer.apple.com/documentation/appstoreserverapi/updateappaccounttokenrequest)
8+
public struct UpdateAppAccountTokenRequest: Decodable, Encodable, Hashable, Sendable {
9+
///The UUID that an app optionally generates to map a customer’s in-app purchase with its resulting App Store transaction.
10+
///
11+
///[appAccountToken](https://developer.apple.com/documentation/appstoreserverapi/appaccounttoken)
12+
public let appAccountToken: UUID
13+
14+
public init(appAccountToken: UUID) {
15+
self.appAccountToken = appAccountToken
16+
}
17+
18+
public enum CodingKeys: CodingKey {
19+
case appAccountToken
20+
}
21+
22+
public init (from decoder: any Decoder) throws {
23+
let container = try decoder.container(keyedBy: CodingKeys.self)
24+
self.appAccountToken = try container.decode(UUID.self, forKey: .appAccountToken)
25+
}
26+
27+
public func encode(to encoder: any Encoder) throws {
28+
var container = encoder.container(keyedBy: CodingKeys.self)
29+
try container.encode(self.appAccountToken, forKey: .appAccountToken)
30+
}
31+
}

Tests/AppStoreServerLibraryTests/AppStoreServerAPIClientTests.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,82 @@ final class AppStoreServerAPIClientTests: XCTestCase {
634634
XCTAssertEqual(AppStoreServerAPIClient.ConfigurationError.invalidEnvironment, e as! AppStoreServerAPIClient.ConfigurationError)
635635
}
636636
}
637+
638+
public func testSetAppAccountToken() async throws {
639+
let client = try getAppStoreServerAPIClient("") { request, body in
640+
XCTAssertEqual(.PUT, request.method)
641+
XCTAssertEqual("https://local-testing-base-url/inApps/v1/transactions/49571273/appAccountToken", request.url)
642+
XCTAssertEqual(["application/json"], request.headers["Content-Type"])
643+
let decodedJson = try! JSONSerialization.jsonObject(with: body!) as! [String: Any]
644+
XCTAssertEqual("7389A31A-FB6D-4569-A2A6-DB7D85D84813", decodedJson["appAccountToken"] as! String)
645+
}
646+
647+
let updateAppAccountTokenRequest = UpdateAppAccountTokenRequest(
648+
appAccountToken: UUID(uuidString: "7389a31a-fb6d-4569-a2a6-db7d85d84813")!
649+
)
650+
TestingUtility.confirmCodableInternallyConsistent(updateAppAccountTokenRequest)
651+
652+
let response = await client.setAppAccountToken(originalTransactionId: "49571273", updateAppAccountTokenRequest: updateAppAccountTokenRequest)
653+
guard case .success(_) = response else {
654+
XCTAssertTrue(false)
655+
return
656+
}
657+
}
658+
659+
660+
public func testInvalidAppAccountTokenUUIDError() async throws {
661+
let body = TestingUtility.readFile("resources/models/invalidAppAccountTokenUUIDError.json")
662+
let client = try getAppStoreServerAPIClient(body, .badRequest, nil)
663+
let updateAppAccountTokenRequest = UpdateAppAccountTokenRequest(
664+
appAccountToken: UUID(uuidString: "7389a31a-fb6d-4569-a2a6-db7d85d84813")!
665+
)
666+
let result = await client.setAppAccountToken(originalTransactionId: "1234", updateAppAccountTokenRequest: updateAppAccountTokenRequest)
667+
guard case .failure(let statusCode, let rawApiError, let apiError, let errorMessage, let causedBy) = result else {
668+
XCTAssertTrue(false)
669+
return
670+
}
671+
XCTAssertEqual(400, statusCode)
672+
XCTAssertNotNil(apiError)
673+
XCTAssertEqual(4000183, rawApiError)
674+
XCTAssertEqual("Invalid request. The app account token field must be a valid UUID.", errorMessage)
675+
XCTAssertNil(causedBy)
676+
}
677+
678+
public func testFamilySharedTransactionNotSupportedError() async throws {
679+
let body = TestingUtility.readFile("resources/models/familyTransactionNotSupportedError.json")
680+
let client = try getAppStoreServerAPIClient(body, .badRequest, nil)
681+
let updateAppAccountTokenRequest = UpdateAppAccountTokenRequest(
682+
appAccountToken: UUID(uuidString: "7389a31a-fb6d-4569-a2a6-db7d85d84813")!
683+
)
684+
let result = await client.setAppAccountToken(originalTransactionId: "1234", updateAppAccountTokenRequest: updateAppAccountTokenRequest)
685+
guard case .failure(let statusCode, let rawApiError, let apiError, let errorMessage, let causedBy) = result else {
686+
XCTAssertTrue(false)
687+
return
688+
}
689+
XCTAssertEqual(400, statusCode)
690+
XCTAssertNotNil(apiError)
691+
XCTAssertEqual(4000185, rawApiError)
692+
XCTAssertEqual("Invalid request. Family Sharing transactions aren't supported by this endpoint.", errorMessage)
693+
XCTAssertNil(causedBy)
694+
}
695+
696+
public func testTransactionIdNotOriginalTransactionIdError() async throws {
697+
let body = TestingUtility.readFile("resources/models/transactionIdNotOriginalTransactionId.json")
698+
let client = try getAppStoreServerAPIClient(body, .badRequest, nil)
699+
let updateAppAccountTokenRequest = UpdateAppAccountTokenRequest(
700+
appAccountToken: UUID(uuidString: "7389a31a-fb6d-4569-a2a6-db7d85d84813")!
701+
)
702+
let result = await client.setAppAccountToken(originalTransactionId: "1234", updateAppAccountTokenRequest: updateAppAccountTokenRequest)
703+
guard case .failure(let statusCode, let rawApiError, let apiError, let errorMessage, let causedBy) = result else {
704+
XCTAssertTrue(false)
705+
return
706+
}
707+
XCTAssertEqual(400, statusCode)
708+
XCTAssertNotNil(apiError)
709+
XCTAssertEqual(4000187, rawApiError)
710+
XCTAssertEqual("Invalid request. The transaction ID provided is not an original transaction ID.", errorMessage)
711+
XCTAssertNil(causedBy)
712+
}
637713

638714
public func getClientWithBody(_ path: String, _ requestVerifier: @escaping RequestVerifier) throws -> AppStoreServerAPIClient {
639715
let body = TestingUtility.readFile(path)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"errorCode": 4000185,
3+
"errorMessage": "Invalid request. Family Sharing transactions aren't supported by this endpoint."
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"errorCode": 4000183,
3+
"errorMessage": "Invalid request. The app account token field must be a valid UUID."
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"errorCode": 4000187,
3+
"errorMessage": "Invalid request. The transaction ID provided is not an original transaction ID."
4+
}

0 commit comments

Comments
 (0)