@mpgd/game-services 0.23.0 → 0.24.1

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.
@@ -1,14 +1,23 @@
1
- import type { MonetizationOperationRecord, MonetizationOperationStore } from './monetization-recovery.js';
1
+ import type { MonetizationOperationRecord, MonetizationOperationStore, RetiredPurchaseOperationRecord } from './monetization-recovery.js';
2
2
  /**
3
3
  * Whether a journal record still needs a reconcile pass. A game's durable
4
4
  * `MonetizationOperationStore.listRecoverable` applies this rule (or an
5
- * equivalent index): a v1 purchase or rewarded-ad record is open until it
6
- * holds a non-pending result with no pending finalization; a commerce v2
7
- * purchase record is open until its state is terminal (`finished`,
8
- * `cancelled`, `failed`, `rejected`, `revoked`). Terminal records stay
9
- * addressable by `read` for duplicate protection.
5
+ * equivalent index): a rewarded-ad record is open until it holds a
6
+ * non-pending result; a commerce purchase record is open until its state is
7
+ * terminal (`finished`, `cancelled`, `failed`, `rejected`, `revoked`). A
8
+ * retired v1 `purchase` record (written before commerce v2 C4) is open only
9
+ * while it holds a completed platform result and either no terminal result or
10
+ * a grant whose finalization is still pending: the recoverable client sends
11
+ * its verification to the backend once more and closes it. Terminal records stay addressable by `read` for duplicate
12
+ * protection.
10
13
  */
11
14
  export declare function isMonetizationOperationRecoverable(record: MonetizationOperationRecord): boolean;
15
+ /**
16
+ * A retired v1 purchase record whose backend verification was never settled,
17
+ * or whose grant still waits for the backend to consume or acknowledge the
18
+ * store purchase (`response.finalization.status === 'pending'`).
19
+ */
20
+ export declare function isRetiredPurchaseOpen(record: RetiredPurchaseOperationRecord): boolean;
12
21
  /** Whether a commerce v2 record holds a ledger grant the revocation pass should keep checking. */
13
22
  export declare function isMonetizationOperationGranted(record: MonetizationOperationRecord): boolean;
14
23
  /**
@@ -2,20 +2,35 @@ import { isTerminalCommercePurchaseState } from '@mpgd/platform/commerce';
2
2
  /**
3
3
  * Whether a journal record still needs a reconcile pass. A game's durable
4
4
  * `MonetizationOperationStore.listRecoverable` applies this rule (or an
5
- * equivalent index): a v1 purchase or rewarded-ad record is open until it
6
- * holds a non-pending result with no pending finalization; a commerce v2
7
- * purchase record is open until its state is terminal (`finished`,
8
- * `cancelled`, `failed`, `rejected`, `revoked`). Terminal records stay
9
- * addressable by `read` for duplicate protection.
5
+ * equivalent index): a rewarded-ad record is open until it holds a
6
+ * non-pending result; a commerce purchase record is open until its state is
7
+ * terminal (`finished`, `cancelled`, `failed`, `rejected`, `revoked`). A
8
+ * retired v1 `purchase` record (written before commerce v2 C4) is open only
9
+ * while it holds a completed platform result and either no terminal result or
10
+ * a grant whose finalization is still pending: the recoverable client sends
11
+ * its verification to the backend once more and closes it. Terminal records stay addressable by `read` for duplicate
12
+ * protection.
10
13
  */
11
14
  export function isMonetizationOperationRecoverable(record) {
12
15
  if (record.kind === 'commerce-purchase') {
13
16
  return !isTerminalCommercePurchaseState(record.state);
14
17
  }
15
- if (record.result === undefined || record.result.status === 'pending') {
16
- return true;
18
+ if (record.kind === 'purchase') {
19
+ return isRetiredPurchaseOpen(record);
17
20
  }
18
- return record.kind === 'purchase' && record.response?.finalization?.status === 'pending';
21
+ return record.result === undefined || record.result.status === 'pending';
22
+ }
23
+ /**
24
+ * A retired v1 purchase record whose backend verification was never settled,
25
+ * or whose grant still waits for the backend to consume or acknowledge the
26
+ * store purchase (`response.finalization.status === 'pending'`).
27
+ */
28
+ export function isRetiredPurchaseOpen(record) {
29
+ return record.platform?.status === 'completed'
30
+ && record.platform.transactionId !== undefined
31
+ && (record.result === undefined
32
+ || record.result.status === 'pending'
33
+ || record.response?.finalization?.status === 'pending');
19
34
  }
20
35
  /** Whether a commerce v2 record holds a ledger grant the revocation pass should keep checking. */
21
36
  export function isMonetizationOperationGranted(record) {
@@ -13,27 +13,42 @@ interface OperationIdentity {
13
13
  /** Fixed when a platform result is first journaled, before backend dispatch. */
14
14
  readonly platformCompletedAt?: string;
15
15
  }
16
- /** The v1 purchase and rewarded-ad journal records. */
17
- export type LegacyMonetizationOperationRecord = (OperationIdentity & {
16
+ /**
17
+ * The v1 purchase journal record. Releases before commerce v2 C4 wrote them;
18
+ * the reconciler is now the only purchase path, so nothing starts, reopens, or
19
+ * grants from one. A durable journal still parses it, and the one thing
20
+ * `reconcile()` still does with it is finish an in-flight verification: a
21
+ * record that holds a completed platform result and no terminal result sends
22
+ * its saved verify request (or the one its own fields describe) to the
23
+ * backend once more, through the ledger and under the same idempotency key,
24
+ * then closes the record with what the backend answered. A record without a
25
+ * completed platform result is closed without a request: whatever the store
26
+ * still holds is found again from the store's own unfinished list.
27
+ */
28
+ export type RetiredPurchaseOperationRecord = OperationIdentity & {
18
29
  readonly kind: 'purchase';
19
30
  readonly input: GameServicesPurchaseInput;
20
31
  readonly platform?: PurchaseResult;
21
32
  readonly request?: VerifyPurchaseRequest;
22
33
  readonly response?: VerifyPurchaseResponse;
23
34
  readonly result?: GameServicesPurchaseResult;
24
- }) | (OperationIdentity & {
35
+ };
36
+ /** The rewarded-ad journal record. */
37
+ export type RewardedAdOperationRecord = OperationIdentity & {
25
38
  readonly kind: 'rewarded-ad';
26
39
  readonly input: GameServicesRewardedAdInput;
27
40
  readonly platform?: RewardedAdResult;
28
41
  readonly request?: ClaimAdRewardRequest;
29
42
  readonly response?: ClaimAdRewardResponse;
30
43
  readonly result?: GameServicesRewardedAdResult;
31
- });
44
+ };
45
+ /** The pre-reconciler journal records: a retired purchase record and the live rewarded-ad record. */
46
+ export type LegacyMonetizationOperationRecord = RetiredPurchaseOperationRecord | RewardedAdOperationRecord;
32
47
  /**
33
48
  * Every record kind the journal holds. `commerce-purchase` records are the
34
- * commerce v2 reconciler's (`./commerce-reconciler`); their open state is
35
- * `state`, not `result`, and `isMonetizationOperationRecoverable` applies the
36
- * rule for every kind.
49
+ * reconciler's (`./commerce-reconciler`); their open state is `state`, not
50
+ * `result`, and `isMonetizationOperationRecoverable` applies the rule for
51
+ * every kind.
37
52
  */
38
53
  export type MonetizationOperationRecord = LegacyMonetizationOperationRecord | CommercePurchaseOperationRecord;
39
54
  /** Games must supply a durable, atomic implementation; an in-memory map is not recovery. */
@@ -49,13 +64,13 @@ export interface MonetizationOperationStore {
49
64
  /** Return only unresolved operations; keep terminal records addressable by read for deduplication. */
50
65
  listRecoverable(playerId: string): Promise<readonly MonetizationOperationRecord[]>;
51
66
  /**
52
- * Commerce v2 records in `granted`, `finishing`, or `finished` kept within
67
+ * Reconciler records in `granted`, `finishing`, or `finished` kept within
53
68
  * the journal retention window, for the reconciler's revocation pass. A
54
69
  * store without it only reports reversals of grants seen in this session.
55
70
  */
56
71
  listGranted?(playerId: string): Promise<readonly MonetizationOperationRecord[]>;
57
72
  /**
58
- * The commerce v2 record, terminal or not, whose `purchase.transactionId`
73
+ * The reconciler record, terminal or not, whose `purchase.transactionId`
59
74
  * is this store transaction. With it a transaction the store lists again
60
75
  * after a restart (an owned durable, a rejected or finished purchase) meets
61
76
  * its own record instead of being verified again under a derived key.
@@ -75,7 +90,7 @@ export interface MonetizationOperationSummary {
75
90
  readonly kind: MonetizationOperationRecord['kind'];
76
91
  readonly idempotencyKey: string;
77
92
  /**
78
- * RFC "Purchase lifecycle" mapping for a commerce v2 record: `granted`
93
+ * RFC "Purchase lifecycle" mapping for a reconciler record: `granted`
79
94
  * covers `granted`, `finishing`, and `finished`; `pending` covers
80
95
  * `launched`, `pending`, `purchased`, and `verifying`; `revoked` is a
81
96
  * reversed grant.
@@ -84,29 +99,19 @@ export interface MonetizationOperationSummary {
84
99
  readonly finalizationPending: boolean;
85
100
  }
86
101
  export interface RecoverableMonetizationClient extends GameServicesOperationClient {
87
- /**
88
- * Replays a platform purchase callback into a v1 journal record.
89
- *
90
- * @deprecated For a gateway with a commerce v2 provider the reconciler
91
- * learns the result from the provider itself: this call then runs one
92
- * reconcile pass and returns the journaled result for the key, ignoring
93
- * `platform`. Removed with the v1 adapter in C4.
94
- */
95
- recoverPurchaseResult(idempotencyKey: string, platform: PurchaseResult): Promise<GameServicesPurchaseResult>;
96
102
  recoverRewardResult(idempotencyKey: string, platform: RewardedAdResult): Promise<GameServicesRewardedAdResult>;
97
103
  /**
98
- * Startup and app-resume recovery: resumes v1 records and, with a commerce
99
- * v2 provider, starts or runs the reconciler (subscribe, read available
104
+ * Startup and app-resume recovery: resumes rewarded-ad records and, with a
105
+ * commerce provider, starts or runs the reconciler (subscribe, read available
100
106
  * purchases, send saved verify requests, finish granted purchases).
101
107
  */
102
108
  reconcile(): Promise<readonly MonetizationOperationSummary[]>;
103
109
  /**
104
- * The manual restore trigger of the commerce v2 reconciler
105
- * (`provider.restorePurchases` when offered, then a pass). Without a v2
106
- * provider it is `reconcile()`.
110
+ * The manual restore trigger of the reconciler (`provider.restorePurchases`
111
+ * when offered, then a pass). Without a commerce provider it is `reconcile()`.
107
112
  */
108
113
  restorePurchases(): Promise<readonly MonetizationOperationSummary[]>;
109
- /** Present when the gateway exposes `commerceV2`. */
114
+ /** Present when the gateway exposes `commerce`. */
110
115
  readonly commerceReconciler?: CommerceReconciler;
111
116
  }
112
117
  /**