@mpgd/game-services 0.21.1 → 0.22.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/README.md +30 -0
- package/dist/admob-ssv-conformance.js +3 -1
- package/dist/app-store-recovery.js +5 -0
- package/dist/client.d.ts +39 -1
- package/dist/client.js +198 -0
- package/dist/commerce-evidence-registry.d.ts +406 -0
- package/dist/commerce-evidence-registry.js +773 -0
- package/dist/commerce-ledger.d.ts +64 -0
- package/dist/commerce-ledger.js +146 -0
- package/dist/commerce-reconciler.d.ts +191 -0
- package/dist/commerce-reconciler.js +1251 -0
- package/dist/contract.d.ts +4 -1
- package/dist/contract.js +8 -0
- package/dist/evidence-verification.d.ts +14 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/monetization-operation-store.d.ts +20 -0
- package/dist/monetization-operation-store.js +80 -0
- package/dist/monetization-recovery.d.ts +57 -2
- package/dist/monetization-recovery.js +102 -8
- package/dist/operation-results.d.ts +14 -0
- package/dist/operations.d.ts +2 -1
- package/dist/platform-order.d.ts +47 -0
- package/dist/platform-order.js +86 -0
- package/dist/runtime.js +12 -1
- package/dist/server.d.ts +43 -1
- package/dist/server.js +305 -7
- package/dist/types.d.ts +79 -3
- package/dist/types.js +97 -6
- package/package.json +18 -6
package/README.md
CHANGED
|
@@ -20,6 +20,36 @@ installed by game clients, and no other subpath imports it. It moved here from
|
|
|
20
20
|
`@mpgd/adapter-capacitor/app-store-server`, which stays as a deprecated
|
|
21
21
|
re-export for one release.
|
|
22
22
|
|
|
23
|
+
## Commerce v2 reconciler
|
|
24
|
+
|
|
25
|
+
`createCommerceReconciler` from `@mpgd/game-services/commerce-reconciler` is
|
|
26
|
+
the RFC "Reconciler" over any commerce v2 `CommerceProvider`: it journals every
|
|
27
|
+
purchase as a `commerce-purchase` record of the `MonetizationOperationStore`,
|
|
28
|
+
sends the saved verify request to the backend, finishes a purchase only after
|
|
29
|
+
the ledger grant, and never grants on the client. `createGameServicesClient`
|
|
30
|
+
purchases through it whenever `gateway.commerceV2` exists and exposes it as
|
|
31
|
+
`commerceReconciler`; `createRecoverableMonetizationClient` and
|
|
32
|
+
`createGameServicesRuntime` run it from `monetizationRecovery.reconcile()` and
|
|
33
|
+
`restorePurchases()` over the durable journal. See
|
|
34
|
+
[docs/MONETIZATION_RECOVERY.md](../../docs/MONETIZATION_RECOVERY.md).
|
|
35
|
+
|
|
36
|
+
## Commerce evidence registry
|
|
37
|
+
|
|
38
|
+
`createCommerceEvidenceRegistry` from
|
|
39
|
+
`@mpgd/game-services/commerce-evidence-registry` is the RFC "Evidence
|
|
40
|
+
registry": the backend routes every verify request by `evidence.schema` to the
|
|
41
|
+
registered verifier and finalizer, publishes the stores it accepts through
|
|
42
|
+
`getCommerceCapabilities` (`CommerceServiceCapabilities`), and rejects an
|
|
43
|
+
unknown schema (`EVIDENCE_SCHEMA_UNSUPPORTED`) or one registered for another
|
|
44
|
+
store (`EVIDENCE_SCHEMA_STORE_MISMATCH`) before any verifier runs. Reference
|
|
45
|
+
entries wrap the Play, App Store, Microsoft Store, and Apps in Toss verifiers,
|
|
46
|
+
and the server-push stores (`devvit.order.v1`, `verse8.agent8-event.v1`) get
|
|
47
|
+
lookup verifiers that answer the grant the host's fulfillment path already
|
|
48
|
+
wrote, never the client envelope. `@mpgd/game-services/commerce-ledger` adds
|
|
49
|
+
the `revoke` entry write path (`revokeEntitlementGrant`), the ledger status
|
|
50
|
+
read behind `getPurchaseLedgerStatus`, and `listActiveEntitlementTransactions`.
|
|
51
|
+
See [docs/GAME_SERVICES_BACKEND.md](../../docs/GAME_SERVICES_BACKEND.md).
|
|
52
|
+
|
|
23
53
|
## Registered rewarded-ad evidence
|
|
24
54
|
|
|
25
55
|
`createGameServicesClient`, `createRecoverableMonetizationClient`, and
|
|
@@ -241,7 +241,9 @@ function fixedPublicKeySource(publicKeys) {
|
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
function assertDecision(decision, status, reason) {
|
|
244
|
-
const receivedReason = decision.status === 'verified'
|
|
244
|
+
const receivedReason = decision.status === 'verified' || decision.status === 'settled'
|
|
245
|
+
? undefined
|
|
246
|
+
: decision.reason;
|
|
245
247
|
assert(decision.status === status && receivedReason === reason, `expected ${status}/${reason}, received ${decision.status}/${String(receivedReason)}`);
|
|
246
248
|
}
|
|
247
249
|
function assert(condition, message) {
|
|
@@ -177,6 +177,11 @@ async function verifyRestoredNonConsumable(input, request, options) {
|
|
|
177
177
|
if (decision.status === 'rejected') {
|
|
178
178
|
return rejected(decision.reason);
|
|
179
179
|
}
|
|
180
|
+
if (decision.status === 'settled') {
|
|
181
|
+
// Restored non-consumables are re-verified against Apple, never settled
|
|
182
|
+
// by a ledger lookup.
|
|
183
|
+
return rejected('APP_STORE_RESTORE_EVIDENCE_MISMATCH');
|
|
184
|
+
}
|
|
180
185
|
const originalTransactionId = decision.payload?.appStoreOriginalTransactionId;
|
|
181
186
|
const environment = decision.payload?.appStoreEnvironment;
|
|
182
187
|
const bundleId = decision.payload?.appStoreBundleId;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { type AnalyticsSink } from '@mpgd/analytics';
|
|
2
2
|
import type { LeaderboardScoreInput, PlatformGateway, PlatformTarget } from '@mpgd/platform';
|
|
3
|
+
import type { CommerceServiceCapabilities } from '@mpgd/platform/commerce';
|
|
3
4
|
import { type ClientRewardEvidenceRegistry } from './client-reward-evidence.js';
|
|
5
|
+
import { type CommerceReconciler, type CommerceReconcilerEvent, type CommerceReconcilerProduct } from './commerce-reconciler.js';
|
|
6
|
+
import type { MonetizationOperationStore } from './monetization-recovery.js';
|
|
4
7
|
import type { GameServicesOperationClient } from './operations.js';
|
|
5
8
|
export type { GameServicesOperationClient, GameServicesPurchaseInput, GameServicesPurchaseResult, GameServicesRewardedAdInput, GameServicesRewardedAdResult, } from './operations.js';
|
|
6
9
|
import type { GameServicesContractClient } from './contract.js';
|
|
7
|
-
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, GameServicesLedgerTarget, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
10
|
+
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, GameServicesLedgerTarget, PurchaseLedgerStatusRequest, PurchaseLedgerStatusResponse, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
8
11
|
export type { GameServicesOperationKind, GameServicesOperationLocation, GameServicesOperationOptions, GameServicesOperationProgress, GameServicesPurchaseProgress, GameServicesRewardedAdProgress, } from './operation-progress.js';
|
|
9
12
|
export interface PurchaseVerificationApi {
|
|
10
13
|
verifyPurchase(input: VerifyPurchaseRequest): Promise<VerifyPurchaseResponse>;
|
|
14
|
+
/**
|
|
15
|
+
* Whether earlier ledger grants are still active (RFC "Revocation and
|
|
16
|
+
* expiry entries"). The commerce reconciler runs its revocation pass only
|
|
17
|
+
* when a backend offers this read; the reference server offers it since C2.
|
|
18
|
+
*/
|
|
19
|
+
getPurchaseLedgerStatus?(input: PurchaseLedgerStatusRequest): Promise<PurchaseLedgerStatusResponse>;
|
|
20
|
+
/**
|
|
21
|
+
* What the backend accepts (RFC "Provider capabilities endpoint"): the
|
|
22
|
+
* stores its commerce evidence registry serves, their schemas, and who
|
|
23
|
+
* finishes. The commerce reconciler compares it with its provider at start
|
|
24
|
+
* when a backend offers it; the reference server offers it when it was
|
|
25
|
+
* created with a `commerceEvidenceRegistry`.
|
|
26
|
+
*/
|
|
27
|
+
getCommerceCapabilities?(): Promise<CommerceServiceCapabilities>;
|
|
11
28
|
}
|
|
12
29
|
export interface AdRewardClaimApi {
|
|
13
30
|
claimAdReward(input: ClaimAdRewardRequest): Promise<ClaimAdRewardResponse>;
|
|
@@ -23,6 +40,8 @@ export interface GameServicesBackendApi {
|
|
|
23
40
|
}
|
|
24
41
|
export declare const gameServicesBackendEndpoints: {
|
|
25
42
|
readonly verifyPurchase: '/game-services/purchases/verify';
|
|
43
|
+
readonly getPurchaseLedgerStatus: '/game-services/purchases/ledger-status';
|
|
44
|
+
readonly getCommerceCapabilities: '/game-services/purchases/capabilities';
|
|
26
45
|
readonly claimAdReward: '/game-services/ad-rewards/claim';
|
|
27
46
|
readonly recordLeaderboardScore: '/game-services/leaderboard/record';
|
|
28
47
|
};
|
|
@@ -89,6 +108,12 @@ export declare function resolveGameServicesRequestHeaders(input: {
|
|
|
89
108
|
}): Promise<Readonly<Record<string, string>>>;
|
|
90
109
|
export interface GameServicesClient extends GameServicesOperationClient {
|
|
91
110
|
submitLeaderboardScore(input: GameServicesLeaderboardInput): Promise<GameServicesLeaderboardResult>;
|
|
111
|
+
/**
|
|
112
|
+
* The commerce v2 reconciler behind `purchase` when the gateway exposes
|
|
113
|
+
* `commerceV2`. Call `start()` at runtime start and `reconcile()` on app
|
|
114
|
+
* resume; `purchase` starts it on first use.
|
|
115
|
+
*/
|
|
116
|
+
readonly commerceReconciler?: CommerceReconciler;
|
|
92
117
|
}
|
|
93
118
|
export interface CreateGameServicesClientInput {
|
|
94
119
|
readonly gateway: PlatformGateway;
|
|
@@ -103,6 +128,19 @@ export interface CreateGameServicesClientInput {
|
|
|
103
128
|
readonly requestNow?: () => string;
|
|
104
129
|
/** Explicit client decoders only; server verification is configured independently. */
|
|
105
130
|
readonly rewardEvidenceRegistry?: ClientRewardEvidenceRegistry;
|
|
131
|
+
/**
|
|
132
|
+
* Journal of the commerce v2 reconciler used when `gateway.commerceV2`
|
|
133
|
+
* exists. Without one the journal lives in memory for this session only:
|
|
134
|
+
* duplicates are then caught by the backend ledger, not by the client.
|
|
135
|
+
*/
|
|
136
|
+
readonly operationStore?: MonetizationOperationStore;
|
|
137
|
+
/** A reconciler shared with another client, for example the recovery client's. */
|
|
138
|
+
readonly commerceReconciler?: CommerceReconciler;
|
|
139
|
+
/** Receives the one-time warning about a session-only commerce journal; defaults to `console.warn`. */
|
|
140
|
+
readonly warn?: (message: string) => void;
|
|
141
|
+
/** Catalog kinds and SKUs the reconciler resolves without a store read. */
|
|
142
|
+
readonly commerceProducts?: readonly CommerceReconcilerProduct[];
|
|
143
|
+
readonly onCommerceEvent?: (event: CommerceReconcilerEvent) => void;
|
|
106
144
|
}
|
|
107
145
|
export interface GameServicesLeaderboardInput extends LeaderboardScoreInput {
|
|
108
146
|
}
|
package/dist/client.js
CHANGED
|
@@ -3,10 +3,14 @@ import { RPCLink } from '@orpc/client/fetch';
|
|
|
3
3
|
import { createAnalyticsReporter } from '@mpgd/analytics';
|
|
4
4
|
import { findAdapterSettledGrant } from './authoritative-purchase.js';
|
|
5
5
|
import { resolveClientRewardClaim, } from './client-reward-evidence.js';
|
|
6
|
+
import { createCommerceReconciler, toGameServicesPurchaseResult, } from './commerce-reconciler.js';
|
|
6
7
|
import { createDefaultClientRewardEvidenceRegistry } from './default-client-reward-evidence.js';
|
|
8
|
+
import { createInMemoryMonetizationOperationStore } from './monetization-operation-store.js';
|
|
7
9
|
import { observeGameServicesOperation } from './operation-progress.js';
|
|
8
10
|
export const gameServicesBackendEndpoints = {
|
|
9
11
|
verifyPurchase: '/game-services/purchases/verify',
|
|
12
|
+
getPurchaseLedgerStatus: '/game-services/purchases/ledger-status',
|
|
13
|
+
getCommerceCapabilities: '/game-services/purchases/capabilities',
|
|
10
14
|
claimAdReward: '/game-services/ad-rewards/claim',
|
|
11
15
|
recordLeaderboardScore: '/game-services/leaderboard/record',
|
|
12
16
|
};
|
|
@@ -66,8 +70,190 @@ export function createGameServicesClient(input) {
|
|
|
66
70
|
now,
|
|
67
71
|
...(input.analytics === undefined ? {} : { sink: input.analytics }),
|
|
68
72
|
});
|
|
73
|
+
const commerceProvider = input.gateway.commerceV2;
|
|
74
|
+
let commerceReconciler;
|
|
75
|
+
if (commerceProvider !== undefined) {
|
|
76
|
+
if (input.commerceReconciler === undefined && input.operationStore === undefined) {
|
|
77
|
+
(input.warn ?? console.warn)('@mpgd/game-services: the commerce v2 reconciler is journaling in memory for this session '
|
|
78
|
+
+ 'because no operationStore was given; purchases are not recoverable across restarts and '
|
|
79
|
+
+ 'the backend ledger is the only duplicate backstop. Pass a durable MonetizationOperationStore.');
|
|
80
|
+
}
|
|
81
|
+
commerceReconciler = input.commerceReconciler ?? createCommerceReconciler({
|
|
82
|
+
provider: commerceProvider,
|
|
83
|
+
backend: input.backend.purchases,
|
|
84
|
+
store: input.operationStore ?? createInMemoryMonetizationOperationStore(),
|
|
85
|
+
playerId: input.playerId,
|
|
86
|
+
target: input.target,
|
|
87
|
+
...(input.deploymentTarget === undefined ? {} : { deploymentTarget: input.deploymentTarget }),
|
|
88
|
+
...(input.commerceProducts === undefined ? {} : { products: input.commerceProducts }),
|
|
89
|
+
now: requestNow,
|
|
90
|
+
});
|
|
91
|
+
if (input.onCommerceEvent !== undefined) {
|
|
92
|
+
commerceReconciler.subscribe(input.onCommerceEvent);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Keys of purchase calls in flight, and those whose backend verdict the observer tracked meanwhile. */
|
|
96
|
+
const inFlightKeys = new Set();
|
|
97
|
+
const trackedVerdicts = new Set();
|
|
98
|
+
const markTracked = (key) => {
|
|
99
|
+
if (inFlightKeys.has(key)) {
|
|
100
|
+
trackedVerdicts.add(key);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
if (commerceReconciler !== undefined && input.commerceReconciler === undefined) {
|
|
104
|
+
// Verdicts arrive as events, inside or after a purchase call; each is tracked once.
|
|
105
|
+
commerceReconciler.subscribe((event) => {
|
|
106
|
+
if (event.type !== 'transition' || event.previous === undefined) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const { record } = event;
|
|
110
|
+
const productId = record.logicalId;
|
|
111
|
+
if (event.previous === 'verifying' && record.state === 'granted') {
|
|
112
|
+
markTracked(record.idempotencyKey);
|
|
113
|
+
analytics.track({
|
|
114
|
+
name: 'purchase_granted',
|
|
115
|
+
properties: {
|
|
116
|
+
productId,
|
|
117
|
+
status: 'granted',
|
|
118
|
+
ledgerEntryId: record.ledgerEntryId,
|
|
119
|
+
alreadyProcessed: record.response?.alreadyProcessed,
|
|
120
|
+
},
|
|
121
|
+
}).catch(() => { });
|
|
122
|
+
}
|
|
123
|
+
else if (event.previous === 'verifying' && record.state === 'rejected') {
|
|
124
|
+
markTracked(record.idempotencyKey);
|
|
125
|
+
analytics.track({
|
|
126
|
+
name: 'purchase_rejected',
|
|
127
|
+
properties: { productId, status: 'rejected', reason: record.response?.reason },
|
|
128
|
+
}).catch(() => { });
|
|
129
|
+
}
|
|
130
|
+
else if (event.previous === 'verifying' && record.state === 'pending') {
|
|
131
|
+
markTracked(record.idempotencyKey);
|
|
132
|
+
analytics.track({
|
|
133
|
+
name: 'purchase_pending',
|
|
134
|
+
properties: {
|
|
135
|
+
productId,
|
|
136
|
+
status: 'pending',
|
|
137
|
+
reason: record.response?.reason ?? 'purchase_pending',
|
|
138
|
+
},
|
|
139
|
+
}).catch(() => { });
|
|
140
|
+
}
|
|
141
|
+
else if (record.state === 'cancelled' || record.state === 'failed') {
|
|
142
|
+
markTracked(record.idempotencyKey);
|
|
143
|
+
analytics.track({
|
|
144
|
+
name: 'purchase_rejected',
|
|
145
|
+
properties: { productId, status: record.state, reason: record.error?.code },
|
|
146
|
+
}).catch(() => { });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
69
150
|
return {
|
|
151
|
+
...(commerceReconciler === undefined ? {} : { commerceReconciler }),
|
|
70
152
|
async purchase(purchaseInput, options) {
|
|
153
|
+
const reconciler = commerceReconciler;
|
|
154
|
+
if (reconciler !== undefined) {
|
|
155
|
+
return observeGameServicesOperation('purchase', options, async (progress) => {
|
|
156
|
+
const key = purchaseInput.idempotencyKey;
|
|
157
|
+
trackedVerdicts.delete(key);
|
|
158
|
+
inFlightKeys.add(key);
|
|
159
|
+
if (!reconciler.started) {
|
|
160
|
+
await reconciler.start();
|
|
161
|
+
}
|
|
162
|
+
// The store phase ends when the journal records what the store answered;
|
|
163
|
+
// the reconcile pass inside requestPurchase then reports the server phases.
|
|
164
|
+
let platformReported = false;
|
|
165
|
+
let serverRequested = false;
|
|
166
|
+
let serverResulted = false;
|
|
167
|
+
const reportPlatform = (status) => {
|
|
168
|
+
if (!platformReported) {
|
|
169
|
+
platformReported = true;
|
|
170
|
+
progress.platformResult(status);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const unsubscribe = reconciler.subscribe((event) => {
|
|
174
|
+
if (event.type === 'failure' || event.record.idempotencyKey !== key) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (event.type === 'transition') {
|
|
178
|
+
const { state } = event.record;
|
|
179
|
+
if (state === 'purchased') {
|
|
180
|
+
reportPlatform('completed');
|
|
181
|
+
}
|
|
182
|
+
else if (state === 'pending' && event.previous === 'launched') {
|
|
183
|
+
reportPlatform('pending');
|
|
184
|
+
}
|
|
185
|
+
else if ((state === 'cancelled' || state === 'failed') && event.previous !== 'verifying') {
|
|
186
|
+
reportPlatform(state);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else if (event.type === 'verify-requested') {
|
|
190
|
+
reportPlatform('completed');
|
|
191
|
+
// A server-owned finish may re-read finalization in the same
|
|
192
|
+
// pass; the server phase is reported once per call.
|
|
193
|
+
if (!serverRequested) {
|
|
194
|
+
serverRequested = true;
|
|
195
|
+
progress.serverRequested();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (!serverResulted) {
|
|
199
|
+
serverResulted = true;
|
|
200
|
+
progress.serverResult(event.response.verified);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
try {
|
|
204
|
+
progress.platformRequested();
|
|
205
|
+
const outcome = await reconciler.requestPurchase({
|
|
206
|
+
logicalId: purchaseInput.productId,
|
|
207
|
+
idempotencyKey: key,
|
|
208
|
+
source: purchaseInput.source,
|
|
209
|
+
});
|
|
210
|
+
reportPlatform(outcome.outcome === 'launched'
|
|
211
|
+
? 'pending'
|
|
212
|
+
: outcome.outcome === 'purchased'
|
|
213
|
+
? (outcome.purchase.state === 'purchased' ? 'completed' : 'pending')
|
|
214
|
+
: outcome.outcome);
|
|
215
|
+
const record = await reconciler.read(key);
|
|
216
|
+
const result = record === undefined
|
|
217
|
+
? {
|
|
218
|
+
status: 'failed',
|
|
219
|
+
purchase: {
|
|
220
|
+
status: 'failed',
|
|
221
|
+
entitlementIds: [],
|
|
222
|
+
...(outcome.outcome === 'cancelled' || outcome.outcome === 'failed'
|
|
223
|
+
? { diagnostic: { code: outcome.error.code, retryable: outcome.error.retryable } }
|
|
224
|
+
: {}),
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
: toGameServicesPurchaseResult(record);
|
|
228
|
+
if (!trackedVerdicts.has(key)) {
|
|
229
|
+
// The call ended without a backend verdict (the store answers later,
|
|
230
|
+
// or a journaled attempt was replayed); report the status it returns.
|
|
231
|
+
await analytics.track({
|
|
232
|
+
name: result.status === 'granted'
|
|
233
|
+
? 'purchase_granted'
|
|
234
|
+
: result.status === 'pending'
|
|
235
|
+
? 'purchase_pending'
|
|
236
|
+
: 'purchase_rejected',
|
|
237
|
+
properties: {
|
|
238
|
+
productId: purchaseInput.productId,
|
|
239
|
+
status: result.status,
|
|
240
|
+
ledgerEntryId: result.ledgerEntryId,
|
|
241
|
+
alreadyProcessed: result.verification?.alreadyProcessed,
|
|
242
|
+
reason: result.status === 'pending'
|
|
243
|
+
? 'purchase_pending'
|
|
244
|
+
: (result.verification?.reason ?? record?.error?.code),
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
unsubscribe();
|
|
252
|
+
inFlightKeys.delete(key);
|
|
253
|
+
trackedVerdicts.delete(key);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
71
257
|
return observeGameServicesOperation('purchase', options, async (progress) => {
|
|
72
258
|
const target = input.target;
|
|
73
259
|
if (!isGameServicesCommerceTarget(target)) {
|
|
@@ -313,6 +499,12 @@ export function createGameServicesHttpBackendApi(input) {
|
|
|
313
499
|
async verifyPurchase(body) {
|
|
314
500
|
return sendGameServicesBackendRequest(input.transport, gameServicesBackendEndpoints.verifyPurchase, body);
|
|
315
501
|
},
|
|
502
|
+
async getPurchaseLedgerStatus(body) {
|
|
503
|
+
return sendGameServicesBackendRequest(input.transport, gameServicesBackendEndpoints.getPurchaseLedgerStatus, body);
|
|
504
|
+
},
|
|
505
|
+
async getCommerceCapabilities() {
|
|
506
|
+
return sendGameServicesBackendRequest(input.transport, gameServicesBackendEndpoints.getCommerceCapabilities, {});
|
|
507
|
+
},
|
|
316
508
|
},
|
|
317
509
|
adRewards: {
|
|
318
510
|
async claimAdReward(body) {
|
|
@@ -368,6 +560,12 @@ export function createGameServicesOrpcBackendApi(client) {
|
|
|
368
560
|
verifyPurchase(input) {
|
|
369
561
|
return client.commerce.verifyPurchase(input);
|
|
370
562
|
},
|
|
563
|
+
getPurchaseLedgerStatus(input) {
|
|
564
|
+
return client.commerce.getPurchaseLedgerStatus(input);
|
|
565
|
+
},
|
|
566
|
+
getCommerceCapabilities() {
|
|
567
|
+
return client.commerce.getCapabilities();
|
|
568
|
+
},
|
|
371
569
|
},
|
|
372
570
|
adRewards: {
|
|
373
571
|
claimAdReward(input) {
|