@gamecore-api/sdk 0.2.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 +343 -0
- package/dist/client.d.ts +251 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +245 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +29 -0
- package/dist/types.d.ts +353 -0
- package/dist/utils.d.ts +13 -0
- package/dist/webhooks.d.ts +21 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# @gamecore/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for GameCore API — zero external dependencies, browser-safe.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @gamecore/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { GameCoreClient } from "@gamecore/sdk";
|
|
15
|
+
|
|
16
|
+
const gc = new GameCoreClient({
|
|
17
|
+
apiKey: "gc_live_YOUR_KEY",
|
|
18
|
+
baseUrl: "https://api.gamecore-api.tech",
|
|
19
|
+
onAuthError: () => window.location.href = "/login",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Browse catalog
|
|
23
|
+
const games = await gc.catalog.getGames();
|
|
24
|
+
const game = await gc.catalog.getGame("honkai-star-rail");
|
|
25
|
+
const products = await gc.catalog.getProducts("honkai-star-rail");
|
|
26
|
+
|
|
27
|
+
// Search
|
|
28
|
+
const results = await gc.catalog.search("roblox");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
### Telegram Auth Flow
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// 1. Start auth → get bot link
|
|
37
|
+
const { token, botLink } = await gc.auth.initTelegram();
|
|
38
|
+
|
|
39
|
+
// 2. Open Telegram bot (user clicks "Start")
|
|
40
|
+
window.open(botLink, "_blank");
|
|
41
|
+
|
|
42
|
+
// 3. Poll until user authenticates
|
|
43
|
+
const user = await gc.auth.pollTelegramStatus(token);
|
|
44
|
+
console.log("Logged in:", user.firstName);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### VK Auth
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const { user } = await gc.auth.verifyVk(vkAccessToken);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Session
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const me = await gc.auth.getMe(); // Get current user
|
|
57
|
+
await gc.auth.logout(); // Clear session
|
|
58
|
+
const identities = await gc.auth.getIdentities(); // Linked providers
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Catalog
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// All games
|
|
65
|
+
const games = await gc.catalog.getGames({ type: "game" });
|
|
66
|
+
|
|
67
|
+
// Homepage ranked games
|
|
68
|
+
const homepage = await gc.catalog.getHomepageGames();
|
|
69
|
+
|
|
70
|
+
// Single game with categories
|
|
71
|
+
const game = await gc.catalog.getGame("genshin-impact");
|
|
72
|
+
|
|
73
|
+
// Products (optionally filtered by category)
|
|
74
|
+
const products = await gc.catalog.getProducts("genshin-impact", "crystals");
|
|
75
|
+
|
|
76
|
+
// Products grouped by category
|
|
77
|
+
const grouped = await gc.catalog.getProductsGrouped("genshin-impact");
|
|
78
|
+
|
|
79
|
+
// Search (returns games + products)
|
|
80
|
+
const { games, products } = await gc.catalog.search("roblox");
|
|
81
|
+
|
|
82
|
+
// Search suggestions
|
|
83
|
+
const { suggestions } = await gc.catalog.searchSuggestions("rob");
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Cart & Checkout
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Cart
|
|
90
|
+
const items = await gc.cart.get();
|
|
91
|
+
await gc.cart.add({ productId: 10, gameId: "roblox", gameName: "Roblox", productName: "800 Robux", price: 799, deliveryData: { username: "player123" } });
|
|
92
|
+
await gc.cart.remove(itemId);
|
|
93
|
+
await gc.cart.clear();
|
|
94
|
+
|
|
95
|
+
// Checkout (auto-generates idempotency key)
|
|
96
|
+
const checkout = await gc.checkout.create({
|
|
97
|
+
items: [{ productId: 10, deliveryData: { username: "player123" } }],
|
|
98
|
+
paymentMethod: "antilopay",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Redirect to payment page
|
|
102
|
+
if (checkout.payment?.paymentUrl) {
|
|
103
|
+
window.location.href = checkout.payment.paymentUrl;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Or pay with balance
|
|
107
|
+
await gc.checkout.completeWithBalance(checkout.payment.code);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Orders
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const orders = await gc.orders.list();
|
|
114
|
+
const order = await gc.orders.get("ORD-A7X9K2");
|
|
115
|
+
await gc.orders.cancel("ORD-A7X9K2");
|
|
116
|
+
|
|
117
|
+
// Track order in real-time (SSE)
|
|
118
|
+
const source = gc.sse.trackOrder("ORD-A7X9K2");
|
|
119
|
+
source.addEventListener("order_status", (e) => {
|
|
120
|
+
const data = JSON.parse(e.data);
|
|
121
|
+
console.log("Status:", data.status, "Items:", data.items);
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Profile
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Balance (permanent + bonus with expiration details)
|
|
129
|
+
const balance = await gc.profile.getBalance();
|
|
130
|
+
// { permanent: 500, bonus: 100, total: 600, bonusDetails: [{ remaining: 100, expiresAt: "..." }] }
|
|
131
|
+
|
|
132
|
+
// Level status with progress
|
|
133
|
+
const level = await gc.profile.getLevelStatus();
|
|
134
|
+
// { currentLevel: 3, currentDiscount: 5, nextLevel: 4, requirements: { spending: { current: 5000, required: 10000 } } }
|
|
135
|
+
|
|
136
|
+
// Transaction history
|
|
137
|
+
const transactions = await gc.profile.getTransactions({ limit: 20 });
|
|
138
|
+
|
|
139
|
+
// Orders
|
|
140
|
+
const orders = await gc.profile.getOrders();
|
|
141
|
+
|
|
142
|
+
// Notifications
|
|
143
|
+
const notifications = await gc.profile.getNotifications();
|
|
144
|
+
const { count } = await gc.profile.getUnreadCount();
|
|
145
|
+
await gc.profile.markRead(notificationId);
|
|
146
|
+
await gc.profile.markAllRead();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Favorites
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const favorites = await gc.favorites.list();
|
|
153
|
+
await gc.favorites.add(productId, "genshin-impact"); // gameId as slug string
|
|
154
|
+
await gc.favorites.remove(productId);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Reviews
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Public reviews (paginated)
|
|
161
|
+
const { data, pagination } = await gc.reviews.listPublic({ limit: 10 });
|
|
162
|
+
|
|
163
|
+
// Stats
|
|
164
|
+
const stats = await gc.reviews.getStats("genshin-impact");
|
|
165
|
+
// { averageRating: 4.8, totalReviews: 156 }
|
|
166
|
+
|
|
167
|
+
// Random reviews (for homepage)
|
|
168
|
+
const random = await gc.reviews.getRandom(5);
|
|
169
|
+
|
|
170
|
+
// Submit review (authenticated)
|
|
171
|
+
const review = await gc.reviews.create(orderId, 5, "Great service!");
|
|
172
|
+
|
|
173
|
+
// Orders waiting for review
|
|
174
|
+
const pending = await gc.reviews.getPending();
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Coupons & Gift Cards
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Apply coupon
|
|
181
|
+
const result = await gc.coupons.apply("WELCOME10");
|
|
182
|
+
// { type: "bonus_balance", value: 10, code: "WELCOME10", bonusAmount: 100 }
|
|
183
|
+
|
|
184
|
+
await gc.coupons.remove();
|
|
185
|
+
const active = await gc.coupons.getActive();
|
|
186
|
+
|
|
187
|
+
// Gift cards
|
|
188
|
+
const card = await gc.giftCards.purchase(5); // amountUsd
|
|
189
|
+
await gc.giftCards.redeem("GC-XXXX-XXXX-XXXX");
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Referrals
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const stats = await gc.referrals.getStats();
|
|
196
|
+
const links = await gc.referrals.getLinks();
|
|
197
|
+
const link = await gc.referrals.createLink({ label: "YouTube", slug: "my-channel" });
|
|
198
|
+
await gc.referrals.updateLink(link.id, { label: "Updated" });
|
|
199
|
+
const linkStats = await gc.referrals.getLinkStats(link.id);
|
|
200
|
+
const commissions = await gc.referrals.getCommissions();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Balance Top-up
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const methods = await gc.topup.getPaymentMethods();
|
|
207
|
+
const topup = await gc.topup.create(500, "lava");
|
|
208
|
+
// Redirect to topup.paymentUrl
|
|
209
|
+
const status = await gc.topup.getStatus(topup.code);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## SSE (Real-time Events)
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Authenticated notification stream
|
|
216
|
+
const events = gc.sse.connectEvents();
|
|
217
|
+
events.addEventListener("notification", (e) => {
|
|
218
|
+
const { type, data } = JSON.parse(e.data);
|
|
219
|
+
// type: "order_completed", "balance_updated", "level_up", etc.
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Order tracking (no auth, uses order code)
|
|
223
|
+
const tracker = gc.sse.trackOrder("ORD-A7X9K2");
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## SEO
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// entityId is numeric (canonical game ID), not slug
|
|
230
|
+
const seo = await gc.seo.getContent("game", 1076, "ru");
|
|
231
|
+
// Schema is available for "product" page type
|
|
232
|
+
const schema = await gc.seo.getSchema("product", 42);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Webhook Verification (Server-side)
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// Import from server entrypoint (uses node:crypto)
|
|
239
|
+
import { verifyWebhookSignature, parseWebhookPayload } from "@gamecore/sdk/server";
|
|
240
|
+
|
|
241
|
+
const isValid = verifyWebhookSignature(
|
|
242
|
+
requestBody,
|
|
243
|
+
request.headers["x-webhook-signature"],
|
|
244
|
+
WEBHOOK_SECRET,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (isValid) {
|
|
248
|
+
const payload = parseWebhookPayload(requestBody);
|
|
249
|
+
console.log(payload.event, payload.data);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Utilities
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { convertPrice, formatPrice, generateIdempotencyKey } from "@gamecore/sdk";
|
|
257
|
+
|
|
258
|
+
const rub = convertPrice(1.99, 92.5); // 184.08
|
|
259
|
+
const formatted = formatPrice(rub, "RUB"); // "184 ₽"
|
|
260
|
+
const key = generateIdempotencyKey(); // UUID v4
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Next.js App Router Examples
|
|
264
|
+
|
|
265
|
+
### Server Component (SSR catalog)
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// app/catalog/page.tsx
|
|
269
|
+
import { GameCoreClient } from "@gamecore/sdk";
|
|
270
|
+
|
|
271
|
+
const gc = new GameCoreClient({
|
|
272
|
+
apiKey: process.env.GAMECORE_API_KEY!,
|
|
273
|
+
baseUrl: process.env.GAMECORE_API_URL!,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
export default async function CatalogPage() {
|
|
277
|
+
const games = await gc.catalog.getGames();
|
|
278
|
+
return <GameGrid games={games} />;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Client Component (cart)
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
"use client";
|
|
286
|
+
import { useEffect, useState } from "react";
|
|
287
|
+
import { gc } from "@/lib/gamecore-browser";
|
|
288
|
+
|
|
289
|
+
export function CartWidget() {
|
|
290
|
+
const [items, setItems] = useState([]);
|
|
291
|
+
useEffect(() => { gc.cart.get().then(setItems); }, []);
|
|
292
|
+
return <span>{items.length} items</span>;
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Route Handler (webhook)
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// app/api/webhooks/gamecore/route.ts
|
|
300
|
+
import { verifyWebhookSignature, parseWebhookPayload } from "@gamecore/sdk/server";
|
|
301
|
+
|
|
302
|
+
export async function POST(req: Request) {
|
|
303
|
+
const body = await req.text();
|
|
304
|
+
const sig = req.headers.get("x-webhook-signature") || "";
|
|
305
|
+
|
|
306
|
+
if (!verifyWebhookSignature(body, sig, process.env.WEBHOOK_SECRET!)) {
|
|
307
|
+
return new Response("Unauthorized", { status: 401 });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const payload = parseWebhookPayload(body);
|
|
311
|
+
// Handle event...
|
|
312
|
+
return new Response("OK");
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## API Namespaces
|
|
317
|
+
|
|
318
|
+
| Namespace | Methods |
|
|
319
|
+
|-----------|---------|
|
|
320
|
+
| `gc.site` | getConfig, getRates, getLegal |
|
|
321
|
+
| `gc.auth` | initTelegram, pollTelegramStatus, verifyVk, getMe, logout, getIdentities, linkVk, unlinkProvider |
|
|
322
|
+
| `gc.catalog` | getGames, getHomepageGames, getGame, getProducts, getProductsGrouped, search, searchSuggestions, getProduct |
|
|
323
|
+
| `gc.cart` | get, add, sync, remove, clear |
|
|
324
|
+
| `gc.checkout` | create, completeWithBalance, getPaymentMethods |
|
|
325
|
+
| `gc.orders` | list, get, getByPayment, cancel |
|
|
326
|
+
| `gc.profile` | getBalance, getLevelStatus, getTransactions, getOrders, getNotifications, getUnreadCount, markRead, markAllRead |
|
|
327
|
+
| `gc.favorites` | list, add, remove |
|
|
328
|
+
| `gc.coupons` | apply, remove, validate, getActive |
|
|
329
|
+
| `gc.referrals` | getStats, getLinks, createLink, updateLink, deleteLink, getLinkStats, getCommissions |
|
|
330
|
+
| `gc.reviews` | listPublic, getStats, getRandom, getMine, getPending, create |
|
|
331
|
+
| `gc.topup` | getPaymentMethods, create, getStatus |
|
|
332
|
+
| `gc.giftCards` | purchase, redeem, check, getMine |
|
|
333
|
+
| `gc.announcements` | list, get |
|
|
334
|
+
| `gc.analytics` | recordView |
|
|
335
|
+
| `gc.seo` | getContent, getSchema |
|
|
336
|
+
| `gc.sse` | connectEvents, trackOrder |
|
|
337
|
+
|
|
338
|
+
## Browser vs Server
|
|
339
|
+
|
|
340
|
+
| Import | Environment | Includes |
|
|
341
|
+
|--------|-------------|----------|
|
|
342
|
+
| `@gamecore/sdk` | Browser + Node | Client, types, utilities |
|
|
343
|
+
| `@gamecore/sdk/server` | Node only | Webhook verification (uses node:crypto) |
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { TelegramInitResponse, User, SiteConfig, ExchangeRates, LegalDocument, Game, GameDetail, Product, SearchResult, Category, CartItem, CheckoutRequest, CheckoutResponse, Order, UserBalance, LevelStatus, Transaction, Notification, Favorite, Review, ReviewStats, CouponResult, ReferralStats, ReferralLink, ReferralCommission, TopupMethod, TopupResponse, TopupStatus, GiftCard, Announcement, PaginatedResponse } from "./types";
|
|
2
|
+
export interface GameCoreOptions {
|
|
3
|
+
/** Site API key (gc_live_xxx or gc_test_xxx) */
|
|
4
|
+
apiKey: string;
|
|
5
|
+
/** Base URL of GameCore API (e.g. https://api.gamecore-api.tech) */
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
/** Called on 401 — use to redirect to login */
|
|
8
|
+
onAuthError?: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare class GameCoreClient {
|
|
11
|
+
private apiKey;
|
|
12
|
+
private baseUrl;
|
|
13
|
+
private onAuthError?;
|
|
14
|
+
constructor(options: GameCoreOptions);
|
|
15
|
+
private request;
|
|
16
|
+
site: {
|
|
17
|
+
/** Get site configuration (modules, auth methods, payments, currency) */
|
|
18
|
+
getConfig: () => Promise<SiteConfig>;
|
|
19
|
+
/** Get current exchange rates (USD→RUB and others) */
|
|
20
|
+
getRates: () => Promise<ExchangeRates>;
|
|
21
|
+
/** Get legal document by type (privacy, terms, etc.) */
|
|
22
|
+
getLegal: (type: string, locale?: string) => Promise<LegalDocument>;
|
|
23
|
+
};
|
|
24
|
+
auth: {
|
|
25
|
+
/** Start Telegram auth flow → returns bot link for user to click */
|
|
26
|
+
initTelegram: () => Promise<TelegramInitResponse>;
|
|
27
|
+
/** Poll Telegram auth status until authenticated or expired */
|
|
28
|
+
pollTelegramStatus: (token: string, intervalMs?: number) => Promise<User>;
|
|
29
|
+
/** Verify VK access token and login/register */
|
|
30
|
+
verifyVk: (accessToken: string, ref?: string) => Promise<{
|
|
31
|
+
status: string;
|
|
32
|
+
user: User;
|
|
33
|
+
}>;
|
|
34
|
+
/** Get current authenticated user */
|
|
35
|
+
getMe: () => Promise<User>;
|
|
36
|
+
/** Logout and clear session */
|
|
37
|
+
logout: () => Promise<void>;
|
|
38
|
+
/** List linked auth providers (Telegram, VK) */
|
|
39
|
+
getIdentities: () => Promise<{
|
|
40
|
+
providers: Array<{
|
|
41
|
+
provider: string;
|
|
42
|
+
linked: boolean;
|
|
43
|
+
displayName: string;
|
|
44
|
+
}>;
|
|
45
|
+
}>;
|
|
46
|
+
/** Link VK account to current user */
|
|
47
|
+
linkVk: (accessToken: string) => Promise<void>;
|
|
48
|
+
/** Unlink auth provider */
|
|
49
|
+
unlinkProvider: (provider: string) => Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
catalog: {
|
|
52
|
+
/** Get all games with product counts */
|
|
53
|
+
getGames: (params?: {
|
|
54
|
+
locale?: string;
|
|
55
|
+
type?: string;
|
|
56
|
+
}) => Promise<Game[]>;
|
|
57
|
+
/** Get homepage ranked games */
|
|
58
|
+
getHomepageGames: () => Promise<Game[]>;
|
|
59
|
+
/** Get single game with categories */
|
|
60
|
+
getGame: (slug: string, locale?: string) => Promise<GameDetail>;
|
|
61
|
+
/** Get products for a game */
|
|
62
|
+
getProducts: (gameSlug: string, categorySlug?: string) => Promise<Product[]>;
|
|
63
|
+
/** Get products grouped by category */
|
|
64
|
+
getProductsGrouped: (gameSlug: string) => Promise<{
|
|
65
|
+
category: Category;
|
|
66
|
+
products: Product[];
|
|
67
|
+
}[]>;
|
|
68
|
+
/** Search catalog (returns both games and products) */
|
|
69
|
+
search: (query: string, limit?: number) => Promise<SearchResult>;
|
|
70
|
+
/** Get search suggestions */
|
|
71
|
+
searchSuggestions: (query: string, limit?: number) => Promise<{
|
|
72
|
+
suggestions: string[];
|
|
73
|
+
}>;
|
|
74
|
+
/** Get single product by ID */
|
|
75
|
+
getProduct: (productId: number) => Promise<Product>;
|
|
76
|
+
};
|
|
77
|
+
cart: {
|
|
78
|
+
/** Get cart items for authenticated user */
|
|
79
|
+
get: () => Promise<CartItem[]>;
|
|
80
|
+
/** Add single item to cart */
|
|
81
|
+
add: (item: Omit<CartItem, "id">) => Promise<CartItem>;
|
|
82
|
+
/** Sync localStorage cart to server (replaces server cart) */
|
|
83
|
+
sync: (items: Omit<CartItem, "id">[]) => Promise<CartItem[]>;
|
|
84
|
+
/** Remove item from cart by ID */
|
|
85
|
+
remove: (id: number) => Promise<void>;
|
|
86
|
+
/** Clear all cart items */
|
|
87
|
+
clear: () => Promise<void>;
|
|
88
|
+
};
|
|
89
|
+
checkout: {
|
|
90
|
+
/** Create checkout (payment + orders). Auto-generates idempotency key. */
|
|
91
|
+
create: (data: CheckoutRequest, idempotencyKey?: string) => Promise<CheckoutResponse>;
|
|
92
|
+
/** Complete payment with balance (no external gateway) */
|
|
93
|
+
completeWithBalance: (paymentCode: string) => Promise<{
|
|
94
|
+
success: boolean;
|
|
95
|
+
}>;
|
|
96
|
+
/** Get available payment methods for checkout */
|
|
97
|
+
getPaymentMethods: () => Promise<{
|
|
98
|
+
type: string;
|
|
99
|
+
label: string;
|
|
100
|
+
description?: string;
|
|
101
|
+
feePercent?: number;
|
|
102
|
+
gatewayType?: string;
|
|
103
|
+
}[]>;
|
|
104
|
+
};
|
|
105
|
+
orders: {
|
|
106
|
+
/** List all orders for authenticated user */
|
|
107
|
+
list: () => Promise<Order[]>;
|
|
108
|
+
/** Get single order by code (includes items + payment) */
|
|
109
|
+
get: (code: string) => Promise<Order>;
|
|
110
|
+
/** Get orders by payment code (route is under /checkout prefix) */
|
|
111
|
+
getByPayment: (paymentCode: string) => Promise<{
|
|
112
|
+
payment: unknown;
|
|
113
|
+
orders: Order[];
|
|
114
|
+
}>;
|
|
115
|
+
/** Cancel a pending order */
|
|
116
|
+
cancel: (code: string) => Promise<{
|
|
117
|
+
status: string;
|
|
118
|
+
}>;
|
|
119
|
+
};
|
|
120
|
+
profile: {
|
|
121
|
+
/** Get user balance (permanent + bonus with details) */
|
|
122
|
+
getBalance: () => Promise<UserBalance>;
|
|
123
|
+
/** Get level status with progress toward next level */
|
|
124
|
+
getLevelStatus: () => Promise<LevelStatus>;
|
|
125
|
+
/** Get balance transaction history */
|
|
126
|
+
getTransactions: (params?: {
|
|
127
|
+
limit?: number;
|
|
128
|
+
offset?: number;
|
|
129
|
+
}) => Promise<Transaction[]>;
|
|
130
|
+
/** Get user's orders */
|
|
131
|
+
getOrders: () => Promise<Order[]>;
|
|
132
|
+
/** Get notifications */
|
|
133
|
+
getNotifications: () => Promise<Notification[]>;
|
|
134
|
+
/** Get unread notification count */
|
|
135
|
+
getUnreadCount: () => Promise<{
|
|
136
|
+
count: number;
|
|
137
|
+
}>;
|
|
138
|
+
/** Mark notification as read */
|
|
139
|
+
markRead: (id: number) => Promise<void>;
|
|
140
|
+
/** Mark all notifications as read */
|
|
141
|
+
markAllRead: () => Promise<void>;
|
|
142
|
+
};
|
|
143
|
+
favorites: {
|
|
144
|
+
/** List user's favorite products */
|
|
145
|
+
list: () => Promise<Favorite[]>;
|
|
146
|
+
/** Add product to favorites */
|
|
147
|
+
add: (productId: number, gameId: string) => Promise<{
|
|
148
|
+
id: number;
|
|
149
|
+
}>;
|
|
150
|
+
/** Remove product from favorites */
|
|
151
|
+
remove: (productId: number) => Promise<void>;
|
|
152
|
+
};
|
|
153
|
+
coupons: {
|
|
154
|
+
/** Apply coupon code (sets active coupon for checkout) */
|
|
155
|
+
apply: (code: string) => Promise<CouponResult>;
|
|
156
|
+
/** Remove active coupon */
|
|
157
|
+
remove: () => Promise<void>;
|
|
158
|
+
/** Validate coupon without applying */
|
|
159
|
+
validate: (code: string, gameId?: string) => Promise<CouponResult>;
|
|
160
|
+
/** Get user's currently active coupon */
|
|
161
|
+
getActive: () => Promise<CouponResult | null>;
|
|
162
|
+
};
|
|
163
|
+
referrals: {
|
|
164
|
+
/** Get referral dashboard stats */
|
|
165
|
+
getStats: () => Promise<ReferralStats>;
|
|
166
|
+
/** List user's referral links */
|
|
167
|
+
getLinks: () => Promise<ReferralLink[]>;
|
|
168
|
+
/** Create new referral link */
|
|
169
|
+
createLink: (data?: {
|
|
170
|
+
label?: string;
|
|
171
|
+
slug?: string;
|
|
172
|
+
targetUrl?: string;
|
|
173
|
+
}) => Promise<ReferralLink>;
|
|
174
|
+
/** Update referral link */
|
|
175
|
+
updateLink: (id: number, data: {
|
|
176
|
+
label?: string;
|
|
177
|
+
slug?: string;
|
|
178
|
+
targetUrl?: string;
|
|
179
|
+
}) => Promise<void>;
|
|
180
|
+
/** Delete referral link */
|
|
181
|
+
deleteLink: (id: number) => Promise<void>;
|
|
182
|
+
/** Get stats for a specific link */
|
|
183
|
+
getLinkStats: (id: number) => Promise<{
|
|
184
|
+
link: ReferralLink;
|
|
185
|
+
commissions: ReferralCommission[];
|
|
186
|
+
totalCommission: number;
|
|
187
|
+
}>;
|
|
188
|
+
/** Get commission history */
|
|
189
|
+
getCommissions: () => Promise<ReferralCommission[]>;
|
|
190
|
+
};
|
|
191
|
+
reviews: {
|
|
192
|
+
/** Get public reviews (paginated) */
|
|
193
|
+
listPublic: (params?: {
|
|
194
|
+
gameSlug?: string;
|
|
195
|
+
limit?: number;
|
|
196
|
+
offset?: number;
|
|
197
|
+
}) => Promise<PaginatedResponse<Review>>;
|
|
198
|
+
/** Get review stats (average rating, total count) */
|
|
199
|
+
getStats: (gameSlug?: string) => Promise<ReviewStats>;
|
|
200
|
+
/** Get random reviews for homepage */
|
|
201
|
+
getRandom: (limit?: number) => Promise<Review[]>;
|
|
202
|
+
/** Get user's own reviews */
|
|
203
|
+
getMine: () => Promise<Review[]>;
|
|
204
|
+
/** Get orders eligible for review */
|
|
205
|
+
getPending: () => Promise<Order[]>;
|
|
206
|
+
/** Submit review for a completed order */
|
|
207
|
+
create: (orderId: number, rating: number, text?: string) => Promise<Review>;
|
|
208
|
+
};
|
|
209
|
+
topup: {
|
|
210
|
+
/** Get available topup payment methods */
|
|
211
|
+
getPaymentMethods: () => Promise<TopupMethod[]>;
|
|
212
|
+
/** Create topup (returns payment URL for redirect) */
|
|
213
|
+
create: (amount: number, paymentMethod?: string) => Promise<TopupResponse>;
|
|
214
|
+
/** Get topup status by code */
|
|
215
|
+
getStatus: (code: string) => Promise<TopupStatus>;
|
|
216
|
+
};
|
|
217
|
+
giftCards: {
|
|
218
|
+
/** Purchase a gift card (amount in USD) */
|
|
219
|
+
purchase: (amountUsd: number) => Promise<GiftCard>;
|
|
220
|
+
/** Redeem a gift card code */
|
|
221
|
+
redeem: (code: string) => Promise<{
|
|
222
|
+
amount: number;
|
|
223
|
+
}>;
|
|
224
|
+
/** Check gift card status */
|
|
225
|
+
check: (code: string) => Promise<GiftCard>;
|
|
226
|
+
/** Get user's purchased gift cards */
|
|
227
|
+
getMine: () => Promise<GiftCard[]>;
|
|
228
|
+
};
|
|
229
|
+
announcements: {
|
|
230
|
+
/** List published announcements */
|
|
231
|
+
list: (limit?: number) => Promise<Announcement[]>;
|
|
232
|
+
/** Get single announcement */
|
|
233
|
+
get: (id: number) => Promise<Announcement>;
|
|
234
|
+
};
|
|
235
|
+
sse: {
|
|
236
|
+
/** Connect to authenticated notification stream. Returns EventSource. */
|
|
237
|
+
connectEvents: () => EventSource;
|
|
238
|
+
/** Track order status by code (no auth required). Returns EventSource. */
|
|
239
|
+
trackOrder: (orderCode: string) => EventSource;
|
|
240
|
+
};
|
|
241
|
+
analytics: {
|
|
242
|
+
/** Record game page view (for ranking) */
|
|
243
|
+
recordView: (gameId: string) => Promise<void>;
|
|
244
|
+
};
|
|
245
|
+
seo: {
|
|
246
|
+
/** Get SEO content for a page */
|
|
247
|
+
getContent: (pageType: string, entityId: string | number, locale?: string) => Promise<Record<string, unknown>>;
|
|
248
|
+
/** Get schema.org JSON-LD for a page */
|
|
249
|
+
getSchema: (pageType: string, entityId: string | number) => Promise<Record<string, unknown>>;
|
|
250
|
+
};
|
|
251
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
class GameCoreError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(message, status, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "GameCoreError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/utils.ts
|
|
14
|
+
function convertPrice(priceUsd, rate) {
|
|
15
|
+
return Math.round(priceUsd * rate * 100) / 100;
|
|
16
|
+
}
|
|
17
|
+
function formatPrice(amount, currency = "RUB") {
|
|
18
|
+
if (currency === "RUB") {
|
|
19
|
+
const formatted = Math.round(amount).toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
|
20
|
+
return `${formatted} ₽`;
|
|
21
|
+
}
|
|
22
|
+
if (currency === "USD") {
|
|
23
|
+
return `$${amount.toFixed(2)}`;
|
|
24
|
+
}
|
|
25
|
+
if (currency === "EUR") {
|
|
26
|
+
return `€${amount.toFixed(2)}`;
|
|
27
|
+
}
|
|
28
|
+
return `${amount.toFixed(2)} ${currency}`;
|
|
29
|
+
}
|
|
30
|
+
function generateIdempotencyKey() {
|
|
31
|
+
return crypto.randomUUID();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/client.ts
|
|
35
|
+
class GameCoreClient {
|
|
36
|
+
apiKey;
|
|
37
|
+
baseUrl;
|
|
38
|
+
onAuthError;
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.apiKey = options.apiKey;
|
|
41
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
42
|
+
this.onAuthError = options.onAuthError;
|
|
43
|
+
}
|
|
44
|
+
async request(method, path, body, options) {
|
|
45
|
+
const headers = {
|
|
46
|
+
"X-Api-Key": this.apiKey,
|
|
47
|
+
"Content-Type": "application/json"
|
|
48
|
+
};
|
|
49
|
+
if (options?.idempotencyKey) {
|
|
50
|
+
headers["X-Idempotency-Key"] = options.idempotencyKey;
|
|
51
|
+
}
|
|
52
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
53
|
+
method,
|
|
54
|
+
headers,
|
|
55
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
56
|
+
credentials: "include"
|
|
57
|
+
});
|
|
58
|
+
if (res.status === 401) {
|
|
59
|
+
this.onAuthError?.();
|
|
60
|
+
throw new GameCoreError("Unauthorized", 401, "UNAUTHORIZED");
|
|
61
|
+
}
|
|
62
|
+
if (res.status === 429) {
|
|
63
|
+
throw new GameCoreError("Rate limit exceeded", 429, "RATE_LIMITED");
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const data = await res.json().catch(() => ({}));
|
|
67
|
+
throw new GameCoreError(data.error || `HTTP ${res.status}`, res.status, data.code);
|
|
68
|
+
}
|
|
69
|
+
const json = await res.json();
|
|
70
|
+
if (!options?.rawResponse && json && typeof json === "object" && "success" in json && "data" in json) {
|
|
71
|
+
return json.data;
|
|
72
|
+
}
|
|
73
|
+
return json;
|
|
74
|
+
}
|
|
75
|
+
site = {
|
|
76
|
+
getConfig: () => this.request("GET", "/site/config"),
|
|
77
|
+
getRates: () => this.request("GET", "/rates"),
|
|
78
|
+
getLegal: (type, locale) => this.request("GET", `/legal/${type}${locale ? `?locale=${locale}` : ""}`)
|
|
79
|
+
};
|
|
80
|
+
auth = {
|
|
81
|
+
initTelegram: () => this.request("POST", "/auth/telegram/init"),
|
|
82
|
+
pollTelegramStatus: (token, intervalMs = 2000) => new Promise((resolve, reject) => {
|
|
83
|
+
const poll = setInterval(async () => {
|
|
84
|
+
try {
|
|
85
|
+
const res = await this.request("GET", `/auth/telegram/status/${token}`, undefined, { rawResponse: true });
|
|
86
|
+
if (res.status === "authenticated" && res.user) {
|
|
87
|
+
clearInterval(poll);
|
|
88
|
+
resolve(res.user);
|
|
89
|
+
} else if (res.status === "expired" || res.status === "used") {
|
|
90
|
+
clearInterval(poll);
|
|
91
|
+
reject(new GameCoreError("Auth token expired", 410, "TOKEN_EXPIRED"));
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
clearInterval(poll);
|
|
95
|
+
reject(err);
|
|
96
|
+
}
|
|
97
|
+
}, intervalMs);
|
|
98
|
+
}),
|
|
99
|
+
verifyVk: (accessToken, ref) => this.request("POST", "/auth/vk/verify", { accessToken, ref }, { rawResponse: true }),
|
|
100
|
+
getMe: () => this.request("GET", "/auth/me", undefined, { rawResponse: true }),
|
|
101
|
+
logout: () => this.request("POST", "/auth/logout"),
|
|
102
|
+
getIdentities: () => this.request("GET", "/auth/identities", undefined, { rawResponse: true }),
|
|
103
|
+
linkVk: (accessToken) => this.request("POST", "/auth/link/vk", { accessToken }),
|
|
104
|
+
unlinkProvider: (provider) => this.request("POST", `/auth/unlink/${provider}`)
|
|
105
|
+
};
|
|
106
|
+
catalog = {
|
|
107
|
+
getGames: (params) => {
|
|
108
|
+
const qs = new URLSearchParams;
|
|
109
|
+
if (params?.locale)
|
|
110
|
+
qs.set("locale", params.locale);
|
|
111
|
+
if (params?.type)
|
|
112
|
+
qs.set("type", params.type);
|
|
113
|
+
const q = qs.toString();
|
|
114
|
+
return this.request("GET", `/catalog/games${q ? `?${q}` : ""}`);
|
|
115
|
+
},
|
|
116
|
+
getHomepageGames: () => this.request("GET", "/catalog/homepage-games"),
|
|
117
|
+
getGame: (slug, locale) => {
|
|
118
|
+
const qs = locale ? `?locale=${locale}` : "";
|
|
119
|
+
return this.request("GET", `/catalog/games/${slug}${qs}`);
|
|
120
|
+
},
|
|
121
|
+
getProducts: (gameSlug, categorySlug) => {
|
|
122
|
+
const qs = categorySlug ? `?category=${categorySlug}` : "";
|
|
123
|
+
return this.request("GET", `/catalog/games/${gameSlug}/products${qs}`);
|
|
124
|
+
},
|
|
125
|
+
getProductsGrouped: (gameSlug) => this.request("GET", `/catalog/games/${gameSlug}/products/grouped`),
|
|
126
|
+
search: (query, limit = 20) => this.request("GET", `/catalog/search?q=${encodeURIComponent(query)}&limit=${limit}`),
|
|
127
|
+
searchSuggestions: (query, limit = 5) => this.request("GET", `/catalog/search/suggestions?q=${encodeURIComponent(query)}&limit=${limit}`),
|
|
128
|
+
getProduct: (productId) => this.request("GET", `/catalog/products/${productId}`)
|
|
129
|
+
};
|
|
130
|
+
cart = {
|
|
131
|
+
get: () => this.request("GET", "/cart"),
|
|
132
|
+
add: (item) => this.request("POST", "/cart", item),
|
|
133
|
+
sync: (items) => this.request("POST", "/cart/sync", { items }),
|
|
134
|
+
remove: (id) => this.request("DELETE", `/cart/${id}`),
|
|
135
|
+
clear: () => this.request("DELETE", "/cart")
|
|
136
|
+
};
|
|
137
|
+
checkout = {
|
|
138
|
+
create: (data, idempotencyKey) => this.request("POST", "/checkout", data, {
|
|
139
|
+
idempotencyKey: idempotencyKey || generateIdempotencyKey()
|
|
140
|
+
}),
|
|
141
|
+
completeWithBalance: (paymentCode) => this.request("POST", `/checkout/${paymentCode}/complete`),
|
|
142
|
+
getPaymentMethods: () => this.request("GET", "/payment-methods")
|
|
143
|
+
};
|
|
144
|
+
orders = {
|
|
145
|
+
list: () => this.request("GET", "/orders"),
|
|
146
|
+
get: (code) => this.request("GET", `/orders/${code}`),
|
|
147
|
+
getByPayment: (paymentCode) => this.request("GET", `/checkout/orders/payment/${paymentCode}`, undefined, { rawResponse: true }),
|
|
148
|
+
cancel: (code) => this.request("POST", `/orders/${code}/cancel`)
|
|
149
|
+
};
|
|
150
|
+
profile = {
|
|
151
|
+
getBalance: () => this.request("GET", "/profile/balance"),
|
|
152
|
+
getLevelStatus: () => this.request("GET", "/profile/level-status"),
|
|
153
|
+
getTransactions: (params) => {
|
|
154
|
+
const qs = new URLSearchParams;
|
|
155
|
+
if (params?.limit)
|
|
156
|
+
qs.set("limit", String(params.limit));
|
|
157
|
+
if (params?.offset)
|
|
158
|
+
qs.set("offset", String(params.offset));
|
|
159
|
+
const q = qs.toString();
|
|
160
|
+
return this.request("GET", `/profile/balance/transactions${q ? `?${q}` : ""}`);
|
|
161
|
+
},
|
|
162
|
+
getOrders: () => this.request("GET", "/profile/orders"),
|
|
163
|
+
getNotifications: () => this.request("GET", "/profile/notifications"),
|
|
164
|
+
getUnreadCount: () => this.request("GET", "/profile/notifications/unread-count"),
|
|
165
|
+
markRead: (id) => this.request("POST", `/profile/notifications/${id}/read`),
|
|
166
|
+
markAllRead: () => this.request("POST", "/profile/notifications/read-all")
|
|
167
|
+
};
|
|
168
|
+
favorites = {
|
|
169
|
+
list: () => this.request("GET", "/favorites"),
|
|
170
|
+
add: (productId, gameId) => this.request("POST", "/favorites", { productId, gameId }),
|
|
171
|
+
remove: (productId) => this.request("DELETE", `/favorites/${productId}`)
|
|
172
|
+
};
|
|
173
|
+
coupons = {
|
|
174
|
+
apply: (code) => this.request("POST", "/coupons/apply", { code }),
|
|
175
|
+
remove: () => this.request("DELETE", "/coupons/active"),
|
|
176
|
+
validate: (code, gameId) => this.request("POST", "/coupons/validate", { code, gameId }),
|
|
177
|
+
getActive: () => this.request("GET", "/profile/active-coupon")
|
|
178
|
+
};
|
|
179
|
+
referrals = {
|
|
180
|
+
getStats: () => this.request("GET", "/referral/stats"),
|
|
181
|
+
getLinks: () => this.request("GET", "/referral/links"),
|
|
182
|
+
createLink: (data) => this.request("POST", "/referral/links", data),
|
|
183
|
+
updateLink: (id, data) => this.request("PATCH", `/referral/links/${id}`, data),
|
|
184
|
+
deleteLink: (id) => this.request("DELETE", `/referral/links/${id}`),
|
|
185
|
+
getLinkStats: (id) => this.request("GET", `/referral/links/${id}/stats`),
|
|
186
|
+
getCommissions: () => this.request("GET", "/referral/commissions")
|
|
187
|
+
};
|
|
188
|
+
reviews = {
|
|
189
|
+
listPublic: (params) => {
|
|
190
|
+
const qs = new URLSearchParams;
|
|
191
|
+
if (params?.gameSlug)
|
|
192
|
+
qs.set("gameSlug", params.gameSlug);
|
|
193
|
+
if (params?.limit)
|
|
194
|
+
qs.set("limit", String(params.limit));
|
|
195
|
+
if (params?.offset)
|
|
196
|
+
qs.set("offset", String(params.offset));
|
|
197
|
+
const q = qs.toString();
|
|
198
|
+
return this.request("GET", `/reviews/public${q ? `?${q}` : ""}`, undefined, { rawResponse: true });
|
|
199
|
+
},
|
|
200
|
+
getStats: (gameSlug) => {
|
|
201
|
+
const qs = gameSlug ? `?gameSlug=${gameSlug}` : "";
|
|
202
|
+
return this.request("GET", `/reviews/public/stats${qs}`);
|
|
203
|
+
},
|
|
204
|
+
getRandom: (limit = 5) => this.request("GET", `/reviews/public/random?limit=${limit}`),
|
|
205
|
+
getMine: () => this.request("GET", "/reviews/mine"),
|
|
206
|
+
getPending: () => this.request("GET", "/reviews/orders-without-reviews"),
|
|
207
|
+
create: (orderId, rating, text) => this.request("POST", "/reviews", { orderId, rating, text })
|
|
208
|
+
};
|
|
209
|
+
topup = {
|
|
210
|
+
getPaymentMethods: () => this.request("GET", "/topup/payment-methods"),
|
|
211
|
+
create: (amount, paymentMethod) => this.request("POST", "/topup", { amount, paymentMethod }),
|
|
212
|
+
getStatus: (code) => this.request("GET", `/topup/${code}`)
|
|
213
|
+
};
|
|
214
|
+
giftCards = {
|
|
215
|
+
purchase: (amountUsd) => this.request("POST", "/gift-cards/purchase", { amountUsd }),
|
|
216
|
+
redeem: (code) => this.request("POST", "/gift-cards/redeem", { code }),
|
|
217
|
+
check: (code) => this.request("GET", `/gift-cards/check?code=${code}`),
|
|
218
|
+
getMine: () => this.request("GET", "/profile/gift-cards")
|
|
219
|
+
};
|
|
220
|
+
announcements = {
|
|
221
|
+
list: (limit = 10) => this.request("GET", `/announcements?limit=${limit}`),
|
|
222
|
+
get: (id) => this.request("GET", `/announcements/${id}`)
|
|
223
|
+
};
|
|
224
|
+
sse = {
|
|
225
|
+
connectEvents: () => new EventSource(`${this.baseUrl}/sse/events`, { withCredentials: true }),
|
|
226
|
+
trackOrder: (orderCode) => new EventSource(`${this.baseUrl}/sse/orders/${orderCode}`)
|
|
227
|
+
};
|
|
228
|
+
analytics = {
|
|
229
|
+
recordView: (gameId) => this.request("POST", "/analytics/game-view", { gameId })
|
|
230
|
+
};
|
|
231
|
+
seo = {
|
|
232
|
+
getContent: (pageType, entityId, locale) => {
|
|
233
|
+
const qs = locale ? `?locale=${locale}` : "";
|
|
234
|
+
return this.request("GET", `/seo/${pageType}/${entityId}${qs}`, undefined, { rawResponse: true });
|
|
235
|
+
},
|
|
236
|
+
getSchema: (pageType, entityId) => this.request("GET", `/seo/schema/${pageType}/${entityId}`, undefined, { rawResponse: true })
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
export {
|
|
240
|
+
generateIdempotencyKey,
|
|
241
|
+
formatPrice,
|
|
242
|
+
convertPrice,
|
|
243
|
+
GameCoreError,
|
|
244
|
+
GameCoreClient
|
|
245
|
+
};
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/webhooks.ts
|
|
2
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
|
+
function verifyWebhookSignature(payload, signature, secret, maxAgeSeconds = 300) {
|
|
4
|
+
const expected = `sha256=${createHmac("sha256", secret).update(payload).digest("hex")}`;
|
|
5
|
+
const sigBuf = Buffer.from(signature);
|
|
6
|
+
const expectedBuf = Buffer.from(expected);
|
|
7
|
+
if (sigBuf.length !== expectedBuf.length)
|
|
8
|
+
return false;
|
|
9
|
+
if (!timingSafeEqual(sigBuf, expectedBuf))
|
|
10
|
+
return false;
|
|
11
|
+
if (maxAgeSeconds > 0) {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(payload);
|
|
14
|
+
if (parsed.timestamp) {
|
|
15
|
+
const age = (Date.now() - new Date(parsed.timestamp).getTime()) / 1000;
|
|
16
|
+
if (age > maxAgeSeconds)
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function parseWebhookPayload(body) {
|
|
24
|
+
return JSON.parse(body);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
verifyWebhookSignature,
|
|
28
|
+
parseWebhookPayload
|
|
29
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
export interface TelegramInitResponse {
|
|
2
|
+
success: boolean;
|
|
3
|
+
token: string;
|
|
4
|
+
botLink: string;
|
|
5
|
+
expiresIn: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AuthStatusResponse {
|
|
8
|
+
status: "pending" | "verified" | "authenticated" | "expired" | "used";
|
|
9
|
+
user?: User;
|
|
10
|
+
}
|
|
11
|
+
export interface User {
|
|
12
|
+
id: number;
|
|
13
|
+
firstName: string;
|
|
14
|
+
lastName?: string;
|
|
15
|
+
username?: string;
|
|
16
|
+
photoUrl?: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
role: string;
|
|
19
|
+
level: number;
|
|
20
|
+
telegramId?: string;
|
|
21
|
+
vkId?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SiteConfig {
|
|
24
|
+
apiVersion: string;
|
|
25
|
+
site: {
|
|
26
|
+
slug: string;
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
modules: Record<string, boolean>;
|
|
30
|
+
auth: string[];
|
|
31
|
+
payments: string[];
|
|
32
|
+
displayCurrency: string;
|
|
33
|
+
rateMode: "auto" | "manual";
|
|
34
|
+
currentRate: number;
|
|
35
|
+
supportedLocales?: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface ExchangeRates {
|
|
38
|
+
usdToRub: number;
|
|
39
|
+
base: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
rates: Record<string, number>;
|
|
42
|
+
}
|
|
43
|
+
export interface LegalDocument {
|
|
44
|
+
title: string;
|
|
45
|
+
content: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
locale?: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
}
|
|
50
|
+
export interface Game {
|
|
51
|
+
id: number;
|
|
52
|
+
slug: string;
|
|
53
|
+
name: string;
|
|
54
|
+
icon: string | null;
|
|
55
|
+
localIcon?: string | null;
|
|
56
|
+
productCount: number;
|
|
57
|
+
minPrice?: number | null;
|
|
58
|
+
type?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface GameDetail {
|
|
61
|
+
id: number;
|
|
62
|
+
slug: string;
|
|
63
|
+
name: string;
|
|
64
|
+
icon: string | null;
|
|
65
|
+
localIcon?: string | null;
|
|
66
|
+
description: string | null;
|
|
67
|
+
categories: Category[];
|
|
68
|
+
}
|
|
69
|
+
export interface Category {
|
|
70
|
+
id: number;
|
|
71
|
+
slug: string;
|
|
72
|
+
name: string;
|
|
73
|
+
productCount: number;
|
|
74
|
+
}
|
|
75
|
+
export interface Product {
|
|
76
|
+
id: number;
|
|
77
|
+
name: string;
|
|
78
|
+
icon: string | null;
|
|
79
|
+
price: number | null;
|
|
80
|
+
priceWithoutDiscount?: number;
|
|
81
|
+
discountPercent?: number;
|
|
82
|
+
gameId: string;
|
|
83
|
+
gameName: string;
|
|
84
|
+
gameIcon: string | null;
|
|
85
|
+
categoryId: number;
|
|
86
|
+
categoryName: string;
|
|
87
|
+
productType: string;
|
|
88
|
+
deliveryDataSchema: unknown[];
|
|
89
|
+
region?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface SearchResult {
|
|
92
|
+
games: Game[];
|
|
93
|
+
products: Product[];
|
|
94
|
+
}
|
|
95
|
+
export interface CartItem {
|
|
96
|
+
id?: number;
|
|
97
|
+
productId: number;
|
|
98
|
+
gameId: string;
|
|
99
|
+
gameName: string;
|
|
100
|
+
productName: string;
|
|
101
|
+
price: number;
|
|
102
|
+
deliveryData: Record<string, string>;
|
|
103
|
+
}
|
|
104
|
+
export interface CheckoutRequest {
|
|
105
|
+
email?: string;
|
|
106
|
+
items: Array<{
|
|
107
|
+
productId: number;
|
|
108
|
+
gameId?: string;
|
|
109
|
+
gameName?: string;
|
|
110
|
+
productName?: string;
|
|
111
|
+
amount?: number;
|
|
112
|
+
deliveryData?: Record<string, string>;
|
|
113
|
+
}>;
|
|
114
|
+
paymentMethod?: string;
|
|
115
|
+
couponCode?: string;
|
|
116
|
+
}
|
|
117
|
+
export interface CheckoutResponse {
|
|
118
|
+
payment?: {
|
|
119
|
+
code: string;
|
|
120
|
+
total: number;
|
|
121
|
+
paymentUrl?: string;
|
|
122
|
+
status?: string;
|
|
123
|
+
};
|
|
124
|
+
orders?: Array<{
|
|
125
|
+
code: string;
|
|
126
|
+
status: string;
|
|
127
|
+
}>;
|
|
128
|
+
}
|
|
129
|
+
export type OrderStatus = "pending" | "paid" | "processing" | "completed" | "cancelled" | "refunded" | "failed";
|
|
130
|
+
export interface Order {
|
|
131
|
+
id: number;
|
|
132
|
+
code: string;
|
|
133
|
+
status: OrderStatus;
|
|
134
|
+
totalAmount: number;
|
|
135
|
+
gameName: string;
|
|
136
|
+
gameId: string;
|
|
137
|
+
paymentCode?: string;
|
|
138
|
+
items: OrderItem[];
|
|
139
|
+
payment?: {
|
|
140
|
+
code: string;
|
|
141
|
+
status: string;
|
|
142
|
+
totalAmount: number;
|
|
143
|
+
};
|
|
144
|
+
createdAt: string;
|
|
145
|
+
completedAt?: string;
|
|
146
|
+
}
|
|
147
|
+
export interface OrderItem {
|
|
148
|
+
id: number;
|
|
149
|
+
productId: number;
|
|
150
|
+
productName: string;
|
|
151
|
+
gameName: string;
|
|
152
|
+
price: number;
|
|
153
|
+
amount: number;
|
|
154
|
+
status?: string;
|
|
155
|
+
supplierStatus?: string;
|
|
156
|
+
cdKey?: string;
|
|
157
|
+
deliveryData: Record<string, unknown>;
|
|
158
|
+
}
|
|
159
|
+
export interface UserBalance {
|
|
160
|
+
permanent: number;
|
|
161
|
+
bonus: number;
|
|
162
|
+
total: number;
|
|
163
|
+
level: number;
|
|
164
|
+
levelDiscount: number;
|
|
165
|
+
totalSpent: number;
|
|
166
|
+
bonusDetails: Array<{
|
|
167
|
+
id: number;
|
|
168
|
+
remaining: number;
|
|
169
|
+
expiresAt: string;
|
|
170
|
+
}>;
|
|
171
|
+
}
|
|
172
|
+
export interface LevelStatus {
|
|
173
|
+
currentLevel: number;
|
|
174
|
+
currentDiscount: number;
|
|
175
|
+
nextLevel: number | null;
|
|
176
|
+
nextDiscount: number | null;
|
|
177
|
+
isMaxLevel: boolean;
|
|
178
|
+
requirements?: {
|
|
179
|
+
spending?: {
|
|
180
|
+
current: number;
|
|
181
|
+
required: number;
|
|
182
|
+
met: boolean;
|
|
183
|
+
};
|
|
184
|
+
reviews?: {
|
|
185
|
+
current: number;
|
|
186
|
+
required: number;
|
|
187
|
+
met: boolean;
|
|
188
|
+
};
|
|
189
|
+
referrals?: {
|
|
190
|
+
current: number;
|
|
191
|
+
required: number;
|
|
192
|
+
met: boolean;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export interface Transaction {
|
|
197
|
+
id: number;
|
|
198
|
+
type: string;
|
|
199
|
+
amount: number;
|
|
200
|
+
description: string;
|
|
201
|
+
createdAt: string;
|
|
202
|
+
}
|
|
203
|
+
export interface Notification {
|
|
204
|
+
id: number;
|
|
205
|
+
type: string;
|
|
206
|
+
title: string;
|
|
207
|
+
body: string;
|
|
208
|
+
isRead: boolean;
|
|
209
|
+
data?: Record<string, unknown>;
|
|
210
|
+
createdAt: string;
|
|
211
|
+
}
|
|
212
|
+
export interface Favorite {
|
|
213
|
+
id: number;
|
|
214
|
+
productId: number;
|
|
215
|
+
productName: string;
|
|
216
|
+
productSlug?: string;
|
|
217
|
+
productIcon?: string | null;
|
|
218
|
+
price: number;
|
|
219
|
+
gameId: string;
|
|
220
|
+
gameSlug?: string;
|
|
221
|
+
gameName: string;
|
|
222
|
+
gameIcon?: string | null;
|
|
223
|
+
categorySlug?: string;
|
|
224
|
+
createdAt: string;
|
|
225
|
+
}
|
|
226
|
+
export interface Review {
|
|
227
|
+
id: number;
|
|
228
|
+
userId: number;
|
|
229
|
+
userName?: string;
|
|
230
|
+
orderId: number;
|
|
231
|
+
orderCode?: string;
|
|
232
|
+
rating: number;
|
|
233
|
+
text?: string;
|
|
234
|
+
gameName?: string;
|
|
235
|
+
bonus?: {
|
|
236
|
+
amount: number;
|
|
237
|
+
percent: number;
|
|
238
|
+
expiresAt: string;
|
|
239
|
+
};
|
|
240
|
+
createdAt: string;
|
|
241
|
+
}
|
|
242
|
+
export interface ReviewStats {
|
|
243
|
+
averageRating: number;
|
|
244
|
+
totalCount: number;
|
|
245
|
+
distribution?: Record<string, number>;
|
|
246
|
+
}
|
|
247
|
+
export interface CouponResult {
|
|
248
|
+
type: string;
|
|
249
|
+
value: number;
|
|
250
|
+
code: string;
|
|
251
|
+
gameId?: number | null;
|
|
252
|
+
bonusAmount?: number;
|
|
253
|
+
}
|
|
254
|
+
export interface ReferralStats {
|
|
255
|
+
totalReferrals: number;
|
|
256
|
+
lifetimeEarnings: number;
|
|
257
|
+
commissionRate: number;
|
|
258
|
+
commissionBasis: string;
|
|
259
|
+
links?: ReferralLink[];
|
|
260
|
+
}
|
|
261
|
+
export interface ReferralLink {
|
|
262
|
+
id: number;
|
|
263
|
+
code: string;
|
|
264
|
+
slug?: string | null;
|
|
265
|
+
label?: string;
|
|
266
|
+
targetUrl?: string | null;
|
|
267
|
+
clicks: number;
|
|
268
|
+
registrations: number;
|
|
269
|
+
url?: string;
|
|
270
|
+
createdAt: string;
|
|
271
|
+
}
|
|
272
|
+
export interface ReferralCommission {
|
|
273
|
+
id: number;
|
|
274
|
+
referredUserId: number;
|
|
275
|
+
orderId: number;
|
|
276
|
+
orderAmount: number;
|
|
277
|
+
commissionAmount: number;
|
|
278
|
+
commissionPercent: number;
|
|
279
|
+
createdAt: string;
|
|
280
|
+
}
|
|
281
|
+
export interface TopupMethod {
|
|
282
|
+
type: string;
|
|
283
|
+
label: string;
|
|
284
|
+
description?: string;
|
|
285
|
+
feePercent?: number;
|
|
286
|
+
gatewayType?: string;
|
|
287
|
+
invoiceCurrency?: string;
|
|
288
|
+
}
|
|
289
|
+
export interface TopupResponse {
|
|
290
|
+
code: string;
|
|
291
|
+
amount: number;
|
|
292
|
+
paymentUrl?: string;
|
|
293
|
+
}
|
|
294
|
+
export interface TopupStatus {
|
|
295
|
+
code: string;
|
|
296
|
+
status: string;
|
|
297
|
+
amount: number;
|
|
298
|
+
createdAt?: string;
|
|
299
|
+
}
|
|
300
|
+
export interface GiftCard {
|
|
301
|
+
id: number;
|
|
302
|
+
code: string;
|
|
303
|
+
denomination: number;
|
|
304
|
+
status: string;
|
|
305
|
+
createdAt: string;
|
|
306
|
+
redeemedAt?: string;
|
|
307
|
+
}
|
|
308
|
+
export interface Announcement {
|
|
309
|
+
id: number;
|
|
310
|
+
title: string;
|
|
311
|
+
body: string;
|
|
312
|
+
type?: string;
|
|
313
|
+
imageUrl?: string | null;
|
|
314
|
+
createdAt: string;
|
|
315
|
+
}
|
|
316
|
+
export interface OrderUpdateEvent {
|
|
317
|
+
orderId: number;
|
|
318
|
+
orderCode: string;
|
|
319
|
+
status: string;
|
|
320
|
+
items?: Array<{
|
|
321
|
+
productName: string;
|
|
322
|
+
status: string;
|
|
323
|
+
cdKey?: string;
|
|
324
|
+
}>;
|
|
325
|
+
}
|
|
326
|
+
export type WebhookEvent = "order.created" | "order.completed" | "order.failed" | "order.cancelled" | "payment.received" | "payment.failed" | "user.registered" | "catalog.updated";
|
|
327
|
+
export interface WebhookPayload {
|
|
328
|
+
event: WebhookEvent;
|
|
329
|
+
timestamp: string;
|
|
330
|
+
siteId: number;
|
|
331
|
+
sandbox: boolean;
|
|
332
|
+
data: Record<string, unknown>;
|
|
333
|
+
}
|
|
334
|
+
/** Standard API response wrapper. Most endpoints return { success, data } or { success, error }. */
|
|
335
|
+
export interface ApiResponse<T = unknown> {
|
|
336
|
+
success: boolean;
|
|
337
|
+
data?: T;
|
|
338
|
+
error?: string;
|
|
339
|
+
}
|
|
340
|
+
export interface PaginatedResponse<T> {
|
|
341
|
+
data: T[];
|
|
342
|
+
pagination: {
|
|
343
|
+
limit: number;
|
|
344
|
+
offset: number;
|
|
345
|
+
total: number;
|
|
346
|
+
hasMore: boolean;
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
export declare class GameCoreError extends Error {
|
|
350
|
+
status: number;
|
|
351
|
+
code?: string;
|
|
352
|
+
constructor(message: string, status: number, code?: string);
|
|
353
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a price from USD to target currency.
|
|
3
|
+
*/
|
|
4
|
+
export declare function convertPrice(priceUsd: number, rate: number): number;
|
|
5
|
+
/**
|
|
6
|
+
* Format a price with currency symbol.
|
|
7
|
+
* Examples: "1 234 ₽", "$12.34", "€12.34"
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatPrice(amount: number, currency?: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Generate a unique idempotency key for checkout requests.
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateIdempotencyKey(): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook verification utilities — SERVER-SIDE ONLY.
|
|
3
|
+
* Uses node:crypto, do not import in browser bundles.
|
|
4
|
+
*
|
|
5
|
+
* Import from "@gamecore/sdk/server" instead of "@gamecore/sdk".
|
|
6
|
+
*/
|
|
7
|
+
import type { WebhookPayload } from "./types";
|
|
8
|
+
/**
|
|
9
|
+
* Verify webhook signature (HMAC-SHA256) with timing-safe comparison.
|
|
10
|
+
* Use on your server to validate incoming webhooks from GameCore.
|
|
11
|
+
*
|
|
12
|
+
* @param payload - Raw request body string
|
|
13
|
+
* @param signature - Value of X-Webhook-Signature header
|
|
14
|
+
* @param secret - Your webhook secret from site settings
|
|
15
|
+
* @param maxAgeSeconds - Max age of webhook (default 300 = 5 minutes). Set to 0 to disable.
|
|
16
|
+
*/
|
|
17
|
+
export declare function verifyWebhookSignature(payload: string, signature: string, secret: string, maxAgeSeconds?: number): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Parse a webhook payload string into a typed object.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseWebhookPayload(body: string): WebhookPayload;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gamecore-api/sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TypeScript SDK for GameCore API — browser-safe, zero dependencies",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"import": "./dist/server.js",
|
|
16
|
+
"require": "./dist/server.js",
|
|
17
|
+
"types": "./dist/server.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist"],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "bun build src/index.ts --outdir dist --target browser && bun build src/server.ts --outdir dist --target node && bun x tsc --emitDeclarationOnly",
|
|
23
|
+
"typecheck": "bun x tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.5.0",
|
|
27
|
+
"typescript": "^5.0.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": ["gamecore", "game-donation", "sdk", "api-client", "typescript"],
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|