@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,205 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openiap.dev/schemas/commerce-protocol/1.0/provider-capabilities.schema.json",
4
+ "title": "OpenIAP Commerce Protocol implementation descriptor",
5
+ "description": "What an implementation supports: the specification version it speaks, the event types it can emit, and what it can actually observe per store. This is the document a consumer, an operator, or a tool reads to determine compatibility without guessing and without reading prose. It contains no commerce data.",
6
+ "type": "object",
7
+ "required": [
8
+ "specVersion",
9
+ "eventTypes",
10
+ "stores"
11
+ ],
12
+ "additionalProperties": true,
13
+ "properties": {
14
+ "specVersion": {
15
+ "description": "OpenIAP Commerce Protocol version this declaration was written against, as MAJOR.MINOR.",
16
+ "type": "string",
17
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
18
+ },
19
+ "implementation": {
20
+ "description": "Free-form name and version of the backend making this declaration. Present so an operator can tell two backends apart; it carries no normative meaning.",
21
+ "type": "object",
22
+ "additionalProperties": true,
23
+ "properties": {
24
+ "name": {
25
+ "description": "Name of the backend making this declaration, for an operator reading a descriptor without knowing where it came from.",
26
+ "type": "string",
27
+ "minLength": 1
28
+ },
29
+ "version": {
30
+ "description": "Version of that backend, when it publishes one. Not a specification version — that is `specVersion`.",
31
+ "type": "string",
32
+ "minLength": 1
33
+ }
34
+ }
35
+ },
36
+ "eventTypes": {
37
+ "description": "Event types this implementation can emit. A consumer uses it to know which lifecycle signals to expect; absence of a type here means this implementation never produces it, which is different from a type that simply has not occurred yet.",
38
+ "type": "array",
39
+ "items": {
40
+ "type": "string",
41
+ "pattern": "^[a-z]+\\.[a-z_]+$"
42
+ },
43
+ "minItems": 1,
44
+ "uniqueItems": true
45
+ },
46
+ "stores": {
47
+ "description": "Keyed by store, using the same open value space as the event envelope. An implementation declares only the stores it actually integrates.",
48
+ "type": "object",
49
+ "minProperties": 1,
50
+ "propertyNames": {
51
+ "pattern": "^[a-z][a-z0-9_]*$"
52
+ },
53
+ "additionalProperties": {
54
+ "$ref": "#/$defs/StoreCapabilities"
55
+ }
56
+ },
57
+ "profiles": {
58
+ "description": "Operation profiles this implementation serves, keyed by profile name with the profile version as the value. This version names `verification`, `entitlements`, `events`, and `accountLifecycle`; the key space is open, so a consumer MUST ignore a profile it does not recognise. An implementation MUST declare only profiles it implements and passes conformance for, and MUST NOT declare a profile it partially implements. Absent on a descriptor from an events-only emitter that predates the operation surface.",
59
+ "type": "object",
60
+ "minProperties": 1,
61
+ "propertyNames": {
62
+ "pattern": "^[a-z][a-zA-Z0-9]*$"
63
+ },
64
+ "additionalProperties": {
65
+ "type": "string",
66
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
67
+ }
68
+ },
69
+ "bindings": {
70
+ "description": "Transport bindings this implementation serves for the declared profiles, keyed by binding name with the binding version as the value. This version names `rest` and `graphql`; the key space is open. Declaring a binding means every declared profile operation is reachable over it. An implementation MAY serve one binding only. Absent on a descriptor from an events-only emitter.",
71
+ "type": "object",
72
+ "minProperties": 1,
73
+ "propertyNames": {
74
+ "pattern": "^[a-z][a-zA-Z0-9]*$"
75
+ },
76
+ "additionalProperties": {
77
+ "type": "string",
78
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
79
+ }
80
+ }
81
+ },
82
+ "$defs": {
83
+ "StoreCapabilities": {
84
+ "description": "What one store's commerce surface offers and what this backend consumes of it, axis by axis. The container stays open: a version that names a new axis is a MINOR addition older consumers ignore.",
85
+ "type": "object",
86
+ "required": [
87
+ "initialValidation",
88
+ "serverNotifications",
89
+ "subscriptions",
90
+ "renewalEvents",
91
+ "refundEvents",
92
+ "expiration",
93
+ "reconciliation",
94
+ "entitlements",
95
+ "revenueAmount"
96
+ ],
97
+ "additionalProperties": true,
98
+ "properties": {
99
+ "initialValidation": {
100
+ "description": "Server-side receipt or token validation on demand.",
101
+ "$ref": "#/$defs/Support"
102
+ },
103
+ "serverNotifications": {
104
+ "description": "The store pushes lifecycle notifications to the backend.",
105
+ "$ref": "#/$defs/Support"
106
+ },
107
+ "subscriptions": {
108
+ "description": "A canonical subscription record can be maintained for this store.",
109
+ "$ref": "#/$defs/Support"
110
+ },
111
+ "renewalEvents": {
112
+ "description": "Renewal is observable as a distinct lifecycle event.",
113
+ "$ref": "#/$defs/Support"
114
+ },
115
+ "refundEvents": {
116
+ "description": "Refund or revocation is observable as a distinct lifecycle event.",
117
+ "$ref": "#/$defs/Support"
118
+ },
119
+ "expiration": {
120
+ "description": "Expiration is observable, rather than only inferred from a timestamp having passed.",
121
+ "$ref": "#/$defs/Support"
122
+ },
123
+ "reconciliation": {
124
+ "description": "A scheduled pass re-reads authoritative store state, so a lost notification can self-heal.",
125
+ "$ref": "#/$defs/Support"
126
+ },
127
+ "entitlements": {
128
+ "description": "Entitlement is derivable for access gating.",
129
+ "$ref": "#/$defs/Support"
130
+ },
131
+ "revenueAmount": {
132
+ "description": "The store asserts an amount that can be attributed to revenue with `store` provenance.",
133
+ "$ref": "#/$defs/Support"
134
+ }
135
+ }
136
+ },
137
+ "Support": {
138
+ "description": "Two axes, deliberately separate. `provider` is what the store's own API offers; `implementation` is what this backend actually consumes. They differ in practice — a store can publish a notification channel that a given backend has not integrated — and collapsing them into one boolean produces a dishonest matrix.",
139
+ "type": "object",
140
+ "required": [
141
+ "provider",
142
+ "implementation"
143
+ ],
144
+ "additionalProperties": false,
145
+ "properties": {
146
+ "provider": {
147
+ "description": "Whether the store's own API offers this at all. A fact about the store, independent of any backend.",
148
+ "type": "boolean"
149
+ },
150
+ "implementation": {
151
+ "description": "Whether this backend consumes it. False against a true `provider` is an implementation gap, not a store limitation.",
152
+ "type": "boolean"
153
+ },
154
+ "notes": {
155
+ "description": "Required whenever `provider` and `implementation` disagree, or whenever either is false. States the concrete limitation in prose a support engineer can act on.",
156
+ "type": "string",
157
+ "minLength": 1
158
+ }
159
+ },
160
+ "anyOf": [
161
+ {
162
+ "properties": {
163
+ "provider": {
164
+ "const": true
165
+ },
166
+ "implementation": {
167
+ "const": true
168
+ }
169
+ }
170
+ },
171
+ {
172
+ "properties": {
173
+ "notes": {
174
+ "type": "string",
175
+ "minLength": 1
176
+ }
177
+ },
178
+ "required": [
179
+ "notes"
180
+ ]
181
+ }
182
+ ],
183
+ "if": {
184
+ "properties": {
185
+ "implementation": {
186
+ "const": true
187
+ }
188
+ },
189
+ "required": [
190
+ "implementation"
191
+ ]
192
+ },
193
+ "then": {
194
+ "properties": {
195
+ "provider": {
196
+ "const": true
197
+ }
198
+ },
199
+ "required": [
200
+ "provider"
201
+ ]
202
+ }
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,211 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openiap.dev/schemas/commerce-protocol/1.0/store-event-mapping.schema.json",
4
+ "title": "OpenIAP Commerce Protocol store event mapping",
5
+ "description": "How each store's own notification vocabulary maps onto the normalized event types. This is the reference an implementer follows to normalize a store notification without reverse-engineering an existing backend, and the reference a consumer reads to understand why a given store produces the events it does. A row states what a notification means, not that an emitter can always act on it: one that lacks the state to interpret a notification — a redelivery it already applied, a purchase whose product it cannot resolve, a token the store has superseded — emits nothing, and that is conformant.",
6
+ "type": "object",
7
+ "required": [
8
+ "specVersion",
9
+ "stores"
10
+ ],
11
+ "additionalProperties": true,
12
+ "properties": {
13
+ "specVersion": {
14
+ "description": "OpenIAP Commerce Protocol version this table was written against, as MAJOR.MINOR.",
15
+ "type": "string",
16
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
17
+ },
18
+ "stores": {
19
+ "description": "Keyed by store, using the same open value space as the event envelope. A table lists only the stores it has mappings for.",
20
+ "type": "object",
21
+ "minProperties": 1,
22
+ "propertyNames": {
23
+ "pattern": "^[a-z][a-z0-9_]*$"
24
+ },
25
+ "additionalProperties": {
26
+ "$ref": "#/$defs/StoreMapping"
27
+ }
28
+ }
29
+ },
30
+ "$defs": {
31
+ "StoreMapping": {
32
+ "description": "One store's notification lane: the channel it publishes, the rows that translate its notifications, and what an emitter could still derive without one.",
33
+ "type": "object",
34
+ "required": [
35
+ "notificationChannel",
36
+ "mappings"
37
+ ],
38
+ "additionalProperties": true,
39
+ "properties": {
40
+ "notificationChannel": {
41
+ "description": "The store's own server-to-server notification product, or null when the store publishes none. This is a fact about the STORE, never about any implementation: a channel a store publishes but this version does not map is still named here, with `mappings` empty and `notes` saying why. Recording an unmapped channel as null would collapse a store limitation and a specification gap into the same value, which is the conflation the capability descriptor's two axes exist to prevent.",
42
+ "type": [
43
+ "string",
44
+ "null"
45
+ ]
46
+ },
47
+ "notes": {
48
+ "description": "Required when there is no notification channel, or when the store's semantics need a caveat an implementer would otherwise get wrong.",
49
+ "type": "string",
50
+ "minLength": 1
51
+ },
52
+ "derivableByPolling": {
53
+ "description": "Events an implementation could produce for this store by re-asking an authoritative endpoint on a schedule rather than by consuming a notification. Present so that a store this version maps no notifications for is not mistaken for a store with no possible signal — which is true whether the store publishes a channel that went unmapped or publishes none at all. This version specifies no cadence, so an implementation that derives these MUST still declare its capabilities honestly rather than claim parity with a store whose notifications it consumes.",
54
+ "type": "array",
55
+ "items": {
56
+ "type": "string",
57
+ "pattern": "^entitlement\\.[a-z_]+$"
58
+ },
59
+ "uniqueItems": true
60
+ },
61
+ "mappings": {
62
+ "description": "Notification-to-event rows. Empty when the store publishes no channel, or when it publishes one this version does not map; `notes` MUST distinguish the two.",
63
+ "type": "array",
64
+ "items": {
65
+ "$ref": "#/$defs/Mapping"
66
+ }
67
+ }
68
+ },
69
+ "anyOf": [
70
+ {
71
+ "properties": {
72
+ "notificationChannel": {
73
+ "type": "string"
74
+ },
75
+ "mappings": {
76
+ "type": "array",
77
+ "minItems": 1
78
+ }
79
+ },
80
+ "required": [
81
+ "notificationChannel",
82
+ "mappings"
83
+ ]
84
+ },
85
+ {
86
+ "properties": {
87
+ "notes": {
88
+ "type": "string",
89
+ "minLength": 1
90
+ }
91
+ },
92
+ "required": [
93
+ "notes"
94
+ ]
95
+ }
96
+ ],
97
+ "allOf": [
98
+ {
99
+ "if": {
100
+ "properties": {
101
+ "notificationChannel": {
102
+ "type": "null"
103
+ }
104
+ },
105
+ "required": [
106
+ "notificationChannel"
107
+ ]
108
+ },
109
+ "then": {
110
+ "properties": {
111
+ "mappings": {
112
+ "type": "array",
113
+ "maxItems": 0
114
+ }
115
+ },
116
+ "required": [
117
+ "mappings"
118
+ ]
119
+ }
120
+ }
121
+ ]
122
+ },
123
+ "Mapping": {
124
+ "description": "One notification-to-event row. Closed: an unrecognised qualifier would leave a reader selecting this row on fewer conditions than its author intended.",
125
+ "type": "object",
126
+ "required": [
127
+ "storeNotification",
128
+ "event"
129
+ ],
130
+ "additionalProperties": false,
131
+ "properties": {
132
+ "storeNotification": {
133
+ "description": "The notification type in the store's own vocabulary, verbatim.",
134
+ "type": "string",
135
+ "minLength": 1
136
+ },
137
+ "storeNotificationCode": {
138
+ "description": "The value the store actually transmits, when it differs from the human-readable name. Google Play sends a numeric notificationType; the names are documentation labels that never appear on the wire.",
139
+ "type": "string",
140
+ "minLength": 1
141
+ },
142
+ "storeSubtype": {
143
+ "description": "A qualifier the store sends alongside the type. Null means this row is the default for that notification: it applies when the store sends no subtype, and also when it sends one no other row claims.",
144
+ "type": [
145
+ "string",
146
+ "null"
147
+ ]
148
+ },
149
+ "whenNoPriorStoreEvent": {
150
+ "description": "Restricts this row to the case where the emitter has seen no earlier store notification for this purchase. That covers both a purchase it has never heard of and one it learned about from a client receipt but never from the store — in each case the notification is the beginning of the story it can tell. A purchase with store history behind it takes the unconditional row instead.",
151
+ "type": "boolean",
152
+ "const": true
153
+ },
154
+ "whenPreviousState": {
155
+ "description": "Restricts this row to the case where the subscription was in one of these states immediately before the notification. Use it only where the store itself provides no qualifier: Google sends SUBSCRIPTION_RECOVERED for both a recovery and a resume from pause and marks nothing, so prior state is the only separator. Where a store does send a qualifier, use `storeSubtype` instead. A row without any condition is the default for its (notification, subtype) pair, and an implementation MUST prefer a conditional row that matches.",
156
+ "type": "array",
157
+ "items": {
158
+ "$ref": "primitives.schema.json#/$defs/SubscriptionState"
159
+ },
160
+ "minItems": 1,
161
+ "uniqueItems": true
162
+ },
163
+ "event": {
164
+ "description": "The normalized event type to emit, or null when the notification is received but produces no event — an audit-only or informational notification. A null event is a deliberate mapping, not a gap. Never an `entitlement.*` type: SPEC.md 9.2 derives those from the gate, not from a notification.",
165
+ "type": [
166
+ "string",
167
+ "null"
168
+ ],
169
+ "pattern": "^subscription\\.[a-z_]+$"
170
+ },
171
+ "notes": {
172
+ "description": "Why this row is what it is, in prose an implementer can act on. Required wherever the row emits nothing, so a reader can tell a deliberate no-op from an oversight.",
173
+ "type": "string",
174
+ "minLength": 1
175
+ }
176
+ },
177
+ "if": {
178
+ "properties": {
179
+ "event": {
180
+ "type": "null"
181
+ }
182
+ }
183
+ },
184
+ "then": {
185
+ "properties": {
186
+ "notes": {
187
+ "type": "string",
188
+ "minLength": 1
189
+ }
190
+ },
191
+ "required": [
192
+ "notes"
193
+ ]
194
+ },
195
+ "not": {
196
+ "properties": {
197
+ "whenNoPriorStoreEvent": {
198
+ "type": "boolean"
199
+ },
200
+ "whenPreviousState": {
201
+ "type": "array"
202
+ }
203
+ },
204
+ "required": [
205
+ "whenNoPriorStoreEvent",
206
+ "whenPreviousState"
207
+ ]
208
+ }
209
+ }
210
+ }
211
+ }