@drawbridge/drawbridge-utils 0.0.132 → 0.0.134

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.
@@ -1370,10 +1370,6 @@ var featuresFor = (array = []) => Object.values({
1370
1370
  },
1371
1371
  { denied: {}, granted: {} }
1372
1372
  );
1373
- var overage = (actionCents) => ({
1374
- actionCents,
1375
- overages: { actions: String(actionCents) }
1376
- });
1377
1373
  var all = {
1378
1374
  features: (array = []) => featuresFor([
1379
1375
  connection.attentive.key,
@@ -1455,7 +1451,7 @@ var plans = {
1455
1451
  ["Storage", "10GB"]
1456
1452
  ]
1457
1453
  },
1458
- ...overage(2.5),
1454
+ actionCents: 2.5,
1459
1455
  title: "Starter",
1460
1456
  conversion: 2
1461
1457
  },
@@ -1486,7 +1482,7 @@ var plans = {
1486
1482
  ["Storage", "20GB"]
1487
1483
  ]
1488
1484
  },
1489
- ...overage(2),
1485
+ actionCents: 2,
1490
1486
  title: "Pro",
1491
1487
  conversion: 1.5
1492
1488
  },
@@ -1517,7 +1513,7 @@ var plans = {
1517
1513
  ["Storage", "50GB"]
1518
1514
  ]
1519
1515
  },
1520
- ...overage(1.85),
1516
+ actionCents: 1.85,
1521
1517
  title: "Premium",
1522
1518
  conversion: 1
1523
1519
  },
@@ -1548,7 +1544,7 @@ var plans = {
1548
1544
  ["Storage", "100GB"]
1549
1545
  ]
1550
1546
  },
1551
- ...overage(1.5),
1547
+ actionCents: 1.5,
1552
1548
  title: "Elite",
1553
1549
  conversion: 0.5
1554
1550
  }
@@ -1570,12 +1566,10 @@ var resolvePlan = (subscription) => {
1570
1566
  // silently lacking it would be a support ticket, not a pricing decision.
1571
1567
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
1572
1568
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
1573
- // A custom plan stores its overage BARE on `custom.overages` a different
1574
- // shape from the catalog's nested one. Number() so a deal stored as a string
1575
- // still resolves to cents-per-action; an unnamed overage stays undefined
1576
- // (it bills nothing) rather than becoming NaN.
1569
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
1570
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
1571
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
1577
1572
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
1578
- overages: { actions: custom.overages },
1579
1573
  title: custom.title || "Custom"
1580
1574
  };
1581
1575
  };
@@ -1296,10 +1296,6 @@ var featuresFor = (array = []) => Object.values({
1296
1296
  },
1297
1297
  { denied: {}, granted: {} }
1298
1298
  );
1299
- var overage = (actionCents) => ({
1300
- actionCents,
1301
- overages: { actions: String(actionCents) }
1302
- });
1303
1299
  var all = {
1304
1300
  features: (array = []) => featuresFor([
1305
1301
  connection.attentive.key,
@@ -1381,7 +1377,7 @@ var plans = {
1381
1377
  ["Storage", "10GB"]
1382
1378
  ]
1383
1379
  },
1384
- ...overage(2.5),
1380
+ actionCents: 2.5,
1385
1381
  title: "Starter",
1386
1382
  conversion: 2
1387
1383
  },
@@ -1412,7 +1408,7 @@ var plans = {
1412
1408
  ["Storage", "20GB"]
1413
1409
  ]
1414
1410
  },
1415
- ...overage(2),
1411
+ actionCents: 2,
1416
1412
  title: "Pro",
1417
1413
  conversion: 1.5
1418
1414
  },
@@ -1443,7 +1439,7 @@ var plans = {
1443
1439
  ["Storage", "50GB"]
1444
1440
  ]
1445
1441
  },
1446
- ...overage(1.85),
1442
+ actionCents: 1.85,
1447
1443
  title: "Premium",
1448
1444
  conversion: 1
1449
1445
  },
@@ -1474,7 +1470,7 @@ var plans = {
1474
1470
  ["Storage", "100GB"]
1475
1471
  ]
1476
1472
  },
1477
- ...overage(1.5),
1473
+ actionCents: 1.5,
1478
1474
  title: "Elite",
1479
1475
  conversion: 0.5
1480
1476
  }
@@ -1496,12 +1492,10 @@ var resolvePlan = (subscription) => {
1496
1492
  // silently lacking it would be a support ticket, not a pricing decision.
1497
1493
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
1498
1494
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
1499
- // A custom plan stores its overage BARE on `custom.overages` a different
1500
- // shape from the catalog's nested one. Number() so a deal stored as a string
1501
- // still resolves to cents-per-action; an unnamed overage stays undefined
1502
- // (it bills nothing) rather than becoming NaN.
1495
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
1496
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
1497
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
1503
1498
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
1504
- overages: { actions: custom.overages },
1505
1499
  title: custom.title || "Custom"
1506
1500
  };
1507
1501
  };
package/dist/plans.cjs CHANGED
@@ -291,10 +291,6 @@ var featuresFor = (array = []) => Object.values({
291
291
  },
292
292
  { denied: {}, granted: {} }
293
293
  );
294
- var overage = (actionCents) => ({
295
- actionCents,
296
- overages: { actions: String(actionCents) }
297
- });
298
294
  var unitAmountDecimal = (cents) => {
299
295
  const value = Number(cents);
300
296
  if (cents == null || !Number.isFinite(value)) throw new Error(`Invalid overage rate: ${cents}`);
@@ -381,7 +377,7 @@ var plans = {
381
377
  ["Storage", "10GB"]
382
378
  ]
383
379
  },
384
- ...overage(2.5),
380
+ actionCents: 2.5,
385
381
  title: "Starter",
386
382
  conversion: 2
387
383
  },
@@ -412,7 +408,7 @@ var plans = {
412
408
  ["Storage", "20GB"]
413
409
  ]
414
410
  },
415
- ...overage(2),
411
+ actionCents: 2,
416
412
  title: "Pro",
417
413
  conversion: 1.5
418
414
  },
@@ -443,7 +439,7 @@ var plans = {
443
439
  ["Storage", "50GB"]
444
440
  ]
445
441
  },
446
- ...overage(1.85),
442
+ actionCents: 1.85,
447
443
  title: "Premium",
448
444
  conversion: 1
449
445
  },
@@ -474,7 +470,7 @@ var plans = {
474
470
  ["Storage", "100GB"]
475
471
  ]
476
472
  },
477
- ...overage(1.5),
473
+ actionCents: 1.5,
478
474
  title: "Elite",
479
475
  conversion: 0.5
480
476
  }
@@ -496,12 +492,10 @@ var resolvePlan = (subscription) => {
496
492
  // silently lacking it would be a support ticket, not a pricing decision.
497
493
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
498
494
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
499
- // A custom plan stores its overage BARE on `custom.overages` a different
500
- // shape from the catalog's nested one. Number() so a deal stored as a string
501
- // still resolves to cents-per-action; an unnamed overage stays undefined
502
- // (it bills nothing) rather than becoming NaN.
495
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
496
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
497
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
503
498
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
504
- overages: { actions: custom.overages },
505
499
  title: custom.title || "Custom"
506
500
  };
507
501
  };
package/dist/plans.d.cts CHANGED
@@ -31,21 +31,12 @@ const featuresFor = ( array = [] ) => Object.values({
31
31
  { denied : {}, granted : {} }
32
32
  );
33
33
 
34
- // Overage pricing, unit-explicit.
35
- //
36
- // `actionCents` is CENTS PER ACTION, as a number: 2.5 = $0.025. Before
37
- // 2026-05-31 the same rate was encoded as dollars-per-1,000-actions, Stripe read
38
- // it as per-action, and every paid tier billed 10x. `overages.actions` — the
39
- // string this used to be the only name for — is kept as a byte-identical alias
40
- // so consumers can be pin-bumped in any order; drop it once they all read
41
- // `actionCents`.
34
+ // Each paid plan's `actionCents` is CENTS PER ACTION, as a number: 2.5 = $0.025.
35
+ // Before 2026-05-31 the rate was encoded as dollars-per-1,000-actions, Stripe
36
+ // read it as per-action, and every paid tier billed 10x.
42
37
  //
43
38
  // Sub-cent precision is deliberate: Premium's 1.85¢ does not fit integer cents
44
39
  // (2¢ is +8%, 1¢ is −46%).
45
- const overage = ( actionCents ) => ({
46
- actionCents,
47
- overages : { actions : String( actionCents ) }
48
- });
49
40
 
50
41
  // Cents → Stripe's `unit_amount_decimal`.
51
42
  //
@@ -148,7 +139,7 @@ const plans = {
148
139
  [ 'Storage', '10GB' ]
149
140
  ]
150
141
  },
151
- ...overage( 2.5 ),
142
+ actionCents : 2.5,
152
143
  title : 'Starter',
153
144
  conversion : 2
154
145
  },
@@ -179,7 +170,7 @@ const plans = {
179
170
  [ 'Storage', '20GB' ]
180
171
  ]
181
172
  },
182
- ...overage( 2 ),
173
+ actionCents : 2,
183
174
  title : 'Pro',
184
175
  conversion : 1.5
185
176
  },
@@ -210,7 +201,7 @@ const plans = {
210
201
  [ 'Storage', '50GB' ]
211
202
  ]
212
203
  },
213
- ...overage( 1.85 ),
204
+ actionCents : 1.85,
214
205
  title : 'Premium',
215
206
  conversion : 1
216
207
  },
@@ -241,7 +232,7 @@ const plans = {
241
232
  [ 'Storage', '100GB' ]
242
233
  ]
243
234
  },
244
- ...overage( 1.5 ),
235
+ actionCents : 1.5,
245
236
  title : 'Elite',
246
237
  conversion : 0.5
247
238
  },
@@ -279,12 +270,10 @@ const resolvePlan = ( subscription ) => {
279
270
  // silently lacking it would be a support ticket, not a pricing decision.
280
271
  features : all.features([ organization.networking.key, ...( custom.features?.granted || [] ) ]),
281
272
  limits : all.limits( custom.limits?.organization || {} ),
282
- // A custom plan stores its overage BARE on `custom.overages` a different
283
- // shape from the catalog's nested one. Number() so a deal stored as a string
284
- // still resolves to cents-per-action; an unnamed overage stays undefined
285
- // (it bills nothing) rather than becoming NaN.
273
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
274
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
275
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
286
276
  actionCents : custom.overages == null ? undefined : Number( custom.overages ),
287
- overages : { actions : custom.overages },
288
277
  title : custom.title || 'Custom'
289
278
  };
290
279
 
package/dist/plans.d.ts CHANGED
@@ -31,21 +31,12 @@ const featuresFor = ( array = [] ) => Object.values({
31
31
  { denied : {}, granted : {} }
32
32
  );
33
33
 
34
- // Overage pricing, unit-explicit.
35
- //
36
- // `actionCents` is CENTS PER ACTION, as a number: 2.5 = $0.025. Before
37
- // 2026-05-31 the same rate was encoded as dollars-per-1,000-actions, Stripe read
38
- // it as per-action, and every paid tier billed 10x. `overages.actions` — the
39
- // string this used to be the only name for — is kept as a byte-identical alias
40
- // so consumers can be pin-bumped in any order; drop it once they all read
41
- // `actionCents`.
34
+ // Each paid plan's `actionCents` is CENTS PER ACTION, as a number: 2.5 = $0.025.
35
+ // Before 2026-05-31 the rate was encoded as dollars-per-1,000-actions, Stripe
36
+ // read it as per-action, and every paid tier billed 10x.
42
37
  //
43
38
  // Sub-cent precision is deliberate: Premium's 1.85¢ does not fit integer cents
44
39
  // (2¢ is +8%, 1¢ is −46%).
45
- const overage = ( actionCents ) => ({
46
- actionCents,
47
- overages : { actions : String( actionCents ) }
48
- });
49
40
 
50
41
  // Cents → Stripe's `unit_amount_decimal`.
51
42
  //
@@ -148,7 +139,7 @@ const plans = {
148
139
  [ 'Storage', '10GB' ]
149
140
  ]
150
141
  },
151
- ...overage( 2.5 ),
142
+ actionCents : 2.5,
152
143
  title : 'Starter',
153
144
  conversion : 2
154
145
  },
@@ -179,7 +170,7 @@ const plans = {
179
170
  [ 'Storage', '20GB' ]
180
171
  ]
181
172
  },
182
- ...overage( 2 ),
173
+ actionCents : 2,
183
174
  title : 'Pro',
184
175
  conversion : 1.5
185
176
  },
@@ -210,7 +201,7 @@ const plans = {
210
201
  [ 'Storage', '50GB' ]
211
202
  ]
212
203
  },
213
- ...overage( 1.85 ),
204
+ actionCents : 1.85,
214
205
  title : 'Premium',
215
206
  conversion : 1
216
207
  },
@@ -241,7 +232,7 @@ const plans = {
241
232
  [ 'Storage', '100GB' ]
242
233
  ]
243
234
  },
244
- ...overage( 1.5 ),
235
+ actionCents : 1.5,
245
236
  title : 'Elite',
246
237
  conversion : 0.5
247
238
  },
@@ -279,12 +270,10 @@ const resolvePlan = ( subscription ) => {
279
270
  // silently lacking it would be a support ticket, not a pricing decision.
280
271
  features : all.features([ organization.networking.key, ...( custom.features?.granted || [] ) ]),
281
272
  limits : all.limits( custom.limits?.organization || {} ),
282
- // A custom plan stores its overage BARE on `custom.overages` a different
283
- // shape from the catalog's nested one. Number() so a deal stored as a string
284
- // still resolves to cents-per-action; an unnamed overage stays undefined
285
- // (it bills nothing) rather than becoming NaN.
273
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
274
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
275
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
286
276
  actionCents : custom.overages == null ? undefined : Number( custom.overages ),
287
- overages : { actions : custom.overages },
288
277
  title : custom.title || 'Custom'
289
278
  };
290
279
 
package/dist/plans.js CHANGED
@@ -252,10 +252,6 @@ var featuresFor = (array = []) => Object.values({
252
252
  },
253
253
  { denied: {}, granted: {} }
254
254
  );
255
- var overage = (actionCents) => ({
256
- actionCents,
257
- overages: { actions: String(actionCents) }
258
- });
259
255
  var unitAmountDecimal = (cents) => {
260
256
  const value = Number(cents);
261
257
  if (cents == null || !Number.isFinite(value)) throw new Error(`Invalid overage rate: ${cents}`);
@@ -342,7 +338,7 @@ var plans = {
342
338
  ["Storage", "10GB"]
343
339
  ]
344
340
  },
345
- ...overage(2.5),
341
+ actionCents: 2.5,
346
342
  title: "Starter",
347
343
  conversion: 2
348
344
  },
@@ -373,7 +369,7 @@ var plans = {
373
369
  ["Storage", "20GB"]
374
370
  ]
375
371
  },
376
- ...overage(2),
372
+ actionCents: 2,
377
373
  title: "Pro",
378
374
  conversion: 1.5
379
375
  },
@@ -404,7 +400,7 @@ var plans = {
404
400
  ["Storage", "50GB"]
405
401
  ]
406
402
  },
407
- ...overage(1.85),
403
+ actionCents: 1.85,
408
404
  title: "Premium",
409
405
  conversion: 1
410
406
  },
@@ -435,7 +431,7 @@ var plans = {
435
431
  ["Storage", "100GB"]
436
432
  ]
437
433
  },
438
- ...overage(1.5),
434
+ actionCents: 1.5,
439
435
  title: "Elite",
440
436
  conversion: 0.5
441
437
  }
@@ -457,12 +453,10 @@ var resolvePlan = (subscription) => {
457
453
  // silently lacking it would be a support ticket, not a pricing decision.
458
454
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
459
455
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
460
- // A custom plan stores its overage BARE on `custom.overages` a different
461
- // shape from the catalog's nested one. Number() so a deal stored as a string
462
- // still resolves to cents-per-action; an unnamed overage stays undefined
463
- // (it bills nothing) rather than becoming NaN.
456
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
457
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
458
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
464
459
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
465
- overages: { actions: custom.overages },
466
460
  title: custom.title || "Custom"
467
461
  };
468
462
  };
package/dist/pricing.cjs CHANGED
@@ -303,10 +303,6 @@ var featuresFor = (array = []) => Object.values({
303
303
  },
304
304
  { denied: {}, granted: {} }
305
305
  );
306
- var overage = (actionCents) => ({
307
- actionCents,
308
- overages: { actions: String(actionCents) }
309
- });
310
306
  var unitAmountDecimal = (cents) => {
311
307
  const value = Number(cents);
312
308
  if (cents == null || !Number.isFinite(value)) throw new Error(`Invalid overage rate: ${cents}`);
@@ -393,7 +389,7 @@ var plans = {
393
389
  ["Storage", "10GB"]
394
390
  ]
395
391
  },
396
- ...overage(2.5),
392
+ actionCents: 2.5,
397
393
  title: "Starter",
398
394
  conversion: 2
399
395
  },
@@ -424,7 +420,7 @@ var plans = {
424
420
  ["Storage", "20GB"]
425
421
  ]
426
422
  },
427
- ...overage(2),
423
+ actionCents: 2,
428
424
  title: "Pro",
429
425
  conversion: 1.5
430
426
  },
@@ -455,7 +451,7 @@ var plans = {
455
451
  ["Storage", "50GB"]
456
452
  ]
457
453
  },
458
- ...overage(1.85),
454
+ actionCents: 1.85,
459
455
  title: "Premium",
460
456
  conversion: 1
461
457
  },
@@ -486,7 +482,7 @@ var plans = {
486
482
  ["Storage", "100GB"]
487
483
  ]
488
484
  },
489
- ...overage(1.5),
485
+ actionCents: 1.5,
490
486
  title: "Elite",
491
487
  conversion: 0.5
492
488
  }
@@ -508,12 +504,10 @@ var resolvePlan = (subscription) => {
508
504
  // silently lacking it would be a support ticket, not a pricing decision.
509
505
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
510
506
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
511
- // A custom plan stores its overage BARE on `custom.overages` a different
512
- // shape from the catalog's nested one. Number() so a deal stored as a string
513
- // still resolves to cents-per-action; an unnamed overage stays undefined
514
- // (it bills nothing) rather than becoming NaN.
507
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
508
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
509
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
515
510
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
516
- overages: { actions: custom.overages },
517
511
  title: custom.title || "Custom"
518
512
  };
519
513
  };
@@ -25,8 +25,6 @@ import '@drawbridge/drawbridge-telemetry';
25
25
  // actionCents CENTS per action, number. 2.5 = $0.025.
26
26
  // Starter 2.5 · Pro 2 · Premium 1.85 · Elite 1.5.
27
27
  // Free has none — it is hard-capped at 200 actions.
28
- // overages.actions DEPRECATED alias of actionCents, as a string. Kept for
29
- // one release so consumers pin-bump in any order.
30
28
  // unitAmountDecimal() cents → Stripe `unit_amount_decimal` (a cents string).
31
29
  // plans[ id ].conversion PERCENT of order value taken as the conversion fee.
32
30
  // limits.* counts, except storage which is BYTES.
package/dist/pricing.d.ts CHANGED
@@ -25,8 +25,6 @@ import '@drawbridge/drawbridge-telemetry';
25
25
  // actionCents CENTS per action, number. 2.5 = $0.025.
26
26
  // Starter 2.5 · Pro 2 · Premium 1.85 · Elite 1.5.
27
27
  // Free has none — it is hard-capped at 200 actions.
28
- // overages.actions DEPRECATED alias of actionCents, as a string. Kept for
29
- // one release so consumers pin-bump in any order.
30
28
  // unitAmountDecimal() cents → Stripe `unit_amount_decimal` (a cents string).
31
29
  // plans[ id ].conversion PERCENT of order value taken as the conversion fee.
32
30
  // limits.* counts, except storage which is BYTES.
package/dist/pricing.js CHANGED
@@ -252,10 +252,6 @@ var featuresFor = (array = []) => Object.values({
252
252
  },
253
253
  { denied: {}, granted: {} }
254
254
  );
255
- var overage = (actionCents) => ({
256
- actionCents,
257
- overages: { actions: String(actionCents) }
258
- });
259
255
  var unitAmountDecimal = (cents) => {
260
256
  const value = Number(cents);
261
257
  if (cents == null || !Number.isFinite(value)) throw new Error(`Invalid overage rate: ${cents}`);
@@ -342,7 +338,7 @@ var plans = {
342
338
  ["Storage", "10GB"]
343
339
  ]
344
340
  },
345
- ...overage(2.5),
341
+ actionCents: 2.5,
346
342
  title: "Starter",
347
343
  conversion: 2
348
344
  },
@@ -373,7 +369,7 @@ var plans = {
373
369
  ["Storage", "20GB"]
374
370
  ]
375
371
  },
376
- ...overage(2),
372
+ actionCents: 2,
377
373
  title: "Pro",
378
374
  conversion: 1.5
379
375
  },
@@ -404,7 +400,7 @@ var plans = {
404
400
  ["Storage", "50GB"]
405
401
  ]
406
402
  },
407
- ...overage(1.85),
403
+ actionCents: 1.85,
408
404
  title: "Premium",
409
405
  conversion: 1
410
406
  },
@@ -435,7 +431,7 @@ var plans = {
435
431
  ["Storage", "100GB"]
436
432
  ]
437
433
  },
438
- ...overage(1.5),
434
+ actionCents: 1.5,
439
435
  title: "Elite",
440
436
  conversion: 0.5
441
437
  }
@@ -457,12 +453,10 @@ var resolvePlan = (subscription) => {
457
453
  // silently lacking it would be a support ticket, not a pricing decision.
458
454
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
459
455
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
460
- // A custom plan stores its overage BARE on `custom.overages` a different
461
- // shape from the catalog's nested one. Number() so a deal stored as a string
462
- // still resolves to cents-per-action; an unnamed overage stays undefined
463
- // (it bills nothing) rather than becoming NaN.
456
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
457
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
458
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
464
459
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
465
- overages: { actions: custom.overages },
466
460
  title: custom.title || "Custom"
467
461
  };
468
462
  };
@@ -1276,10 +1276,6 @@ var featuresFor = (array = []) => Object.values({
1276
1276
  },
1277
1277
  { denied: {}, granted: {} }
1278
1278
  );
1279
- var overage = (actionCents) => ({
1280
- actionCents,
1281
- overages: { actions: String(actionCents) }
1282
- });
1283
1279
  var all = {
1284
1280
  features: (array = []) => featuresFor([
1285
1281
  connection.attentive.key,
@@ -1361,7 +1357,7 @@ var plans = {
1361
1357
  ["Storage", "10GB"]
1362
1358
  ]
1363
1359
  },
1364
- ...overage(2.5),
1360
+ actionCents: 2.5,
1365
1361
  title: "Starter",
1366
1362
  conversion: 2
1367
1363
  },
@@ -1392,7 +1388,7 @@ var plans = {
1392
1388
  ["Storage", "20GB"]
1393
1389
  ]
1394
1390
  },
1395
- ...overage(2),
1391
+ actionCents: 2,
1396
1392
  title: "Pro",
1397
1393
  conversion: 1.5
1398
1394
  },
@@ -1423,7 +1419,7 @@ var plans = {
1423
1419
  ["Storage", "50GB"]
1424
1420
  ]
1425
1421
  },
1426
- ...overage(1.85),
1422
+ actionCents: 1.85,
1427
1423
  title: "Premium",
1428
1424
  conversion: 1
1429
1425
  },
@@ -1454,7 +1450,7 @@ var plans = {
1454
1450
  ["Storage", "100GB"]
1455
1451
  ]
1456
1452
  },
1457
- ...overage(1.5),
1453
+ actionCents: 1.5,
1458
1454
  title: "Elite",
1459
1455
  conversion: 0.5
1460
1456
  }
@@ -1476,12 +1472,10 @@ var resolvePlan = (subscription) => {
1476
1472
  // silently lacking it would be a support ticket, not a pricing decision.
1477
1473
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
1478
1474
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
1479
- // A custom plan stores its overage BARE on `custom.overages` a different
1480
- // shape from the catalog's nested one. Number() so a deal stored as a string
1481
- // still resolves to cents-per-action; an unnamed overage stays undefined
1482
- // (it bills nothing) rather than becoming NaN.
1475
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
1476
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
1477
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
1483
1478
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
1484
- overages: { actions: custom.overages },
1485
1479
  title: custom.title || "Custom"
1486
1480
  };
1487
1481
  };
package/dist/providers.js CHANGED
@@ -1232,10 +1232,6 @@ var featuresFor = (array = []) => Object.values({
1232
1232
  },
1233
1233
  { denied: {}, granted: {} }
1234
1234
  );
1235
- var overage = (actionCents) => ({
1236
- actionCents,
1237
- overages: { actions: String(actionCents) }
1238
- });
1239
1235
  var all = {
1240
1236
  features: (array = []) => featuresFor([
1241
1237
  connection.attentive.key,
@@ -1317,7 +1313,7 @@ var plans = {
1317
1313
  ["Storage", "10GB"]
1318
1314
  ]
1319
1315
  },
1320
- ...overage(2.5),
1316
+ actionCents: 2.5,
1321
1317
  title: "Starter",
1322
1318
  conversion: 2
1323
1319
  },
@@ -1348,7 +1344,7 @@ var plans = {
1348
1344
  ["Storage", "20GB"]
1349
1345
  ]
1350
1346
  },
1351
- ...overage(2),
1347
+ actionCents: 2,
1352
1348
  title: "Pro",
1353
1349
  conversion: 1.5
1354
1350
  },
@@ -1379,7 +1375,7 @@ var plans = {
1379
1375
  ["Storage", "50GB"]
1380
1376
  ]
1381
1377
  },
1382
- ...overage(1.85),
1378
+ actionCents: 1.85,
1383
1379
  title: "Premium",
1384
1380
  conversion: 1
1385
1381
  },
@@ -1410,7 +1406,7 @@ var plans = {
1410
1406
  ["Storage", "100GB"]
1411
1407
  ]
1412
1408
  },
1413
- ...overage(1.5),
1409
+ actionCents: 1.5,
1414
1410
  title: "Elite",
1415
1411
  conversion: 0.5
1416
1412
  }
@@ -1432,12 +1428,10 @@ var resolvePlan = (subscription) => {
1432
1428
  // silently lacking it would be a support ticket, not a pricing decision.
1433
1429
  features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
1434
1430
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
1435
- // A custom plan stores its overage BARE on `custom.overages` a different
1436
- // shape from the catalog's nested one. Number() so a deal stored as a string
1437
- // still resolves to cents-per-action; an unnamed overage stays undefined
1438
- // (it bills nothing) rather than becoming NaN.
1431
+ // A custom plan stores its overage BARE on `custom.overages`. Number() so
1432
+ // a deal stored as a string still resolves to cents-per-action; an unnamed
1433
+ // overage stays undefined (it bills nothing) rather than becoming NaN.
1439
1434
  actionCents: custom.overages == null ? void 0 : Number(custom.overages),
1440
- overages: { actions: custom.overages },
1441
1435
  title: custom.title || "Custom"
1442
1436
  };
1443
1437
  };
package/dist/twilio.cjs CHANGED
@@ -107,28 +107,37 @@ var twilio = {
107
107
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
108
108
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
109
109
  // IncomingPhoneNumbers resource docs:
110
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
111
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
110
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
111
+ // query: SmsEnabled, Contains, PageSize (1-1000)
112
112
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
113
113
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
114
114
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
115
115
  // Basic auth throughout, like Messages.
116
- searchNumbers: async ({ accountSid, areaCode, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
116
+ //
117
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
118
+ // than an inventory one. A US local number may not send A2P SMS without full
119
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
120
+ // Toll-Free Verification — one API submission per number (see
121
+ // submitVerification below), no TCR brand, no campaign fees. Until a
122
+ // verification is approved, Twilio blocks the number's US-bound messages
123
+ // outright — which is why a purchased number starts `pending` and the send
124
+ // path refuses anything but `verified`.
125
+ searchNumbers: async ({ accountSid, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
117
126
  if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
118
127
  const page = Number(cursor) || 0;
119
128
  const result = await request2({
120
129
  method: "GET",
121
- url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/Local.json",
130
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/TollFree.json",
122
131
  headers: {
123
132
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
124
133
  },
125
134
  query: {
126
135
  SmsEnabled: true,
127
136
  PageSize: limit,
128
- ...areaCode && { AreaCode: areaCode },
129
137
  // `Contains` is the vendor's matching-pattern filter — digits
130
- // match anywhere in the number, so one search box serves "my
131
- // area code" and "ends in 7777" alike.
138
+ // match anywhere in the number, so one search box serves "an 888
139
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
140
+ // filter and went with Local.json.)
132
141
  ...contains && { Contains: contains },
133
142
  ...page && { Page: page }
134
143
  }
@@ -175,6 +184,84 @@ var twilio = {
175
184
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
176
185
  }
177
186
  });
187
+ },
188
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
189
+ // number actually deliver. From the Tollfree Verification resource docs
190
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
191
+ // POST /v1/Tollfree/Verifications
192
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
193
+ // UseCaseCategories (array), UseCaseSummary,
194
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
195
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
196
+ // GET /v1/Tollfree/Verifications/{Sid}
197
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
198
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
199
+ // Basic auth, form-encoded, like everything above. There is no status
200
+ // callback on this resource — the caller polls the fetch.
201
+ //
202
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
203
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
204
+ // ISVs, support.twilio.com) — so businessName, website and email here are
205
+ // the merchant's, collected at purchase.
206
+ //
207
+ // The use-case fields are PLATFORM facts and live here beside the message
208
+ // composition for the same reason it does: how Drawbridge collects opt-in
209
+ // (its own hosted entry forms) and what its messages look like are
210
+ // properties of sending through Drawbridge, not of any one merchant.
211
+ submitVerification: async ({
212
+ accountSid,
213
+ authToken,
214
+ businessName,
215
+ email,
216
+ numberSid,
217
+ optInImage,
218
+ request: request2 = request,
219
+ volume,
220
+ website
221
+ }) => {
222
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website }).filter(([, value]) => !value).map(([name]) => name);
223
+ if (missing.length) throw new Error("Toll-free verification submission missing \u2014 " + missing.join(", "));
224
+ const body = new URLSearchParams();
225
+ body.append("BusinessName", businessName);
226
+ body.append("BusinessWebsite", website);
227
+ body.append("NotificationEmail", email);
228
+ body.append("UseCaseCategories", "MARKETING");
229
+ body.append("UseCaseSummary", "Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business's Drawbridge-hosted entry form.");
230
+ body.append("ProductionMessageSample", businessName + "\nThanks for entering! We'll text you here about your entry.\nReply STOP to opt out");
231
+ body.append("OptInImageUrls", optInImage);
232
+ body.append("OptInType", "WEB_FORM");
233
+ body.append("MessageVolume", volume || "1,000");
234
+ body.append("TollfreePhoneNumberSid", numberSid);
235
+ const result = await request2({
236
+ method: "POST",
237
+ type: "form",
238
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications",
239
+ headers: {
240
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
241
+ },
242
+ body
243
+ });
244
+ return {
245
+ sid: result == null ? void 0 : result.sid,
246
+ status: result == null ? void 0 : result.status
247
+ };
248
+ },
249
+ fetchVerification: async ({ accountSid, authToken, request: request2 = request, sid }) => {
250
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
251
+ if (!sid) throw new Error("Twilio verification fetch needs the verification sid");
252
+ const result = await request2({
253
+ method: "GET",
254
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications/" + sid,
255
+ headers: {
256
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
257
+ }
258
+ });
259
+ return {
260
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
261
+ // review states → pending.
262
+ outcome: (result == null ? void 0 : result.status) === "TWILIO_APPROVED" ? "approved" : (result == null ? void 0 : result.status) === "TWILIO_REJECTED" ? "rejected" : "pending",
263
+ reason: (result == null ? void 0 : result.rejection_reason) || null
264
+ };
178
265
  }
179
266
  };
180
267
  // Annotate the CommonJS export names for ESM import in node:
package/dist/twilio.d.cts CHANGED
@@ -70,14 +70,23 @@ const twilio = {
70
70
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
71
71
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
72
72
  // IncomingPhoneNumbers resource docs:
73
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
74
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
73
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
74
+ // query: SmsEnabled, Contains, PageSize (1-1000)
75
75
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
76
76
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
77
77
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
78
78
  // Basic auth throughout, like Messages.
79
+ //
80
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
81
+ // than an inventory one. A US local number may not send A2P SMS without full
82
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
83
+ // Toll-Free Verification — one API submission per number (see
84
+ // submitVerification below), no TCR brand, no campaign fees. Until a
85
+ // verification is approved, Twilio blocks the number's US-bound messages
86
+ // outright — which is why a purchased number starts `pending` and the send
87
+ // path refuses anything but `verified`.
79
88
 
80
- searchNumbers : async ({ accountSid, areaCode, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
89
+ searchNumbers : async ({ accountSid, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
81
90
 
82
91
  if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
83
92
 
@@ -93,17 +102,17 @@ const twilio = {
93
102
 
94
103
  const result = await request$1({
95
104
  method : 'GET',
96
- url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/Local.json',
105
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/TollFree.json',
97
106
  headers : {
98
107
  'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
99
108
  },
100
109
  query : {
101
110
  SmsEnabled : true,
102
111
  PageSize : limit,
103
- ...( areaCode && { AreaCode : areaCode } ),
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...( contains && { Contains : contains } ),
108
117
  ...( page && { Page : page } )
109
118
  }
@@ -163,6 +172,111 @@ const twilio = {
163
172
  }
164
173
  });
165
174
 
175
+ },
176
+
177
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
178
+ // number actually deliver. From the Tollfree Verification resource docs
179
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
180
+ // POST /v1/Tollfree/Verifications
181
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
182
+ // UseCaseCategories (array), UseCaseSummary,
183
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
184
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
185
+ // GET /v1/Tollfree/Verifications/{Sid}
186
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
187
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
188
+ // Basic auth, form-encoded, like everything above. There is no status
189
+ // callback on this resource — the caller polls the fetch.
190
+ //
191
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
192
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
193
+ // ISVs, support.twilio.com) — so businessName, website and email here are
194
+ // the merchant's, collected at purchase.
195
+ //
196
+ // The use-case fields are PLATFORM facts and live here beside the message
197
+ // composition for the same reason it does: how Drawbridge collects opt-in
198
+ // (its own hosted entry forms) and what its messages look like are
199
+ // properties of sending through Drawbridge, not of any one merchant.
200
+ submitVerification : async ({
201
+ accountSid,
202
+ authToken,
203
+ businessName,
204
+ email,
205
+ numberSid,
206
+ optInImage,
207
+ request: request$1 = request,
208
+ volume,
209
+ website
210
+ }) => {
211
+
212
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website })
213
+ .filter( ( [ , value ] ) => ! value )
214
+ .map( ( [ name ] ) => name );
215
+
216
+ if( missing.length ) throw new Error( 'Toll-free verification submission missing — ' + missing.join( ', ' ) );
217
+
218
+ // URLSearchParams rather than an object: UseCaseCategories and
219
+ // OptInImageUrls are array parameters, which Twilio takes as REPEATED
220
+ // form keys — an object body would comma-join them into one value.
221
+ const body = new URLSearchParams();
222
+
223
+ body.append( 'BusinessName', businessName );
224
+ body.append( 'BusinessWebsite', website );
225
+ body.append( 'NotificationEmail', email );
226
+ body.append( 'UseCaseCategories', 'MARKETING' );
227
+ body.append( 'UseCaseSummary', 'Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business\'s Drawbridge-hosted entry form.' );
228
+ // Mirrors the sms() composition above — newline-separated, brand first,
229
+ // opt-out line appended — so the reviewed sample is the sent shape.
230
+ body.append( 'ProductionMessageSample', businessName + '\nThanks for entering! We\'ll text you here about your entry.\nReply STOP to opt out' );
231
+ body.append( 'OptInImageUrls', optInImage );
232
+ // The entry form is a web form; the phone field is beside the consent
233
+ // language on the merchant's campaign page.
234
+ body.append( 'OptInType', 'WEB_FORM' );
235
+ body.append( 'MessageVolume', volume || '1,000' );
236
+ body.append( 'TollfreePhoneNumberSid', numberSid );
237
+
238
+ const result = await request$1({
239
+ method : 'POST',
240
+ type : 'form',
241
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications',
242
+ headers : {
243
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
244
+ },
245
+ body
246
+ });
247
+
248
+ return {
249
+ sid : result?.sid,
250
+ status : result?.status
251
+ };
252
+
253
+ },
254
+
255
+ fetchVerification : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
256
+
257
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
258
+ if( ! sid ) throw new Error( 'Twilio verification fetch needs the verification sid' );
259
+
260
+ const result = await request$1({
261
+ method : 'GET',
262
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications/' + sid,
263
+ headers : {
264
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
265
+ }
266
+ });
267
+
268
+ // The caller-facing shape is ours; the vendor's vocabulary stays here.
269
+ return {
270
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
271
+ // review states → pending.
272
+ outcome : result?.status === 'TWILIO_APPROVED'
273
+ ? 'approved'
274
+ : result?.status === 'TWILIO_REJECTED'
275
+ ? 'rejected'
276
+ : 'pending',
277
+ reason : result?.rejection_reason || null
278
+ };
279
+
166
280
  }
167
281
 
168
282
  };
package/dist/twilio.d.ts CHANGED
@@ -70,14 +70,23 @@ const twilio = {
70
70
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
71
71
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
72
72
  // IncomingPhoneNumbers resource docs:
73
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
74
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
73
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
74
+ // query: SmsEnabled, Contains, PageSize (1-1000)
75
75
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
76
76
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
77
77
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
78
78
  // Basic auth throughout, like Messages.
79
+ //
80
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
81
+ // than an inventory one. A US local number may not send A2P SMS without full
82
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
83
+ // Toll-Free Verification — one API submission per number (see
84
+ // submitVerification below), no TCR brand, no campaign fees. Until a
85
+ // verification is approved, Twilio blocks the number's US-bound messages
86
+ // outright — which is why a purchased number starts `pending` and the send
87
+ // path refuses anything but `verified`.
79
88
 
80
- searchNumbers : async ({ accountSid, areaCode, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
89
+ searchNumbers : async ({ accountSid, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
81
90
 
82
91
  if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
83
92
 
@@ -93,17 +102,17 @@ const twilio = {
93
102
 
94
103
  const result = await request$1({
95
104
  method : 'GET',
96
- url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/Local.json',
105
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/TollFree.json',
97
106
  headers : {
98
107
  'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
99
108
  },
100
109
  query : {
101
110
  SmsEnabled : true,
102
111
  PageSize : limit,
103
- ...( areaCode && { AreaCode : areaCode } ),
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...( contains && { Contains : contains } ),
108
117
  ...( page && { Page : page } )
109
118
  }
@@ -163,6 +172,111 @@ const twilio = {
163
172
  }
164
173
  });
165
174
 
175
+ },
176
+
177
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
178
+ // number actually deliver. From the Tollfree Verification resource docs
179
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
180
+ // POST /v1/Tollfree/Verifications
181
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
182
+ // UseCaseCategories (array), UseCaseSummary,
183
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
184
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
185
+ // GET /v1/Tollfree/Verifications/{Sid}
186
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
187
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
188
+ // Basic auth, form-encoded, like everything above. There is no status
189
+ // callback on this resource — the caller polls the fetch.
190
+ //
191
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
192
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
193
+ // ISVs, support.twilio.com) — so businessName, website and email here are
194
+ // the merchant's, collected at purchase.
195
+ //
196
+ // The use-case fields are PLATFORM facts and live here beside the message
197
+ // composition for the same reason it does: how Drawbridge collects opt-in
198
+ // (its own hosted entry forms) and what its messages look like are
199
+ // properties of sending through Drawbridge, not of any one merchant.
200
+ submitVerification : async ({
201
+ accountSid,
202
+ authToken,
203
+ businessName,
204
+ email,
205
+ numberSid,
206
+ optInImage,
207
+ request: request$1 = request,
208
+ volume,
209
+ website
210
+ }) => {
211
+
212
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website })
213
+ .filter( ( [ , value ] ) => ! value )
214
+ .map( ( [ name ] ) => name );
215
+
216
+ if( missing.length ) throw new Error( 'Toll-free verification submission missing — ' + missing.join( ', ' ) );
217
+
218
+ // URLSearchParams rather than an object: UseCaseCategories and
219
+ // OptInImageUrls are array parameters, which Twilio takes as REPEATED
220
+ // form keys — an object body would comma-join them into one value.
221
+ const body = new URLSearchParams();
222
+
223
+ body.append( 'BusinessName', businessName );
224
+ body.append( 'BusinessWebsite', website );
225
+ body.append( 'NotificationEmail', email );
226
+ body.append( 'UseCaseCategories', 'MARKETING' );
227
+ body.append( 'UseCaseSummary', 'Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business\'s Drawbridge-hosted entry form.' );
228
+ // Mirrors the sms() composition above — newline-separated, brand first,
229
+ // opt-out line appended — so the reviewed sample is the sent shape.
230
+ body.append( 'ProductionMessageSample', businessName + '\nThanks for entering! We\'ll text you here about your entry.\nReply STOP to opt out' );
231
+ body.append( 'OptInImageUrls', optInImage );
232
+ // The entry form is a web form; the phone field is beside the consent
233
+ // language on the merchant's campaign page.
234
+ body.append( 'OptInType', 'WEB_FORM' );
235
+ body.append( 'MessageVolume', volume || '1,000' );
236
+ body.append( 'TollfreePhoneNumberSid', numberSid );
237
+
238
+ const result = await request$1({
239
+ method : 'POST',
240
+ type : 'form',
241
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications',
242
+ headers : {
243
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
244
+ },
245
+ body
246
+ });
247
+
248
+ return {
249
+ sid : result?.sid,
250
+ status : result?.status
251
+ };
252
+
253
+ },
254
+
255
+ fetchVerification : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
256
+
257
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
258
+ if( ! sid ) throw new Error( 'Twilio verification fetch needs the verification sid' );
259
+
260
+ const result = await request$1({
261
+ method : 'GET',
262
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications/' + sid,
263
+ headers : {
264
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
265
+ }
266
+ });
267
+
268
+ // The caller-facing shape is ours; the vendor's vocabulary stays here.
269
+ return {
270
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
271
+ // review states → pending.
272
+ outcome : result?.status === 'TWILIO_APPROVED'
273
+ ? 'approved'
274
+ : result?.status === 'TWILIO_REJECTED'
275
+ ? 'rejected'
276
+ : 'pending',
277
+ reason : result?.rejection_reason || null
278
+ };
279
+
166
280
  }
167
281
 
168
282
  };
package/dist/twilio.js CHANGED
@@ -82,28 +82,37 @@ var twilio = {
82
82
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
83
83
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
84
84
  // IncomingPhoneNumbers resource docs:
85
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
86
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
85
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
86
+ // query: SmsEnabled, Contains, PageSize (1-1000)
87
87
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
88
88
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
89
89
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
90
90
  // Basic auth throughout, like Messages.
91
- searchNumbers: async ({ accountSid, areaCode, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
91
+ //
92
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
93
+ // than an inventory one. A US local number may not send A2P SMS without full
94
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
95
+ // Toll-Free Verification — one API submission per number (see
96
+ // submitVerification below), no TCR brand, no campaign fees. Until a
97
+ // verification is approved, Twilio blocks the number's US-bound messages
98
+ // outright — which is why a purchased number starts `pending` and the send
99
+ // path refuses anything but `verified`.
100
+ searchNumbers: async ({ accountSid, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
92
101
  if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
93
102
  const page = Number(cursor) || 0;
94
103
  const result = await request2({
95
104
  method: "GET",
96
- url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/Local.json",
105
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/TollFree.json",
97
106
  headers: {
98
107
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
99
108
  },
100
109
  query: {
101
110
  SmsEnabled: true,
102
111
  PageSize: limit,
103
- ...areaCode && { AreaCode: areaCode },
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...contains && { Contains: contains },
108
117
  ...page && { Page: page }
109
118
  }
@@ -150,6 +159,84 @@ var twilio = {
150
159
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
151
160
  }
152
161
  });
162
+ },
163
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
164
+ // number actually deliver. From the Tollfree Verification resource docs
165
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
166
+ // POST /v1/Tollfree/Verifications
167
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
168
+ // UseCaseCategories (array), UseCaseSummary,
169
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
170
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
171
+ // GET /v1/Tollfree/Verifications/{Sid}
172
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
173
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
174
+ // Basic auth, form-encoded, like everything above. There is no status
175
+ // callback on this resource — the caller polls the fetch.
176
+ //
177
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
178
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
179
+ // ISVs, support.twilio.com) — so businessName, website and email here are
180
+ // the merchant's, collected at purchase.
181
+ //
182
+ // The use-case fields are PLATFORM facts and live here beside the message
183
+ // composition for the same reason it does: how Drawbridge collects opt-in
184
+ // (its own hosted entry forms) and what its messages look like are
185
+ // properties of sending through Drawbridge, not of any one merchant.
186
+ submitVerification: async ({
187
+ accountSid,
188
+ authToken,
189
+ businessName,
190
+ email,
191
+ numberSid,
192
+ optInImage,
193
+ request: request2 = request,
194
+ volume,
195
+ website
196
+ }) => {
197
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website }).filter(([, value]) => !value).map(([name]) => name);
198
+ if (missing.length) throw new Error("Toll-free verification submission missing \u2014 " + missing.join(", "));
199
+ const body = new URLSearchParams();
200
+ body.append("BusinessName", businessName);
201
+ body.append("BusinessWebsite", website);
202
+ body.append("NotificationEmail", email);
203
+ body.append("UseCaseCategories", "MARKETING");
204
+ body.append("UseCaseSummary", "Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business's Drawbridge-hosted entry form.");
205
+ body.append("ProductionMessageSample", businessName + "\nThanks for entering! We'll text you here about your entry.\nReply STOP to opt out");
206
+ body.append("OptInImageUrls", optInImage);
207
+ body.append("OptInType", "WEB_FORM");
208
+ body.append("MessageVolume", volume || "1,000");
209
+ body.append("TollfreePhoneNumberSid", numberSid);
210
+ const result = await request2({
211
+ method: "POST",
212
+ type: "form",
213
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications",
214
+ headers: {
215
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
216
+ },
217
+ body
218
+ });
219
+ return {
220
+ sid: result == null ? void 0 : result.sid,
221
+ status: result == null ? void 0 : result.status
222
+ };
223
+ },
224
+ fetchVerification: async ({ accountSid, authToken, request: request2 = request, sid }) => {
225
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
226
+ if (!sid) throw new Error("Twilio verification fetch needs the verification sid");
227
+ const result = await request2({
228
+ method: "GET",
229
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications/" + sid,
230
+ headers: {
231
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
232
+ }
233
+ });
234
+ return {
235
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
236
+ // review states → pending.
237
+ outcome: (result == null ? void 0 : result.status) === "TWILIO_APPROVED" ? "approved" : (result == null ? void 0 : result.status) === "TWILIO_REJECTED" ? "rejected" : "pending",
238
+ reason: (result == null ? void 0 : result.rejection_reason) || null
239
+ };
153
240
  }
154
241
  };
155
242
  export {
package/package.json CHANGED
@@ -215,5 +215,5 @@
215
215
  "test": ". \"$HOME/.nvm/nvm.sh\" && nvm use && node --test"
216
216
  },
217
217
  "types": "dist/index.d.ts",
218
- "version": "0.0.132"
218
+ "version": "0.0.134"
219
219
  }