diff --git a/src/main/java/com/apple/itunes/storekit/client/APIError.java b/src/main/java/com/apple/itunes/storekit/client/APIError.java index 80ad04e6..dc3a84d2 100644 --- a/src/main/java/com/apple/itunes/storekit/client/APIError.java +++ b/src/main/java/com/apple/itunes/storekit/client/APIError.java @@ -496,6 +496,13 @@ public enum APIError { */ MESSAGE_NOT_FOUND(4040015L), + /** + * An error response that indicates an app transaction doesn’t exist for the specified customer. + * + * @see AppTransactionDoesNotExistError + */ + APP_TRANSACTION_DOES_NOT_EXIST_ERROR(4040019L), + /** * An error that indicates the image identifier already exists. * diff --git a/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java b/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java index 588297c8..fc3e458c 100644 --- a/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java +++ b/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java @@ -2,6 +2,7 @@ package com.apple.itunes.storekit.client; +import com.apple.itunes.storekit.model.AppTransactionInfoResponse; import com.apple.itunes.storekit.model.CheckTestNotificationResponse; import com.apple.itunes.storekit.model.ConsumptionRequest; import com.apple.itunes.storekit.model.DefaultConfigurationRequest; @@ -485,6 +486,19 @@ public void deleteDefaultMessage(String productId, String locale) throws APIExce makeHttpCall("/inApps/v1/messaging/default/" + productId + "/" + locale, "DELETE", Map.of(), null, Void.class, null); } + /** + * Get a customer’s app transaction information for your app. + * + * @param transactionId Any originalTransactionId, transactionId or appTransactionId that belongs to the customer for your app. + * @return A response that contains signed app transaction information for a customer. + * @throws APIException If a response was returned indicating the request could not be processed. + * @throws IOException If an exception was thrown while making the request. + * @see Get App Transaction Info + */ + public AppTransactionInfoResponse getAppTransactionInfo(String transactionId) throws APIException, IOException { + return makeHttpCall("/inApps/v1/transactions/appTransactions/" + transactionId, "GET", Map.of(), null, AppTransactionInfoResponse.class, null); + } + protected interface HttpResponseInterface extends Closeable { /** * @return The HTTP status code of the response diff --git a/src/main/java/com/apple/itunes/storekit/model/AppTransactionInfoResponse.java b/src/main/java/com/apple/itunes/storekit/model/AppTransactionInfoResponse.java new file mode 100644 index 00000000..3af17c76 --- /dev/null +++ b/src/main/java/com/apple/itunes/storekit/model/AppTransactionInfoResponse.java @@ -0,0 +1,89 @@ +// Copyright (c) 2025 Apple Inc. Licensed under MIT License. + +package com.apple.itunes.storekit.model; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; +import java.util.Objects; + +/** + * A response that contains signed app transaction information for a customer. + * + * @see AppTransactionInfoResponse + */ +public class AppTransactionInfoResponse { + private static final String SERIALIZED_NAME_SIGNED_APP_TRANSACTION_INFO = "signedAppTransactionInfo"; + + @JsonProperty(SERIALIZED_NAME_SIGNED_APP_TRANSACTION_INFO) + private String signedAppTransactionInfo; + @JsonAnySetter + private Map unknownFields; + + public AppTransactionInfoResponse() { + } + + public AppTransactionInfoResponse signedAppTransactionInfo(String signedAppTransactionInfo) { + this.signedAppTransactionInfo = signedAppTransactionInfo; + return this; + } + + /** + * A customer’s app transaction information, signed by Apple, in JSON Web Signature (JWS) format. + * + * @return signedAppTransactionInfo + * @see JWSAppTransaction + **/ + public String getSignedAppTransactionInfo() { + return signedAppTransactionInfo; + } + + public void setSignedAppTransactionInfo(String signedAppTransactionInfo) { + this.signedAppTransactionInfo = signedAppTransactionInfo; + } + + public AppTransactionInfoResponse unknownFields(Map unknownFields) { + this.unknownFields = unknownFields; + return this; + } + + /** + * Fields that are not recognized for this object. + * + * @return A map of JSON keys to objects. + */ + public Map getUnknownFields() { + return unknownFields; + } + + public void setUnknownFields(Map unknownFields) { + this.unknownFields = unknownFields; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AppTransactionInfoResponse that = (AppTransactionInfoResponse) o; + return Objects.equals(this.signedAppTransactionInfo, that.signedAppTransactionInfo) && + Objects.equals(this.unknownFields, that.unknownFields); + } + + @Override + public int hashCode() { + return Objects.hash(signedAppTransactionInfo, unknownFields); + } + + @Override + public String toString() { + return "AppTransactionInfoResponse{" + + "signedAppTransactionInfo='" + signedAppTransactionInfo + '\'' + + ", unknownFields=" + unknownFields + + '}'; + } +} \ No newline at end of file diff --git a/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java b/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java index 9d4d47a5..ffe55ace 100644 --- a/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java +++ b/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java @@ -3,6 +3,7 @@ package com.apple.itunes.storekit.client; import com.apple.itunes.storekit.model.AccountTenure; +import com.apple.itunes.storekit.model.AppTransactionInfoResponse; import com.apple.itunes.storekit.model.CheckTestNotificationResponse; import com.apple.itunes.storekit.model.ConsumptionRequest; import com.apple.itunes.storekit.model.ConsumptionStatus; @@ -913,6 +914,68 @@ public void testTransactionIdNotOriginalTransactionId() throws IOException { Assertions.fail(); } + @Test + public void testGetAppTransactionInfo() throws APIException, IOException { + AppStoreServerAPIClient client = getClientWithBody("models/appTransactionInfoResponse.json", request -> { + Assertions.assertEquals("GET", request.method()); + Assertions.assertEquals("/inApps/v1/transactions/appTransactions/1234", request.url().encodedPath()); + Assertions.assertNull(request.body()); + }); + + AppTransactionInfoResponse appTransactionInfoResponse = client.getAppTransactionInfo("1234"); + + Assertions.assertNotNull(appTransactionInfoResponse); + Assertions.assertEquals("signed_app_transaction_info_value", appTransactionInfoResponse.getSignedAppTransactionInfo()); + } + + @Test + public void testGetAppTransactionInfoInvalidTransactionIdError() throws IOException { + String body = TestingUtility.readFile("models/invalidTransactionIdError.json"); + AppStoreServerAPIClient client = getAppStoreServerAPIClient(body, request -> {}, 400); + try { + client.getAppTransactionInfo("invalid_transaction_id"); + } catch (APIException e) { + Assertions.assertEquals(400, e.getHttpStatusCode()); + Assertions.assertEquals(APIError.INVALID_TRANSACTION_ID, e.getApiError()); + Assertions.assertEquals(4000006L, e.getRawApiError()); + Assertions.assertEquals("Invalid transaction id.", e.getApiErrorMessage()); + return; + } + Assertions.fail(); + } + + @Test + public void testGetAppTransactionInfoAppTransactionDoesNotExistError() throws IOException { + String body = TestingUtility.readFile("models/appTransactionDoesNotExistError.json"); + AppStoreServerAPIClient client = getAppStoreServerAPIClient(body, request -> {}, 404); + try { + client.getAppTransactionInfo("nonexistent_transaction_id"); + } catch (APIException e) { + Assertions.assertEquals(404, e.getHttpStatusCode()); + Assertions.assertEquals(APIError.APP_TRANSACTION_DOES_NOT_EXIST_ERROR, e.getApiError()); + Assertions.assertEquals(4040019L, e.getRawApiError()); + Assertions.assertEquals("No AppTransaction exists for the customer.", e.getApiErrorMessage()); + return; + } + Assertions.fail(); + } + + @Test + public void testGetAppTransactionInfoTransactionIdNotFoundError() throws IOException { + String body = TestingUtility.readFile("models/transactionIdNotFoundError.json"); + AppStoreServerAPIClient client = getAppStoreServerAPIClient(body, request -> {}, 404); + try { + client.getAppTransactionInfo("not_found_transaction_id"); + } catch (APIException e) { + Assertions.assertEquals(404, e.getHttpStatusCode()); + Assertions.assertEquals(APIError.TRANSACTION_ID_NOT_FOUND, e.getApiError()); + Assertions.assertEquals(4040010L, e.getRawApiError()); + Assertions.assertEquals("Transaction id not found.", e.getApiErrorMessage()); + return; + } + Assertions.fail(); + } + public AppStoreServerAPIClient getClientWithBody(String path, Consumer requestVerifier) throws IOException { String body = TestingUtility.readFile(path); return getAppStoreServerAPIClient(body, requestVerifier); diff --git a/src/test/resources/models/appTransactionDoesNotExistError.json b/src/test/resources/models/appTransactionDoesNotExistError.json new file mode 100644 index 00000000..a5730eae --- /dev/null +++ b/src/test/resources/models/appTransactionDoesNotExistError.json @@ -0,0 +1,4 @@ +{ + "errorCode": 4040019, + "errorMessage": "No AppTransaction exists for the customer." +} \ No newline at end of file diff --git a/src/test/resources/models/appTransactionInfoResponse.json b/src/test/resources/models/appTransactionInfoResponse.json new file mode 100644 index 00000000..6984d671 --- /dev/null +++ b/src/test/resources/models/appTransactionInfoResponse.json @@ -0,0 +1,3 @@ +{ + "signedAppTransactionInfo": "signed_app_transaction_info_value" +} \ No newline at end of file diff --git a/src/test/resources/models/invalidTransactionIdError.json b/src/test/resources/models/invalidTransactionIdError.json new file mode 100644 index 00000000..32fc281a --- /dev/null +++ b/src/test/resources/models/invalidTransactionIdError.json @@ -0,0 +1,4 @@ +{ + "errorCode": 4000006, + "errorMessage": "Invalid transaction id." +} \ No newline at end of file diff --git a/src/test/resources/models/transactionIdNotFoundError.json b/src/test/resources/models/transactionIdNotFoundError.json new file mode 100644 index 00000000..f445639b --- /dev/null +++ b/src/test/resources/models/transactionIdNotFoundError.json @@ -0,0 +1,4 @@ +{ + "errorCode": 4040010, + "errorMessage": "Transaction id not found." +} \ No newline at end of file