@chatarmin/os 1.5.9 → 1.8.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.
Files changed (69) hide show
  1. package/README.md +417 -8
  2. package/dist/billing/client.d.ts +8 -0
  3. package/dist/billing/client.d.ts.map +1 -0
  4. package/dist/billing/client.js +46 -0
  5. package/dist/billing/index.d.ts +3 -0
  6. package/dist/billing/index.d.ts.map +1 -0
  7. package/dist/billing/index.js +1 -0
  8. package/dist/billing/types.d.ts +201 -0
  9. package/dist/billing/types.d.ts.map +1 -0
  10. package/dist/billing/usage-breakdown-types.d.ts +132 -0
  11. package/dist/billing/usage-breakdown-types.d.ts.map +1 -0
  12. package/dist/companies/client.d.ts +1 -1
  13. package/dist/companies/client.d.ts.map +1 -1
  14. package/dist/features/client.d.ts +8 -0
  15. package/dist/features/client.d.ts.map +1 -0
  16. package/dist/features/client.js +18 -0
  17. package/dist/features/index.d.ts +3 -0
  18. package/dist/features/index.d.ts.map +1 -0
  19. package/dist/features/index.js +1 -0
  20. package/dist/features/types.d.ts +184 -0
  21. package/dist/features/types.d.ts.map +1 -0
  22. package/dist/index.d.ts +29 -760
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +8 -361
  25. package/dist/webhooks/constants.d.ts +5 -0
  26. package/dist/webhooks/constants.d.ts.map +1 -0
  27. package/dist/webhooks/constants.js +4 -0
  28. package/dist/webhooks/construct-event.d.ts +3 -0
  29. package/dist/webhooks/construct-event.d.ts.map +1 -0
  30. package/dist/webhooks/construct-event.js +64 -0
  31. package/dist/webhooks/errors.d.ts +4 -0
  32. package/dist/webhooks/errors.d.ts.map +1 -0
  33. package/dist/webhooks/errors.js +6 -0
  34. package/dist/webhooks/generate-test-headers.d.ts +3 -0
  35. package/dist/webhooks/generate-test-headers.d.ts.map +1 -0
  36. package/dist/webhooks/generate-test-headers.js +11 -0
  37. package/dist/webhooks/index.d.ts +33 -0
  38. package/dist/webhooks/index.d.ts.map +1 -0
  39. package/dist/webhooks/index.js +31 -0
  40. package/dist/webhooks/read-header.d.ts +3 -0
  41. package/dist/webhooks/read-header.d.ts.map +1 -0
  42. package/dist/webhooks/read-header.js +16 -0
  43. package/dist/webhooks/signing.d.ts +4 -0
  44. package/dist/webhooks/signing.d.ts.map +1 -0
  45. package/dist/webhooks/signing.js +16 -0
  46. package/dist/webhooks/types.d.ts +30 -0
  47. package/dist/webhooks/types.d.ts.map +1 -0
  48. package/package.json +7 -1
  49. package/dist/billing-get-dashboard-context.d.ts +0 -16
  50. package/dist/billing-get-dashboard-context.d.ts.map +0 -1
  51. package/dist/billing-get-dashboard-context.js +0 -7
  52. package/dist/chatarmin-os-billing-namespace.d.ts +0 -122
  53. package/dist/chatarmin-os-billing-namespace.d.ts.map +0 -1
  54. package/dist/chatarmin-os-billing-namespace.js +0 -24
  55. package/dist/features-list-for-product.d.ts +0 -26
  56. package/dist/features-list-for-product.d.ts.map +0 -1
  57. package/dist/features-list-for-product.js +0 -17
  58. package/dist/types/billing-dashboard-context.d.ts +0 -33
  59. package/dist/types/billing-dashboard-context.d.ts.map +0 -1
  60. package/dist/types/chatarmin-os-billing-inputs.d.ts +0 -90
  61. package/dist/types/chatarmin-os-billing-inputs.d.ts.map +0 -1
  62. package/dist/types/feature-access-list-item.d.ts +0 -7
  63. package/dist/types/feature-access-list-item.d.ts.map +0 -1
  64. package/dist/types/product-feature-catalog.d.ts +0 -24
  65. package/dist/types/product-feature-catalog.d.ts.map +0 -1
  66. /package/dist/{types/billing-dashboard-context.js → billing/types.js} +0 -0
  67. /package/dist/{types/chatarmin-os-billing-inputs.js → billing/usage-breakdown-types.js} +0 -0
  68. /package/dist/{types/feature-access-list-item.js → features/types.js} +0 -0
  69. /package/dist/{types/product-feature-catalog.js → webhooks/types.js} +0 -0
package/README.md CHANGED
@@ -30,6 +30,7 @@
30
30
  - [Subscriptions & Tiers](#subscriptions--tiers)
31
31
  - [Links](#links)
32
32
  - [Onboarding](#onboarding)
33
+ - [Product Webhooks](#product-webhooks)
33
34
  - [Common Patterns](#common-patterns)
34
35
  - [TypeScript Support](#typescript-support)
35
36
  - [Configuration](#configuration)
@@ -193,6 +194,8 @@ const [primary] = await os.contacts.list("comp_xxx", true)
193
194
 
194
195
  Check and manage feature access for companies.
195
196
 
197
+ **Identifiers:** pass **`featureCode`** on input (e.g. `"ai_credit"`). Responses include `featureCode` and `featureAccessId`. Codes are set at feature create and are not renamed via the API — change display with `featureName`, or create a new feature and migrate for a real rename. See [`product-billing-api.md`](../../docs/engineering/product-billing-api.md#feature-identifiers-featurecode) for details.
198
+
196
199
  #### `features.check(input)`
197
200
 
198
201
  Check if a company has access to a specific feature.
@@ -293,6 +296,8 @@ if (access.canUse) {
293
296
 
294
297
  Track usage, claim subscriptions, and manage billing.
295
298
 
299
+ > **API reference:** [`docs/engineering/product-billing-api.md`](../../docs/engineering/product-billing-api.md) — full request/response shapes for every `os.billing.*` method.
300
+
296
301
  #### `billing.trackUsage(input)`
297
302
 
298
303
  Track usage for a metered feature.
@@ -353,23 +358,396 @@ console.log({
353
358
  })
354
359
  ```
355
360
 
356
- #### `billing.getStatus(companyId)`
361
+ #### `billing.getStatus(companyIdOrInput)`
362
+
363
+ Quick Stripe subscription status for a company — `activeSubscriptions` (`active` / `trialing` / `past_due`), resolved billing profile, and enabled feature count. Scoped to the product link's billing profile when `externalOrgId` is provided.
357
364
 
358
- Get billing status overview for a company.
365
+ See [Product-scoped billing profile — `getStatus` example](../../docs/engineering/product-billing-api.md#example-getstatus-for-subscription-health) for the full walkthrough + sample response.
359
366
 
360
367
  ```typescript
361
- const status = await os.billing.getStatus("comp_xxx")
368
+ const status = await os.billing.getStatus('ee0f4a19-2b69-4969-8807-d0013f1670ee')
369
+
370
+ const statusByOrg = await os.billing.getStatus({ externalOrgId: 'org_kKZHiUHBSjdu' })
371
+
372
+ if (status.activeSubscriptions[0]?.status === 'past_due') {
373
+ // gate access for this external org
374
+ }
362
375
 
363
376
  console.log({
364
- hasBilling: status.hasBillingProfile,
365
- subscriptions: status.activeSubscriptions.length,
377
+ stripeCustomer: status.billingProfile?.stripeCustomerId,
378
+ subscriptionStatus: status.activeSubscriptions[0]?.status,
366
379
  features: status.enabledFeaturesCount,
367
380
  })
381
+ ```
382
+
383
+ #### `billing.getOverview(input)`
384
+
385
+ Combined billing page payload: company, billing profile, primary subscription, and recent invoices.
386
+
387
+ ```typescript
388
+ const overview = await os.billing.getOverview({
389
+ companyId: "comp_xxx",
390
+ invoiceLimit: 12,
391
+ includeUsage: true, // optional: embed usage rows
392
+ })
393
+
394
+ console.log({
395
+ plan: overview.subscription?.tier?.name,
396
+ status: overview.subscription?.status,
397
+ paymentMethod: overview.billingProfile?.primaryPaymentMethod,
398
+ invoices: overview.invoices.length,
399
+ usageRows: overview.usage?.length,
400
+ })
401
+ ```
402
+
403
+ #### `billing.getUsage(companyIdOrInput, options?)`
404
+
405
+ Metronome-style usage aggregates. Returns `{ data: UsageRecord[] }` — feature-first nesting with per-feature billing cycles.
406
+
407
+ **Company scope:** pass **`companyId`** as the first string argument, or pass an input object with **`companyId`** or **`externalOrgId`** (one required). Prefer `externalOrgId` when CX only knows the org slug — usage is scoped to that product link.
408
+
409
+ ```typescript
410
+ // CX-native (single product link)
411
+ const { data } = await os.billing.getUsage({
412
+ externalOrgId: 'org_FVCNOtEdpeeP',
413
+ windowSize: 'none',
414
+ })
415
+
416
+ // Or companyId as first arg
417
+ const byCompany = await os.billing.getUsage('comp_xxx', { windowSize: 'none' })
418
+ ```
419
+
420
+ - **`data[].featureCode`** — stable feature slug
421
+ - **`data[].cycles[]`** — billing cycles for this feature (`periodOffset`, bounds, `usage[]`)
422
+
423
+ `windowSize` controls whether `usage` points are period totals or time buckets. Filter with **`featureCode`** (stable slug), not `featureAccessId` — see [Feature identifiers](../../docs/engineering/product-billing-api.md#feature-identifiers-featurecode).
424
+
425
+ **`periodOffsetRange`** (time scope for whole billing cycles):
426
+
427
+ | Intent | `periodOffsetRange` |
428
+ |--------|---------------------|
429
+ | Current cycle (default) | omit or `{ from: 0, to: 0 }` |
430
+ | Previous only | `{ from: -1, to: -1 }` |
431
+ | Current + previous | `{ from: -1, to: 0 }` |
432
+ | 12-cycle picker | `{ from: -11, to: 0 }` |
433
+
434
+ Multi-cycle range requires `windowSize: 'none'` (max 12 cycles). Cannot combine with `startingOn`/`endingBefore`.
368
435
 
369
- // Check active tier
370
- if (status.activeSubscriptions[0]?.tier) {
371
- console.log(`Current tier: ${status.activeSubscriptions[0].tier.name}`)
436
+ Every `cycles[]` bucket includes `subscriptionItem` and `summary` when it has usage rows. `usage[]` stays thin (`value` + dates).
437
+
438
+ **Response shape:**
439
+
440
+ ```typescript
441
+ interface UsageQueryResult {
442
+ data: UsageRecord[]
443
+ }
444
+
445
+ interface UsageRecord {
446
+ featureCode: string
447
+ featureName: string
448
+ cycles: UsageCycleBucket[]
372
449
  }
450
+
451
+ interface UsageCycleBucket {
452
+ periodOffset: number
453
+ startingOn: Date
454
+ endingBefore: Date
455
+ label: string
456
+ subscriptionItem?: UsageSubscriptionItem // present when cycle has usage rows
457
+ summary?: UsageCycleSummary // totals + projection; present when cycle has usage rows
458
+ usage: UsagePoint[] // thin: value + dates (+ optional identity/grouping)
459
+ }
460
+
461
+ interface UsagePoint {
462
+ value: number
463
+ startingOn: Date
464
+ endingBefore: Date
465
+ featureAccessId?: string
466
+ meterCode?: string
467
+ groupKey?: string | null // with groupBy: 'productLink'
468
+ groupValue?: string | null
469
+ productLinkId?: string | null // groupBy: 'productLink', or bucketed multi-link companyId
470
+ productLinkLabel?: string | null
471
+ externalOrgId?: string | null
472
+ }
473
+ ```
474
+
475
+ Read billing from `cycle.summary` and `cycle.subscriptionItem` — not from `usage[]` points.
476
+
477
+ `productLinkId` (+ label, `externalOrgId`) appears on `usage[]` points when `groupBy: 'productLink'`, or on bucketed `companyId` queries when multiple product links share the company (without `groupKey`).
478
+
479
+ | `windowSize` | `cycles[].usage` | Billing context |
480
+ | ------------ | ---------------- | --------------- |
481
+ | `'none'` | 1 point | `subscriptionItem` + `summary` (always) |
482
+ | `'day'` / `'week'` / `'month'` | N thin points | Same — billing once per cycle, not per bucket |
483
+
484
+ #### Examples by `windowSize`
485
+
486
+ **`windowSize: 'none'`** — period totals:
487
+
488
+ ```json
489
+ {
490
+ "data": [{
491
+ "featureCode": "messages",
492
+ "featureName": "Messages",
493
+ "cycles": [{
494
+ "periodOffset": 0,
495
+ "startingOn": "2026-07-01T00:00:00.000Z",
496
+ "endingBefore": "2026-08-01T00:00:00.000Z",
497
+ "label": "Jul 1, 2026 – Aug 1, 2026",
498
+ "subscriptionItem": {
499
+ "includedQuantity": 100,
500
+ "unitAmountCents": 500,
501
+ "currency": "eur"
502
+ },
503
+ "summary": {
504
+ "totalUsage": 120,
505
+ "billableQuantity": 20,
506
+ "estimatedOverageCents": 10000,
507
+ "projectedBillableQuantity": 240,
508
+ "projectedOverageCents": 70000
509
+ },
510
+ "usage": [{
511
+ "value": 120,
512
+ "startingOn": "2026-07-01T00:00:00.000Z",
513
+ "endingBefore": "2026-08-01T00:00:00.000Z"
514
+ }]
515
+ }]
516
+ }]
517
+ }
518
+ ```
519
+
520
+ **`windowSize: 'day'`** — daily chart buckets:
521
+
522
+ ```json
523
+ {
524
+ "data": [{
525
+ "featureCode": "messages",
526
+ "featureName": "Messages",
527
+ "cycles": [{
528
+ "periodOffset": 0,
529
+ "subscriptionItem": { "includedQuantity": 100 },
530
+ "summary": { "totalUsage": 20, "billableQuantity": 0 },
531
+ "usage": [
532
+ { "value": 8, "startingOn": "2026-07-01T00:00:00.000Z", "endingBefore": "2026-07-02T00:00:00.000Z" },
533
+ { "value": 12, "startingOn": "2026-07-02T00:00:00.000Z", "endingBefore": "2026-07-03T00:00:00.000Z" }
534
+ ]
535
+ }]
536
+ }]
537
+ }
538
+ ```
539
+
540
+ **Multi-cycle** (`periodOffsetRange: { from: -1, to: 0 }`):
541
+
542
+ ```json
543
+ {
544
+ "data": [{
545
+ "featureCode": "messages",
546
+ "featureName": "Messages",
547
+ "cycles": [
548
+ { "periodOffset": 0, "label": "Jul 1 – Aug 1", "summary": { "totalUsage": 120, "billableQuantity": 20 }, "usage": [{ "value": 120 }] },
549
+ { "periodOffset": -1, "label": "Jun 1 – Jul 1", "summary": { "totalUsage": 98, "billableQuantity": 0 }, "usage": [{ "value": 98 }] }
550
+ ]
551
+ }]
552
+ }
553
+ ```
554
+
555
+ Full examples: [`docs/engineering/product-billing-api.md#billinggetusage`](../../docs/engineering/product-billing-api.md#billinggetusage)
556
+
557
+ ```typescript
558
+ const { data } = await os.billing.getUsage('comp_xxx', { windowSize: 'none' })
559
+ const messages = data.find((r) => r.featureCode === 'messages')
560
+ const cycle = messages?.cycles[0]
561
+ const currentTotal = cycle?.usage[0]?.value
562
+ const overage = cycle?.summary?.billableQuantity
563
+ const cost = cycle?.summary?.estimatedOverageCents
564
+
565
+ const daily = await os.billing.getUsage('comp_xxx', {
566
+ windowSize: 'day',
567
+ featureCode: 'messages',
568
+ })
569
+ const dailySeries = (daily.data[0]?.cycles[0]?.usage ?? []).map((point) => ({
570
+ day: point.startingOn.toISOString().slice(0, 10),
571
+ usage: point.value,
572
+ }))
573
+
574
+ // Previous billing period totals
575
+ const lastMonth = await os.billing.getUsage('comp_xxx', {
576
+ windowSize: 'none',
577
+ periodOffsetRange: { from: -1, to: -1 },
578
+ })
579
+
580
+ // Last 12 billing cycles (cycle picker)
581
+ const history = await os.billing.getUsage('comp_xxx', {
582
+ windowSize: 'none',
583
+ periodOffsetRange: { from: -11, to: 0 },
584
+ })
585
+ // history.data[0].cycles → one bucket per period; cycle.usage[0] → period total
586
+
587
+ // Custom sub-range within the current billing period (both dates required)
588
+ // usage[] points cover the sub-range; summary totals match it; projections use the full cycle
589
+ const week = await os.billing.getUsage('comp_xxx', {
590
+ windowSize: 'day',
591
+ featureCode: 'messages',
592
+ startingOn: new Date('2026-07-10T00:00:00Z'),
593
+ endingBefore: new Date('2026-07-17T00:00:00Z'),
594
+ })
595
+ ```
596
+
597
+ | `windowSize` | Shape |
598
+ | ------------ | ----- |
599
+ | `'none'` | One point per feature (collapsed total when multiple product links share a company; use `groupBy: 'productLink'` for per-link rows) |
600
+ | `'day'` / `'week'` / `'month'` | One point per feature per bucket; one per link per bucket when multiple product links share the company (`productLinkId` on each point) |
601
+
602
+ | Time scope | When to use |
603
+ | ---------- | ----------- |
604
+ | omit | Current billing period (default) |
605
+ | `periodOffsetRange: { from: -1, to: -1 }` | Whole previous billing period |
606
+ | `periodOffsetRange: { from: -11, to: 0 }` | Last 12 billing cycles |
607
+ | `startingOn` + `endingBefore` | Sub-range inside the **current** period (clamped; totals follow sub-range; projections use full cycle) |
608
+
609
+ #### `billing.exportUsage(companyIdOrInput, options?)`
610
+
611
+ Export usage aggregates as CSV. Accepts the same company scope and query options as `getUsage` (plus implicit `format: 'csv'`). CSV includes a `periodOffset` column (first column) for multi-cycle exports. Filename uses exported period bounds: `usage_{windowSize}_{companyId}_{periodStart}_{periodEnd}.csv`.
612
+
613
+ ```typescript
614
+ const csv = await os.billing.exportUsage({
615
+ externalOrgId: 'org_FVCNOtEdpeeP',
616
+ windowSize: 'none',
617
+ periodOffsetRange: { from: -11, to: 0 },
618
+ })
619
+ // csv.filename → "usage_none_<companyId>_2026-06-01_2026-08-01.csv"
620
+ // csv.content → first line:
621
+ // periodOffset,featureCode,featureName,value,startingOn,endingBefore,groupKey,groupValue,productLinkId,productLinkLabel,externalOrgId,includedQuantity,billableQuantity,quantityLimit,remaining,percentUsed,unitAmountCents,currency,estimatedOverageCents,projectedBillableQuantity,projectedOverageCents
622
+ ```
623
+
624
+ #### `billing.listInvoices(companyIdOrInput, options?)`
625
+
626
+ List invoices for a company (excludes draft/void).
627
+
628
+ ```typescript
629
+ const { invoices } = await os.billing.listInvoices("comp_xxx", { limit: 24 })
630
+ ```
631
+
632
+ #### `billing.getPaymentMethod(companyIdOrInput)`
633
+
634
+ Payment method summary for the resolved billing profile.
635
+
636
+ ```typescript
637
+ const { billingProfile } = await os.billing.getPaymentMethod("comp_xxx")
638
+ ```
639
+
640
+ #### `billing.createPortalSession(input)`
641
+
642
+ Open Stripe Customer Portal for manage-billing flows. `returnUrl` must be on an allowed product host (`products.metadata.billing.returnUrlHosts`).
643
+
644
+ ```typescript
645
+ const { url } = await os.billing.createPortalSession({
646
+ companyId: "comp_xxx",
647
+ returnUrl: "https://app.example.com/settings/billing",
648
+ })
649
+
650
+ // Redirect the user to url
651
+ ```
652
+
653
+ #### `billing.resolveInvoicePaymentUrl(input)`
654
+
655
+ Resolve a hosted invoice payment URL for a specific invoice.
656
+
657
+ ```typescript
658
+ const { hostedInvoiceUrl } = await os.billing.resolveInvoicePaymentUrl({
659
+ companyId: "comp_xxx",
660
+ invoiceId: "inv_xxx",
661
+ })
662
+ ```
663
+
664
+ #### `billing.listPastDuePaymentUrls(companyIdOrInput)`
665
+
666
+ List overdue invoices with payment URLs.
667
+
668
+ ```typescript
669
+ const { items } = await os.billing.listPastDuePaymentUrls("comp_xxx")
670
+ ```
671
+
672
+ #### `billing.sendPastDueInvoice(input)`
673
+
674
+ Email a past-due invoice via Stripe (when collection method is `send_invoice`).
675
+
676
+ ```typescript
677
+ await os.billing.sendPastDueInvoice({
678
+ companyId: "comp_xxx",
679
+ invoiceId: "inv_xxx",
680
+ })
681
+ ```
682
+
683
+ #### `billing.syncCompanyFromStripe(input)`
684
+
685
+ Pull billing profile, subscriptions, and feature links from Stripe.
686
+
687
+ ```typescript
688
+ const result = await os.billing.syncCompanyFromStripe({
689
+ companyId: "comp_xxx",
690
+ stripeCustomerId: "cus_xxx",
691
+ })
692
+
693
+ console.log({
694
+ subscriptionsSynced: result.subscriptionsSynced,
695
+ featuresLinked: result.featuresLinked,
696
+ })
697
+ ```
698
+
699
+ #### `billing.relinkBillingProfile(input)`
700
+
701
+ Replace the company's billing profile with a new Stripe customer (destructive).
702
+
703
+ ```typescript
704
+ await os.billing.relinkBillingProfile({
705
+ companyId: "comp_xxx",
706
+ stripeCustomerId: "cus_new_xxx",
707
+ })
708
+ ```
709
+
710
+ #### `billing.ensureFeatureSubscription(input)`
711
+
712
+ Ensure a metered feature is linked to a Stripe subscription item.
713
+
714
+ ```typescript
715
+ const result = await os.billing.ensureFeatureSubscription({
716
+ companyId: "comp_xxx",
717
+ featureCode: "ai_credit",
718
+ })
719
+
720
+ console.log(result.action) // 'already_linked' | 'linked_existing' | 'created_new' | 'failed'
721
+ ```
722
+
723
+ #### `billing.setUsage(input)`
724
+
725
+ Set absolute usage (corrections / admin). Requires a `reason` for audit.
726
+
727
+ ```typescript
728
+ const result = await os.billing.setUsage({
729
+ companyId: "comp_xxx",
730
+ featureCode: "messages",
731
+ absoluteValue: 420,
732
+ reason: "Reconcile after import",
733
+ })
734
+
735
+ console.log({
736
+ previous: result.previousUsage,
737
+ current: result.currentUsage,
738
+ delta: result.delta,
739
+ })
740
+ ```
741
+
742
+ #### `billing.getUsageHistory(companyId, featureCode, options?)`
743
+
744
+ Raw usage events for audit/debug (not aggregated rollups).
745
+
746
+ ```typescript
747
+ const events = await os.billing.getUsageHistory("comp_xxx", "messages", {
748
+ limit: 50,
749
+ eventType: "increment",
750
+ })
373
751
  ```
374
752
 
375
753
  ---
@@ -598,6 +976,36 @@ switch (result.linkStatus) {
598
976
 
599
977
  ---
600
978
 
979
+ ## Product Webhooks
980
+
981
+ When ChatarminOS pushes company and product-link changes to your app, verify the request with the SDK before handling the payload.
982
+
983
+ ```typescript
984
+ import { webhooks } from "@chatarmin/os/webhooks"
985
+
986
+ export async function POST(request: Request) {
987
+ const rawBody = await request.text()
988
+
989
+ const event = webhooks.constructEvent(
990
+ rawBody,
991
+ request.headers,
992
+ process.env.OS_WEBHOOK_SIGNING_SECRET!,
993
+ )
994
+
995
+ switch (`${event.type}.${event.action}`) {
996
+ case "company.updated":
997
+ // event.data, event.webhookId, event.idempotency_key
998
+ break
999
+ }
1000
+
1001
+ return Response.json({ received: true })
1002
+ }
1003
+ ```
1004
+
1005
+ Use `ChatarminOS.webhooks.constructEvent` if you already import the main client. Dedupe on `event.webhookId` or `event.idempotency_key`.
1006
+
1007
+ ---
1008
+
601
1009
  ## Common Patterns
602
1010
 
603
1011
  ### Feature Gating
@@ -782,6 +1190,7 @@ try {
782
1190
 
783
1191
  ## Related Documentation
784
1192
 
1193
+ - **[Product billing API](../../docs/engineering/product-billing-api.md)** — Billing read APIs, `externalOrgId` scoping, and preview test tenants
785
1194
  - **[QUICKSTART.md](./QUICKSTART.md)** — Quick publishing & installation guide
786
1195
  - **[PUBLISHING.md](./PUBLISHING.md)** — Detailed publishing instructions
787
1196
  - **[test-sdk.ts](./test-sdk.ts)** — Complete usage examples
@@ -0,0 +1,8 @@
1
+ import type { AppRouter } from '@chatarmin/api/trpc-types';
2
+ import type { createTRPCClient } from '@trpc/client';
3
+ import type { BillingClient } from './types';
4
+ type SdkClient = ReturnType<typeof createTRPCClient<AppRouter>>;
5
+ type SdkCall = <T>(operation: string, action: () => Promise<T>) => Promise<T>;
6
+ export declare function createBillingClient(client: SdkClient, call: SdkCall): BillingClient;
7
+ export {};
8
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/billing/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,KAAK,EACV,aAAa,EAyBd,MAAM,SAAS,CAAA;AAsChB,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAA;AAC/D,KAAK,OAAO,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAE7E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG,aAAa,CAiHnF"}
@@ -0,0 +1,46 @@
1
+ function mergeBillingInput(companyIdOrInput, maybeOptions) {
2
+ if (typeof companyIdOrInput === 'string') {
3
+ return { companyId: companyIdOrInput, ...(maybeOptions ?? {}) };
4
+ }
5
+ return { ...companyIdOrInput, ...(maybeOptions ?? {}) };
6
+ }
7
+ function resolveBillingCompanyScopeInput(companyIdOrInput, maybeOptions) {
8
+ return mergeBillingInput(companyIdOrInput, maybeOptions);
9
+ }
10
+ function resolveBillingUsageInput(companyIdOrInput, maybeOptions) {
11
+ return mergeBillingInput(companyIdOrInput, maybeOptions);
12
+ }
13
+ function resolveBillingExportInput(companyIdOrInput, maybeOptions) {
14
+ return {
15
+ ...mergeBillingInput(companyIdOrInput, maybeOptions),
16
+ format: 'csv',
17
+ };
18
+ }
19
+ export function createBillingClient(client, call) {
20
+ return {
21
+ setUsage: (input) => call('billing.setUsage', () => client.billing.setUsage.mutate({
22
+ ...input,
23
+ source: 'api',
24
+ })),
25
+ getUsageHistory: (companyId, featureCode, options) => call('billing.getUsageHistory', () => client.billing.getUsageHistory.query({
26
+ companyId,
27
+ featureCode,
28
+ ...options,
29
+ })),
30
+ claimCheckout: (input) => call('billing.claimCheckout', () => client.billing.claimCheckout.mutate(input)),
31
+ linkSubscription: (input) => call('billing.linkSubscription', () => client.billing.linkSubscription.mutate(input)),
32
+ getStatus: (companyIdOrInput) => call('billing.getStatus', () => client.billing.getStatus.query(resolveBillingCompanyScopeInput(companyIdOrInput))),
33
+ getOverview: (input) => call('billing.getOverview', () => client.billing.getOverview.query(input)),
34
+ getUsage: (companyIdOrInput, maybeOptions) => call('billing.getUsage', () => client.billing.getUsage.query(resolveBillingUsageInput(companyIdOrInput, maybeOptions))),
35
+ exportUsage: (companyIdOrInput, maybeOptions) => call('billing.exportUsage', () => client.billing.exportUsage.query(resolveBillingExportInput(companyIdOrInput, maybeOptions))),
36
+ listInvoices: (companyIdOrInput, options) => call('billing.listInvoices', () => client.billing.listInvoices.query(resolveBillingCompanyScopeInput(companyIdOrInput, options))),
37
+ getPaymentMethod: (companyIdOrInput) => call('billing.getPaymentMethod', () => client.billing.getPaymentMethod.query(resolveBillingCompanyScopeInput(companyIdOrInput))),
38
+ createPortalSession: (input) => call('billing.createPortalSession', () => client.billing.createPortalSession.mutate(input)),
39
+ resolveInvoicePaymentUrl: (input) => call('billing.resolveProductInvoicePaymentUrl', () => client.billing.resolveProductInvoicePaymentUrl.mutate(input)),
40
+ listPastDuePaymentUrls: (companyIdOrInput) => call('billing.listProductPastDuePaymentUrls', () => client.billing.listProductPastDuePaymentUrls.query(resolveBillingCompanyScopeInput(companyIdOrInput))),
41
+ sendPastDueInvoice: (input) => call('billing.sendProductPastDueInvoice', () => client.billing.sendProductPastDueInvoice.mutate(input)),
42
+ syncCompanyFromStripe: (input) => call('billing.syncCompanyFromStripe', () => client.billing.syncCompanyFromStripe.mutate(input)),
43
+ relinkBillingProfile: (input) => call('billing.relinkBillingProfile', () => client.billing.relinkBillingProfile.mutate(input)),
44
+ ensureFeatureSubscription: (input) => call('billing.ensureFeatureSubscription', () => client.billing.ensureFeatureSubscription.mutate(input)),
45
+ };
46
+ }
@@ -0,0 +1,3 @@
1
+ export { createBillingClient } from './client';
2
+ export type { BillingClient, BillingInvoiceSummary, BillingOverview, BillingProfileSummary, BillingStatus, BillingStatusSubscription, BillingSubscriptionSummary, BillingUsageItem, ClaimCheckoutInput, ClaimCheckoutResult, CreateBillingPortalSessionInput, EnsureFeatureSubscriptionInput, EnsureFeatureSubscriptionResult, ExportUsageInput, ExportUsageResult, GetBillingOverviewInput, GetUsageInput, LinkSubscriptionInput, LinkSubscriptionResult, ListBillingInvoicesOptions, ProductBillingCompanyScopeInput, PeriodOffsetRange, RelinkBillingProfileInput, ResolveInvoicePaymentUrlInput, SendPastDueInvoiceInput, SendPastDueInvoiceResult, SetUsageInput, SyncCompanyFromStripeInput, SyncCompanyFromStripeResult, UsageAggregate, UsageCycleBucket, UsageCycleSummary, UsageGroupBy, UsagePoint, UsageQueryResult, UsageRecord, UsageSubscriptionItem, UsageResult, UsageWindowSize, } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/billing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAC9C,YAAY,EACV,aAAa,EACb,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,yBAAyB,EACzB,0BAA0B,EAC1B,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,+BAA+B,EAC/B,8BAA8B,EAC9B,+BAA+B,EAC/B,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,iBAAiB,EACjB,yBAAyB,EACzB,6BAA6B,EAC7B,uBAAuB,EACvB,wBAAwB,EACxB,aAAa,EACb,0BAA0B,EAC1B,2BAA2B,EAC3B,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,WAAW,EACX,eAAe,GAChB,MAAM,SAAS,CAAA"}
@@ -0,0 +1 @@
1
+ export { createBillingClient } from './client';