diff --git a/src/main/java/com/apple/itunes/storekit/client/AppStoreServerAPIClient.java b/src/main/java/com/apple/itunes/storekit/client/AppStoreServerAPIClient.java
index 881c9b6a..f566cadd 100644
--- a/src/main/java/com/apple/itunes/storekit/client/AppStoreServerAPIClient.java
+++ b/src/main/java/com/apple/itunes/storekit/client/AppStoreServerAPIClient.java
@@ -286,7 +286,7 @@ public HistoryResponse getTransactionHistory(String transactionId, String revisi
* @see Get Transaction Info
*/
public TransactionInfoResponse getTransactionInfo(String transactionId) throws APIException, IOException {
- return makeHttpCall("inApps/v1/transactions/" + transactionId, "GET", Map.of(), null, TransactionInfoResponse.class);
+ return makeHttpCall("/inApps/v1/transactions/" + transactionId, "GET", Map.of(), null, TransactionInfoResponse.class);
}
/**
diff --git a/src/main/java/com/apple/itunes/storekit/model/StatusResponse.java b/src/main/java/com/apple/itunes/storekit/model/StatusResponse.java
index 772106cc..00cce787 100644
--- a/src/main/java/com/apple/itunes/storekit/model/StatusResponse.java
+++ b/src/main/java/com/apple/itunes/storekit/model/StatusResponse.java
@@ -19,7 +19,7 @@ public class StatusResponse {
private static final String SERIALIZED_NAME_APP_APPLE_ID = "appAppleId";
private static final String SERIALIZED_NAME_DATA = "data";
@SerializedName(SERIALIZED_NAME_ENVIRONMENT)
- private Environment environment;
+ private String environment;
@SerializedName(SERIALIZED_NAME_BUNDLE_ID)
private String bundleId;
@SerializedName(SERIALIZED_NAME_APP_APPLE_ID)
@@ -32,7 +32,7 @@ public StatusResponse() {
}
public StatusResponse environment(Environment environment) {
- this.environment = environment;
+ this.environment = environment != null ? environment.getValue() : null;
return this;
}
@@ -43,11 +43,22 @@ public StatusResponse environment(Environment environment) {
* @see environment
**/
public Environment getEnvironment() {
+ return environment != null ? Environment.fromValue(environment) : null;
+ }
+
+ /**
+ * @see #getEnvironment()
+ */
+ public String getRawEnvironment() {
return environment;
}
public void setEnvironment(Environment environment) {
- this.environment = environment;
+ this.environment = environment != null ? environment.getValue() : null;
+ }
+
+ public void setRawEnvironment(String rawEnvironment) {
+ this.environment = rawEnvironment;
}
public StatusResponse bundleId(String bundleId) {
diff --git a/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java b/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java
new file mode 100644
index 00000000..c4fff965
--- /dev/null
+++ b/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java
@@ -0,0 +1,503 @@
+// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.client;
+
+import com.apple.itunes.storekit.model.AccountTenure;
+import com.apple.itunes.storekit.model.CheckTestNotificationResponse;
+import com.apple.itunes.storekit.model.ConsumptionRequest;
+import com.apple.itunes.storekit.model.ConsumptionStatus;
+import com.apple.itunes.storekit.model.DeliveryStatus;
+import com.apple.itunes.storekit.model.Environment;
+import com.apple.itunes.storekit.model.ExtendReasonCode;
+import com.apple.itunes.storekit.model.ExtendRenewalDateRequest;
+import com.apple.itunes.storekit.model.ExtendRenewalDateResponse;
+import com.apple.itunes.storekit.model.HistoryResponse;
+import com.apple.itunes.storekit.model.InAppOwnershipType;
+import com.apple.itunes.storekit.model.LastTransactionsItem;
+import com.apple.itunes.storekit.model.LifetimeDollarsPurchased;
+import com.apple.itunes.storekit.model.LifetimeDollarsRefunded;
+import com.apple.itunes.storekit.model.MassExtendRenewalDateRequest;
+import com.apple.itunes.storekit.model.MassExtendRenewalDateResponse;
+import com.apple.itunes.storekit.model.MassExtendRenewalDateStatusResponse;
+import com.apple.itunes.storekit.model.NotificationHistoryRequest;
+import com.apple.itunes.storekit.model.NotificationHistoryResponse;
+import com.apple.itunes.storekit.model.NotificationHistoryResponseItem;
+import com.apple.itunes.storekit.model.NotificationTypeV2;
+import com.apple.itunes.storekit.model.OrderLookupResponse;
+import com.apple.itunes.storekit.model.OrderLookupStatus;
+import com.apple.itunes.storekit.model.Platform;
+import com.apple.itunes.storekit.model.PlayTime;
+import com.apple.itunes.storekit.model.RefundHistoryResponse;
+import com.apple.itunes.storekit.model.SendAttemptItem;
+import com.apple.itunes.storekit.model.SendAttemptResult;
+import com.apple.itunes.storekit.model.SendTestNotificationResponse;
+import com.apple.itunes.storekit.model.Status;
+import com.apple.itunes.storekit.model.StatusResponse;
+import com.apple.itunes.storekit.model.SubscriptionGroupIdentifierItem;
+import com.apple.itunes.storekit.model.Subtype;
+import com.apple.itunes.storekit.model.TransactionHistoryRequest;
+import com.apple.itunes.storekit.model.TransactionInfoResponse;
+import com.apple.itunes.storekit.model.UserStatus;
+import com.apple.itunes.storekit.util.TestingUtility;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.google.gson.Gson;
+import okhttp3.MediaType;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class AppStoreServerAPIClientTest {
+
+ private final MediaType expectedMediaType = MediaType.parse("application/json; charset=utf-8");
+
+ @Test
+ public void testExtendRenewalDateForAllActiveSubscribers() throws IOException, APIException {
+ AppStoreServerAPIClient client = getClientWithBody("models/extendRenewalDateForAllActiveSubscribersResponse.json", request -> {
+ Assertions.assertEquals("POST", request.method());
+ Assertions.assertEquals("/inApps/v1/subscriptions/extend/mass", request.url().encodedPath());
+ RequestBody body = request.body();
+ Assertions.assertNotNull(body);
+ Assertions.assertEquals(expectedMediaType, body.contentType());
+ Buffer buffer = new Buffer();
+ try {
+ body.writeTo(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ Map root = new Gson().fromJson(buffer.readUtf8(), Map.class);
+ Assertions.assertEquals(45, ((Number) root.get("extendByDays")).intValue());
+ Assertions.assertEquals(1, ((Number) root.get("extendReasonCode")).intValue());
+ Assertions.assertEquals("fdf964a4-233b-486c-aac1-97d8d52688ac", root.get("requestIdentifier"));
+ Assertions.assertEquals(List.of("USA", "MEX"), root.get("storefrontCountryCodes"));
+ Assertions.assertEquals("com.example.productId", root.get("productId"));
+ });
+
+ MassExtendRenewalDateRequest extendRenewalDateRequest = new MassExtendRenewalDateRequest()
+ .extendByDays(45)
+ .extendReasonCode(ExtendReasonCode.CUSTOMER_SATISFACTION)
+ .requestIdentifier("fdf964a4-233b-486c-aac1-97d8d52688ac")
+ .storefrontCountryCodes(List.of("USA", "MEX"))
+ .productId("com.example.productId");
+
+ MassExtendRenewalDateResponse massExtendRenewalDateResponse = client.extendRenewalDateForAllActiveSubscribers(extendRenewalDateRequest);
+
+ Assertions.assertNotNull(massExtendRenewalDateResponse);
+ Assertions.assertEquals("758883e8-151b-47b7-abd0-60c4d804c2f5", massExtendRenewalDateResponse.getRequestIdentifier());
+ }
+
+ @Test
+ public void testExtendSubscriptionRenewalDate() throws IOException, APIException {
+ AppStoreServerAPIClient client = getClientWithBody("models/extendSubscriptionRenewalDateResponse.json", request -> {
+ Assertions.assertEquals("PUT", request.method());
+ Assertions.assertEquals("/inApps/v1/subscriptions/extend/4124214", request.url().encodedPath());
+ RequestBody body = request.body();
+ Assertions.assertNotNull(body);
+ Assertions.assertEquals(expectedMediaType, body.contentType());
+ Buffer buffer = new Buffer();
+ try {
+ body.writeTo(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ Map root = new Gson().fromJson(buffer.readUtf8(), Map.class);
+ Assertions.assertEquals(45, ((Number) root.get("extendByDays")).intValue());
+ Assertions.assertEquals(1, ((Number) root.get("extendReasonCode")).intValue());
+ Assertions.assertEquals("fdf964a4-233b-486c-aac1-97d8d52688ac", root.get("requestIdentifier"));
+ });
+
+ ExtendRenewalDateRequest extendRenewalDateRequest = new ExtendRenewalDateRequest()
+ .extendByDays(45)
+ .extendReasonCode(ExtendReasonCode.CUSTOMER_SATISFACTION)
+ .requestIdentifier("fdf964a4-233b-486c-aac1-97d8d52688ac");
+
+ ExtendRenewalDateResponse extendRenewalDateResponse = client.extendSubscriptionRenewalDate("4124214", extendRenewalDateRequest);
+
+ Assertions.assertNotNull(extendRenewalDateResponse);
+ Assertions.assertEquals("2312412", extendRenewalDateResponse.getOriginalTransactionId());
+ Assertions.assertEquals("9993", extendRenewalDateResponse.getWebOrderLineItemId());
+ Assertions.assertTrue(extendRenewalDateResponse.getSuccess());
+ Assertions.assertEquals(1698148900000L, extendRenewalDateResponse.getEffectiveDate());
+ }
+
+ @Test
+ public void testGetAllSubscriptionStatuses() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/getAllSubscriptionStatusesResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/subscriptions/4321", request.url().encodedPath());
+ Assertions.assertEquals(List.of("2", "1"), request.url().queryParameterValues("status"));
+ Assertions.assertNull(request.body());
+ });
+
+ StatusResponse statusResponse = client.getAllSubscriptionStatuses("4321", new Status[] {Status.EXPIRED, Status.ACTIVE});
+
+ Assertions.assertNotNull(statusResponse);
+ Assertions.assertEquals(Environment.LOCAL_TESTING, statusResponse.getEnvironment());
+ Assertions.assertEquals("LocalTesting", statusResponse.getRawEnvironment());
+ Assertions.assertEquals("com.example", statusResponse.getBundleId());
+ Assertions.assertEquals(5454545L, statusResponse.getAppAppleId());
+
+ SubscriptionGroupIdentifierItem item = new SubscriptionGroupIdentifierItem()
+ .subscriptionGroupIdentifier("sub_group_one")
+ .lastTransactions(List.of(
+ new LastTransactionsItem()
+ .status(Status.ACTIVE)
+ .originalTransactionId("3749183")
+ .signedTransactionInfo("signed_transaction_one")
+ .signedRenewalInfo("signed_renewal_one"),
+ new LastTransactionsItem()
+ .status(Status.REVOKED)
+ .originalTransactionId("5314314134")
+ .signedTransactionInfo("signed_transaction_two")
+ .signedRenewalInfo("signed_renewal_two")
+ ));
+ SubscriptionGroupIdentifierItem secondItem = new SubscriptionGroupIdentifierItem()
+ .subscriptionGroupIdentifier("sub_group_two")
+ .lastTransactions(List.of(
+ new LastTransactionsItem()
+ .status(Status.EXPIRED)
+ .originalTransactionId("3413453")
+ .signedTransactionInfo("signed_transaction_three")
+ .signedRenewalInfo("signed_renewal_three")
+ ));
+ Assertions.assertEquals(List.of(item, secondItem), statusResponse.getData());
+ }
+
+ @Test
+ public void testGetRefundHistory() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/getRefundHistoryResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v2/refund/lookup/555555", request.url().encodedPath());
+ Assertions.assertEquals("revision_input", request.url().queryParameter("revision"));
+ Assertions.assertNull(request.body());
+ });
+
+ RefundHistoryResponse refundHistoryResponse = client.getRefundHistory("555555", "revision_input");
+
+ Assertions.assertNotNull(refundHistoryResponse);
+ Assertions.assertEquals(List.of("signed_transaction_one", "signed_transaction_two"), refundHistoryResponse.getSignedTransactions());
+ Assertions.assertEquals("revision_output", refundHistoryResponse.getRevision());
+ Assertions.assertTrue(refundHistoryResponse.getHasMore());
+ }
+
+ @Test
+ public void testGetStatusOfSubscriptionRenewalDateExtensions() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/subscriptions/extend/mass/20fba8a0-2b80-4a7d-a17f-85c1854727f8/com.example.product", request.url().encodedPath());
+ Assertions.assertNull(request.body());
+ });
+
+ MassExtendRenewalDateStatusResponse massExtendRenewalDateStatusResponse = client.getStatusOfSubscriptionRenewalDateExtensions("com.example.product", "20fba8a0-2b80-4a7d-a17f-85c1854727f8");
+
+ Assertions.assertNotNull(massExtendRenewalDateStatusResponse);
+ Assertions.assertEquals("20fba8a0-2b80-4a7d-a17f-85c1854727f8", massExtendRenewalDateStatusResponse.getRequestIdentifier());
+ Assertions.assertTrue(massExtendRenewalDateStatusResponse.getComplete());
+ Assertions.assertEquals(1698148900000L, massExtendRenewalDateStatusResponse.getCompleteDate());
+ Assertions.assertEquals(30, massExtendRenewalDateStatusResponse.getSucceededCount());
+ Assertions.assertEquals(2, massExtendRenewalDateStatusResponse.getFailedCount());
+ }
+
+ @Test
+ public void testGetTestNotificationStatus() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/getTestNotificationStatusResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/notifications/test/8cd2974c-f905-492a-bf9a-b2f47c791d19", request.url().encodedPath());
+ Assertions.assertNull(request.body());
+ });
+
+ CheckTestNotificationResponse checkTestNotificationResponse = client.getTestNotificationStatus("8cd2974c-f905-492a-bf9a-b2f47c791d19");
+
+ Assertions.assertNotNull(checkTestNotificationResponse);
+ Assertions.assertEquals("signed_payload", checkTestNotificationResponse.getSignedPayload());
+ List sendAttemptItems = List.of(
+ new SendAttemptItem()
+ .attemptDate(1698148900000L)
+ .sendAttemptResult(SendAttemptResult.NO_RESPONSE),
+ new SendAttemptItem()
+ .attemptDate(1698148950000L)
+ .sendAttemptResult(SendAttemptResult.SUCCESS)
+ );
+ Assertions.assertEquals(sendAttemptItems, checkTestNotificationResponse.getSendAttempts());
+ }
+
+ @Test
+ public void testGetNotificationHistory() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/getNotificationHistoryResponse.json", request -> {
+ Assertions.assertEquals("POST", request.method());
+ Assertions.assertEquals("/inApps/v1/notifications/history", request.url().encodedPath());
+ Assertions.assertEquals("a036bc0e-52b8-4bee-82fc-8c24cb6715d6", request.url().queryParameter("paginationToken"));
+ RequestBody body = request.body();
+ Assertions.assertNotNull(body);
+ Assertions.assertEquals(expectedMediaType, body.contentType());
+ Buffer buffer = new Buffer();
+ try {
+ body.writeTo(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ Map root = new Gson().fromJson(buffer.readUtf8(), Map.class);
+ Assertions.assertEquals(1698148900000L, ((Number) root.get("startDate")).longValue());
+ Assertions.assertEquals(1698148950000L, ((Number) root.get("endDate")).longValue());
+ Assertions.assertEquals("SUBSCRIBED", root.get("notificationType"));
+ Assertions.assertEquals("INITIAL_BUY", root.get("notificationSubtype"));
+ Assertions.assertEquals("999733843", root.get("transactionId"));
+ Assertions.assertTrue((Boolean) root.get("onlyFailures"));
+ });
+
+ NotificationHistoryRequest notificationHistoryRequest = new NotificationHistoryRequest()
+ .startDate(1698148900000L)
+ .endDate(1698148950000L)
+ .notificationType(NotificationTypeV2.SUBSCRIBED)
+ .notificationSubtype(Subtype.INITIAL_BUY)
+ .transactionId("999733843")
+ .onlyFailures(true);
+
+ NotificationHistoryResponse notificationHistoryResponse = client.getNotificationHistory("a036bc0e-52b8-4bee-82fc-8c24cb6715d6", notificationHistoryRequest);
+
+ Assertions.assertNotNull(notificationHistoryResponse);
+ Assertions.assertEquals("57715481-805a-4283-8499-1c19b5d6b20a", notificationHistoryResponse.getPaginationToken());
+ Assertions.assertTrue(notificationHistoryResponse.getHasMore());
+ List expectedNotificationHistory = List.of(
+ new NotificationHistoryResponseItem()
+ .sendAttempts(List.of(
+ new SendAttemptItem()
+ .attemptDate(1698148900000L)
+ .sendAttemptResult(SendAttemptResult.NO_RESPONSE),
+ new SendAttemptItem()
+ .attemptDate(1698148950000L)
+ .sendAttemptResult(SendAttemptResult.SUCCESS)
+ ))
+ .signedPayload("signed_payload_one"),
+ new NotificationHistoryResponseItem()
+ .sendAttempts(List.of(
+ new SendAttemptItem()
+ .attemptDate(1698148800000L)
+ .sendAttemptResult(SendAttemptResult.CIRCULAR_REDIRECT)
+ ))
+ .signedPayload("signed_payload_two")
+ );
+ Assertions.assertEquals(expectedNotificationHistory, notificationHistoryResponse.getNotificationHistory());
+ }
+
+ @Test
+ public void testGetTransactionHistory() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/transactionHistoryResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/history/1234", request.url().encodedPath());
+ Assertions.assertEquals("revision_input", request.url().queryParameter("revision"));
+ Assertions.assertEquals("123455", request.url().queryParameter("startDate"));
+ Assertions.assertEquals("123456", request.url().queryParameter("endDate"));
+ Assertions.assertEquals(List.of("com.example.1", "com.example.2"), request.url().queryParameterValues("productId"));
+ Assertions.assertEquals(List.of("CONSUMABLE", "AUTO_RENEWABLE"), request.url().queryParameterValues("productType"));
+ Assertions.assertEquals("ASCENDING", request.url().queryParameter("sort"));
+ Assertions.assertEquals(List.of("sub_group_id", "sub_group_id_2"), request.url().queryParameterValues("subscriptionGroupIdentifier"));
+ Assertions.assertEquals("FAMILY_SHARED", request.url().queryParameter("inAppOwnershipType"));
+ Assertions.assertEquals("false", request.url().queryParameter("revoked"));
+ Assertions.assertNull(request.body());
+ });
+
+ TransactionHistoryRequest request = new TransactionHistoryRequest()
+ .sort(TransactionHistoryRequest.Order.ASCENDING)
+ .productTypes(List.of(TransactionHistoryRequest.ProductType.CONSUMABLE, TransactionHistoryRequest.ProductType.AUTO_RENEWABLE))
+ .endDate(123456L)
+ .startDate(123455L)
+ .revoked(false)
+ .inAppOwnershipType(InAppOwnershipType.FAMILY_SHARED)
+ .productIds(List.of("com.example.1", "com.example.2"))
+ .subscriptionGroupIdentifiers(List.of("sub_group_id", "sub_group_id_2"));
+
+ HistoryResponse historyResponse = client.getTransactionHistory("1234", "revision_input", request);
+
+ Assertions.assertNotNull(historyResponse);
+ Assertions.assertEquals("revision_output", historyResponse.getRevision());
+ Assertions.assertTrue(historyResponse.getHasMore());
+ Assertions.assertEquals("com.example", historyResponse.getBundleId());
+ Assertions.assertEquals(323232L, historyResponse.getAppAppleId());
+ Assertions.assertEquals(Environment.LOCAL_TESTING, historyResponse.getEnvironment());
+ Assertions.assertEquals("LocalTesting", historyResponse.getRawEnvironment());
+ Assertions.assertEquals(List.of("signed_transaction_value", "signed_transaction_value2"), historyResponse.getSignedTransactions());
+ }
+
+ @Test
+ public void testGetTransactionInfo() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/transactionInfoResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/transactions/1234", request.url().encodedPath());
+ Assertions.assertNull(request.body());
+ });
+
+ TransactionInfoResponse transactionInfoResponse = client.getTransactionInfo("1234");
+
+ Assertions.assertNotNull(transactionInfoResponse);
+ Assertions.assertEquals("signed_transaction_info_value", transactionInfoResponse.getSignedTransactionInfo());
+ }
+
+ @Test
+ public void testLookUpOrderId() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/lookupOrderIdResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/lookup/W002182", request.url().encodedPath());
+ Assertions.assertNull(request.body());
+ });
+
+ OrderLookupResponse orderLookupResponse = client.lookUpOrderId("W002182");
+
+ Assertions.assertNotNull(orderLookupResponse);
+ Assertions.assertEquals(OrderLookupStatus.INVALID, orderLookupResponse.getStatus());
+ Assertions.assertEquals(1, orderLookupResponse.getRawStatus());
+ Assertions.assertEquals(List.of("signed_transaction_one", "signed_transaction_two"), orderLookupResponse.getSignedTransactions());
+ }
+
+ @Test
+ public void testRequestTestNotification() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/requestTestNotificationResponse.json", request -> {
+ Assertions.assertEquals("POST", request.method());
+ Assertions.assertEquals("/inApps/v1/notifications/test", request.url().encodedPath());
+ RequestBody body = request.body();
+ Assertions.assertNotNull(body);
+ Assertions.assertNull(body.contentType());
+ try {
+ Assertions.assertEquals(0, body.contentLength());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ SendTestNotificationResponse sendTestNotificationResponse = client.requestTestNotification();
+
+ Assertions.assertNotNull(sendTestNotificationResponse);
+ Assertions.assertEquals("ce3af791-365e-4c60-841b-1674b43c1609", sendTestNotificationResponse.getTestNotificationToken());
+ }
+
+ @Test
+ public void testSendConsumptionData() throws APIException, IOException {
+ AppStoreServerAPIClient client = getAppStoreServerAPIClient("", request -> {
+ Assertions.assertEquals("PUT", request.method());
+ Assertions.assertEquals("/inApps/v1/transactions/consumption/49571273", request.url().encodedPath());
+ RequestBody body = request.body();
+ Assertions.assertNotNull(body);
+ Assertions.assertEquals(expectedMediaType, body.contentType());
+ Buffer buffer = new Buffer();
+ try {
+ body.writeTo(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ Map root = new Gson().fromJson(buffer.readUtf8(), Map.class);
+ Assertions.assertTrue((Boolean) root.get("customerConsented"));
+ Assertions.assertEquals(1, ((Number) root.get("consumptionStatus")).intValue());
+ Assertions.assertEquals(2, ((Number) root.get("platform")).intValue());
+ Assertions.assertFalse((Boolean) root.get("sampleContentProvided"));
+ Assertions.assertEquals(3, ((Number) root.get("deliveryStatus")).intValue());
+ Assertions.assertEquals("7389a31a-fb6d-4569-a2a6-db7d85d84813", root.get("appAccountToken"));
+ Assertions.assertEquals(4, ((Number) root.get("accountTenure")).intValue());
+ Assertions.assertEquals(5, ((Number) root.get("playTime")).intValue());
+ Assertions.assertEquals(6, ((Number) root.get("lifetimeDollarsRefunded")).intValue());
+ Assertions.assertEquals(7, ((Number) root.get("lifetimeDollarsPurchased")).intValue());
+ Assertions.assertEquals(4, ((Number) root.get("userStatus")).intValue());
+ });
+
+ ConsumptionRequest consumptionRequest = new ConsumptionRequest()
+ .customerConsented(true)
+ .consumptionStatus(ConsumptionStatus.NOT_CONSUMED)
+ .platform(Platform.NON_APPLE)
+ .sampleContentProvided(false)
+ .deliveryStatus(DeliveryStatus.DID_NOT_DELIVER_DUE_TO_SERVER_OUTAGE)
+ .appAccountToken(UUID.fromString("7389a31a-fb6d-4569-a2a6-db7d85d84813"))
+ .accountTenure(AccountTenure.THIRTY_DAYS_TO_NINETY_DAYS)
+ .playTime(PlayTime.ONE_DAY_TO_FOUR_DAYS)
+ .lifetimeDollarsRefunded(LifetimeDollarsRefunded.ONE_THOUSAND_DOLLARS_TO_ONE_THOUSAND_NINE_HUNDRED_NINETY_NINE_DOLLARS_AND_NINETY_NINE_CENTS)
+ .lifetimeDollarsPurchased(LifetimeDollarsPurchased.TWO_THOUSAND_DOLLARS_OR_GREATER)
+ .userStatus(UserStatus.LIMITED_ACCESS);
+
+ client.sendConsumptionData("49571273", consumptionRequest);
+ }
+
+ @Test
+ public void testHeaders() throws APIException, IOException {
+ AppStoreServerAPIClient client = getClientWithBody("models/transactionInfoResponse.json", request -> {
+ Assertions.assertTrue(request.header("User-Agent").startsWith("app-store-server-library/java"));
+ Assertions.assertEquals("application/json", request.header("Accept"));
+ String authorization = request.header("Authorization");
+ Assertions.assertTrue(authorization.startsWith("Bearer "));
+ DecodedJWT token = JWT.decode(authorization.substring(7));
+ Assertions.assertEquals(List.of("appstoreconnect-v1"), token.getAudience());
+ Assertions.assertEquals("issuerId", token.getIssuer());
+ Assertions.assertEquals("keyId", token.getKeyId());
+ Assertions.assertEquals("com.example", token.getClaim("bid").asString());
+ Assertions.assertEquals("ES256", token.getAlgorithm());
+ });
+
+ client.getTransactionInfo("1234");
+ }
+
+ @Test
+ public void testAPIError() throws IOException {
+ String body = TestingUtility.readFile("models/apiException.json");
+ AppStoreServerAPIClient client = getAppStoreServerAPIClient(body, request -> {}, 500);
+ try {
+ client.getTransactionInfo("1234");
+ } catch (APIException e) {
+ Assertions.assertEquals(500 , e.getHttpStatusCode());
+ Assertions.assertEquals(APIError.GENERAL_INTERNAL, e.getApiError());
+ Assertions.assertEquals(5000000L, e.getRawApiError());
+ return;
+ }
+ Assertions.fail();
+ }
+
+ @Test
+ public void testAPITooManyRequests() throws IOException {
+ String body = TestingUtility.readFile("models/apiTooManyRequestsException.json");
+ AppStoreServerAPIClient client = getAppStoreServerAPIClient(body, request -> {}, 429);
+ try {
+ client.getTransactionInfo("1234");
+ } catch (APIException e) {
+ Assertions.assertEquals(429 , e.getHttpStatusCode());
+ Assertions.assertEquals(APIError.RATE_LIMIT_EXCEEDED, e.getApiError());
+ Assertions.assertEquals(4290000L, e.getRawApiError());
+ return;
+ }
+ Assertions.fail();
+ }
+
+ public AppStoreServerAPIClient getClientWithBody(String path, Consumer requestVerifier) throws IOException {
+ String body = TestingUtility.readFile(path);
+ return getAppStoreServerAPIClient(body, requestVerifier);
+ }
+
+ private AppStoreServerAPIClient getAppStoreServerAPIClient(String body, Consumer requestVerifier) throws IOException {
+ return getAppStoreServerAPIClient(body, requestVerifier, 200);
+ }
+
+ private AppStoreServerAPIClient getAppStoreServerAPIClient(String body, Consumer requestVerifier, int statusCode) throws IOException {
+ try (InputStream key = this.getClass().getClassLoader().getResourceAsStream("certs/testSigningKey.p8")) {
+ return new AppStoreServerAPIClient(new String(key.readAllBytes()), "keyId", "issuerId", "com.example", Environment.LOCAL_TESTING) {
+ @Override
+ protected Response getResponse(Request request) {
+ requestVerifier.accept(request);
+ return new Response.Builder()
+ .body(ResponseBody.create(body, MediaType.parse("application/json")))
+ .code(statusCode)
+ .request(request)
+ .protocol(Protocol.HTTP_1_1)
+ .message("")
+ .build();
+ }
+ };
+ }
+ }
+}
diff --git a/src/test/java/com/apple/itunes/storekit/client/BearerTokenAuthenticatorTest.java b/src/test/java/com/apple/itunes/storekit/client/BearerTokenAuthenticatorTest.java
index 9f6757d4..151e31dd 100644
--- a/src/test/java/com/apple/itunes/storekit/client/BearerTokenAuthenticatorTest.java
+++ b/src/test/java/com/apple/itunes/storekit/client/BearerTokenAuthenticatorTest.java
@@ -2,6 +2,7 @@
package com.apple.itunes.storekit.client;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
@@ -9,10 +10,11 @@
class BearerTokenAuthenticatorTest {
@Test
- void testConstructor() throws Exception {
- try (InputStream key = this.getClass().getClassLoader().getResourceAsStream("testSigningKey.p8")) {
- new BearerTokenAuthenticator(new String(key.readAllBytes()), "keyId", "issuerId", "bundleId");
+ void testCreatingToken() throws Exception {
+ try (InputStream key = this.getClass().getClassLoader().getResourceAsStream("certs/testSigningKey.p8")) {
+ var tokenGenerator = new BearerTokenAuthenticator(new String(key.readAllBytes()), "keyId", "issuerId", "bundleId");
+ String token = tokenGenerator.generateToken();
+ Assertions.assertNotNull(token);
}
}
-
}
diff --git a/src/test/java/com/apple/itunes/storekit/migration/ReceiptUtilityTest.java b/src/test/java/com/apple/itunes/storekit/migration/ReceiptUtilityTest.java
new file mode 100644
index 00000000..a20b48a5
--- /dev/null
+++ b/src/test/java/com/apple/itunes/storekit/migration/ReceiptUtilityTest.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.migration;
+
+import com.apple.itunes.storekit.util.TestingUtility;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+public class ReceiptUtilityTest {
+
+ private static final String APP_RECEIPT_EXPECTED_TRANSACTION_ID = "0";
+ private static final String TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID = "33993399";
+
+ @Test
+ public void testXcodeAppReceiptExtractionWithNoTransactions() throws IOException {
+ String receipt = TestingUtility.readFile("xcode/xcode-app-receipt-empty");
+
+ ReceiptUtility util = new ReceiptUtility();
+ String extractedTransactionId = util.extractTransactionIdFromAppReceipt(receipt);
+
+ Assertions.assertNull(extractedTransactionId);
+ }
+
+ @Test
+ public void testXcodeAppReceiptExtractionWithTransactions() throws IOException {
+ String receipt = TestingUtility.readFile("xcode/xcode-app-receipt-with-transaction");
+
+ ReceiptUtility util = new ReceiptUtility();
+ String extractedTransactionId = util.extractTransactionIdFromAppReceipt(receipt);
+
+ Assertions.assertEquals(APP_RECEIPT_EXPECTED_TRANSACTION_ID, extractedTransactionId);
+ }
+
+ @Test
+ public void testTransactionReceiptExtraction() throws IOException {
+ String receipt = TestingUtility.readFile("mock_signed_data/legacyTransaction");
+
+ ReceiptUtility util = new ReceiptUtility();
+ String extractedTransactionId = util.extractTransactionIdFromTransactionReceipt(receipt);
+
+ Assertions.assertEquals(TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID, extractedTransactionId);
+ }
+}
diff --git a/src/test/java/com/apple/itunes/storekit/model/AppTransactionTest.java b/src/test/java/com/apple/itunes/storekit/model/AppTransactionTest.java
new file mode 100644
index 00000000..760a2ec7
--- /dev/null
+++ b/src/test/java/com/apple/itunes/storekit/model/AppTransactionTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+
+import com.apple.itunes.storekit.util.SignedDataCreator;
+import com.apple.itunes.storekit.util.TestingUtility;
+import com.apple.itunes.storekit.verification.VerificationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.UUID;
+
+public class AppTransactionTest {
+
+ @Test
+ public void testAppTransactionDecoding() throws IOException, NoSuchAlgorithmException, VerificationException {
+ String signedAppTransaction = SignedDataCreator.createSignedDataFromJson("models/appTransaction.json");
+
+ AppTransaction appTransaction = TestingUtility.getSignedPayloadVerifier().verifyAndDecodeAppTransaction(signedAppTransaction);
+
+ Assertions.assertEquals(Environment.LOCAL_TESTING, appTransaction.getReceiptType());
+ Assertions.assertEquals("LocalTesting", appTransaction.getRawReceiptType());
+ Assertions.assertEquals(531412, appTransaction.getAppAppleId());
+ Assertions.assertEquals("com.example", appTransaction.getBundleId());
+ Assertions.assertEquals("1.2.3", appTransaction.getApplicationVersion());
+ Assertions.assertEquals(512, appTransaction.versionExternalIdentifier());
+ Assertions.assertEquals(1698148900000L, appTransaction.getReceiptCreationDate());
+ Assertions.assertEquals(1698148800000L, appTransaction.originalPurchaseDate());
+ Assertions.assertEquals("1.1.2", appTransaction.getOriginalApplicationVersion());
+ Assertions.assertEquals("device_verification_value", appTransaction.getDeviceVerification());
+ Assertions.assertEquals(UUID.fromString("48ccfa42-7431-4f22-9908-7e88983e105a"), appTransaction.getDeviceVerificationNonce());
+ Assertions.assertEquals(1698148700000L, appTransaction.getPreorderDate());
+ }
+}
diff --git a/src/test/java/com/apple/itunes/storekit/model/JWSRenewalInfoDecodedPayloadTest.java b/src/test/java/com/apple/itunes/storekit/model/JWSRenewalInfoDecodedPayloadTest.java
new file mode 100644
index 00000000..78dba267
--- /dev/null
+++ b/src/test/java/com/apple/itunes/storekit/model/JWSRenewalInfoDecodedPayloadTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.apple.itunes.storekit.util.SignedDataCreator;
+import com.apple.itunes.storekit.util.TestingUtility;
+import com.apple.itunes.storekit.verification.VerificationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+public class JWSRenewalInfoDecodedPayloadTest {
+
+ @Test
+ public void testRenewalInfoDecoding() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, VerificationException {
+ String signedRenewalInfo = SignedDataCreator.createSignedDataFromJson("models/signedRenewalInfo.json");
+
+ JWSRenewalInfoDecodedPayload renewalInfo = TestingUtility.getSignedPayloadVerifier().verifyAndDecodeRenewalInfo(signedRenewalInfo);
+
+ Assertions.assertEquals(ExpirationIntent.CUSTOMER_CANCELLED, renewalInfo.getExpirationIntent());
+ Assertions.assertEquals(1, renewalInfo.getRawExpirationIntent());
+ Assertions.assertEquals("12345", renewalInfo.getOriginalTransactionId());
+ Assertions.assertEquals("com.example.product.2", renewalInfo.getAutoRenewProductId());
+ Assertions.assertEquals("com.example.product", renewalInfo.getProductId());
+ Assertions.assertEquals(AutoRenewStatus.ON, renewalInfo.getAutoRenewStatus());
+ Assertions.assertEquals(1, renewalInfo.getRawAutoRenewStatus());
+ Assertions.assertTrue(renewalInfo.getIsInBillingRetryPeriod());
+ Assertions.assertEquals(PriceIncreaseStatus.CUSTOMER_HAS_NOT_RESPONDED, renewalInfo.getPriceIncreaseStatus());
+ Assertions.assertEquals(0, renewalInfo.getRawPriceIncreaseStatus());
+ Assertions.assertEquals(1698148900000L, renewalInfo.getGracePeriodExpiresDate());
+ Assertions.assertEquals(OfferType.PROMOTIONAL_OFFER, renewalInfo.getOfferType());
+ Assertions.assertEquals(2, renewalInfo.getRawOfferType());
+ Assertions.assertEquals("abc.123", renewalInfo.getOfferIdentifier());
+ Assertions.assertEquals(1698148800000L, renewalInfo.getSignedDate());
+ Assertions.assertEquals(Environment.LOCAL_TESTING, renewalInfo.getEnvironment());
+ Assertions.assertEquals("LocalTesting", renewalInfo.getRawEnvironment());
+ Assertions.assertEquals(1698148800000L, renewalInfo.getRecentSubscriptionStartDate());
+ Assertions.assertEquals(1698148850000L, renewalInfo.getRenewalDate());
+ }
+}
diff --git a/src/test/java/com/apple/itunes/storekit/model/JWSTransactionDecodedPayloadTest.java b/src/test/java/com/apple/itunes/storekit/model/JWSTransactionDecodedPayloadTest.java
new file mode 100644
index 00000000..2c091f1d
--- /dev/null
+++ b/src/test/java/com/apple/itunes/storekit/model/JWSTransactionDecodedPayloadTest.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.apple.itunes.storekit.util.SignedDataCreator;
+import com.apple.itunes.storekit.util.TestingUtility;
+import com.apple.itunes.storekit.verification.VerificationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.UUID;
+
+public class JWSTransactionDecodedPayloadTest {
+
+ @Test
+ public void testTransactionDecoding() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, VerificationException {
+ String signedTransaction = SignedDataCreator.createSignedDataFromJson("models/signedTransaction.json");
+
+ JWSTransactionDecodedPayload transaction = TestingUtility.getSignedPayloadVerifier().verifyAndDecodeTransaction(signedTransaction);
+
+ Assertions.assertEquals("12345", transaction.getOriginalTransactionId());
+ Assertions.assertEquals("23456", transaction.getTransactionId());
+ Assertions.assertEquals("34343", transaction.getWebOrderLineItemId());
+ Assertions.assertEquals("com.example", transaction.getBundleId());
+ Assertions.assertEquals("com.example.product", transaction.getProductId());
+ Assertions.assertEquals("55555", transaction.getSubscriptionGroupIdentifier());
+ Assertions.assertEquals(1698148800000L, transaction.getOriginalPurchaseDate());
+ Assertions.assertEquals(1698148900000L, transaction.getPurchaseDate());
+ Assertions.assertEquals(1698148950000L, transaction.getRevocationDate());
+ Assertions.assertEquals(1698149000000L, transaction.getExpiresDate());
+ Assertions.assertEquals(1, transaction.getQuantity());
+ Assertions.assertEquals(Type.AUTO_RENEWABLE_SUBSCRIPTION, transaction.getType());
+ Assertions.assertEquals("Auto-Renewable Subscription", transaction.getRawType());
+ Assertions.assertEquals(UUID.fromString("7e3fb20b-4cdb-47cc-936d-99d65f608138"), transaction.getAppAccountToken());
+ Assertions.assertEquals(InAppOwnershipType.PURCHASED, transaction.getInAppOwnershipType());
+ Assertions.assertEquals("PURCHASED", transaction.getRawInAppOwnershipType());
+ Assertions.assertEquals(1698148900000L, transaction.getSignedDate());
+ Assertions.assertEquals(RevocationReason.REFUNDED_DUE_TO_ISSUE, transaction.getRevocationReason());
+ Assertions.assertEquals(1, transaction.getRawRevocationReason());
+ Assertions.assertEquals("abc.123", transaction.getOfferIdentifier());
+ Assertions.assertTrue(transaction.getIsUpgraded());
+ Assertions.assertEquals(OfferType.INTRODUCTORY_OFFER, transaction.getOfferType());
+ Assertions.assertEquals(1, transaction.getRawOfferType());
+ Assertions.assertEquals("USA", transaction.getStorefront());
+ Assertions.assertEquals("143441", transaction.getStorefrontId());
+ Assertions.assertEquals(TransactionReason.PURCHASE, transaction.getTransactionReason());
+ Assertions.assertEquals("PURCHASE", transaction.getRawTransactionReason());
+ Assertions.assertEquals(Environment.LOCAL_TESTING, transaction.getEnvironment());
+ Assertions.assertEquals("LocalTesting", transaction.getRawEnvironment());
+ }
+}
diff --git a/src/test/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayloadTest.java b/src/test/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayloadTest.java
new file mode 100644
index 00000000..4ed22e1f
--- /dev/null
+++ b/src/test/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayloadTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.apple.itunes.storekit.util.SignedDataCreator;
+import com.apple.itunes.storekit.util.TestingUtility;
+import com.apple.itunes.storekit.verification.VerificationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.List;
+import java.util.UUID;
+
+public class ResponseBodyV2DecodedPayloadTest {
+
+ @Test
+ public void testNotificationDecoding() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, VerificationException {
+ String signedNotification = SignedDataCreator.createSignedDataFromJson("models/signedNotification.json");
+
+ ResponseBodyV2DecodedPayload notification = TestingUtility.getSignedPayloadVerifier().verifyAndDecodeNotification(signedNotification);
+
+ Assertions.assertEquals(NotificationTypeV2.SUBSCRIBED, notification.getNotificationType());
+ Assertions.assertEquals("SUBSCRIBED", notification.getRawNotificationType());
+ Assertions.assertEquals(Subtype.INITIAL_BUY, notification.getSubtype());
+ Assertions.assertEquals("INITIAL_BUY", notification.getRawSubtype());
+ Assertions.assertEquals("002e14d5-51f5-4503-b5a8-c3a1af68eb20", notification.getNotificationUUID());
+ Assertions.assertEquals("2.0", notification.getVersion());
+ Assertions.assertEquals(1698148900000L, notification.getSignedDate());
+ Assertions.assertNotNull(notification.getData());
+ Assertions.assertNull(notification.getSummary());
+ Assertions.assertEquals(Environment.LOCAL_TESTING, notification.getData().getEnvironment());
+ Assertions.assertEquals("LocalTesting", notification.getData().getRawEnvironment());
+ Assertions.assertEquals(41234L, notification.getData().getAppAppleId());
+ Assertions.assertEquals("com.example", notification.getData().getBundleId());
+ Assertions.assertEquals("1.2.3", notification.getData().getBundleVersion());
+ Assertions.assertEquals("signed_transaction_info_value", notification.getData().getSignedTransactionInfo());
+ Assertions.assertEquals("signed_renewal_info_value", notification.getData().getSignedRenewalInfo());
+ Assertions.assertEquals(Status.ACTIVE, notification.getData().getStatus());
+ Assertions.assertEquals(1, notification.getData().getRawStatus());
+ }
+
+ @Test
+ public void testSummaryNotificationDecoding() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, VerificationException {
+ String signedNotification = SignedDataCreator.createSignedDataFromJson("models/signedSummaryNotification.json");
+
+ ResponseBodyV2DecodedPayload notification = TestingUtility.getSignedPayloadVerifier().verifyAndDecodeNotification(signedNotification);
+
+ Assertions.assertEquals(NotificationTypeV2.RENEWAL_EXTENSION, notification.getNotificationType());
+ Assertions.assertEquals("RENEWAL_EXTENSION", notification.getRawNotificationType());
+ Assertions.assertEquals(Subtype.SUMMARY, notification.getSubtype());
+ Assertions.assertEquals("SUMMARY", notification.getRawSubtype());
+ Assertions.assertEquals("002e14d5-51f5-4503-b5a8-c3a1af68eb20", notification.getNotificationUUID());
+ Assertions.assertEquals("2.0", notification.getVersion());
+ Assertions.assertEquals(1698148900000L, notification.getSignedDate());
+ Assertions.assertNull(notification.getData());
+ Assertions.assertNotNull(notification.getSummary());
+ Assertions.assertEquals(Environment.LOCAL_TESTING, notification.getSummary().getEnvironment());
+ Assertions.assertEquals("LocalTesting", notification.getSummary().getRawEnvironment());
+ Assertions.assertEquals(41234L, notification.getSummary().getAppAppleId());
+ Assertions.assertEquals("com.example", notification.getSummary().getBundleId());
+ Assertions.assertEquals("com.example.product", notification.getSummary().getProductId());
+ Assertions.assertEquals("efb27071-45a4-4aca-9854-2a1e9146f265", notification.getSummary().getRequestIdentifier());
+ Assertions.assertEquals(List.of("CAN", "USA", "MEX"), notification.getSummary().getStorefrontCountryCodes());
+ Assertions.assertEquals(5, notification.getSummary().getSucceededCount());
+ Assertions.assertEquals(2, notification.getSummary().getFailedCount());
+ }
+}
diff --git a/src/test/java/com/apple/itunes/storekit/offers/PromotionalOfferSignatureCreatorTest.java b/src/test/java/com/apple/itunes/storekit/offers/PromotionalOfferSignatureCreatorTest.java
index ca59f421..20e07dda 100644
--- a/src/test/java/com/apple/itunes/storekit/offers/PromotionalOfferSignatureCreatorTest.java
+++ b/src/test/java/com/apple/itunes/storekit/offers/PromotionalOfferSignatureCreatorTest.java
@@ -2,17 +2,20 @@
package com.apple.itunes.storekit.offers;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
+import java.util.UUID;
class PromotionalOfferSignatureCreatorTest {
@Test
- void testConstructor() throws Exception {
- try (InputStream key = this.getClass().getClassLoader().getResourceAsStream("testSigningKey.p8")) {
- new PromotionalOfferSignatureCreator(new String(key.readAllBytes()), "keyId", "bundleId");
+ void testSignatureCreator() throws Exception {
+ try (InputStream key = this.getClass().getClassLoader().getResourceAsStream("certs/testSigningKey.p8")) {
+ PromotionalOfferSignatureCreator signatureCreator = new PromotionalOfferSignatureCreator(new String(key.readAllBytes()), "keyId", "bundleId");
+ String signature = signatureCreator.createSignature("productId", "offerId", "applicationUsername", UUID.fromString("20fba8a0-2b80-4a7d-a17f-85c1854727f8"), 1698148900000L);
+ Assertions.assertNotNull(signature);
}
}
-
}
diff --git a/src/test/java/com/apple/itunes/storekit/util/SignedDataCreator.java b/src/test/java/com/apple/itunes/storekit/util/SignedDataCreator.java
new file mode 100644
index 00000000..2738bb42
--- /dev/null
+++ b/src/test/java/com/apple/itunes/storekit/util/SignedDataCreator.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.ECDSAKeyProvider;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.Map;
+
+public class SignedDataCreator {
+
+ public static String createSignedDataFromJson(String path) throws IOException, NoSuchAlgorithmException {
+ String json = TestingUtility.readFile(path);
+ KeyPairGenerator ec = KeyPairGenerator.getInstance("EC");
+ ec.initialize(256);
+ return JWT.create()
+ .withPayload(json)
+ .sign(Algorithm.ECDSA256((ECPrivateKey) ec.generateKeyPair().getPrivate()));
+ }
+}
diff --git a/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java b/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java
index d51e3c51..9e060280 100644
--- a/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java
+++ b/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java
@@ -7,86 +7,74 @@
import com.apple.itunes.storekit.model.JWSTransactionDecodedPayload;
import com.apple.itunes.storekit.model.NotificationTypeV2;
import com.apple.itunes.storekit.model.ResponseBodyV2DecodedPayload;
+import com.apple.itunes.storekit.util.TestingUtility;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-import java.io.ByteArrayInputStream;
-import java.util.Base64;
-import java.util.Set;
+import java.io.IOException;
public class SignedDataVerifierTest {
- private static final String ROOT_CA_BASE64_ENCODED = "MIIBgjCCASmgAwIBAgIJALUc5ALiH5pbMAoGCCqGSM49BAMDMDYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDdXBlcnRpbm8wHhcNMjMwMTA1MjEzMDIyWhcNMzMwMTAyMjEzMDIyWjA2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ3VwZXJ0aW5vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc+/Bl+gospo6tf9Z7io5tdKdrlN1YdVnqEhEDXDShzdAJPQijamXIMHf8xWWTa1zgoYTxOKpbuJtDplz1XriTaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDRwAwRAIgemWQXnMAdTad2JDJWng9U4uBBL5mA7WI05H7oH7c6iQCIHiRqMjNfzUAyiu9h6rOU/K+iTR0I/3Y/NSWsXHX+acc";
-
- private static final String TEST_NOTIFICATION = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImFwcEFwcGxlSWQiOjEyMzQsImVudmlyb25tZW50IjoiU2FuZGJveCIsImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsInNpZ25lZERhdGUiOjE2ODEzMTQzMjQwMDAsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.VVXYwuNm2Y3XsOUva-BozqatRCsDuykA7xIe_CCRw6aIAAxJ1nb2sw871jfZ6dcgNhUuhoZ93hfbc1v_5zB7Og";
- private static final String MISSING_X5C_HEADER_CLAIM = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1Y3dyb25nIjpbIk1JSUJvRENDQVVhZ0F3SUJBZ0lCRERBS0JnZ3Foa2pPUFFRREF6QkZNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1CNFhEVEl6TURFd05USXhNekV6TkZvWERUTXpNREV3TVRJeE16RXpORm93UFRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eERUQUxCZ05WQkFvTUJFeGxZV1l3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRpdFlIRWFZVnVjOGc5QWpUT3dFck12R3lQeWtQYStwdXZUSThoSlRIWlpETEdhczJxWDErRXJ4Z1FUSmdWWHY3Nm5tTGhoUkpIK2oyNUFpQUk4aUdzb3k4d0xUQUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVFCZ29xaGtpRzkyTmtCZ3NCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05JQURCRkFpQlg0YytUMEZwNW5KNVFSQ2xSZnU1UFNCeVJ2TlB0dWFUc2swdlBCM1dBSUFJaEFOZ2FhdUFqL1lQOXMwQWtFaHlKaHhRTy82UTJ6b3VaK0gxQ0lPZWhuTXpRIiwiTUlJQm56Q0NBVVdnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQXpBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1CNFhEVEl6TURFd05USXhNekV3TlZvWERUTXpNREV3TVRJeE16RXdOVm93UlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eEZUQVRCZ05WQkFvTURFbHVkR1Z5YldWa2FXRjBaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCQlVONVY5cktqZlJpTUFJb2pFQTBBdjVNcDBvRitPMGNMNGd6clRGMTc4aW5VSHVnajdFdDQ2TnJrUTdoS2dNVm5qb2dxNDVRMXJNcytjTUhWTklMV3FqTlRBek1BOEdBMVVkRXdRSU1BWUJBZjhDQVFBd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJBR0NpcUdTSWIzWTJRR0FnRUVBZ1VBTUFvR0NDcUdTTTQ5QkFNREEwZ0FNRVVDSVFDbXNJS1lzNDF1bGxzc0hYNHJWdmVVVDBaN0lzNS9oTEsxbEZQVHR1bjNoQUlnYzIrMlJHNStnTmNGVmNzK1hKZUVsNEdaK29qbDNST09tbGwreWU3ZHluUT0iLCJNSUlCZ2pDQ0FTbWdBd0lCQWdJSkFMVWM1QUxpSDVwYk1Bb0dDQ3FHU000OUJBTURNRFl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh3SGhjTk1qTXdNVEExTWpFek1ESXlXaGNOTXpNd01UQXlNakV6TURJeVdqQTJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVjKy9CbCtnb3NwbzZ0ZjlaN2lvNXRkS2RybE4xWWRWbnFFaEVEWERTaHpkQUpQUWlqYW1YSU1IZjh4V1dUYTF6Z29ZVHhPS3BidUp0RHBsejFYcmlUYU1nTUI0d0RBWURWUjBUQkFVd0F3RUIvekFPQmdOVkhROEJBZjhFQkFNQ0FRWXdDZ1lJS29aSXpqMEVBd01EUndBd1JBSWdlbVdRWG5NQWRUYWQySkRKV25nOVU0dUJCTDVtQTdXSTA1SDdvSDdjNmlRQ0lIaVJxTWpOZnpVQXlpdTloNnJPVS9LK2lUUjBJLzNZL05TV3NYSFgrYWNjIl19.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.1TFhjDR4WwQJNgizVGYXz3WE3ajxTdH1wKLQQ71MtrkadSxxOo3yPo_6L9Z03unIU7YK-NRNzSIb5bh5WqTprQ";
- private static final String WRONG_BUNDLE_ID = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUud3JvbmcifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.WWE31hTB_mcv2O_lf-xI-MNY3d8txc0MzpqFx4QnYDfFIxB95Lo2Fm3r46YSjLLdL7xCWdEJrJP5bHgRCejAGg";
- private static final String RENEWAL_INFO = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJzaWduZWREYXRlIjoxNjcyOTU2MTU0MDAwfQ.FbK2OL-t6l4892W7fzWyus_g9mIl2CzWLbVt7Kgcnt6zzVulF8bzovgpe0v_y490blROGixy8KDoe2dSU53-Xw";
- private static final String TRANSACTION_INFO = "eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlIiwic2lnbmVkRGF0ZSI6MTY3Mjk1NjE1NDAwMH0.PnHWpeIJZ8f2Q218NSGLo_aR0IBEJvC6PxmxKXh-qfYTrZccx2suGl223OSNAX78e4Ylf2yJCG2N-FfU-NIhZQ";
@Test
- public void testAppStoreServerNotificationDecoding() throws VerificationException {
- SignedDataVerifier verifier = getSignedPayloadVerifier();
- ResponseBodyV2DecodedPayload notification = verifier.verifyAndDecodeNotification(TEST_NOTIFICATION);
+ public void testAppStoreServerNotificationDecoding() throws VerificationException, IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.SANDBOX, "com.example");
+ ResponseBodyV2DecodedPayload notification = verifier.verifyAndDecodeNotification(TestingUtility.readFile("mock_signed_data/testNotification"));
Assertions.assertEquals(NotificationTypeV2.TEST, notification.getNotificationType());
}
@Test
- public void testMissingX5CHeader() {
- SignedDataVerifier verifier = getSignedPayloadVerifier();
- VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification(MISSING_X5C_HEADER_CLAIM));
+ public void testMissingX5CHeader() throws IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.SANDBOX, "com.example");
+ VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification(TestingUtility.readFile("mock_signed_data/missingX5CHeaderClaim")));
Assertions.assertEquals(Status.VERIFICATION_FAILURE, exception.getStatus());
}
@Test
- public void testWrongBundleIdForServerNotification() {
- SignedDataVerifier verifier = getSignedPayloadVerifier();
- VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification(WRONG_BUNDLE_ID));
+ public void testWrongBundleIdForServerNotification() throws IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.SANDBOX, "com.example");
+ VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification(TestingUtility.readFile("mock_signed_data/wrongBundleId")));
Assertions.assertEquals(Status.INVALID_APP_IDENTIFIER, exception.getStatus());
}
@Test
- public void testWrongBundleIdForTransaction() {
- SignedDataVerifier verifier = new SignedDataVerifier(Set.of(new ByteArrayInputStream(Base64.getDecoder().decode(ROOT_CA_BASE64_ENCODED))), "com.example.x", 1234L, Environment.SANDBOX, false);
- VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeTransaction(TRANSACTION_INFO));
+ public void testWrongBundleIdForTransaction() throws IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.SANDBOX, "com.example.x");
+ VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeTransaction(TestingUtility.readFile("mock_signed_data/transactionInfo")));
Assertions.assertEquals(Status.INVALID_APP_IDENTIFIER, exception.getStatus());
}
@Test
- public void testWrongEnvironmentForServerNotification() {
- SignedDataVerifier verifier = new SignedDataVerifier(Set.of(new ByteArrayInputStream(Base64.getDecoder().decode(ROOT_CA_BASE64_ENCODED))), "com.example", 1234L, Environment.PRODUCTION, false);
- VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification(TEST_NOTIFICATION));
+ public void testWrongEnvironmentForServerNotification() throws IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.PRODUCTION, "com.example");
+ VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification(TestingUtility.readFile("mock_signed_data/testNotification")));
Assertions.assertEquals(Status.INVALID_ENVIRONMENT, exception.getStatus());
}
@Test
- public void testRenewalInfoDecoding() throws VerificationException {
- SignedDataVerifier verifier = getSignedPayloadVerifier();
- JWSRenewalInfoDecodedPayload renewalInfo = verifier.verifyAndDecodeRenewalInfo(RENEWAL_INFO);
+ public void testRenewalInfoDecoding() throws VerificationException, IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.SANDBOX, "com.example");
+ JWSRenewalInfoDecodedPayload renewalInfo = verifier.verifyAndDecodeRenewalInfo(TestingUtility.readFile("mock_signed_data/renewalInfo"));
Assertions.assertEquals(Environment.SANDBOX, renewalInfo.getEnvironment());
}
@Test
- public void testTransactionInfoDecoding() throws VerificationException {
- SignedDataVerifier verifier = getSignedPayloadVerifier();
- JWSTransactionDecodedPayload transaction = verifier.verifyAndDecodeTransaction(TRANSACTION_INFO);
+ public void testTransactionInfoDecoding() throws VerificationException, IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.SANDBOX, "com.example");
+ JWSTransactionDecodedPayload transaction = verifier.verifyAndDecodeTransaction(TestingUtility.readFile("mock_signed_data/transactionInfo"));
Assertions.assertEquals(Environment.SANDBOX, transaction.getEnvironment());
}
@Test
- public void testMalformedJWTWithTooManyParts() {
- SignedDataVerifier verifier = getSignedPayloadVerifier();
+ public void testMalformedJWTWithTooManyParts() throws IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier();
VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification("a.b.c.d"));
Assertions.assertEquals(Status.VERIFICATION_FAILURE, exception.getStatus());
}
@Test
- public void testMalformedJWTWithMalformedData() {
- SignedDataVerifier verifier = getSignedPayloadVerifier();
+ public void testMalformedJWTWithMalformedData() throws IOException {
+ SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier();
VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification("a.b.c"));
Assertions.assertEquals(Status.VERIFICATION_FAILURE, exception.getStatus());
}
-
- private SignedDataVerifier getSignedPayloadVerifier() {
- return new SignedDataVerifier(Set.of(new ByteArrayInputStream(Base64.getDecoder().decode(ROOT_CA_BASE64_ENCODED))), "com.example", 1234L, Environment.SANDBOX, false);
- }
}
diff --git a/src/test/java/com/apple/itunes/storekit/verification/XcodeSignedDataVerifierTest.java b/src/test/java/com/apple/itunes/storekit/verification/XcodeSignedDataVerifierTest.java
index 2c7536c6..ff42499d 100644
--- a/src/test/java/com/apple/itunes/storekit/verification/XcodeSignedDataVerifierTest.java
+++ b/src/test/java/com/apple/itunes/storekit/verification/XcodeSignedDataVerifierTest.java
@@ -39,14 +39,16 @@ public void testXcodeSignedAppTransaction() throws IOException, VerificationExce
Assertions.assertEquals("cYUsXc53EbYc0pOeXG5d6/31LGHeVGf84sqSN0OrJi5u/j2H89WWKgS8N0hMsMlf", appTransaction.getDeviceVerification());
Assertions.assertEquals(UUID.fromString("48c8b92d-ce0d-4229-bedf-e61b4f9cfc92"), appTransaction.getDeviceVerificationNonce());
Assertions.assertNull(appTransaction.getPreorderDate());
+ Assertions.assertEquals(Environment.XCODE, appTransaction.getReceiptType());
+ Assertions.assertEquals("Xcode", appTransaction.getRawReceiptType());
}
@Test
public void testXcodeSignedTransaction() throws IOException, VerificationException {
SignedDataVerifier verifier = TestingUtility.getSignedPayloadVerifier(Environment.XCODE, XCODE_BUNDLE_ID);
- String encodedTransactino = TestingUtility.readFile("xcode/xcode-signed-transaction");
+ String encodedTransaction = TestingUtility.readFile("xcode/xcode-signed-transaction");
- JWSTransactionDecodedPayload transaction = verifier.verifyAndDecodeTransaction(encodedTransactino);
+ JWSTransactionDecodedPayload transaction = verifier.verifyAndDecodeTransaction(encodedTransaction);
Assertions.assertEquals("0", transaction.getOriginalTransactionId());
Assertions.assertEquals("0", transaction.getTransactionId());
@@ -59,18 +61,23 @@ public void testXcodeSignedTransaction() throws IOException, VerificationExcepti
Assertions.assertEquals(1700358336049L, transaction.getExpiresDate());
Assertions.assertEquals(1, transaction.getQuantity());
Assertions.assertEquals(Type.AUTO_RENEWABLE_SUBSCRIPTION, transaction.getType());
+ Assertions.assertEquals("Auto-Renewable Subscription", transaction.getRawType());
Assertions.assertNull(transaction.getAppAccountToken());
Assertions.assertEquals(InAppOwnershipType.PURCHASED, transaction.getInAppOwnershipType());
+ Assertions.assertEquals("PURCHASED", transaction.getRawInAppOwnershipType());
Assertions.assertEquals(1697679936056L, transaction.getSignedDate());
Assertions.assertNull(transaction.getRevocationReason());
Assertions.assertNull(transaction.getRevocationDate());
Assertions.assertFalse(transaction.getIsUpgraded());
Assertions.assertEquals(OfferType.INTRODUCTORY_OFFER, transaction.getOfferType());
+ Assertions.assertEquals(1, transaction.getRawOfferType());
Assertions.assertNull(transaction.getOfferIdentifier());
Assertions.assertEquals(Environment.XCODE, transaction.getEnvironment());
+ Assertions.assertEquals("Xcode", transaction.getRawEnvironment());
Assertions.assertEquals("USA", transaction.getStorefront());
Assertions.assertEquals("143441", transaction.getStorefrontId());
Assertions.assertEquals(TransactionReason.PURCHASE, transaction.getTransactionReason());
+ Assertions.assertEquals("PURCHASE", transaction.getRawTransactionReason());
}
@Test
@@ -86,6 +93,7 @@ public void testXcodeSignedRenewalInfo() throws IOException, VerificationExcepti
Assertions.assertEquals("pass.premium", renewalInfo.getAutoRenewProductId());
Assertions.assertEquals("pass.premium", renewalInfo.getProductId());
Assertions.assertEquals(AutoRenewStatus.ON, renewalInfo.getAutoRenewStatus());
+ Assertions.assertEquals(1, renewalInfo.getRawAutoRenewStatus());
Assertions.assertNull(renewalInfo.getIsInBillingRetryPeriod());
Assertions.assertNull(renewalInfo.getPriceIncreaseStatus());
Assertions.assertNull(renewalInfo.getGracePeriodExpiresDate());
@@ -93,6 +101,7 @@ public void testXcodeSignedRenewalInfo() throws IOException, VerificationExcepti
Assertions.assertNull(renewalInfo.getOfferIdentifier());
Assertions.assertEquals(1697679936711L, renewalInfo.getSignedDate());
Assertions.assertEquals(Environment.XCODE, renewalInfo.getEnvironment());
+ Assertions.assertEquals("Xcode", renewalInfo.getRawEnvironment());
Assertions.assertEquals(1697679936049L, renewalInfo.getRecentSubscriptionStartDate());
Assertions.assertEquals(1700358336049L, renewalInfo.getRenewalDate());
}
diff --git a/src/test/resources/testCA.key b/src/test/resources/certs/testCA.key
similarity index 100%
rename from src/test/resources/testCA.key
rename to src/test/resources/certs/testCA.key
diff --git a/src/test/resources/testCA.pem b/src/test/resources/certs/testCA.pem
similarity index 100%
rename from src/test/resources/testCA.pem
rename to src/test/resources/certs/testCA.pem
diff --git a/src/test/resources/testIntermediate.key b/src/test/resources/certs/testIntermediate.key
similarity index 100%
rename from src/test/resources/testIntermediate.key
rename to src/test/resources/certs/testIntermediate.key
diff --git a/src/test/resources/testIntermediate.pem b/src/test/resources/certs/testIntermediate.pem
similarity index 100%
rename from src/test/resources/testIntermediate.pem
rename to src/test/resources/certs/testIntermediate.pem
diff --git a/src/test/resources/testInvalidIntermediateLeaf.pem b/src/test/resources/certs/testInvalidIntermediateLeaf.pem
similarity index 100%
rename from src/test/resources/testInvalidIntermediateLeaf.pem
rename to src/test/resources/certs/testInvalidIntermediateLeaf.pem
diff --git a/src/test/resources/testInvalidOIDIntermediate.pem b/src/test/resources/certs/testInvalidOIDIntermediate.pem
similarity index 100%
rename from src/test/resources/testInvalidOIDIntermediate.pem
rename to src/test/resources/certs/testInvalidOIDIntermediate.pem
diff --git a/src/test/resources/testInvalidOIDLeaf.pem b/src/test/resources/certs/testInvalidOIDLeaf.pem
similarity index 100%
rename from src/test/resources/testInvalidOIDLeaf.pem
rename to src/test/resources/certs/testInvalidOIDLeaf.pem
diff --git a/src/test/resources/testLeaf.key b/src/test/resources/certs/testLeaf.key
similarity index 100%
rename from src/test/resources/testLeaf.key
rename to src/test/resources/certs/testLeaf.key
diff --git a/src/test/resources/testLeaf.pem b/src/test/resources/certs/testLeaf.pem
similarity index 100%
rename from src/test/resources/testLeaf.pem
rename to src/test/resources/certs/testLeaf.pem
diff --git a/src/test/resources/testSigningKey.p8 b/src/test/resources/certs/testSigningKey.p8
similarity index 100%
rename from src/test/resources/testSigningKey.p8
rename to src/test/resources/certs/testSigningKey.p8
diff --git a/src/test/resources/mock_signed_data/legacyTransaction b/src/test/resources/mock_signed_data/legacyTransaction
new file mode 100644
index 00000000..534391da
--- /dev/null
+++ b/src/test/resources/mock_signed_data/legacyTransaction
@@ -0,0 +1 @@
+ewoicHVyY2hhc2UtaW5mbyIgPSAiZXdvaWRISmhibk5oWTNScGIyNHRhV1FpSUQwZ0lqTXpPVGt6TXprNUlqc0tmUW89IjsKfQo=
\ No newline at end of file
diff --git a/src/test/resources/mock_signed_data/missingX5CHeaderClaim b/src/test/resources/mock_signed_data/missingX5CHeaderClaim
new file mode 100644
index 00000000..b4112b4a
--- /dev/null
+++ b/src/test/resources/mock_signed_data/missingX5CHeaderClaim
@@ -0,0 +1 @@
+eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1Y3dyb25nIjpbIk1JSUJvRENDQVVhZ0F3SUJBZ0lCRERBS0JnZ3Foa2pPUFFRREF6QkZNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1CNFhEVEl6TURFd05USXhNekV6TkZvWERUTXpNREV3TVRJeE16RXpORm93UFRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eERUQUxCZ05WQkFvTUJFeGxZV1l3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRpdFlIRWFZVnVjOGc5QWpUT3dFck12R3lQeWtQYStwdXZUSThoSlRIWlpETEdhczJxWDErRXJ4Z1FUSmdWWHY3Nm5tTGhoUkpIK2oyNUFpQUk4aUdzb3k4d0xUQUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVFCZ29xaGtpRzkyTmtCZ3NCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05JQURCRkFpQlg0YytUMEZwNW5KNVFSQ2xSZnU1UFNCeVJ2TlB0dWFUc2swdlBCM1dBSUFJaEFOZ2FhdUFqL1lQOXMwQWtFaHlKaHhRTy82UTJ6b3VaK0gxQ0lPZWhuTXpRIiwiTUlJQm56Q0NBVVdnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQXpBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1CNFhEVEl6TURFd05USXhNekV3TlZvWERUTXpNREV3TVRJeE16RXdOVm93UlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eEZUQVRCZ05WQkFvTURFbHVkR1Z5YldWa2FXRjBaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCQlVONVY5cktqZlJpTUFJb2pFQTBBdjVNcDBvRitPMGNMNGd6clRGMTc4aW5VSHVnajdFdDQ2TnJrUTdoS2dNVm5qb2dxNDVRMXJNcytjTUhWTklMV3FqTlRBek1BOEdBMVVkRXdRSU1BWUJBZjhDQVFBd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJBR0NpcUdTSWIzWTJRR0FnRUVBZ1VBTUFvR0NDcUdTTTQ5QkFNREEwZ0FNRVVDSVFDbXNJS1lzNDF1bGxzc0hYNHJWdmVVVDBaN0lzNS9oTEsxbEZQVHR1bjNoQUlnYzIrMlJHNStnTmNGVmNzK1hKZUVsNEdaK29qbDNST09tbGwreWU3ZHluUT0iLCJNSUlCZ2pDQ0FTbWdBd0lCQWdJSkFMVWM1QUxpSDVwYk1Bb0dDQ3FHU000OUJBTURNRFl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh3SGhjTk1qTXdNVEExTWpFek1ESXlXaGNOTXpNd01UQXlNakV6TURJeVdqQTJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVjKy9CbCtnb3NwbzZ0ZjlaN2lvNXRkS2RybE4xWWRWbnFFaEVEWERTaHpkQUpQUWlqYW1YSU1IZjh4V1dUYTF6Z29ZVHhPS3BidUp0RHBsejFYcmlUYU1nTUI0d0RBWURWUjBUQkFVd0F3RUIvekFPQmdOVkhROEJBZjhFQkFNQ0FRWXdDZ1lJS29aSXpqMEVBd01EUndBd1JBSWdlbVdRWG5NQWRUYWQySkRKV25nOVU0dUJCTDVtQTdXSTA1SDdvSDdjNmlRQ0lIaVJxTWpOZnpVQXlpdTloNnJPVS9LK2lUUjBJLzNZL05TV3NYSFgrYWNjIl19.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.1TFhjDR4WwQJNgizVGYXz3WE3ajxTdH1wKLQQ71MtrkadSxxOo3yPo_6L9Z03unIU7YK-NRNzSIb5bh5WqTprQ
\ No newline at end of file
diff --git a/src/test/resources/mock_signed_data/renewalInfo b/src/test/resources/mock_signed_data/renewalInfo
new file mode 100644
index 00000000..d14a20dc
--- /dev/null
+++ b/src/test/resources/mock_signed_data/renewalInfo
@@ -0,0 +1 @@
+eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJzaWduZWREYXRlIjoxNjcyOTU2MTU0MDAwfQ.FbK2OL-t6l4892W7fzWyus_g9mIl2CzWLbVt7Kgcnt6zzVulF8bzovgpe0v_y490blROGixy8KDoe2dSU53-Xw
\ No newline at end of file
diff --git a/src/test/resources/mock_signed_data/testNotification b/src/test/resources/mock_signed_data/testNotification
new file mode 100644
index 00000000..7bb78cf1
--- /dev/null
+++ b/src/test/resources/mock_signed_data/testNotification
@@ -0,0 +1 @@
+eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImFwcEFwcGxlSWQiOjEyMzQsImVudmlyb25tZW50IjoiU2FuZGJveCIsImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsInNpZ25lZERhdGUiOjE2ODEzMTQzMjQwMDAsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.VVXYwuNm2Y3XsOUva-BozqatRCsDuykA7xIe_CCRw6aIAAxJ1nb2sw871jfZ6dcgNhUuhoZ93hfbc1v_5zB7Og
\ No newline at end of file
diff --git a/src/test/resources/mock_signed_data/transactionInfo b/src/test/resources/mock_signed_data/transactionInfo
new file mode 100644
index 00000000..3ddf0b02
--- /dev/null
+++ b/src/test/resources/mock_signed_data/transactionInfo
@@ -0,0 +1 @@
+eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlIiwic2lnbmVkRGF0ZSI6MTY3Mjk1NjE1NDAwMH0.PnHWpeIJZ8f2Q218NSGLo_aR0IBEJvC6PxmxKXh-qfYTrZccx2suGl223OSNAX78e4Ylf2yJCG2N-FfU-NIhZQ
\ No newline at end of file
diff --git a/src/test/resources/mock_signed_data/wrongBundleId b/src/test/resources/mock_signed_data/wrongBundleId
new file mode 100644
index 00000000..cc7091e6
--- /dev/null
+++ b/src/test/resources/mock_signed_data/wrongBundleId
@@ -0,0 +1 @@
+eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUud3JvbmcifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.WWE31hTB_mcv2O_lf-xI-MNY3d8txc0MzpqFx4QnYDfFIxB95Lo2Fm3r46YSjLLdL7xCWdEJrJP5bHgRCejAGg
\ No newline at end of file
diff --git a/src/test/resources/models/apiException.json b/src/test/resources/models/apiException.json
new file mode 100644
index 00000000..a270c53f
--- /dev/null
+++ b/src/test/resources/models/apiException.json
@@ -0,0 +1,4 @@
+{
+ "errorCode": 5000000,
+ "errorMessage": "An unknown error occurred."
+}
\ No newline at end of file
diff --git a/src/test/resources/models/apiTooManyRequestsException.json b/src/test/resources/models/apiTooManyRequestsException.json
new file mode 100644
index 00000000..3b3cb9c9
--- /dev/null
+++ b/src/test/resources/models/apiTooManyRequestsException.json
@@ -0,0 +1,4 @@
+{
+ "errorCode": 4290000,
+ "errorMessage": "Rate limit exceeded."
+}
\ No newline at end of file
diff --git a/src/test/resources/models/appTransaction.json b/src/test/resources/models/appTransaction.json
new file mode 100644
index 00000000..b3b937b8
--- /dev/null
+++ b/src/test/resources/models/appTransaction.json
@@ -0,0 +1,13 @@
+{
+ "receiptType": "LocalTesting",
+ "appAppleId": 531412,
+ "bundleId": "com.example",
+ "applicationVersion": "1.2.3",
+ "versionExternalIdentifier": 512,
+ "receiptCreationDate": 1698148900000,
+ "originalPurchaseDate": 1698148800000,
+ "originalApplicationVersion": "1.1.2",
+ "deviceVerification": "device_verification_value",
+ "deviceVerificationNonce": "48ccfa42-7431-4f22-9908-7e88983e105a",
+ "preorderDate": 1698148700000
+}
\ No newline at end of file
diff --git a/src/test/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json b/src/test/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json
new file mode 100644
index 00000000..21fd582d
--- /dev/null
+++ b/src/test/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json
@@ -0,0 +1,3 @@
+{
+ "requestIdentifier": "758883e8-151b-47b7-abd0-60c4d804c2f5"
+}
\ No newline at end of file
diff --git a/src/test/resources/models/extendSubscriptionRenewalDateResponse.json b/src/test/resources/models/extendSubscriptionRenewalDateResponse.json
new file mode 100644
index 00000000..6c5f89f0
--- /dev/null
+++ b/src/test/resources/models/extendSubscriptionRenewalDateResponse.json
@@ -0,0 +1,6 @@
+{
+ "originalTransactionId": "2312412",
+ "webOrderLineItemId": "9993",
+ "success": true,
+ "effectiveDate": 1698148900000
+}
\ No newline at end of file
diff --git a/src/test/resources/models/getAllSubscriptionStatusesResponse.json b/src/test/resources/models/getAllSubscriptionStatusesResponse.json
new file mode 100644
index 00000000..b5d139c1
--- /dev/null
+++ b/src/test/resources/models/getAllSubscriptionStatusesResponse.json
@@ -0,0 +1,35 @@
+{
+ "environment": "LocalTesting",
+ "bundleId": "com.example",
+ "appAppleId": 5454545,
+ "data": [
+ {
+ "subscriptionGroupIdentifier": "sub_group_one",
+ "lastTransactions": [
+ {
+ "status": 1,
+ "originalTransactionId": "3749183",
+ "signedTransactionInfo": "signed_transaction_one",
+ "signedRenewalInfo": "signed_renewal_one"
+ },
+ {
+ "status": 5,
+ "originalTransactionId": "5314314134",
+ "signedTransactionInfo": "signed_transaction_two",
+ "signedRenewalInfo": "signed_renewal_two"
+ }
+ ]
+ },
+ {
+ "subscriptionGroupIdentifier": "sub_group_two",
+ "lastTransactions": [
+ {
+ "status": 2,
+ "originalTransactionId": "3413453",
+ "signedTransactionInfo": "signed_transaction_three",
+ "signedRenewalInfo": "signed_renewal_three"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/models/getNotificationHistoryResponse.json b/src/test/resources/models/getNotificationHistoryResponse.json
new file mode 100644
index 00000000..75c27b7e
--- /dev/null
+++ b/src/test/resources/models/getNotificationHistoryResponse.json
@@ -0,0 +1,27 @@
+{
+ "paginationToken": "57715481-805a-4283-8499-1c19b5d6b20a",
+ "hasMore": true,
+ "notificationHistory": [
+ {
+ "sendAttempts": [
+ {
+ "attemptDate": 1698148900000,
+ "sendAttemptResult": "NO_RESPONSE"
+ }, {
+ "attemptDate": 1698148950000,
+ "sendAttemptResult": "SUCCESS"
+ }
+ ],
+ "signedPayload": "signed_payload_one"
+ },
+ {
+ "sendAttempts": [
+ {
+ "attemptDate": 1698148800000,
+ "sendAttemptResult": "CIRCULAR_REDIRECT"
+ }
+ ],
+ "signedPayload": "signed_payload_two"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/models/getRefundHistoryResponse.json b/src/test/resources/models/getRefundHistoryResponse.json
new file mode 100644
index 00000000..d1ff65a9
--- /dev/null
+++ b/src/test/resources/models/getRefundHistoryResponse.json
@@ -0,0 +1,8 @@
+{
+ "signedTransactions": [
+ "signed_transaction_one",
+ "signed_transaction_two"
+ ],
+ "revision": "revision_output",
+ "hasMore": true
+}
\ No newline at end of file
diff --git a/src/test/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json b/src/test/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json
new file mode 100644
index 00000000..9bd7ddc2
--- /dev/null
+++ b/src/test/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json
@@ -0,0 +1,7 @@
+{
+ "requestIdentifier": "20fba8a0-2b80-4a7d-a17f-85c1854727f8",
+ "complete": true,
+ "completeDate": 1698148900000,
+ "succeededCount": 30,
+ "failedCount": 2
+}
\ No newline at end of file
diff --git a/src/test/resources/models/getTestNotificationStatusResponse.json b/src/test/resources/models/getTestNotificationStatusResponse.json
new file mode 100644
index 00000000..9f83b901
--- /dev/null
+++ b/src/test/resources/models/getTestNotificationStatusResponse.json
@@ -0,0 +1,12 @@
+{
+ "signedPayload": "signed_payload",
+ "sendAttempts": [
+ {
+ "attemptDate": 1698148900000,
+ "sendAttemptResult": "NO_RESPONSE"
+ }, {
+ "attemptDate": 1698148950000,
+ "sendAttemptResult": "SUCCESS"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/models/lookupOrderIdResponse.json b/src/test/resources/models/lookupOrderIdResponse.json
new file mode 100644
index 00000000..09a43d55
--- /dev/null
+++ b/src/test/resources/models/lookupOrderIdResponse.json
@@ -0,0 +1,7 @@
+{
+ "status": 1,
+ "signedTransactions": [
+ "signed_transaction_one",
+ "signed_transaction_two"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/models/requestTestNotificationResponse.json b/src/test/resources/models/requestTestNotificationResponse.json
new file mode 100644
index 00000000..3ecc9e21
--- /dev/null
+++ b/src/test/resources/models/requestTestNotificationResponse.json
@@ -0,0 +1,3 @@
+{
+ "testNotificationToken": "ce3af791-365e-4c60-841b-1674b43c1609"
+}
\ No newline at end of file
diff --git a/src/test/resources/models/signedNotification.json b/src/test/resources/models/signedNotification.json
new file mode 100644
index 00000000..bd473c44
--- /dev/null
+++ b/src/test/resources/models/signedNotification.json
@@ -0,0 +1,16 @@
+{
+ "notificationType": "SUBSCRIBED",
+ "subtype": "INITIAL_BUY",
+ "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20",
+ "data": {
+ "environment": "LocalTesting",
+ "appAppleId": 41234,
+ "bundleId": "com.example",
+ "bundleVersion": "1.2.3",
+ "signedTransactionInfo": "signed_transaction_info_value",
+ "signedRenewalInfo": "signed_renewal_info_value",
+ "status": 1
+ },
+ "version": "2.0",
+ "signedDate": 1698148900000
+}
\ No newline at end of file
diff --git a/src/test/resources/models/signedRenewalInfo.json b/src/test/resources/models/signedRenewalInfo.json
new file mode 100644
index 00000000..17c07a8e
--- /dev/null
+++ b/src/test/resources/models/signedRenewalInfo.json
@@ -0,0 +1,16 @@
+{
+ "expirationIntent": 1,
+ "originalTransactionId": "12345",
+ "autoRenewProductId": "com.example.product.2",
+ "productId": "com.example.product",
+ "autoRenewStatus": 1,
+ "isInBillingRetryPeriod": true,
+ "priceIncreaseStatus": 0,
+ "gracePeriodExpiresDate": 1698148900000,
+ "offerType": 2,
+ "offerIdentifier": "abc.123",
+ "signedDate": 1698148800000,
+ "environment": "LocalTesting",
+ "recentSubscriptionStartDate": 1698148800000,
+ "renewalDate": 1698148850000
+}
\ No newline at end of file
diff --git a/src/test/resources/models/signedSummaryNotification.json b/src/test/resources/models/signedSummaryNotification.json
new file mode 100644
index 00000000..3a22ec09
--- /dev/null
+++ b/src/test/resources/models/signedSummaryNotification.json
@@ -0,0 +1,21 @@
+{
+ "notificationType": "RENEWAL_EXTENSION",
+ "subtype": "SUMMARY",
+ "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20",
+ "version": "2.0",
+ "signedDate": 1698148900000,
+ "summary": {
+ "environment": "LocalTesting",
+ "appAppleId": 41234,
+ "bundleId": "com.example",
+ "productId": "com.example.product",
+ "requestIdentifier": "efb27071-45a4-4aca-9854-2a1e9146f265",
+ "storefrontCountryCodes": [
+ "CAN",
+ "USA",
+ "MEX"
+ ],
+ "succeededCount": 5,
+ "failedCount": 2
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/models/signedTransaction.json b/src/test/resources/models/signedTransaction.json
new file mode 100644
index 00000000..3c44a569
--- /dev/null
+++ b/src/test/resources/models/signedTransaction.json
@@ -0,0 +1,25 @@
+{
+ "transactionId":"23456",
+ "originalTransactionId":"12345",
+ "webOrderLineItemId":"34343",
+ "bundleId":"com.example",
+ "productId":"com.example.product",
+ "subscriptionGroupIdentifier":"55555",
+ "purchaseDate":1698148900000,
+ "originalPurchaseDate":1698148800000,
+ "expiresDate":1698149000000,
+ "quantity":1,
+ "type":"Auto-Renewable Subscription",
+ "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138",
+ "inAppOwnershipType":"PURCHASED",
+ "signedDate":1698148900000,
+ "revocationReason": 1,
+ "revocationDate": 1698148950000,
+ "isUpgraded": true,
+ "offerType":1,
+ "offerIdentifier": "abc.123",
+ "environment":"LocalTesting",
+ "transactionReason":"PURCHASE",
+ "storefront":"USA",
+ "storefrontId":"143441"
+}
\ No newline at end of file
diff --git a/src/test/resources/models/transactionHistoryResponse.json b/src/test/resources/models/transactionHistoryResponse.json
new file mode 100644
index 00000000..c5cc6383
--- /dev/null
+++ b/src/test/resources/models/transactionHistoryResponse.json
@@ -0,0 +1,11 @@
+{
+ "revision": "revision_output",
+ "hasMore": true,
+ "bundleId": "com.example",
+ "appAppleId": 323232,
+ "environment": "LocalTesting",
+ "signedTransactions": [
+ "signed_transaction_value",
+ "signed_transaction_value2"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/models/transactionInfoResponse.json b/src/test/resources/models/transactionInfoResponse.json
new file mode 100644
index 00000000..57d84e20
--- /dev/null
+++ b/src/test/resources/models/transactionInfoResponse.json
@@ -0,0 +1,3 @@
+{
+ "signedTransactionInfo": "signed_transaction_info_value"
+}
\ No newline at end of file