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 dc3a84d2..92225258 100644
--- a/src/main/java/com/apple/itunes/storekit/client/APIError.java
+++ b/src/main/java/com/apple/itunes/storekit/client/APIError.java
@@ -356,6 +356,62 @@ public enum APIError {
*/
TRANSACTION_ID_IS_NOT_ORIGINAL_TRANSACTION_ID_ERROR(4000187L),
+ /**
+ * An error the API returns that indicates the performance test request is invalid.
+ *
+ * @see InvalidPerformanceTestRequestError
+ */
+ INVALID_PERFORMANCE_TEST_REQUEST(4000211L),
+
+ /**
+ * An error that indicates the request ID is invalid.
+ *
+ * @see InvalidRequestIdError
+ */
+ INVALID_REQUEST_ID(4000212L),
+
+ /**
+ * An error that indicates an error with an existing test.
+ *
+ * @see ExistingPerformanceTestRunError
+ */
+ EXISTING_PERFORMANCE_TEST_RUN(4000213L),
+
+ /**
+ * An error that indicates the URL is invalid.
+ *
+ * @see BadRequestRealtimeUrlError
+ */
+ BAD_REQUEST_REALTIME_URL(4000215L),
+
+ /**
+ * An error that indicates the image size provided is invalid.
+ *
+ * @see BadRequestImageSizeError
+ */
+ BAD_REQUEST_IMAGE_SIZE(4000216L),
+
+ /**
+ * An error that indicates there are too many bullet points.
+ *
+ * @see BadRequestTooManyBulletPointsError
+ */
+ BAD_REQUEST_TOO_MANY_BULLET_POINTS(4000218L),
+
+ /**
+ * An error that indicates the text for a bullet point is too long.
+ *
+ * @see BadRequestBulletPointTextTooLongError
+ */
+ BAD_REQUEST_BULLET_POINT_TEXT_TOO_LONG(4000219L),
+
+ /**
+ * An error that indicates that no image object is included, but the request indicates that the header should be placed above the image.
+ *
+ * @see BadRequestAboveImageRequiresAnImageError
+ */
+ BAD_REQUEST_ABOVE_IMAGE_REQUIRES_AN_IMAGE(4000224L),
+
/**
* An error that indicates the subscription doesn't qualify for a renewal-date extension due to its subscription state.
*
@@ -412,6 +468,13 @@ public enum APIError {
*/
IMAGE_IN_USE(4030019L),
+ /**
+ * An error that indicates that passing a performance test is required before you can set a URL for the production environment.
+ *
+ * @see ForbiddenNoPassingTestError
+ */
+ FORBIDDEN_NO_PASSING_TEST(4030026L),
+
/**
* An error that indicates the App Store account wasn't found.
*
@@ -496,6 +559,13 @@ public enum APIError {
*/
MESSAGE_NOT_FOUND(4040015L),
+ /**
+ * An error the API returns if the service can’t find the specified test run.
+ *
+ * @see PerformanceTestRunNotFoundError
+ */
+ PERFORMANCE_TEST_RUN_NOT_FOUND(4040018L),
+
/**
* An error response that indicates an app transaction doesn’t exist for the specified customer.
*
@@ -503,6 +573,20 @@ public enum APIError {
*/
APP_TRANSACTION_DOES_NOT_EXIST_ERROR(4040019L),
+ /**
+ * An error that indicates a default message isn’t configured.
+ *
+ * @see DefaultMessageNotFoundError
+ */
+ DEFAULT_MESSAGE_NOT_FOUND(4040020L),
+
+ /**
+ * An error that indicates that the URL for your endpoint isn’t configured.
+ *
+ * @see RealtimeUrlNotFoundError
+ */
+ REALTIME_URL_NOT_FOUND(4040021L),
+
/**
* An error that indicates the image identifier already exists.
*
diff --git a/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java b/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java
index 4aa30f1a..6a73cd4b 100644
--- a/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java
+++ b/src/main/java/com/apple/itunes/storekit/client/BaseAppStoreServerAPIClient.java
@@ -7,6 +7,7 @@
import com.apple.itunes.storekit.model.ConsumptionRequest;
import com.apple.itunes.storekit.model.ConsumptionRequestV1;
import com.apple.itunes.storekit.model.DefaultConfigurationRequest;
+import com.apple.itunes.storekit.model.DefaultConfigurationResponse;
import com.apple.itunes.storekit.model.Environment;
import com.apple.itunes.storekit.model.ErrorPayload;
import com.apple.itunes.storekit.model.ExtendRenewalDateRequest;
@@ -14,12 +15,18 @@
import com.apple.itunes.storekit.model.GetImageListResponse;
import com.apple.itunes.storekit.model.GetMessageListResponse;
import com.apple.itunes.storekit.model.HistoryResponse;
+import com.apple.itunes.storekit.model.ImageSize;
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.OrderLookupResponse;
+import com.apple.itunes.storekit.model.PerformanceTestRequest;
+import com.apple.itunes.storekit.model.PerformanceTestResponse;
+import com.apple.itunes.storekit.model.PerformanceTestResultResponse;
+import com.apple.itunes.storekit.model.RealtimeUrlRequest;
+import com.apple.itunes.storekit.model.RealtimeUrlResponse;
import com.apple.itunes.storekit.model.RefundHistoryResponse;
import com.apple.itunes.storekit.model.SendTestNotificationResponse;
import com.apple.itunes.storekit.model.Status;
@@ -282,7 +289,7 @@ public HistoryResponse getTransactionHistory(String transactionId, String revisi
* Get a customer’s in-app purchase transaction history for your app.
*
* @param transactionId The identifier of a transaction that belongs to the customer, and which may be an original transaction identifier.
- * @param revision A token you provide to get the next set of up to 20 transactions. All responses include a revision token. Note: For requests that use the revision token, include the same query parameters from the initial request. Use the revision token from the previous HistoryResponse.
+ * @param revision A token you provide to get the next set of up to 20 transactions. All responses include a revision token. Note: For requests that use the revision token, include the same query parameters from the initial request. Use the revision token from the previous HistoryResponse.
* @param version The version of the Get Transaction History endpoint to use. V2 is recommended.
* @return A response that contains the customer’s transaction history for an app.
* @throws APIException If a response was returned indicating the request could not be processed
@@ -402,16 +409,29 @@ public void setAppAccountToken(String originalTransactionId, UpdateAppAccountTok
}
/**
- * Upload an image to use for retention messaging.
+ * @see #uploadImage(UUID, byte[], ImageSize)
+ */
+ @Deprecated(since = "5.1.0")
+ public void uploadImage(UUID imageIdentifier, byte[] image) throws APIException, IOException {
+ uploadImage(imageIdentifier, image, null);
+ }
+
+ /**
+ * Uploads an image to use for retention messaging.
*
* @param imageIdentifier A UUID you provide to uniquely identify the image you upload.
* @param image The image file to upload.
+ * @param imageSize The size of the image you upload.
* @throws APIException If a response was returned indicating the request could not be processed
* @throws IOException If an exception was thrown while making the request
* @see Upload Image
*/
- public void uploadImage(UUID imageIdentifier, byte[] image) throws APIException, IOException {
- makeHttpCall("/inApps/v1/messaging/image/" + imageIdentifier, "PUT", Map.of(), image, Void.class, PNG);
+ public void uploadImage(UUID imageIdentifier, byte[] image, ImageSize imageSize) throws APIException, IOException {
+ HashMap> queryParameters = new HashMap<>();
+ if (imageSize != null) {
+ queryParameters.put("imageSize", List.of(imageSize.name()));
+ }
+ makeHttpCall("/inApps/v1/messaging/image/" + imageIdentifier, "PUT", queryParameters, image, Void.class, PNG);
}
/**
@@ -502,6 +522,81 @@ public void deleteDefaultMessage(String productId, String locale) throws APIExce
makeHttpCall("/inApps/v1/messaging/default/" + productId + "/" + locale, "DELETE", Map.of(), null, Void.class, null);
}
+ /**
+ * Gets the default message for a specific product in a specific locale, if it’s configured.
+ *
+ * @param productId The product identifier of the message.
+ * @param locale The locale of the message.
+ * @return The response body that contains the default configuration information.
+ * @throws APIException If a response was returned indicating the request could not be processed
+ * @throws IOException If an exception was thrown while making the request
+ * @see Get Default Message
+ */
+ public DefaultConfigurationResponse getDefaultMessage(String productId, String locale) throws APIException, IOException {
+ return makeHttpCall("/inApps/v1/messaging/default/" + productId + "/" + locale, "GET", Map.of(), null, DefaultConfigurationResponse.class, null);
+ }
+
+ /**
+ * Configures the URL for your Get Retention Message endpoint in the sandbox and production environments.
+ *
+ * @param realtimeUrlRequest The request body that includes your endpoint’s URL.
+ * @throws APIException If a response was returned indicating the request could not be processed
+ * @throws IOException If an exception was thrown while making the request
+ * @see Configure Realtime URL
+ */
+ public void configureRealtimeURL(RealtimeUrlRequest realtimeUrlRequest) throws APIException, IOException {
+ makeHttpCall("/inApps/v1/messaging/realtime/url", "PUT", Map.of(), realtimeUrlRequest, Void.class, JSON);
+ }
+
+ /**
+ * Deletes the URL for your Get Retention Message endpoint, in the sandbox or production environments.
+ *
+ * @throws APIException If a response was returned indicating the request could not be processed
+ * @throws IOException If an exception was thrown while making the request
+ * @see Delete Realtime URL
+ */
+ public void deleteRealtimeURL() throws APIException, IOException {
+ makeHttpCall("/inApps/v1/messaging/realtime/url", "DELETE", Map.of(), null, Void.class, null);
+ }
+
+ /**
+ * Gets the URL for real-time messages that points to your Get Retention Message endpoint, which you previously configured.
+ *
+ * @return The response body that contains the URL for your Get Retention Message endpoint.
+ * @throws APIException If a response was returned indicating the request could not be processed
+ * @throws IOException If an exception was thrown while making the request
+ * @see Get Realtime URL
+ */
+ public RealtimeUrlResponse getRealtimeURL() throws APIException, IOException {
+ return makeHttpCall("/inApps/v1/messaging/realtime/url", "GET", Map.of(), null, RealtimeUrlResponse.class, null);
+ }
+
+ /**
+ * Initiates a performance test of your Get Retention Message endpoint in the sandbox environment.
+ *
+ * @param performanceTestRequest The request body which specifies a transaction identifier of an In-App Purchase to use for this test.
+ * @return The performance test response object.
+ * @throws APIException If a response was returned indicating the request could not be processed
+ * @throws IOException If an exception was thrown while making the request
+ * @see Initiate Performance Test
+ */
+ public PerformanceTestResponse initiatePerformanceTest(PerformanceTestRequest performanceTestRequest) throws APIException, IOException {
+ return makeHttpCall("/inApps/v1/messaging/performanceTest", "POST", Map.of(), performanceTestRequest, PerformanceTestResponse.class, JSON);
+ }
+
+ /**
+ * Gets the results of the performance test for the specified identifier.
+ *
+ * @param requestId The ID of the performance test to return, which you receive in the PerformanceTestResponse when you call Initiate Performance Test.
+ * @return An object the API returns that describes the performance test results.
+ * @throws APIException If a response was returned indicating the request could not be processed
+ * @throws IOException If an exception was thrown while making the request
+ * @see Get Performance Test Results
+ */
+ public PerformanceTestResultResponse getPerformanceTestResults(String requestId) throws APIException, IOException {
+ return makeHttpCall("/inApps/v1/messaging/performanceTest/result/" + requestId, "GET", Map.of(), null, PerformanceTestResultResponse.class, null);
+ }
+
/**
* Get a customer’s app transaction information for your app.
*
diff --git a/src/main/java/com/apple/itunes/storekit/model/BulletPoint.java b/src/main/java/com/apple/itunes/storekit/model/BulletPoint.java
new file mode 100644
index 00000000..3b00182e
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/BulletPoint.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * The text and its bullet-point image to include in a retention message’s bulleted list.
+ *
+ * @see BulletPoint
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BulletPoint {
+ private static final String SERIALIZED_NAME_TEXT = "text";
+ private static final String SERIALIZED_NAME_IMAGE_IDENTIFIER = "imageIdentifier";
+ private static final String SERIALIZED_NAME_ALT_TEXT = "altText";
+ @JsonProperty(value = SERIALIZED_NAME_TEXT, required = true)
+ private String text;
+ @JsonProperty(value = SERIALIZED_NAME_IMAGE_IDENTIFIER, required = true)
+ private UUID imageIdentifier;
+ @JsonProperty(value = SERIALIZED_NAME_ALT_TEXT, required = true)
+ private String altText;
+
+ private static final int MAXIMUM_TEXT_LENGTH = 66;
+ private static final int MAXIMUM_ALT_TEXT_LENGTH = 150;
+
+ private BulletPoint() {
+ }
+
+ public BulletPoint(String text,
+ UUID imageIdentifier,
+ String altText) {
+ this.text = validateText(text);
+ this.imageIdentifier = Objects.requireNonNull(imageIdentifier);
+ this.altText = validateAltText(altText);
+ }
+
+ public BulletPoint text(String text) {
+ this.text = validateText(text);
+ return this;
+ }
+
+ /**
+ * The text of the individual bullet point.
+ *
+ * @return text
+ * @see bulletPointText
+ **/
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = validateText(text);
+ }
+
+ public BulletPoint imageIdentifier(UUID imageIdentifier) {
+ this.imageIdentifier = Objects.requireNonNull(imageIdentifier);
+ return this;
+ }
+
+ /**
+ * The identifier of the image to use as the bullet point.
+ *
+ * @return imageIdentifier
+ * @see imageIdentifier
+ **/
+ public UUID getImageIdentifier() {
+ return imageIdentifier;
+ }
+
+ public void setImageIdentifier(UUID imageIdentifier) {
+ this.imageIdentifier = imageIdentifier;
+ }
+
+ public BulletPoint altText(String altText) {
+ this.altText = validateAltText(altText);
+ return this;
+ }
+
+ /**
+ * The alternative text you provide for the corresponding image of the bullet point.
+ *
+ * @return altText
+ * @see altText
+ **/
+ public String getAltText() {
+ return altText;
+ }
+
+ public void setAltText(String altText) {
+ this.altText = validateAltText(altText);
+ }
+
+ private String validateText(String text) {
+ Objects.requireNonNull(text);
+ if (text.length() > MAXIMUM_TEXT_LENGTH) {
+ throw new IllegalArgumentException("text length longer than maximum allowed");
+ }
+ return text;
+ }
+
+ private String validateAltText(String altText) {
+ Objects.requireNonNull(altText);
+ if (altText.length() > MAXIMUM_ALT_TEXT_LENGTH) {
+ throw new IllegalArgumentException("altText length longer than maximum allowed");
+ }
+ return altText;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BulletPoint bulletPoint = (BulletPoint) o;
+ return Objects.equals(this.text, bulletPoint.text) &&
+ Objects.equals(this.imageIdentifier, bulletPoint.imageIdentifier) &&
+ Objects.equals(this.altText, bulletPoint.altText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(text, imageIdentifier, altText);
+ }
+
+ @Override
+ public String toString() {
+ return "BulletPoint{" +
+ "text='" + text + '\'' +
+ ", imageIdentifier=" + imageIdentifier +
+ ", altText='" + altText + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/apple/itunes/storekit/model/DefaultConfigurationRequest.java b/src/main/java/com/apple/itunes/storekit/model/DefaultConfigurationRequest.java
index 76c98c2f..bb0f49dd 100644
--- a/src/main/java/com/apple/itunes/storekit/model/DefaultConfigurationRequest.java
+++ b/src/main/java/com/apple/itunes/storekit/model/DefaultConfigurationRequest.java
@@ -19,9 +19,17 @@ public class DefaultConfigurationRequest {
@JsonProperty(SERIALIZED_NAME_MESSAGE_IDENTIFIER)
private UUID messageIdentifier;
+ /**
+ * @deprecated Use {@link #DefaultConfigurationRequest(UUID)} instead.
+ */
+ @Deprecated
public DefaultConfigurationRequest() {
}
+ public DefaultConfigurationRequest(UUID messageIdentifier) {
+ this.messageIdentifier = Objects.requireNonNull(messageIdentifier);
+ }
+
public DefaultConfigurationRequest messageIdentifier(UUID messageIdentifier) {
this.messageIdentifier = messageIdentifier;
return this;
diff --git a/src/main/java/com/apple/itunes/storekit/model/DefaultConfigurationResponse.java b/src/main/java/com/apple/itunes/storekit/model/DefaultConfigurationResponse.java
new file mode 100644
index 00000000..e7164fb8
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/DefaultConfigurationResponse.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * The response body that contains the default configuration information.
+ *
+ * @see DefaultConfigurationResponse
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class DefaultConfigurationResponse {
+ private static final String SERIALIZED_NAME_MESSAGE_IDENTIFIER = "messageIdentifier";
+ @JsonProperty(value = SERIALIZED_NAME_MESSAGE_IDENTIFIER, required = true)
+ private UUID messageIdentifier;
+
+ private DefaultConfigurationResponse() {
+ }
+
+ public DefaultConfigurationResponse(UUID messageIdentifier) {
+ this.messageIdentifier = Objects.requireNonNull(messageIdentifier);
+ }
+
+ public DefaultConfigurationResponse messageIdentifier(UUID messageIdentifier) {
+ this.messageIdentifier = Objects.requireNonNull(messageIdentifier);
+ return this;
+ }
+
+ /**
+ * The message identifier of the retention message you configured as a default.
+ *
+ * @return messageIdentifier
+ * @see messageIdentifier
+ **/
+ public UUID getMessageIdentifier() {
+ return messageIdentifier;
+ }
+
+ public void setMessageIdentifier(UUID messageIdentifier) {
+ this.messageIdentifier = Objects.requireNonNull(messageIdentifier);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DefaultConfigurationResponse defaultConfigurationResponse = (DefaultConfigurationResponse) o;
+ return Objects.equals(this.messageIdentifier, defaultConfigurationResponse.messageIdentifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(messageIdentifier);
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultConfigurationResponse{" +
+ "messageIdentifier=" + messageIdentifier +
+ '}';
+ }
+}
+
diff --git a/src/main/java/com/apple/itunes/storekit/model/GetImageListResponseItem.java b/src/main/java/com/apple/itunes/storekit/model/GetImageListResponseItem.java
index 8f5b5bf4..d906ebd6 100644
--- a/src/main/java/com/apple/itunes/storekit/model/GetImageListResponseItem.java
+++ b/src/main/java/com/apple/itunes/storekit/model/GetImageListResponseItem.java
@@ -17,10 +17,13 @@
public class GetImageListResponseItem {
private static final String SERIALIZED_NAME_IMAGE_IDENTIFIER = "imageIdentifier";
private static final String SERIALIZED_NAME_IMAGE_STATE = "imageState";
+ private static final String SERIALIZED_NAME_IMAGE_SIZE = "imageSize";
@JsonProperty(SERIALIZED_NAME_IMAGE_IDENTIFIER)
private UUID imageIdentifier;
@JsonProperty(SERIALIZED_NAME_IMAGE_STATE)
private String imageState;
+ @JsonProperty(SERIALIZED_NAME_IMAGE_SIZE)
+ private String imageSize;
@JsonAnySetter
private Map unknownFields;
@@ -76,6 +79,36 @@ public void setRawImageState(String rawImageState) {
this.imageState = rawImageState;
}
+ public GetImageListResponseItem imageSize(ImageSize imageSize) {
+ this.imageSize = imageSize != null ? imageSize.getValue() : null;
+ return this;
+ }
+
+ /**
+ * The size of the image.
+ *
+ * @return imageSize
+ * @see imageSize
+ **/
+ public ImageSize getImageSize() {
+ return imageSize != null ? ImageSize.fromValue(imageSize) : null;
+ }
+
+ /**
+ * @see #getImageSize()
+ */
+ public String getRawImageSize() {
+ return imageSize;
+ }
+
+ public void setImageSize(ImageSize imageSize) {
+ this.imageSize = imageSize != null ? imageSize.getValue() : null;
+ }
+
+ public void setRawImageSize(String rawImageSize) {
+ this.imageSize = rawImageSize;
+ }
+
public GetImageListResponseItem unknownFields(Map unknownFields) {
this.unknownFields = unknownFields;
@@ -106,12 +139,13 @@ public boolean equals(Object o) {
GetImageListResponseItem getImageListResponseItem = (GetImageListResponseItem) o;
return Objects.equals(this.imageIdentifier, getImageListResponseItem.imageIdentifier) &&
Objects.equals(this.imageState, getImageListResponseItem.imageState) &&
+ Objects.equals(this.imageSize, getImageListResponseItem.imageSize) &&
Objects.equals(this.unknownFields, getImageListResponseItem.unknownFields);
}
@Override
public int hashCode() {
- return Objects.hash(imageIdentifier, imageState, unknownFields);
+ return Objects.hash(imageIdentifier, imageState, imageSize, unknownFields);
}
@Override
@@ -119,6 +153,7 @@ public String toString() {
return "GetImageListResponseItem{" +
"imageIdentifier=" + imageIdentifier +
", imageState='" + imageState + '\'' +
+ ", imageSize='" + imageSize + '\'' +
", unknownFields=" + unknownFields +
'}';
}
diff --git a/src/main/java/com/apple/itunes/storekit/model/HeaderPosition.java b/src/main/java/com/apple/itunes/storekit/model/HeaderPosition.java
new file mode 100644
index 00000000..dc45526b
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/HeaderPosition.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * The position where the header text appears in a message.
+ *
+ * @see headerPosition
+ */
+public enum HeaderPosition {
+
+ ABOVE_BODY("ABOVE_BODY"),
+ ABOVE_IMAGE("ABOVE_IMAGE");
+
+ private final String value;
+
+ HeaderPosition(String value) {
+ this.value = value;
+ }
+
+ public static HeaderPosition fromValue(String value) {
+ for (HeaderPosition b : HeaderPosition.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/ImageSize.java b/src/main/java/com/apple/itunes/storekit/model/ImageSize.java
new file mode 100644
index 00000000..c9bb360b
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/ImageSize.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * The size of an image.
+ *
+ * @see imageSize
+ */
+public enum ImageSize {
+
+ FULL_SIZE("FULL_SIZE"),
+ BULLET_POINT("BULLET_POINT");
+
+ private final String value;
+
+ ImageSize(String value) {
+ this.value = value;
+ }
+
+ public static ImageSize fromValue(String value) {
+ for (ImageSize b : ImageSize.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/PerformanceTestConfig.java b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestConfig.java
new file mode 100644
index 00000000..4560e20a
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestConfig.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * An object that enumerates the test configuration parameters.
+ *
+ * @see PerformanceTestConfig
+ */
+public class PerformanceTestConfig {
+ private static final String SERIALIZED_NAME_MAX_CONCURRENT_REQUESTS = "maxConcurrentRequests";
+ private static final String SERIALIZED_NAME_TOTAL_REQUESTS = "totalRequests";
+ private static final String SERIALIZED_NAME_TOTAL_DURATION = "totalDuration";
+ private static final String SERIALIZED_NAME_RESPONSE_TIME_THRESHOLD = "responseTimeThreshold";
+ private static final String SERIALIZED_NAME_SUCCESS_RATE_THRESHOLD = "successRateThreshold";
+ @JsonProperty(SERIALIZED_NAME_MAX_CONCURRENT_REQUESTS)
+ private Long maxConcurrentRequests;
+ @JsonProperty(SERIALIZED_NAME_TOTAL_REQUESTS)
+ private Long totalRequests;
+ @JsonProperty(SERIALIZED_NAME_TOTAL_DURATION)
+ private Long totalDuration;
+ @JsonProperty(SERIALIZED_NAME_RESPONSE_TIME_THRESHOLD)
+ private Long responseTimeThreshold;
+ @JsonProperty(SERIALIZED_NAME_SUCCESS_RATE_THRESHOLD)
+ private Integer successRateThreshold;
+ @JsonAnySetter
+ private Map unknownFields;
+
+
+ public PerformanceTestConfig() {
+ }
+
+ public PerformanceTestConfig maxConcurrentRequests(Long maxConcurrentRequests) {
+ this.maxConcurrentRequests = maxConcurrentRequests;
+ return this;
+ }
+
+ /**
+ * The maximum number of concurrent requests the API allows.
+ *
+ * @return maxConcurrentRequests
+ * @see maxConcurrentRequests
+ **/
+ public Long getMaxConcurrentRequests() {
+ return maxConcurrentRequests;
+ }
+
+ public void setMaxConcurrentRequests(Long maxConcurrentRequests) {
+ this.maxConcurrentRequests = maxConcurrentRequests;
+ }
+
+ public PerformanceTestConfig totalRequests(Long totalRequests) {
+ this.totalRequests = totalRequests;
+ return this;
+ }
+
+ /**
+ * The total number of requests to make during the test.
+ *
+ * @return totalRequests
+ * @see totalRequests
+ **/
+ public Long getTotalRequests() {
+ return totalRequests;
+ }
+
+ public void setTotalRequests(Long totalRequests) {
+ this.totalRequests = totalRequests;
+ }
+
+ public PerformanceTestConfig totalDuration(Long totalDuration) {
+ this.totalDuration = totalDuration;
+ return this;
+ }
+
+ /**
+ * The total duration of the test in milliseconds.
+ *
+ * @return totalDuration
+ * @see totalDuration
+ **/
+ public Long getTotalDuration() {
+ return totalDuration;
+ }
+
+ public void setTotalDuration(Long totalDuration) {
+ this.totalDuration = totalDuration;
+ }
+
+ public PerformanceTestConfig responseTimeThreshold(Long responseTimeThreshold) {
+ this.responseTimeThreshold = responseTimeThreshold;
+ return this;
+ }
+
+ /**
+ * The maximum time your server has to respond when the system calls your Get Retention Message endpoint in the sandbox environment.
+ *
+ * @return responseTimeThreshold
+ * @see responseTimeThreshold
+ **/
+ public Long getResponseTimeThreshold() {
+ return responseTimeThreshold;
+ }
+
+ public void setResponseTimeThreshold(Long responseTimeThreshold) {
+ this.responseTimeThreshold = responseTimeThreshold;
+ }
+
+ public PerformanceTestConfig successRateThreshold(Integer successRateThreshold) {
+ this.successRateThreshold = successRateThreshold;
+ return this;
+ }
+
+ /**
+ * The success rate threshold percentage.
+ *
+ * @return successRateThreshold
+ * @see successRateThreshold
+ **/
+ public Integer getSuccessRateThreshold() {
+ return successRateThreshold;
+ }
+
+ public void setSuccessRateThreshold(Integer successRateThreshold) {
+ this.successRateThreshold = successRateThreshold;
+ }
+
+
+ public PerformanceTestConfig unknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ return this;
+ }
+
+ /**
+ Fields that are not recognized for this object
+
+ @return A map of JSON keys to objects
+ */
+ public Map getUnknownFields() {
+ return unknownFields;
+ }
+
+ public void setUnknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PerformanceTestConfig performanceTestConfig = (PerformanceTestConfig) o;
+ return Objects.equals(this.maxConcurrentRequests, performanceTestConfig.maxConcurrentRequests) &&
+ Objects.equals(this.totalRequests, performanceTestConfig.totalRequests) &&
+ Objects.equals(this.totalDuration, performanceTestConfig.totalDuration) &&
+ Objects.equals(this.responseTimeThreshold, performanceTestConfig.responseTimeThreshold) &&
+ Objects.equals(this.successRateThreshold, performanceTestConfig.successRateThreshold) &&
+ Objects.equals(this.unknownFields, performanceTestConfig.unknownFields);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(maxConcurrentRequests, totalRequests, totalDuration, responseTimeThreshold, successRateThreshold, unknownFields);
+ }
+
+ @Override
+ public String toString() {
+ return "PerformanceTestConfig{" +
+ "maxConcurrentRequests=" + maxConcurrentRequests +
+ ", totalRequests=" + totalRequests +
+ ", totalDuration=" + totalDuration +
+ ", responseTimeThreshold=" + responseTimeThreshold +
+ ", successRateThreshold=" + successRateThreshold +
+ ", unknownFields=" + unknownFields +
+ '}';
+ }
+}
diff --git a/src/main/java/com/apple/itunes/storekit/model/PerformanceTestRequest.java b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestRequest.java
new file mode 100644
index 00000000..c6f86cfa
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestRequest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+
+/**
+ * The request object you provide for a performance test that contains an original transaction identifier.
+ *
+ * @see PerformanceTestRequest
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PerformanceTestRequest {
+ private static final String SERIALIZED_NAME_ORIGINAL_TRANSACTION_ID = "originalTransactionId";
+ @JsonProperty(value = SERIALIZED_NAME_ORIGINAL_TRANSACTION_ID, required = true)
+ private String originalTransactionId;
+
+ private PerformanceTestRequest() {
+ }
+
+ public PerformanceTestRequest(String originalTransactionId) {
+ this.originalTransactionId = Objects.requireNonNull(originalTransactionId);
+ }
+
+ public PerformanceTestRequest originalTransactionId(String originalTransactionId) {
+ this.originalTransactionId = Objects.requireNonNull(originalTransactionId);
+ return this;
+ }
+
+ /**
+ * The original transaction identifier of an In-App Purchase you initiate in the sandbox environment, to use as the purchase for this test.
+ *
+ * @return originalTransactionId
+ * @see originalTransactionId
+ **/
+ public String getOriginalTransactionId() {
+ return originalTransactionId;
+ }
+
+ public void setOriginalTransactionId(String originalTransactionId) {
+ this.originalTransactionId = Objects.requireNonNull(originalTransactionId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PerformanceTestRequest performanceTestRequest = (PerformanceTestRequest) o;
+ return Objects.equals(this.originalTransactionId, performanceTestRequest.originalTransactionId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(originalTransactionId);
+ }
+
+ @Override
+ public String toString() {
+ return "PerformanceTestRequest{" +
+ "originalTransactionId='" + originalTransactionId + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResponse.java b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResponse.java
new file mode 100644
index 00000000..81f143de
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResponse.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The performance test response object.
+ *
+ * @see PerformanceTestResponse
+ */
+public class PerformanceTestResponse {
+ private static final String SERIALIZED_NAME_CONFIG = "config";
+ private static final String SERIALIZED_NAME_REQUEST_ID = "requestId";
+ @JsonProperty(SERIALIZED_NAME_CONFIG)
+ private PerformanceTestConfig config;
+ @JsonProperty(SERIALIZED_NAME_REQUEST_ID)
+ private String requestId;
+ @JsonAnySetter
+ private Map unknownFields;
+
+
+ public PerformanceTestResponse() {
+ }
+
+ public PerformanceTestResponse config(PerformanceTestConfig config) {
+ this.config = config;
+ return this;
+ }
+
+ /**
+ * The performance test configuration object.
+ *
+ * @return config
+ * @see PerformanceTestConfig
+ **/
+ public PerformanceTestConfig getConfig() {
+ return config;
+ }
+
+ public void setConfig(PerformanceTestConfig config) {
+ this.config = config;
+ }
+
+ public PerformanceTestResponse requestId(String requestId) {
+ this.requestId = requestId;
+ return this;
+ }
+
+ /**
+ * The performance test request identifier.
+ *
+ * @return requestId
+ * @see requestId
+ **/
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+
+
+ public PerformanceTestResponse unknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ return this;
+ }
+
+ /**
+ Fields that are not recognized for this object
+
+ @return A map of JSON keys to objects
+ */
+ public Map getUnknownFields() {
+ return unknownFields;
+ }
+
+ public void setUnknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PerformanceTestResponse performanceTestResponse = (PerformanceTestResponse) o;
+ return Objects.equals(this.config, performanceTestResponse.config) &&
+ Objects.equals(this.requestId, performanceTestResponse.requestId) &&
+ Objects.equals(this.unknownFields, performanceTestResponse.unknownFields);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(config, requestId, unknownFields);
+ }
+
+ @Override
+ public String toString() {
+ return "PerformanceTestResponse{" +
+ "config=" + config +
+ ", requestId='" + requestId + '\'' +
+ ", unknownFields=" + unknownFields +
+ '}';
+ }
+}
diff --git a/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResponseTimes.java b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResponseTimes.java
new file mode 100644
index 00000000..8128ddd1
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResponseTimes.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * An object that describes test response times.
+ *
+ * @see PerformanceTestResponseTimes
+ */
+public class PerformanceTestResponseTimes {
+ private static final String SERIALIZED_NAME_AVERAGE = "average";
+ private static final String SERIALIZED_NAME_P50 = "p50";
+ private static final String SERIALIZED_NAME_P90 = "p90";
+ private static final String SERIALIZED_NAME_P95 = "p95";
+ private static final String SERIALIZED_NAME_P99 = "p99";
+ @JsonProperty(SERIALIZED_NAME_AVERAGE)
+ private Long average;
+ @JsonProperty(SERIALIZED_NAME_P50)
+ private Long p50;
+ @JsonProperty(SERIALIZED_NAME_P90)
+ private Long p90;
+ @JsonProperty(SERIALIZED_NAME_P95)
+ private Long p95;
+ @JsonProperty(SERIALIZED_NAME_P99)
+ private Long p99;
+ @JsonAnySetter
+ private Map unknownFields;
+
+
+ public PerformanceTestResponseTimes() {
+ }
+
+ public PerformanceTestResponseTimes average(Long average) {
+ this.average = average;
+ return this;
+ }
+
+ /**
+ * Average response time in milliseconds.
+ *
+ * @return average
+ * @see average
+ **/
+ public Long getAverage() {
+ return average;
+ }
+
+ public void setAverage(Long average) {
+ this.average = average;
+ }
+
+ public PerformanceTestResponseTimes p50(Long p50) {
+ this.p50 = p50;
+ return this;
+ }
+
+ /**
+ * The 50th percentile response time in milliseconds.
+ *
+ * @return p50
+ * @see p50
+ **/
+ public Long getP50() {
+ return p50;
+ }
+
+ public void setP50(Long p50) {
+ this.p50 = p50;
+ }
+
+ public PerformanceTestResponseTimes p90(Long p90) {
+ this.p90 = p90;
+ return this;
+ }
+
+ /**
+ * The 90th percentile response time in milliseconds.
+ *
+ * @return p90
+ * @see p90
+ **/
+ public Long getP90() {
+ return p90;
+ }
+
+ public void setP90(Long p90) {
+ this.p90 = p90;
+ }
+
+ public PerformanceTestResponseTimes p95(Long p95) {
+ this.p95 = p95;
+ return this;
+ }
+
+ /**
+ * The 95th percentile response time in milliseconds.
+ *
+ * @return p95
+ * @see p95
+ **/
+ public Long getP95() {
+ return p95;
+ }
+
+ public void setP95(Long p95) {
+ this.p95 = p95;
+ }
+
+ public PerformanceTestResponseTimes p99(Long p99) {
+ this.p99 = p99;
+ return this;
+ }
+
+ /**
+ * The 99th percentile response time in milliseconds.
+ *
+ * @return p99
+ * @see p99
+ **/
+ public Long getP99() {
+ return p99;
+ }
+
+ public void setP99(Long p99) {
+ this.p99 = p99;
+ }
+
+
+ public PerformanceTestResponseTimes unknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ return this;
+ }
+
+ /**
+ Fields that are not recognized for this object
+
+ @return A map of JSON keys to objects
+ */
+ public Map getUnknownFields() {
+ return unknownFields;
+ }
+
+ public void setUnknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PerformanceTestResponseTimes performanceTestResponseTimes = (PerformanceTestResponseTimes) o;
+ return Objects.equals(this.average, performanceTestResponseTimes.average) &&
+ Objects.equals(this.p50, performanceTestResponseTimes.p50) &&
+ Objects.equals(this.p90, performanceTestResponseTimes.p90) &&
+ Objects.equals(this.p95, performanceTestResponseTimes.p95) &&
+ Objects.equals(this.p99, performanceTestResponseTimes.p99) &&
+ Objects.equals(this.unknownFields, performanceTestResponseTimes.unknownFields);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(average, p50, p90, p95, p99, unknownFields);
+ }
+
+ @Override
+ public String toString() {
+ return "PerformanceTestResponseTimes{" +
+ "average=" + average +
+ ", p50=" + p50 +
+ ", p90=" + p90 +
+ ", p95=" + p95 +
+ ", p99=" + p99 +
+ ", unknownFields=" + unknownFields +
+ '}';
+ }
+}
diff --git a/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResultResponse.java b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResultResponse.java
new file mode 100644
index 00000000..c97b1175
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestResultResponse.java
@@ -0,0 +1,281 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * An object the API returns that describes the performance test results.
+ *
+ * @see PerformanceTestResultResponse
+ */
+public class PerformanceTestResultResponse {
+ private static final String SERIALIZED_NAME_CONFIG = "config";
+ private static final String SERIALIZED_NAME_TARGET = "target";
+ private static final String SERIALIZED_NAME_RESULT = "result";
+ private static final String SERIALIZED_NAME_SUCCESS_RATE = "successRate";
+ private static final String SERIALIZED_NAME_NUM_PENDING = "numPending";
+ private static final String SERIALIZED_NAME_RESPONSE_TIMES = "responseTimes";
+ private static final String SERIALIZED_NAME_FAILURES = "failures";
+ @JsonProperty(SERIALIZED_NAME_CONFIG)
+ private PerformanceTestConfig config;
+ @JsonProperty(SERIALIZED_NAME_TARGET)
+ private String target;
+ @JsonProperty(SERIALIZED_NAME_RESULT)
+ private String result;
+ @JsonProperty(SERIALIZED_NAME_SUCCESS_RATE)
+ private Integer successRate;
+ @JsonProperty(SERIALIZED_NAME_NUM_PENDING)
+ private Integer numPending;
+ @JsonProperty(SERIALIZED_NAME_RESPONSE_TIMES)
+ private PerformanceTestResponseTimes responseTimes;
+ @JsonProperty(SERIALIZED_NAME_FAILURES)
+ private Map failures;
+ @JsonAnySetter
+ private Map unknownFields;
+
+
+ public PerformanceTestResultResponse() {
+ }
+
+ public PerformanceTestResultResponse config(PerformanceTestConfig config) {
+ this.config = config;
+ return this;
+ }
+
+ /**
+ * A PerformanceTestConfig object that enumerates the test parameters.
+ *
+ * @return config
+ * @see PerformanceTestConfig
+ **/
+ public PerformanceTestConfig getConfig() {
+ return config;
+ }
+
+ public void setConfig(PerformanceTestConfig config) {
+ this.config = config;
+ }
+
+ public PerformanceTestResultResponse target(String target) {
+ this.target = target;
+ return this;
+ }
+
+ /**
+ * The target URL for the performance test.
+ *
+ * @return target
+ * @see target
+ **/
+ public String getTarget() {
+ return target;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ public PerformanceTestResultResponse result(PerformanceTestStatus result) {
+ this.result = result != null ? result.getValue() : null;
+ return this;
+ }
+
+ /**
+ * A PerformanceTestStatus object that describes the overall result of the test.
+ *
+ * @return result
+ * @see performanceTestStatus
+ **/
+ public PerformanceTestStatus getResult() {
+ return result != null ? PerformanceTestStatus.fromValue(result) : null;
+ }
+
+ /**
+ * @see #getResult()
+ */
+ public String getRawResult() {
+ return result;
+ }
+
+ public void setResult(PerformanceTestStatus result) {
+ this.result = result != null ? result.getValue() : null;
+ }
+
+ public void setRawResult(String rawResult) {
+ this.result = rawResult;
+ }
+
+ public PerformanceTestResultResponse successRate(Integer successRate) {
+ this.successRate = successRate;
+ return this;
+ }
+
+ /**
+ * An integer that describes he success rate percentage of the performance test.
+ *
+ * @return successRate
+ * @see successRate
+ **/
+ public Integer getSuccessRate() {
+ return successRate;
+ }
+
+ public void setSuccessRate(Integer successRate) {
+ this.successRate = successRate;
+ }
+
+ public PerformanceTestResultResponse numPending(Integer numPending) {
+ this.numPending = numPending;
+ return this;
+ }
+
+ /**
+ * An integer that describes the number of pending requests in the performance test.
+ *
+ * @return numPending
+ * @see numPending
+ **/
+ public Integer getNumPending() {
+ return numPending;
+ }
+
+ public void setNumPending(Integer numPending) {
+ this.numPending = numPending;
+ }
+
+ public PerformanceTestResultResponse responseTimes(PerformanceTestResponseTimes responseTimes) {
+ this.responseTimes = responseTimes;
+ return this;
+ }
+
+ /**
+ * A PerformanceTestResponseTimes object that enumerates the response times measured during the test.
+ *
+ * @return responseTimes
+ * @see PerformanceTestResponseTimes
+ **/
+ public PerformanceTestResponseTimes getResponseTimes() {
+ return responseTimes;
+ }
+
+ public void setResponseTimes(PerformanceTestResponseTimes responseTimes) {
+ this.responseTimes = responseTimes;
+ }
+
+ public PerformanceTestResultResponse failures(Map failures) {
+ if (failures != null) {
+ this.failures = new HashMap<>();
+ for (Map.Entry entry : failures.entrySet()) {
+ this.failures.put(entry.getKey().getValue(), entry.getValue());
+ }
+ } else {
+ this.failures = null;
+ }
+ return this;
+ }
+
+ /**
+ * A Failures object that represents a map of server-to-server notification failure reasons and counts that represent the number of failures encountered during the performance test.
+ *
+ * @return failures
+ * @see Failures
+ **/
+ public Map getFailures() {
+ if (failures == null) {
+ return null;
+ }
+ Map result = new HashMap<>();
+ for (Map.Entry entry : failures.entrySet()) {
+ SendAttemptResult key = SendAttemptResult.fromValue(entry.getKey());
+ if (key != null) {
+ result.put(key, entry.getValue());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @see #getFailures()
+ */
+ public Map getRawFailures() {
+ return failures;
+ }
+
+ public void setFailures(Map failures) {
+ if (failures != null) {
+ this.failures = new HashMap<>();
+ for (Map.Entry entry : failures.entrySet()) {
+ this.failures.put(entry.getKey().getValue(), entry.getValue());
+ }
+ } else {
+ this.failures = null;
+ }
+ }
+
+ public void setRawFailures(Map rawFailures) {
+ this.failures = rawFailures;
+ }
+
+
+ public PerformanceTestResultResponse unknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ return this;
+ }
+
+ /**
+ Fields that are not recognized for this object
+
+ @return A map of JSON keys to objects
+ */
+ public Map getUnknownFields() {
+ return unknownFields;
+ }
+
+ public void setUnknownFields(Map unknownFields) {
+ this.unknownFields = unknownFields;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PerformanceTestResultResponse performanceTestResultResponse = (PerformanceTestResultResponse) o;
+ return Objects.equals(this.config, performanceTestResultResponse.config) &&
+ Objects.equals(this.target, performanceTestResultResponse.target) &&
+ Objects.equals(this.result, performanceTestResultResponse.result) &&
+ Objects.equals(this.successRate, performanceTestResultResponse.successRate) &&
+ Objects.equals(this.numPending, performanceTestResultResponse.numPending) &&
+ Objects.equals(this.responseTimes, performanceTestResultResponse.responseTimes) &&
+ Objects.equals(this.failures, performanceTestResultResponse.failures) &&
+ Objects.equals(this.unknownFields, performanceTestResultResponse.unknownFields);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(config, target, result, successRate, numPending, responseTimes, failures, unknownFields);
+ }
+
+ @Override
+ public String toString() {
+ return "PerformanceTestResultResponse{" +
+ "config=" + config +
+ ", target='" + target + '\'' +
+ ", result='" + result + '\'' +
+ ", successRate=" + successRate +
+ ", numPending=" + numPending +
+ ", responseTimes=" + responseTimes +
+ ", failures=" + failures +
+ ", unknownFields=" + unknownFields +
+ '}';
+ }
+}
diff --git a/src/main/java/com/apple/itunes/storekit/model/PerformanceTestStatus.java b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestStatus.java
new file mode 100644
index 00000000..62b9d3ce
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/PerformanceTestStatus.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * The status of the performance test.
+ *
+ * @see performanceTestStatus
+ */
+public enum PerformanceTestStatus {
+
+ PENDING("PENDING"),
+ PASS("PASS"),
+ FAIL("FAIL");
+
+ private final String value;
+
+ PerformanceTestStatus(String value) {
+ this.value = value;
+ }
+
+ public static PerformanceTestStatus fromValue(String value) {
+ for (PerformanceTestStatus b : PerformanceTestStatus.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/RealtimeUrlRequest.java b/src/main/java/com/apple/itunes/storekit/model/RealtimeUrlRequest.java
new file mode 100644
index 00000000..08ede516
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/RealtimeUrlRequest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+
+/**
+ * The request body for configuring the URL of your Get Retention Message endpoint.
+ *
+ * @see RealtimeUrlRequest
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class RealtimeUrlRequest {
+ private static final String SERIALIZED_NAME_REALTIME_URL = "realtimeURL";
+ @JsonProperty(value = SERIALIZED_NAME_REALTIME_URL, required = true)
+ private String realtimeURL;
+
+ private static final int MAXIMUM_REALTIME_URL_LENGTH = 256;
+
+ private RealtimeUrlRequest() {
+ }
+
+ public RealtimeUrlRequest(String realtimeURL) {
+ this.realtimeURL = validateRealtimeUrl(realtimeURL);
+ }
+
+ public RealtimeUrlRequest realtimeURL(String realtimeURL) {
+ this.realtimeURL = validateRealtimeUrl(realtimeURL);
+ return this;
+ }
+
+ /**
+ * A string that contains the URL of your Get Retention Message endpoint for configuration.
+ *
+ * @return realtimeURL
+ * @see realtimeURL
+ **/
+ public String getRealtimeURL() {
+ return realtimeURL;
+ }
+
+ public void setRealtimeURL(String realtimeURL) {
+ this.realtimeURL = validateRealtimeUrl(realtimeURL);
+ }
+
+ private String validateRealtimeUrl(String realtimeURL) {
+ Objects.requireNonNull(realtimeURL);
+ if (realtimeURL.length() > MAXIMUM_REALTIME_URL_LENGTH) {
+ throw new IllegalArgumentException("realtimeURL length longer than maximum allowed");
+ }
+ return realtimeURL;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RealtimeUrlRequest realtimeUrlRequest = (RealtimeUrlRequest) o;
+ return Objects.equals(this.realtimeURL, realtimeUrlRequest.realtimeURL);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(realtimeURL);
+ }
+
+ @Override
+ public String toString() {
+ return "RealtimeUrlRequest{" +
+ "realtimeURL=" + realtimeURL +
+ '}';
+ }
+}
+
diff --git a/src/main/java/com/apple/itunes/storekit/model/RealtimeUrlResponse.java b/src/main/java/com/apple/itunes/storekit/model/RealtimeUrlResponse.java
new file mode 100644
index 00000000..9f84e477
--- /dev/null
+++ b/src/main/java/com/apple/itunes/storekit/model/RealtimeUrlResponse.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
+
+package com.apple.itunes.storekit.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+
+/**
+ * The response body that contains the URL for your Get Retention Message endpoint.
+ *
+ * @see RealtimeUrlResponse
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class RealtimeUrlResponse {
+ private static final String SERIALIZED_NAME_REALTIME_URL = "realtimeURL";
+ @JsonProperty(SERIALIZED_NAME_REALTIME_URL)
+ private String realtimeURL;
+
+ private static final int MAXIMUM_REALTIME_URL_LENGTH = 256;
+
+ private RealtimeUrlResponse() {
+ }
+
+ public RealtimeUrlResponse(String realtimeURL) {
+ this.realtimeURL = validateRealtimeUrl(realtimeURL);
+ }
+
+ public RealtimeUrlResponse realtimeURL(String realtimeURL) {
+ this.realtimeURL = validateRealtimeUrl(realtimeURL);
+ return this;
+ }
+
+ /**
+ * A string that contains the URL you provided for your Get Retention Message endpoint.
+ *
+ * @return realtimeURL
+ * @see realtimeURL
+ **/
+ public String getRealtimeURL() {
+ return realtimeURL;
+ }
+
+ public void setRealtimeURL(String realtimeURL) {
+ this.realtimeURL = validateRealtimeUrl(realtimeURL);
+ }
+
+ private String validateRealtimeUrl(String realtimeURL) {
+ Objects.requireNonNull(realtimeURL);
+ if (realtimeURL.length() > MAXIMUM_REALTIME_URL_LENGTH) {
+ throw new IllegalArgumentException("realtimeURL length longer than maximum allowed");
+ }
+ return realtimeURL;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RealtimeUrlResponse realtimeUrlRequest = (RealtimeUrlResponse) o;
+ return Objects.equals(this.realtimeURL, realtimeUrlRequest.realtimeURL);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(realtimeURL);
+ }
+
+ @Override
+ public String toString() {
+ return "RealtimeUrlResponse{" +
+ "realtimeURL=" + realtimeURL +
+ '}';
+ }
+}
+
diff --git a/src/main/java/com/apple/itunes/storekit/model/UploadMessageRequestBody.java b/src/main/java/com/apple/itunes/storekit/model/UploadMessageRequestBody.java
index 482786e9..766053c0 100644
--- a/src/main/java/com/apple/itunes/storekit/model/UploadMessageRequestBody.java
+++ b/src/main/java/com/apple/itunes/storekit/model/UploadMessageRequestBody.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
import java.util.Objects;
/**
@@ -17,19 +18,30 @@ public class UploadMessageRequestBody {
private static final String SERIALIZED_NAME_HEADER = "header";
private static final String SERIALIZED_NAME_BODY = "body";
private static final String SERIALIZED_NAME_IMAGE = "image";
+ private static final String SERIALIZED_NAME_HEADER_POSITION = "headerPosition";
+ private static final String SERIALIZED_NAME_BULLET_POINTS = "bulletPoints";
@JsonProperty(value = SERIALIZED_NAME_HEADER, required = true)
private String header;
@JsonProperty(value = SERIALIZED_NAME_BODY, required = true)
private String body;
@JsonProperty(value = SERIALIZED_NAME_IMAGE)
private UploadMessageImage image;
+ @JsonProperty(value = SERIALIZED_NAME_HEADER_POSITION)
+ private String headerPosition;
+ @JsonProperty(value = SERIALIZED_NAME_BULLET_POINTS)
+ private List bulletPoints;
private static final int MAXIMUM_HEADER_LENGTH = 66;
private static final int MAXIMUM_BODY_LENGTH = 144;
+ private static final int MAXIMUM_BULLET_POINTS_COUNT = 5;
private UploadMessageRequestBody() {
}
+ /**
+ * @deprecated Use {@link #UploadMessageRequestBody(String, String)} instead.
+ */
+ @Deprecated
public UploadMessageRequestBody(String header,
String body,
UploadMessageImage image) {
@@ -38,6 +50,11 @@ public UploadMessageRequestBody(String header,
this.image = image;
}
+ public UploadMessageRequestBody(String header,
+ String body) {
+ this(header, body, null);
+ }
+
public UploadMessageRequestBody header(String header) {
this.header = validateHeader(header);
return this;
@@ -111,6 +128,62 @@ public void setImage(UploadMessageImage image) {
this.image = image;
}
+ public UploadMessageRequestBody headerPosition(HeaderPosition headerPosition) {
+ this.headerPosition = headerPosition != null ? headerPosition.getValue() : null;
+ return this;
+ }
+
+ /**
+ * The position of header text, which defaults to placing header text above the body.
+ *
+ * @return headerPosition
+ * @see headerPosition
+ **/
+ public HeaderPosition getHeaderPosition() {
+ return headerPosition != null ? HeaderPosition.fromValue(headerPosition) : null;
+ }
+
+ /**
+ * @see #getHeaderPosition()
+ */
+ public String getRawHeaderPosition() {
+ return headerPosition;
+ }
+
+ public void setHeaderPosition(HeaderPosition headerPosition) {
+ this.headerPosition = headerPosition != null ? headerPosition.getValue() : null;
+ }
+
+ public void setRawHeaderPosition(String rawHeaderPosition) {
+ this.headerPosition = rawHeaderPosition;
+ }
+
+ public UploadMessageRequestBody bulletPoints(List bulletPoints) {
+ this.bulletPoints = validateBulletPoints(bulletPoints);
+ return this;
+ }
+
+ /**
+ * An optional array of bullet points.
+ *
+ * @return bulletPoints
+ * @see BulletPoint
+ **/
+ public List getBulletPoints() {
+ return bulletPoints;
+ }
+
+ public void setBulletPoints(List bulletPoints) {
+ this.bulletPoints = validateBulletPoints(bulletPoints);
+ }
+
+ private List validateBulletPoints(List bulletPoints) {
+ if (bulletPoints != null && bulletPoints.size() > MAXIMUM_BULLET_POINTS_COUNT) {
+ throw new IllegalArgumentException("bulletPoints count exceeds the maximum allowed");
+ }
+ return bulletPoints;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -122,12 +195,14 @@ public boolean equals(Object o) {
UploadMessageRequestBody uploadMessageRequestBody = (UploadMessageRequestBody) o;
return Objects.equals(this.header, uploadMessageRequestBody.header) &&
Objects.equals(this.body, uploadMessageRequestBody.body) &&
- Objects.equals(this.image, uploadMessageRequestBody.image);
+ Objects.equals(this.image, uploadMessageRequestBody.image) &&
+ Objects.equals(this.headerPosition, uploadMessageRequestBody.headerPosition) &&
+ Objects.equals(this.bulletPoints, uploadMessageRequestBody.bulletPoints);
}
@Override
public int hashCode() {
- return Objects.hash(header, body, image);
+ return Objects.hash(header, body, image, headerPosition, bulletPoints);
}
@Override
@@ -136,6 +211,8 @@ public String toString() {
"header='" + header + '\'' +
", body='" + body + '\'' +
", image=" + image +
+ ", headerPosition='" + headerPosition + '\'' +
+ ", bulletPoints=" + bulletPoints +
'}';
}
}
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 4f37185a..ca4f5384 100644
--- a/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java
+++ b/src/test/java/com/apple/itunes/storekit/client/AppStoreServerAPIClientTest.java
@@ -4,11 +4,13 @@
import com.apple.itunes.storekit.model.AccountTenure;
import com.apple.itunes.storekit.model.AppTransactionInfoResponse;
+import com.apple.itunes.storekit.model.BulletPoint;
import com.apple.itunes.storekit.model.CheckTestNotificationResponse;
import com.apple.itunes.storekit.model.ConsumptionRequest;
import com.apple.itunes.storekit.model.ConsumptionRequestV1;
import com.apple.itunes.storekit.model.ConsumptionStatus;
import com.apple.itunes.storekit.model.DefaultConfigurationRequest;
+import com.apple.itunes.storekit.model.DefaultConfigurationResponse;
import com.apple.itunes.storekit.model.DeliveryStatus;
import com.apple.itunes.storekit.model.DeliveryStatusV1;
import com.apple.itunes.storekit.model.Environment;
@@ -17,7 +19,9 @@
import com.apple.itunes.storekit.model.ExtendRenewalDateResponse;
import com.apple.itunes.storekit.model.GetImageListResponse;
import com.apple.itunes.storekit.model.GetMessageListResponse;
+import com.apple.itunes.storekit.model.HeaderPosition;
import com.apple.itunes.storekit.model.HistoryResponse;
+import com.apple.itunes.storekit.model.ImageSize;
import com.apple.itunes.storekit.model.ImageState;
import com.apple.itunes.storekit.model.InAppOwnershipType;
import com.apple.itunes.storekit.model.LastTransactionsItem;
@@ -33,8 +37,16 @@
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.PerformanceTestConfig;
+import com.apple.itunes.storekit.model.PerformanceTestRequest;
+import com.apple.itunes.storekit.model.PerformanceTestResponse;
+import com.apple.itunes.storekit.model.PerformanceTestResponseTimes;
+import com.apple.itunes.storekit.model.PerformanceTestResultResponse;
+import com.apple.itunes.storekit.model.PerformanceTestStatus;
import com.apple.itunes.storekit.model.Platform;
import com.apple.itunes.storekit.model.PlayTime;
+import com.apple.itunes.storekit.model.RealtimeUrlRequest;
+import com.apple.itunes.storekit.model.RealtimeUrlResponse;
import com.apple.itunes.storekit.model.RefundHistoryResponse;
import com.apple.itunes.storekit.model.RefundPreference;
import com.apple.itunes.storekit.model.RefundPreferenceV1;
@@ -593,6 +605,7 @@ public void testGetImageList() throws IOException, APIException {
Assertions.assertEquals(1, response.getImageIdentifiers().size());
Assertions.assertEquals(UUID.fromString("a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890"), response.getImageIdentifiers().get(0).getImageIdentifier());
Assertions.assertEquals(ImageState.APPROVED, response.getImageIdentifiers().get(0).getImageState());
+ Assertions.assertEquals(ImageSize.FULL_SIZE, response.getImageIdentifiers().get(0).getImageSize());
}
@Test
@@ -619,7 +632,7 @@ public void testUploadMessage() throws IOException, APIException {
Assertions.assertEquals("Body text", root.get("body"));
});
- UploadMessageRequestBody uploadMessageRequestBody = new UploadMessageRequestBody("Header text", "Body text", null);
+ UploadMessageRequestBody uploadMessageRequestBody = new UploadMessageRequestBody("Header text", "Body text");
client.uploadMessage(UUID.fromString("a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890"), uploadMessageRequestBody);
}
@@ -651,7 +664,8 @@ public void testUploadMessageWithImage() throws IOException, APIException {
});
UploadMessageImage image = new UploadMessageImage(UUID.fromString("b2c3d4e5-f6a7-8901-b2c3-d4e5f6a78901"), "Alt text");
- UploadMessageRequestBody uploadMessageRequestBody = new UploadMessageRequestBody("Header text", "Body text", image);
+ UploadMessageRequestBody uploadMessageRequestBody = new UploadMessageRequestBody("Header text", "Body text")
+ .image(image);
client.uploadMessage(UUID.fromString("a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890"), uploadMessageRequestBody);
}
@@ -716,6 +730,135 @@ public void testDeleteDefaultMessage() throws IOException, APIException {
client.deleteDefaultMessage("com.example.product", "en-US");
}
+ @Test
+ public void testGetDefaultMessage() throws IOException, APIException {
+ AppStoreServerAPIClient client = getClientWithBody("models/getDefaultMessageResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/messaging/default/com.example.product/en-US", request.url().encodedPath());
+ });
+
+ DefaultConfigurationResponse response = client.getDefaultMessage("com.example.product", "en-US");
+ Assertions.assertEquals(UUID.fromString("a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890"), response.getMessageIdentifier());
+ }
+
+ @Test
+ public void testUploadImageWithImageSize() throws IOException, APIException {
+ AppStoreServerAPIClient client = getAppStoreServerAPIClient("", request -> {
+ Assertions.assertEquals("PUT", request.method());
+ Assertions.assertEquals("/inApps/v1/messaging/image/a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890", request.url().encodedPath());
+ Assertions.assertEquals("FULL_SIZE", request.url().queryParameter("imageSize"));
+ RequestBody body = request.body();
+ Assertions.assertNotNull(body);
+ Assertions.assertEquals(MediaType.parse("image/png"), body.contentType());
+ Buffer buffer = new Buffer();
+ try {
+ body.writeTo(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ byte[] bytes = buffer.readByteArray();
+ Assertions.assertArrayEquals(new byte[]{1, 2, 3}, bytes);
+ });
+
+ client.uploadImage(UUID.fromString("a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890"), new byte[]{1, 2, 3}, ImageSize.FULL_SIZE);
+ }
+
+ @Test
+ public void testConfigureRealtimeURL() throws IOException, APIException {
+ AppStoreServerAPIClient client = getAppStoreServerAPIClient("", request -> {
+ Assertions.assertEquals("PUT", request.method());
+ Assertions.assertEquals("/inApps/v1/messaging/realtime/url", 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;
+ try {
+ root = new ObjectMapper().readValue(buffer.readUtf8(), Map.class);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ Assertions.assertEquals("https://example.com/realtime", root.get("realtimeURL"));
+ });
+
+ RealtimeUrlRequest realtimeUrlRequest = new RealtimeUrlRequest("https://example.com/realtime");
+ client.configureRealtimeURL(realtimeUrlRequest);
+ }
+
+ @Test
+ public void testDeleteRealtimeURL() throws IOException, APIException {
+ AppStoreServerAPIClient client = getAppStoreServerAPIClient("", request -> {
+ Assertions.assertEquals("DELETE", request.method());
+ Assertions.assertEquals("/inApps/v1/messaging/realtime/url", request.url().encodedPath());
+ });
+
+ client.deleteRealtimeURL();
+ }
+
+ @Test
+ public void testGetRealtimeURL() throws IOException, APIException {
+ AppStoreServerAPIClient client = getClientWithBody("models/getRealtimeUrlResponse.json", request -> {
+ Assertions.assertEquals("GET", request.method());
+ Assertions.assertEquals("/inApps/v1/messaging/realtime/url", request.url().encodedPath());
+ });
+
+ RealtimeUrlResponse response = client.getRealtimeURL();
+ Assertions.assertEquals("https://example.com/realtime", response.getRealtimeURL());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testUploadMessageWithBulletPoints() throws IOException, APIException {
+ AppStoreServerAPIClient client = getAppStoreServerAPIClient("", request -> {
+ Assertions.assertEquals("PUT", request.method());
+ Assertions.assertEquals("/inApps/v1/messaging/message/a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890", 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;
+ try {
+ root = new ObjectMapper().readValue(buffer.readUtf8(), Map.class);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ Assertions.assertEquals("Header text", root.get("header"));
+ Assertions.assertEquals("Body text", root.get("body"));
+ Assertions.assertEquals("ABOVE_IMAGE", root.get("headerPosition"));
+ Map image = (Map) root.get("image");
+ Assertions.assertEquals("b2c3d4e5-f6a7-8901-b2c3-d4e5f6a78901", image.get("imageIdentifier"));
+ Assertions.assertEquals("Image alt text", image.get("altText"));
+ List