diff --git a/src/main/java/com/apple/itunes/storekit/client/APIError.java b/src/main/java/com/apple/itunes/storekit/client/APIError.java
index e97ffd12..22c5fd5b 100644
--- a/src/main/java/com/apple/itunes/storekit/client/APIError.java
+++ b/src/main/java/com/apple/itunes/storekit/client/APIError.java
@@ -282,8 +282,16 @@ public enum APIError {
*
* @see InvalidTransactionNotConsumableError
*/
+ @Deprecated
INVALID_TRANSACTION_NOT_CONSUMABLE(4000043L),
+ /**
+ * An error that indicates the transaction identifier represents an unsupported in-app purchase type.
+ *
+ * @see InvalidTransactionTypeNotSupportedError
+ */
+ INVALID_TRANSACTION_TYPE_NOT_SUPPORTED(4000047L),
+
/**
* An error that indicates the subscription doesn't qualify for a renewal-date extension due to its subscription state.
*
diff --git a/src/main/java/com/apple/itunes/storekit/model/ConsumptionRequest.java b/src/main/java/com/apple/itunes/storekit/model/ConsumptionRequest.java
index 3540ed6e..401ffb5a 100644
--- a/src/main/java/com/apple/itunes/storekit/model/ConsumptionRequest.java
+++ b/src/main/java/com/apple/itunes/storekit/model/ConsumptionRequest.java
@@ -24,6 +24,7 @@ public class ConsumptionRequest {
private static final String SERIALIZED_NAME_LIFETIME_DOLLARS_REFUNDED = "lifetimeDollarsRefunded";
private static final String SERIALIZED_NAME_LIFETIME_DOLLARS_PURCHASED = "lifetimeDollarsPurchased";
private static final String SERIALIZED_NAME_USER_STATUS = "userStatus";
+ private static final String SERIALIZED_NAME_REFUND_PREFERENCE = "refundPreference";
@JsonProperty(SERIALIZED_NAME_CUSTOMER_CONSENTED)
private Boolean customerConsented;
@JsonProperty(SERIALIZED_NAME_CONSUMPTION_STATUS)
@@ -46,6 +47,8 @@ public class ConsumptionRequest {
private Integer lifetimeDollarsPurchased;
@JsonProperty(SERIALIZED_NAME_USER_STATUS)
private Integer userStatus;
+ @JsonProperty(SERIALIZED_NAME_REFUND_PREFERENCE)
+ private Integer refundPreference;
public ConsumptionRequest() {
@@ -348,6 +351,36 @@ public void setRawUserStatus(Integer rawUserStatus) {
this.userStatus = rawUserStatus;
}
+ public ConsumptionRequest refundPreference(RefundPreference refundPreference) {
+ this.refundPreference = refundPreference != null ? refundPreference.getValue() : null;
+ return this;
+ }
+
+ /**
+ * A value that indicates your preference, based on your operational logic, as to whether Apple should grant the refund.
+ *
+ * @return refundPreference
+ * @see refundPreference
+ **/
+ public RefundPreference getRefundPreference() {
+ return refundPreference != null ? RefundPreference.fromValue(refundPreference) : null;
+ }
+
+ /**
+ * @see #getRefundPreference()
+ */
+ public Integer getRawRefundPreference() {
+ return refundPreference;
+ }
+
+ public void setRefundPreference(RefundPreference refundPreference) {
+ this.refundPreference = refundPreference != null ? refundPreference.getValue() : null;
+ }
+
+ public void setRawRefundPreference(Integer rawRefundPreference) {
+ this.refundPreference = rawRefundPreference;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -367,12 +400,13 @@ public boolean equals(Object o) {
Objects.equals(this.playTime, consumptionRequest.playTime) &&
Objects.equals(this.lifetimeDollarsRefunded, consumptionRequest.lifetimeDollarsRefunded) &&
Objects.equals(this.lifetimeDollarsPurchased, consumptionRequest.lifetimeDollarsPurchased) &&
- Objects.equals(this.userStatus, consumptionRequest.userStatus);
+ Objects.equals(this.userStatus, consumptionRequest.userStatus) &&
+ Objects.equals(this.refundPreference, consumptionRequest.refundPreference);
}
@Override
public int hashCode() {
- return Objects.hash(customerConsented, consumptionStatus, platform, sampleContentProvided, deliveryStatus, appAccountToken, accountTenure, playTime, lifetimeDollarsRefunded, lifetimeDollarsPurchased, userStatus);
+ return Objects.hash(customerConsented, consumptionStatus, platform, sampleContentProvided, deliveryStatus, appAccountToken, accountTenure, playTime, lifetimeDollarsRefunded, lifetimeDollarsPurchased, userStatus, refundPreference);
}
@Override
@@ -389,6 +423,7 @@ public String toString() {
", lifetimeDollarsRefunded=" + lifetimeDollarsRefunded +
", lifetimeDollarsPurchased=" + lifetimeDollarsPurchased +
", userStatus=" + userStatus +
+ ", refundPreference=" + refundPreference +
'}';
}
}
diff --git a/src/main/java/com/apple/itunes/storekit/model/ConsumptionRequestReason.java b/src/main/java/com/apple/itunes/storekit/model/ConsumptionRequestReason.java
new file mode 100644
index 00000000..6c7e3d66
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/ConsumptionRequestReason.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2024 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * The customer-provided reason for a refund request.
+ *
+ * @see consumptionRequestReason
+ */
+public enum ConsumptionRequestReason {
+
+ UNINTENDED_PURCHASE("UNINTENDED_PURCHASE"),
+ FULFILLMENT_ISSUE("FULFILLMENT_ISSUE"),
+ UNSATISFIED_WITH_PURCHASE("UNSATISFIED_WITH_PURCHASE"),
+ LEGAL("LEGAL"),
+ OTHER("OTHER");
+
+ private final String value;
+
+ ConsumptionRequestReason(String value) {
+ this.value = value;
+ }
+
+ public static ConsumptionRequestReason fromValue(String value) {
+ for (ConsumptionRequestReason b : ConsumptionRequestReason.values()) {
+ if (b.value.equals(value)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+}
+
diff --git a/src/main/java/com/apple/itunes/storekit/model/Data.java b/src/main/java/com/apple/itunes/storekit/model/Data.java
index 919c9a30..4d3cd498 100644
--- a/src/main/java/com/apple/itunes/storekit/model/Data.java
+++ b/src/main/java/com/apple/itunes/storekit/model/Data.java
@@ -21,6 +21,7 @@ public class Data {
private static final String SERIALIZED_NAME_SIGNED_TRANSACTION_INFO = "signedTransactionInfo";
private static final String SERIALIZED_NAME_SIGNED_RENEWAL_INFO = "signedRenewalInfo";
private static final String SERIALIZED_NAME_STATUS = "status";
+ private static final String SERIALIZED_NAME_CONSUMPTION_REQUEST_REASON = "consumptionRequestReason";
@JsonProperty(SERIALIZED_NAME_ENVIRONMENT)
private String environment;
@JsonProperty(SERIALIZED_NAME_APP_APPLE_ID)
@@ -35,6 +36,8 @@ public class Data {
private String signedRenewalInfo;
@JsonProperty(SERIALIZED_NAME_STATUS)
private Integer status;
+ @JsonProperty(SERIALIZED_NAME_CONSUMPTION_REQUEST_REASON)
+ private String consumptionRequestReason;
@JsonAnySetter
private Map unknownFields;
@@ -197,6 +200,36 @@ public void setRawStatus(Integer rawStatus) {
this.status = rawStatus;
}
+ public Data consumptionRequestReason(ConsumptionRequestReason consumptionRequestReason) {
+ this.consumptionRequestReason = consumptionRequestReason != null ? consumptionRequestReason.getValue() : null;
+ return this;
+ }
+
+ /**
+ * The reason the customer requested the refund.
+ *
+ * @return consumptionRequestReason
+ * @see consumptionRequestReason
+ **/
+ public ConsumptionRequestReason getConsumptionRequestReason() {
+ return consumptionRequestReason != null ? ConsumptionRequestReason.fromValue(consumptionRequestReason) : null;
+ }
+
+ /**
+ * @see #getConsumptionRequestReason()
+ */
+ public String getRawConsumptionRequestReason() {
+ return consumptionRequestReason;
+ }
+
+ public void setConsumptionRequestReason(ConsumptionRequestReason consumptionRequestReason) {
+ this.consumptionRequestReason = consumptionRequestReason != null ? consumptionRequestReason.getValue() : null;
+ }
+
+ public void setRawConsumptionRequestReason(String rawConsumptionRequestReason) {
+ this.consumptionRequestReason = rawConsumptionRequestReason;
+ }
+
public Data unknownFields(Map unknownFields) {
this.unknownFields = unknownFields;
return this;
@@ -231,12 +264,13 @@ public boolean equals(Object o) {
Objects.equals(this.signedTransactionInfo, data.signedTransactionInfo) &&
Objects.equals(this.signedRenewalInfo, data.signedRenewalInfo) &&
Objects.equals(this.status, data.status) &&
+ Objects.equals(this.consumptionRequestReason, data.consumptionRequestReason) &&
Objects.equals(this.unknownFields, data.unknownFields);
}
@Override
public int hashCode() {
- return Objects.hash(environment, appAppleId, bundleId, bundleVersion, signedTransactionInfo, signedRenewalInfo, status, unknownFields);
+ return Objects.hash(environment, appAppleId, bundleId, bundleVersion, signedTransactionInfo, signedRenewalInfo, status, consumptionRequestReason, unknownFields);
}
@Override
@@ -249,6 +283,7 @@ public String toString() {
", signedTransactionInfo='" + signedTransactionInfo + '\'' +
", signedRenewalInfo='" + signedRenewalInfo + '\'' +
", status=" + status +
+ ", consumptionRequestReason='" + consumptionRequestReason + '\'' +
", unknownFields=" + unknownFields +
'}';
}
diff --git a/src/main/java/com/apple/itunes/storekit/model/RefundPreference.java b/src/main/java/com/apple/itunes/storekit/model/RefundPreference.java
new file mode 100644
index 00000000..1aaa5907
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/RefundPreference.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2024 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * A value that indicates your preferred outcome for the refund request.
+ *
+ * @see refundPreference
+ */
+public enum RefundPreference {
+
+ UNDECLARED(0),
+ PREFER_GRANT(1),
+ PREFER_DECLINE(2),
+ NO_PREFERENCE(3);
+
+ private final Integer value;
+
+ RefundPreference(Integer value) {
+ this.value = value;
+ }
+
+ public static RefundPreference fromValue(Integer value) {
+ for (RefundPreference b : RefundPreference.values()) {
+ if (b.value.equals(value)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ @JsonValue
+ public Integer getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+}
+
diff --git a/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java b/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java
index c7b7d2f5..2ffe0e0e 100644
--- a/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java
+++ b/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java
@@ -28,6 +28,7 @@
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.RefundPreference;
import com.apple.itunes.storekit.model.SendAttemptItem;
import com.apple.itunes.storekit.model.SendAttemptResult;
import com.apple.itunes.storekit.model.SendTestNotificationResponse;
@@ -429,6 +430,7 @@ public void testSendConsumptionData() throws APIException, IOException {
Assertions.assertEquals(6, ((Number) root.get("lifetimeDollarsRefunded")).intValue());
Assertions.assertEquals(7, ((Number) root.get("lifetimeDollarsPurchased")).intValue());
Assertions.assertEquals(4, ((Number) root.get("userStatus")).intValue());
+ Assertions.assertEquals(3, ((Number) root.get("refundPreference")).intValue());
});
ConsumptionRequest consumptionRequest = new ConsumptionRequest()
@@ -442,7 +444,8 @@ public void testSendConsumptionData() throws APIException, IOException {
.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);
+ .userStatus(UserStatus.LIMITED_ACCESS)
+ .refundPreference(RefundPreference.NO_PREFERENCE);
client.sendConsumptionData("49571273", consumptionRequest);
}
diff --git a/src/test/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayloadTest.java b/src/test/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayloadTest.java
index b2be5c52..c14bb7f1 100644
--- a/src/test/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayloadTest.java
+++ b/src/test/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayloadTest.java
@@ -42,6 +42,37 @@ public void testNotificationDecoding() throws IOException, NoSuchAlgorithmExcept
Assertions.assertEquals("signed_renewal_info_value", notification.getData().getSignedRenewalInfo());
Assertions.assertEquals(Status.ACTIVE, notification.getData().getStatus());
Assertions.assertEquals(1, notification.getData().getRawStatus());
+ Assertions.assertNull(notification.getData().getConsumptionRequestReason());
+ Assertions.assertNull(notification.getData().getRawConsumptionRequestReason());
+ }
+
+ @Test
+ public void testConsumptionRequestNotificationDecoding() throws IOException, NoSuchAlgorithmException, VerificationException {
+ String signedNotification = SignedDataCreator.createSignedDataFromJson("models/signedConsumptionRequestNotification.json");
+
+ ResponseBodyV2DecodedPayload notification = TestingUtility.getSignedPayloadVerifier().verifyAndDecodeNotification(signedNotification);
+
+ Assertions.assertEquals(NotificationTypeV2.CONSUMPTION_REQUEST, notification.getNotificationType());
+ Assertions.assertEquals("CONSUMPTION_REQUEST", notification.getRawNotificationType());
+ Assertions.assertNull(notification.getSubtype());
+ Assertions.assertNull(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.assertNull(notification.getExternalPurchaseToken());
+ 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());
+ Assertions.assertEquals(ConsumptionRequestReason.UNINTENDED_PURCHASE, notification.getData().getConsumptionRequestReason());
+ Assertions.assertEquals("UNINTENDED_PURCHASE", notification.getData().getRawConsumptionRequestReason());
}
@Test
diff --git a/src/test/resources/models/signedConsumptionRequestNotification.json b/src/test/resources/models/signedConsumptionRequestNotification.json
new file mode 100644
index 00000000..5cfa6ea1
--- /dev/null
+++ b/src/test/resources/models/signedConsumptionRequestNotification.json
@@ -0,0 +1,16 @@
+{
+ "notificationType": "CONSUMPTION_REQUEST",
+ "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,
+ "consumptionRequestReason": "UNINTENDED_PURCHASE"
+ },
+ "version": "2.0",
+ "signedDate": 1698148900000
+}
\ No newline at end of file