@vrplatform/hyperline-client 1.0.30 → 1.0.32

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.
Files changed (60) hide show
  1. package/build/main/_types.generated.d.ts +44928 -16283
  2. package/build/main/_types.generated.js.map +1 -1
  3. package/build/main/client.d.ts +1634 -428
  4. package/build/main/client.js +87 -62
  5. package/build/main/client.js.map +1 -1
  6. package/build/main/const.d.ts +19 -20
  7. package/build/main/const.js +31 -22
  8. package/build/main/const.js.map +1 -1
  9. package/build/main/hyperlineError.d.ts +5 -0
  10. package/build/main/hyperlineError.js +14 -0
  11. package/build/main/hyperlineError.js.map +1 -0
  12. package/build/main/index.d.ts +1 -0
  13. package/build/main/index.js +1 -0
  14. package/build/main/index.js.map +1 -1
  15. package/build/main/types.d.ts +3 -10
  16. package/build/main/types.js.map +1 -1
  17. package/build/main/utils/calculateMrr.d.ts +5 -5
  18. package/build/main/utils/calculateMrr.js +7 -16
  19. package/build/main/utils/calculateMrr.js.map +1 -1
  20. package/build/main/utils/index.d.ts +0 -1
  21. package/build/main/utils/index.js +0 -1
  22. package/build/main/utils/index.js.map +1 -1
  23. package/build/module/_types.generated.d.ts +44928 -16283
  24. package/build/module/_types.generated.js.map +1 -1
  25. package/build/module/client.d.ts +1634 -428
  26. package/build/module/client.js +87 -62
  27. package/build/module/client.js.map +1 -1
  28. package/build/module/const.d.ts +19 -20
  29. package/build/module/const.js +31 -22
  30. package/build/module/const.js.map +1 -1
  31. package/build/module/hyperlineError.d.ts +5 -0
  32. package/build/module/hyperlineError.js +16 -0
  33. package/build/module/hyperlineError.js.map +1 -0
  34. package/build/module/index.d.ts +1 -0
  35. package/build/module/index.js +1 -0
  36. package/build/module/index.js.map +1 -1
  37. package/build/module/types.d.ts +3 -10
  38. package/build/module/types.js.map +1 -1
  39. package/build/module/utils/calculateMrr.d.ts +5 -5
  40. package/build/module/utils/calculateMrr.js +7 -16
  41. package/build/module/utils/calculateMrr.js.map +1 -1
  42. package/build/module/utils/index.d.ts +0 -1
  43. package/build/module/utils/index.js +0 -1
  44. package/build/module/utils/index.js.map +1 -1
  45. package/package.json +4 -4
  46. package/src/_types.generated.ts +44918 -16274
  47. package/src/client.ts +152 -92
  48. package/src/const.ts +41 -28
  49. package/src/hyperlineError.ts +17 -0
  50. package/src/index.ts +1 -0
  51. package/src/types.ts +3 -11
  52. package/src/utils/calculateMrr.ts +15 -33
  53. package/src/utils/index.ts +0 -1
  54. package/build/main/utils/isTestingTeam.d.ts +0 -5
  55. package/build/main/utils/isTestingTeam.js +0 -4
  56. package/build/main/utils/isTestingTeam.js.map +0 -1
  57. package/build/module/utils/isTestingTeam.d.ts +0 -5
  58. package/build/module/utils/isTestingTeam.js +0 -4
  59. package/build/module/utils/isTestingTeam.js.map +0 -1
  60. package/src/utils/isTestingTeam.ts +0 -11
package/src/client.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { utc } from '@vrplatform/utils';
2
- import type { operations, paths } from './_types.generated';
1
+ import type { paths } from './_types.generated';
2
+ import { HyperlineError } from './hyperlineError';
3
3
  import { BillableEvents, CreateBillableEvent } from './types';
4
4
  import { arrayBufferToBase64 } from './utils';
5
5
 
@@ -64,20 +64,13 @@ export class HyperlineAPI {
64
64
  (content ? JSON.stringify(content) : null) ||
65
65
  'Unknown error';
66
66
 
67
- if (status === 429) {
68
- // get difference between rate limit reset and now
69
- const rateLimitReset = utc(result?.headers.get('x-ratelimit-reset'));
70
- const diff = rateLimitReset.diff(utc(), 'milliseconds');
71
-
72
- // wait until rate limit resets
73
- await new Promise((resolve) => setTimeout(resolve, diff));
74
-
75
- return await this.fetchBasic<T>(path, options, data);
76
- } else if (status === 404 && method === 'GET') {
67
+ if (status === 404 && method === 'GET') {
77
68
  return null;
78
69
  } else {
79
- throw new Error(
80
- `Hyperline Error ${status} (${statusText}) in ${path} for ${method}: ${message}`
70
+ throw new HyperlineError(
71
+ `Hyperline Error ${status} (${statusText}) in ${path} for ${method}: ${message}`,
72
+ status,
73
+ result.headers.get('Retry-After')
81
74
  );
82
75
  }
83
76
  } else if (result.headers.get('content-type')?.includes('application/pdf'))
@@ -125,6 +118,25 @@ export class HyperlineAPI {
125
118
 
126
119
  // CUSTOMER
127
120
 
121
+ async getOrganisation(id: string) {
122
+ if (!id) throw new Error('Missing organisation ID');
123
+ return await this.fetch<
124
+ paths['/v1/organisations/{id}']['get']['responses'][200]['content']['application/json']
125
+ >(`/v1/organisations/${id}`);
126
+ }
127
+
128
+ async updateOrganisation(
129
+ id: string,
130
+ data: NonNullable<
131
+ paths['/v1/organisations/{id}']['patch']['requestBody']
132
+ >['content']['application/json']
133
+ ) {
134
+ if (!id) throw new Error('Missing organisation ID');
135
+ return await this.fetch<
136
+ paths['/v1/organisations/{id}']['patch']['responses'][200]['content']['application/json']
137
+ >(`/v1/organisations/${id}`, { method: 'PATCH' }, data);
138
+ }
139
+
128
140
  async getCustomer(id?: string) {
129
141
  if (!id) {
130
142
  this.logger.error('Missing customer ID', { id });
@@ -132,8 +144,8 @@ export class HyperlineAPI {
132
144
  }
133
145
 
134
146
  return await this.fetch<
135
- paths['/v1/customers/{id}']['get']['responses'][200]['content']['application/json']
136
- >(`/v1/customers/${id}`);
147
+ paths['/v2/customers/{id}']['get']['responses'][200]['content']['application/json']
148
+ >(`/v2/customers/${id}`);
137
149
  }
138
150
 
139
151
  async getCustomerById(id?: string) {
@@ -142,48 +154,51 @@ export class HyperlineAPI {
142
154
  throw new Error('Missing tenant ID');
143
155
  }
144
156
 
145
- const results = await this.fetch<
146
- paths['/v1/customers']['get']['responses'][200]['content']['application/json']
147
- >(`/v1/customers?external_id__equals=${id}`);
157
+ const results = await this.getCustomers({
158
+ external_id__equals: id,
159
+ status: 'all',
160
+ limit: 1,
161
+ });
148
162
 
149
163
  return results?.data[0];
150
164
  }
151
165
 
152
166
  async getCustomers(
153
- skip = 0,
154
- take = 100,
155
- query: operations['getCustomers']['parameters']['query'] = {}
167
+ query: paths['/v2/customers']['get']['parameters']['query'] = {}
156
168
  ) {
157
169
  const queryParams = new URLSearchParams();
158
- queryParams.set('take', take.toString());
159
- queryParams.set('skip', skip.toString());
160
170
 
161
- for (const [key, value] of Object.entries(query)) {
171
+ for (const [key, value] of Object.entries({ limit: 100, ...query })) {
162
172
  if (value === undefined || value === null) continue;
163
173
  queryParams.set(key, value.toString());
164
174
  }
165
175
 
166
176
  return await this.fetch<
167
- paths['/v1/customers']['get']['responses'][200]['content']['application/json']
168
- >(`/v1/customers?${queryParams.toString()}`);
177
+ paths['/v2/customers']['get']['responses'][200]['content']['application/json']
178
+ >(`/v2/customers?${queryParams.toString()}`);
169
179
  }
170
180
 
171
181
  async getAllCustomers(
172
- items: paths['/v1/customers']['get']['responses'][200]['content']['application/json']['data'] = [],
173
- query: operations['getCustomers']['parameters']['query'] = {}
182
+ items: paths['/v2/customers']['get']['responses'][200]['content']['application/json']['data'] = [],
183
+ query: paths['/v2/customers']['get']['parameters']['query'] = {}
174
184
  ): Promise<
175
- paths['/v1/customers']['get']['responses'][200]['content']['application/json']['data']
185
+ paths['/v2/customers']['get']['responses'][200]['content']['application/json']['data']
176
186
  > {
177
- const { data } = (await this.getCustomers(items.length, 100, {
187
+ await new Promise((resolve) => setTimeout(resolve, 300));
188
+ const page = await this.getCustomers({
178
189
  status: 'all',
179
190
  ...query,
180
- })) || {
181
- data: [],
182
- };
191
+ });
192
+ if (!page) throw new Error('Customer list not found');
193
+ if (page.has_more && !page.next_cursor)
194
+ throw new Error('Incomplete customer list');
183
195
 
184
- return data.length
185
- ? this.getAllCustomers([...items, ...data], { status: 'all', ...query })
186
- : [...items, ...data];
196
+ return page.next_cursor
197
+ ? this.getAllCustomers([...items, ...page.data], {
198
+ ...query,
199
+ cursor: page.next_cursor,
200
+ })
201
+ : [...items, ...page.data];
187
202
  }
188
203
 
189
204
  async createCustomer(
@@ -249,6 +264,39 @@ export class HyperlineAPI {
249
264
  >(`/v1/customers/${id}/portal`);
250
265
  }
251
266
 
267
+ // SUBSCRIPTION TEMPLATES
268
+
269
+ async getSubscriptionTemplate(id: string) {
270
+ if (!id) {
271
+ this.logger.error('Missing subscription template ID', { id });
272
+ throw new Error('Missing subscription template ID');
273
+ }
274
+
275
+ return await this.fetch<
276
+ paths['/v1/subscriptions/templates/{id}']['get']['responses'][200]['content']['application/json']
277
+ >(`/v1/subscriptions/templates/${id}`);
278
+ }
279
+
280
+ async previewSubscriptionTransition(
281
+ data: NonNullable<
282
+ paths['/v2/subscriptions/transitions/preview']['post']['requestBody']
283
+ >['content']['application/json']
284
+ ) {
285
+ return await this.fetch<
286
+ paths['/v2/subscriptions/transitions/preview']['post']['responses'][200]['content']['application/json']
287
+ >('/v2/subscriptions/transitions/preview', { method: 'POST' }, data);
288
+ }
289
+
290
+ async createSubscriptionTransition(
291
+ data: NonNullable<
292
+ paths['/v2/subscriptions/transitions']['post']['requestBody']
293
+ >['content']['application/json']
294
+ ) {
295
+ return await this.fetch<
296
+ paths['/v2/subscriptions/transitions']['post']['responses'][200]['content']['application/json']
297
+ >('/v2/subscriptions/transitions', { method: 'POST' }, data);
298
+ }
299
+
252
300
  // SUBSCRIPTION
253
301
 
254
302
  async getSubscription(id: string, liveBilling = false) {
@@ -267,7 +315,7 @@ export class HyperlineAPI {
267
315
  async getSubscriptions(
268
316
  skip = 0,
269
317
  take = 100,
270
- query: operations['getSubscriptions']['parameters']['query'] = {
318
+ query: paths['/v2/subscriptions']['get']['parameters']['query'] = {
271
319
  status: 'all',
272
320
  }
273
321
  ) {
@@ -287,7 +335,7 @@ export class HyperlineAPI {
287
335
 
288
336
  async getAllSubscriptions(
289
337
  items: paths['/v2/subscriptions']['get']['responses'][200]['content']['application/json']['data'] = [],
290
- query: operations['getSubscriptions']['parameters']['query'] = {}
338
+ query: paths['/v2/subscriptions']['get']['parameters']['query'] = {}
291
339
  ): Promise<
292
340
  paths['/v2/subscriptions']['get']['responses'][200]['content']['application/json']['data']
293
341
  > {
@@ -312,6 +360,12 @@ export class HyperlineAPI {
312
360
  >('/v2/subscriptions', { method: 'POST' }, data);
313
361
  }
314
362
 
363
+ async activateSubscription(id: string) {
364
+ return await this.fetch<
365
+ paths['/v1/subscriptions/{id}/activate']['post']['responses'][200]['content']['application/json']
366
+ >(`/v1/subscriptions/${id}/activate`, { method: 'POST' });
367
+ }
368
+
315
369
  async updateSubscription(
316
370
  id: string,
317
371
  data: NonNullable<
@@ -334,6 +388,17 @@ export class HyperlineAPI {
334
388
  >(`/v1/subscriptions/${id}/update`, { method: 'POST' }, data);
335
389
  }
336
390
 
391
+ async createSubscriptionUpdates(
392
+ id: string,
393
+ data: NonNullable<
394
+ paths['/v1/subscriptions/{id}/update-many']['post']['requestBody']
395
+ >['content']['application/json']
396
+ ) {
397
+ return await this.fetch<
398
+ paths['/v1/subscriptions/{id}/update-many']['post']['responses'][201]['content']['application/json']
399
+ >(`/v1/subscriptions/${id}/update-many`, { method: 'POST' }, data);
400
+ }
401
+
337
402
  async cancelSubscription(
338
403
  id: string,
339
404
  data: NonNullable<
@@ -359,27 +424,30 @@ export class HyperlineAPI {
359
424
  });
360
425
  }
361
426
 
362
- // PLAN
363
-
364
- async getPlan(id: string) {
365
- if (!id) {
366
- this.logger.error('Missing plan ID', { id });
367
- throw new Error('Missing plan ID');
368
- }
427
+ // INVOICE
369
428
 
429
+ async createInvoice(
430
+ data: NonNullable<
431
+ paths['/v1/invoices']['post']['requestBody']
432
+ >['content']['application/json']
433
+ ) {
370
434
  return await this.fetch<
371
- paths['/v1/plans/{id}']['get']['responses'][200]['content']['application/json']
372
- >(`/v1/plans/${id}`);
435
+ paths['/v1/invoices']['post']['responses'][201]['content']['application/json']
436
+ >('/v1/invoices', { method: 'POST' }, data);
373
437
  }
374
438
 
375
- async getPlans(skip = 0, take = 100) {
439
+ async updateInvoice(
440
+ id: string,
441
+ data: NonNullable<
442
+ paths['/v1/invoices/{id}']['patch']['requestBody']
443
+ >['content']['application/json']
444
+ ) {
445
+ if (!id) throw new Error('Missing invoice ID');
376
446
  return await this.fetch<
377
- paths['/v1/plans']['get']['responses'][200]['content']['application/json']
378
- >(`/v1/plans?take=${take}&skip=${skip}`);
447
+ paths['/v1/invoices/{id}']['patch']['responses'][200]['content']['application/json']
448
+ >(`/v1/invoices/${id}`, { method: 'PATCH' }, data);
379
449
  }
380
450
 
381
- // INVOICE
382
-
383
451
  async getInvoice(id: string) {
384
452
  if (!id) {
385
453
  this.logger.error('Missing invoice ID', { id });
@@ -387,47 +455,40 @@ export class HyperlineAPI {
387
455
  }
388
456
 
389
457
  return await this.fetch<
390
- paths['/v1/invoices/{id}']['get']['responses'][200]['content']['application/json']
391
- >(`/v1/invoices/${id}`);
458
+ paths['/v2/invoices/{id}']['get']['responses'][200]['content']['application/json']
459
+ >(`/v2/invoices/${id}`);
460
+ }
461
+
462
+ async validateInvoice(id: string) {
463
+ if (!id) throw new Error('Missing invoice ID');
464
+ return await this.fetch<
465
+ paths['/v1/invoices/{id}/validate']['post']['responses'][200]['content']['application/json']
466
+ >(`/v1/invoices/${id}/validate`, { method: 'POST' });
392
467
  }
393
468
 
394
469
  async getInvoices(
395
- skip = 0,
396
- take = 100,
397
- query: operations['getInvoices']['parameters']['query'] = { status: 'all' }
470
+ query: paths['/v2/invoices']['get']['parameters']['query'] = {}
398
471
  ) {
399
472
  const queryParams = new URLSearchParams();
400
- queryParams.set('take', take.toString());
401
- queryParams.set('skip', skip.toString());
402
-
403
- for (const [key, value] of Object.entries(query)) {
473
+ for (const [key, value] of Object.entries({
474
+ limit: 100,
475
+ status: 'all',
476
+ ...query,
477
+ })) {
404
478
  if (value === undefined || value === null) continue;
405
479
  queryParams.set(key, value.toString());
406
480
  }
407
481
 
408
482
  return await this.fetch<
409
- paths['/v1/invoices']['get']['responses'][200]['content']['application/json']
410
- >(`/v1/invoices?${queryParams.toString()}`);
483
+ paths['/v2/invoices']['get']['responses'][200]['content']['application/json']
484
+ >(`/v2/invoices?${queryParams.toString()}`);
411
485
  }
412
486
 
413
487
  async getInvoicesByCustomerId(
414
488
  id: string,
415
- filters?: Partial<operations['getInvoices']['parameters']['query']>
489
+ filters?: paths['/v2/invoices']['get']['parameters']['query']
416
490
  ) {
417
- const queryParams = new URLSearchParams();
418
- queryParams.append('customerId__equals', id);
419
-
420
- if (filters) {
421
- Object.entries(filters).forEach(([key, value]) => {
422
- if (value !== undefined && value !== null) {
423
- queryParams.append(key, value.toString());
424
- }
425
- });
426
- }
427
-
428
- return await this.fetch<
429
- paths['/v1/invoices']['get']['responses'][200]['content']['application/json']
430
- >(`/v1/invoices?${queryParams.toString()}`);
491
+ return await this.getInvoices({ ...filters, customer_id: id });
431
492
  }
432
493
 
433
494
  async getInvoiceDownload(id: string) {
@@ -441,21 +502,20 @@ export class HyperlineAPI {
441
502
  }
442
503
 
443
504
  async getAllInvoices(
444
- items: paths['/v1/invoices']['get']['responses'][200]['content']['application/json']['data'] = [],
445
- query: operations['getInvoices']['parameters']['query'] = { status: 'all' }
505
+ items: paths['/v2/invoices']['get']['responses'][200]['content']['application/json']['data'] = [],
506
+ query: paths['/v2/invoices']['get']['parameters']['query'] = {}
446
507
  ): Promise<
447
- paths['/v1/invoices']['get']['responses'][200]['content']['application/json']['data']
508
+ paths['/v2/invoices']['get']['responses'][200]['content']['application/json']['data']
448
509
  > {
449
- const { data } = (await this.getInvoices(items.length, 100, {
450
- status: 'all',
451
- ...query,
452
- })) || {
453
- data: [],
454
- };
510
+ const page = await this.getInvoices(query);
511
+ if (!page) throw new Error('Invoice list not found');
455
512
 
456
- return data.length
457
- ? this.getAllInvoices([...items, ...data], { status: 'all', ...query })
458
- : [...items, ...data];
513
+ return page.next_cursor
514
+ ? this.getAllInvoices([...items, ...page.data], {
515
+ ...query,
516
+ cursor: page.next_cursor,
517
+ })
518
+ : [...items, ...page.data];
459
519
  }
460
520
 
461
521
  async voidInvoice(
@@ -505,7 +565,7 @@ export class HyperlineAPI {
505
565
  }
506
566
 
507
567
  async createBillableEvent(data: CreateBillableEvent[]) {
508
- if (!data.length) return { events_created: [] };
568
+ if (!data.length) return { events_created: [], events_failed: [] };
509
569
 
510
570
  return await this.fetchIngest<BillableEvents>(
511
571
  '/v1/events/batch',
package/src/const.ts CHANGED
@@ -7,15 +7,20 @@ export const DEMO_TEST_PARTNER_TENANT_ID =
7
7
  '0b6a7bd6-56cc-46b5-b2ab-1bd7d1e994fa';
8
8
  export const XIMPLIFI_TENANT_ID = 'd6ab2bbe-e0fc-48d9-89a3-c3c05f4e79b5';
9
9
  export const TEST_TENANT_ID = 'fa4d1a20-2f47-4021-9c72-c5a809be9c48';
10
-
11
- // Customers
12
- export const XIMPLIFI_CUSTOMER_ID = 'cus_HDUZawnaJbPa9p';
13
- export const XIMPLIFI_DEV_CUSTOMER_ID = 'cus_dNUGCETGYVu3ZV';
10
+ export const BOBW_TENANT_ID = 'cb23b44b-c96b-4fc4-95aa-1bf71b1c0f92';
11
+ export const HOSTAWAY_PRODUCTION_TENANT_ID =
12
+ '4d1583e6-1c76-4bd2-bf8d-ca07d45eedb7';
13
+ export const HOSTAWAY_STAGING_TENANT_ID =
14
+ '10834664-606b-4b92-acb9-f3e454632f7a';
15
+ export const HOSTAWAY_COSELLING_TENANT_ID =
16
+ '636dcf13-7b34-4a7f-9b45-60bbb1380d15';
17
+ export const HOSTAWAY_PARTNER_TENANT_IDS = [
18
+ HOSTAWAY_STAGING_TENANT_ID,
19
+ HOSTAWAY_PRODUCTION_TENANT_ID,
20
+ HOSTAWAY_COSELLING_TENANT_ID,
21
+ ];
14
22
 
15
23
  // Coupons
16
- export const XIMPLIFI_COUPON_ID = 'cou_MXCaik3r19_Xkn';
17
- export const XIMPLIFI_DEV_COUPON_ID = 'cou_MGtxHbT8IR1gK9';
18
-
19
24
  export const ACCOUNTING_PARTNER_COUPON_ID = 'cou_PJQ328MBAD9kxY';
20
25
  export const ACCOUNTING_PARTNER_DEV_COUPON_ID = 'cou_y6UfaI2X1m34Qg';
21
26
 
@@ -40,32 +45,39 @@ export const HYPERLINE_PARTNER_TIER_COUPON_IDS = {
40
45
  60: HYPERLINE_PARTNER_TIER_60_PERCENT_COUPON_ID,
41
46
  } as const;
42
47
 
43
- // Plans
44
- export const HYPERLINE_DEFAULT_PLAN_ID = 'plan_Dyb5P0ICZ6HsIT';
45
- export const HYPERLINE_DEFAULT_DEV_PLAN_ID = 'plan_UTCVXFQnKD0oMS';
46
- export const HYPERLINE_DEFAULT_PLAN_NAME = 'All-in-One VRP Subscription';
47
- export const HYPERLINE_DEFAULT_PRODUCT_ID = 'itm_9NU7ffT14I4ris';
48
- export const HYPERLINE_DEFAULT_MIN_AMOUNT = 12500;
48
+ // Subscription templates
49
49
 
50
- export const HYPERLINE_NEW_PRICING_DIRECT_PRODUCT_ID = 'itm_VeR6AJi9OyUQN2';
51
- export const HYPERLINE_NEW_PRICING_PARTNER_CLIENT_PRODUCT_ID =
52
- 'itm_wrrT0HuAZ8Tlh1';
50
+ export const DIRECT_PRICING_TEMPLATE_ID = 'subt_Knj7TspKQNAdgE';
51
+ export const DIRECT_PRICING_DEV_TEMPLATE_ID = 'subt_3kdBL2APUz94Vh';
53
52
 
54
- export const HYPERLINE_PARTNER_PLAN_ID = 'plan_vkYJcCz7z0XD0N';
55
- export const HYPERLINE_PARTNER_DEV_PLAN_ID = 'plan_8qtj8T4j0W3oiS';
56
- export const HYPERLINE_PARTNER_PLAN_NAME = 'VRP Subscription - Partners';
57
- export const HYPERLINE_PARTNER_PRODUCT_ID = 'itm_AJBM0_jPsrqizl';
53
+ export const PARTNER_CLIENT_PRICING_TEMPLATE_ID = 'subt_p0bWvUpe59ilo4';
54
+ export const PARTNER_CLIENT_PRICING_DEV_TEMPLATE_ID = 'subt_c972Mz800ZB3C8';
58
55
 
59
- export const GL_HYPERLINE_DEFAULT_PLAN_ID = 'plan_JXviWSpU5yuokD';
60
- export const GL_HYPERLINE_DEFAULT_DEV_PLAN_ID = 'plan_Nnozk8_TfYqTkj';
61
- export const GL_HYPERLINE_DEFAULT_PLAN_NAME = 'VRTrust Subscription';
62
- export const GL_HYPERLINE_DEFAULT_PRODUCT_ID = 'itm_NRTRBQ0fc8bFGW';
63
- export const GL_HYPERLINE_DEFAULT_MIN_AMOUNT = 25000;
56
+ export const PARTNER_PRICING_TEMPLATE_ID = 'subt_qt58CknWGtPHxH';
57
+ export const PARTNER_PRICING_DEV_TEMPLATE_ID = 'subt_NIoe1kA3Xs4353';
58
+
59
+ export const HOSTAWAY_PRICING_TEMPLATE_ID = 'subt_gVo8boDZBZvsZk';
60
+ export const HOSTAWAY_PRICING_DEV_TEMPLATE_ID = 'subt_PkEm701PH78Bgn';
64
61
 
65
- export const GL_HYPERLINE_PARTNER_PLAN_ID = 'plan_vyxckdvUi8X_mr';
66
- export const GL_HYPERLINE_PARTNER_DEV_PLAN_ID = 'plan_CVXQ47ErOWANhJ';
67
- export const GL_HYPERLINE_PARTNER_PLAN_NAME = 'VRTrust Subscription - Partners';
62
+ export const BOBW_PRICING_TEMPLATE_ID = 'subt_XpHcieqR2Exn4q';
63
+ export const BOBW_PRICING_DEV_TEMPLATE_ID = 'subt_yRmLj6Fdr5R9Sh';
64
+
65
+ export const SUBSCRIPTION_TEMPLATE_NAMES = [
66
+ 'VRPlatform Subscription - Direct',
67
+ 'VRPlatform Subscription - Partner Clients',
68
+ 'VRPlatform Subscription - Partner',
69
+ 'VRPlatform Subscription - Hostaway',
70
+ 'VRPlatform Subscription - BobW',
71
+ ] as const;
72
+
73
+ // Product identifiers retained only for historical invoice comparisons.
74
+ export const HYPERLINE_DEFAULT_PRODUCT_ID = 'itm_9NU7ffT14I4ris';
75
+ export const HYPERLINE_PARTNER_PRODUCT_ID = 'itm_AJBM0_jPsrqizl';
76
+ export const GL_HYPERLINE_DEFAULT_PRODUCT_ID = 'itm_NRTRBQ0fc8bFGW';
68
77
  export const GL_HYPERLINE_PARTNER_PRODUCT_ID = 'itm_bE4y9lhrZ6Qtsf';
78
+ export const HYPERLINE_NEW_PRICING_DIRECT_PRODUCT_ID = 'itm_VeR6AJi9OyUQN2';
79
+ export const HYPERLINE_NEW_PRICING_PARTNER_CLIENT_PRODUCT_ID =
80
+ 'itm_wrrT0HuAZ8Tlh1';
69
81
 
70
82
  export const WEBHOOKS = {
71
83
  'payment_method.created': 'hyperline_payment_method_created',
@@ -73,6 +85,7 @@ export const WEBHOOKS = {
73
85
  'payment_method.deleted': 'hyperline_payment_method_deleted',
74
86
  'payment_method.expired': 'hyperline_payment_method_expired',
75
87
  'subscription.activated': 'hyperline_subscription_status_changed',
88
+ 'subscription.updated': 'hyperline_subscription_status_changed',
76
89
  'subscription.reactivated': 'hyperline_subscription_status_changed',
77
90
  'subscription.cancellation_scheduled':
78
91
  'hyperline_subscription_cancellation_scheduled',
@@ -0,0 +1,17 @@
1
+ export class HyperlineError extends Error {
2
+ readonly retryAt?: Date;
3
+
4
+ constructor(
5
+ message: string,
6
+ readonly status: number,
7
+ retryAfter: string | null
8
+ ) {
9
+ super(message);
10
+ this.name = 'HyperlineError';
11
+ if (status !== 429 || retryAfter === null) return;
12
+ const seconds = Number(retryAfter);
13
+ if (!Number.isFinite(seconds) || seconds < 0)
14
+ throw new Error('Invalid Hyperline Retry-After header');
15
+ this.retryAt = new Date(Date.now() + seconds * 1000);
16
+ }
17
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './client';
2
2
  export * from './const';
3
3
  export * from './hooks';
4
+ export * from './hyperlineError';
4
5
  export * from './types';
5
6
  export * from './utils';
package/src/types.ts CHANGED
@@ -14,18 +14,9 @@ export type HyperlinePayload<T extends HyperlineWebhook> = {
14
14
  data: Record<string, any>;
15
15
  };
16
16
 
17
- export type BillableEvent = {
18
- record: {
19
- id: '202408_cus_w_Q7M9bXcHMKB6';
20
- amount: 26;
21
- };
22
- timestamp: '2024-08-07T07:35:20.307Z';
23
- event_type: 'api_call';
24
- customer_id: 'cus_w_Q7M9bXcHMKB6';
25
- };
26
-
27
17
  export type BillableEvents = {
28
- events_created: BillableEvent[];
18
+ events_created: CreateBillableEvent[];
19
+ events_failed: unknown[];
29
20
  };
30
21
 
31
22
  export type CreateBillableEvent = {
@@ -39,5 +30,6 @@ export type CreateBillableEvent = {
39
30
  amount?: number;
40
31
  amountGl?: number;
41
32
  amountNonGl?: number;
33
+ amountAll?: number;
42
34
  };
43
35
  };
@@ -1,11 +1,3 @@
1
- import {
2
- GL_HYPERLINE_DEFAULT_DEV_PLAN_ID,
3
- GL_HYPERLINE_DEFAULT_MIN_AMOUNT,
4
- GL_HYPERLINE_DEFAULT_PLAN_ID,
5
- HYPERLINE_DEFAULT_DEV_PLAN_ID,
6
- HYPERLINE_DEFAULT_MIN_AMOUNT,
7
- HYPERLINE_DEFAULT_PLAN_ID,
8
- } from '../const';
9
1
  import { useHyperlineByToken } from '../hooks';
10
2
  import type { HyperlineEntities } from '../types';
11
3
 
@@ -19,22 +11,22 @@ export async function calculateMrr(
19
11
  liveBilling?: boolean
20
12
  ): Promise<{
21
13
  customer_id: string;
22
- plan_id: string | null;
23
- products?: unknown[];
24
14
  coupons?: (
25
15
  | {
26
16
  type: 'amount';
27
- repeat: 'once' | 'forever' | 'custom';
17
+ repeat: HyperlineEntities['Coupon']['repeat'];
28
18
  discount_amount: number;
29
19
  }
30
20
  | {
31
21
  type: 'percent';
32
- repeat: 'once' | 'forever' | 'custom';
22
+ repeat: HyperlineEntities['Coupon']['repeat'];
33
23
  discount_percent: number;
34
24
  }
35
25
  )[];
36
26
  phases?: {
37
27
  type: HyperlineEntities['SubscriptionDetails']['phases'][number]['type'];
28
+ status?: HyperlineEntities['SubscriptionDetails']['phases'][number]['status'];
29
+ do_not_invoice_phase: boolean;
38
30
  products: {
39
31
  min_amount?: number | null;
40
32
  max_amount?: number | null;
@@ -62,24 +54,27 @@ export async function calculateMrr(
62
54
  } | null>;
63
55
  getInvoicesByCustomerId(id: string): Promise<{
64
56
  data: {
65
- emitted_at: string;
57
+ issued_at: string;
66
58
  total_amount: number;
67
59
  }[];
68
60
  } | null>;
69
61
  } = useHyperlineByToken(process.env.HYPERLINE_TOKEN!),
70
62
  includeLiveBilling = false
71
63
  ) {
72
- const _subscription = await hyperline.getSubscription(
64
+ const subscription = await hyperline.getSubscription(
73
65
  subscriptionId,
74
66
  includeLiveBilling
75
67
  );
76
- if (!_subscription) throw new Error('Subscription not found');
77
-
78
- const { products, phases, ...subscription } = _subscription;
68
+ if (!subscription) throw new Error('Subscription not found');
79
69
 
80
70
  let mrr = 0;
81
71
  const activeListings = _activeListings || 0;
82
- const phase = phases?.find((x) => x.type === 'standard');
72
+ const phase = subscription.phases?.find(
73
+ (x) =>
74
+ x.type === 'standard' &&
75
+ x.status !== 'finished' &&
76
+ !x.do_not_invoice_phase
77
+ );
83
78
  const product = phase?.products?.[0];
84
79
  const type = product?.prices?.[0].type;
85
80
  if (type === 'volume') {
@@ -148,20 +143,7 @@ export async function calculateMrr(
148
143
  ? firstTier.amount *
149
144
  ((firstTier.to - firstTier.from) / firstTier.unit_count)
150
145
  : 0;
151
- const plan_min_amount = [
152
- GL_HYPERLINE_DEFAULT_PLAN_ID,
153
- GL_HYPERLINE_DEFAULT_DEV_PLAN_ID,
154
- ].includes(subscription.plan_id!)
155
- ? GL_HYPERLINE_DEFAULT_MIN_AMOUNT
156
- : [HYPERLINE_DEFAULT_PLAN_ID, HYPERLINE_DEFAULT_DEV_PLAN_ID].includes(
157
- subscription.plan_id!
158
- )
159
- ? HYPERLINE_DEFAULT_MIN_AMOUNT
160
- : 0;
161
-
162
- const min_amount =
163
- ((product_min_amount || plan_min_amount || inferred_min_amount) ?? 0) /
164
- 100;
146
+ const min_amount = (product_min_amount ?? inferred_min_amount) / 100;
165
147
  mrr = Math.max(mrr, min_amount);
166
148
 
167
149
  const max_amount = ((product_max_amount || 0) ?? 0) / 100;
@@ -185,7 +167,7 @@ export async function calculateMrr(
185
167
 
186
168
  // sort invoices by date
187
169
  const invoices = data
188
- ?.sort((a, b) => a.emitted_at.localeCompare(b.emitted_at))
170
+ ?.sort((a, b) => a.issued_at.localeCompare(b.issued_at))
189
171
  .reverse();
190
172
 
191
173
  // get the last invoice
@@ -1,3 +1,2 @@
1
1
  export * from './arrayBufferToBase64';
2
2
  export * from './calculateMrr';
3
- export * from './isTestingTeam';
@@ -1,5 +0,0 @@
1
- export declare const isTestingTeam: (tenant: {
2
- id?: string;
3
- partnerId?: string | null;
4
- billingPartnerId?: string | null;
5
- }) => boolean;
@@ -1,4 +0,0 @@
1
- import { DEMO_TEST_PARTNER_TENANT_ID, TEST_TENANT_ID } from '../const';
2
- export const isTestingTeam = (tenant) => tenant.id === TEST_TENANT_ID ||
3
- [tenant.id, tenant.partnerId, tenant.billingPartnerId].includes(DEMO_TEST_PARTNER_TENANT_ID);
4
- //# sourceMappingURL=isTestingTeam.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"isTestingTeam.js","sourceRoot":"src/","sources":["utils/isTestingTeam.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEvE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAI7B,EAAE,EAAE,CACH,MAAM,CAAC,EAAE,KAAK,cAAc;IAC5B,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAC7D,2BAA2B,CAC5B,CAAC","sourcesContent":["import { DEMO_TEST_PARTNER_TENANT_ID, TEST_TENANT_ID } from '../const';\n\nexport const isTestingTeam = (tenant: {\n id?: string;\n partnerId?: string | null;\n billingPartnerId?: string | null;\n}) =>\n tenant.id === TEST_TENANT_ID ||\n [tenant.id, tenant.partnerId, tenant.billingPartnerId].includes(\n DEMO_TEST_PARTNER_TENANT_ID\n );\n"]}
@@ -1,5 +0,0 @@
1
- export declare const isTestingTeam: (tenant: {
2
- id?: string;
3
- partnerId?: string | null;
4
- billingPartnerId?: string | null;
5
- }) => boolean;