@hyodotdev/openiap-commerce-protocol 0.0.0-bootstrap.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CONVENTION.md +168 -0
  2. package/DESIGN.md +1056 -0
  3. package/README.md +227 -5
  4. package/SPEC.md +1471 -0
  5. package/conformance/index.d.ts +303 -0
  6. package/conformance/index.mjs +2126 -0
  7. package/conformance/mock-provider.mjs +491 -0
  8. package/examples/entitlement-granted-no-subscription.json +12 -0
  9. package/examples/entitlement-revoked.json +21 -0
  10. package/examples/provider-capabilities.json +209 -0
  11. package/examples/store-event-mapping.json +287 -0
  12. package/examples/subscription-canceled.json +22 -0
  13. package/examples/subscription-product-changed.json +30 -0
  14. package/examples/subscription-renewed.json +29 -0
  15. package/examples/verify-purchase-request.json +6 -0
  16. package/examples/verify-purchase-result.json +7 -0
  17. package/generated/bindings/graphql-operations.json +87 -0
  18. package/generated/bindings/http-binding.json +143 -0
  19. package/generated/bindings/introspection-signature.json +320 -0
  20. package/generated/bindings/operations-sdl.json +4 -0
  21. package/generated/bindings/operations.graphql +366 -0
  22. package/generated/commerce-protocol.graphql +1219 -0
  23. package/generated/openapi/commerce-protocol.openapi.json +1413 -0
  24. package/generated/schemas/commerce-event.schema.json +499 -0
  25. package/generated/schemas/commerce-protocol.bundle.schema.json +1576 -0
  26. package/generated/schemas/operations.schema.json +578 -0
  27. package/generated/schemas/primitives.schema.json +101 -0
  28. package/generated/schemas/provider-capabilities.schema.json +205 -0
  29. package/generated/schemas/store-event-mapping.schema.json +211 -0
  30. package/generated/vectors/lifecycle.json +908 -0
  31. package/generated/vectors/operations.json +1122 -0
  32. package/package.json +62 -12
  33. package/schema/01-primitives.graphql +102 -0
  34. package/schema/02-commerce-event.graphql +195 -0
  35. package/schema/03-provider-capabilities.graphql +139 -0
  36. package/schema/04-store-event-mapping.graphql +98 -0
  37. package/schema/05-operations.graphql +461 -0
  38. package/schema/06-compiler-vocabulary.graphql +139 -0
  39. package/schema/07-protocol-metadata.graphql +76 -0
  40. package/src/index.d.ts +63 -0
  41. package/src/index.mjs +121 -0
  42. package/vectors/signatures.json +139 -0
@@ -0,0 +1,209 @@
1
+ {
2
+ "$comment": "The reference implementation's own descriptor, published as the worked example. Its `implementation` axis is one backend's answer, not a specification requirement; the `provider` axis is a fact about each store.",
3
+ "specVersion": "1.0",
4
+ "implementation": {
5
+ "name": "IAPKit"
6
+ },
7
+ "eventTypes": [
8
+ "subscription.started",
9
+ "subscription.renewed",
10
+ "subscription.recovered",
11
+ "subscription.entered_grace_period",
12
+ "subscription.entered_billing_retry",
13
+ "subscription.expired",
14
+ "subscription.canceled",
15
+ "subscription.uncanceled",
16
+ "subscription.revoked",
17
+ "subscription.refunded",
18
+ "subscription.product_changed",
19
+ "subscription.price_changed",
20
+ "subscription.deferred",
21
+ "subscription.paused",
22
+ "subscription.resumed",
23
+ "entitlement.granted",
24
+ "entitlement.revoked"
25
+ ],
26
+ "profiles": {
27
+ "verification": "1.0",
28
+ "entitlements": "1.0",
29
+ "events": "1.0",
30
+ "accountLifecycle": "1.0"
31
+ },
32
+ "bindings": {
33
+ "rest": "1.0",
34
+ "graphql": "1.0"
35
+ },
36
+ "stores": {
37
+ "apple": {
38
+ "initialValidation": {
39
+ "provider": true,
40
+ "implementation": true
41
+ },
42
+ "serverNotifications": {
43
+ "provider": true,
44
+ "implementation": true
45
+ },
46
+ "subscriptions": {
47
+ "provider": true,
48
+ "implementation": true
49
+ },
50
+ "renewalEvents": {
51
+ "provider": true,
52
+ "implementation": true
53
+ },
54
+ "refundEvents": {
55
+ "provider": true,
56
+ "implementation": true
57
+ },
58
+ "expiration": {
59
+ "provider": true,
60
+ "implementation": true
61
+ },
62
+ "reconciliation": {
63
+ "provider": true,
64
+ "implementation": false,
65
+ "notes": "The App Store Server API can be polled for authoritative subscription status, but this implementation runs no scheduled pass. A notification lost past Apple's retry window is therefore not self-healing."
66
+ },
67
+ "entitlements": {
68
+ "provider": true,
69
+ "implementation": true
70
+ },
71
+ "revenueAmount": {
72
+ "provider": true,
73
+ "implementation": true
74
+ }
75
+ },
76
+ "google": {
77
+ "initialValidation": {
78
+ "provider": true,
79
+ "implementation": true
80
+ },
81
+ "serverNotifications": {
82
+ "provider": true,
83
+ "implementation": true
84
+ },
85
+ "subscriptions": {
86
+ "provider": true,
87
+ "implementation": true
88
+ },
89
+ "renewalEvents": {
90
+ "provider": true,
91
+ "implementation": true
92
+ },
93
+ "refundEvents": {
94
+ "provider": true,
95
+ "implementation": true
96
+ },
97
+ "expiration": {
98
+ "provider": true,
99
+ "implementation": true
100
+ },
101
+ "reconciliation": {
102
+ "provider": true,
103
+ "implementation": false,
104
+ "notes": "purchases.subscriptionsv2.get is authoritative and is called to enrich notifications, but no scheduled pass re-reads state, so a dropped RTDN message is not repaired."
105
+ },
106
+ "entitlements": {
107
+ "provider": true,
108
+ "implementation": true
109
+ },
110
+ "revenueAmount": {
111
+ "provider": true,
112
+ "implementation": true,
113
+ "notes": "Renewal notifications carry the amount the store charged. A refund arrives as a voided-purchase notification, which carries none, so a reversal has no amount to attribute."
114
+ }
115
+ },
116
+ "horizon": {
117
+ "initialValidation": {
118
+ "provider": true,
119
+ "implementation": true,
120
+ "notes": "The Graph verify_entitlement endpoint answers whether the viewer currently owns a SKU. It returns ownership only \u2014 no amount, currency, expiry, state, or transaction identity."
121
+ },
122
+ "serverNotifications": {
123
+ "provider": false,
124
+ "implementation": false,
125
+ "notes": "Meta exposes no server-to-server notification channel for commerce, so no backend can observe Horizon lifecycle changes as they happen."
126
+ },
127
+ "subscriptions": {
128
+ "provider": false,
129
+ "implementation": false,
130
+ "notes": "No server-side subscription resource exists to read, so no canonical subscription record can be maintained."
131
+ },
132
+ "renewalEvents": {
133
+ "provider": false,
134
+ "implementation": false,
135
+ "notes": "No renewal signal exists on any Meta server API."
136
+ },
137
+ "refundEvents": {
138
+ "provider": false,
139
+ "implementation": false,
140
+ "notes": "refund_iap_entitlement performs a refund; it does not report refunds initiated elsewhere."
141
+ },
142
+ "expiration": {
143
+ "provider": false,
144
+ "implementation": false,
145
+ "notes": "No expiry timestamp is exposed. Loss of entitlement is observable only by asking again."
146
+ },
147
+ "reconciliation": {
148
+ "provider": true,
149
+ "implementation": false,
150
+ "notes": "verify_entitlement could be re-polled on a schedule to approximate reconciliation, but this implementation runs no such pass."
151
+ },
152
+ "entitlements": {
153
+ "provider": true,
154
+ "implementation": true,
155
+ "notes": "Point-in-time only. The answer is valid at the moment it is asked and carries no validity window."
156
+ },
157
+ "revenueAmount": {
158
+ "provider": false,
159
+ "implementation": false,
160
+ "notes": "No monetary amount is returned by any Meta server commerce API, so no revenue can be attributed with store provenance."
161
+ }
162
+ },
163
+ "amazon": {
164
+ "initialValidation": {
165
+ "provider": true,
166
+ "implementation": true
167
+ },
168
+ "serverNotifications": {
169
+ "provider": true,
170
+ "implementation": false,
171
+ "notes": "Amazon Real-Time Notifications exist, but this implementation integrates no receiver for them. This is an implementation gap, not a store limitation."
172
+ },
173
+ "subscriptions": {
174
+ "provider": true,
175
+ "implementation": false,
176
+ "notes": "Real-Time Notifications carry the lifecycle transitions a canonical record needs. This implementation polls Receipt Verification Service instead, which reports current validity rather than transitions."
177
+ },
178
+ "renewalEvents": {
179
+ "provider": true,
180
+ "implementation": false,
181
+ "notes": "Real-Time Notifications are the store's lifecycle channel. This implementation polls Receipt Verification Service, which reports current validity rather than transitions."
182
+ },
183
+ "refundEvents": {
184
+ "provider": true,
185
+ "implementation": false,
186
+ "notes": "Real-Time Notifications are the store's lifecycle channel. Polling Receipt Verification Service surfaces a cancellation only as a later validity change."
187
+ },
188
+ "expiration": {
189
+ "provider": true,
190
+ "implementation": false,
191
+ "notes": "Real-Time Notifications are the store's lifecycle channel. Polling Receipt Verification Service only infers expiry from a later response."
192
+ },
193
+ "reconciliation": {
194
+ "provider": true,
195
+ "implementation": true,
196
+ "notes": "A scheduled pass re-reads Receipt Verification Service for active receipts. The interval is bounded but not guaranteed, so a change is observed within roughly two days rather than promptly. Cadence is an implementation choice, not a contract."
197
+ },
198
+ "entitlements": {
199
+ "provider": true,
200
+ "implementation": true
201
+ },
202
+ "revenueAmount": {
203
+ "provider": false,
204
+ "implementation": false,
205
+ "notes": "Neither Receipt Verification Service nor the mapping this version defines for Amazon carries an amount, so no revenue can be attributed with store provenance."
206
+ }
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,287 @@
1
+ {
2
+ "specVersion": "1.0",
3
+ "stores": {
4
+ "apple": {
5
+ "notificationChannel": "App Store Server Notifications V2",
6
+ "notes": "The signed JWS payload must be verified against Apple's certificate chain before any mapping is applied. `notificationUUID` is the value that belongs in `sourceStoreEventId`.",
7
+ "mappings": [
8
+ {
9
+ "storeNotification": "SUBSCRIBED",
10
+ "storeSubtype": "INITIAL_BUY",
11
+ "whenNoPriorStoreEvent": true,
12
+ "event": "subscription.started",
13
+ "notes": "This is the first store event for the purchase, so it begins the lifecycle even if a client receipt already created a record."
14
+ },
15
+ {
16
+ "storeNotification": "SUBSCRIBED",
17
+ "storeSubtype": "INITIAL_BUY",
18
+ "event": "subscription.recovered",
19
+ "notes": "Earlier store history exists for this purchase, so the reused original transaction identity resumes that lifecycle instead of beginning one."
20
+ },
21
+ {
22
+ "storeNotification": "SUBSCRIBED",
23
+ "storeSubtype": "RESUBSCRIBE",
24
+ "whenNoPriorStoreEvent": true,
25
+ "event": "subscription.started",
26
+ "notes": "With no earlier store event for the purchase, this resubscription begins the lifecycle even if a client receipt already created a record."
27
+ },
28
+ {
29
+ "storeNotification": "SUBSCRIBED",
30
+ "storeSubtype": "RESUBSCRIBE",
31
+ "event": "subscription.recovered",
32
+ "notes": "Earlier store history exists, and Apple reuses originalTransactionId across a resubscribe, so this revives the existing lifecycle."
33
+ },
34
+ {
35
+ "storeNotification": "DID_RENEW",
36
+ "storeSubtype": "BILLING_RECOVERY",
37
+ "event": "subscription.recovered",
38
+ "notes": "Billing succeeded after a failure. Apple marks the difference with this subtype."
39
+ },
40
+ {
41
+ "storeNotification": "DID_RENEW",
42
+ "event": "subscription.renewed",
43
+ "notes": "An ordinary renewal: the previous period completed and another began."
44
+ },
45
+ {
46
+ "storeNotification": "DID_FAIL_TO_RENEW",
47
+ "storeSubtype": "GRACE_PERIOD",
48
+ "event": "subscription.entered_grace_period",
49
+ "notes": "Access is retained while Apple retries. The deadline is the grace window's end, not the period that failed."
50
+ },
51
+ {
52
+ "storeNotification": "DID_FAIL_TO_RENEW",
53
+ "event": "subscription.entered_billing_retry",
54
+ "notes": "No subtype means no grace period was configured, so access is suspended. The subtype is what separates the two outcomes."
55
+ },
56
+ {
57
+ "storeNotification": "GRACE_PERIOD_EXPIRED",
58
+ "event": "subscription.entered_billing_retry",
59
+ "notes": "Grace ended with no successful renewal, so access is now suspended while Apple keeps retrying."
60
+ },
61
+ {
62
+ "storeNotification": "EXPIRED",
63
+ "event": "subscription.expired"
64
+ },
65
+ {
66
+ "storeNotification": "DID_CHANGE_RENEWAL_STATUS",
67
+ "storeSubtype": "AUTO_RENEW_DISABLED",
68
+ "event": "subscription.canceled",
69
+ "notes": "Auto-renew was turned off. Entitlement continues until the paid period ends."
70
+ },
71
+ {
72
+ "storeNotification": "DID_CHANGE_RENEWAL_STATUS",
73
+ "storeSubtype": "AUTO_RENEW_ENABLED",
74
+ "event": "subscription.uncanceled"
75
+ },
76
+ {
77
+ "storeNotification": "DID_CHANGE_RENEWAL_PREF",
78
+ "event": "subscription.product_changed",
79
+ "notes": "An UPGRADE subtype takes effect immediately; a downgrade or scheduled crossgrade describes the next period, so the canonical product stays unchanged until renewal."
80
+ },
81
+ {
82
+ "storeNotification": "PRICE_INCREASE",
83
+ "whenNoPriorStoreEvent": true,
84
+ "event": null,
85
+ "notes": "No earlier store event establishes a price baseline, so nothing is emitted."
86
+ },
87
+ {
88
+ "storeNotification": "PRICE_INCREASE",
89
+ "event": "subscription.price_changed"
90
+ },
91
+ {
92
+ "storeNotification": "REVOKE",
93
+ "event": "subscription.revoked"
94
+ },
95
+ {
96
+ "storeNotification": "REFUND",
97
+ "event": "subscription.refunded",
98
+ "notes": "Applies to a subscription; refunds of one-time products are outside this version."
99
+ },
100
+ {
101
+ "storeNotification": "REFUND_REVERSED",
102
+ "whenNoPriorStoreEvent": true,
103
+ "event": "subscription.started",
104
+ "notes": "With no earlier store event for the purchase, the reinstated purchase begins the lifecycle."
105
+ },
106
+ {
107
+ "storeNotification": "REFUND_REVERSED",
108
+ "event": "subscription.recovered",
109
+ "notes": "Apple reversed a refund after earlier store history, so the subscription is revived. Access returns through the entitlement delta, not through a fresh start."
110
+ },
111
+ {
112
+ "storeNotification": "CONSUMPTION_REQUEST",
113
+ "event": null,
114
+ "notes": "Apple is asking the developer for consumption data. It reports no lifecycle change, so it emits nothing."
115
+ },
116
+ {
117
+ "storeNotification": "TEST",
118
+ "event": null,
119
+ "notes": "Delivery test from App Store Connect. Acknowledge it and emit nothing."
120
+ }
121
+ ]
122
+ },
123
+ "google": {
124
+ "notificationChannel": "Google Play Real-Time Developer Notifications",
125
+ "notes": "RTDN carries no subscription detail beyond the token, so an implementation must call purchases.subscriptionsv2.get to resolve state, expiry, price, and cancellation reason before it can emit a complete event. The Pub/Sub messageId is the value that belongs in `sourceStoreEventId`. Google reissues purchaseToken across upgrade and downgrade, so one logical subscription can span several tokens.",
126
+ "mappings": [
127
+ {
128
+ "storeNotification": "SUBSCRIPTION_PURCHASED",
129
+ "storeNotificationCode": "4",
130
+ "whenNoPriorStoreEvent": true,
131
+ "event": "subscription.started",
132
+ "notes": "This is the first store event for the purchase, so it begins the lifecycle even if a client receipt already created a record."
133
+ },
134
+ {
135
+ "storeNotification": "SUBSCRIPTION_PURCHASED",
136
+ "storeNotificationCode": "4",
137
+ "event": "subscription.recovered",
138
+ "notes": "Earlier store history exists, so this revives the lifecycle rather than starting a new one. On an upgrade or downgrade Google reissues the purchase token and links it to the predecessor; an emitter that follows that link reports the product or renewal change instead, which is outside what this table can express."
139
+ },
140
+ {
141
+ "storeNotification": "SUBSCRIPTION_RENEWED",
142
+ "storeNotificationCode": "2",
143
+ "event": "subscription.renewed"
144
+ },
145
+ {
146
+ "storeNotification": "SUBSCRIPTION_RECOVERED",
147
+ "storeNotificationCode": "1",
148
+ "whenPreviousState": ["Paused"],
149
+ "event": "subscription.resumed",
150
+ "notes": "A paused subscription became active again. Google sends the same code as a recovery, so only the prior state separates them."
151
+ },
152
+ {
153
+ "storeNotification": "SUBSCRIPTION_RECOVERED",
154
+ "storeNotificationCode": "1",
155
+ "event": "subscription.recovered",
156
+ "notes": "Billing succeeded after a failure."
157
+ },
158
+ {
159
+ "storeNotification": "SUBSCRIPTION_IN_GRACE_PERIOD",
160
+ "storeNotificationCode": "6",
161
+ "event": "subscription.entered_grace_period"
162
+ },
163
+ {
164
+ "storeNotification": "SUBSCRIPTION_ON_HOLD",
165
+ "storeNotificationCode": "5",
166
+ "event": "subscription.entered_billing_retry"
167
+ },
168
+ {
169
+ "storeNotification": "SUBSCRIPTION_EXPIRED",
170
+ "storeNotificationCode": "13",
171
+ "event": "subscription.expired"
172
+ },
173
+ {
174
+ "storeNotification": "SUBSCRIPTION_CANCELED",
175
+ "storeNotificationCode": "3",
176
+ "event": "subscription.canceled",
177
+ "notes": "Access continues until the current period or installment commitment ends."
178
+ },
179
+ {
180
+ "storeNotification": "SUBSCRIPTION_CANCELLATION_SCHEDULED",
181
+ "storeNotificationCode": "18",
182
+ "event": "subscription.canceled"
183
+ },
184
+ {
185
+ "storeNotification": "SUBSCRIPTION_RESTARTED",
186
+ "storeNotificationCode": "7",
187
+ "event": "subscription.uncanceled",
188
+ "notes": "Auto-renew was re-enabled while the period was still active."
189
+ },
190
+ {
191
+ "storeNotification": "SUBSCRIPTION_REVOKED",
192
+ "storeNotificationCode": "12",
193
+ "event": "subscription.revoked"
194
+ },
195
+ {
196
+ "storeNotification": "SUBSCRIPTION_ITEMS_CHANGED",
197
+ "storeNotificationCode": "17",
198
+ "event": "subscription.product_changed",
199
+ "notes": "When the replacement product cannot be resolved from the notification alone, an emitter holds the canonical product and emits nothing until a later verification settles it."
200
+ },
201
+ {
202
+ "storeNotification": "SUBSCRIPTION_PRICE_CHANGE_CONFIRMED",
203
+ "storeNotificationCode": "8",
204
+ "whenNoPriorStoreEvent": true,
205
+ "event": null,
206
+ "notes": "No earlier store event establishes a price baseline."
207
+ },
208
+ {
209
+ "storeNotification": "SUBSCRIPTION_PRICE_CHANGE_CONFIRMED",
210
+ "storeNotificationCode": "8",
211
+ "event": "subscription.price_changed"
212
+ },
213
+ {
214
+ "storeNotification": "SUBSCRIPTION_PRICE_CHANGE_UPDATED",
215
+ "storeNotificationCode": "19",
216
+ "whenNoPriorStoreEvent": true,
217
+ "event": null,
218
+ "notes": "No earlier store event establishes a price baseline."
219
+ },
220
+ {
221
+ "storeNotification": "SUBSCRIPTION_PRICE_CHANGE_UPDATED",
222
+ "storeNotificationCode": "19",
223
+ "event": "subscription.price_changed"
224
+ },
225
+ {
226
+ "storeNotification": "SUBSCRIPTION_DEFERRED",
227
+ "storeNotificationCode": "9",
228
+ "whenNoPriorStoreEvent": true,
229
+ "event": null,
230
+ "notes": "No earlier store event establishes a billing-date baseline."
231
+ },
232
+ {
233
+ "storeNotification": "SUBSCRIPTION_DEFERRED",
234
+ "storeNotificationCode": "9",
235
+ "event": "subscription.deferred",
236
+ "notes": "Recurrence time was extended without a product change."
237
+ },
238
+ {
239
+ "storeNotification": "SUBSCRIPTION_PAUSED",
240
+ "storeNotificationCode": "10",
241
+ "event": "subscription.paused"
242
+ },
243
+ {
244
+ "storeNotification": "SUBSCRIPTION_PAUSE_SCHEDULE_CHANGED",
245
+ "storeNotificationCode": "11",
246
+ "event": null,
247
+ "notes": "Schedule metadata only. It does not change current entitlement, so it emits nothing."
248
+ },
249
+ {
250
+ "storeNotification": "SUBSCRIPTION_PENDING_PURCHASE_CANCELED",
251
+ "storeNotificationCode": "20",
252
+ "event": null,
253
+ "notes": "No completed purchase or entitlement ever existed, so there is no lifecycle change to report."
254
+ },
255
+ {
256
+ "storeNotification": "SUBSCRIPTION_PRICE_STEP_UP_CONSENT_UPDATED",
257
+ "storeNotificationCode": "22",
258
+ "event": null,
259
+ "notes": "Consent metadata. It changes neither entitlement nor the catalog price."
260
+ },
261
+ {
262
+ "storeNotification": "voidedPurchaseNotification",
263
+ "storeSubtype": "1",
264
+ "event": "subscription.refunded",
265
+ "notes": "Sent on its own field of the message rather than as a subscription notification type. The qualifier is Google's productType=1 (subscription); productType=2 is a one-time purchase and has no matching row in this version."
266
+ },
267
+ {
268
+ "storeNotification": "testNotification",
269
+ "event": null,
270
+ "notes": "Delivery test from Play Console. Acknowledge it and emit nothing."
271
+ }
272
+ ]
273
+ },
274
+ "horizon": {
275
+ "notificationChannel": null,
276
+ "notes": "Meta publishes no server-to-server commerce notification channel. Its Graph commerce surface answers whether a user currently owns a SKU, plus an endpoint that performs a refund but reports none — no amount, currency, expiry, state, or transaction identity. Nothing can therefore be mapped from a notification, and no canonical subscription record can be maintained. A backend that re-asks the entitlement question on a schedule can still observe the answer changing, which is enough for the entitlement events below but never for subscription lifecycle: it learns that access ended, never why or when. This version specifies no cadence for that polling, so those events remain implementation-defined; the capability descriptor is where an implementation states whether it does it at all. A consumer MUST read that descriptor rather than infer absence of a subscription from absence of events.",
277
+ "derivableByPolling": ["entitlement.granted", "entitlement.revoked"],
278
+ "mappings": []
279
+ },
280
+ "amazon": {
281
+ "notificationChannel": "Amazon Real-Time Notifications",
282
+ "notes": "Amazon publishes this channel, but this version of the specification defines no mapping for it and the reference implementation integrates no receiver. That is a gap in this document and in that backend, not a limitation of the store — the capability descriptor's `implementation` axis is where a backend records its own side. Receipt Verification Service, which some backends poll instead, reports current validity rather than transitions, so polling it reveals that entitlement changed without attributing the change to a lifecycle event.",
283
+ "derivableByPolling": ["entitlement.granted", "entitlement.revoked"],
284
+ "mappings": []
285
+ }
286
+ }
287
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "eventId": "evt_01J8Z5B2R9P4Q7S1D3G6K8M0V2",
3
+ "eventType": "subscription.canceled",
4
+ "eventVersion": "1.0",
5
+ "occurredAt": 1756304400000,
6
+ "processedAt": 1756304401110,
7
+ "store": "apple",
8
+ "environment": "production",
9
+ "projectId": "proj_7f3a9c",
10
+ "userId": "user_5e91a7",
11
+ "productId": "premium.monthly",
12
+ "subscription": {
13
+ "state": "Active",
14
+ "productId": "premium.monthly",
15
+ "expiresAt": 1758979200000,
16
+ "willRenew": false,
17
+ "cancellationReason": "UserCanceled",
18
+ "active": true
19
+ },
20
+ "sourceStoreEventId": "9a4c2e1f-5b6d-7c8e-9f0a-1b2c3d4e5f60",
21
+ "originalTransactionId": "2000000811111111"
22
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "eventId": "evt_01J8Z7D6V3Y8Z1A4F6J9N2Q3C5",
3
+ "eventType": "subscription.product_changed",
4
+ "eventVersion": "1.0",
5
+ "occurredAt": 1756390800000,
6
+ "processedAt": 1756390801900,
7
+ "store": "apple",
8
+ "environment": "sandbox",
9
+ "projectId": "proj_7f3a9c",
10
+ "userId": "user_5e91a7",
11
+ "productId": "premium.annual",
12
+ "previousProductId": "premium.monthly",
13
+ "originalTransactionId": "2000000811111111",
14
+ "subscription": {
15
+ "state": "Active",
16
+ "productId": "premium.annual",
17
+ "expiresAt": 1787926800000,
18
+ "renewsAt": 1787926800000,
19
+ "willRenew": true,
20
+ "active": true
21
+ },
22
+ "price": {
23
+ "currency": "EUR",
24
+ "amountMicros": 79990000,
25
+ "provenance": "store"
26
+ },
27
+ "extensions": {
28
+ "appleSubtype": "UPGRADE"
29
+ }
30
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "eventId": "evt_01J8Z4WQK7N2M0X5A9C3F6H8T1",
3
+ "eventType": "subscription.renewed",
4
+ "eventVersion": "1.0",
5
+ "occurredAt": 1756300800000,
6
+ "processedAt": 1756300801420,
7
+ "store": "apple",
8
+ "environment": "production",
9
+ "projectId": "proj_7f3a9c",
10
+ "applicationId": "app_2b8d41",
11
+ "userId": "user_5e91a7",
12
+ "productId": "premium.monthly",
13
+ "transactionId": "2000000912345678",
14
+ "originalTransactionId": "2000000811111111",
15
+ "subscription": {
16
+ "state": "Active",
17
+ "productId": "premium.monthly",
18
+ "expiresAt": 1758979200000,
19
+ "renewsAt": 1758979200000,
20
+ "willRenew": true,
21
+ "active": true
22
+ },
23
+ "price": {
24
+ "currency": "USD",
25
+ "amountMicros": 9990000,
26
+ "provenance": "store"
27
+ },
28
+ "sourceStoreEventId": "8f3b1c2d-4e5a-6b7c-8d9e-0f1a2b3c4d5e"
29
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "store": "apple",
3
+ "apple": {
4
+ "jws": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoiMjAwMDAwMDEyMzQ1Njc4OSIsInByb2R1Y3RJZCI6InByZW1pdW0ubW9udGhseSJ9.MEUCIQDkQmFrb2Zha2VzaWduYXR1cmVmb3JleGFtcGxlAiBkb2N1bWVudG9ubHlmYWtlc2lnbmF0dXJldmFsdWU"
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "store": "apple",
3
+ "isValid": true,
4
+ "state": "ENTITLED",
5
+ "productId": "premium.monthly",
6
+ "environment": "production"
7
+ }