Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support App Store Server Notifications v2.10 #74

Merged
merged 1 commit into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) 2024 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 payload data that contains an external purchase token.
*
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken">externalPurchaseToken</a>
*/
public class ExternalPurchaseToken {
private static final String SERIALIZED_NAME_EXTERNAL_PURCHASE_ID = "externalPurchaseId";
private static final String SERIALIZED_NAME_TOKEN_CREATION_DATE = "tokenCreationDate";
private static final String SERIALIZED_NAME_APP_APPLE_ID = "appAppleId";
private static final String SERIALIZED_NAME_BUNDLE_ID = "bundleId";
@JsonProperty(SERIALIZED_NAME_EXTERNAL_PURCHASE_ID)
private String externalPurchaseId;
@JsonProperty(SERIALIZED_NAME_TOKEN_CREATION_DATE)
private Long tokenCreationDate;
@JsonProperty(SERIALIZED_NAME_APP_APPLE_ID)
private Long appAppleId;
@JsonProperty(SERIALIZED_NAME_BUNDLE_ID)
private String bundleId;
@JsonAnySetter
private Map<String, Object> unknownFields;

public ExternalPurchaseToken() {
}

public ExternalPurchaseToken externalPurchaseId(String externalPurchaseId) {
this.externalPurchaseId = externalPurchaseId;
return this;
}

/**
* The field of an external purchase token that uniquely identifies the token.
*
* @return externalPurchaseId
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/externalpurchaseid">externalPurchaseId</a>
**/
public String getExternalPurchaseId() {
return externalPurchaseId;
}

public void setExternalPurchaseId(String externalPurchaseId) {
this.externalPurchaseId = externalPurchaseId;
}

public ExternalPurchaseToken tokenCreationDate(Long tokenCreationDate) {
this.tokenCreationDate = tokenCreationDate;
return this;
}

/**
* The field of an external purchase token that contains the UNIX date, in milliseconds, when the system created the token.
*
* @return tokenCreationDate
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/tokencreationdate">tokenCreationDate</a>
**/
public Long getTokenCreationDate() {
return tokenCreationDate;
}

public void setTokenCreationDate(Long tokenCreationDate) {
this.tokenCreationDate = tokenCreationDate;
}

public ExternalPurchaseToken appAppleId(Long appAppleId) {
this.appAppleId = appAppleId;
return this;
}

/**
* The unique identifier of an app in the App Store.
*
* @return appAppleId
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/appappleid">appAppleId</a>
**/
public Long getAppAppleId() {
return appAppleId;
}

public void setAppAppleId(Long appAppleId) {
this.appAppleId = appAppleId;
}

public ExternalPurchaseToken bundleId(String bundleId) {
this.bundleId = bundleId;
return this;
}

/**
* The bundle identifier of an app.
*
* @return bundleId
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/bundleid">bundleId</a>
**/
public String getBundleId() {
return bundleId;
}

public void setBundleId(String bundleId) {
this.bundleId = bundleId;
}

public ExternalPurchaseToken unknownFields(Map<String, Object> unknownFields) {
this.unknownFields = unknownFields;
return this;
}

/**
Fields that are not recognized for this object

@return A map of JSON keys to objects
*/
public Map<String, Object> getUnknownFields() {
return unknownFields;
}

public void setUnknownFields(Map<String, Object> unknownFields) {
this.unknownFields = unknownFields;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ExternalPurchaseToken externalPurchaseToken = (ExternalPurchaseToken) o;
return Objects.equals(this.externalPurchaseId, externalPurchaseToken.externalPurchaseId) &&
Objects.equals(this.tokenCreationDate, externalPurchaseToken.tokenCreationDate) &&
Objects.equals(this.appAppleId, externalPurchaseToken.appAppleId) &&
Objects.equals(this.bundleId, externalPurchaseToken.bundleId) &&
Objects.equals(this.unknownFields, externalPurchaseToken.unknownFields);
}

@Override
public int hashCode() {
return Objects.hash(externalPurchaseId, tokenCreationDate, appAppleId, bundleId, unknownFields);
}

@Override
public String toString() {
return "ExternalPurchaseToken{" +
"externalPurchaseId='" + externalPurchaseId + '\'' +
", tokenCreationDate=" + tokenCreationDate +
", appAppleId=" + appAppleId +
", bundleId='" + bundleId + '\'' +
", unknownFields=" + unknownFields +
'}';
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public enum NotificationTypeV2 {
REVOKE("REVOKE"),
TEST("TEST"),
RENEWAL_EXTENSION("RENEWAL_EXTENSION"),
REFUND_REVERSED("REFUND_REVERSED");
REFUND_REVERSED("REFUND_REVERSED"),
EXTERNAL_PURCHASE_TOKEN("EXTERNAL_PURCHASE_TOKEN");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ResponseBodyV2DecodedPayload implements DecodedSignedData {
private static final String SERIALIZED_NAME_VERSION = "version";
private static final String SERIALIZED_NAME_SIGNED_DATE = "signedDate";
private static final String SERIALIZED_NAME_SUMMARY = "summary";
private static final String SERIALIZED_NAME_EXTERNAL_PURCHASE_TOKEN = "externalPurchaseToken";
@JsonProperty(SERIALIZED_NAME_NOTIFICATION_TYPE)
private String notificationType;
@JsonProperty(SERIALIZED_NAME_SUBTYPE)
Expand All @@ -35,6 +36,8 @@ public class ResponseBodyV2DecodedPayload implements DecodedSignedData {
private Long signedDate;
@JsonProperty(SERIALIZED_NAME_SUMMARY)
private Summary summary;
@JsonProperty(SERIALIZED_NAME_EXTERNAL_PURCHASE_TOKEN)
private ExternalPurchaseToken externalPurchaseToken;
@JsonAnySetter
private Map<String, Object> unknownFields;

Expand Down Expand Up @@ -128,7 +131,7 @@ public ResponseBodyV2DecodedPayload data(Data data) {

/**
* The object that contains the app metadata and signed renewal and transaction information.
* The data and summary fields are mutually exclusive. The payload contains one of the fields, but not both.
* The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields.
*
* @return data
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/data">data</a>
Expand Down Expand Up @@ -186,7 +189,7 @@ public ResponseBodyV2DecodedPayload summary(Summary summary) {

/**
* The summary data that appears when the App Store server completes your request to extend a subscription renewal date for eligible subscribers.
* The data and summary fields are mutually exclusive. The payload contains one of the fields, but not both.
* The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields.
*
* @return summary
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/summary">summary</a>
Expand All @@ -199,6 +202,25 @@ public void setSummary(Summary summary) {
this.summary = summary;
}

public ResponseBodyV2DecodedPayload externalPurchaseToken(ExternalPurchaseToken externalPurchaseToken) {
this.externalPurchaseToken = externalPurchaseToken;
return this;
}

/**
* This field appears when the notificationType is EXTERNAL_PURCHASE_TOKEN.
* The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields.
*
* @return externalPurchaseToken
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken">externalPurchaseToken</a>
**/
public ExternalPurchaseToken getExternalPurchaseToken() {
return externalPurchaseToken;
}

public void setExternalPurchaseToken(ExternalPurchaseToken externalPurchaseToken) {
this.externalPurchaseToken = externalPurchaseToken;
}

public ResponseBodyV2DecodedPayload unknownFields(Map<String, Object> unknownFields) {
this.unknownFields = unknownFields;
Expand Down Expand Up @@ -234,12 +256,13 @@ public boolean equals(Object o) {
Objects.equals(this.version, responseBodyV2DecodedPayload.version) &&
Objects.equals(this.signedDate, responseBodyV2DecodedPayload.signedDate) &&
Objects.equals(this.summary, responseBodyV2DecodedPayload.summary) &&
Objects.equals(this.externalPurchaseToken, responseBodyV2DecodedPayload.externalPurchaseToken) &&
Objects.equals(this.unknownFields, responseBodyV2DecodedPayload.unknownFields);
}

@Override
public int hashCode() {
return Objects.hash(notificationType, subtype, notificationUUID, data, version, signedDate, summary, unknownFields);
return Objects.hash(notificationType, subtype, notificationUUID, data, version, signedDate, summary, externalPurchaseToken, unknownFields);
}

@Override
Expand All @@ -252,6 +275,7 @@ public String toString() {
", version='" + version + '\'' +
", signedDate=" + signedDate +
", summary=" + summary +
", externalPurchaseToken=" + externalPurchaseToken +
", unknownFields=" + unknownFields +
'}';
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/apple/itunes/storekit/model/Subtype.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public enum Subtype {
BILLING_RECOVERY("BILLING_RECOVERY"),
PRODUCT_NOT_FOR_SALE("PRODUCT_NOT_FOR_SALE"),
SUMMARY("SUMMARY"),
FAILURE("FAILURE");
FAILURE("FAILURE"),
UNREPORTED("UNREPORTED");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,42 @@ public JWSRenewalInfoDecodedPayload verifyAndDecodeRenewalInfo(String signedRene
*/
public ResponseBodyV2DecodedPayload verifyAndDecodeNotification(String signedPayload) throws VerificationException {
ResponseBodyV2DecodedPayload notification = decodeSignedObject(signedPayload, ResponseBodyV2DecodedPayload.class);
Environment notificationEnv = notification.getData() != null ? notification.getData().getEnvironment() : (notification.getSummary() != null ? notification.getSummary().getEnvironment() : null);
Long appAppleId = notification.getData() != null ? notification.getData().getAppAppleId() : (notification.getSummary() != null ? notification.getSummary().getAppAppleId() : null);
String bundleId = notification.getData() != null ? notification.getData().getBundleId() : (notification.getSummary() != null ? notification.getSummary().getBundleId() : null);
String bundleId;
Long appAppleId;
Environment notificationEnv;
if (notification.getData() != null) {
bundleId = notification.getData().getBundleId();
appAppleId = notification.getData().getAppAppleId();
notificationEnv = notification.getData().getEnvironment();
} else if (notification.getSummary() != null) {
bundleId = notification.getSummary().getBundleId();
appAppleId = notification.getSummary().getAppAppleId();
notificationEnv = notification.getSummary().getEnvironment();
} else if (notification.getExternalPurchaseToken() != null) {
bundleId = notification.getExternalPurchaseToken().getBundleId();
appAppleId = notification.getExternalPurchaseToken().getAppAppleId();
String externalPurchaseId = notification.getExternalPurchaseToken().getExternalPurchaseId();
if (externalPurchaseId != null && externalPurchaseId.startsWith("SANDBOX")) {
notificationEnv = Environment.SANDBOX;
} else {
notificationEnv = Environment.PRODUCTION;
}
} else {
bundleId = null;
appAppleId = null;
notificationEnv = null;
}
verifyNotification(bundleId, appAppleId, notificationEnv);
return notification;
}

protected void verifyNotification(String bundleId, Long appAppleId, Environment notificationEnv) throws VerificationException {
if (!this.bundleId.equals(bundleId) || (this.environment.equals(Environment.PRODUCTION) && !this.appAppleId.equals(appAppleId))) {
throw new VerificationException(VerificationStatus.INVALID_APP_IDENTIFIER);
}
if (!this.environment.equals(notificationEnv)) {
throw new VerificationException(VerificationStatus.INVALID_ENVIRONMENT);
}
return notification;
}

/**
Expand Down
Loading
Loading