Skip to content

Commit 00b705e

Browse files
author
Riyaz Panjwani
committed
Add support for Set App Account Token endpoint
1 parent 5bd4298 commit 00b705e

File tree

5 files changed

+129
-0
lines changed

5 files changed

+129
-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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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: String, CodingKey {
19+
case appAccountToken
20+
}
21+
22+
public init (from decoder: any Decoder) throws {
23+
let container = try decoder.container(keyedBy: CodingKeys.self)
24+
let appAccountTokenString = try container.decode(String.self, forKey: .appAccountToken)
25+
// Attempt to create a UUID from the decoded string.
26+
// If the string is not a valid UUID, this will cause a decoding error.
27+
guard let uuid = UUID(uuidString: appAccountTokenString) else {
28+
throw DecodingError.dataCorruptedError(forKey: .appAccountToken,
29+
in: container,
30+
debugDescription: "Expected a valid UUID string for appAccountToken.")
31+
}
32+
self.appAccountToken = uuid
33+
}
34+
35+
public func encode(to encoder: any Encoder) throws {
36+
var container = encoder.container(keyedBy: CodingKeys.self)
37+
try container.encode(self.appAccountToken.uuidString, forKey: .appAccountToken)
38+
}
39+
}

Tests/AppStoreServerLibraryTests/AppStoreServerAPIClientTests.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,63 @@ 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+
public func testFamilySharedTransactionNotSupportedError() async throws {
660+
let body = TestingUtility.readFile("resources/models/familyTransactionNotSupportedError.json")
661+
let client = try getAppStoreServerAPIClient(body, .badRequest, nil)
662+
let updateAppAccountTokenRequest = UpdateAppAccountTokenRequest(
663+
appAccountToken: UUID(uuidString: "7389a31a-fb6d-4569-a2a6-db7d85d84813")
664+
)
665+
let result = await client.setAppAccountToken(originalTransactionId: "1234", updateAppAccountTokenRequest: updateAppAccountTokenRequest)
666+
guard case .failure(let statusCode, let rawApiError, let apiError, let errorMessage, let causedBy) = result else {
667+
XCTAssertTrue(false)
668+
return
669+
}
670+
XCTAssertEqual(400, statusCode)
671+
XCTAssertNil(apiError)
672+
XCTAssertEqual(4000185, rawApiError)
673+
XCTAssertEqual("Invalid request. Family Sharing transactions aren't supported by this endpoint.", errorMessage)
674+
XCTAssertNil(causedBy)
675+
}
676+
677+
public func testTransactionIdNotOriginalTransactionIdError() async throws {
678+
let body = TestingUtility.readFile("resources/models/transactionIdNotOriginalTransactionId.json")
679+
let client = try getAppStoreServerAPIClient(body, .badRequest, nil)
680+
let updateAppAccountTokenRequest = UpdateAppAccountTokenRequest(
681+
appAccountToken: UUID(uuidString: "7389a31a-fb6d-4569-a2a6-db7d85d84813")
682+
)
683+
let result = await client.setAppAccountToken(originalTransactionId: "1234", updateAppAccountTokenRequest: updateAppAccountTokenRequest)
684+
guard case .failure(let statusCode, let rawApiError, let apiError, let errorMessage, let causedBy) = result else {
685+
XCTAssertTrue(false)
686+
return
687+
}
688+
XCTAssertEqual(400, statusCode)
689+
XCTAssertNil(apiError)
690+
XCTAssertEqual(4000187, rawApiError)
691+
XCTAssertEqual("Invalid request. The transaction ID provided is not an original transaction ID.", errorMessage)
692+
XCTAssertNil(causedBy)
693+
}
637694

638695
public func getClientWithBody(_ path: String, _ requestVerifier: @escaping RequestVerifier) throws -> AppStoreServerAPIClient {
639696
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": 4000187,
3+
"errorMessage": "Invalid request. The transaction ID provided is not an original transaction ID."
4+
}

0 commit comments

Comments
 (0)