Skip to content

Commit

Permalink
Merge pull request #74 from alexanderjordanbaker/ASSNv2.10
Browse files Browse the repository at this point in the history
Support App Store Server Notifications v2.10
  • Loading branch information
alexanderjordanbaker authored Mar 15, 2024
2 parents 7a02183 + 86458b9 commit 5c3c30a
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 10 deletions.
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

0 comments on commit 5c3c30a

Please sign in to comment.