@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.
- package/CONVENTION.md +168 -0
- package/DESIGN.md +1056 -0
- package/README.md +227 -5
- package/SPEC.md +1471 -0
- package/conformance/index.d.ts +303 -0
- package/conformance/index.mjs +2126 -0
- package/conformance/mock-provider.mjs +491 -0
- package/examples/entitlement-granted-no-subscription.json +12 -0
- package/examples/entitlement-revoked.json +21 -0
- package/examples/provider-capabilities.json +209 -0
- package/examples/store-event-mapping.json +287 -0
- package/examples/subscription-canceled.json +22 -0
- package/examples/subscription-product-changed.json +30 -0
- package/examples/subscription-renewed.json +29 -0
- package/examples/verify-purchase-request.json +6 -0
- package/examples/verify-purchase-result.json +7 -0
- package/generated/bindings/graphql-operations.json +87 -0
- package/generated/bindings/http-binding.json +143 -0
- package/generated/bindings/introspection-signature.json +320 -0
- package/generated/bindings/operations-sdl.json +4 -0
- package/generated/bindings/operations.graphql +366 -0
- package/generated/commerce-protocol.graphql +1219 -0
- package/generated/openapi/commerce-protocol.openapi.json +1413 -0
- package/generated/schemas/commerce-event.schema.json +499 -0
- package/generated/schemas/commerce-protocol.bundle.schema.json +1576 -0
- package/generated/schemas/operations.schema.json +578 -0
- package/generated/schemas/primitives.schema.json +101 -0
- package/generated/schemas/provider-capabilities.schema.json +205 -0
- package/generated/schemas/store-event-mapping.schema.json +211 -0
- package/generated/vectors/lifecycle.json +908 -0
- package/generated/vectors/operations.json +1122 -0
- package/package.json +62 -12
- package/schema/01-primitives.graphql +102 -0
- package/schema/02-commerce-event.graphql +195 -0
- package/schema/03-provider-capabilities.graphql +139 -0
- package/schema/04-store-event-mapping.graphql +98 -0
- package/schema/05-operations.graphql +461 -0
- package/schema/06-compiler-vocabulary.graphql +139 -0
- package/schema/07-protocol-metadata.graphql +76 -0
- package/src/index.d.ts +63 -0
- package/src/index.mjs +121 -0
- package/vectors/signatures.json +139 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openiap.dev/schemas/commerce-protocol/1.0/operations.schema.json",
|
|
4
|
+
"title": "OpenIAP Commerce Protocol operations",
|
|
5
|
+
"description": "Input and result documents for the portable operation surface, plus the protocol error shape both transport bindings share. Operations travel over the REST and GraphQL bindings; these schemas validate their JSON projections.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"ProtocolError": {
|
|
8
|
+
"description": "One protocol operation failure. Both bindings carry the same code space: REST wraps it in ProtocolErrorResponse, GraphQL carries `code` in `errors[].extensions`. A message is human-readable and MUST NOT contain credentials, store evidence, stack traces, or implementation source paths.",
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": [
|
|
11
|
+
"code",
|
|
12
|
+
"message"
|
|
13
|
+
],
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"properties": {
|
|
16
|
+
"code": {
|
|
17
|
+
"description": "A portable protocol error code. The value space is OPEN: a caller MUST tolerate a code it does not recognise and treat it as a failure of the operation, and a MINOR version can add a code. The listed values are the ones this version names; SPEC.md 8 defines each one and its HTTP status.",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"pattern": "^[A-Z][A-Z0-9_]*$",
|
|
20
|
+
"examples": [
|
|
21
|
+
"INVALID_REQUEST",
|
|
22
|
+
"UNAUTHORIZED",
|
|
23
|
+
"FORBIDDEN",
|
|
24
|
+
"NOT_FOUND",
|
|
25
|
+
"PURCHASE_NOT_FOUND",
|
|
26
|
+
"VERIFICATION_FAILED",
|
|
27
|
+
"CONFLICT",
|
|
28
|
+
"RATE_LIMITED",
|
|
29
|
+
"UNSUPPORTED_PROFILE",
|
|
30
|
+
"UNSUPPORTED_STORE",
|
|
31
|
+
"INTERNAL_ERROR"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"message": {
|
|
35
|
+
"description": "Human-readable failure summary. Never carries credentials, store evidence, stack traces, or source paths.",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"minLength": 1
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"ProtocolErrorResponse": {
|
|
42
|
+
"description": "The REST error envelope. Every non-success REST response carries exactly this shape — CLOSED, so an error body cannot smuggle members past the tokenless rules (a failure response is the easiest place to hide a leak, because callers rarely inspect it).",
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": [
|
|
45
|
+
"error"
|
|
46
|
+
],
|
|
47
|
+
"additionalProperties": false,
|
|
48
|
+
"properties": {
|
|
49
|
+
"error": {
|
|
50
|
+
"$ref": "#/$defs/ProtocolError"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"AppleEvidence": {
|
|
55
|
+
"description": "Store evidence for one Apple App Store purchase: the signed StoreKit 2 transaction JWS the app received from the store.",
|
|
56
|
+
"type": "object",
|
|
57
|
+
"required": [
|
|
58
|
+
"jws"
|
|
59
|
+
],
|
|
60
|
+
"additionalProperties": true,
|
|
61
|
+
"properties": {
|
|
62
|
+
"jws": {
|
|
63
|
+
"description": "A compact signed transaction JWS. Bounded so an oversized body cannot be forwarded to a store: an Apple transaction JWS is a few kilobytes.",
|
|
64
|
+
"type": "string",
|
|
65
|
+
"minLength": 1,
|
|
66
|
+
"maxLength": 16384
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"GoogleEvidence": {
|
|
71
|
+
"description": "Store evidence for one Google Play purchase: the opaque purchase token the store issued to the device.",
|
|
72
|
+
"type": "object",
|
|
73
|
+
"required": [
|
|
74
|
+
"purchaseToken"
|
|
75
|
+
],
|
|
76
|
+
"additionalProperties": true,
|
|
77
|
+
"properties": {
|
|
78
|
+
"purchaseToken": {
|
|
79
|
+
"description": "An opaque store purchase token. Bounded to keep an oversized body off the store API.",
|
|
80
|
+
"type": "string",
|
|
81
|
+
"minLength": 1,
|
|
82
|
+
"maxLength": 4096
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"HorizonEvidence": {
|
|
87
|
+
"description": "Store evidence for one Meta Horizon (Quest) entitlement. Horizon issues no server-verifiable receipt, so verification identifies the entitlement by user and SKU; the provider holds the store credentials.",
|
|
88
|
+
"type": "object",
|
|
89
|
+
"required": [
|
|
90
|
+
"userId",
|
|
91
|
+
"sku"
|
|
92
|
+
],
|
|
93
|
+
"additionalProperties": true,
|
|
94
|
+
"properties": {
|
|
95
|
+
"userId": {
|
|
96
|
+
"description": "A store-issued user identity, as the store's own API returns it. Distinct from the protocol's opaque `userId`.",
|
|
97
|
+
"type": "string",
|
|
98
|
+
"minLength": 1,
|
|
99
|
+
"maxLength": 512
|
|
100
|
+
},
|
|
101
|
+
"sku": {
|
|
102
|
+
"description": "A store add-on SKU. Bounded to a predictable identifier length.",
|
|
103
|
+
"type": "string",
|
|
104
|
+
"minLength": 1,
|
|
105
|
+
"maxLength": 256
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"AmazonEvidence": {
|
|
110
|
+
"description": "Store evidence for one Amazon Appstore purchase, as returned by the Amazon SDK. `sandbox` selects the RVS sandbox for App Tester receipts where the provider permits it.",
|
|
111
|
+
"type": "object",
|
|
112
|
+
"required": [
|
|
113
|
+
"userId",
|
|
114
|
+
"receiptId"
|
|
115
|
+
],
|
|
116
|
+
"additionalProperties": true,
|
|
117
|
+
"properties": {
|
|
118
|
+
"userId": {
|
|
119
|
+
"description": "A store-issued user identity, as the store's own API returns it. Distinct from the protocol's opaque `userId`.",
|
|
120
|
+
"type": "string",
|
|
121
|
+
"minLength": 1,
|
|
122
|
+
"maxLength": 512
|
|
123
|
+
},
|
|
124
|
+
"receiptId": {
|
|
125
|
+
"description": "A store receipt identifier. Bounded to keep an oversized body off the store API.",
|
|
126
|
+
"type": "string",
|
|
127
|
+
"minLength": 1,
|
|
128
|
+
"maxLength": 4096
|
|
129
|
+
},
|
|
130
|
+
"sandbox": {
|
|
131
|
+
"description": "Verify against the store's sandbox where the provider permits it. Omitted means production.",
|
|
132
|
+
"type": "boolean"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"VerifyPurchaseInput": {
|
|
137
|
+
"description": "One purchase's store evidence, discriminated by `store`. The store space stays open: a provider that does not integrate the named store rejects the call with UNSUPPORTED_STORE rather than failing schema validation, so a future store is a MINOR evidence-member addition, not a breaking change. For each store this version names, the matching evidence member is required.",
|
|
138
|
+
"type": "object",
|
|
139
|
+
"required": [
|
|
140
|
+
"store"
|
|
141
|
+
],
|
|
142
|
+
"additionalProperties": true,
|
|
143
|
+
"properties": {
|
|
144
|
+
"store": {
|
|
145
|
+
"$ref": "primitives.schema.json#/$defs/Store"
|
|
146
|
+
},
|
|
147
|
+
"apple": {
|
|
148
|
+
"$ref": "#/$defs/AppleEvidence"
|
|
149
|
+
},
|
|
150
|
+
"google": {
|
|
151
|
+
"$ref": "#/$defs/GoogleEvidence"
|
|
152
|
+
},
|
|
153
|
+
"horizon": {
|
|
154
|
+
"$ref": "#/$defs/HorizonEvidence"
|
|
155
|
+
},
|
|
156
|
+
"amazon": {
|
|
157
|
+
"$ref": "#/$defs/AmazonEvidence"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"allOf": [
|
|
161
|
+
{
|
|
162
|
+
"if": {
|
|
163
|
+
"properties": {
|
|
164
|
+
"store": {
|
|
165
|
+
"const": "apple"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"required": [
|
|
169
|
+
"store"
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
"then": {
|
|
173
|
+
"properties": {
|
|
174
|
+
"apple": {}
|
|
175
|
+
},
|
|
176
|
+
"required": [
|
|
177
|
+
"apple"
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"if": {
|
|
183
|
+
"properties": {
|
|
184
|
+
"store": {
|
|
185
|
+
"const": "google"
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"required": [
|
|
189
|
+
"store"
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
"then": {
|
|
193
|
+
"properties": {
|
|
194
|
+
"google": {}
|
|
195
|
+
},
|
|
196
|
+
"required": [
|
|
197
|
+
"google"
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"if": {
|
|
203
|
+
"properties": {
|
|
204
|
+
"store": {
|
|
205
|
+
"const": "horizon"
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"required": [
|
|
209
|
+
"store"
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
"then": {
|
|
213
|
+
"properties": {
|
|
214
|
+
"horizon": {}
|
|
215
|
+
},
|
|
216
|
+
"required": [
|
|
217
|
+
"horizon"
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"if": {
|
|
223
|
+
"properties": {
|
|
224
|
+
"store": {
|
|
225
|
+
"const": "amazon"
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"required": [
|
|
229
|
+
"store"
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
"then": {
|
|
233
|
+
"properties": {
|
|
234
|
+
"amazon": {}
|
|
235
|
+
},
|
|
236
|
+
"required": [
|
|
237
|
+
"amazon"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
"VerifyPurchaseResult": {
|
|
244
|
+
"description": "The verification verdict. `isValid` is the authoritative acceptance gate: a caller MUST read it and MUST NOT re-derive acceptance from `state`. A verdict is a statement about the evidence at verification time, never an account mutation — verification binds no user.",
|
|
245
|
+
"type": "object",
|
|
246
|
+
"required": [
|
|
247
|
+
"store",
|
|
248
|
+
"isValid",
|
|
249
|
+
"state"
|
|
250
|
+
],
|
|
251
|
+
"additionalProperties": true,
|
|
252
|
+
"properties": {
|
|
253
|
+
"store": {
|
|
254
|
+
"$ref": "primitives.schema.json#/$defs/Store"
|
|
255
|
+
},
|
|
256
|
+
"isValid": {
|
|
257
|
+
"description": "Whether the provider accepts the evidence. The single authoritative acceptance gate.",
|
|
258
|
+
"type": "boolean"
|
|
259
|
+
},
|
|
260
|
+
"state": {
|
|
261
|
+
"description": "The verification verdict for one piece of purchase evidence, as a normalized token. This is the purchase-validation axis SPEC.md 2.3 warns about — a CANCELED verification verdict and a canceled-but-still-entitled subscription are different facts. The value space is OPEN: a caller MUST tolerate a token it does not know and gate on `isValid` alone.",
|
|
262
|
+
"type": "string",
|
|
263
|
+
"pattern": "^[A-Z][A-Z0-9_]*$",
|
|
264
|
+
"examples": [
|
|
265
|
+
"ENTITLED",
|
|
266
|
+
"PENDING_ACKNOWLEDGMENT",
|
|
267
|
+
"PENDING",
|
|
268
|
+
"CANCELED",
|
|
269
|
+
"EXPIRED",
|
|
270
|
+
"READY_TO_CONSUME",
|
|
271
|
+
"CONSUMED",
|
|
272
|
+
"UNKNOWN",
|
|
273
|
+
"INAUTHENTIC"
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
"productId": {
|
|
277
|
+
"description": "The product the store verified, when its response exposes one. Never the caller's claim.",
|
|
278
|
+
"type": "string",
|
|
279
|
+
"minLength": 1
|
|
280
|
+
},
|
|
281
|
+
"environment": {
|
|
282
|
+
"description": "The store environment the provider verified against, using the same open value space as the event envelope.",
|
|
283
|
+
"$ref": "primitives.schema.json#/$defs/Environment"
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
"BindPurchaseInput": {
|
|
288
|
+
"description": "Connects verified store evidence to the adopter's own opaque user identity. Binding is a server-side account mutation: only the server role may call it, and possession of a purchase token is deliberately not proof of ownership — a provider MUST refuse to move an existing binding through this operation.",
|
|
289
|
+
"type": "object",
|
|
290
|
+
"required": [
|
|
291
|
+
"userId",
|
|
292
|
+
"store"
|
|
293
|
+
],
|
|
294
|
+
"additionalProperties": true,
|
|
295
|
+
"properties": {
|
|
296
|
+
"userId": {
|
|
297
|
+
"$ref": "primitives.schema.json#/$defs/Identifier"
|
|
298
|
+
},
|
|
299
|
+
"store": {
|
|
300
|
+
"$ref": "primitives.schema.json#/$defs/Store"
|
|
301
|
+
},
|
|
302
|
+
"apple": {
|
|
303
|
+
"$ref": "#/$defs/AppleEvidence"
|
|
304
|
+
},
|
|
305
|
+
"google": {
|
|
306
|
+
"$ref": "#/$defs/GoogleEvidence"
|
|
307
|
+
},
|
|
308
|
+
"horizon": {
|
|
309
|
+
"$ref": "#/$defs/HorizonEvidence"
|
|
310
|
+
},
|
|
311
|
+
"amazon": {
|
|
312
|
+
"$ref": "#/$defs/AmazonEvidence"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
"allOf": [
|
|
316
|
+
{
|
|
317
|
+
"if": {
|
|
318
|
+
"properties": {
|
|
319
|
+
"store": {
|
|
320
|
+
"const": "apple"
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
"required": [
|
|
324
|
+
"store"
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
"then": {
|
|
328
|
+
"properties": {
|
|
329
|
+
"apple": {}
|
|
330
|
+
},
|
|
331
|
+
"required": [
|
|
332
|
+
"apple"
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"if": {
|
|
338
|
+
"properties": {
|
|
339
|
+
"store": {
|
|
340
|
+
"const": "google"
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"required": [
|
|
344
|
+
"store"
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
"then": {
|
|
348
|
+
"properties": {
|
|
349
|
+
"google": {}
|
|
350
|
+
},
|
|
351
|
+
"required": [
|
|
352
|
+
"google"
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"if": {
|
|
358
|
+
"properties": {
|
|
359
|
+
"store": {
|
|
360
|
+
"const": "horizon"
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
"required": [
|
|
364
|
+
"store"
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
"then": {
|
|
368
|
+
"properties": {
|
|
369
|
+
"horizon": {}
|
|
370
|
+
},
|
|
371
|
+
"required": [
|
|
372
|
+
"horizon"
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"if": {
|
|
378
|
+
"properties": {
|
|
379
|
+
"store": {
|
|
380
|
+
"const": "amazon"
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
"required": [
|
|
384
|
+
"store"
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
"then": {
|
|
388
|
+
"properties": {
|
|
389
|
+
"amazon": {}
|
|
390
|
+
},
|
|
391
|
+
"required": [
|
|
392
|
+
"amazon"
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
},
|
|
398
|
+
"BindPurchaseResult": {
|
|
399
|
+
"description": "Whether a binding now exists between this user and the evidenced purchase. `bound: false` covers every non-binding outcome — unknown evidence, evidence bound to a different user, or a store the provider cannot bind — without distinguishing them, so the operation cannot be used to probe whether someone else's purchase exists. Re-binding the same user to the same evidence is idempotent and reports `bound: true`.",
|
|
400
|
+
"type": "object",
|
|
401
|
+
"required": [
|
|
402
|
+
"bound"
|
|
403
|
+
],
|
|
404
|
+
"additionalProperties": true,
|
|
405
|
+
"properties": {
|
|
406
|
+
"bound": {
|
|
407
|
+
"description": "True when a binding between this user and the evidenced purchase now exists.",
|
|
408
|
+
"type": "boolean"
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
"SubscriptionStatusInput": {
|
|
413
|
+
"description": "Selects the user whose subscription standing the caller's backend reads.",
|
|
414
|
+
"type": "object",
|
|
415
|
+
"required": [
|
|
416
|
+
"userId"
|
|
417
|
+
],
|
|
418
|
+
"additionalProperties": true,
|
|
419
|
+
"properties": {
|
|
420
|
+
"userId": {
|
|
421
|
+
"description": "The opaque app-scoped user identity the caller's backend selected, in the identity space shared by provider and caller.",
|
|
422
|
+
"$ref": "primitives.schema.json#/$defs/Identifier"
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
"SubscriptionStatusSnapshot": {
|
|
427
|
+
"description": "One subscription as the provider currently records it, shaped for a server-side read. It is tokenless by construction: this object is CLOSED, so the schema itself rejects any member beyond those listed — no purchase token, store transaction identity, signed receipt, or provider-internal record identifier can appear, and the response is safe to hold in a developer backend.",
|
|
428
|
+
"type": "object",
|
|
429
|
+
"required": [
|
|
430
|
+
"productId",
|
|
431
|
+
"state",
|
|
432
|
+
"active"
|
|
433
|
+
],
|
|
434
|
+
"additionalProperties": false,
|
|
435
|
+
"properties": {
|
|
436
|
+
"productId": {
|
|
437
|
+
"description": "Store product identifier of the subscription.",
|
|
438
|
+
"type": "string",
|
|
439
|
+
"minLength": 1
|
|
440
|
+
},
|
|
441
|
+
"state": {
|
|
442
|
+
"$ref": "primitives.schema.json#/$defs/SubscriptionState"
|
|
443
|
+
},
|
|
444
|
+
"active": {
|
|
445
|
+
"description": "The entitlement gate for this subscription, evaluated with the predicate in SPEC.md 2.3 at the provider's read time. A caller gates access on this member, never on `state`.",
|
|
446
|
+
"type": "boolean"
|
|
447
|
+
},
|
|
448
|
+
"store": {
|
|
449
|
+
"$ref": "primitives.schema.json#/$defs/Store"
|
|
450
|
+
},
|
|
451
|
+
"expiresAt": {
|
|
452
|
+
"$ref": "primitives.schema.json#/$defs/Timestamp"
|
|
453
|
+
},
|
|
454
|
+
"renewsAt": {
|
|
455
|
+
"$ref": "primitives.schema.json#/$defs/Timestamp"
|
|
456
|
+
},
|
|
457
|
+
"willRenew": {
|
|
458
|
+
"description": "Whether the store will attempt another billing period, when the provider records it.",
|
|
459
|
+
"type": "boolean"
|
|
460
|
+
},
|
|
461
|
+
"cancellationReason": {
|
|
462
|
+
"description": "Why the subscription stopped renewing, as the same open normalized token space the event envelope uses. Advisory, never a billing fact.",
|
|
463
|
+
"type": "string",
|
|
464
|
+
"minLength": 1
|
|
465
|
+
},
|
|
466
|
+
"startedAt": {
|
|
467
|
+
"$ref": "primitives.schema.json#/$defs/Timestamp"
|
|
468
|
+
},
|
|
469
|
+
"updatedAt": {
|
|
470
|
+
"$ref": "primitives.schema.json#/$defs/Timestamp"
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
"SubscriptionStatusResult": {
|
|
475
|
+
"description": "The user's current subscription standing. `active` answers the gate for the user as a whole; `subscription` is the most relevant record — the current entitling subscription when one exists, otherwise the provider's most recent record as context. It is omitted when the provider has no record for this user. A provider that cannot enumerate the user's records completely MUST fail the operation instead of answering from a partial read.",
|
|
476
|
+
"type": "object",
|
|
477
|
+
"required": [
|
|
478
|
+
"active"
|
|
479
|
+
],
|
|
480
|
+
"additionalProperties": false,
|
|
481
|
+
"properties": {
|
|
482
|
+
"active": {
|
|
483
|
+
"description": "Whether the user is entitled to anything right now. The gate for simple access checks.",
|
|
484
|
+
"type": "boolean"
|
|
485
|
+
},
|
|
486
|
+
"subscription": {
|
|
487
|
+
"$ref": "#/$defs/SubscriptionStatusSnapshot"
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
"EntitlementsInput": {
|
|
492
|
+
"description": "Selects the user whose access decision the caller's backend reads.",
|
|
493
|
+
"type": "object",
|
|
494
|
+
"required": [
|
|
495
|
+
"userId"
|
|
496
|
+
],
|
|
497
|
+
"additionalProperties": true,
|
|
498
|
+
"properties": {
|
|
499
|
+
"userId": {
|
|
500
|
+
"description": "The opaque app-scoped user identity the caller's backend selected, in the identity space shared by provider and caller.",
|
|
501
|
+
"$ref": "primitives.schema.json#/$defs/Identifier"
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
"EntitlementsResult": {
|
|
506
|
+
"description": "Every product the user may access right now, with the entitling subscription records. Unknown, expired, and ambiguous records contribute nothing: a product appears only when its gate is open at the provider's read time. A provider that cannot enumerate the user's records completely MUST fail the operation instead of answering from a partial read.",
|
|
507
|
+
"type": "object",
|
|
508
|
+
"required": [
|
|
509
|
+
"userId",
|
|
510
|
+
"productIds",
|
|
511
|
+
"subscriptions"
|
|
512
|
+
],
|
|
513
|
+
"additionalProperties": false,
|
|
514
|
+
"properties": {
|
|
515
|
+
"userId": {
|
|
516
|
+
"$ref": "primitives.schema.json#/$defs/Identifier"
|
|
517
|
+
},
|
|
518
|
+
"productIds": {
|
|
519
|
+
"description": "Every product the user may access right now, deduplicated.",
|
|
520
|
+
"type": "array",
|
|
521
|
+
"items": {
|
|
522
|
+
"type": "string",
|
|
523
|
+
"minLength": 1
|
|
524
|
+
},
|
|
525
|
+
"uniqueItems": true
|
|
526
|
+
},
|
|
527
|
+
"subscriptions": {
|
|
528
|
+
"description": "The subscription records whose open gates produced `productIds`.",
|
|
529
|
+
"type": "array",
|
|
530
|
+
"items": {
|
|
531
|
+
"$ref": "#/$defs/SubscriptionStatusSnapshot"
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
"EraseUserInput": {
|
|
537
|
+
"description": "Selects the user identity to erase from the provider's records.",
|
|
538
|
+
"type": "object",
|
|
539
|
+
"required": [
|
|
540
|
+
"userId"
|
|
541
|
+
],
|
|
542
|
+
"additionalProperties": true,
|
|
543
|
+
"properties": {
|
|
544
|
+
"userId": {
|
|
545
|
+
"description": "The opaque app-scoped user identity to remove from the provider's subscription records and protocol event identity.",
|
|
546
|
+
"$ref": "primitives.schema.json#/$defs/Identifier"
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
"EraseUserResult": {
|
|
551
|
+
"description": "Acknowledgement of an erasure request. Erasure removes the user identity from the provider's own records and event store; it is asynchronous where the provider processes it as a job, and re-requesting the same user is idempotent — it reports the current job rather than failing. A provider CANNOT unsend events: copies already delivered to the caller's own systems are the caller's responsibility to erase.",
|
|
552
|
+
"type": "object",
|
|
553
|
+
"required": [
|
|
554
|
+
"accepted"
|
|
555
|
+
],
|
|
556
|
+
"additionalProperties": true,
|
|
557
|
+
"properties": {
|
|
558
|
+
"accepted": {
|
|
559
|
+
"description": "Whether the provider accepted the erasure request.",
|
|
560
|
+
"type": "boolean"
|
|
561
|
+
},
|
|
562
|
+
"jobId": {
|
|
563
|
+
"$ref": "primitives.schema.json#/$defs/Identifier"
|
|
564
|
+
},
|
|
565
|
+
"status": {
|
|
566
|
+
"description": "The lifecycle state of a user-erasure job. The value space is open; `completed` is the only terminal token this version names.",
|
|
567
|
+
"type": "string",
|
|
568
|
+
"pattern": "^[a-z][a-z_]*$",
|
|
569
|
+
"examples": [
|
|
570
|
+
"queued",
|
|
571
|
+
"running",
|
|
572
|
+
"completed"
|
|
573
|
+
]
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openiap.dev/schemas/commerce-protocol/1.0/primitives.schema.json",
|
|
4
|
+
"title": "OpenIAP Commerce Protocol primitives",
|
|
5
|
+
"description": "Shared value types. Every other schema in this specification references these definitions rather than restating them.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"Store": {
|
|
8
|
+
"description": "The commerce platform that is authoritative for the purchase. This is a store, not a device platform: one device platform can host several stores. The value space is OPEN. The listed values are the stores this version names, but an implementation that observes commerce on another platform MUST be able to emit it, and a consumer MUST accept and preserve an unrecognised store opaquely rather than reject the event. Use a lowercase, stable, unambiguous token.",
|
|
9
|
+
"type": "string",
|
|
10
|
+
"pattern": "^[a-z][a-z0-9_]*$",
|
|
11
|
+
"examples": [
|
|
12
|
+
"apple",
|
|
13
|
+
"google",
|
|
14
|
+
"horizon",
|
|
15
|
+
"amazon"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"Environment": {
|
|
19
|
+
"description": "The store environment the purchase belongs to. The value space is open: receivers MUST accept and preserve an unrecognised value opaquely and MUST NOT reject an event because of it.",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"minLength": 1,
|
|
22
|
+
"examples": [
|
|
23
|
+
"production",
|
|
24
|
+
"sandbox",
|
|
25
|
+
"xcode"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"Timestamp": {
|
|
29
|
+
"description": "An instant, as integer milliseconds since the Unix epoch, UTC. Every timestamp in this specification uses this encoding. The one exception is the transport signature timestamp, which is in seconds; see the webhook section of SPEC.md.",
|
|
30
|
+
"type": "integer",
|
|
31
|
+
"minimum": 0
|
|
32
|
+
},
|
|
33
|
+
"Identifier": {
|
|
34
|
+
"description": "An opaque, implementation-assigned identifier. Consumers MUST treat it as an opaque string and MUST NOT parse structure out of it.",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"maxLength": 256
|
|
38
|
+
},
|
|
39
|
+
"DataProvenance": {
|
|
40
|
+
"description": "Where a value came from. An implementation MUST NOT present an inferred value as store-authoritative.",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"enum": [
|
|
43
|
+
"store",
|
|
44
|
+
"catalog",
|
|
45
|
+
"inferred"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"Money": {
|
|
49
|
+
"description": "A monetary amount. A missing Money means the amount is unknown; it never means zero.",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"required": [
|
|
52
|
+
"currency",
|
|
53
|
+
"amountMicros",
|
|
54
|
+
"provenance"
|
|
55
|
+
],
|
|
56
|
+
"additionalProperties": true,
|
|
57
|
+
"properties": {
|
|
58
|
+
"currency": {
|
|
59
|
+
"description": "ISO 4217 alphabetic code, uppercase.",
|
|
60
|
+
"type": "string",
|
|
61
|
+
"pattern": "^[A-Z]{3}$"
|
|
62
|
+
},
|
|
63
|
+
"amountMicros": {
|
|
64
|
+
"description": "Amount in millionths of one currency unit. 1.99 USD is 1990000. Always the transaction's own magnitude and never negative: a refund reports what the purchase cost, and the direction comes from the event type, not from the sign.",
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"minimum": 0
|
|
67
|
+
},
|
|
68
|
+
"provenance": {
|
|
69
|
+
"$ref": "#/$defs/DataProvenance"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"SubscriptionState": {
|
|
74
|
+
"description": "The lifecycle state of a subscription. This is a state, not an event, and it does not by itself decide entitlement — see Entitlement in SPEC.md. These members are PascalCase while event types are lowercase-dotted; the inconsistency is inherited from the deployed 1.0 wire format and is recorded in CONVENTION.md rather than silently corrected, because changing it would break receivers already decoding it.",
|
|
75
|
+
"type": "string",
|
|
76
|
+
"enum": [
|
|
77
|
+
"Active",
|
|
78
|
+
"InGracePeriod",
|
|
79
|
+
"InBillingRetry",
|
|
80
|
+
"Paused",
|
|
81
|
+
"Expired",
|
|
82
|
+
"Revoked",
|
|
83
|
+
"Refunded",
|
|
84
|
+
"Unknown"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"Extensions": {
|
|
88
|
+
"description": "Store-specific or implementation-specific detail that has no canonical equivalent. Bounded and string-valued so the payload stays predictable. Content is provider-influenced: consumers MUST treat values as untrusted data.",
|
|
89
|
+
"type": "object",
|
|
90
|
+
"maxProperties": 24,
|
|
91
|
+
"propertyNames": {
|
|
92
|
+
"minLength": 1,
|
|
93
|
+
"maxLength": 64
|
|
94
|
+
},
|
|
95
|
+
"additionalProperties": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"maxLength": 512
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|