orb-billing 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +29 -0
- package/core.d.ts.map +1 -1
- package/core.js +1 -1
- package/core.js.map +1 -1
- package/core.mjs +1 -1
- package/core.mjs.map +1 -1
- package/index.d.mts +8 -1
- package/index.d.ts +8 -1
- package/index.d.ts.map +1 -1
- package/index.js +5 -1
- package/index.js.map +1 -1
- package/index.mjs +5 -1
- package/index.mjs.map +1 -1
- package/package.json +2 -3
- package/resources/customers/credits/ledger.d.ts +4 -4
- package/resources/index.d.ts +1 -0
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +3 -1
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +1 -0
- package/resources/index.mjs.map +1 -1
- package/resources/invoices.d.ts +2 -0
- package/resources/invoices.d.ts.map +1 -1
- package/resources/invoices.js.map +1 -1
- package/resources/invoices.mjs.map +1 -1
- package/resources/prices/prices.d.ts +147 -2
- package/resources/prices/prices.d.ts.map +1 -1
- package/resources/prices/prices.js.map +1 -1
- package/resources/prices/prices.mjs.map +1 -1
- package/resources/subscriptions.d.ts +44 -1
- package/resources/subscriptions.d.ts.map +1 -1
- package/resources/subscriptions.js.map +1 -1
- package/resources/subscriptions.mjs.map +1 -1
- package/resources/webhooks.d.ts +24 -0
- package/resources/webhooks.d.ts.map +1 -0
- package/resources/webhooks.js +108 -0
- package/resources/webhooks.js.map +1 -0
- package/resources/webhooks.mjs +104 -0
- package/resources/webhooks.mjs.map +1 -0
- package/src/core.ts +2 -1
- package/src/index.ts +11 -0
- package/src/resources/customers/credits/ledger.ts +4 -4
- package/src/resources/index.ts +1 -0
- package/src/resources/invoices.ts +4 -0
- package/src/resources/prices/prices.ts +209 -0
- package/src/resources/subscriptions.ts +55 -0
- package/src/resources/webhooks.ts +140 -0
- package/src/version.ts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/index.ts
CHANGED
|
@@ -14,6 +14,11 @@ export interface ClientOptions {
|
|
|
14
14
|
*/
|
|
15
15
|
apiKey?: string | undefined;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Defaults to process.env['ORB_WEBHOOK_SECRET'].
|
|
19
|
+
*/
|
|
20
|
+
webhookSecret?: string | null | undefined;
|
|
21
|
+
|
|
17
22
|
/**
|
|
18
23
|
* Override the default base URL for the API, e.g., "https://api.example.com/v2/"
|
|
19
24
|
*
|
|
@@ -74,6 +79,7 @@ export interface ClientOptions {
|
|
|
74
79
|
/** API Client for interfacing with the Orb API. */
|
|
75
80
|
export class Orb extends Core.APIClient {
|
|
76
81
|
apiKey: string;
|
|
82
|
+
webhookSecret: string | null;
|
|
77
83
|
|
|
78
84
|
private _options: ClientOptions;
|
|
79
85
|
|
|
@@ -81,6 +87,7 @@ export class Orb extends Core.APIClient {
|
|
|
81
87
|
* API Client for interfacing with the Orb API.
|
|
82
88
|
*
|
|
83
89
|
* @param {string | undefined} [opts.apiKey=process.env['ORB_API_KEY'] ?? undefined]
|
|
90
|
+
* @param {string | null | undefined} [opts.webhookSecret=process.env['ORB_WEBHOOK_SECRET'] ?? null]
|
|
84
91
|
* @param {string} [opts.baseURL=process.env['ORB_BASE_URL'] ?? https://api.withorb.com/v1] - Override the default base URL for the API.
|
|
85
92
|
* @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
|
|
86
93
|
* @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections.
|
|
@@ -92,6 +99,7 @@ export class Orb extends Core.APIClient {
|
|
|
92
99
|
constructor({
|
|
93
100
|
baseURL = Core.readEnv('ORB_BASE_URL'),
|
|
94
101
|
apiKey = Core.readEnv('ORB_API_KEY'),
|
|
102
|
+
webhookSecret = Core.readEnv('ORB_WEBHOOK_SECRET') ?? null,
|
|
95
103
|
...opts
|
|
96
104
|
}: ClientOptions = {}) {
|
|
97
105
|
if (apiKey === undefined) {
|
|
@@ -102,6 +110,7 @@ export class Orb extends Core.APIClient {
|
|
|
102
110
|
|
|
103
111
|
const options: ClientOptions = {
|
|
104
112
|
apiKey,
|
|
113
|
+
webhookSecret,
|
|
105
114
|
...opts,
|
|
106
115
|
baseURL: baseURL || `https://api.withorb.com/v1`,
|
|
107
116
|
};
|
|
@@ -117,6 +126,7 @@ export class Orb extends Core.APIClient {
|
|
|
117
126
|
this.idempotencyHeader = 'Idempotency-Key';
|
|
118
127
|
|
|
119
128
|
this.apiKey = apiKey;
|
|
129
|
+
this.webhookSecret = webhookSecret;
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
topLevel: API.TopLevel = new API.TopLevel(this);
|
|
@@ -132,6 +142,7 @@ export class Orb extends Core.APIClient {
|
|
|
132
142
|
prices: API.Prices = new API.Prices(this);
|
|
133
143
|
subscriptions: API.Subscriptions = new API.Subscriptions(this);
|
|
134
144
|
beta: API.Beta = new API.Beta(this);
|
|
145
|
+
webhooks: API.Webhooks = new API.Webhooks(this);
|
|
135
146
|
|
|
136
147
|
protected override defaultQuery(): Core.DefaultQuery | undefined {
|
|
137
148
|
return this._options.defaultQuery;
|
|
@@ -2084,8 +2084,8 @@ export namespace LedgerCreateEntryParams {
|
|
|
2084
2084
|
metadata?: Record<string, string | null> | null;
|
|
2085
2085
|
|
|
2086
2086
|
/**
|
|
2087
|
-
* Can only be specified when entry_type=increment. How much, in
|
|
2088
|
-
* paid for a single credit in this block
|
|
2087
|
+
* Can only be specified when entry_type=increment. How much, in the customer's
|
|
2088
|
+
* currency, a customer paid for a single credit in this block
|
|
2089
2089
|
*/
|
|
2090
2090
|
per_unit_cost_basis?: string | null;
|
|
2091
2091
|
}
|
|
@@ -2329,8 +2329,8 @@ export namespace LedgerCreateEntryByExternalIDParams {
|
|
|
2329
2329
|
metadata?: Record<string, string | null> | null;
|
|
2330
2330
|
|
|
2331
2331
|
/**
|
|
2332
|
-
* Can only be specified when entry_type=increment. How much, in
|
|
2333
|
-
* paid for a single credit in this block
|
|
2332
|
+
* Can only be specified when entry_type=increment. How much, in the customer's
|
|
2333
|
+
* currency, a customer paid for a single credit in this block
|
|
2334
2334
|
*/
|
|
2335
2335
|
per_unit_cost_basis?: string | null;
|
|
2336
2336
|
}
|
package/src/resources/index.ts
CHANGED
|
@@ -273,6 +273,8 @@ export interface Invoice {
|
|
|
273
273
|
*/
|
|
274
274
|
invoice_pdf: string | null;
|
|
275
275
|
|
|
276
|
+
invoice_source: 'subscription' | 'partial' | 'one_off';
|
|
277
|
+
|
|
276
278
|
/**
|
|
277
279
|
* If the invoice failed to issue, this will be the last time it failed to issue
|
|
278
280
|
* (even if it is now in a different state.)
|
|
@@ -1353,6 +1355,8 @@ export interface InvoiceFetchUpcomingResponse {
|
|
|
1353
1355
|
*/
|
|
1354
1356
|
invoice_pdf: string | null;
|
|
1355
1357
|
|
|
1358
|
+
invoice_source: 'subscription' | 'partial' | 'one_off';
|
|
1359
|
+
|
|
1356
1360
|
/**
|
|
1357
1361
|
* If the invoice failed to issue, this will be the last time it failed to issue
|
|
1358
1362
|
* (even if it is now in a different state.)
|
|
@@ -297,7 +297,9 @@ export type Price =
|
|
|
297
297
|
| Price.BulkPrice
|
|
298
298
|
| Price.ThresholdTotalAmountPrice
|
|
299
299
|
| Price.TieredPackagePrice
|
|
300
|
+
| Price.GroupedTieredPrice
|
|
300
301
|
| Price.TieredWithMinimumPrice
|
|
302
|
+
| Price.TieredPackageWithMinimumPrice
|
|
301
303
|
| Price.PackageWithAllocationPrice
|
|
302
304
|
| Price.UnitWithPercentPrice
|
|
303
305
|
| Price.MatrixWithAllocationPrice;
|
|
@@ -1234,6 +1236,82 @@ export namespace Price {
|
|
|
1234
1236
|
}
|
|
1235
1237
|
}
|
|
1236
1238
|
|
|
1239
|
+
export interface GroupedTieredPrice {
|
|
1240
|
+
id: string;
|
|
1241
|
+
|
|
1242
|
+
billable_metric: GroupedTieredPrice.BillableMetric | null;
|
|
1243
|
+
|
|
1244
|
+
cadence: 'one_time' | 'monthly' | 'quarterly' | 'annual';
|
|
1245
|
+
|
|
1246
|
+
created_at: string;
|
|
1247
|
+
|
|
1248
|
+
currency: string;
|
|
1249
|
+
|
|
1250
|
+
discount: Shared.Discount | null;
|
|
1251
|
+
|
|
1252
|
+
external_price_id: string | null;
|
|
1253
|
+
|
|
1254
|
+
fixed_price_quantity: number | null;
|
|
1255
|
+
|
|
1256
|
+
grouped_tiered_config: Record<string, unknown>;
|
|
1257
|
+
|
|
1258
|
+
item: GroupedTieredPrice.Item;
|
|
1259
|
+
|
|
1260
|
+
maximum: GroupedTieredPrice.Maximum | null;
|
|
1261
|
+
|
|
1262
|
+
maximum_amount: string | null;
|
|
1263
|
+
|
|
1264
|
+
minimum: GroupedTieredPrice.Minimum | null;
|
|
1265
|
+
|
|
1266
|
+
minimum_amount: string | null;
|
|
1267
|
+
|
|
1268
|
+
model_type: 'grouped_tiered';
|
|
1269
|
+
|
|
1270
|
+
name: string;
|
|
1271
|
+
|
|
1272
|
+
plan_phase_order: number | null;
|
|
1273
|
+
|
|
1274
|
+
price_type: 'usage_price' | 'fixed_price';
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export namespace GroupedTieredPrice {
|
|
1278
|
+
export interface BillableMetric {
|
|
1279
|
+
id: string;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
export interface Item {
|
|
1283
|
+
id: string;
|
|
1284
|
+
|
|
1285
|
+
name: string;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
export interface Maximum {
|
|
1289
|
+
/**
|
|
1290
|
+
* List of price_ids that this maximum amount applies to. For plan/plan phase
|
|
1291
|
+
* maximums, this can be a subset of prices.
|
|
1292
|
+
*/
|
|
1293
|
+
applies_to_price_ids: Array<string>;
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Maximum amount applied
|
|
1297
|
+
*/
|
|
1298
|
+
maximum_amount: string;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
export interface Minimum {
|
|
1302
|
+
/**
|
|
1303
|
+
* List of price_ids that this minimum amount applies to. For plan/plan phase
|
|
1304
|
+
* minimums, this can be a subset of prices.
|
|
1305
|
+
*/
|
|
1306
|
+
applies_to_price_ids: Array<string>;
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Minimum amount applied
|
|
1310
|
+
*/
|
|
1311
|
+
minimum_amount: string;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1237
1315
|
export interface TieredWithMinimumPrice {
|
|
1238
1316
|
id: string;
|
|
1239
1317
|
|
|
@@ -1310,6 +1388,82 @@ export namespace Price {
|
|
|
1310
1388
|
}
|
|
1311
1389
|
}
|
|
1312
1390
|
|
|
1391
|
+
export interface TieredPackageWithMinimumPrice {
|
|
1392
|
+
id: string;
|
|
1393
|
+
|
|
1394
|
+
billable_metric: TieredPackageWithMinimumPrice.BillableMetric | null;
|
|
1395
|
+
|
|
1396
|
+
cadence: 'one_time' | 'monthly' | 'quarterly' | 'annual';
|
|
1397
|
+
|
|
1398
|
+
created_at: string;
|
|
1399
|
+
|
|
1400
|
+
currency: string;
|
|
1401
|
+
|
|
1402
|
+
discount: Shared.Discount | null;
|
|
1403
|
+
|
|
1404
|
+
external_price_id: string | null;
|
|
1405
|
+
|
|
1406
|
+
fixed_price_quantity: number | null;
|
|
1407
|
+
|
|
1408
|
+
item: TieredPackageWithMinimumPrice.Item;
|
|
1409
|
+
|
|
1410
|
+
maximum: TieredPackageWithMinimumPrice.Maximum | null;
|
|
1411
|
+
|
|
1412
|
+
maximum_amount: string | null;
|
|
1413
|
+
|
|
1414
|
+
minimum: TieredPackageWithMinimumPrice.Minimum | null;
|
|
1415
|
+
|
|
1416
|
+
minimum_amount: string | null;
|
|
1417
|
+
|
|
1418
|
+
model_type: 'tiered_package_with_minimum';
|
|
1419
|
+
|
|
1420
|
+
name: string;
|
|
1421
|
+
|
|
1422
|
+
plan_phase_order: number | null;
|
|
1423
|
+
|
|
1424
|
+
price_type: 'usage_price' | 'fixed_price';
|
|
1425
|
+
|
|
1426
|
+
tiered_package_with_minimum_config: Record<string, unknown>;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
export namespace TieredPackageWithMinimumPrice {
|
|
1430
|
+
export interface BillableMetric {
|
|
1431
|
+
id: string;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
export interface Item {
|
|
1435
|
+
id: string;
|
|
1436
|
+
|
|
1437
|
+
name: string;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
export interface Maximum {
|
|
1441
|
+
/**
|
|
1442
|
+
* List of price_ids that this maximum amount applies to. For plan/plan phase
|
|
1443
|
+
* maximums, this can be a subset of prices.
|
|
1444
|
+
*/
|
|
1445
|
+
applies_to_price_ids: Array<string>;
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Maximum amount applied
|
|
1449
|
+
*/
|
|
1450
|
+
maximum_amount: string;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
export interface Minimum {
|
|
1454
|
+
/**
|
|
1455
|
+
* List of price_ids that this minimum amount applies to. For plan/plan phase
|
|
1456
|
+
* minimums, this can be a subset of prices.
|
|
1457
|
+
*/
|
|
1458
|
+
applies_to_price_ids: Array<string>;
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Minimum amount applied
|
|
1462
|
+
*/
|
|
1463
|
+
minimum_amount: string;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1313
1467
|
export interface PackageWithAllocationPrice {
|
|
1314
1468
|
id: string;
|
|
1315
1469
|
|
|
@@ -1589,6 +1743,7 @@ export type PriceCreateParams =
|
|
|
1589
1743
|
| PriceCreateParams.NewFloatingBulkPrice
|
|
1590
1744
|
| PriceCreateParams.NewFloatingThresholdTotalAmountPrice
|
|
1591
1745
|
| PriceCreateParams.NewFloatingTieredPackagePrice
|
|
1746
|
+
| PriceCreateParams.NewFloatingGroupedTieredPrice
|
|
1592
1747
|
| PriceCreateParams.NewFloatingTieredWithMinimumPrice
|
|
1593
1748
|
| PriceCreateParams.NewFloatingPackageWithAllocationPrice
|
|
1594
1749
|
| PriceCreateParams.NewFloatingTieredPackageWithMinimumPrice
|
|
@@ -2416,6 +2571,60 @@ export namespace PriceCreateParams {
|
|
|
2416
2571
|
invoice_grouping_key?: string | null;
|
|
2417
2572
|
}
|
|
2418
2573
|
|
|
2574
|
+
export interface NewFloatingGroupedTieredPrice {
|
|
2575
|
+
/**
|
|
2576
|
+
* The cadence to bill for this price on.
|
|
2577
|
+
*/
|
|
2578
|
+
cadence: 'annual' | 'monthly' | 'quarterly' | 'one_time';
|
|
2579
|
+
|
|
2580
|
+
/**
|
|
2581
|
+
* An ISO 4217 currency string for which this price is billed in.
|
|
2582
|
+
*/
|
|
2583
|
+
currency: string;
|
|
2584
|
+
|
|
2585
|
+
grouped_tiered_config: Record<string, unknown>;
|
|
2586
|
+
|
|
2587
|
+
/**
|
|
2588
|
+
* The id of the item the plan will be associated with.
|
|
2589
|
+
*/
|
|
2590
|
+
item_id: string;
|
|
2591
|
+
|
|
2592
|
+
model_type: 'grouped_tiered';
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* The name of the price.
|
|
2596
|
+
*/
|
|
2597
|
+
name: string;
|
|
2598
|
+
|
|
2599
|
+
/**
|
|
2600
|
+
* The id of the billable metric for the price. Only needed if the price is
|
|
2601
|
+
* usage-based.
|
|
2602
|
+
*/
|
|
2603
|
+
billable_metric_id?: string | null;
|
|
2604
|
+
|
|
2605
|
+
/**
|
|
2606
|
+
* If the Price represents a fixed cost, the price will be billed in-advance if
|
|
2607
|
+
* this is true, and in-arrears if this is false.
|
|
2608
|
+
*/
|
|
2609
|
+
billed_in_advance?: boolean | null;
|
|
2610
|
+
|
|
2611
|
+
/**
|
|
2612
|
+
* An alias for the price.
|
|
2613
|
+
*/
|
|
2614
|
+
external_price_id?: string | null;
|
|
2615
|
+
|
|
2616
|
+
/**
|
|
2617
|
+
* If the Price represents a fixed cost, this represents the quantity of units
|
|
2618
|
+
* applied.
|
|
2619
|
+
*/
|
|
2620
|
+
fixed_price_quantity?: number | null;
|
|
2621
|
+
|
|
2622
|
+
/**
|
|
2623
|
+
* The property used to group this price on an invoice
|
|
2624
|
+
*/
|
|
2625
|
+
invoice_grouping_key?: string | null;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2419
2628
|
export interface NewFloatingTieredWithMinimumPrice {
|
|
2420
2629
|
/**
|
|
2421
2630
|
* The cadence to bill for this price on.
|
|
@@ -3309,6 +3309,7 @@ export namespace SubscriptionPriceIntervalsParams {
|
|
|
3309
3309
|
| Add.NewFloatingBulkPrice
|
|
3310
3310
|
| Add.NewFloatingThresholdTotalAmountPrice
|
|
3311
3311
|
| Add.NewFloatingTieredPackagePrice
|
|
3312
|
+
| Add.NewFloatingGroupedTieredPrice
|
|
3312
3313
|
| Add.NewFloatingTieredWithMinimumPrice
|
|
3313
3314
|
| Add.NewFloatingPackageWithAllocationPrice
|
|
3314
3315
|
| Add.NewFloatingTieredPackageWithMinimumPrice
|
|
@@ -4184,6 +4185,60 @@ export namespace SubscriptionPriceIntervalsParams {
|
|
|
4184
4185
|
invoice_grouping_key?: string | null;
|
|
4185
4186
|
}
|
|
4186
4187
|
|
|
4188
|
+
export interface NewFloatingGroupedTieredPrice {
|
|
4189
|
+
/**
|
|
4190
|
+
* The cadence to bill for this price on.
|
|
4191
|
+
*/
|
|
4192
|
+
cadence: 'annual' | 'monthly' | 'quarterly' | 'one_time';
|
|
4193
|
+
|
|
4194
|
+
/**
|
|
4195
|
+
* An ISO 4217 currency string for which this price is billed in.
|
|
4196
|
+
*/
|
|
4197
|
+
currency: string;
|
|
4198
|
+
|
|
4199
|
+
grouped_tiered_config: Record<string, unknown>;
|
|
4200
|
+
|
|
4201
|
+
/**
|
|
4202
|
+
* The id of the item the plan will be associated with.
|
|
4203
|
+
*/
|
|
4204
|
+
item_id: string;
|
|
4205
|
+
|
|
4206
|
+
model_type: 'grouped_tiered';
|
|
4207
|
+
|
|
4208
|
+
/**
|
|
4209
|
+
* The name of the price.
|
|
4210
|
+
*/
|
|
4211
|
+
name: string;
|
|
4212
|
+
|
|
4213
|
+
/**
|
|
4214
|
+
* The id of the billable metric for the price. Only needed if the price is
|
|
4215
|
+
* usage-based.
|
|
4216
|
+
*/
|
|
4217
|
+
billable_metric_id?: string | null;
|
|
4218
|
+
|
|
4219
|
+
/**
|
|
4220
|
+
* If the Price represents a fixed cost, the price will be billed in-advance if
|
|
4221
|
+
* this is true, and in-arrears if this is false.
|
|
4222
|
+
*/
|
|
4223
|
+
billed_in_advance?: boolean | null;
|
|
4224
|
+
|
|
4225
|
+
/**
|
|
4226
|
+
* An alias for the price.
|
|
4227
|
+
*/
|
|
4228
|
+
external_price_id?: string | null;
|
|
4229
|
+
|
|
4230
|
+
/**
|
|
4231
|
+
* If the Price represents a fixed cost, this represents the quantity of units
|
|
4232
|
+
* applied.
|
|
4233
|
+
*/
|
|
4234
|
+
fixed_price_quantity?: number | null;
|
|
4235
|
+
|
|
4236
|
+
/**
|
|
4237
|
+
* The property used to group this price on an invoice
|
|
4238
|
+
*/
|
|
4239
|
+
invoice_grouping_key?: string | null;
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4187
4242
|
export interface NewFloatingTieredWithMinimumPrice {
|
|
4188
4243
|
/**
|
|
4189
4244
|
* The cadence to bill for this price on.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// File generated from our OpenAPI spec by Stainless.
|
|
2
|
+
|
|
3
|
+
import { APIResource } from "../resource";
|
|
4
|
+
import { createHmac } from 'crypto';
|
|
5
|
+
import { getRequiredHeader, HeadersLike } from "../core";
|
|
6
|
+
|
|
7
|
+
export class Webhooks extends APIResource {
|
|
8
|
+
/**
|
|
9
|
+
* Validates that the given payload was sent by Orb and parses the payload.
|
|
10
|
+
*
|
|
11
|
+
* An error will be raised if the webhook payload was not sent by Orb.
|
|
12
|
+
*/
|
|
13
|
+
unwrap(
|
|
14
|
+
payload: string,
|
|
15
|
+
headers: HeadersLike,
|
|
16
|
+
secret: string | undefined | null = this._client.webhookSecret,
|
|
17
|
+
): Object {
|
|
18
|
+
this.verifySignature(payload, headers, secret);
|
|
19
|
+
return JSON.parse(payload);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private parseSecret(secret: string | null | undefined): Uint8Array {
|
|
23
|
+
if (!secret) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"The webhook secret must either be set using the env var, ORB_WEBHOOK_SECRET, on the client class, Orb({ webhookSecret: '123' }), or passed to this function",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const buf = Buffer.from(secret, 'utf-8');
|
|
30
|
+
if (buf.toString('utf-8') !== secret) {
|
|
31
|
+
throw new Error(`Given secret is not valid`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return new Uint8Array(buf);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private signPayload(payload: string, { timestamp, secret }: { timestamp: string; secret: Uint8Array }) {
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
const toSign = encoder.encode(`v1:${timestamp}:${payload}`);
|
|
40
|
+
|
|
41
|
+
const hmac = createHmac('sha256', secret);
|
|
42
|
+
hmac.update(toSign);
|
|
43
|
+
|
|
44
|
+
return `v1=${hmac.digest('hex')}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Make an assertion, if not `true`, then throw. */
|
|
48
|
+
private assert(expr: unknown, msg = ''): asserts expr {
|
|
49
|
+
if (!expr) {
|
|
50
|
+
throw new Error(msg);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Compare to array buffers or data views in a way that timing based attacks
|
|
55
|
+
* cannot gain information about the platform. */
|
|
56
|
+
private timingSafeEqual(
|
|
57
|
+
a: ArrayBufferView | ArrayBufferLike | DataView,
|
|
58
|
+
b: ArrayBufferView | ArrayBufferLike | DataView,
|
|
59
|
+
): boolean {
|
|
60
|
+
if (a.byteLength !== b.byteLength) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (!(a instanceof DataView)) {
|
|
64
|
+
a = new DataView(ArrayBuffer.isView(a) ? a.buffer : a);
|
|
65
|
+
}
|
|
66
|
+
if (!(b instanceof DataView)) {
|
|
67
|
+
b = new DataView(ArrayBuffer.isView(b) ? b.buffer : b);
|
|
68
|
+
}
|
|
69
|
+
this.assert(a instanceof DataView);
|
|
70
|
+
this.assert(b instanceof DataView);
|
|
71
|
+
const length = a.byteLength;
|
|
72
|
+
let out = 0;
|
|
73
|
+
let i = -1;
|
|
74
|
+
while (++i < length) {
|
|
75
|
+
out |= a.getUint8(i) ^ b.getUint8(i);
|
|
76
|
+
}
|
|
77
|
+
return out === 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates whether or not the webhook payload was sent by Orb.
|
|
82
|
+
*
|
|
83
|
+
* An error will be raised if the webhook payload was not sent by Orb.
|
|
84
|
+
*/
|
|
85
|
+
verifySignature(
|
|
86
|
+
body: string,
|
|
87
|
+
headers: HeadersLike,
|
|
88
|
+
secret: string | undefined | null = this._client.webhookSecret,
|
|
89
|
+
): void {
|
|
90
|
+
const whsecret = this.parseSecret(secret);
|
|
91
|
+
|
|
92
|
+
const msgTimestamp = getRequiredHeader(headers, 'X-Orb-Timestamp');
|
|
93
|
+
const msgSignature = getRequiredHeader(headers, 'X-Orb-Signature');
|
|
94
|
+
|
|
95
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
96
|
+
const timestamp = new Date(msgTimestamp);
|
|
97
|
+
const timestampSeconds = Math.floor(timestamp.getTime() / 1000);
|
|
98
|
+
if (isNaN(timestampSeconds)) {
|
|
99
|
+
throw new Error('Invalid timestamp header');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const webhookToleranceInSeconds = 5 * 60; // 5 minutes
|
|
103
|
+
if (nowSeconds - timestampSeconds > webhookToleranceInSeconds) {
|
|
104
|
+
throw new Error('Webhook timestamp is too old');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (timestampSeconds > nowSeconds + webhookToleranceInSeconds) {
|
|
108
|
+
console.warn({ timestampSeconds, nowSeconds, webhookToleranceInSeconds });
|
|
109
|
+
throw new Error('Webhook timestamp is too new');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof body !== 'string') {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'Webhook body must be passed as the raw JSON string sent from the server (do not parse it first).',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const computedSignature = this.signPayload(body, { timestamp: msgTimestamp, secret: whsecret });
|
|
119
|
+
const expectedSignature = computedSignature.split('=')[1];
|
|
120
|
+
|
|
121
|
+
const passedSignatures = msgSignature.split(' ');
|
|
122
|
+
|
|
123
|
+
const encoder = new globalThis.TextEncoder();
|
|
124
|
+
for (const versionedSignature of passedSignatures) {
|
|
125
|
+
const [version, signature] = versionedSignature.split('=');
|
|
126
|
+
console.log({ version, signature, expectedSignature, computedSignature });
|
|
127
|
+
|
|
128
|
+
if (version !== 'v1') {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (this.timingSafeEqual(encoder.encode(signature), encoder.encode(expectedSignature))) {
|
|
133
|
+
// valid!
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw new Error('None of the given webhook signatures match the expected signature');
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '2.
|
|
1
|
+
export const VERSION = '2.2.0'; // x-release-please-version
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "2.
|
|
1
|
+
export declare const VERSION = "2.2.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.js
CHANGED
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '2.
|
|
1
|
+
export const VERSION = '2.2.0'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|