@skalpgg/sdk 1.4.0 → 1.6.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 +13 -2
- package/dist/cjs/index.d.ts +4 -3
- package/dist/cjs/index.js +48 -6
- package/dist/cjs/types.d.ts +50 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.js +48 -6
- package/dist/esm/types.d.ts +50 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,11 +43,12 @@ Create the key on the API keys page of your dashboard. Each key is bound to one
|
|
|
43
43
|
| --- | --- |
|
|
44
44
|
| `checkout.create({ productId, quantity?, customerEmail?, metadata?, couponCode?, returnUrl?, customFields? })` | `checkout:write` |
|
|
45
45
|
| `checkout.get(id)` | `checkout:read` |
|
|
46
|
-
| `orders.list({ status?, productId?, email?, createdAfter?, createdBefore?, updatedSince?, limit?, offset? })` | `orders:read` |
|
|
46
|
+
| `orders.list({ status?, productId?, email?, identifier?, createdAfter?, createdBefore?, updatedSince?, limit?, offset? })` | `orders:read` |
|
|
47
47
|
| `orders.get(id)` | `orders:read` |
|
|
48
48
|
| `orders.cancel(id)` | `orders:write` |
|
|
49
|
+
| `orders.redeliver(id)` | `orders:write` |
|
|
49
50
|
| `orders.replace(id, { serialIds?, values?, reason? })` | `orders:write` |
|
|
50
|
-
| `products.list({ status? })` | `products:read` |
|
|
51
|
+
| `products.list({ status?, limit?, offset? })` | `products:read` |
|
|
51
52
|
| `products.get(idOrSlug)` | `products:read` |
|
|
52
53
|
| `products.create({ name, price, fulfillmentType?, submit?, ... })` | `products:write` |
|
|
53
54
|
| `products.update(idOrSlug, { price?, status?: 'DRAFT' \| 'ARCHIVED', ... })` | `products:write` |
|
|
@@ -108,6 +109,8 @@ await skalp.products.submit(product.id)
|
|
|
108
109
|
|
|
109
110
|
A product starts as a `DRAFT`. `submit` sends it to review, and the review sets `ACTIVE` or `REJECTED`; read the product to see which. `update` accepts only `DRAFT` and `ARCHIVED` as a `status`, and `archive` takes a product off sale without deleting anything. Pass `null` to `update` to clear `comparePrice`, `storeCategoryId`, `dynamicUrl` or `licenseDurationDays`.
|
|
110
111
|
|
|
112
|
+
`price` is in your store currency, and buyers in Poland, the euro area, the US and Brazil pay it converted at the day's rate. To charge a round local price instead, pass `prices: [{ currency: 'PLN', price: 79 }]`: up to three currencies other than the store one, from 5 to 999999 each, not on coin packs. The list replaces the old one, and `[]` removes them.
|
|
113
|
+
|
|
111
114
|
`addSerials` splits `text` on new lines unless you pass `delimiter` (`'comma'`, `'semicolon'` or any other string). A key the product already has is counted in `skipped` and not added again. `listSerials` returns the plain key values, which is why it needs `products:write`.
|
|
112
115
|
|
|
113
116
|
## Orders and customers
|
|
@@ -117,11 +120,19 @@ const { data: orders } = await skalp.orders.list({ status: 'COMPLETED', updatedS
|
|
|
117
120
|
|
|
118
121
|
await skalp.orders.cancel(order.id)
|
|
119
122
|
|
|
123
|
+
const { data: mine } = await skalp.orders.list({ identifier: ['steam:110000112345679', 'discord:123456789012345678'] })
|
|
124
|
+
|
|
125
|
+
await skalp.orders.redeliver(order.id)
|
|
126
|
+
|
|
120
127
|
const { replacements } = await skalp.orders.replace(order.id, { reason: 'Key does not activate' })
|
|
121
128
|
```
|
|
122
129
|
|
|
123
130
|
`cancel` works only on a `PENDING` order and returns it as `CANCELED`. `replace` swaps the delivered keys of a completed order: with no `serialIds` it replaces every key, and with no `values` it draws the new ones from stock (`values` is required for keys that come from your server). The replacement keys come back in `replacements[].value`. `orders.get` also returns `serialsReplaced`, the `replacements` history and `serials`: the id, status, `deliveredAt` and `replacedById` of every delivered or replaced key (never its value), which are the ids `serialIds` expects.
|
|
124
131
|
|
|
132
|
+
`identifier` keeps the orders of one player (up to 10 values of up to 200 characters; a longer list or value is a 400). A value is a game identity: `steam:<hex>`, a SteamID64, any `kind:value` such as `discord:123456789012345678` or `license:abc...`, or a bare value. It matches orders whose buyer or gift recipient signed in with that Steam account, or where a checkout custom field answer equals the value (case and a leading `kind:` prefix on either side are ignored). It combines with the other filters.
|
|
133
|
+
|
|
134
|
+
`redeliver` takes the order's id or its number (`SKP-XXXX-XXXX-XXXX`) and sends a completed order that was not delivered again: a `FAILED` delivery goes back to `PENDING` and the `order.paid` and `purchase.completed` webhooks are sent again to the store's game server, which delivers it as a normal purchase. It returns the order as `orders.get` does. It answers 400 when the order is not `COMPLETED`, is already `DELIVERED`, is a gift nobody has claimed yet, or never had a licence issued, and 404 when the order is not in the key's store.
|
|
135
|
+
|
|
125
136
|
`updatedSince` lists oldest change first, so you can page through it and keep the last `updatedAt` as your cursor; without it, newest orders come first. Amounts are in major units (`19.5`).
|
|
126
137
|
|
|
127
138
|
Coupons take `validFrom`, `validTo` as an ISO string or a `Date`; `coupons.update` accepts `null` to clear `validTo`, `maxUses`, `minPurchaseAmount` and `description`.
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AccessRule, AddAccessRuleParams, AddSerialsParams, AddSerialsResult, AppealFeedbackParams, ChangeSubscriptionPlanParams, CheckoutSession, CheckoutSessionStatus, CoinCheckout, CoinCheckoutStatus, Coupon, CreateCheckoutParams, CreateCoinCheckoutParams, CreateCouponParams, CreateCustomerParams, CreateProductGroupParams, CreateProductParams, CreateStoreCategoryParams, CreateSubscriptionCheckoutParams, CreateUsageChargeParams, Customer, CustomerDetails, CustomerList, Deleted, Feedback, FeedbackList, HeartbeatParams, Invoice, LicenseKeyInfo, LicenseValidation, LicenseVerification, ListAccessRulesParams, ListCustomersParams, ListFeedbackParams, ListOrdersParams, ListProductsParams, ListQueriesParams, ListSerialsParams, ListSubscriptionsParams, Order, OrderKeysReplaced, OrderList, Product, ProductGroup, ProductStock, ReplaceOrderKeysParams, ReplyParams, SerialList, SkalpEvent, StoreCategory, StoreCategoryWithCount, Subscription, SubscriptionCheckout, SubscriptionCheckoutStatus, SubscriptionList, SupportQueryDetails, SupportQueryList, ResetLicenseDevicesParams, UpdateCouponParams, UpdateCustomerParams, UpdateProductGroupParams, UpdateProductParams, UpdateStoreCategoryParams, UsageCharge, ValidateLicenseParams, VerifyLicenseParams } from './types.js';
|
|
2
2
|
export type * from './types.js';
|
|
3
|
-
export declare const VERSION = "1.
|
|
3
|
+
export declare const VERSION = "1.6.0";
|
|
4
4
|
export interface SkalpOptions {
|
|
5
5
|
apiKey?: string;
|
|
6
6
|
webhookSecret?: string;
|
|
@@ -36,13 +36,14 @@ export declare class Skalp {
|
|
|
36
36
|
get: (id: string) => Promise<CheckoutSessionStatus>;
|
|
37
37
|
};
|
|
38
38
|
readonly orders: {
|
|
39
|
-
list: ({ status, productId, email, createdAfter, createdBefore, updatedSince, limit, offset }?: ListOrdersParams) => Promise<OrderList>;
|
|
39
|
+
list: ({ status, productId, email, identifier, createdAfter, createdBefore, updatedSince, limit, offset }?: ListOrdersParams) => Promise<OrderList>;
|
|
40
40
|
get: (id: string) => Promise<Order>;
|
|
41
41
|
cancel: (id: string) => Promise<Order>;
|
|
42
|
+
redeliver: (id: string) => Promise<Order>;
|
|
42
43
|
replace: (id: string, { serialIds, values, reason }?: ReplaceOrderKeysParams) => Promise<OrderKeysReplaced>;
|
|
43
44
|
};
|
|
44
45
|
readonly products: {
|
|
45
|
-
list: ({ status }?: ListProductsParams) => Promise<Product
|
|
46
|
+
list: ({ status, limit, offset }?: ListProductsParams) => Promise<Array<Product>>;
|
|
46
47
|
get: (idOrSlug: string) => Promise<Product>;
|
|
47
48
|
create: (params: CreateProductParams) => Promise<Product>;
|
|
48
49
|
update: (idOrSlug: string, params: UpdateProductParams) => Promise<Product>;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Skalp = exports.SkalpSignatureError = exports.SkalpConnectionError = exports.SkalpError = exports.VERSION = void 0;
|
|
4
|
-
exports.VERSION = '1.
|
|
4
|
+
exports.VERSION = '1.6.0';
|
|
5
5
|
const SIGNATURE_HEADER = 'skalp-signature';
|
|
6
6
|
const SIGNATURE_TOLERANCE_SECONDS = 300;
|
|
7
|
+
const PRODUCT_PAGE = 100;
|
|
7
8
|
const USER_AGENT = `skalp-node/${exports.VERSION}`;
|
|
8
9
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
9
10
|
const MAX_RETRY_WAIT_SECONDS = 10;
|
|
@@ -51,6 +52,7 @@ const PRODUCT_FIELDS = [
|
|
|
51
52
|
'comparePrice',
|
|
52
53
|
'category',
|
|
53
54
|
'categoryId',
|
|
55
|
+
'game',
|
|
54
56
|
'storeCategoryId',
|
|
55
57
|
'tags',
|
|
56
58
|
'images',
|
|
@@ -61,6 +63,24 @@ const PRODUCT_FIELDS = [
|
|
|
61
63
|
'dynamicUrl',
|
|
62
64
|
'licenseDeviceLimit',
|
|
63
65
|
'licenseDurationDays',
|
|
66
|
+
'deliveryType',
|
|
67
|
+
'deliveryPayload',
|
|
68
|
+
'revokePayload',
|
|
69
|
+
'coinConfig',
|
|
70
|
+
'visibility',
|
|
71
|
+
'sortPriority',
|
|
72
|
+
'deliveryNote',
|
|
73
|
+
'termsOfService',
|
|
74
|
+
'warrantyDays',
|
|
75
|
+
'warrantyText',
|
|
76
|
+
'blockVpn',
|
|
77
|
+
'maxRiskScore',
|
|
78
|
+
'customFields',
|
|
79
|
+
'minQuantity',
|
|
80
|
+
'maxQuantity',
|
|
81
|
+
'volumeDiscounts',
|
|
82
|
+
'upsells',
|
|
83
|
+
'prices',
|
|
64
84
|
];
|
|
65
85
|
const COUPON_FIELDS = [
|
|
66
86
|
'discountType',
|
|
@@ -109,11 +129,12 @@ class Skalp {
|
|
|
109
129
|
get: (id) => this.#request('GET', `/v1/checkout/sessions/${segment(id)}`),
|
|
110
130
|
};
|
|
111
131
|
orders = {
|
|
112
|
-
list: ({ status, productId, email, createdAfter, createdBefore, updatedSince, limit, offset } = {}) => this.#request('GET', '/v1/orders', {
|
|
132
|
+
list: ({ status, productId, email, identifier, createdAfter, createdBefore, updatedSince, limit, offset } = {}) => this.#request('GET', '/v1/orders', {
|
|
113
133
|
query: {
|
|
114
134
|
status,
|
|
115
135
|
productId,
|
|
116
136
|
email,
|
|
137
|
+
identifier,
|
|
117
138
|
createdAfter: optionalDate(createdAfter),
|
|
118
139
|
createdBefore: optionalDate(createdBefore),
|
|
119
140
|
updatedSince: optionalDate(updatedSince),
|
|
@@ -123,12 +144,27 @@ class Skalp {
|
|
|
123
144
|
}),
|
|
124
145
|
get: (id) => this.#request('GET', `/v1/orders/${segment(id)}`),
|
|
125
146
|
cancel: (id) => this.#request('POST', `/v1/orders/${segment(id)}/cancel`),
|
|
147
|
+
redeliver: (id) => this.#request('POST', `/v1/orders/${segment(id)}/redeliver`),
|
|
126
148
|
replace: (id, { serialIds, values, reason } = {}) => this.#request('POST', `/v1/orders/${segment(id)}/replacement`, {
|
|
127
149
|
body: { serialIds, values, reason },
|
|
128
150
|
}),
|
|
129
151
|
};
|
|
130
152
|
products = {
|
|
131
|
-
list: ({ status } = {}) =>
|
|
153
|
+
list: async ({ status, limit, offset } = {}) => {
|
|
154
|
+
if (limit !== undefined || offset !== undefined) {
|
|
155
|
+
return (await this.#send('GET', '/v1/products', { query: { status, limit, offset } })).data;
|
|
156
|
+
}
|
|
157
|
+
const products = [];
|
|
158
|
+
for (;;) {
|
|
159
|
+
const page = await this.#send('GET', '/v1/products', {
|
|
160
|
+
query: { status, limit: PRODUCT_PAGE, offset: products.length },
|
|
161
|
+
});
|
|
162
|
+
products.push(...page.data);
|
|
163
|
+
const total = Number(page.headers.get('x-total-count'));
|
|
164
|
+
if (page.data.length < PRODUCT_PAGE || products.length >= total)
|
|
165
|
+
return products;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
132
168
|
get: (idOrSlug) => this.#request('GET', `/v1/products/${segment(idOrSlug)}`),
|
|
133
169
|
create: (params) => this.#request('POST', '/v1/products', {
|
|
134
170
|
body: pick(params, [...PRODUCT_FIELDS, 'productType', 'submit']),
|
|
@@ -294,13 +330,19 @@ class Skalp {
|
|
|
294
330
|
const hmac = await crypto.subtle.importKey('raw', encoder.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
295
331
|
return (await crypto.subtle.verify('HMAC', hmac, parsed.signature, signed)) ? undefined : 'invalid';
|
|
296
332
|
}
|
|
297
|
-
async #request(method, path,
|
|
333
|
+
async #request(method, path, options = {}) {
|
|
334
|
+
return (await this.#send(method, path, options)).data;
|
|
335
|
+
}
|
|
336
|
+
async #send(method, path, { body, query, auth = true } = {}) {
|
|
298
337
|
if (auth && !this.#apiKey) {
|
|
299
338
|
throw new Error('Skalp: missing API key. Pass it to new Skalp(apiKey) or set SKALP_API_KEY.');
|
|
300
339
|
}
|
|
301
340
|
const url = new URL(this.#baseUrl + path);
|
|
302
341
|
for (const [name, value] of Object.entries(query ?? {})) {
|
|
303
|
-
if (value
|
|
342
|
+
if (Array.isArray(value))
|
|
343
|
+
for (const item of value)
|
|
344
|
+
url.searchParams.append(name, String(item));
|
|
345
|
+
else if (value !== undefined && value !== null)
|
|
304
346
|
url.searchParams.set(name, String(value));
|
|
305
347
|
}
|
|
306
348
|
const headers = { accept: 'application/json', 'user-agent': USER_AGENT };
|
|
@@ -339,7 +381,7 @@ class Skalp {
|
|
|
339
381
|
}
|
|
340
382
|
if (!res.ok)
|
|
341
383
|
throw toError(res.status, data);
|
|
342
|
-
return data;
|
|
384
|
+
return { data: data, headers: res.headers };
|
|
343
385
|
}
|
|
344
386
|
}
|
|
345
387
|
}
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type Currency = 'EUR' | 'USD' | 'PLN' | (string & {});
|
|
|
2
2
|
export type FulfillmentType = 'FILE' | 'SERIAL' | 'GAME' | 'SERVICE' | 'DYNAMIC' | 'LICENSE';
|
|
3
3
|
export type ProductStatus = 'DRAFT' | 'PENDING_REVIEW' | 'PROCESSING' | 'PROCESSING_FAILED' | 'ACTIVE' | 'REJECTED' | 'ARCHIVED';
|
|
4
4
|
export type ProductType = 'ONE_TIME' | 'RECURRING' | 'COINS';
|
|
5
|
+
export type Game = 'fivem' | 'minecraft' | 'rust';
|
|
5
6
|
export type BillingInterval = 'WEEK' | 'MONTH' | 'YEAR';
|
|
6
7
|
export type OrderStatus = 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'REFUNDED' | 'DISPUTED' | 'CANCELED';
|
|
7
8
|
export type PaymentProvider = 'STRIPE' | 'NOWPAYMENTS' | 'PAYPAL';
|
|
@@ -91,6 +92,7 @@ export interface ListOrdersParams {
|
|
|
91
92
|
status?: OrderStatus;
|
|
92
93
|
productId?: string;
|
|
93
94
|
email?: string;
|
|
95
|
+
identifier?: string | Array<string>;
|
|
94
96
|
createdAfter?: string | Date;
|
|
95
97
|
createdBefore?: string | Date;
|
|
96
98
|
updatedSince?: string | Date;
|
|
@@ -176,6 +178,10 @@ export type DeliveryType = 'COMMAND' | 'SQL';
|
|
|
176
178
|
* ON_HOLD is listed but not buyable, PRIVATE sells only through the API.
|
|
177
179
|
*/
|
|
178
180
|
export type ProductVisibility = 'PUBLIC' | 'UNLISTED' | 'ON_HOLD' | 'PRIVATE';
|
|
181
|
+
export interface ProductPrice {
|
|
182
|
+
currency: Currency;
|
|
183
|
+
price: number;
|
|
184
|
+
}
|
|
179
185
|
export interface Product {
|
|
180
186
|
id: string;
|
|
181
187
|
storeId: string;
|
|
@@ -186,12 +192,15 @@ export interface Product {
|
|
|
186
192
|
price: number;
|
|
187
193
|
comparePrice: number | null;
|
|
188
194
|
currency: Currency;
|
|
195
|
+
/** Round prices the seller set in other currencies. A buyer whose local currency is listed pays it exactly. */
|
|
196
|
+
prices: Array<ProductPrice>;
|
|
189
197
|
status: ProductStatus;
|
|
190
198
|
rejectionReason: string | null;
|
|
191
199
|
productType: ProductType;
|
|
192
200
|
billingInterval: BillingInterval | null;
|
|
193
201
|
trialPeriodDays: number;
|
|
194
202
|
fulfillmentType: FulfillmentType | null;
|
|
203
|
+
game: Game | null;
|
|
195
204
|
category: string;
|
|
196
205
|
categoryId: string | null;
|
|
197
206
|
storeCategoryId: string | null;
|
|
@@ -205,6 +214,13 @@ export interface Product {
|
|
|
205
214
|
deliveryType: DeliveryType | null;
|
|
206
215
|
/** Server stores: what the game server runs after payment. */
|
|
207
216
|
deliveryPayload: string | null;
|
|
217
|
+
revokePayload: string | null;
|
|
218
|
+
coinConfig: CoinConfig | null;
|
|
219
|
+
pendingChanges: {
|
|
220
|
+
submittedAt: string;
|
|
221
|
+
changes: Partial<Record<ReviewedProductField, unknown>>;
|
|
222
|
+
} | null;
|
|
223
|
+
pendingChangesRejection: string | null;
|
|
208
224
|
visibility: ProductVisibility;
|
|
209
225
|
/** Higher comes first on the store page. */
|
|
210
226
|
sortPriority: number;
|
|
@@ -232,8 +248,24 @@ export interface Product {
|
|
|
232
248
|
createdAt: string;
|
|
233
249
|
updatedAt: string;
|
|
234
250
|
}
|
|
251
|
+
export interface CoinBonusTier {
|
|
252
|
+
thresholdCoins: number;
|
|
253
|
+
bonusPercent: number;
|
|
254
|
+
}
|
|
255
|
+
export interface CoinConfig {
|
|
256
|
+
unitPriceCents: number;
|
|
257
|
+
minQty: number;
|
|
258
|
+
maxQty: number;
|
|
259
|
+
step: number;
|
|
260
|
+
currency: string;
|
|
261
|
+
bonusTiers: Array<CoinBonusTier>;
|
|
262
|
+
}
|
|
263
|
+
export type CoinConfigInput = Omit<CoinConfig, 'currency'>;
|
|
264
|
+
export type ReviewedProductField = 'name' | 'shortDescription' | 'description' | 'images' | 'videoUrl' | 'features' | 'requirements' | 'customFields' | 'deliveryNote' | 'termsOfService' | 'warrantyText' | 'deliveryType' | 'deliveryPayload' | 'revokePayload';
|
|
235
265
|
export interface ListProductsParams {
|
|
236
266
|
status?: ProductStatus | 'all';
|
|
267
|
+
limit?: number;
|
|
268
|
+
offset?: number;
|
|
237
269
|
}
|
|
238
270
|
interface ProductFields {
|
|
239
271
|
slug?: string;
|
|
@@ -241,6 +273,7 @@ interface ProductFields {
|
|
|
241
273
|
shortDescription?: string;
|
|
242
274
|
category?: string;
|
|
243
275
|
categoryId?: string;
|
|
276
|
+
game?: Game | null;
|
|
244
277
|
storeCategoryId?: string | null;
|
|
245
278
|
tags?: Array<string>;
|
|
246
279
|
images?: Array<string>;
|
|
@@ -255,6 +288,8 @@ interface ProductFields {
|
|
|
255
288
|
deliveryType?: DeliveryType;
|
|
256
289
|
/** Required for a server store; needs a placeholder such as {steamhex}. */
|
|
257
290
|
deliveryPayload?: string;
|
|
291
|
+
revokePayload?: string;
|
|
292
|
+
coinConfig?: CoinConfigInput;
|
|
258
293
|
visibility?: ProductVisibility;
|
|
259
294
|
sortPriority?: number;
|
|
260
295
|
/** null or an empty string clears it. */
|
|
@@ -274,12 +309,14 @@ interface ProductFields {
|
|
|
274
309
|
volumeDiscounts?: Array<VolumeDiscount> | null;
|
|
275
310
|
/** Up to 3 one-time products of the store; the list replaces the old one, [] or null removes them all. */
|
|
276
311
|
upsells?: Array<ProductUpsellInput> | null;
|
|
312
|
+
/** Up to 3 currencies other than the store currency, not for coin packs; the list replaces the old one, [] or null removes them all. */
|
|
313
|
+
prices?: Array<ProductPrice> | null;
|
|
277
314
|
}
|
|
278
315
|
export interface CreateProductParams extends ProductFields {
|
|
279
316
|
name: string;
|
|
280
317
|
price: number;
|
|
281
318
|
comparePrice?: number;
|
|
282
|
-
productType?: 'ONE_TIME' | 'RECURRING';
|
|
319
|
+
productType?: 'ONE_TIME' | 'RECURRING' | 'COINS';
|
|
283
320
|
submit?: boolean;
|
|
284
321
|
}
|
|
285
322
|
export interface UpdateProductParams extends ProductFields {
|
|
@@ -799,6 +836,16 @@ export interface OrderRefundedData {
|
|
|
799
836
|
granted?: boolean;
|
|
800
837
|
fullRefund: boolean;
|
|
801
838
|
licenses: Array<WebhookLicense>;
|
|
839
|
+
revocations: Array<Revocation>;
|
|
840
|
+
}
|
|
841
|
+
export interface Revocation {
|
|
842
|
+
productId: string;
|
|
843
|
+
productSlug: string;
|
|
844
|
+
productName: string;
|
|
845
|
+
deliveryKey: string;
|
|
846
|
+
delivered: boolean;
|
|
847
|
+
deliveryType: DeliveryType;
|
|
848
|
+
revokeCommand: string;
|
|
802
849
|
}
|
|
803
850
|
export interface OrderDisputedData {
|
|
804
851
|
orderId: string;
|
|
@@ -851,6 +898,7 @@ export interface ProductEventData {
|
|
|
851
898
|
price: number;
|
|
852
899
|
fulfillmentType: FulfillmentType | null;
|
|
853
900
|
productType: ProductType;
|
|
901
|
+
game: Game | null;
|
|
854
902
|
}
|
|
855
903
|
export interface ProductStockData {
|
|
856
904
|
productId: string;
|
|
@@ -914,6 +962,7 @@ export type SkalpEvent = Envelope<'order.created' | 'order.updated' | 'order.can
|
|
|
914
962
|
}> | Envelope<'subscription.canceled', SubscriptionEventData & {
|
|
915
963
|
endedAt: string | null;
|
|
916
964
|
cancelReason: 'requested' | 'payment_failure' | 'dispute' | 'stripe';
|
|
965
|
+
revokeCommand?: string;
|
|
917
966
|
}> | Envelope<'subscription.refunded', SubscriptionEventData & {
|
|
918
967
|
invoiceId: string;
|
|
919
968
|
amountRefunded: number;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AccessRule, AddAccessRuleParams, AddSerialsParams, AddSerialsResult, AppealFeedbackParams, ChangeSubscriptionPlanParams, CheckoutSession, CheckoutSessionStatus, CoinCheckout, CoinCheckoutStatus, Coupon, CreateCheckoutParams, CreateCoinCheckoutParams, CreateCouponParams, CreateCustomerParams, CreateProductGroupParams, CreateProductParams, CreateStoreCategoryParams, CreateSubscriptionCheckoutParams, CreateUsageChargeParams, Customer, CustomerDetails, CustomerList, Deleted, Feedback, FeedbackList, HeartbeatParams, Invoice, LicenseKeyInfo, LicenseValidation, LicenseVerification, ListAccessRulesParams, ListCustomersParams, ListFeedbackParams, ListOrdersParams, ListProductsParams, ListQueriesParams, ListSerialsParams, ListSubscriptionsParams, Order, OrderKeysReplaced, OrderList, Product, ProductGroup, ProductStock, ReplaceOrderKeysParams, ReplyParams, SerialList, SkalpEvent, StoreCategory, StoreCategoryWithCount, Subscription, SubscriptionCheckout, SubscriptionCheckoutStatus, SubscriptionList, SupportQueryDetails, SupportQueryList, ResetLicenseDevicesParams, UpdateCouponParams, UpdateCustomerParams, UpdateProductGroupParams, UpdateProductParams, UpdateStoreCategoryParams, UsageCharge, ValidateLicenseParams, VerifyLicenseParams } from './types.js';
|
|
2
2
|
export type * from './types.js';
|
|
3
|
-
export declare const VERSION = "1.
|
|
3
|
+
export declare const VERSION = "1.6.0";
|
|
4
4
|
export interface SkalpOptions {
|
|
5
5
|
apiKey?: string;
|
|
6
6
|
webhookSecret?: string;
|
|
@@ -36,13 +36,14 @@ export declare class Skalp {
|
|
|
36
36
|
get: (id: string) => Promise<CheckoutSessionStatus>;
|
|
37
37
|
};
|
|
38
38
|
readonly orders: {
|
|
39
|
-
list: ({ status, productId, email, createdAfter, createdBefore, updatedSince, limit, offset }?: ListOrdersParams) => Promise<OrderList>;
|
|
39
|
+
list: ({ status, productId, email, identifier, createdAfter, createdBefore, updatedSince, limit, offset }?: ListOrdersParams) => Promise<OrderList>;
|
|
40
40
|
get: (id: string) => Promise<Order>;
|
|
41
41
|
cancel: (id: string) => Promise<Order>;
|
|
42
|
+
redeliver: (id: string) => Promise<Order>;
|
|
42
43
|
replace: (id: string, { serialIds, values, reason }?: ReplaceOrderKeysParams) => Promise<OrderKeysReplaced>;
|
|
43
44
|
};
|
|
44
45
|
readonly products: {
|
|
45
|
-
list: ({ status }?: ListProductsParams) => Promise<Product
|
|
46
|
+
list: ({ status, limit, offset }?: ListProductsParams) => Promise<Array<Product>>;
|
|
46
47
|
get: (idOrSlug: string) => Promise<Product>;
|
|
47
48
|
create: (params: CreateProductParams) => Promise<Product>;
|
|
48
49
|
update: (idOrSlug: string, params: UpdateProductParams) => Promise<Product>;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export const VERSION = '1.
|
|
1
|
+
export const VERSION = '1.6.0';
|
|
2
2
|
const SIGNATURE_HEADER = 'skalp-signature';
|
|
3
3
|
const SIGNATURE_TOLERANCE_SECONDS = 300;
|
|
4
|
+
const PRODUCT_PAGE = 100;
|
|
4
5
|
const USER_AGENT = `skalp-node/${VERSION}`;
|
|
5
6
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
6
7
|
const MAX_RETRY_WAIT_SECONDS = 10;
|
|
@@ -45,6 +46,7 @@ const PRODUCT_FIELDS = [
|
|
|
45
46
|
'comparePrice',
|
|
46
47
|
'category',
|
|
47
48
|
'categoryId',
|
|
49
|
+
'game',
|
|
48
50
|
'storeCategoryId',
|
|
49
51
|
'tags',
|
|
50
52
|
'images',
|
|
@@ -55,6 +57,24 @@ const PRODUCT_FIELDS = [
|
|
|
55
57
|
'dynamicUrl',
|
|
56
58
|
'licenseDeviceLimit',
|
|
57
59
|
'licenseDurationDays',
|
|
60
|
+
'deliveryType',
|
|
61
|
+
'deliveryPayload',
|
|
62
|
+
'revokePayload',
|
|
63
|
+
'coinConfig',
|
|
64
|
+
'visibility',
|
|
65
|
+
'sortPriority',
|
|
66
|
+
'deliveryNote',
|
|
67
|
+
'termsOfService',
|
|
68
|
+
'warrantyDays',
|
|
69
|
+
'warrantyText',
|
|
70
|
+
'blockVpn',
|
|
71
|
+
'maxRiskScore',
|
|
72
|
+
'customFields',
|
|
73
|
+
'minQuantity',
|
|
74
|
+
'maxQuantity',
|
|
75
|
+
'volumeDiscounts',
|
|
76
|
+
'upsells',
|
|
77
|
+
'prices',
|
|
58
78
|
];
|
|
59
79
|
const COUPON_FIELDS = [
|
|
60
80
|
'discountType',
|
|
@@ -103,11 +123,12 @@ export class Skalp {
|
|
|
103
123
|
get: (id) => this.#request('GET', `/v1/checkout/sessions/${segment(id)}`),
|
|
104
124
|
};
|
|
105
125
|
orders = {
|
|
106
|
-
list: ({ status, productId, email, createdAfter, createdBefore, updatedSince, limit, offset } = {}) => this.#request('GET', '/v1/orders', {
|
|
126
|
+
list: ({ status, productId, email, identifier, createdAfter, createdBefore, updatedSince, limit, offset } = {}) => this.#request('GET', '/v1/orders', {
|
|
107
127
|
query: {
|
|
108
128
|
status,
|
|
109
129
|
productId,
|
|
110
130
|
email,
|
|
131
|
+
identifier,
|
|
111
132
|
createdAfter: optionalDate(createdAfter),
|
|
112
133
|
createdBefore: optionalDate(createdBefore),
|
|
113
134
|
updatedSince: optionalDate(updatedSince),
|
|
@@ -117,12 +138,27 @@ export class Skalp {
|
|
|
117
138
|
}),
|
|
118
139
|
get: (id) => this.#request('GET', `/v1/orders/${segment(id)}`),
|
|
119
140
|
cancel: (id) => this.#request('POST', `/v1/orders/${segment(id)}/cancel`),
|
|
141
|
+
redeliver: (id) => this.#request('POST', `/v1/orders/${segment(id)}/redeliver`),
|
|
120
142
|
replace: (id, { serialIds, values, reason } = {}) => this.#request('POST', `/v1/orders/${segment(id)}/replacement`, {
|
|
121
143
|
body: { serialIds, values, reason },
|
|
122
144
|
}),
|
|
123
145
|
};
|
|
124
146
|
products = {
|
|
125
|
-
list: ({ status } = {}) =>
|
|
147
|
+
list: async ({ status, limit, offset } = {}) => {
|
|
148
|
+
if (limit !== undefined || offset !== undefined) {
|
|
149
|
+
return (await this.#send('GET', '/v1/products', { query: { status, limit, offset } })).data;
|
|
150
|
+
}
|
|
151
|
+
const products = [];
|
|
152
|
+
for (;;) {
|
|
153
|
+
const page = await this.#send('GET', '/v1/products', {
|
|
154
|
+
query: { status, limit: PRODUCT_PAGE, offset: products.length },
|
|
155
|
+
});
|
|
156
|
+
products.push(...page.data);
|
|
157
|
+
const total = Number(page.headers.get('x-total-count'));
|
|
158
|
+
if (page.data.length < PRODUCT_PAGE || products.length >= total)
|
|
159
|
+
return products;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
126
162
|
get: (idOrSlug) => this.#request('GET', `/v1/products/${segment(idOrSlug)}`),
|
|
127
163
|
create: (params) => this.#request('POST', '/v1/products', {
|
|
128
164
|
body: pick(params, [...PRODUCT_FIELDS, 'productType', 'submit']),
|
|
@@ -288,13 +324,19 @@ export class Skalp {
|
|
|
288
324
|
const hmac = await crypto.subtle.importKey('raw', encoder.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
289
325
|
return (await crypto.subtle.verify('HMAC', hmac, parsed.signature, signed)) ? undefined : 'invalid';
|
|
290
326
|
}
|
|
291
|
-
async #request(method, path,
|
|
327
|
+
async #request(method, path, options = {}) {
|
|
328
|
+
return (await this.#send(method, path, options)).data;
|
|
329
|
+
}
|
|
330
|
+
async #send(method, path, { body, query, auth = true } = {}) {
|
|
292
331
|
if (auth && !this.#apiKey) {
|
|
293
332
|
throw new Error('Skalp: missing API key. Pass it to new Skalp(apiKey) or set SKALP_API_KEY.');
|
|
294
333
|
}
|
|
295
334
|
const url = new URL(this.#baseUrl + path);
|
|
296
335
|
for (const [name, value] of Object.entries(query ?? {})) {
|
|
297
|
-
if (value
|
|
336
|
+
if (Array.isArray(value))
|
|
337
|
+
for (const item of value)
|
|
338
|
+
url.searchParams.append(name, String(item));
|
|
339
|
+
else if (value !== undefined && value !== null)
|
|
298
340
|
url.searchParams.set(name, String(value));
|
|
299
341
|
}
|
|
300
342
|
const headers = { accept: 'application/json', 'user-agent': USER_AGENT };
|
|
@@ -333,7 +375,7 @@ export class Skalp {
|
|
|
333
375
|
}
|
|
334
376
|
if (!res.ok)
|
|
335
377
|
throw toError(res.status, data);
|
|
336
|
-
return data;
|
|
378
|
+
return { data: data, headers: res.headers };
|
|
337
379
|
}
|
|
338
380
|
}
|
|
339
381
|
}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type Currency = 'EUR' | 'USD' | 'PLN' | (string & {});
|
|
|
2
2
|
export type FulfillmentType = 'FILE' | 'SERIAL' | 'GAME' | 'SERVICE' | 'DYNAMIC' | 'LICENSE';
|
|
3
3
|
export type ProductStatus = 'DRAFT' | 'PENDING_REVIEW' | 'PROCESSING' | 'PROCESSING_FAILED' | 'ACTIVE' | 'REJECTED' | 'ARCHIVED';
|
|
4
4
|
export type ProductType = 'ONE_TIME' | 'RECURRING' | 'COINS';
|
|
5
|
+
export type Game = 'fivem' | 'minecraft' | 'rust';
|
|
5
6
|
export type BillingInterval = 'WEEK' | 'MONTH' | 'YEAR';
|
|
6
7
|
export type OrderStatus = 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'REFUNDED' | 'DISPUTED' | 'CANCELED';
|
|
7
8
|
export type PaymentProvider = 'STRIPE' | 'NOWPAYMENTS' | 'PAYPAL';
|
|
@@ -91,6 +92,7 @@ export interface ListOrdersParams {
|
|
|
91
92
|
status?: OrderStatus;
|
|
92
93
|
productId?: string;
|
|
93
94
|
email?: string;
|
|
95
|
+
identifier?: string | Array<string>;
|
|
94
96
|
createdAfter?: string | Date;
|
|
95
97
|
createdBefore?: string | Date;
|
|
96
98
|
updatedSince?: string | Date;
|
|
@@ -176,6 +178,10 @@ export type DeliveryType = 'COMMAND' | 'SQL';
|
|
|
176
178
|
* ON_HOLD is listed but not buyable, PRIVATE sells only through the API.
|
|
177
179
|
*/
|
|
178
180
|
export type ProductVisibility = 'PUBLIC' | 'UNLISTED' | 'ON_HOLD' | 'PRIVATE';
|
|
181
|
+
export interface ProductPrice {
|
|
182
|
+
currency: Currency;
|
|
183
|
+
price: number;
|
|
184
|
+
}
|
|
179
185
|
export interface Product {
|
|
180
186
|
id: string;
|
|
181
187
|
storeId: string;
|
|
@@ -186,12 +192,15 @@ export interface Product {
|
|
|
186
192
|
price: number;
|
|
187
193
|
comparePrice: number | null;
|
|
188
194
|
currency: Currency;
|
|
195
|
+
/** Round prices the seller set in other currencies. A buyer whose local currency is listed pays it exactly. */
|
|
196
|
+
prices: Array<ProductPrice>;
|
|
189
197
|
status: ProductStatus;
|
|
190
198
|
rejectionReason: string | null;
|
|
191
199
|
productType: ProductType;
|
|
192
200
|
billingInterval: BillingInterval | null;
|
|
193
201
|
trialPeriodDays: number;
|
|
194
202
|
fulfillmentType: FulfillmentType | null;
|
|
203
|
+
game: Game | null;
|
|
195
204
|
category: string;
|
|
196
205
|
categoryId: string | null;
|
|
197
206
|
storeCategoryId: string | null;
|
|
@@ -205,6 +214,13 @@ export interface Product {
|
|
|
205
214
|
deliveryType: DeliveryType | null;
|
|
206
215
|
/** Server stores: what the game server runs after payment. */
|
|
207
216
|
deliveryPayload: string | null;
|
|
217
|
+
revokePayload: string | null;
|
|
218
|
+
coinConfig: CoinConfig | null;
|
|
219
|
+
pendingChanges: {
|
|
220
|
+
submittedAt: string;
|
|
221
|
+
changes: Partial<Record<ReviewedProductField, unknown>>;
|
|
222
|
+
} | null;
|
|
223
|
+
pendingChangesRejection: string | null;
|
|
208
224
|
visibility: ProductVisibility;
|
|
209
225
|
/** Higher comes first on the store page. */
|
|
210
226
|
sortPriority: number;
|
|
@@ -232,8 +248,24 @@ export interface Product {
|
|
|
232
248
|
createdAt: string;
|
|
233
249
|
updatedAt: string;
|
|
234
250
|
}
|
|
251
|
+
export interface CoinBonusTier {
|
|
252
|
+
thresholdCoins: number;
|
|
253
|
+
bonusPercent: number;
|
|
254
|
+
}
|
|
255
|
+
export interface CoinConfig {
|
|
256
|
+
unitPriceCents: number;
|
|
257
|
+
minQty: number;
|
|
258
|
+
maxQty: number;
|
|
259
|
+
step: number;
|
|
260
|
+
currency: string;
|
|
261
|
+
bonusTiers: Array<CoinBonusTier>;
|
|
262
|
+
}
|
|
263
|
+
export type CoinConfigInput = Omit<CoinConfig, 'currency'>;
|
|
264
|
+
export type ReviewedProductField = 'name' | 'shortDescription' | 'description' | 'images' | 'videoUrl' | 'features' | 'requirements' | 'customFields' | 'deliveryNote' | 'termsOfService' | 'warrantyText' | 'deliveryType' | 'deliveryPayload' | 'revokePayload';
|
|
235
265
|
export interface ListProductsParams {
|
|
236
266
|
status?: ProductStatus | 'all';
|
|
267
|
+
limit?: number;
|
|
268
|
+
offset?: number;
|
|
237
269
|
}
|
|
238
270
|
interface ProductFields {
|
|
239
271
|
slug?: string;
|
|
@@ -241,6 +273,7 @@ interface ProductFields {
|
|
|
241
273
|
shortDescription?: string;
|
|
242
274
|
category?: string;
|
|
243
275
|
categoryId?: string;
|
|
276
|
+
game?: Game | null;
|
|
244
277
|
storeCategoryId?: string | null;
|
|
245
278
|
tags?: Array<string>;
|
|
246
279
|
images?: Array<string>;
|
|
@@ -255,6 +288,8 @@ interface ProductFields {
|
|
|
255
288
|
deliveryType?: DeliveryType;
|
|
256
289
|
/** Required for a server store; needs a placeholder such as {steamhex}. */
|
|
257
290
|
deliveryPayload?: string;
|
|
291
|
+
revokePayload?: string;
|
|
292
|
+
coinConfig?: CoinConfigInput;
|
|
258
293
|
visibility?: ProductVisibility;
|
|
259
294
|
sortPriority?: number;
|
|
260
295
|
/** null or an empty string clears it. */
|
|
@@ -274,12 +309,14 @@ interface ProductFields {
|
|
|
274
309
|
volumeDiscounts?: Array<VolumeDiscount> | null;
|
|
275
310
|
/** Up to 3 one-time products of the store; the list replaces the old one, [] or null removes them all. */
|
|
276
311
|
upsells?: Array<ProductUpsellInput> | null;
|
|
312
|
+
/** Up to 3 currencies other than the store currency, not for coin packs; the list replaces the old one, [] or null removes them all. */
|
|
313
|
+
prices?: Array<ProductPrice> | null;
|
|
277
314
|
}
|
|
278
315
|
export interface CreateProductParams extends ProductFields {
|
|
279
316
|
name: string;
|
|
280
317
|
price: number;
|
|
281
318
|
comparePrice?: number;
|
|
282
|
-
productType?: 'ONE_TIME' | 'RECURRING';
|
|
319
|
+
productType?: 'ONE_TIME' | 'RECURRING' | 'COINS';
|
|
283
320
|
submit?: boolean;
|
|
284
321
|
}
|
|
285
322
|
export interface UpdateProductParams extends ProductFields {
|
|
@@ -799,6 +836,16 @@ export interface OrderRefundedData {
|
|
|
799
836
|
granted?: boolean;
|
|
800
837
|
fullRefund: boolean;
|
|
801
838
|
licenses: Array<WebhookLicense>;
|
|
839
|
+
revocations: Array<Revocation>;
|
|
840
|
+
}
|
|
841
|
+
export interface Revocation {
|
|
842
|
+
productId: string;
|
|
843
|
+
productSlug: string;
|
|
844
|
+
productName: string;
|
|
845
|
+
deliveryKey: string;
|
|
846
|
+
delivered: boolean;
|
|
847
|
+
deliveryType: DeliveryType;
|
|
848
|
+
revokeCommand: string;
|
|
802
849
|
}
|
|
803
850
|
export interface OrderDisputedData {
|
|
804
851
|
orderId: string;
|
|
@@ -851,6 +898,7 @@ export interface ProductEventData {
|
|
|
851
898
|
price: number;
|
|
852
899
|
fulfillmentType: FulfillmentType | null;
|
|
853
900
|
productType: ProductType;
|
|
901
|
+
game: Game | null;
|
|
854
902
|
}
|
|
855
903
|
export interface ProductStockData {
|
|
856
904
|
productId: string;
|
|
@@ -914,6 +962,7 @@ export type SkalpEvent = Envelope<'order.created' | 'order.updated' | 'order.can
|
|
|
914
962
|
}> | Envelope<'subscription.canceled', SubscriptionEventData & {
|
|
915
963
|
endedAt: string | null;
|
|
916
964
|
cancelReason: 'requested' | 'payment_failure' | 'dispute' | 'stripe';
|
|
965
|
+
revokeCommand?: string;
|
|
917
966
|
}> | Envelope<'subscription.refunded', SubscriptionEventData & {
|
|
918
967
|
invoiceId: string;
|
|
919
968
|
amountRefunded: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skalpgg/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Official Skalp SDK for Node.js and edge runtimes: checkout, subscriptions, catalog and key stock, webhooks and license verification.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://skalp.gg/docs/sdk",
|