@mpgd/platform 0.18.0 → 0.20.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/dist/capability-conformance.js +103 -1
- package/dist/commerce-capabilities.d.ts +9 -0
- package/dist/commerce-capabilities.js +24 -0
- package/dist/commerce-conformance.d.ts +518 -0
- package/dist/commerce-conformance.js +2162 -0
- package/dist/commerce-contract.d.ts +222 -0
- package/dist/commerce-contract.js +1 -0
- package/dist/commerce-vectors.d.ts +277 -0
- package/dist/commerce-vectors.js +3089 -0
- package/dist/commerce.d.ts +303 -0
- package/dist/commerce.js +5623 -0
- package/dist/index.d.ts +53 -0
- package/package.json +18 -2
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { type ActiveSubscription, type CommerceBillingAuthority, type CommerceCapabilities, type CommerceErrorCode, type CommerceErrorInfo, type CommerceEvent, type CommerceEvidenceAuthority, type CommerceFinishOwner, type CommerceLogicalProductId, type CommerceProvider, type CommercePurchaseState, type CreditBalance, type CreditDebit, type CreditEntitlement, type CreditLot, type CreditLotSpendScope, type CreditMembershipTerms, type CreditOffer, type CreditSpendInput, type CreditSpendResult, type CreditWalletClient, type FetchProductsInput, type PurchaseKind, type RequestPurchaseInput, type RequestPurchaseResult, type StoreId, type StoreProduct, type StorePurchase } from './commerce.js';
|
|
2
|
+
import type { CommerceConformanceCatalogEntry, CommerceConformanceErrorFixture, CommerceConformanceExpectation, CommerceConformanceFinishResultFixture, CommerceConformanceLotFixture, CommerceConformanceProfile, CommerceConformancePurchaseFixture, CommerceConformanceRequestResultFixture, CommerceConformanceSubscriptionEventType, CommerceConformanceVector, CommerceConformanceVerifyDecision } from './commerce-vectors.js';
|
|
3
|
+
import type { PlatformEvidenceEnvelope } from './index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Commerce v2 conformance runner (RFC "Conformance vectors") with the in-memory
|
|
6
|
+
* reference pieces every vector needs: a fake clock, a fake store with its
|
|
7
|
+
* provider, a fake backend and ledger, an in-memory journal, the reference
|
|
8
|
+
* reconciler runtime, and a reference credit wallet with its provider. Pure
|
|
9
|
+
* in-process code: no Phaser, DOM, network, or SDK imports. Nothing here
|
|
10
|
+
* grants outside the fake ledger, and the fake ledger only writes what a
|
|
11
|
+
* vector scripts through `queueVerify`, `push`, or `revoke`.
|
|
12
|
+
*
|
|
13
|
+
* Store (B-phase) providers run the same vectors by supplying
|
|
14
|
+
* `createProvider` and translating the store actor steps into their own fake
|
|
15
|
+
* SDK; the reconciler, journal, backend, and matcher stay shared. A vector
|
|
16
|
+
* flagged `profile.adaptive` then matches expectations derived from the
|
|
17
|
+
* provider's declared capabilities rather than its fixture profile; see
|
|
18
|
+
* `deriveAdaptiveCommerceConformanceExpectation` and
|
|
19
|
+
* `commerceConformanceAdaptiveSkipReason`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const commerceConformanceDefaultPlayerId = "player-1";
|
|
22
|
+
export declare const commerceConformanceDefaultTitleId = "title-a";
|
|
23
|
+
export declare const commerceConformanceDefaultClockStart = "2026-10-01T00:00:00Z";
|
|
24
|
+
/** An operation the vector did not script; it fails the vector with its message. */
|
|
25
|
+
export declare class CommerceConformanceScriptError extends Error {
|
|
26
|
+
readonly name = "CommerceConformanceScriptError";
|
|
27
|
+
}
|
|
28
|
+
export interface CommerceConformanceClock {
|
|
29
|
+
/** ISO-8601 timestamp of the fake "now". */
|
|
30
|
+
now(): string;
|
|
31
|
+
set(at: string): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createCommerceConformanceClock(initial?: string): CommerceConformanceClock;
|
|
34
|
+
/** Canonical `YYYY-MM-DDTHH:mm:ssZ`; milliseconds are kept only when non-zero. */
|
|
35
|
+
export declare function normalizeIsoTimestamp(input: string): string;
|
|
36
|
+
/** Adds an ISO-8601 duration (`P30D`, `P1M`, `PT12H`, ...) to a timestamp, in UTC calendar units. */
|
|
37
|
+
export declare function addIsoDuration(at: string, duration: string): string;
|
|
38
|
+
/** Capabilities of a vector profile: omitted blocks default to all supported and `available`. */
|
|
39
|
+
export declare function createCommerceConformanceCapabilities(profile: CommerceConformanceProfile): CommerceCapabilities;
|
|
40
|
+
export interface CommerceConformanceFixtureContext {
|
|
41
|
+
readonly profile: CommerceConformanceProfile;
|
|
42
|
+
readonly catalog: readonly CommerceConformanceCatalogEntry[];
|
|
43
|
+
readonly playerId: string;
|
|
44
|
+
}
|
|
45
|
+
/** Fake evidence for a fixture purchase: `fixture.<store>.v1` carrying the token. */
|
|
46
|
+
export declare function createCommerceConformanceEvidence(store: StoreId, token: string): PlatformEvidenceEnvelope;
|
|
47
|
+
/**
|
|
48
|
+
* Fills vector purchase shorthand into a typed `StorePurchase`: the profile's
|
|
49
|
+
* store, quantity 1, the catalog logical id of the SKU, fake evidence, and the
|
|
50
|
+
* account binding the profile's authority block implies.
|
|
51
|
+
*/
|
|
52
|
+
export declare function fillCommerceConformancePurchase(fixture: CommerceConformancePurchaseFixture, context: CommerceConformanceFixtureContext): StorePurchase;
|
|
53
|
+
export declare function commerceErrorInfoOfFixture(fixture: CommerceConformanceErrorFixture): CommerceErrorInfo;
|
|
54
|
+
export interface CommerceConformanceFinishCall {
|
|
55
|
+
readonly by: 'client' | 'host';
|
|
56
|
+
readonly transactionId: string;
|
|
57
|
+
readonly ledgerEntryId: string;
|
|
58
|
+
readonly isConsumable: boolean;
|
|
59
|
+
readonly result: 'finished' | 'unfinished' | CommerceErrorCode;
|
|
60
|
+
}
|
|
61
|
+
/** The store actor of a vector: scripted answers, store-side events, and store observations. */
|
|
62
|
+
export interface CommerceConformanceStoreActor {
|
|
63
|
+
/** Calls that reached the store's purchase UI. */
|
|
64
|
+
readonly purchaseUiOpened: number;
|
|
65
|
+
readonly finishCalls: readonly CommerceConformanceFinishCall[];
|
|
66
|
+
complete(purchase: CommerceConformancePurchaseFixture): void;
|
|
67
|
+
update(purchase: CommerceConformancePurchaseFixture): void;
|
|
68
|
+
error(input: {
|
|
69
|
+
readonly logicalId?: CommerceLogicalProductId;
|
|
70
|
+
readonly error: CommerceConformanceErrorFixture;
|
|
71
|
+
}): void;
|
|
72
|
+
queueRequest(result: CommerceConformanceRequestResultFixture): void;
|
|
73
|
+
queueFinish(result: CommerceConformanceFinishResultFixture): void;
|
|
74
|
+
/** The backend's finalizer consumed the purchase (`lifecycle.finish: 'server'`). */
|
|
75
|
+
finalize?(input: {
|
|
76
|
+
readonly transactionId: string;
|
|
77
|
+
readonly ledgerEntryId: string;
|
|
78
|
+
}): void | Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* The `token` this store reports for a fixture purchase when it is not the
|
|
81
|
+
* fixture's own: a host-order store reports the transaction id (RFC "Client
|
|
82
|
+
* contract"). An adaptive run replays the fixture purchases with this token
|
|
83
|
+
* into the reference store, so the derived verify requests carry the token
|
|
84
|
+
* the provider under test reports. Absent, the fixture token is used.
|
|
85
|
+
*/
|
|
86
|
+
tokenOf?(purchase: CommerceConformancePurchaseFixture): string;
|
|
87
|
+
}
|
|
88
|
+
export interface CommerceConformanceStore extends CommerceConformanceStoreActor {
|
|
89
|
+
/** The `CommerceProvider` over this fake store. */
|
|
90
|
+
readonly provider: CommerceProvider;
|
|
91
|
+
/** Purchases the store still reports as unfinished. */
|
|
92
|
+
readonly purchases: readonly StorePurchase[];
|
|
93
|
+
readonly restoreCalls: number;
|
|
94
|
+
/** Removes a purchase from the unfinished list. */
|
|
95
|
+
markFinished(transactionId: string): void;
|
|
96
|
+
}
|
|
97
|
+
export type CreateCommerceConformanceStoreInput = CommerceConformanceFixtureContext;
|
|
98
|
+
/**
|
|
99
|
+
* Fake store: answers `launched` unless a `queueRequest` entry is queued,
|
|
100
|
+
* `{ finished: true }` unless a `queueFinish` entry is queued, lists reported
|
|
101
|
+
* purchases until they are finished, and buffers events emitted before the
|
|
102
|
+
* first listener with `createCommerceEventBuffer`.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createCommerceConformanceStore(input: CreateCommerceConformanceStoreInput): CommerceConformanceStore;
|
|
105
|
+
export interface CommerceVerifyRequest {
|
|
106
|
+
readonly idempotencyKey: string;
|
|
107
|
+
readonly playerId: string;
|
|
108
|
+
readonly store: StoreId;
|
|
109
|
+
readonly transactionId: string;
|
|
110
|
+
readonly token: string;
|
|
111
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
112
|
+
readonly quantity: number;
|
|
113
|
+
readonly evidence: PlatformEvidenceEnvelope;
|
|
114
|
+
}
|
|
115
|
+
export interface CommerceVerifyResponse {
|
|
116
|
+
readonly status: 'granted' | 'pending' | 'rejected';
|
|
117
|
+
readonly ledgerEntryId?: string;
|
|
118
|
+
/** Reported for `lifecycle.finish: 'server'` stores. */
|
|
119
|
+
readonly finalization?: 'pending' | 'completed';
|
|
120
|
+
readonly alreadyProcessed?: boolean;
|
|
121
|
+
}
|
|
122
|
+
export type CommerceLedgerEntry = {
|
|
123
|
+
readonly entryId: string;
|
|
124
|
+
readonly type: 'grant';
|
|
125
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
126
|
+
readonly store: StoreId;
|
|
127
|
+
readonly transactionId: string;
|
|
128
|
+
readonly idempotencyKey?: string;
|
|
129
|
+
/** Written by the host's fulfillment path rather than a verify call. */
|
|
130
|
+
readonly pushed?: boolean;
|
|
131
|
+
} | {
|
|
132
|
+
readonly entryId: string;
|
|
133
|
+
readonly type: 'revoke';
|
|
134
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
135
|
+
readonly reverses: string;
|
|
136
|
+
readonly reason: string;
|
|
137
|
+
};
|
|
138
|
+
export interface CommerceLedgerStatus {
|
|
139
|
+
readonly ledgerEntryId: string;
|
|
140
|
+
readonly status: 'active' | 'reversed' | 'unknown';
|
|
141
|
+
readonly reversedBy?: string;
|
|
142
|
+
}
|
|
143
|
+
/** What the reference runtime needs from a backend. */
|
|
144
|
+
export interface CommerceConformanceBackendClient {
|
|
145
|
+
verifyPurchase(request: CommerceVerifyRequest): Promise<CommerceVerifyResponse>;
|
|
146
|
+
getLedgerStatus(ledgerEntryIds: readonly string[]): Promise<readonly CommerceLedgerStatus[]>;
|
|
147
|
+
}
|
|
148
|
+
export interface CommerceConformanceBackend extends CommerceConformanceBackendClient {
|
|
149
|
+
readonly verifyRequests: readonly CommerceVerifyRequest[];
|
|
150
|
+
readonly ledger: readonly CommerceLedgerEntry[];
|
|
151
|
+
queueVerify(decision: CommerceConformanceVerifyDecision): void;
|
|
152
|
+
/** Writes a server-push grant keyed by transaction id; a retry for the same transaction replays the original entry. */
|
|
153
|
+
push(input: {
|
|
154
|
+
readonly transactionId: string;
|
|
155
|
+
readonly entryId: string;
|
|
156
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
157
|
+
readonly store?: StoreId;
|
|
158
|
+
}): void;
|
|
159
|
+
revoke(input: {
|
|
160
|
+
readonly ledgerEntryId: string;
|
|
161
|
+
readonly entryId: string;
|
|
162
|
+
readonly reason: string;
|
|
163
|
+
}): void;
|
|
164
|
+
/** Grants the ledger still holds active. */
|
|
165
|
+
entitlements(): readonly {
|
|
166
|
+
readonly id: string;
|
|
167
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
168
|
+
}[];
|
|
169
|
+
}
|
|
170
|
+
export interface CreateCommerceConformanceBackendInput {
|
|
171
|
+
/** The server-side finalizer, invoked when a decision reports `finalization: 'completed'`. */
|
|
172
|
+
readonly finalize?: (input: {
|
|
173
|
+
readonly store: StoreId;
|
|
174
|
+
readonly transactionId: string;
|
|
175
|
+
readonly ledgerEntryId: string;
|
|
176
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
177
|
+
}) => void | Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* When it returns true, a verify request that replays a key this backend
|
|
180
|
+
* already granted is a finalization re-read (the reference reconciler under a
|
|
181
|
+
* server-owned finish): it is answered `{ granted, ledgerEntryId,
|
|
182
|
+
* finalization: 'completed', alreadyProcessed: true }` from the backend's own
|
|
183
|
+
* grant state and runs the finalizer, consuming no queued decision. Read on
|
|
184
|
+
* every call, so the runner can decide it once the provider's capabilities
|
|
185
|
+
* are known.
|
|
186
|
+
*/
|
|
187
|
+
readonly finalizeOnReread?: () => boolean;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Fake backend: every verify call consumes the next `queueVerify` decision in
|
|
191
|
+
* order and is logged; an unqueued verify is an unscripted operation. A
|
|
192
|
+
* `granted` decision makes sure the ledger holds the entry, so a replayed
|
|
193
|
+
* grant and a host-pushed grant never create a second entry. With
|
|
194
|
+
* `finalizeOnReread`, a replayed granted key is answered as finalized from
|
|
195
|
+
* the backend's own state.
|
|
196
|
+
*/
|
|
197
|
+
export declare function createCommerceConformanceBackend(input?: CreateCommerceConformanceBackendInput): CommerceConformanceBackend;
|
|
198
|
+
export interface CommerceJournalRecord {
|
|
199
|
+
/** The purchase idempotency key: the game's key or a derived key. */
|
|
200
|
+
readonly key: string;
|
|
201
|
+
readonly revision: number;
|
|
202
|
+
readonly playerId: string;
|
|
203
|
+
readonly store: StoreId;
|
|
204
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
205
|
+
readonly kind: PurchaseKind;
|
|
206
|
+
readonly origin: 'game' | 'discovered';
|
|
207
|
+
readonly state: CommercePurchaseState;
|
|
208
|
+
readonly updatedAt: string;
|
|
209
|
+
readonly purchase?: StorePurchase;
|
|
210
|
+
/** Saved before the first send and resent unchanged on later passes. */
|
|
211
|
+
readonly request?: CommerceVerifyRequest;
|
|
212
|
+
readonly ledgerEntryId?: string;
|
|
213
|
+
readonly error?: CommerceErrorInfo;
|
|
214
|
+
readonly revokedBy?: string;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Purchase journal with the compare-and-replace semantics of
|
|
218
|
+
* `MonetizationOperationStore`. A game supplies a durable, atomic
|
|
219
|
+
* implementation; the in-memory one below exists for conformance runs.
|
|
220
|
+
*/
|
|
221
|
+
export interface CommerceJournalStore {
|
|
222
|
+
/** Atomically insert or return the existing record for this key. */
|
|
223
|
+
reserve(record: CommerceJournalRecord): Promise<{
|
|
224
|
+
readonly created: boolean;
|
|
225
|
+
readonly record: CommerceJournalRecord;
|
|
226
|
+
}>;
|
|
227
|
+
read(key: string): Promise<CommerceJournalRecord | undefined>;
|
|
228
|
+
/** Compare-and-swap on `expectedRevision`; the stored record carries the next revision. */
|
|
229
|
+
replace(expectedRevision: number, record: CommerceJournalRecord): Promise<CommerceJournalRecord>;
|
|
230
|
+
/** Non-terminal records; terminal records stay addressable by `read`. */
|
|
231
|
+
listRecoverable(playerId: string): Promise<readonly CommerceJournalRecord[]>;
|
|
232
|
+
/** Records in `granted`, `finishing`, or `finished` kept for revocation reporting. */
|
|
233
|
+
listGranted(playerId: string): Promise<readonly CommerceJournalRecord[]>;
|
|
234
|
+
}
|
|
235
|
+
export interface CommerceConformanceJournal extends CommerceJournalStore {
|
|
236
|
+
readonly keys: readonly string[];
|
|
237
|
+
/** Every state revision of a key, in write order. */
|
|
238
|
+
history(key: string): readonly CommercePurchaseState[];
|
|
239
|
+
/** `history` for every key, in creation order. */
|
|
240
|
+
snapshot(): Readonly<Record<string, readonly CommercePurchaseState[]>>;
|
|
241
|
+
}
|
|
242
|
+
/** In-memory journal that keeps every revision so `expect.journal` can read the full sequence. */
|
|
243
|
+
export declare function createCommerceConformanceJournal(): CommerceConformanceJournal;
|
|
244
|
+
export interface CommerceConformanceRuntime {
|
|
245
|
+
readonly started: boolean;
|
|
246
|
+
/** Subscribe, read available purchases when declared, then reconcile until idle. */
|
|
247
|
+
start(): Promise<void>;
|
|
248
|
+
/** Drop in-memory state; the journal, store, ledger, and wallet are kept. */
|
|
249
|
+
stop(): Promise<void>;
|
|
250
|
+
/** One reconcile pass, then any passes that events arriving meanwhile trigger. */
|
|
251
|
+
reconcile(): Promise<void>;
|
|
252
|
+
/** Reconcile while a trigger is pending; a no-op when stopped. */
|
|
253
|
+
runUntilIdle(): Promise<void>;
|
|
254
|
+
requestPurchase(input: RequestPurchaseInput): Promise<RequestPurchaseResult>;
|
|
255
|
+
fetchProducts(input?: FetchProductsInput): Promise<readonly StoreProduct[]>;
|
|
256
|
+
}
|
|
257
|
+
export interface CreateCommerceConformanceRuntimeInput {
|
|
258
|
+
readonly provider: CommerceProvider;
|
|
259
|
+
readonly backend: CommerceConformanceBackendClient;
|
|
260
|
+
readonly journal: CommerceJournalStore;
|
|
261
|
+
readonly clock: CommerceConformanceClock;
|
|
262
|
+
readonly catalog: readonly CommerceConformanceCatalogEntry[];
|
|
263
|
+
readonly playerId: string;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Reference reconciler. Every transition is its own journal revision written
|
|
267
|
+
* through compare-and-replace; the backend is in-process, so there is no
|
|
268
|
+
* transport, but a thrown finish or verify keeps the state for the next pass
|
|
269
|
+
* as a transport failure would. Only a ledger entry moves a record to
|
|
270
|
+
* `granted`, and only a ledger revocation moves it to `revoked`.
|
|
271
|
+
*/
|
|
272
|
+
export declare function createCommerceConformanceRuntime(input: CreateCommerceConformanceRuntimeInput): CommerceConformanceRuntime;
|
|
273
|
+
export interface CommerceScheduledDebit extends CreditDebit {
|
|
274
|
+
readonly idempotencyKey: string;
|
|
275
|
+
}
|
|
276
|
+
export interface CommerceConformanceSubscriptionEvent {
|
|
277
|
+
/** Stable id `<subscriptionId>:<periodIndex>:<type>`, so redelivery is idempotent. */
|
|
278
|
+
readonly eventId: string;
|
|
279
|
+
readonly type: CommerceConformanceSubscriptionEventType;
|
|
280
|
+
readonly subscriptionId: string;
|
|
281
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
282
|
+
readonly periodIndex: number;
|
|
283
|
+
readonly expiresAt: string;
|
|
284
|
+
readonly reason?: CommerceErrorCode;
|
|
285
|
+
}
|
|
286
|
+
export interface CommerceConformanceMembershipStart {
|
|
287
|
+
readonly subscriptionId: string;
|
|
288
|
+
readonly titleId: string;
|
|
289
|
+
readonly logicalId: CommerceLogicalProductId;
|
|
290
|
+
readonly terms: CreditMembershipTerms;
|
|
291
|
+
}
|
|
292
|
+
export type CommerceConformanceMembershipResult = {
|
|
293
|
+
readonly status: 'started';
|
|
294
|
+
readonly subscription: ActiveSubscription;
|
|
295
|
+
readonly entitlementId: string;
|
|
296
|
+
} | {
|
|
297
|
+
readonly status: 'rejected';
|
|
298
|
+
readonly error: CommerceErrorInfo;
|
|
299
|
+
};
|
|
300
|
+
/** The wallet actor of a vector: wallet steps plus wallet observations. */
|
|
301
|
+
export interface CommerceConformanceWalletActor {
|
|
302
|
+
readonly lots: readonly CreditLot[];
|
|
303
|
+
/** Every debit of every processed spend, in order; replays add nothing. */
|
|
304
|
+
readonly debits: readonly CommerceScheduledDebit[];
|
|
305
|
+
readonly subscriptionEvents: readonly CommerceConformanceSubscriptionEvent[];
|
|
306
|
+
creditFromGrant(lot: CommerceConformanceLotFixture, options?: {
|
|
307
|
+
readonly spendScope?: CreditLotSpendScope;
|
|
308
|
+
}): CreditLot;
|
|
309
|
+
spend(input: CreditSpendInput): Promise<CreditSpendResult>;
|
|
310
|
+
/** Runs the renewal scheduler for memberships whose paid period has ended. */
|
|
311
|
+
renewDue(): Promise<void>;
|
|
312
|
+
getBalance(input: {
|
|
313
|
+
readonly titleId: string;
|
|
314
|
+
}): Promise<CreditBalance>;
|
|
315
|
+
getActiveSubscriptions(): Promise<readonly ActiveSubscription[]>;
|
|
316
|
+
/** The backend's finalizer redeemed the entitlement (`lifecycle.finish: 'server'`). */
|
|
317
|
+
finalize?(input: {
|
|
318
|
+
readonly transactionId: string;
|
|
319
|
+
readonly ledgerEntryId: string;
|
|
320
|
+
}): void | Promise<void>;
|
|
321
|
+
}
|
|
322
|
+
export interface CommerceConformanceWallet extends CreditWalletClient, CommerceConformanceWalletActor {
|
|
323
|
+
readonly entitlements: readonly CreditEntitlement[];
|
|
324
|
+
startMembership(input: CommerceConformanceMembershipStart): Promise<CommerceConformanceMembershipResult>;
|
|
325
|
+
subscribe(listener: (event: CommerceEvent) => void): () => void;
|
|
326
|
+
}
|
|
327
|
+
export interface CreateCommerceConformanceWalletInput {
|
|
328
|
+
readonly store: StoreId;
|
|
329
|
+
readonly clock: CommerceConformanceClock;
|
|
330
|
+
readonly currencies: readonly string[];
|
|
331
|
+
/** Offers served by `listOffers`; defaults to none. */
|
|
332
|
+
readonly offers?: readonly CreditOffer[];
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Reference in-memory wallet implementing the RFC "Credit lots" and "Billing
|
|
336
|
+
* authority and memberships" rules: lot ids equal ledger entry ids, spend
|
|
337
|
+
* scope derives from the lot origin, spends consume eligible lots in the
|
|
338
|
+
* fixed order, are idempotent by key, and debit nothing on
|
|
339
|
+
* `INSUFFICIENT_BALANCE`; memberships debit `membership:<id>:<period>` per
|
|
340
|
+
* paid period and lapse at period end when the eligible balance runs out.
|
|
341
|
+
*/
|
|
342
|
+
export declare function createCommerceConformanceWallet(input: CreateCommerceConformanceWalletInput): CommerceConformanceWallet;
|
|
343
|
+
/** Store lots are title-scoped, off-store lots cross-title, promotion lots as issued (title by default). */
|
|
344
|
+
export declare function deriveCreditLotSpendScope(origin: CreditLot['origin'], explicit?: CreditLotSpendScope): CreditLotSpendScope;
|
|
345
|
+
/** RFC spend order: earliest `expiresAt` (none last), `title` before `cross-title`, oldest `createdAt`, then `lotId`. */
|
|
346
|
+
export declare function compareCreditLotSpendOrder(left: CreditLot, right: CreditLot): number;
|
|
347
|
+
export interface CreateCommerceConformanceWalletProviderInput {
|
|
348
|
+
readonly wallet: CommerceConformanceWallet;
|
|
349
|
+
readonly profile: CommerceConformanceProfile;
|
|
350
|
+
readonly catalog: readonly CommerceConformanceCatalogEntry[];
|
|
351
|
+
readonly currencies: readonly string[];
|
|
352
|
+
readonly billingAuthority: CommerceBillingAuthority;
|
|
353
|
+
readonly titleId: string;
|
|
354
|
+
readonly clock: CommerceConformanceClock;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* `CommerceProvider` over the reference wallet: `fetchProducts` lists the
|
|
358
|
+
* wallet's offers (catalog-derived offers only when the wallet lists none),
|
|
359
|
+
* `requestPurchase` spends the offer price times the requested quantity
|
|
360
|
+
* (outcome `purchased`, `settlement` set, evidence adapter-settled),
|
|
361
|
+
* `getAvailablePurchases` lists unredeemed entitlements, and finishing is the
|
|
362
|
+
* backend's `redeem`. Memberships exist only where the billing authority is
|
|
363
|
+
* `host`.
|
|
364
|
+
*/
|
|
365
|
+
export declare function createCommerceConformanceWalletProvider(input: CreateCommerceConformanceWalletProviderInput): CommerceProvider;
|
|
366
|
+
export interface CommerceConformanceHarness {
|
|
367
|
+
readonly provider: CommerceProvider;
|
|
368
|
+
/** Handles `store` steps; required by store vectors. */
|
|
369
|
+
readonly store?: CommerceConformanceStoreActor;
|
|
370
|
+
/** Handles `wallet` steps; required by wallet vectors. */
|
|
371
|
+
readonly wallet?: CommerceConformanceWalletActor;
|
|
372
|
+
}
|
|
373
|
+
export interface CommerceConformanceContext extends CommerceConformanceFixtureContext {
|
|
374
|
+
readonly vector: CommerceConformanceVector;
|
|
375
|
+
readonly titleId: string;
|
|
376
|
+
readonly clock: CommerceConformanceClock;
|
|
377
|
+
readonly backend: CommerceConformanceBackend;
|
|
378
|
+
readonly journal: CommerceConformanceJournal;
|
|
379
|
+
}
|
|
380
|
+
export interface CommerceConformanceRunOptions {
|
|
381
|
+
/** Supplies a provider over the caller's fake SDK; defaults to the fake store or the reference wallet. */
|
|
382
|
+
readonly createProvider?: (context: CommerceConformanceContext) => CommerceConformanceHarness | Promise<CommerceConformanceHarness>;
|
|
383
|
+
readonly playerId?: string;
|
|
384
|
+
readonly titleId?: string;
|
|
385
|
+
}
|
|
386
|
+
export interface CommerceConformanceFailure {
|
|
387
|
+
readonly path: string;
|
|
388
|
+
readonly expected: unknown;
|
|
389
|
+
readonly actual: unknown;
|
|
390
|
+
}
|
|
391
|
+
export type CommerceConformanceVectorStatus = 'passed' | 'failed' | 'skipped';
|
|
392
|
+
/**
|
|
393
|
+
* `CommerceConformanceExpectation` as the matcher consumes it: a derived
|
|
394
|
+
* `finishCalls` list carries the store actor's own records, whose `result` may
|
|
395
|
+
* also be `unfinished`.
|
|
396
|
+
*/
|
|
397
|
+
export interface CommerceConformanceResolvedExpectation extends Omit<CommerceConformanceExpectation, 'finishCalls'> {
|
|
398
|
+
readonly finishCalls?: readonly CommerceConformanceFinishCall[];
|
|
399
|
+
}
|
|
400
|
+
/** How an adaptive vector's expectations were derived for the provider under test. */
|
|
401
|
+
export interface CommerceConformanceAdaptation {
|
|
402
|
+
/** The provider's store id; it replaces the fixture store id in derived keys and verify requests. */
|
|
403
|
+
readonly store: StoreId;
|
|
404
|
+
readonly finish: CommerceFinishOwner;
|
|
405
|
+
readonly evidence: CommerceEvidenceAuthority;
|
|
406
|
+
/** The expectation the vector was matched against. */
|
|
407
|
+
readonly expect: CommerceConformanceResolvedExpectation;
|
|
408
|
+
}
|
|
409
|
+
export interface CommerceConformanceVectorResult {
|
|
410
|
+
readonly vector: string;
|
|
411
|
+
/** `skipped` vectors did not run; `passed` is false and `reason` says why. */
|
|
412
|
+
readonly status: CommerceConformanceVectorStatus;
|
|
413
|
+
readonly passed: boolean;
|
|
414
|
+
readonly failures: readonly CommerceConformanceFailure[];
|
|
415
|
+
readonly reason?: string;
|
|
416
|
+
/** Present when the vector ran with adaptive expectations. */
|
|
417
|
+
readonly adaptive?: CommerceConformanceAdaptation;
|
|
418
|
+
}
|
|
419
|
+
export declare function createCommerceConformanceHarness(context: CommerceConformanceContext): CommerceConformanceHarness;
|
|
420
|
+
/**
|
|
421
|
+
* Runs one vector: executes every step, lets the runtime run until idle after
|
|
422
|
+
* each, then matches `expect`. Matching compares only the listed fields; arrays
|
|
423
|
+
* compare in order and by length; a listed `journal` is the whole journal.
|
|
424
|
+
* Any unscripted operation fails the vector at the step that needed it.
|
|
425
|
+
*
|
|
426
|
+
* With `options.createProvider`, a vector flagged `profile.adaptive` is first
|
|
427
|
+
* checked against the provider's declared capabilities
|
|
428
|
+
* (`commerceConformanceAdaptiveSkipReason`) and then matched against
|
|
429
|
+
* expectations derived from a reference run under those capabilities
|
|
430
|
+
* (`deriveAdaptiveCommerceConformanceExpectation`). Duplicate detection needs
|
|
431
|
+
* the whole run set, so it applies through `runCommerceConformanceVectors`.
|
|
432
|
+
*/
|
|
433
|
+
export declare function runCommerceConformanceVector(vector: CommerceConformanceVector, options?: CommerceConformanceRunOptions): Promise<CommerceConformanceVectorResult>;
|
|
434
|
+
export declare function runCommerceConformanceVectors(vectors: readonly CommerceConformanceVector[], options?: CommerceConformanceRunOptions): Promise<readonly CommerceConformanceVectorResult[]>;
|
|
435
|
+
export declare function isAdaptiveCommerceConformanceVector(vector: CommerceConformanceVector): boolean;
|
|
436
|
+
/**
|
|
437
|
+
* Why an adaptive vector does not apply to a provider with `capabilities`, or
|
|
438
|
+
* `undefined` when it runs. `set` is the whole run (for duplicate detection).
|
|
439
|
+
* The rules:
|
|
440
|
+
*
|
|
441
|
+
* - a `game.requestPurchase` of a product kind the provider does not sell
|
|
442
|
+
* (`purchase.<kind>` false, by the vector catalog), or with a quantity above
|
|
443
|
+
* one without `purchase.quantity`, cannot open the store UI;
|
|
444
|
+
* - a store-side `pending` purchase needs `lifecycle.pending`;
|
|
445
|
+
* - a store `error` event needs `lifecycle.events`;
|
|
446
|
+
* - without `lifecycle.events`, a store `complete` or `update` reaches the
|
|
447
|
+
* reconciler only by polling, so the next `runtime` step after it must be
|
|
448
|
+
* `reconcile` or `start`;
|
|
449
|
+
* - a `queueFinish` script needs a `client` or `host` finish owner, since the
|
|
450
|
+
* provider never calls `finishTransaction` under `server` or `none`;
|
|
451
|
+
* - within the `purchase-<owner>-finish` family only one member runs: the
|
|
452
|
+
* one named after the provider's owner, else the first by name.
|
|
453
|
+
*/
|
|
454
|
+
export declare function commerceConformanceAdaptiveSkipReason(vector: CommerceConformanceVector, capabilities: CommerceCapabilities, set?: readonly CommerceConformanceVector[]): string | undefined;
|
|
455
|
+
/**
|
|
456
|
+
* The vector with every scripted store purchase (`complete`, `update`, and a
|
|
457
|
+
* `queueRequest` answering `purchased`) carrying the token the store actor
|
|
458
|
+
* reports for it (`CommerceConformanceStoreActor.tokenOf`).
|
|
459
|
+
*/
|
|
460
|
+
export declare function withStoreTokens(vector: CommerceConformanceVector, tokenOf: (purchase: CommerceConformancePurchaseFixture) => string): CommerceConformanceVector;
|
|
461
|
+
/**
|
|
462
|
+
* Derives the adaptive expectation for `vector` from the observable state of a
|
|
463
|
+
* finished reference run under the provider's capabilities, applying the
|
|
464
|
+
* rules the RFC table states:
|
|
465
|
+
*
|
|
466
|
+
* - `journal`: the reference journal, so the tail after `granted` is
|
|
467
|
+
* `finishing, finished` for `client`, `host`, and `server` owners and
|
|
468
|
+
* `finished` for `none`; derived keys carry the provider's store id;
|
|
469
|
+
* - `finishCalls`: the reference store's calls, `by` the owner for `client`
|
|
470
|
+
* and `host`, none for `server` and `none`;
|
|
471
|
+
* - `verifyRequests`: the reference backend's log, with the finalization
|
|
472
|
+
* re-read a `server` owner adds unless the grant already reported
|
|
473
|
+
* `finalization: 'completed'`;
|
|
474
|
+
* - `available`: what the reference store still lists once idle, so empty
|
|
475
|
+
* after a completed finish for `client`, `host`, and `server` owners.
|
|
476
|
+
*
|
|
477
|
+
* Fields the fixture does not assert stay unasserted. The derivation is
|
|
478
|
+
* refused (a skip reason) when the reference run does not end every journal
|
|
479
|
+
* key in the fixture's terminal state, since the scenario then depends on a
|
|
480
|
+
* capability the provider lacks.
|
|
481
|
+
*/
|
|
482
|
+
export declare function deriveAdaptiveCommerceConformanceExpectation(vector: CommerceConformanceVector, reference: {
|
|
483
|
+
readonly store: StoreId;
|
|
484
|
+
readonly journal: Readonly<Record<string, readonly CommercePurchaseState[]>>;
|
|
485
|
+
readonly verifyRequests: readonly CommerceVerifyRequest[];
|
|
486
|
+
readonly finishCalls: readonly CommerceConformanceFinishCall[];
|
|
487
|
+
/** What the reference store still lists (pending included) once the run is idle. */
|
|
488
|
+
readonly available: readonly StorePurchase[];
|
|
489
|
+
}): Promise<{
|
|
490
|
+
readonly expect: CommerceConformanceResolvedExpectation;
|
|
491
|
+
readonly reason?: undefined;
|
|
492
|
+
} | {
|
|
493
|
+
readonly expect?: undefined;
|
|
494
|
+
readonly reason: string;
|
|
495
|
+
}>;
|
|
496
|
+
/**
|
|
497
|
+
* A fixture journal key as the provider's store id derives it: only the
|
|
498
|
+
* encoded store segment of a `store:<store>:<transactionId>` key
|
|
499
|
+
* (`createDerivedPurchaseIdempotencyKey`) is replaced, so a transaction id
|
|
500
|
+
* that happens to contain the fixture store id is left alone. Game-chosen
|
|
501
|
+
* keys and store-specific key forms carry no such prefix and pass through.
|
|
502
|
+
*/
|
|
503
|
+
export declare function adaptDerivedPurchaseKeyStore(key: string, fixtureStore: string, providerStore: string): string;
|
|
504
|
+
/**
|
|
505
|
+
* A fixture journal key as the provider's store id derives it. When the key is
|
|
506
|
+
* the derived key of a purchase the vector scripts under the fixture store, it
|
|
507
|
+
* is re-derived with `createDerivedPurchaseIdempotencyKey` for the provider
|
|
508
|
+
* store, which also covers the store-specific key forms (`google-play` token
|
|
509
|
+
* keys, `apps-in-toss:purchase:<id>`); otherwise only the generic
|
|
510
|
+
* `store:<store>:` prefix is swapped (`adaptDerivedPurchaseKeyStore`).
|
|
511
|
+
*/
|
|
512
|
+
export declare function adaptDerivedPurchaseKey(vector: CommerceConformanceVector, key: string, providerStore: string): Promise<string>;
|
|
513
|
+
/**
|
|
514
|
+
* Partial match: an expected object asserts only its listed keys, an expected
|
|
515
|
+
* array asserts its length and each element in order, and primitives compare
|
|
516
|
+
* strictly. Failures name the deepest differing path.
|
|
517
|
+
*/
|
|
518
|
+
export declare function matchPartial(expected: unknown, actual: unknown, path: string, failures: CommerceConformanceFailure[]): void;
|