@mpgd/game-services 0.13.1 → 0.15.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 +74 -0
- package/dist/admob-ssv-conformance.d.ts +2 -2
- package/dist/admob-ssv.d.ts +2 -2
- package/dist/app-store-verifier-conformance.d.ts +1 -1
- package/dist/app-store-verifier.d.ts +1 -1
- package/dist/apps-in-toss-evidence-verification-conformance.d.ts +2 -2
- package/dist/apps-in-toss-evidence-verification-conformance.js +2 -2
- package/dist/apps-in-toss-evidence-verification.d.ts +2 -2
- package/dist/apps-in-toss-partner-api.d.ts +1 -1
- package/dist/client.d.ts +7 -27
- package/dist/client.js +162 -147
- package/dist/contract.d.ts +1 -1
- package/dist/evidence-verification.d.ts +1 -1
- package/dist/google-play-purchase-conformance.d.ts +1 -1
- package/dist/google-play-purchase-conformance.js +1 -1
- package/dist/google-play-purchase.d.ts +1 -1
- package/dist/index.d.ts +16 -15
- package/dist/index.js +16 -15
- package/dist/microsoft-store-identity.d.ts +39 -0
- package/dist/microsoft-store-identity.js +304 -0
- package/dist/microsoft-store-purchase.d.ts +1 -1
- package/dist/notification-delivery.js +3 -1
- package/dist/operation-progress.d.ts +47 -0
- package/dist/operation-progress.js +87 -0
- package/dist/operations.d.ts +30 -0
- package/dist/operations.js +1 -0
- package/dist/progress-link.js +1 -1
- package/dist/server.d.ts +6 -6
- package/dist/server.js +9 -6
- package/dist/verified-leaderboard-conformance.d.ts +1 -1
- package/dist/verified-leaderboard-conformance.js +1 -1
- package/dist/verified-leaderboard-durability-conformance.d.ts +1 -1
- package/dist/verified-leaderboard-durability-conformance.js +1 -1
- package/package.json +13 -5
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Game services
|
|
2
|
+
|
|
3
|
+
Client, contract, backend, and store helpers for authoritative game services.
|
|
4
|
+
See [backend integration](../../docs/GAME_SERVICES_BACKEND.md) for the existing
|
|
5
|
+
verification and ledger boundaries.
|
|
6
|
+
|
|
7
|
+
## Optional operation progress
|
|
8
|
+
|
|
9
|
+
`purchase(input, options?)` and `claimRewardedAd(input, options?)` accept optional
|
|
10
|
+
`onProgress` observers. Existing one-argument calls and client doubles remain
|
|
11
|
+
compatible. Types are available from `@mpgd/game-services/client` and the package
|
|
12
|
+
root. No UI runtime or Phaser dependency is added.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
await client.purchase(input, {
|
|
16
|
+
correlationId: 'view-operation-42',
|
|
17
|
+
onProgress: (event) => updateStatus(event),
|
|
18
|
+
onObserverError: (error) => reportUiObserverError(error),
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Phase | Observed fact |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `platform-requested` | The platform API request is about to be attempted |
|
|
25
|
+
| `platform-result` | The platform API resolved; `status` retains its result code |
|
|
26
|
+
| `server-requested` | The request has been constructed and the backend verification/claim call is about to be attempted |
|
|
27
|
+
| `server-result` | The backend resolved; `accepted` mirrors verification or claim acceptance |
|
|
28
|
+
| `completed` | The local invocation returned a normal result; `status` equals that result's status |
|
|
29
|
+
| `exception` | The invocation threw; `at` identifies platform, server, or local processing |
|
|
30
|
+
|
|
31
|
+
Purchase and rewarded-ad progress are discriminated by `kind` and `phase`.
|
|
32
|
+
`sequence` starts at one per invocation and increases for each actual event.
|
|
33
|
+
Events are frozen and calls have independent observers, sequences, and optional
|
|
34
|
+
correlation IDs. Callback invocation order is deterministic; asynchronous callback
|
|
35
|
+
completion order belongs to the callback. There is no event history, polling,
|
|
36
|
+
retry, or cancellation mechanism.
|
|
37
|
+
|
|
38
|
+
A platform request is not proof that a native screen opened, and a server stage
|
|
39
|
+
is not proof that it closed. Native UI visibility cannot be inferred from these
|
|
40
|
+
events. Platform ad completion/reward flags, backend acceptance, and the final
|
|
41
|
+
service result remain separate. Progress is observation, never grant evidence.
|
|
42
|
+
|
|
43
|
+
Successful verified purchases and claimed ads normally emit all five non-error
|
|
44
|
+
stages. Cancelled, pending, failed, skipped, or unavailable platform results
|
|
45
|
+
complete without fabricating backend stages. Unsupported targets emit only the
|
|
46
|
+
local completed result, retaining existing rejected-result semantics. An
|
|
47
|
+
authoritative Microsoft Store completion emits no duplicate verification.
|
|
48
|
+
Verse8 purchases preserve the existing Agent8 grant restrictions and pending
|
|
49
|
+
semantics. Backend rejection is normal completion; an exception leaves the
|
|
50
|
+
outcome uncertain and does not mean the server denied a grant.
|
|
51
|
+
|
|
52
|
+
Events do not copy receipts, provider evidence, auth headers, player IDs, ledger
|
|
53
|
+
IDs, idempotency keys, raw responses, or provider exception details. A correlation
|
|
54
|
+
ID is caller-supplied UI/diagnostic metadata: use an opaque, non-sensitive value.
|
|
55
|
+
It is independent of the backend idempotency key and confers no grant authority.
|
|
56
|
+
|
|
57
|
+
Synchronous callback exceptions and rejected callback promises are isolated.
|
|
58
|
+
Observer promises are not awaited: slow or unsettled observers cannot delay a
|
|
59
|
+
platform call, verification, claim, or business result. Optional `onObserverError`
|
|
60
|
+
observes only observation failures; its own exceptions/rejections are consumed.
|
|
61
|
+
There is no forced global logging or transport. Original business exceptions
|
|
62
|
+
still reject the client promise unchanged. Existing analytics, purchase recovery,
|
|
63
|
+
and retry policy are not redesigned by this observer API.
|
|
64
|
+
|
|
65
|
+
The root `pnpm test` pipeline includes compiled client JS and public declaration
|
|
66
|
+
consumption checks alongside the client and target conformance suites.
|
|
67
|
+
|
|
68
|
+
All published entrypoints use explicit internal ESM module paths and are smoke-tested
|
|
69
|
+
with native Node imports, without a bundler or TypeScript runtime loader.
|
|
70
|
+
|
|
71
|
+
Headless consumers can import operation input/result/progress types and the two-method
|
|
72
|
+
`GameServicesOperationClient` port from `@mpgd/game-services/operations`. This entrypoint
|
|
73
|
+
has no runtime implementation and its declarations require no DOM or fetch globals.
|
|
74
|
+
The full client continues to re-export the same operation types for compatibility.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AdPlacements } from '@mpgd/catalog';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
3
|
-
import type { ClaimAdRewardRequest } from './types';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
|
+
import type { ClaimAdRewardRequest } from './types.js';
|
|
4
4
|
export declare const adMobSsvConformanceChecks: readonly ['verified callback emits one transaction authority identity', 'missing callback remains pending', 'tampered signature fails closed', 'AdMob-decoded signed query bytes accept equivalent percent-encoding', 'missing and duplicate signed fields fail closed', 'unknown key fails closed', 'secp256k1 key feed entries verify', 'signed identity mismatch fails closed', 'timestamp age and future-skew boundaries fail closed', 'callbacks sharing one signed transaction preserve one authority identity'];
|
|
5
5
|
export interface AdMobSsvConformanceReport {
|
|
6
6
|
readonly checks: readonly string[];
|
package/dist/admob-ssv.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AdPlacementEntry } from '@mpgd/catalog';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
3
|
-
import type { ClaimAdRewardRequest } from './types';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
|
+
import type { ClaimAdRewardRequest } from './types.js';
|
|
4
4
|
export declare const admobSsvCustomDataSchema = "mpgd.admob.ssv.binding.v1";
|
|
5
5
|
export declare const defaultAdMobSsvMaximumCallbackAgeMs = 86400000;
|
|
6
6
|
export declare const defaultAdMobSsvMaximumFutureSkewMs = 300000;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppStoreEnvironment, AppStoreInAppPurchaseType, AppStoreTransactionPayload } from './app-store-verifier';
|
|
1
|
+
import type { AppStoreEnvironment, AppStoreInAppPurchaseType, AppStoreTransactionPayload } from './app-store-verifier.js';
|
|
2
2
|
export interface CreateAppStoreTransactionConformanceFixtureInput {
|
|
3
3
|
readonly transactionId?: string;
|
|
4
4
|
readonly originalTransactionId?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
1
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
2
2
|
export type AppStoreEnvironment = 'Production' | 'Sandbox';
|
|
3
3
|
export declare const appStoreServerApiBaseUrls: Readonly<{
|
|
4
4
|
readonly Production: 'https://api.storekit.apple.com';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AppsInTossPurchaseAuthority, type AppsInTossPurchaseAuthorityInput, type AppsInTossPurchaseAuthorityResult, type AppsInTossRewardAuthority, type AppsInTossRewardAuthorityInput, type AppsInTossRewardAuthorityResult, type CreateAppsInTossProductionEvidenceVerifierInput } from './apps-in-toss-evidence-verification';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
1
|
+
import { type AppsInTossPurchaseAuthority, type AppsInTossPurchaseAuthorityInput, type AppsInTossPurchaseAuthorityResult, type AppsInTossRewardAuthority, type AppsInTossRewardAuthorityInput, type AppsInTossRewardAuthorityResult, type CreateAppsInTossProductionEvidenceVerifierInput } from './apps-in-toss-evidence-verification.js';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
3
|
export declare const appsInTossProductionEvidenceConformanceScenarios: readonly ['callback-only-fail-closed', 'purchase-product-grant-callback', 'purchase-authority-retry', 'purchase-pending-order-restore', 'purchase-authority-matching', 'purchase-timestamp-normalization', 'reward-authority-retry-and-replay', 'reward-timestamp-validation', 'authority-errors-and-reward-matching'];
|
|
4
4
|
export type AppsInTossProductionEvidenceConformanceScenario = (typeof appsInTossProductionEvidenceConformanceScenarios)[number];
|
|
5
5
|
export type CreateAppsInTossProductionEvidenceVerifier = (input?: CreateAppsInTossProductionEvidenceVerifierInput) => GameServicesEvidenceVerifier;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { appsInTossPurchaseCallbackEvidenceSchema, createAppsInTossProductGrantCallback, createAppsInTossProductGrantVerificationPort, createAppsInTossProductionEvidenceVerifier, createAppsInTossPurchaseCallbackEvidence, createAppsInTossRewardCallbackEvidence, } from './apps-in-toss-evidence-verification';
|
|
2
|
-
import { createGameServicesBackend, createInMemoryGameServicesStore, InMemoryGameServicesStore, } from './server';
|
|
1
|
+
import { appsInTossPurchaseCallbackEvidenceSchema, createAppsInTossProductGrantCallback, createAppsInTossProductGrantVerificationPort, createAppsInTossProductionEvidenceVerifier, createAppsInTossPurchaseCallbackEvidence, createAppsInTossRewardCallbackEvidence, } from './apps-in-toss-evidence-verification.js';
|
|
2
|
+
import { createGameServicesBackend, createInMemoryGameServicesStore, InMemoryGameServicesStore, } from './server.js';
|
|
3
3
|
export const appsInTossProductionEvidenceConformanceScenarios = [
|
|
4
4
|
'callback-only-fail-closed',
|
|
5
5
|
'purchase-product-grant-callback',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LogicalProductId, PlatformEvidenceEnvelope } from '@mpgd/platform';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
3
|
-
import type { VerifyPurchaseRequest, VerifyPurchaseResponse } from './types';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
|
+
import type { VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
4
4
|
export declare const appsInTossPurchaseCallbackEvidenceSchema = "apps-in-toss.iap.callback.v1";
|
|
5
5
|
export declare const appsInTossRewardCallbackEvidenceSchema = "apps-in-toss.rewarded-ad.callback.v1";
|
|
6
6
|
/** Leaves five seconds of headroom inside the SDK's documented 30-second window. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NotificationTemplateData } from './notification-delivery';
|
|
1
|
+
import type { NotificationTemplateData } from './notification-delivery.js';
|
|
2
2
|
export declare const appsInTossPartnerApiBaseUrl = "https://apps-in-toss-api.toss.im";
|
|
3
3
|
export declare const defaultAppsInTossPartnerApiTimeoutMs = 10000;
|
|
4
4
|
/** Compatible with a Cloudflare mTLS certificate binding (`env.BINDING.fetch`). */
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type AnalyticsSink } from '@mpgd/analytics';
|
|
2
|
-
import type { LeaderboardScoreInput,
|
|
3
|
-
import type {
|
|
4
|
-
|
|
2
|
+
import type { LeaderboardScoreInput, PlatformGateway, PlatformTarget } from '@mpgd/platform';
|
|
3
|
+
import type { GameServicesOperationClient } from './operations.js';
|
|
4
|
+
export type { GameServicesOperationClient, GameServicesPurchaseInput, GameServicesPurchaseResult, GameServicesRewardedAdInput, GameServicesRewardedAdResult, } from './operations.js';
|
|
5
|
+
import type { GameServicesContractClient } from './contract.js';
|
|
6
|
+
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, GameServicesLedgerTarget, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
7
|
+
export type { GameServicesOperationKind, GameServicesOperationLocation, GameServicesOperationOptions, GameServicesOperationProgress, GameServicesPurchaseProgress, GameServicesRewardedAdProgress, } from './operation-progress.js';
|
|
5
8
|
export interface PurchaseVerificationApi {
|
|
6
9
|
verifyPurchase(input: VerifyPurchaseRequest): Promise<VerifyPurchaseResponse>;
|
|
7
10
|
}
|
|
@@ -64,9 +67,7 @@ export declare class GameServicesBackendError extends Error {
|
|
|
64
67
|
readonly body: unknown;
|
|
65
68
|
constructor(endpoint: GameServicesBackendEndpoint, status: number, body: unknown);
|
|
66
69
|
}
|
|
67
|
-
export interface GameServicesClient {
|
|
68
|
-
purchase(input: GameServicesPurchaseInput): Promise<GameServicesPurchaseResult>;
|
|
69
|
-
claimRewardedAd(input: GameServicesRewardedAdInput): Promise<GameServicesRewardedAdResult>;
|
|
70
|
+
export interface GameServicesClient extends GameServicesOperationClient {
|
|
70
71
|
submitLeaderboardScore(input: GameServicesLeaderboardInput): Promise<GameServicesLeaderboardResult>;
|
|
71
72
|
}
|
|
72
73
|
export interface CreateGameServicesClientInput {
|
|
@@ -79,27 +80,6 @@ export interface CreateGameServicesClientInput {
|
|
|
79
80
|
readonly analyticsSessionId?: string;
|
|
80
81
|
readonly now?: () => string;
|
|
81
82
|
}
|
|
82
|
-
export interface GameServicesPurchaseInput {
|
|
83
|
-
readonly productId: LogicalProductId;
|
|
84
|
-
readonly source: 'shop' | 'stage_fail' | 'result' | 'event';
|
|
85
|
-
readonly idempotencyKey: string;
|
|
86
|
-
}
|
|
87
|
-
export interface GameServicesPurchaseResult {
|
|
88
|
-
readonly status: 'granted' | 'cancelled' | 'pending' | 'failed' | 'rejected';
|
|
89
|
-
readonly purchase: PurchaseResult;
|
|
90
|
-
readonly verification?: VerifyPurchaseResponse;
|
|
91
|
-
readonly ledgerEntryId?: string;
|
|
92
|
-
}
|
|
93
|
-
export interface GameServicesRewardedAdInput {
|
|
94
|
-
readonly placementId: LogicalAdPlacementId;
|
|
95
|
-
readonly idempotencyKey: string;
|
|
96
|
-
}
|
|
97
|
-
export interface GameServicesRewardedAdResult {
|
|
98
|
-
readonly status: 'granted' | 'skipped' | 'unavailable' | 'failed' | 'rejected';
|
|
99
|
-
readonly reward: RewardedAdResult;
|
|
100
|
-
readonly claim?: ClaimAdRewardResponse;
|
|
101
|
-
readonly ledgerEntryId?: string;
|
|
102
|
-
}
|
|
103
83
|
export interface GameServicesLeaderboardInput extends LeaderboardScoreInput {
|
|
104
84
|
}
|
|
105
85
|
export interface GameServicesLeaderboardResult {
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createORPCClient } from '@orpc/client';
|
|
2
2
|
import { RPCLink } from '@orpc/client/fetch';
|
|
3
3
|
import { createAnalyticsReporter } from '@mpgd/analytics';
|
|
4
|
+
import { observeGameServicesOperation } from './operation-progress.js';
|
|
4
5
|
export const gameServicesBackendEndpoints = {
|
|
5
6
|
verifyPurchase: '/game-services/purchases/verify',
|
|
6
7
|
claimAdReward: '/game-services/ad-rewards/claim',
|
|
@@ -27,176 +28,190 @@ export function createGameServicesClient(input) {
|
|
|
27
28
|
...(input.analytics === undefined ? {} : { sink: input.analytics }),
|
|
28
29
|
});
|
|
29
30
|
return {
|
|
30
|
-
async purchase(purchaseInput) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
async purchase(purchaseInput, options) {
|
|
32
|
+
return observeGameServicesOperation('purchase', options, async (progress) => {
|
|
33
|
+
const target = input.target;
|
|
34
|
+
if (!isGameServicesCommerceTarget(target)) {
|
|
35
|
+
const purchase = {
|
|
36
|
+
status: 'failed',
|
|
37
|
+
entitlementIds: [],
|
|
38
|
+
};
|
|
39
|
+
await analytics.track({
|
|
40
|
+
name: 'purchase_rejected',
|
|
41
|
+
properties: {
|
|
42
|
+
productId: purchaseInput.productId,
|
|
43
|
+
status: purchase.status,
|
|
44
|
+
reason: 'unsupported_target',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
status: 'rejected',
|
|
49
|
+
purchase,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
progress.platformRequested();
|
|
53
|
+
const purchase = await input.gateway.commerce.purchase(purchaseInput);
|
|
54
|
+
progress.platformResult(purchase.status);
|
|
55
|
+
if (target === 'verse8') {
|
|
56
|
+
const status = purchase.status === 'completed' ? 'rejected' : purchase.status;
|
|
57
|
+
await analytics.track({
|
|
58
|
+
name: 'purchase_rejected',
|
|
59
|
+
properties: {
|
|
60
|
+
productId: purchaseInput.productId,
|
|
61
|
+
status,
|
|
62
|
+
reason: purchase.status === 'completed'
|
|
63
|
+
? 'verse8_grants_require_agent8_purchase_event'
|
|
64
|
+
: purchase.status === 'pending'
|
|
65
|
+
? 'agent8_purchase_event_pending'
|
|
66
|
+
: undefined,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
57
70
|
status,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
purchase,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (isAuthoritativeMicrosoftStoreCompletion(target, purchase)) {
|
|
75
|
+
const result = {
|
|
76
|
+
status: 'granted',
|
|
77
|
+
purchase,
|
|
78
|
+
ledgerEntryId: purchase.transactionId,
|
|
79
|
+
};
|
|
80
|
+
await analytics.track({
|
|
81
|
+
name: 'purchase_granted',
|
|
82
|
+
properties: {
|
|
83
|
+
productId: purchaseInput.productId,
|
|
84
|
+
status: result.status,
|
|
85
|
+
ledgerEntryId: purchase.transactionId,
|
|
86
|
+
alreadyProcessed: purchase.authoritativeGrant?.alreadyProcessed,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
if (purchase.status !== 'completed' || purchase.transactionId === undefined) {
|
|
92
|
+
await analytics.track({
|
|
93
|
+
name: 'purchase_rejected',
|
|
94
|
+
properties: {
|
|
95
|
+
productId: purchaseInput.productId,
|
|
96
|
+
status: purchase.status,
|
|
97
|
+
reason: purchaseRejectionReason(purchase),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
status: purchase.status === 'completed' ? 'rejected' : purchase.status,
|
|
102
|
+
purchase,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const verificationRequest = {
|
|
106
|
+
target,
|
|
107
|
+
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
108
|
+
? {}
|
|
109
|
+
: { deploymentTarget: input.deploymentTarget }),
|
|
110
|
+
playerId: input.playerId,
|
|
111
|
+
productId: purchaseInput.productId,
|
|
112
|
+
platformTransactionId: purchase.transactionId,
|
|
113
|
+
idempotencyKey: purchaseInput.idempotencyKey,
|
|
114
|
+
purchasedAt: now(),
|
|
115
|
+
...(purchase.evidence === undefined ? {} : { evidence: purchase.evidence }),
|
|
68
116
|
};
|
|
69
|
-
|
|
70
|
-
|
|
117
|
+
progress.serverRequested();
|
|
118
|
+
const verification = await input.backend.purchases.verifyPurchase(verificationRequest);
|
|
119
|
+
progress.serverResult(verification.verified);
|
|
71
120
|
const result = {
|
|
72
|
-
status: 'granted',
|
|
121
|
+
status: verification.verified ? 'granted' : 'rejected',
|
|
73
122
|
purchase,
|
|
74
|
-
|
|
123
|
+
verification,
|
|
124
|
+
...(verification.ledgerEntryId === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: { ledgerEntryId: verification.ledgerEntryId }),
|
|
75
127
|
};
|
|
76
128
|
await analytics.track({
|
|
77
|
-
name: 'purchase_granted',
|
|
129
|
+
name: verification.verified ? 'purchase_granted' : 'purchase_rejected',
|
|
78
130
|
properties: {
|
|
79
131
|
productId: purchaseInput.productId,
|
|
80
132
|
status: result.status,
|
|
81
|
-
ledgerEntryId:
|
|
82
|
-
alreadyProcessed:
|
|
133
|
+
ledgerEntryId: verification.ledgerEntryId,
|
|
134
|
+
alreadyProcessed: verification.alreadyProcessed,
|
|
135
|
+
reason: verification.reason,
|
|
83
136
|
},
|
|
84
137
|
});
|
|
85
138
|
return result;
|
|
86
|
-
}
|
|
87
|
-
if (purchase.status !== 'completed' || purchase.transactionId === undefined) {
|
|
88
|
-
await analytics.track({
|
|
89
|
-
name: 'purchase_rejected',
|
|
90
|
-
properties: {
|
|
91
|
-
productId: purchaseInput.productId,
|
|
92
|
-
status: purchase.status,
|
|
93
|
-
reason: purchaseRejectionReason(purchase),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
return {
|
|
97
|
-
status: purchase.status === 'completed' ? 'rejected' : purchase.status,
|
|
98
|
-
purchase,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
const verification = await input.backend.purchases.verifyPurchase({
|
|
102
|
-
target,
|
|
103
|
-
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
104
|
-
? {}
|
|
105
|
-
: { deploymentTarget: input.deploymentTarget }),
|
|
106
|
-
playerId: input.playerId,
|
|
107
|
-
productId: purchaseInput.productId,
|
|
108
|
-
platformTransactionId: purchase.transactionId,
|
|
109
|
-
idempotencyKey: purchaseInput.idempotencyKey,
|
|
110
|
-
purchasedAt: now(),
|
|
111
|
-
...(purchase.evidence === undefined ? {} : { evidence: purchase.evidence }),
|
|
112
139
|
});
|
|
113
|
-
const result = {
|
|
114
|
-
status: verification.verified ? 'granted' : 'rejected',
|
|
115
|
-
purchase,
|
|
116
|
-
verification,
|
|
117
|
-
...(verification.ledgerEntryId === undefined
|
|
118
|
-
? {}
|
|
119
|
-
: { ledgerEntryId: verification.ledgerEntryId }),
|
|
120
|
-
};
|
|
121
|
-
await analytics.track({
|
|
122
|
-
name: verification.verified ? 'purchase_granted' : 'purchase_rejected',
|
|
123
|
-
properties: {
|
|
124
|
-
productId: purchaseInput.productId,
|
|
125
|
-
status: result.status,
|
|
126
|
-
ledgerEntryId: verification.ledgerEntryId,
|
|
127
|
-
alreadyProcessed: verification.alreadyProcessed,
|
|
128
|
-
reason: verification.reason,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
return result;
|
|
132
140
|
},
|
|
133
|
-
async claimRewardedAd(rewardInput) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
async claimRewardedAd(rewardInput, options) {
|
|
142
|
+
return observeGameServicesOperation('rewarded-ad', options, async (progress) => {
|
|
143
|
+
const target = input.target;
|
|
144
|
+
if (!isGameServicesAdRewardTarget(target)) {
|
|
145
|
+
const reward = {
|
|
146
|
+
status: 'unavailable',
|
|
147
|
+
rewardGranted: false,
|
|
148
|
+
};
|
|
149
|
+
await analytics.track({
|
|
150
|
+
name: 'rewarded_ad_rejected',
|
|
151
|
+
properties: {
|
|
152
|
+
placementId: rewardInput.placementId,
|
|
153
|
+
status: reward.status,
|
|
154
|
+
rewardGranted: reward.rewardGranted,
|
|
155
|
+
reason: 'unsupported_target',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
status: 'rejected',
|
|
160
|
+
reward,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
progress.platformRequested();
|
|
164
|
+
const reward = await input.gateway.ads.showRewarded(rewardInput);
|
|
165
|
+
progress.platformResult(reward.status);
|
|
166
|
+
if (reward.status !== 'completed' || !reward.rewardGranted) {
|
|
167
|
+
await analytics.track({
|
|
168
|
+
name: 'rewarded_ad_rejected',
|
|
169
|
+
properties: {
|
|
170
|
+
placementId: rewardInput.placementId,
|
|
171
|
+
status: reward.status,
|
|
172
|
+
rewardGranted: reward.rewardGranted,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
status: reward.status === 'completed' ? 'rejected' : reward.status,
|
|
177
|
+
reward,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const claimRequest = {
|
|
181
|
+
target,
|
|
182
|
+
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
183
|
+
? {}
|
|
184
|
+
: { deploymentTarget: input.deploymentTarget }),
|
|
185
|
+
playerId: input.playerId,
|
|
186
|
+
placementId: rewardInput.placementId,
|
|
187
|
+
...(reward.ledgerEntryId === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { platformImpressionId: reward.ledgerEntryId }),
|
|
190
|
+
idempotencyKey: rewardInput.idempotencyKey,
|
|
191
|
+
completedAt: now(),
|
|
192
|
+
...(reward.evidence === undefined ? {} : { evidence: reward.evidence }),
|
|
139
193
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
rewardGranted: reward.rewardGranted,
|
|
146
|
-
reason: 'unsupported_target',
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
return {
|
|
150
|
-
status: 'rejected',
|
|
194
|
+
progress.serverRequested();
|
|
195
|
+
const claim = await input.backend.adRewards.claimAdReward(claimRequest);
|
|
196
|
+
progress.serverResult(claim.granted);
|
|
197
|
+
const result = {
|
|
198
|
+
status: claim.granted ? 'granted' : 'rejected',
|
|
151
199
|
reward,
|
|
200
|
+
claim,
|
|
201
|
+
...(claim.ledgerEntryId === undefined ? {} : { ledgerEntryId: claim.ledgerEntryId }),
|
|
152
202
|
};
|
|
153
|
-
}
|
|
154
|
-
const reward = await input.gateway.ads.showRewarded(rewardInput);
|
|
155
|
-
if (reward.status !== 'completed' || !reward.rewardGranted) {
|
|
156
203
|
await analytics.track({
|
|
157
|
-
name: 'rewarded_ad_rejected',
|
|
204
|
+
name: claim.granted ? 'rewarded_ad_granted' : 'rewarded_ad_rejected',
|
|
158
205
|
properties: {
|
|
159
206
|
placementId: rewardInput.placementId,
|
|
160
|
-
status:
|
|
161
|
-
|
|
207
|
+
status: result.status,
|
|
208
|
+
ledgerEntryId: claim.ledgerEntryId,
|
|
209
|
+
alreadyProcessed: claim.alreadyProcessed,
|
|
210
|
+
reason: claim.reason,
|
|
162
211
|
},
|
|
163
212
|
});
|
|
164
|
-
return
|
|
165
|
-
status: reward.status === 'completed' ? 'rejected' : reward.status,
|
|
166
|
-
reward,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
const claim = await input.backend.adRewards.claimAdReward({
|
|
170
|
-
target,
|
|
171
|
-
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
172
|
-
? {}
|
|
173
|
-
: { deploymentTarget: input.deploymentTarget }),
|
|
174
|
-
playerId: input.playerId,
|
|
175
|
-
placementId: rewardInput.placementId,
|
|
176
|
-
...(reward.ledgerEntryId === undefined
|
|
177
|
-
? {}
|
|
178
|
-
: { platformImpressionId: reward.ledgerEntryId }),
|
|
179
|
-
idempotencyKey: rewardInput.idempotencyKey,
|
|
180
|
-
completedAt: now(),
|
|
181
|
-
...(reward.evidence === undefined ? {} : { evidence: reward.evidence }),
|
|
182
|
-
});
|
|
183
|
-
const result = {
|
|
184
|
-
status: claim.granted ? 'granted' : 'rejected',
|
|
185
|
-
reward,
|
|
186
|
-
claim,
|
|
187
|
-
...(claim.ledgerEntryId === undefined ? {} : { ledgerEntryId: claim.ledgerEntryId }),
|
|
188
|
-
};
|
|
189
|
-
await analytics.track({
|
|
190
|
-
name: claim.granted ? 'rewarded_ad_granted' : 'rewarded_ad_rejected',
|
|
191
|
-
properties: {
|
|
192
|
-
placementId: rewardInput.placementId,
|
|
193
|
-
status: result.status,
|
|
194
|
-
ledgerEntryId: claim.ledgerEntryId,
|
|
195
|
-
alreadyProcessed: claim.alreadyProcessed,
|
|
196
|
-
reason: claim.reason,
|
|
197
|
-
},
|
|
213
|
+
return result;
|
|
198
214
|
});
|
|
199
|
-
return result;
|
|
200
215
|
},
|
|
201
216
|
async submitLeaderboardScore(scoreInput) {
|
|
202
217
|
const target = input.target;
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type RouterContractClient } from '@orpc/contract';
|
|
2
|
-
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types';
|
|
2
|
+
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
3
3
|
export interface GameServicesHealthResponse {
|
|
4
4
|
readonly ok: true;
|
|
5
5
|
readonly service: 'game-services';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AdPlacements, ProductCatalog } from '@mpgd/catalog';
|
|
2
|
-
import type { ClaimAdRewardRequest, EntitlementLedgerPayload, PurchaseGrantFinalization, VerifyPurchaseRequest } from './types';
|
|
2
|
+
import type { ClaimAdRewardRequest, EntitlementLedgerPayload, PurchaseGrantFinalization, VerifyPurchaseRequest } from './types.js';
|
|
3
3
|
export type EvidenceVerificationDecision = {
|
|
4
4
|
readonly status: 'verified';
|
|
5
5
|
readonly verificationId: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type GooglePlayAcknowledgementState, type GooglePlayConsumptionState, type GooglePlayPurchaseState } from './google-play-purchase';
|
|
1
|
+
import { type GooglePlayAcknowledgementState, type GooglePlayConsumptionState, type GooglePlayPurchaseState } from './google-play-purchase.js';
|
|
2
2
|
export declare const googlePlayProductPurchaseConformanceEvidence: Readonly<{
|
|
3
3
|
schema: "google-play.product-purchase.v2";
|
|
4
4
|
payload: Readonly<{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { googlePlayProductPurchaseEvidenceSchema, } from './google-play-purchase';
|
|
1
|
+
import { googlePlayProductPurchaseEvidenceSchema, } from './google-play-purchase.js';
|
|
2
2
|
export const googlePlayProductPurchaseConformanceEvidence = Object.freeze({
|
|
3
3
|
schema: googlePlayProductPurchaseEvidenceSchema,
|
|
4
4
|
payload: Object.freeze({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GameServicesEvidenceVerifier, GameServicesPurchaseGrantFinalizer, VerifyPurchaseEvidenceInput } from './evidence-verification';
|
|
1
|
+
import type { GameServicesEvidenceVerifier, GameServicesPurchaseGrantFinalizer, VerifyPurchaseEvidenceInput } from './evidence-verification.js';
|
|
2
2
|
export declare const googlePlayProductPurchaseEvidenceSchema = "google-play.product-purchase.v2";
|
|
3
3
|
export type GooglePlayPurchaseState = 'PURCHASED' | 'CANCELLED' | 'PENDING' | 'PURCHASE_STATE_UNSPECIFIED';
|
|
4
4
|
export type GooglePlayConsumptionState = 'CONSUMPTION_STATE_YET_TO_BE_CONSUMED' | 'CONSUMPTION_STATE_CONSUMED' | 'CONSUMPTION_STATE_UNSPECIFIED';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
export * from './admob-ssv.js';
|
|
2
|
-
export * from './app-store-verifier';
|
|
3
|
-
export * from './apps-in-toss-evidence-verification';
|
|
4
|
-
export * from './apps-in-toss-partner-api';
|
|
5
|
-
export * from './client';
|
|
6
|
-
export * from './contract';
|
|
7
|
-
export * from './evidence-verification';
|
|
8
|
-
export * from './google-play-purchase';
|
|
9
|
-
export * from './microsoft-store-
|
|
10
|
-
export * from './
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
2
|
+
export * from './app-store-verifier.js';
|
|
3
|
+
export * from './apps-in-toss-evidence-verification.js';
|
|
4
|
+
export * from './apps-in-toss-partner-api.js';
|
|
5
|
+
export * from './client.js';
|
|
6
|
+
export * from './contract.js';
|
|
7
|
+
export * from './evidence-verification.js';
|
|
8
|
+
export * from './google-play-purchase.js';
|
|
9
|
+
export * from './microsoft-store-identity.js';
|
|
10
|
+
export * from './microsoft-store-purchase.js';
|
|
11
|
+
export * from './notification-delivery.js';
|
|
12
|
+
export * from './platform-order.js';
|
|
13
|
+
export * from './progress-link.js';
|
|
13
14
|
export * from './runtime.js';
|
|
14
|
-
export * from './server';
|
|
15
|
-
export * from './types';
|
|
16
|
-
export * from './verified-leaderboard';
|
|
17
|
-
export * from './verified-leaderboard-transport';
|
|
15
|
+
export * from './server.js';
|
|
16
|
+
export * from './types.js';
|
|
17
|
+
export * from './verified-leaderboard.js';
|
|
18
|
+
export * from './verified-leaderboard-transport.js';
|