@skalpgg/sdk 1.1.0 → 1.4.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, 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.4.0";
4
4
  export interface SkalpOptions {
5
5
  apiKey?: string;
6
6
  webhookSecret?: string;
@@ -32,14 +32,80 @@ 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
+ };
83
+ readonly feedback: {
84
+ list: ({ productId, rating, limit, offset }?: ListFeedbackParams) => Promise<FeedbackList>;
85
+ get: (id: string) => Promise<Feedback>;
86
+ reply: (id: string, { message }: ReplyParams) => Promise<Feedback>;
87
+ appeal: (id: string, { reason }: AppealFeedbackParams) => Promise<Feedback>;
88
+ };
89
+ readonly queries: {
90
+ list: ({ status, limit, offset }?: ListQueriesParams) => Promise<SupportQueryList>;
91
+ get: (id: string) => Promise<SupportQueryDetails>;
92
+ reply: (id: string, { message }: ReplyParams) => Promise<SupportQueryDetails>;
93
+ close: (id: string) => Promise<SupportQueryDetails>;
94
+ reopen: (id: string) => Promise<SupportQueryDetails>;
95
+ };
96
+ readonly blacklist: {
97
+ list: ({ type }?: ListAccessRulesParams) => Promise<AccessRule[]>;
98
+ add: ({ type, value, note }: AddAccessRuleParams) => Promise<AccessRule>;
99
+ remove: (id: string) => Promise<Deleted>;
100
+ };
101
+ readonly whitelist: {
102
+ list: ({ type }?: ListAccessRulesParams) => Promise<AccessRule[]>;
103
+ add: ({ type, value, note }: AddAccessRuleParams) => Promise<AccessRule>;
104
+ remove: (id: string) => Promise<Deleted>;
105
+ };
106
+ readonly coins: {
107
+ createCheckout: ({ steamHex, coins, idempotencyKey, returnUrl }: CreateCoinCheckoutParams) => Promise<CoinCheckout>;
108
+ getCheckoutStatus: (reference: string) => Promise<CoinCheckoutStatus>;
43
109
  };
44
110
  readonly subscriptions: {
45
111
  createCheckout: ({ productId, productSlug, customerEmail, externalRef, metadata, currency, returnUrl, }: CreateSubscriptionCheckoutParams) => Promise<SubscriptionCheckout>;
@@ -48,6 +114,7 @@ export declare class Skalp {
48
114
  get: (id: string) => Promise<Subscription>;
49
115
  changePlan: (id: string, { productId, productSlug }: ChangeSubscriptionPlanParams) => Promise<Subscription>;
50
116
  cancel: (id: string) => Promise<Subscription>;
117
+ createUsageCharge: (id: string, { amount, currency, description, idempotencyKey, periodStart, periodEnd }: CreateUsageChargeParams) => Promise<UsageCharge>;
51
118
  listInvoices: (id: string) => Promise<Invoice[]>;
52
119
  };
53
120
  readonly licenses: {
@@ -55,6 +122,10 @@ export declare class Skalp {
55
122
  heartbeat: ({ licenseKey, serverIp }: HeartbeatParams) => Promise<{
56
123
  valid: boolean;
57
124
  }>;
125
+ validate: ({ key, productId, hardwareId }: ValidateLicenseParams) => Promise<LicenseValidation>;
126
+ resetDevices: ({ key, hardwareId }: ResetLicenseDevicesParams) => Promise<{
127
+ license: LicenseKeyInfo;
128
+ }>;
58
129
  };
59
130
  readonly webhooks: {
60
131
  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.4.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,116 @@ 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
+ };
183
+ feedback = {
184
+ list: ({ productId, rating, limit, offset } = {}) => this.#request('GET', '/v1/feedback', { query: { productId, rating, limit, offset } }),
185
+ get: (id) => this.#request('GET', `/v1/feedback/${segment(id)}`),
186
+ reply: (id, { message }) => this.#request('POST', `/v1/feedback/${segment(id)}/reply`, { body: { message } }),
187
+ appeal: (id, { reason }) => this.#request('POST', `/v1/feedback/${segment(id)}/appeal`, { body: { reason } }),
188
+ };
189
+ queries = {
190
+ list: ({ status, limit, offset } = {}) => this.#request('GET', '/v1/queries', { query: { status, limit, offset } }),
191
+ get: (id) => this.#request('GET', `/v1/queries/${segment(id)}`),
192
+ reply: (id, { message }) => this.#request('POST', `/v1/queries/${segment(id)}/reply`, { body: { message } }),
193
+ close: (id) => this.#request('POST', `/v1/queries/${segment(id)}/close`),
194
+ reopen: (id) => this.#request('POST', `/v1/queries/${segment(id)}/reopen`),
195
+ };
196
+ blacklist = this.#accessRules('blacklist');
197
+ whitelist = this.#accessRules('whitelist');
198
+ #accessRules(list) {
199
+ return {
200
+ list: ({ type } = {}) => this.#request('GET', `/v1/${list}`, { query: { type } }),
201
+ add: ({ type, value, note }) => this.#request('POST', `/v1/${list}`, { body: { type, value, note } }),
202
+ remove: (id) => this.#request('DELETE', `/v1/${list}/${segment(id)}`),
203
+ };
204
+ }
205
+ coins = {
206
+ createCheckout: ({ steamHex, coins, idempotencyKey, returnUrl }) => this.#request('POST', '/coins/checkout', {
207
+ body: { steamHex, coins, idempotencyKey, returnUrl },
208
+ }),
209
+ getCheckoutStatus: (reference) => this.#request('GET', `/coins/checkout/${segment(reference)}/status`),
67
210
  };
68
211
  subscriptions = {
69
212
  createCheckout: ({ productId, productSlug, customerEmail, externalRef, metadata, currency, returnUrl, }) => this.#request('POST', '/v1/subscriptions/checkout', {
@@ -84,6 +227,16 @@ export class Skalp {
84
227
  body: { productId, productSlug },
85
228
  }),
86
229
  cancel: (id) => this.#request('DELETE', `/v1/subscriptions/${segment(id)}`),
230
+ createUsageCharge: (id, { amount, currency, description, idempotencyKey, periodStart, periodEnd }) => this.#request('POST', `/v1/subscriptions/${segment(id)}/usage-charges`, {
231
+ body: {
232
+ amount,
233
+ currency,
234
+ description,
235
+ idempotencyKey,
236
+ periodStart: isoDate(periodStart),
237
+ periodEnd: isoDate(periodEnd),
238
+ },
239
+ }),
87
240
  listInvoices: (id) => this.#request('GET', `/v1/subscriptions/${segment(id)}/invoices`).then((r) => r.data),
88
241
  };
89
242
  licenses = {
@@ -95,6 +248,16 @@ export class Skalp {
95
248
  body: { licenseKey, serverIp },
96
249
  auth: false,
97
250
  }),
251
+ // Keys sold with the "License key" delivery. Needs the licenses:validate
252
+ // scope; a key with only that scope may ship inside your app.
253
+ validate: ({ key, productId, hardwareId }) => this.#request('POST', '/v1/licenses/validate', {
254
+ body: { key, productId, hardwareId },
255
+ }),
256
+ // Frees the machines a key is bound to. Needs licenses:write; call it
257
+ // from your server only.
258
+ resetDevices: ({ key, hardwareId }) => this.#request('POST', '/v1/licenses/devices/reset', {
259
+ body: { key, hardwareId },
260
+ }),
98
261
  };
99
262
  webhooks = {
100
263
  verify: async (payload, signature, secret) => (await this.#checkSignature(payload, signature, secret)) === undefined,