@vrplatform/hyperline-client 1.0.31 → 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.
- package/build/main/client.d.ts +977 -88
- package/build/main/client.js +49 -2
- package/build/main/client.js.map +1 -1
- package/build/main/const.d.ts +18 -20
- package/build/main/const.js +30 -22
- package/build/main/const.js.map +1 -1
- package/build/main/hyperlineError.d.ts +5 -0
- package/build/main/hyperlineError.js +14 -0
- package/build/main/hyperlineError.js.map +1 -0
- package/build/main/index.d.ts +1 -0
- package/build/main/index.js +1 -0
- package/build/main/index.js.map +1 -1
- package/build/main/types.d.ts +2 -10
- package/build/main/types.js.map +1 -1
- package/build/main/utils/calculateMrr.d.ts +2 -2
- package/build/main/utils/calculateMrr.js +6 -15
- package/build/main/utils/calculateMrr.js.map +1 -1
- package/build/main/utils/index.d.ts +0 -1
- package/build/main/utils/index.js +0 -1
- package/build/main/utils/index.js.map +1 -1
- package/build/module/client.d.ts +977 -88
- package/build/module/client.js +49 -2
- package/build/module/client.js.map +1 -1
- package/build/module/const.d.ts +18 -20
- package/build/module/const.js +30 -22
- package/build/module/const.js.map +1 -1
- package/build/module/hyperlineError.d.ts +5 -0
- package/build/module/hyperlineError.js +16 -0
- package/build/module/hyperlineError.js.map +1 -0
- package/build/module/index.d.ts +1 -0
- package/build/module/index.js +1 -0
- package/build/module/index.js.map +1 -1
- package/build/module/types.d.ts +2 -10
- package/build/module/types.js.map +1 -1
- package/build/module/utils/calculateMrr.d.ts +2 -2
- package/build/module/utils/calculateMrr.js +6 -15
- package/build/module/utils/calculateMrr.js.map +1 -1
- package/build/module/utils/index.d.ts +0 -1
- package/build/module/utils/index.js +0 -1
- package/build/module/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +107 -6
- package/src/const.ts +40 -28
- package/src/hyperlineError.ts +17 -0
- package/src/index.ts +1 -0
- package/src/types.ts +2 -11
- package/src/utils/calculateMrr.ts +11 -30
- package/src/utils/index.ts +0 -1
- package/build/main/utils/isTestingTeam.d.ts +0 -5
- package/build/main/utils/isTestingTeam.js +0 -4
- package/build/main/utils/isTestingTeam.js.map +0 -1
- package/build/module/utils/isTestingTeam.d.ts +0 -5
- package/build/module/utils/isTestingTeam.js +0 -4
- package/build/module/utils/isTestingTeam.js.map +0 -1
- package/src/utils/isTestingTeam.ts +0 -11
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { GL_HYPERLINE_DEFAULT_DEV_PLAN_ID, GL_HYPERLINE_DEFAULT_MIN_AMOUNT, GL_HYPERLINE_DEFAULT_PLAN_ID, HYPERLINE_DEFAULT_DEV_PLAN_ID, HYPERLINE_DEFAULT_MIN_AMOUNT, HYPERLINE_DEFAULT_PLAN_ID, } from '../const';
|
|
2
1
|
import { useHyperlineByToken } from '../hooks';
|
|
3
2
|
export async function calculateMrr(subscriptionId, estimated = false, _activeListings, hyperline = useHyperlineByToken(process.env.HYPERLINE_TOKEN), includeLiveBilling = false) {
|
|
4
|
-
const
|
|
5
|
-
if (!
|
|
3
|
+
const subscription = await hyperline.getSubscription(subscriptionId, includeLiveBilling);
|
|
4
|
+
if (!subscription)
|
|
6
5
|
throw new Error('Subscription not found');
|
|
7
|
-
const { products, phases, ...subscription } = _subscription;
|
|
8
6
|
let mrr = 0;
|
|
9
7
|
const activeListings = _activeListings || 0;
|
|
10
|
-
const phase = phases?.find((x) => x.type === 'standard'
|
|
8
|
+
const phase = subscription.phases?.find((x) => x.type === 'standard' &&
|
|
9
|
+
x.status !== 'finished' &&
|
|
10
|
+
!x.do_not_invoice_phase);
|
|
11
11
|
const product = phase?.products?.[0];
|
|
12
12
|
const type = product?.prices?.[0].type;
|
|
13
13
|
if (type === 'volume') {
|
|
@@ -59,16 +59,7 @@ export async function calculateMrr(subscriptionId, estimated = false, _activeLis
|
|
|
59
59
|
? firstTier.amount *
|
|
60
60
|
((firstTier.to - firstTier.from) / firstTier.unit_count)
|
|
61
61
|
: 0;
|
|
62
|
-
const
|
|
63
|
-
GL_HYPERLINE_DEFAULT_PLAN_ID,
|
|
64
|
-
GL_HYPERLINE_DEFAULT_DEV_PLAN_ID,
|
|
65
|
-
].includes(subscription.plan_id)
|
|
66
|
-
? GL_HYPERLINE_DEFAULT_MIN_AMOUNT
|
|
67
|
-
: [HYPERLINE_DEFAULT_PLAN_ID, HYPERLINE_DEFAULT_DEV_PLAN_ID].includes(subscription.plan_id)
|
|
68
|
-
? HYPERLINE_DEFAULT_MIN_AMOUNT
|
|
69
|
-
: 0;
|
|
70
|
-
const min_amount = ((product_min_amount || plan_min_amount || inferred_min_amount) ?? 0) /
|
|
71
|
-
100;
|
|
62
|
+
const min_amount = (product_min_amount ?? inferred_min_amount) / 100;
|
|
72
63
|
mrr = Math.max(mrr, min_amount);
|
|
73
64
|
const max_amount = ((product_max_amount || 0) ?? 0) / 100;
|
|
74
65
|
mrr = max_amount > 0 ? Math.min(mrr, max_amount) : mrr;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calculateMrr.js","sourceRoot":"src/","sources":["utils/calculateMrr.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n GL_HYPERLINE_DEFAULT_DEV_PLAN_ID,\n GL_HYPERLINE_DEFAULT_MIN_AMOUNT,\n GL_HYPERLINE_DEFAULT_PLAN_ID,\n HYPERLINE_DEFAULT_DEV_PLAN_ID,\n HYPERLINE_DEFAULT_MIN_AMOUNT,\n HYPERLINE_DEFAULT_PLAN_ID,\n} from '../const';\nimport { useHyperlineByToken } from '../hooks';\nimport type { HyperlineEntities } from '../types';\n\nexport async function calculateMrr(\n subscriptionId: string,\n estimated = false,\n _activeListings?: number,\n hyperline: {\n getSubscription(\n id: string,\n liveBilling?: boolean\n ): Promise<{\n customer_id: string;\n // BIL-614: legacy minimums remain until the subscription migration is complete.\n plan_id: string | null;\n products?: unknown[];\n coupons?: (\n | {\n type: 'amount';\n repeat: HyperlineEntities['Coupon']['repeat'];\n discount_amount: number;\n }\n | {\n type: 'percent';\n repeat: HyperlineEntities['Coupon']['repeat'];\n discount_percent: number;\n }\n )[];\n phases?: {\n type: HyperlineEntities['SubscriptionDetails']['phases'][number]['type'];\n products: {\n min_amount?: number | null;\n max_amount?: number | null;\n prices: (\n | {\n type: 'volume';\n amount: number;\n from: number;\n to: number | null;\n unit_count: number;\n }\n | {\n type:\n | 'bps'\n | 'bulk'\n | 'bundle'\n | 'fee'\n | 'flat_fee'\n | 'packaged';\n }\n )[];\n }[];\n }[];\n live_billing?: { total_amount: number };\n } | null>;\n getInvoicesByCustomerId(id: string): Promise<{\n data: {\n issued_at: string;\n total_amount: number;\n }[];\n } | null>;\n } = useHyperlineByToken(process.env.HYPERLINE_TOKEN!),\n includeLiveBilling = false\n) {\n const _subscription = await hyperline.getSubscription(\n subscriptionId,\n includeLiveBilling\n );\n if (!_subscription) throw new Error('Subscription not found');\n\n const { products, phases, ...subscription } = _subscription;\n\n let mrr = 0;\n const activeListings = _activeListings || 0;\n const phase = phases?.find((x) => x.type === 'standard');\n const product = phase?.products?.[0];\n const type = product?.prices?.[0].type;\n if (type === 'volume') {\n const volumePrices: Array<{\n amount: number;\n from: number;\n to: number | null;\n unit_count: number;\n }> = [];\n\n product?.prices?.forEach((price) => {\n if (\n price.type !== 'volume' ||\n !('amount' in price) ||\n !('from' in price) ||\n !('to' in price) ||\n !('unit_count' in price)\n )\n return;\n\n volumePrices.push({\n amount: price.amount,\n from: price.from,\n to: price.to,\n unit_count: price.unit_count,\n });\n });\n volumePrices.sort((a, b) => a.from - b.from);\n\n mrr =\n volumePrices.reduce((acc, price) => {\n const listings =\n price.to && activeListings >= price.to\n ? price.to - price.from\n : activeListings >= price.from\n ? activeListings - price.from\n : 0;\n\n acc += (price.amount / 100) * (listings / price.unit_count);\n\n return acc;\n }, 0) || 0;\n\n const firstTier = volumePrices[0];\n const secondTier = volumePrices[1];\n const product_min_amount =\n product &&\n 'min_amount' in product &&\n typeof product.min_amount === 'number'\n ? product.min_amount\n : null;\n const product_max_amount =\n product &&\n 'max_amount' in product &&\n typeof product.max_amount === 'number'\n ? product.max_amount\n : null;\n const inferred_min_amount =\n firstTier &&\n secondTier &&\n firstTier.from === 0 &&\n firstTier.to != null &&\n secondTier.from === firstTier.to &&\n firstTier.amount === secondTier.amount &&\n firstTier.unit_count === secondTier.unit_count\n ? firstTier.amount *\n ((firstTier.to - firstTier.from) / firstTier.unit_count)\n : 0;\n const plan_min_amount = [\n GL_HYPERLINE_DEFAULT_PLAN_ID,\n GL_HYPERLINE_DEFAULT_DEV_PLAN_ID,\n ].includes(subscription.plan_id!)\n ? GL_HYPERLINE_DEFAULT_MIN_AMOUNT\n : [HYPERLINE_DEFAULT_PLAN_ID, HYPERLINE_DEFAULT_DEV_PLAN_ID].includes(\n subscription.plan_id!\n )\n ? HYPERLINE_DEFAULT_MIN_AMOUNT\n : 0;\n\n const min_amount =\n ((product_min_amount || plan_min_amount || inferred_min_amount) ?? 0) /\n 100;\n mrr = Math.max(mrr, min_amount);\n\n const max_amount = ((product_max_amount || 0) ?? 0) / 100;\n mrr = max_amount > 0 ? Math.min(mrr, max_amount) : mrr;\n\n // apply discounts (only these who repeat forever if estimated is true)\n (subscription.coupons || [])\n .filter((coupon) => (estimated ? coupon.repeat === 'forever' : true))\n .forEach((x) => {\n if ('discount_amount' in x && x.discount_amount)\n mrr -= x.discount_amount / 100;\n else if ('discount_percent' in x && x.discount_percent)\n mrr -= (x.discount_percent / 100) * mrr;\n });\n } else if (type === 'bps') {\n const { data } = subscription?.customer_id\n ? (await hyperline.getInvoicesByCustomerId(subscription.customer_id)) || {\n data: [],\n }\n : { data: [] };\n\n // sort invoices by date\n const invoices = data\n ?.sort((a, b) => a.issued_at.localeCompare(b.issued_at))\n .reverse();\n\n // get the last invoice\n mrr = (invoices[0]?.total_amount || 0) / 100;\n }\n\n return {\n mrr,\n mrrAlt:\n subscription.live_billing?.total_amount == null\n ? null\n : subscription.live_billing.total_amount / 100,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"calculateMrr.js","sourceRoot":"src/","sources":["utils/calculateMrr.ts"],"names":[],"mappings":"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","sourcesContent":["import { useHyperlineByToken } from '../hooks';\nimport type { HyperlineEntities } from '../types';\n\nexport async function calculateMrr(\n subscriptionId: string,\n estimated = false,\n _activeListings?: number,\n hyperline: {\n getSubscription(\n id: string,\n liveBilling?: boolean\n ): Promise<{\n customer_id: string;\n coupons?: (\n | {\n type: 'amount';\n repeat: HyperlineEntities['Coupon']['repeat'];\n discount_amount: number;\n }\n | {\n type: 'percent';\n repeat: HyperlineEntities['Coupon']['repeat'];\n discount_percent: number;\n }\n )[];\n phases?: {\n type: HyperlineEntities['SubscriptionDetails']['phases'][number]['type'];\n status?: HyperlineEntities['SubscriptionDetails']['phases'][number]['status'];\n do_not_invoice_phase: boolean;\n products: {\n min_amount?: number | null;\n max_amount?: number | null;\n prices: (\n | {\n type: 'volume';\n amount: number;\n from: number;\n to: number | null;\n unit_count: number;\n }\n | {\n type:\n | 'bps'\n | 'bulk'\n | 'bundle'\n | 'fee'\n | 'flat_fee'\n | 'packaged';\n }\n )[];\n }[];\n }[];\n live_billing?: { total_amount: number };\n } | null>;\n getInvoicesByCustomerId(id: string): Promise<{\n data: {\n issued_at: string;\n total_amount: number;\n }[];\n } | null>;\n } = useHyperlineByToken(process.env.HYPERLINE_TOKEN!),\n includeLiveBilling = false\n) {\n const subscription = await hyperline.getSubscription(\n subscriptionId,\n includeLiveBilling\n );\n if (!subscription) throw new Error('Subscription not found');\n\n let mrr = 0;\n const activeListings = _activeListings || 0;\n const phase = subscription.phases?.find(\n (x) =>\n x.type === 'standard' &&\n x.status !== 'finished' &&\n !x.do_not_invoice_phase\n );\n const product = phase?.products?.[0];\n const type = product?.prices?.[0].type;\n if (type === 'volume') {\n const volumePrices: Array<{\n amount: number;\n from: number;\n to: number | null;\n unit_count: number;\n }> = [];\n\n product?.prices?.forEach((price) => {\n if (\n price.type !== 'volume' ||\n !('amount' in price) ||\n !('from' in price) ||\n !('to' in price) ||\n !('unit_count' in price)\n )\n return;\n\n volumePrices.push({\n amount: price.amount,\n from: price.from,\n to: price.to,\n unit_count: price.unit_count,\n });\n });\n volumePrices.sort((a, b) => a.from - b.from);\n\n mrr =\n volumePrices.reduce((acc, price) => {\n const listings =\n price.to && activeListings >= price.to\n ? price.to - price.from\n : activeListings >= price.from\n ? activeListings - price.from\n : 0;\n\n acc += (price.amount / 100) * (listings / price.unit_count);\n\n return acc;\n }, 0) || 0;\n\n const firstTier = volumePrices[0];\n const secondTier = volumePrices[1];\n const product_min_amount =\n product &&\n 'min_amount' in product &&\n typeof product.min_amount === 'number'\n ? product.min_amount\n : null;\n const product_max_amount =\n product &&\n 'max_amount' in product &&\n typeof product.max_amount === 'number'\n ? product.max_amount\n : null;\n const inferred_min_amount =\n firstTier &&\n secondTier &&\n firstTier.from === 0 &&\n firstTier.to != null &&\n secondTier.from === firstTier.to &&\n firstTier.amount === secondTier.amount &&\n firstTier.unit_count === secondTier.unit_count\n ? firstTier.amount *\n ((firstTier.to - firstTier.from) / firstTier.unit_count)\n : 0;\n const min_amount = (product_min_amount ?? inferred_min_amount) / 100;\n mrr = Math.max(mrr, min_amount);\n\n const max_amount = ((product_max_amount || 0) ?? 0) / 100;\n mrr = max_amount > 0 ? Math.min(mrr, max_amount) : mrr;\n\n // apply discounts (only these who repeat forever if estimated is true)\n (subscription.coupons || [])\n .filter((coupon) => (estimated ? coupon.repeat === 'forever' : true))\n .forEach((x) => {\n if ('discount_amount' in x && x.discount_amount)\n mrr -= x.discount_amount / 100;\n else if ('discount_percent' in x && x.discount_percent)\n mrr -= (x.discount_percent / 100) * mrr;\n });\n } else if (type === 'bps') {\n const { data } = subscription?.customer_id\n ? (await hyperline.getInvoicesByCustomerId(subscription.customer_id)) || {\n data: [],\n }\n : { data: [] };\n\n // sort invoices by date\n const invoices = data\n ?.sort((a, b) => a.issued_at.localeCompare(b.issued_at))\n .reverse();\n\n // get the last invoice\n mrr = (invoices[0]?.total_amount || 0) / 100;\n }\n\n return {\n mrr,\n mrrAlt:\n subscription.live_billing?.total_amount == null\n ? null\n : subscription.live_billing.total_amount / 100,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"src/","sources":["utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"src/","sources":["utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC","sourcesContent":["export * from './arrayBufferToBase64';\nexport * from './calculateMrr';\n"]}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { paths } from './_types.generated';
|
|
2
|
+
import { HyperlineError } from './hyperlineError';
|
|
2
3
|
import { BillableEvents, CreateBillableEvent } from './types';
|
|
3
4
|
import { arrayBufferToBase64 } from './utils';
|
|
4
5
|
|
|
@@ -66,11 +67,10 @@ export class HyperlineAPI {
|
|
|
66
67
|
if (status === 404 && method === 'GET') {
|
|
67
68
|
return null;
|
|
68
69
|
} else {
|
|
69
|
-
throw
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
{ status }
|
|
70
|
+
throw new HyperlineError(
|
|
71
|
+
`Hyperline Error ${status} (${statusText}) in ${path} for ${method}: ${message}`,
|
|
72
|
+
status,
|
|
73
|
+
result.headers.get('Retry-After')
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
} else if (result.headers.get('content-type')?.includes('application/pdf'))
|
|
@@ -118,6 +118,25 @@ export class HyperlineAPI {
|
|
|
118
118
|
|
|
119
119
|
// CUSTOMER
|
|
120
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
|
+
|
|
121
140
|
async getCustomer(id?: string) {
|
|
122
141
|
if (!id) {
|
|
123
142
|
this.logger.error('Missing customer ID', { id });
|
|
@@ -165,11 +184,14 @@ export class HyperlineAPI {
|
|
|
165
184
|
): Promise<
|
|
166
185
|
paths['/v2/customers']['get']['responses'][200]['content']['application/json']['data']
|
|
167
186
|
> {
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
168
188
|
const page = await this.getCustomers({
|
|
169
189
|
status: 'all',
|
|
170
190
|
...query,
|
|
171
191
|
});
|
|
172
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');
|
|
173
195
|
|
|
174
196
|
return page.next_cursor
|
|
175
197
|
? this.getAllCustomers([...items, ...page.data], {
|
|
@@ -242,6 +264,39 @@ export class HyperlineAPI {
|
|
|
242
264
|
>(`/v1/customers/${id}/portal`);
|
|
243
265
|
}
|
|
244
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
|
+
|
|
245
300
|
// SUBSCRIPTION
|
|
246
301
|
|
|
247
302
|
async getSubscription(id: string, liveBilling = false) {
|
|
@@ -305,6 +360,12 @@ export class HyperlineAPI {
|
|
|
305
360
|
>('/v2/subscriptions', { method: 'POST' }, data);
|
|
306
361
|
}
|
|
307
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
|
+
|
|
308
369
|
async updateSubscription(
|
|
309
370
|
id: string,
|
|
310
371
|
data: NonNullable<
|
|
@@ -327,6 +388,17 @@ export class HyperlineAPI {
|
|
|
327
388
|
>(`/v1/subscriptions/${id}/update`, { method: 'POST' }, data);
|
|
328
389
|
}
|
|
329
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
|
+
|
|
330
402
|
async cancelSubscription(
|
|
331
403
|
id: string,
|
|
332
404
|
data: NonNullable<
|
|
@@ -354,6 +426,28 @@ export class HyperlineAPI {
|
|
|
354
426
|
|
|
355
427
|
// INVOICE
|
|
356
428
|
|
|
429
|
+
async createInvoice(
|
|
430
|
+
data: NonNullable<
|
|
431
|
+
paths['/v1/invoices']['post']['requestBody']
|
|
432
|
+
>['content']['application/json']
|
|
433
|
+
) {
|
|
434
|
+
return await this.fetch<
|
|
435
|
+
paths['/v1/invoices']['post']['responses'][201]['content']['application/json']
|
|
436
|
+
>('/v1/invoices', { method: 'POST' }, data);
|
|
437
|
+
}
|
|
438
|
+
|
|
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');
|
|
446
|
+
return await this.fetch<
|
|
447
|
+
paths['/v1/invoices/{id}']['patch']['responses'][200]['content']['application/json']
|
|
448
|
+
>(`/v1/invoices/${id}`, { method: 'PATCH' }, data);
|
|
449
|
+
}
|
|
450
|
+
|
|
357
451
|
async getInvoice(id: string) {
|
|
358
452
|
if (!id) {
|
|
359
453
|
this.logger.error('Missing invoice ID', { id });
|
|
@@ -365,6 +459,13 @@ export class HyperlineAPI {
|
|
|
365
459
|
>(`/v2/invoices/${id}`);
|
|
366
460
|
}
|
|
367
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' });
|
|
467
|
+
}
|
|
468
|
+
|
|
368
469
|
async getInvoices(
|
|
369
470
|
query: paths['/v2/invoices']['get']['parameters']['query'] = {}
|
|
370
471
|
) {
|
|
@@ -464,7 +565,7 @@ export class HyperlineAPI {
|
|
|
464
565
|
}
|
|
465
566
|
|
|
466
567
|
async createBillableEvent(data: CreateBillableEvent[]) {
|
|
467
|
-
if (!data.length) return { events_created: [] };
|
|
568
|
+
if (!data.length) return { events_created: [], events_failed: [] };
|
|
468
569
|
|
|
469
570
|
return await this.fetchIngest<BillableEvents>(
|
|
470
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
|
-
|
|
12
|
-
|
|
13
|
-
export const
|
|
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
|
-
//
|
|
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
|
|
51
|
-
export const
|
|
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
|
|
55
|
-
export const
|
|
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
|
|
60
|
-
export const
|
|
61
|
-
|
|
62
|
-
export const
|
|
63
|
-
export const
|
|
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
|
|
66
|
-
export const
|
|
67
|
-
|
|
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',
|
|
@@ -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
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:
|
|
18
|
+
events_created: CreateBillableEvent[];
|
|
19
|
+
events_failed: unknown[];
|
|
29
20
|
};
|
|
30
21
|
|
|
31
22
|
export type CreateBillableEvent = {
|
|
@@ -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,9 +11,6 @@ export async function calculateMrr(
|
|
|
19
11
|
liveBilling?: boolean
|
|
20
12
|
): Promise<{
|
|
21
13
|
customer_id: string;
|
|
22
|
-
// BIL-614: legacy minimums remain until the subscription migration is complete.
|
|
23
|
-
plan_id: string | null;
|
|
24
|
-
products?: unknown[];
|
|
25
14
|
coupons?: (
|
|
26
15
|
| {
|
|
27
16
|
type: 'amount';
|
|
@@ -36,6 +25,8 @@ export async function calculateMrr(
|
|
|
36
25
|
)[];
|
|
37
26
|
phases?: {
|
|
38
27
|
type: HyperlineEntities['SubscriptionDetails']['phases'][number]['type'];
|
|
28
|
+
status?: HyperlineEntities['SubscriptionDetails']['phases'][number]['status'];
|
|
29
|
+
do_not_invoice_phase: boolean;
|
|
39
30
|
products: {
|
|
40
31
|
min_amount?: number | null;
|
|
41
32
|
max_amount?: number | null;
|
|
@@ -70,17 +61,20 @@ export async function calculateMrr(
|
|
|
70
61
|
} = useHyperlineByToken(process.env.HYPERLINE_TOKEN!),
|
|
71
62
|
includeLiveBilling = false
|
|
72
63
|
) {
|
|
73
|
-
const
|
|
64
|
+
const subscription = await hyperline.getSubscription(
|
|
74
65
|
subscriptionId,
|
|
75
66
|
includeLiveBilling
|
|
76
67
|
);
|
|
77
|
-
if (!
|
|
78
|
-
|
|
79
|
-
const { products, phases, ...subscription } = _subscription;
|
|
68
|
+
if (!subscription) throw new Error('Subscription not found');
|
|
80
69
|
|
|
81
70
|
let mrr = 0;
|
|
82
71
|
const activeListings = _activeListings || 0;
|
|
83
|
-
const phase = phases?.find(
|
|
72
|
+
const phase = subscription.phases?.find(
|
|
73
|
+
(x) =>
|
|
74
|
+
x.type === 'standard' &&
|
|
75
|
+
x.status !== 'finished' &&
|
|
76
|
+
!x.do_not_invoice_phase
|
|
77
|
+
);
|
|
84
78
|
const product = phase?.products?.[0];
|
|
85
79
|
const type = product?.prices?.[0].type;
|
|
86
80
|
if (type === 'volume') {
|
|
@@ -149,20 +143,7 @@ export async function calculateMrr(
|
|
|
149
143
|
? firstTier.amount *
|
|
150
144
|
((firstTier.to - firstTier.from) / firstTier.unit_count)
|
|
151
145
|
: 0;
|
|
152
|
-
const
|
|
153
|
-
GL_HYPERLINE_DEFAULT_PLAN_ID,
|
|
154
|
-
GL_HYPERLINE_DEFAULT_DEV_PLAN_ID,
|
|
155
|
-
].includes(subscription.plan_id!)
|
|
156
|
-
? GL_HYPERLINE_DEFAULT_MIN_AMOUNT
|
|
157
|
-
: [HYPERLINE_DEFAULT_PLAN_ID, HYPERLINE_DEFAULT_DEV_PLAN_ID].includes(
|
|
158
|
-
subscription.plan_id!
|
|
159
|
-
)
|
|
160
|
-
? HYPERLINE_DEFAULT_MIN_AMOUNT
|
|
161
|
-
: 0;
|
|
162
|
-
|
|
163
|
-
const min_amount =
|
|
164
|
-
((product_min_amount || plan_min_amount || inferred_min_amount) ?? 0) /
|
|
165
|
-
100;
|
|
146
|
+
const min_amount = (product_min_amount ?? inferred_min_amount) / 100;
|
|
166
147
|
mrr = Math.max(mrr, min_amount);
|
|
167
148
|
|
|
168
149
|
const max_amount = ((product_max_amount || 0) ?? 0) / 100;
|
package/src/utils/index.ts
CHANGED
|
@@ -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,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,11 +0,0 @@
|
|
|
1
|
-
import { DEMO_TEST_PARTNER_TENANT_ID, TEST_TENANT_ID } from '../const';
|
|
2
|
-
|
|
3
|
-
export const isTestingTeam = (tenant: {
|
|
4
|
-
id?: string;
|
|
5
|
-
partnerId?: string | null;
|
|
6
|
-
billingPartnerId?: string | null;
|
|
7
|
-
}) =>
|
|
8
|
-
tenant.id === TEST_TENANT_ID ||
|
|
9
|
-
[tenant.id, tenant.partnerId, tenant.billingPartnerId].includes(
|
|
10
|
-
DEMO_TEST_PARTNER_TENANT_ID
|
|
11
|
-
);
|