@skalpgg/sdk 1.1.0 → 1.3.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.
@@ -1,6 +1,6 @@
1
- import type { ChangeSubscriptionPlanParams, CheckoutSession, CheckoutSessionStatus, CreateCheckoutParams, CreateSubscriptionCheckoutParams, HeartbeatParams, Invoice, LicenseVerification, ListSubscriptionsParams, Order, Product, SkalpEvent, Subscription, SubscriptionCheckout, SubscriptionCheckoutStatus, SubscriptionList, VerifyLicenseParams } from './types.js';
1
+ import type { AccessRule, AddAccessRuleParams, AddSerialsParams, AddSerialsResult, AdjustCustomerBalanceParams, 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.1.0";
3
+ export declare const VERSION = "1.3.0";
4
4
  export interface SkalpOptions {
5
5
  apiKey?: string;
6
6
  webhookSecret?: string;
@@ -32,14 +32,81 @@ export declare class Skalp {
32
32
  #private;
33
33
  constructor(options?: string | SkalpOptions);
34
34
  readonly checkout: {
35
- create: ({ productId, customerEmail, metadata, couponCode, returnUrl }: CreateCheckoutParams) => Promise<CheckoutSession>;
35
+ create: ({ productId, quantity, customerEmail, metadata, couponCode, returnUrl, customFields }: CreateCheckoutParams) => Promise<CheckoutSession>;
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
40
  get: (id: string) => Promise<Order>;
41
+ cancel: (id: string) => Promise<Order>;
42
+ replace: (id: string, { serialIds, values, reason }?: ReplaceOrderKeysParams) => Promise<OrderKeysReplaced>;
40
43
  };
41
44
  readonly products: {
42
- list: () => Promise<Product[]>;
45
+ list: ({ status }?: ListProductsParams) => Promise<Product[]>;
46
+ get: (idOrSlug: string) => Promise<Product>;
47
+ create: (params: CreateProductParams) => Promise<Product>;
48
+ update: (idOrSlug: string, params: UpdateProductParams) => Promise<Product>;
49
+ archive: (idOrSlug: string) => Promise<Product>;
50
+ submit: (idOrSlug: string) => Promise<Product>;
51
+ stock: (idOrSlug: string) => Promise<ProductStock>;
52
+ listSerials: (idOrSlug: string, { status, limit, offset }?: ListSerialsParams) => Promise<SerialList>;
53
+ addSerials: (idOrSlug: string, { serials, text, delimiter }: AddSerialsParams) => Promise<AddSerialsResult>;
54
+ deleteSerial: (idOrSlug: string, serialId: string) => Promise<Deleted>;
55
+ };
56
+ readonly categories: {
57
+ list: () => Promise<StoreCategoryWithCount[]>;
58
+ create: ({ name }: CreateStoreCategoryParams) => Promise<StoreCategory>;
59
+ update: (id: string, { name, sortOrder }: UpdateStoreCategoryParams) => Promise<StoreCategory>;
60
+ delete: (id: string) => Promise<Deleted>;
61
+ };
62
+ readonly groups: {
63
+ list: () => Promise<ProductGroup[]>;
64
+ get: (id: string) => Promise<ProductGroup>;
65
+ create: (params: CreateProductGroupParams) => Promise<ProductGroup>;
66
+ update: (id: string, params: UpdateProductGroupParams) => Promise<ProductGroup>;
67
+ delete: (id: string) => Promise<Deleted>;
68
+ };
69
+ readonly coupons: {
70
+ list: () => Promise<Coupon[]>;
71
+ get: (id: string) => Promise<Coupon>;
72
+ create: (params: CreateCouponParams) => Promise<Coupon>;
73
+ update: (id: string, params: UpdateCouponParams) => Promise<Coupon>;
74
+ delete: (id: string) => Promise<Deleted>;
75
+ };
76
+ readonly customers: {
77
+ list: ({ search, limit, offset }?: ListCustomersParams) => Promise<CustomerList>;
78
+ get: (id: string) => Promise<CustomerDetails>;
79
+ create: ({ email, name, note }: CreateCustomerParams) => Promise<Customer>;
80
+ update: (id: string, { name, note }: UpdateCustomerParams) => Promise<Customer>;
81
+ delete: (id: string) => Promise<Deleted>;
82
+ adjustBalance: (id: string, { amount, reason }: AdjustCustomerBalanceParams) => Promise<Customer>;
83
+ };
84
+ readonly feedback: {
85
+ list: ({ productId, rating, limit, offset }?: ListFeedbackParams) => Promise<FeedbackList>;
86
+ get: (id: string) => Promise<Feedback>;
87
+ reply: (id: string, { message }: ReplyParams) => Promise<Feedback>;
88
+ appeal: (id: string, { reason }: AppealFeedbackParams) => Promise<Feedback>;
89
+ };
90
+ readonly queries: {
91
+ list: ({ status, limit, offset }?: ListQueriesParams) => Promise<SupportQueryList>;
92
+ get: (id: string) => Promise<SupportQueryDetails>;
93
+ reply: (id: string, { message }: ReplyParams) => Promise<SupportQueryDetails>;
94
+ close: (id: string) => Promise<SupportQueryDetails>;
95
+ reopen: (id: string) => Promise<SupportQueryDetails>;
96
+ };
97
+ readonly blacklist: {
98
+ list: ({ type }?: ListAccessRulesParams) => Promise<AccessRule[]>;
99
+ add: ({ type, value, note }: AddAccessRuleParams) => Promise<AccessRule>;
100
+ remove: (id: string) => Promise<Deleted>;
101
+ };
102
+ readonly whitelist: {
103
+ list: ({ type }?: ListAccessRulesParams) => Promise<AccessRule[]>;
104
+ add: ({ type, value, note }: AddAccessRuleParams) => Promise<AccessRule>;
105
+ remove: (id: string) => Promise<Deleted>;
106
+ };
107
+ readonly coins: {
108
+ createCheckout: ({ steamHex, coins, idempotencyKey, returnUrl }: CreateCoinCheckoutParams) => Promise<CoinCheckout>;
109
+ getCheckoutStatus: (reference: string) => Promise<CoinCheckoutStatus>;
43
110
  };
44
111
  readonly subscriptions: {
45
112
  createCheckout: ({ productId, productSlug, customerEmail, externalRef, metadata, currency, returnUrl, }: CreateSubscriptionCheckoutParams) => Promise<SubscriptionCheckout>;
@@ -48,6 +115,7 @@ export declare class Skalp {
48
115
  get: (id: string) => Promise<Subscription>;
49
116
  changePlan: (id: string, { productId, productSlug }: ChangeSubscriptionPlanParams) => Promise<Subscription>;
50
117
  cancel: (id: string) => Promise<Subscription>;
118
+ createUsageCharge: (id: string, { amount, currency, description, idempotencyKey, periodStart, periodEnd }: CreateUsageChargeParams) => Promise<UsageCharge>;
51
119
  listInvoices: (id: string) => Promise<Invoice[]>;
52
120
  };
53
121
  readonly licenses: {
@@ -55,6 +123,10 @@ export declare class Skalp {
55
123
  heartbeat: ({ licenseKey, serverIp }: HeartbeatParams) => Promise<{
56
124
  valid: boolean;
57
125
  }>;
126
+ validate: ({ key, productId, hardwareId }: ValidateLicenseParams) => Promise<LicenseValidation>;
127
+ resetDevices: ({ key, hardwareId }: ResetLicenseDevicesParams) => Promise<{
128
+ license: LicenseKeyInfo;
129
+ }>;
58
130
  };
59
131
  readonly webhooks: {
60
132
  verify: (payload: WebhookPayload, signature: SignatureSource, secret?: string) => Promise<boolean>;
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION = '1.1.0';
1
+ export const VERSION = '1.3.0';
2
2
  const SIGNATURE_HEADER = 'skalp-signature';
3
3
  const SIGNATURE_TOLERANCE_SECONDS = 300;
4
4
  const USER_AGENT = `skalp-node/${VERSION}`;
@@ -34,6 +34,49 @@ export class SkalpSignatureError extends Error {
34
34
  }
35
35
  const env = (name) => globalThis.process?.env?.[name];
36
36
  const segment = (id) => encodeURIComponent(id);
37
+ const isoDate = (value) => (value instanceof Date ? value.toISOString() : value);
38
+ const optionalDate = (value) => value instanceof Date ? value.toISOString() : value;
39
+ const PRODUCT_FIELDS = [
40
+ 'name',
41
+ 'slug',
42
+ 'description',
43
+ 'shortDescription',
44
+ 'price',
45
+ 'comparePrice',
46
+ 'category',
47
+ 'categoryId',
48
+ 'storeCategoryId',
49
+ 'tags',
50
+ 'images',
51
+ 'videoUrl',
52
+ 'fulfillmentType',
53
+ 'billingInterval',
54
+ 'trialPeriodDays',
55
+ 'dynamicUrl',
56
+ 'licenseDeviceLimit',
57
+ 'licenseDurationDays',
58
+ ];
59
+ const COUPON_FIELDS = [
60
+ 'discountType',
61
+ 'discountPercent',
62
+ 'discountAmount',
63
+ 'productIds',
64
+ 'disabledWithVolumeDiscounts',
65
+ 'description',
66
+ 'maxUses',
67
+ 'minPurchaseAmount',
68
+ 'oneTimePerUser',
69
+ ];
70
+ const GROUP_FIELDS = ['title', 'productIds', 'description', 'image', 'sortPriority', 'unlisted'];
71
+ function pick(source, keys) {
72
+ const picked = {};
73
+ for (const key of keys) {
74
+ const value = source[key];
75
+ if (value !== undefined)
76
+ picked[key] = value;
77
+ }
78
+ return picked;
79
+ }
37
80
  export class Skalp {
38
81
  #apiKey;
39
82
  #webhookSecret;
@@ -54,16 +97,117 @@ export class Skalp {
54
97
  }
55
98
  }
56
99
  checkout = {
57
- create: ({ productId, customerEmail, metadata, couponCode, returnUrl }) => this.#request('POST', '/v1/checkout/sessions', {
58
- body: { items: [{ productId, quantity: 1 }], customerEmail, metadata, couponCode, returnUrl },
100
+ create: ({ productId, quantity, customerEmail, metadata, couponCode, returnUrl, customFields }) => this.#request('POST', '/v1/checkout/sessions', {
101
+ body: { items: [{ productId, quantity }], customerEmail, metadata, couponCode, returnUrl, customFields },
59
102
  }),
60
103
  get: (id) => this.#request('GET', `/v1/checkout/sessions/${segment(id)}`),
61
104
  };
62
105
  orders = {
106
+ list: ({ status, productId, email, createdAfter, createdBefore, updatedSince, limit, offset } = {}) => this.#request('GET', '/v1/orders', {
107
+ query: {
108
+ status,
109
+ productId,
110
+ email,
111
+ createdAfter: optionalDate(createdAfter),
112
+ createdBefore: optionalDate(createdBefore),
113
+ updatedSince: optionalDate(updatedSince),
114
+ limit,
115
+ offset,
116
+ },
117
+ }),
63
118
  get: (id) => this.#request('GET', `/v1/orders/${segment(id)}`),
119
+ cancel: (id) => this.#request('POST', `/v1/orders/${segment(id)}/cancel`),
120
+ replace: (id, { serialIds, values, reason } = {}) => this.#request('POST', `/v1/orders/${segment(id)}/replacement`, {
121
+ body: { serialIds, values, reason },
122
+ }),
64
123
  };
65
124
  products = {
66
- list: () => this.#request('GET', '/v1/products'),
125
+ list: ({ status } = {}) => this.#request('GET', '/v1/products', { query: { status } }),
126
+ get: (idOrSlug) => this.#request('GET', `/v1/products/${segment(idOrSlug)}`),
127
+ create: (params) => this.#request('POST', '/v1/products', {
128
+ body: pick(params, [...PRODUCT_FIELDS, 'productType', 'submit']),
129
+ }),
130
+ update: (idOrSlug, params) => this.#request('PATCH', `/v1/products/${segment(idOrSlug)}`, {
131
+ body: pick(params, [...PRODUCT_FIELDS, 'status']),
132
+ }),
133
+ archive: (idOrSlug) => this.#request('DELETE', `/v1/products/${segment(idOrSlug)}`),
134
+ submit: (idOrSlug) => this.#request('POST', `/v1/products/${segment(idOrSlug)}/submit`),
135
+ stock: (idOrSlug) => this.#request('GET', `/v1/products/${segment(idOrSlug)}/stock`),
136
+ listSerials: (idOrSlug, { status, limit, offset } = {}) => this.#request('GET', `/v1/products/${segment(idOrSlug)}/serials`, {
137
+ query: { status, limit, offset },
138
+ }),
139
+ addSerials: (idOrSlug, { serials, text, delimiter }) => this.#request('POST', `/v1/products/${segment(idOrSlug)}/serials`, {
140
+ body: { serials, text, delimiter },
141
+ }),
142
+ deleteSerial: (idOrSlug, serialId) => this.#request('DELETE', `/v1/products/${segment(idOrSlug)}/serials/${segment(serialId)}`),
143
+ };
144
+ categories = {
145
+ list: () => this.#request('GET', '/v1/categories'),
146
+ create: ({ name }) => this.#request('POST', '/v1/categories', { body: { name } }),
147
+ update: (id, { name, sortOrder }) => this.#request('PATCH', `/v1/categories/${segment(id)}`, { body: { name, sortOrder } }),
148
+ delete: (id) => this.#request('DELETE', `/v1/categories/${segment(id)}`),
149
+ };
150
+ groups = {
151
+ list: () => this.#request('GET', '/v1/groups'),
152
+ get: (id) => this.#request('GET', `/v1/groups/${segment(id)}`),
153
+ create: (params) => this.#request('POST', '/v1/groups', { body: pick(params, GROUP_FIELDS) }),
154
+ update: (id, params) => this.#request('PATCH', `/v1/groups/${segment(id)}`, { body: pick(params, GROUP_FIELDS) }),
155
+ delete: (id) => this.#request('DELETE', `/v1/groups/${segment(id)}`),
156
+ };
157
+ coupons = {
158
+ list: () => this.#request('GET', '/v1/coupons'),
159
+ get: (id) => this.#request('GET', `/v1/coupons/${segment(id)}`),
160
+ create: (params) => this.#request('POST', '/v1/coupons', {
161
+ body: {
162
+ ...pick(params, [...COUPON_FIELDS, 'code']),
163
+ validFrom: optionalDate(params.validFrom),
164
+ validTo: optionalDate(params.validTo),
165
+ },
166
+ }),
167
+ update: (id, params) => this.#request('PATCH', `/v1/coupons/${segment(id)}`, {
168
+ body: {
169
+ ...pick(params, [...COUPON_FIELDS, 'isActive']),
170
+ validFrom: optionalDate(params.validFrom),
171
+ validTo: optionalDate(params.validTo),
172
+ },
173
+ }),
174
+ delete: (id) => this.#request('DELETE', `/v1/coupons/${segment(id)}`),
175
+ };
176
+ customers = {
177
+ list: ({ search, limit, offset } = {}) => this.#request('GET', '/v1/customers', { query: { search, limit, offset } }),
178
+ get: (id) => this.#request('GET', `/v1/customers/${segment(id)}`),
179
+ create: ({ email, name, note }) => this.#request('POST', '/v1/customers', { body: { email, name, note } }),
180
+ update: (id, { name, note }) => this.#request('PATCH', `/v1/customers/${segment(id)}`, { body: { name, note } }),
181
+ delete: (id) => this.#request('DELETE', `/v1/customers/${segment(id)}`),
182
+ adjustBalance: (id, { amount, reason }) => this.#request('POST', `/v1/customers/${segment(id)}/balance`, { body: { amount, reason } }),
183
+ };
184
+ feedback = {
185
+ list: ({ productId, rating, limit, offset } = {}) => this.#request('GET', '/v1/feedback', { query: { productId, rating, limit, offset } }),
186
+ get: (id) => this.#request('GET', `/v1/feedback/${segment(id)}`),
187
+ reply: (id, { message }) => this.#request('POST', `/v1/feedback/${segment(id)}/reply`, { body: { message } }),
188
+ appeal: (id, { reason }) => this.#request('POST', `/v1/feedback/${segment(id)}/appeal`, { body: { reason } }),
189
+ };
190
+ queries = {
191
+ list: ({ status, limit, offset } = {}) => this.#request('GET', '/v1/queries', { query: { status, limit, offset } }),
192
+ get: (id) => this.#request('GET', `/v1/queries/${segment(id)}`),
193
+ reply: (id, { message }) => this.#request('POST', `/v1/queries/${segment(id)}/reply`, { body: { message } }),
194
+ close: (id) => this.#request('POST', `/v1/queries/${segment(id)}/close`),
195
+ reopen: (id) => this.#request('POST', `/v1/queries/${segment(id)}/reopen`),
196
+ };
197
+ blacklist = this.#accessRules('blacklist');
198
+ whitelist = this.#accessRules('whitelist');
199
+ #accessRules(list) {
200
+ return {
201
+ list: ({ type } = {}) => this.#request('GET', `/v1/${list}`, { query: { type } }),
202
+ add: ({ type, value, note }) => this.#request('POST', `/v1/${list}`, { body: { type, value, note } }),
203
+ remove: (id) => this.#request('DELETE', `/v1/${list}/${segment(id)}`),
204
+ };
205
+ }
206
+ coins = {
207
+ createCheckout: ({ steamHex, coins, idempotencyKey, returnUrl }) => this.#request('POST', '/coins/checkout', {
208
+ body: { steamHex, coins, idempotencyKey, returnUrl },
209
+ }),
210
+ getCheckoutStatus: (reference) => this.#request('GET', `/coins/checkout/${segment(reference)}/status`),
67
211
  };
68
212
  subscriptions = {
69
213
  createCheckout: ({ productId, productSlug, customerEmail, externalRef, metadata, currency, returnUrl, }) => this.#request('POST', '/v1/subscriptions/checkout', {
@@ -84,6 +228,16 @@ export class Skalp {
84
228
  body: { productId, productSlug },
85
229
  }),
86
230
  cancel: (id) => this.#request('DELETE', `/v1/subscriptions/${segment(id)}`),
231
+ createUsageCharge: (id, { amount, currency, description, idempotencyKey, periodStart, periodEnd }) => this.#request('POST', `/v1/subscriptions/${segment(id)}/usage-charges`, {
232
+ body: {
233
+ amount,
234
+ currency,
235
+ description,
236
+ idempotencyKey,
237
+ periodStart: isoDate(periodStart),
238
+ periodEnd: isoDate(periodEnd),
239
+ },
240
+ }),
87
241
  listInvoices: (id) => this.#request('GET', `/v1/subscriptions/${segment(id)}/invoices`).then((r) => r.data),
88
242
  };
89
243
  licenses = {
@@ -95,6 +249,16 @@ export class Skalp {
95
249
  body: { licenseKey, serverIp },
96
250
  auth: false,
97
251
  }),
252
+ // Keys sold with the "License key" delivery. Needs the licenses:validate
253
+ // scope; a key with only that scope may ship inside your app.
254
+ validate: ({ key, productId, hardwareId }) => this.#request('POST', '/v1/licenses/validate', {
255
+ body: { key, productId, hardwareId },
256
+ }),
257
+ // Frees the machines a key is bound to. Needs licenses:write; call it
258
+ // from your server only.
259
+ resetDevices: ({ key, hardwareId }) => this.#request('POST', '/v1/licenses/devices/reset', {
260
+ body: { key, hardwareId },
261
+ }),
98
262
  };
99
263
  webhooks = {
100
264
  verify: async (payload, signature, secret) => (await this.#checkSignature(payload, signature, secret)) === undefined,