@invonetwork/web-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/LICENSE +17 -0
- package/README.md +142 -0
- package/dist/chunk-KUQVVH2P.js +121 -0
- package/dist/errors-B7rVID2r.d.cts +156 -0
- package/dist/errors-B7rVID2r.d.ts +156 -0
- package/dist/index.cjs +357 -0
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +239 -0
- package/dist/server.cjs +340 -0
- package/dist/server.d.cts +42 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +222 -0
- package/package.json +68 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { assertSecureBaseUrl, Http, InvoError } from './chunk-KUQVVH2P.js';
|
|
2
|
+
export { InvoError } from './chunk-KUQVVH2P.js';
|
|
3
|
+
|
|
4
|
+
// src/server.ts
|
|
5
|
+
var DEFAULT_UA = "invonetwork-web-sdk/0.1.0 (+https://invo.network)";
|
|
6
|
+
var MAX_USD_AMOUNT = 999.99;
|
|
7
|
+
function invalidAmount(usdAmount, why) {
|
|
8
|
+
return new InvoError({
|
|
9
|
+
message: `usdAmount ${why} (got ${JSON.stringify(usdAmount)}).`,
|
|
10
|
+
code: "INVALID_INPUT",
|
|
11
|
+
status: 0
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function assertUsdAmount(usdAmount) {
|
|
15
|
+
let n;
|
|
16
|
+
if (typeof usdAmount === "number") {
|
|
17
|
+
n = usdAmount;
|
|
18
|
+
if (Number.isFinite(n) && Math.abs(n * 100 - Math.round(n * 100)) > 1e-9) {
|
|
19
|
+
throw invalidAmount(usdAmount, "must have at most 2 decimal places");
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
if (!/^\d+(\.\d{1,2})?$/.test(usdAmount)) {
|
|
23
|
+
throw invalidAmount(usdAmount, "must be a plain decimal USD value with at most 2 places");
|
|
24
|
+
}
|
|
25
|
+
n = Number(usdAmount);
|
|
26
|
+
}
|
|
27
|
+
if (!Number.isFinite(n) || n <= 0 || n > MAX_USD_AMOUNT) {
|
|
28
|
+
throw invalidAmount(usdAmount, `must be > 0 and <= ${MAX_USD_AMOUNT}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function requireField(value, field, raw) {
|
|
32
|
+
const s = value == null ? "" : String(value);
|
|
33
|
+
if (!s) {
|
|
34
|
+
throw new InvoError({
|
|
35
|
+
message: `INVO response was missing the required \`${field}\` field.`,
|
|
36
|
+
code: "INVALID_RESPONSE",
|
|
37
|
+
status: 0,
|
|
38
|
+
body: raw
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return s;
|
|
42
|
+
}
|
|
43
|
+
var InvoServer = class {
|
|
44
|
+
constructor(config) {
|
|
45
|
+
if (!config.gameSecret) throw new Error("InvoServer requires a `gameSecret`.");
|
|
46
|
+
if (!config.baseUrl) throw new Error("InvoServer requires a `baseUrl`.");
|
|
47
|
+
assertSecureBaseUrl(config.baseUrl);
|
|
48
|
+
this.http = new Http({
|
|
49
|
+
baseUrl: config.baseUrl,
|
|
50
|
+
timeoutMs: config.timeoutMs,
|
|
51
|
+
fetchImpl: config.fetch,
|
|
52
|
+
userAgent: DEFAULT_UA
|
|
53
|
+
// must be a non-blocked UA (handoff doc §9)
|
|
54
|
+
});
|
|
55
|
+
this.auth = { kind: "game-secret", secret: config.gameSecret };
|
|
56
|
+
}
|
|
57
|
+
/** Mint a short-lived, game-scoped player token to hand to the browser InvoClient. */
|
|
58
|
+
async mintPlayerToken(input) {
|
|
59
|
+
const raw = await this.http.post(
|
|
60
|
+
"/api/sdk/player-token",
|
|
61
|
+
{ player_email: input.playerEmail },
|
|
62
|
+
this.auth
|
|
63
|
+
);
|
|
64
|
+
return {
|
|
65
|
+
token: requireField(raw["token"], "token", raw),
|
|
66
|
+
expiresAt: String(raw["expires_at"] ?? ""),
|
|
67
|
+
identityId: String(raw["identity_id"] ?? "")
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Initiate a cross-game currency SEND. Inspect result.verificationMethod. */
|
|
71
|
+
async initiateSend(input) {
|
|
72
|
+
const raw = await this.http.post(
|
|
73
|
+
"/api/currency-sends/initiate-send",
|
|
74
|
+
{
|
|
75
|
+
client_request_id: input.clientRequestId,
|
|
76
|
+
sender_player_name: input.senderPlayerName,
|
|
77
|
+
sender_player_email: input.senderPlayerEmail,
|
|
78
|
+
sender_player_phone: input.senderPlayerPhone,
|
|
79
|
+
receiver_player_email: input.receiverPlayerEmail,
|
|
80
|
+
receiver_player_phone: input.receiverPlayerPhone,
|
|
81
|
+
receiving_game_id: input.receivingGameId,
|
|
82
|
+
amount: input.amount
|
|
83
|
+
},
|
|
84
|
+
this.auth
|
|
85
|
+
);
|
|
86
|
+
return this.toInitiateResult(raw);
|
|
87
|
+
}
|
|
88
|
+
/** Initiate a cross-game TRANSFER. Inspect result.verificationMethod. */
|
|
89
|
+
async initiateTransfer(input) {
|
|
90
|
+
const raw = await this.http.post(
|
|
91
|
+
"/api/transfers/initiate-transfer",
|
|
92
|
+
{
|
|
93
|
+
client_request_id: input.clientRequestId,
|
|
94
|
+
source_player_name: input.sourcePlayerName,
|
|
95
|
+
source_player_email: input.sourcePlayerEmail,
|
|
96
|
+
source_player_phone: input.sourcePlayerPhone,
|
|
97
|
+
target_player_email: input.targetPlayerEmail,
|
|
98
|
+
target_player_phone: input.targetPlayerPhone,
|
|
99
|
+
target_game_id: input.targetGameId,
|
|
100
|
+
amount: input.amount
|
|
101
|
+
},
|
|
102
|
+
this.auth
|
|
103
|
+
);
|
|
104
|
+
return this.toInitiateResult(raw);
|
|
105
|
+
}
|
|
106
|
+
/** Create a hosted checkout session (the recommended purchase path). Open the
|
|
107
|
+
* returned checkoutUrl in a WebView/redirect or an iframe; the INVO-hosted page
|
|
108
|
+
* handles cards, saved cards, and 3-D Secure. Crediting is server-side via the
|
|
109
|
+
* purchase.completed webhook. */
|
|
110
|
+
async createCheckout(input) {
|
|
111
|
+
assertUsdAmount(input.usdAmount);
|
|
112
|
+
const body = {
|
|
113
|
+
player_email: input.playerEmail,
|
|
114
|
+
usd_amount: input.usdAmount
|
|
115
|
+
};
|
|
116
|
+
if (input.rail) body["rail"] = input.rail;
|
|
117
|
+
if (input.successUrl) body["success_url"] = input.successUrl;
|
|
118
|
+
if (input.cancelUrl) body["cancel_url"] = input.cancelUrl;
|
|
119
|
+
if (input.metadata) body["metadata"] = input.metadata;
|
|
120
|
+
const raw = await this.http.post(
|
|
121
|
+
"/api/checkout/sessions",
|
|
122
|
+
body,
|
|
123
|
+
this.auth
|
|
124
|
+
);
|
|
125
|
+
return {
|
|
126
|
+
sessionId: String(raw["session_id"] ?? ""),
|
|
127
|
+
checkoutUrl: requireField(raw["checkout_url"], "checkout_url", raw),
|
|
128
|
+
expiresAt: String(raw["expires_at"] ?? ""),
|
|
129
|
+
raw
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Direct purchase via the rail selector. Use when you need a specific rail.
|
|
134
|
+
* - rail "platform" (default): standard card. May return status "requires_action"
|
|
135
|
+
* (run client-side 3-D Secure with clientSecret, then call confirmPayment).
|
|
136
|
+
* - rail "game": returns status "pending_payment" + paymentUrl (redirect the browser).
|
|
137
|
+
* - rail "steam": NOT a browser flow — the backend returns WRONG_RAIL_ENDPOINT.
|
|
138
|
+
*/
|
|
139
|
+
async purchaseCurrency(input) {
|
|
140
|
+
assertUsdAmount(input.usdAmount);
|
|
141
|
+
if (!input.purchaseReference) {
|
|
142
|
+
throw new InvoError({
|
|
143
|
+
message: "purchaseReference is required (idempotency key for the purchase).",
|
|
144
|
+
code: "MISSING_PURCHASE_REFERENCE",
|
|
145
|
+
status: 0
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (input.rail === "steam") {
|
|
149
|
+
throw new InvoError({
|
|
150
|
+
message: 'rail "steam" is not a /purchase-currency flow \u2014 Steam uses the dedicated in-client Steam endpoints.',
|
|
151
|
+
code: "WRONG_RAIL_ENDPOINT",
|
|
152
|
+
status: 0
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const body = {
|
|
156
|
+
player_email: input.playerEmail,
|
|
157
|
+
usd_amount: input.usdAmount,
|
|
158
|
+
purchase_reference: input.purchaseReference
|
|
159
|
+
};
|
|
160
|
+
if (input.rail) body["rail"] = input.rail;
|
|
161
|
+
if (input.paymentMethodId) body["payment_method_id"] = input.paymentMethodId;
|
|
162
|
+
if (input.savedCardId) body["saved_card_id"] = input.savedCardId;
|
|
163
|
+
if (input.playerName) body["player_name"] = input.playerName;
|
|
164
|
+
if (input.playerPhone) body["player_phone"] = input.playerPhone;
|
|
165
|
+
const raw = await this.http.post(
|
|
166
|
+
"/api/currency-purchases/purchase-currency",
|
|
167
|
+
body,
|
|
168
|
+
this.auth
|
|
169
|
+
);
|
|
170
|
+
return {
|
|
171
|
+
status: String(raw["status"] ?? ""),
|
|
172
|
+
clientSecret: raw["client_secret"],
|
|
173
|
+
paymentIntentId: raw["payment_intent_id"],
|
|
174
|
+
paymentUrl: raw["payment_url"],
|
|
175
|
+
transactionId: raw["transaction_id"],
|
|
176
|
+
orderId: raw["order_id"],
|
|
177
|
+
newBalance: raw["new_balance"] ?? null,
|
|
178
|
+
raw
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Complete the Stripe-rail 3-D Secure step after the client finished card action. */
|
|
182
|
+
async confirmPayment(input) {
|
|
183
|
+
const body = { payment_intent_id: input.paymentIntentId };
|
|
184
|
+
if (input.orderId) body["order_id"] = input.orderId;
|
|
185
|
+
return this.http.post(
|
|
186
|
+
"/api/currency-purchases/confirm-payment",
|
|
187
|
+
body,
|
|
188
|
+
this.auth
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
/** Fetch purchase status (order + financial summary + timeline). */
|
|
192
|
+
async getOrderDetails(query) {
|
|
193
|
+
if (!query.orderId && !query.transactionId) {
|
|
194
|
+
throw new InvoError({
|
|
195
|
+
message: "getOrderDetails requires an `orderId` or `transactionId`.",
|
|
196
|
+
code: "INVALID_INPUT",
|
|
197
|
+
status: 0
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const q = new URLSearchParams();
|
|
201
|
+
if (query.orderId) q.set("order_id", query.orderId);
|
|
202
|
+
if (query.transactionId) q.set("transaction_id", query.transactionId);
|
|
203
|
+
return this.http.get(
|
|
204
|
+
`/api/currency-purchases/order-details?${q.toString()}`,
|
|
205
|
+
this.auth
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
toInitiateResult(raw) {
|
|
209
|
+
const vm = raw["verification_method"];
|
|
210
|
+
const guardian = raw["guardian_approval"];
|
|
211
|
+
return {
|
|
212
|
+
transactionId: String(raw["transaction_id"] ?? ""),
|
|
213
|
+
verificationMethod: vm === "in_app" || vm === "sms" ? vm : void 0,
|
|
214
|
+
guardianApproval: guardian ?? void 0,
|
|
215
|
+
raw
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export { InvoServer };
|
|
221
|
+
//# sourceMappingURL=server.js.map
|
|
222
|
+
//# sourceMappingURL=server.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@invonetwork/web-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "INVO Web SDK — currency purchase + passkey (WebAuthn) verification for partner web platforms.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": false,
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Invo-Technologies/invo-web-sdk.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://docs.invo.network",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Invo-Technologies/invo-web-sdk/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"!dist/**/*.map",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"require": "./dist/index.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./server": {
|
|
34
|
+
"types": "./dist/server.d.ts",
|
|
35
|
+
"import": "./dist/server.js",
|
|
36
|
+
"require": "./dist/server.cjs"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"main": "./dist/index.cjs",
|
|
40
|
+
"module": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"dev": "tsup --watch",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"clean": "rimraf dist",
|
|
49
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"invo",
|
|
53
|
+
"webauthn",
|
|
54
|
+
"passkey",
|
|
55
|
+
"payments",
|
|
56
|
+
"game-currency",
|
|
57
|
+
"sdk"
|
|
58
|
+
],
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"rimraf": "^5.0.5",
|
|
64
|
+
"tsup": "^8.0.1",
|
|
65
|
+
"typescript": "^5.4.5",
|
|
66
|
+
"vitest": "^1.5.0"
|
|
67
|
+
}
|
|
68
|
+
}
|