Skip to content

Commit 86458b9

Browse files
1 parent 6be21bf commit 86458b9

8 files changed

+315
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) 2024 Apple Inc. Licensed under MIT License.
2+
3+
package com.apple.itunes.storekit.model;
4+
5+
import com.fasterxml.jackson.annotation.JsonAnySetter;
6+
import com.fasterxml.jackson.annotation.JsonProperty;
7+
8+
import java.util.Map;
9+
import java.util.Objects;
10+
11+
/**
12+
* The payload data that contains an external purchase token.
13+
*
14+
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken">externalPurchaseToken</a>
15+
*/
16+
public class ExternalPurchaseToken {
17+
private static final String SERIALIZED_NAME_EXTERNAL_PURCHASE_ID = "externalPurchaseId";
18+
private static final String SERIALIZED_NAME_TOKEN_CREATION_DATE = "tokenCreationDate";
19+
private static final String SERIALIZED_NAME_APP_APPLE_ID = "appAppleId";
20+
private static final String SERIALIZED_NAME_BUNDLE_ID = "bundleId";
21+
@JsonProperty(SERIALIZED_NAME_EXTERNAL_PURCHASE_ID)
22+
private String externalPurchaseId;
23+
@JsonProperty(SERIALIZED_NAME_TOKEN_CREATION_DATE)
24+
private Long tokenCreationDate;
25+
@JsonProperty(SERIALIZED_NAME_APP_APPLE_ID)
26+
private Long appAppleId;
27+
@JsonProperty(SERIALIZED_NAME_BUNDLE_ID)
28+
private String bundleId;
29+
@JsonAnySetter
30+
private Map<String, Object> unknownFields;
31+
32+
public ExternalPurchaseToken() {
33+
}
34+
35+
public ExternalPurchaseToken externalPurchaseId(String externalPurchaseId) {
36+
this.externalPurchaseId = externalPurchaseId;
37+
return this;
38+
}
39+
40+
/**
41+
* The field of an external purchase token that uniquely identifies the token.
42+
*
43+
* @return externalPurchaseId
44+
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/externalpurchaseid">externalPurchaseId</a>
45+
**/
46+
public String getExternalPurchaseId() {
47+
return externalPurchaseId;
48+
}
49+
50+
public void setExternalPurchaseId(String externalPurchaseId) {
51+
this.externalPurchaseId = externalPurchaseId;
52+
}
53+
54+
public ExternalPurchaseToken tokenCreationDate(Long tokenCreationDate) {
55+
this.tokenCreationDate = tokenCreationDate;
56+
return this;
57+
}
58+
59+
/**
60+
* The field of an external purchase token that contains the UNIX date, in milliseconds, when the system created the token.
61+
*
62+
* @return tokenCreationDate
63+
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/tokencreationdate">tokenCreationDate</a>
64+
**/
65+
public Long getTokenCreationDate() {
66+
return tokenCreationDate;
67+
}
68+
69+
public void setTokenCreationDate(Long tokenCreationDate) {
70+
this.tokenCreationDate = tokenCreationDate;
71+
}
72+
73+
public ExternalPurchaseToken appAppleId(Long appAppleId) {
74+
this.appAppleId = appAppleId;
75+
return this;
76+
}
77+
78+
/**
79+
* The unique identifier of an app in the App Store.
80+
*
81+
* @return appAppleId
82+
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/appappleid">appAppleId</a>
83+
**/
84+
public Long getAppAppleId() {
85+
return appAppleId;
86+
}
87+
88+
public void setAppAppleId(Long appAppleId) {
89+
this.appAppleId = appAppleId;
90+
}
91+
92+
public ExternalPurchaseToken bundleId(String bundleId) {
93+
this.bundleId = bundleId;
94+
return this;
95+
}
96+
97+
/**
98+
* The bundle identifier of an app.
99+
*
100+
* @return bundleId
101+
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/bundleid">bundleId</a>
102+
**/
103+
public String getBundleId() {
104+
return bundleId;
105+
}
106+
107+
public void setBundleId(String bundleId) {
108+
this.bundleId = bundleId;
109+
}
110+
111+
public ExternalPurchaseToken unknownFields(Map<String, Object> unknownFields) {
112+
this.unknownFields = unknownFields;
113+
return this;
114+
}
115+
116+
/**
117+
Fields that are not recognized for this object
118+
119+
@return A map of JSON keys to objects
120+
*/
121+
public Map<String, Object> getUnknownFields() {
122+
return unknownFields;
123+
}
124+
125+
public void setUnknownFields(Map<String, Object> unknownFields) {
126+
this.unknownFields = unknownFields;
127+
}
128+
129+
@Override
130+
public boolean equals(Object o) {
131+
if (this == o) {
132+
return true;
133+
}
134+
if (o == null || getClass() != o.getClass()) {
135+
return false;
136+
}
137+
ExternalPurchaseToken externalPurchaseToken = (ExternalPurchaseToken) o;
138+
return Objects.equals(this.externalPurchaseId, externalPurchaseToken.externalPurchaseId) &&
139+
Objects.equals(this.tokenCreationDate, externalPurchaseToken.tokenCreationDate) &&
140+
Objects.equals(this.appAppleId, externalPurchaseToken.appAppleId) &&
141+
Objects.equals(this.bundleId, externalPurchaseToken.bundleId) &&
142+
Objects.equals(this.unknownFields, externalPurchaseToken.unknownFields);
143+
}
144+
145+
@Override
146+
public int hashCode() {
147+
return Objects.hash(externalPurchaseId, tokenCreationDate, appAppleId, bundleId, unknownFields);
148+
}
149+
150+
@Override
151+
public String toString() {
152+
return "ExternalPurchaseToken{" +
153+
"externalPurchaseId='" + externalPurchaseId + '\'' +
154+
", tokenCreationDate=" + tokenCreationDate +
155+
", appAppleId=" + appAppleId +
156+
", bundleId='" + bundleId + '\'' +
157+
", unknownFields=" + unknownFields +
158+
'}';
159+
}
160+
}
161+

src/main/java/com/apple/itunes/storekit/model/NotificationTypeV2.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public enum NotificationTypeV2 {
2727
REVOKE("REVOKE"),
2828
TEST("TEST"),
2929
RENEWAL_EXTENSION("RENEWAL_EXTENSION"),
30-
REFUND_REVERSED("REFUND_REVERSED");
30+
REFUND_REVERSED("REFUND_REVERSED"),
31+
EXTERNAL_PURCHASE_TOKEN("EXTERNAL_PURCHASE_TOKEN");
3132

3233
private final String value;
3334

src/main/java/com/apple/itunes/storekit/model/ResponseBodyV2DecodedPayload.java

+27-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class ResponseBodyV2DecodedPayload implements DecodedSignedData {
2121
private static final String SERIALIZED_NAME_VERSION = "version";
2222
private static final String SERIALIZED_NAME_SIGNED_DATE = "signedDate";
2323
private static final String SERIALIZED_NAME_SUMMARY = "summary";
24+
private static final String SERIALIZED_NAME_EXTERNAL_PURCHASE_TOKEN = "externalPurchaseToken";
2425
@JsonProperty(SERIALIZED_NAME_NOTIFICATION_TYPE)
2526
private String notificationType;
2627
@JsonProperty(SERIALIZED_NAME_SUBTYPE)
@@ -35,6 +36,8 @@ public class ResponseBodyV2DecodedPayload implements DecodedSignedData {
3536
private Long signedDate;
3637
@JsonProperty(SERIALIZED_NAME_SUMMARY)
3738
private Summary summary;
39+
@JsonProperty(SERIALIZED_NAME_EXTERNAL_PURCHASE_TOKEN)
40+
private ExternalPurchaseToken externalPurchaseToken;
3841
@JsonAnySetter
3942
private Map<String, Object> unknownFields;
4043

@@ -128,7 +131,7 @@ public ResponseBodyV2DecodedPayload data(Data data) {
128131

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

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

205+
public ResponseBodyV2DecodedPayload externalPurchaseToken(ExternalPurchaseToken externalPurchaseToken) {
206+
this.externalPurchaseToken = externalPurchaseToken;
207+
return this;
208+
}
209+
210+
/**
211+
* This field appears when the notificationType is EXTERNAL_PURCHASE_TOKEN.
212+
* The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields.
213+
*
214+
* @return externalPurchaseToken
215+
* @see <a href="https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken">externalPurchaseToken</a>
216+
**/
217+
public ExternalPurchaseToken getExternalPurchaseToken() {
218+
return externalPurchaseToken;
219+
}
220+
221+
public void setExternalPurchaseToken(ExternalPurchaseToken externalPurchaseToken) {
222+
this.externalPurchaseToken = externalPurchaseToken;
223+
}
202224

203225
public ResponseBodyV2DecodedPayload unknownFields(Map<String, Object> unknownFields) {
204226
this.unknownFields = unknownFields;
@@ -234,12 +256,13 @@ public boolean equals(Object o) {
234256
Objects.equals(this.version, responseBodyV2DecodedPayload.version) &&
235257
Objects.equals(this.signedDate, responseBodyV2DecodedPayload.signedDate) &&
236258
Objects.equals(this.summary, responseBodyV2DecodedPayload.summary) &&
259+
Objects.equals(this.externalPurchaseToken, responseBodyV2DecodedPayload.externalPurchaseToken) &&
237260
Objects.equals(this.unknownFields, responseBodyV2DecodedPayload.unknownFields);
238261
}
239262

240263
@Override
241264
public int hashCode() {
242-
return Objects.hash(notificationType, subtype, notificationUUID, data, version, signedDate, summary, unknownFields);
265+
return Objects.hash(notificationType, subtype, notificationUUID, data, version, signedDate, summary, externalPurchaseToken, unknownFields);
243266
}
244267

245268
@Override
@@ -252,6 +275,7 @@ public String toString() {
252275
", version='" + version + '\'' +
253276
", signedDate=" + signedDate +
254277
", summary=" + summary +
278+
", externalPurchaseToken=" + externalPurchaseToken +
255279
", unknownFields=" + unknownFields +
256280
'}';
257281
}

src/main/java/com/apple/itunes/storekit/model/Subtype.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public enum Subtype {
2626
BILLING_RECOVERY("BILLING_RECOVERY"),
2727
PRODUCT_NOT_FOR_SALE("PRODUCT_NOT_FOR_SALE"),
2828
SUMMARY("SUMMARY"),
29-
FAILURE("FAILURE");
29+
FAILURE("FAILURE"),
30+
UNREPORTED("UNREPORTED");
3031

3132
private final String value;
3233

src/main/java/com/apple/itunes/storekit/verification/SignedDataVerifier.java

+30-4
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,42 @@ public JWSRenewalInfoDecodedPayload verifyAndDecodeRenewalInfo(String signedRene
102102
*/
103103
public ResponseBodyV2DecodedPayload verifyAndDecodeNotification(String signedPayload) throws VerificationException {
104104
ResponseBodyV2DecodedPayload notification = decodeSignedObject(signedPayload, ResponseBodyV2DecodedPayload.class);
105-
Environment notificationEnv = notification.getData() != null ? notification.getData().getEnvironment() : (notification.getSummary() != null ? notification.getSummary().getEnvironment() : null);
106-
Long appAppleId = notification.getData() != null ? notification.getData().getAppAppleId() : (notification.getSummary() != null ? notification.getSummary().getAppAppleId() : null);
107-
String bundleId = notification.getData() != null ? notification.getData().getBundleId() : (notification.getSummary() != null ? notification.getSummary().getBundleId() : null);
105+
String bundleId;
106+
Long appAppleId;
107+
Environment notificationEnv;
108+
if (notification.getData() != null) {
109+
bundleId = notification.getData().getBundleId();
110+
appAppleId = notification.getData().getAppAppleId();
111+
notificationEnv = notification.getData().getEnvironment();
112+
} else if (notification.getSummary() != null) {
113+
bundleId = notification.getSummary().getBundleId();
114+
appAppleId = notification.getSummary().getAppAppleId();
115+
notificationEnv = notification.getSummary().getEnvironment();
116+
} else if (notification.getExternalPurchaseToken() != null) {
117+
bundleId = notification.getExternalPurchaseToken().getBundleId();
118+
appAppleId = notification.getExternalPurchaseToken().getAppAppleId();
119+
String externalPurchaseId = notification.getExternalPurchaseToken().getExternalPurchaseId();
120+
if (externalPurchaseId != null && externalPurchaseId.startsWith("SANDBOX")) {
121+
notificationEnv = Environment.SANDBOX;
122+
} else {
123+
notificationEnv = Environment.PRODUCTION;
124+
}
125+
} else {
126+
bundleId = null;
127+
appAppleId = null;
128+
notificationEnv = null;
129+
}
130+
verifyNotification(bundleId, appAppleId, notificationEnv);
131+
return notification;
132+
}
133+
134+
protected void verifyNotification(String bundleId, Long appAppleId, Environment notificationEnv) throws VerificationException {
108135
if (!this.bundleId.equals(bundleId) || (this.environment.equals(Environment.PRODUCTION) && !this.appAppleId.equals(appAppleId))) {
109136
throw new VerificationException(VerificationStatus.INVALID_APP_IDENTIFIER);
110137
}
111138
if (!this.environment.equals(notificationEnv)) {
112139
throw new VerificationException(VerificationStatus.INVALID_ENVIRONMENT);
113140
}
114-
return notification;
115141
}
116142

117143
/**

0 commit comments

Comments
 (0)