@chatarmin/os 0.1.10 → 0.3.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/dist/index.js CHANGED
@@ -1,35 +1,67 @@
1
1
  import { createTRPCClient, httpBatchLink } from "@trpc/client";
2
2
  import superjson from "superjson";
3
+ // ============================================================================
4
+ // SDK Client
5
+ // ============================================================================
3
6
  /**
4
7
  * ChatarminOS SDK Client
5
8
  *
6
- * Type-safe client for interacting with ChatarminOS from your product.
9
+ * Type-safe client for interacting with ChatarminOS from your B2B SaaS product.
10
+ * Use this SDK to manage companies, track feature usage, and handle billing.
7
11
  *
8
- * @example
12
+ * @example Basic Setup
9
13
  * ```typescript
10
14
  * import { ChatarminOS } from '@chatarmin/os-sdk'
11
15
  *
12
- * const os = new ChatarminOS({ apiKey: process.env.OS_API_KEY! })
16
+ * const os = new ChatarminOS({
17
+ * apiKey: process.env.OS_API_KEY!
18
+ * })
19
+ * ```
20
+ *
21
+ * @example Complete Onboarding Flow
22
+ * ```typescript
23
+ * // When a new user signs up in your product:
24
+ * const result = await os.onboard({
25
+ * externalOrgId: 'org_abc123',
26
+ * hints: {
27
+ * companyName: 'Acme Inc',
28
+ * domain: 'acme.com'
29
+ * },
30
+ * tierCode: 'free' // Apply free tier features
31
+ * })
13
32
  *
14
- * // Resolve company by your org ID
15
- * const company = await os.companies.getByProductLink({ externalOrgId: 'org_123' })
33
+ * console.log(result.companyId) // Use this for all future API calls
34
+ * ```
16
35
  *
17
- * // Track usage
36
+ * @example Track Usage
37
+ * ```typescript
18
38
  * await os.billing.trackUsage({
19
39
  * companyId: company.id,
20
40
  * featureCode: 'ai_credit',
21
41
  * quantity: 1
22
42
  * })
23
- *
24
- * // Check feature access
25
- * const access = await os.features.check({
26
- * companyId: company.id,
27
- * featureCode: 'whatsapp_messages'
28
- * })
29
43
  * ```
44
+ *
45
+ * @see https://os.chatarmin.com/docs for full documentation
30
46
  */
31
47
  export class ChatarminOS {
32
48
  client;
49
+ /**
50
+ * Create a new ChatarminOS SDK client.
51
+ *
52
+ * @param config - Configuration options
53
+ * @param config.apiKey - Your product's API key from OS admin
54
+ * @param config.baseUrl - Custom API base URL (optional)
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const os = new ChatarminOS({
59
+ * apiKey: process.env.OS_API_KEY!,
60
+ * // Optional: custom base URL for development
61
+ * baseUrl: 'http://localhost:3001/api/v1'
62
+ * })
63
+ * ```
64
+ */
33
65
  constructor(config) {
34
66
  const baseUrl = config.baseUrl || "https://os.chatarmin.com/api/v1";
35
67
  this.client = createTRPCClient({
@@ -44,104 +76,460 @@ export class ChatarminOS {
44
76
  ],
45
77
  });
46
78
  }
79
+ // ==========================================================================
80
+ // Companies
81
+ // ==========================================================================
47
82
  /**
48
- * Company operations
83
+ * Company management operations.
84
+ *
85
+ * Companies represent your customers in ChatarminOS. Each company can be
86
+ * linked to your product via an external organization ID.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * // Look up a company by your org ID
91
+ * const company = await os.companies.getByProductLink({
92
+ * externalOrgId: 'org_abc123'
93
+ * })
94
+ *
95
+ * // Create a new company
96
+ * const newCompany = await os.companies.create({
97
+ * name: 'Acme Inc',
98
+ * domain: 'acme.com',
99
+ * externalOrgId: 'org_abc123',
100
+ * contactEmail: 'admin@acme.com'
101
+ * })
102
+ * ```
49
103
  */
50
104
  get companies() {
105
+ const client = this.client;
51
106
  return {
52
107
  /**
53
- * Get company by their external org ID in your product
108
+ * Look up a company by their external organization ID in your product.
109
+ *
110
+ * Use this to resolve your product's org ID to a ChatarminOS company.
111
+ * Returns `null` if no company is linked with this external ID.
112
+ *
113
+ * @param input - The lookup parameters
114
+ * @param input.externalOrgId - Your product's organization ID
115
+ * @returns The company if found, or `null`
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * const company = await os.companies.getByProductLink({
120
+ * externalOrgId: 'org_abc123'
121
+ * })
122
+ *
123
+ * if (company) {
124
+ * console.log(`Found: ${company.name}`)
125
+ * } else {
126
+ * console.log('Company not linked yet')
127
+ * }
128
+ * ```
54
129
  */
55
- getByProductLink: (input) => this.client.companies.getByProductLink.query(input),
130
+ getByProductLink: (input) => client.companies.getByProductLink.query(input),
56
131
  /**
57
- * Create a new company and link to your product
132
+ * Create a new company and link it to your product.
133
+ *
134
+ * This creates a company in ChatarminOS and automatically links it
135
+ * to your product using the provided external organization ID.
136
+ *
137
+ * @param input - Company creation parameters
138
+ * @returns The created company with ID and short ID
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const company = await os.companies.create({
143
+ * name: 'Acme Inc',
144
+ * domain: 'acme.com',
145
+ * externalOrgId: 'org_abc123',
146
+ * contactEmail: 'admin@acme.com',
147
+ * contactName: 'John Doe'
148
+ * })
149
+ *
150
+ * console.log(company.id) // UUID
151
+ * console.log(company.shortId) // e.g., "acme-inc"
152
+ * ```
58
153
  */
59
- create: (input) => this.client.companies.createWithLink.mutate(input),
154
+ create: (input) => client.companies.createWithLink.mutate(input),
60
155
  /**
61
- * Smart link - find and link company using hints
156
+ * Smart link - intelligently find and link a company using hints.
157
+ *
158
+ * This method attempts to find an existing company in ChatarminOS
159
+ * that matches the provided hints (domain, name, emails). If found
160
+ * with sufficient confidence, it automatically creates a link.
161
+ *
162
+ * **Matching priority:**
163
+ * 1. Domain match (95% confidence)
164
+ * 2. Name match (up to 80% confidence)
165
+ * 3. Email domain match (70% confidence)
166
+ *
167
+ * @param input - Smart link parameters
168
+ * @returns Result with status and company info if linked
62
169
  *
63
- * @experimental
170
+ * @example
171
+ * ```typescript
172
+ * const result = await os.companies.smartLink({
173
+ * externalOrgId: 'org_abc123',
174
+ * hints: {
175
+ * companyName: 'Acme Inc',
176
+ * domain: 'acme.com',
177
+ * emails: ['john@acme.com']
178
+ * },
179
+ * minConfidence: 0.7
180
+ * })
181
+ *
182
+ * if (result.status === 'linked') {
183
+ * console.log(`Linked to ${result.customerName}`)
184
+ * } else if (result.status === 'suggestions') {
185
+ * console.log('Possible matches:', result.suggestions)
186
+ * }
187
+ * ```
188
+ *
189
+ * @experimental This API may change in future versions
64
190
  */
65
- smartLink: (input) => this.client.companies.smartLink.mutate(input),
191
+ smartLink: (input) => client.companies.smartLink.mutate(input),
66
192
  };
67
193
  }
194
+ // ==========================================================================
195
+ // Features
196
+ // ==========================================================================
68
197
  /**
69
- * Feature access operations
198
+ * Feature access and entitlement operations.
199
+ *
200
+ * Check what features a company has access to and their current usage.
201
+ * Features are defined in ChatarminOS and assigned via subscription tiers.
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * // Check if company can use a feature
206
+ * const access = await os.features.check({
207
+ * companyId: company.id,
208
+ * featureCode: 'ai_credit'
209
+ * })
210
+ *
211
+ * if (access.canUse) {
212
+ * // Feature is available
213
+ * console.log(`Remaining: ${access.remaining}`)
214
+ * } else {
215
+ * // Feature is disabled or limit reached
216
+ * console.log('Upgrade required')
217
+ * }
218
+ * ```
70
219
  */
71
220
  get features() {
72
221
  const client = this.client;
73
222
  return {
74
223
  /**
75
- * Check if a company has access to a feature
76
- * Returns detailed access info including usage and limits
224
+ * Check if a company has access to a specific feature.
225
+ *
226
+ * Returns detailed access information including whether the feature
227
+ * is enabled, current usage, limits, and whether they can use more.
228
+ *
229
+ * @param input - Check parameters
230
+ * @param input.companyId - Company ID to check
231
+ * @param input.featureCode - Feature code (e.g., "ai_credit")
232
+ * @returns Detailed feature access information
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * const access = await os.features.check({
237
+ * companyId: 'comp_xxx',
238
+ * featureCode: 'ai_credit'
239
+ * })
240
+ *
241
+ * console.log({
242
+ * enabled: access.isEnabled,
243
+ * canUse: access.canUse,
244
+ * usage: access.currentUsage,
245
+ * limit: access.quantityLimit,
246
+ * remaining: access.remaining,
247
+ * included: access.includedQuantity
248
+ * })
249
+ * ```
77
250
  */
78
251
  check: (input) => client.features.check.query(input),
79
252
  /**
80
- * List all features and their access status for a company
81
- * Useful for showing a company's full feature set
253
+ * List all features and their access status for a company.
254
+ *
255
+ * Returns a complete list of features with their current status,
256
+ * useful for displaying a company's full feature set or building
257
+ * a feature comparison view.
258
+ *
259
+ * @param companyId - Company ID to list features for
260
+ * @returns Array of feature access records
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const features = await os.features.listAccess(company.id)
265
+ *
266
+ * for (const f of features) {
267
+ * console.log(`${f.featureName}: ${f.isEnabled ? '✓' : '✗'}`)
268
+ * if (f.quantityLimit) {
269
+ * console.log(` Usage: ${f.currentUsage} / ${f.quantityLimit}`)
270
+ * }
271
+ * }
272
+ * ```
82
273
  */
83
274
  listAccess: (companyId) => client.features.getAccess.query({ companyId }),
84
275
  };
85
276
  }
277
+ // ==========================================================================
278
+ // Billing
279
+ // ==========================================================================
86
280
  /**
87
- * Billing and usage tracking
281
+ * Billing, usage tracking, and subscription management.
282
+ *
283
+ * Track feature usage for billing, claim subscriptions from Stripe checkout,
284
+ * or link existing Stripe subscriptions to companies.
285
+ *
286
+ * @example Track Usage
287
+ * ```typescript
288
+ * await os.billing.trackUsage({
289
+ * companyId: company.id,
290
+ * featureCode: 'ai_credit',
291
+ * quantity: 1,
292
+ * idempotencyKey: `msg_${messageId}` // Prevent double-counting
293
+ * })
294
+ * ```
295
+ *
296
+ * @example Claim Checkout Subscription
297
+ * ```typescript
298
+ * // After Stripe checkout completes
299
+ * const result = await os.billing.claimCheckout({
300
+ * companyId: company.id,
301
+ * checkoutSessionId: 'cs_xxx'
302
+ * })
303
+ * ```
304
+ *
305
+ * @example Link Existing Subscription
306
+ * ```typescript
307
+ * // When you create subscriptions on your Stripe account
308
+ * const result = await os.billing.linkSubscription({
309
+ * companyId: company.id,
310
+ * stripeSubscriptionId: 'sub_xxx',
311
+ * tierCode: 'pro' // Apply tier features
312
+ * })
313
+ * ```
88
314
  */
89
315
  get billing() {
316
+ const client = this.client;
90
317
  return {
91
318
  /**
92
- * Track usage for a feature
93
- * Returns updated usage info after tracking
319
+ * Track usage for a metered feature.
320
+ *
321
+ * Increments the usage counter for a feature and returns the
322
+ * updated usage information. Use idempotency keys to prevent
323
+ * duplicate tracking.
324
+ *
325
+ * @param input - Usage tracking parameters
326
+ * @returns Updated usage information
327
+ * @throws {TRPCError} If feature is not enabled or limit exceeded
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const result = await os.billing.trackUsage({
332
+ * companyId: 'comp_xxx',
333
+ * featureCode: 'ai_credit',
334
+ * quantity: 5,
335
+ * idempotencyKey: `request_${requestId}`,
336
+ * metadata: { model: 'gpt-4' }
337
+ * })
338
+ *
339
+ * console.log({
340
+ * currentUsage: result.currentUsage,
341
+ * remaining: result.remaining,
342
+ * billable: result.billableQuantity,
343
+ * isAtLimit: result.isAtLimit
344
+ * })
345
+ * ```
346
+ */
347
+ trackUsage: (input) => client.billing.trackUsage.mutate(input),
348
+ /**
349
+ * Claim a subscription from a completed Stripe Checkout Session.
350
+ *
351
+ * Use this after a user completes Stripe checkout in your product.
352
+ * This links the subscription and customer to the company in OS.
353
+ *
354
+ * @param input - Claim parameters
355
+ * @returns Result with subscription ID and claim status
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * // In your Stripe checkout success handler:
360
+ * const result = await os.billing.claimCheckout({
361
+ * companyId: company.id,
362
+ * checkoutSessionId: session.id // From Stripe callback
363
+ * })
364
+ *
365
+ * if (result.alreadyClaimed) {
366
+ * console.log('Subscription was already claimed')
367
+ * } else {
368
+ * console.log(`Claimed subscription: ${result.subscriptionId}`)
369
+ * }
370
+ * ```
371
+ */
372
+ claimCheckout: (input) => client.billing.claimCheckout.mutate(input),
373
+ /**
374
+ * Link an existing Stripe subscription to a company.
375
+ *
376
+ * Use this when you create subscriptions on your product's Stripe
377
+ * account and want OS to track them. Optionally applies tier features.
378
+ *
379
+ * @param input - Link parameters
380
+ * @returns Result with subscription ID, link status, and features applied
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * // After creating subscription on your Stripe account:
385
+ * const result = await os.billing.linkSubscription({
386
+ * companyId: company.id,
387
+ * stripeSubscriptionId: 'sub_xxx',
388
+ * stripeCustomerId: 'cus_xxx', // Optional
389
+ * tierCode: 'pro', // Apply tier features
390
+ * displayName: 'Pro Plan'
391
+ * })
392
+ *
393
+ * console.log({
394
+ * subscriptionId: result.subscriptionId,
395
+ * isNew: !result.alreadyLinked,
396
+ * featuresApplied: result.featuresApplied
397
+ * })
398
+ * ```
399
+ */
400
+ linkSubscription: (input) => client.billing.linkSubscription.mutate(input),
401
+ /**
402
+ * Get billing status for a company.
403
+ *
404
+ * Quick check for subscription status, active features, and
405
+ * billing profile information.
406
+ *
407
+ * @param companyId - Company ID to check
408
+ * @returns Billing status overview
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * const status = await os.billing.getStatus(company.id)
413
+ *
414
+ * console.log({
415
+ * hasBilling: status.hasBillingProfile,
416
+ * subscriptions: status.activeSubscriptions.length,
417
+ * features: status.enabledFeaturesCount
418
+ * })
419
+ *
420
+ * // Check active tier
421
+ * if (status.activeSubscriptions[0]?.tier) {
422
+ * console.log(`Current tier: ${status.activeSubscriptions[0].tier.name}`)
423
+ * }
424
+ * ```
94
425
  */
95
- trackUsage: (input) => this.client.billing.trackUsage.mutate(input),
426
+ getStatus: (companyId) => client.billing.getStatus.query({ companyId }),
96
427
  };
97
428
  }
429
+ // ==========================================================================
430
+ // Links
431
+ // ==========================================================================
98
432
  /**
99
- * Link operations - manually link external IDs to companies
433
+ * Manual link operations for associating external IDs with companies.
434
+ *
435
+ * Use these when you need direct control over the linking process,
436
+ * bypassing the smart link algorithm.
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * // Manually link an external org ID to an existing company
441
+ * await os.links.create({
442
+ * companyId: 'comp_xxx',
443
+ * externalOrgId: 'org_abc123',
444
+ * externalUserId: 'user_xyz'
445
+ * })
446
+ * ```
100
447
  */
101
448
  get links() {
449
+ const client = this.client;
102
450
  return {
103
451
  /**
104
- * Simple link: associate external org ID with an existing company
452
+ * Create a manual link between your external org ID and an existing company.
453
+ *
454
+ * Use this when you know the exact company ID and want to create
455
+ * a direct link without smart matching.
456
+ *
457
+ * @param input - Link parameters
458
+ * @returns The created link record
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * const link = await os.links.create({
463
+ * companyId: 'comp_xxx',
464
+ * externalOrgId: 'org_abc123',
465
+ * externalUserId: 'user_xyz' // Optional: user who created the link
466
+ * })
467
+ * ```
105
468
  */
106
- create: (input) => this.client.companies.linkToProduct.mutate({
469
+ create: (input) => client.companies.linkToProduct.mutate({
107
470
  customerId: input.companyId,
108
- productCode: "", // Will use the product from API key
471
+ productCode: "", // Uses product from API key
109
472
  externalOrgId: input.externalOrgId,
110
473
  externalUserId: input.externalUserId,
111
474
  linkType: "identified",
112
475
  }),
113
476
  };
114
477
  }
478
+ // ==========================================================================
479
+ // Subscriptions
480
+ // ==========================================================================
115
481
  /**
116
- * Subscription operations
482
+ * Subscription and tier management.
483
+ *
484
+ * Create subscriptions from tier templates, list available tiers,
485
+ * and manage company entitlements.
117
486
  *
118
487
  * @example
119
488
  * ```typescript
120
- * // Create subscription from a tier template
121
- * const sub = await os.subscriptions.create("free", {
122
- * companyId: "comp_xxx",
123
- * })
489
+ * // List available tiers
490
+ * const tiers = await os.subscriptions.tiers()
124
491
  *
125
- * // Create with overrides
126
- * const sub = await os.subscriptions.create("pro", {
127
- * companyId: "comp_xxx",
492
+ * // Apply a tier to a company
493
+ * const result = await os.subscriptions.create('pro', {
494
+ * companyId: company.id,
128
495
  * features: {
129
- * ai_agent: { included_quantity: 200 }
496
+ * ai_credit: { included_quantity: 500 }
130
497
  * }
131
498
  * })
132
- *
133
- * // Get available tiers
134
- * const tiers = await os.subscriptions.tiers()
135
499
  * ```
136
500
  */
137
501
  get subscriptions() {
138
502
  const client = this.client;
139
503
  return {
140
504
  /**
141
- * Create or apply a subscription tier to a company
505
+ * Apply a subscription tier to a company.
506
+ *
507
+ * This grants the company access to all features defined in the tier.
508
+ * Use feature overrides to customize specific feature limits.
142
509
  *
143
- * @param tierCode - The tier code (e.g., "free", "pro", "enterprise")
144
- * @param options - Subscription options including companyId and optional overrides
510
+ * **Note:** This only applies feature access, it doesn't create a
511
+ * Stripe subscription. Use `billing.linkSubscription` for that.
512
+ *
513
+ * @param tierCode - Tier code (e.g., "free", "pro", "enterprise")
514
+ * @param options - Company ID and optional feature overrides
515
+ * @returns Result with tier info and features applied
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * // Apply tier with defaults
520
+ * await os.subscriptions.create('free', {
521
+ * companyId: company.id
522
+ * })
523
+ *
524
+ * // Apply with custom limits
525
+ * await os.subscriptions.create('pro', {
526
+ * companyId: company.id,
527
+ * features: {
528
+ * ai_credit: { included_quantity: 1000 },
529
+ * seats: { max_quantity: 50 }
530
+ * }
531
+ * })
532
+ * ```
145
533
  */
146
534
  create: (tierCode, options) => client.tiers.applyByCode.mutate({
147
535
  tierCode,
@@ -149,38 +537,159 @@ export class ChatarminOS {
149
537
  featureOverrides: options.features,
150
538
  }),
151
539
  /**
152
- * Get all available tiers for the current product
540
+ * Get all available tiers for your product.
541
+ *
542
+ * Returns the list of subscription tiers configured in ChatarminOS
543
+ * for your product, including their features.
544
+ *
545
+ * @returns Array of available tiers with features
546
+ *
547
+ * @example
548
+ * ```typescript
549
+ * const tiers = await os.subscriptions.tiers()
550
+ *
551
+ * for (const tier of tiers) {
552
+ * console.log(`${tier.name} (${tier.code})`)
553
+ * for (const feature of tier.features) {
554
+ * console.log(` - ${feature.name}`)
555
+ * }
556
+ * }
557
+ * ```
153
558
  */
154
559
  tiers: () => client.tiers.listForProduct.query({ includeInactive: false }),
155
560
  /**
156
- * Get tier details by code
561
+ * Get detailed information about a specific tier.
562
+ *
563
+ * Returns the tier with all its features, including Stripe price IDs
564
+ * if configured. Use this to get the prices needed to create a
565
+ * Stripe subscription.
566
+ *
567
+ * @param tierCode - Tier code to look up
568
+ * @returns Tier details with features and Stripe prices
569
+ *
570
+ * @example
571
+ * ```typescript
572
+ * const tier = await os.subscriptions.getTier('pro')
573
+ *
574
+ * console.log(tier.stripePriceIds)
575
+ * // ['price_xxx', 'price_yyy']
576
+ *
577
+ * // Use these to create a Stripe subscription
578
+ * const sub = await stripe.subscriptions.create({
579
+ * customer: customerId,
580
+ * items: tier.stripePriceIds.map(price => ({ price }))
581
+ * })
582
+ * ```
157
583
  */
158
584
  getTier: (tierCode) => client.tiers.getByCode.query({ code: tierCode }),
159
585
  };
160
586
  }
587
+ // ==========================================================================
588
+ // Tiers
589
+ // ==========================================================================
161
590
  /**
162
- * Tier operations (alias for subscriptions.tiers for convenience)
591
+ * Tier operations (convenient access to subscription tiers).
163
592
  *
164
- * @example
593
+ * Tiers define what features and limits a company gets. Each tier
594
+ * can have associated Stripe prices for billing.
595
+ *
596
+ * @example Get Tier with Stripe Prices
165
597
  * ```typescript
166
- * // Get tier details
167
- * const freeTier = await os.tiers.get("free")
168
- * console.log(freeTier.features)
598
+ * const tier = await os.tiers.get('pro')
599
+ *
600
+ * // Create Stripe subscription using tier prices
601
+ * const stripeSub = await stripe.subscriptions.create({
602
+ * customer: stripeCustomerId,
603
+ * items: tier.stripePriceIds.map(price => ({ price }))
604
+ * })
605
+ *
606
+ * // Link back to OS
607
+ * await os.billing.linkSubscription({
608
+ * companyId: company.id,
609
+ * stripeSubscriptionId: stripeSub.id,
610
+ * tierCode: 'pro'
611
+ * })
169
612
  * ```
170
613
  */
171
614
  get tiers() {
172
615
  const client = this.client;
173
616
  return {
174
617
  /**
175
- * List all available tiers
618
+ * List all available tiers for your product.
619
+ *
620
+ * @returns Array of tiers with their features
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * const tiers = await os.tiers.list()
625
+ * console.log(tiers.map(t => t.name))
626
+ * // ['Free', 'Pro', 'Enterprise']
627
+ * ```
176
628
  */
177
629
  list: () => client.tiers.listForProduct.query({ includeInactive: false }),
178
630
  /**
179
- * Get tier details by code
631
+ * Get detailed tier information by code.
632
+ *
633
+ * Returns full tier details including:
634
+ * - Features with config values
635
+ * - Stripe price IDs for subscription creation
636
+ * - Stripe product info
637
+ *
638
+ * @param tierCode - Tier code (e.g., "free", "pro")
639
+ * @returns Tier with features and Stripe prices
640
+ *
641
+ * @example
642
+ * ```typescript
643
+ * const tier = await os.tiers.get('pro')
644
+ *
645
+ * // Access features
646
+ * for (const feature of tier.features) {
647
+ * console.log(feature.code, feature.configValues)
648
+ * }
649
+ *
650
+ * // Get Stripe prices for subscription creation
651
+ * console.log(tier.stripePriceIds)
652
+ * // ['price_xxx', 'price_yyy']
653
+ *
654
+ * // Feature-level Stripe info
655
+ * const aiFeature = tier.features.find(f => f.code === 'ai_credit')
656
+ * if (aiFeature?.stripe) {
657
+ * console.log(aiFeature.stripe.priceId)
658
+ * console.log(aiFeature.stripe.unitAmount) // in cents
659
+ * }
660
+ * ```
180
661
  */
181
662
  get: (tierCode) => client.tiers.getByCode.query({ code: tierCode }),
182
663
  /**
183
- * Apply a tier to a company
664
+ * Apply a tier's features to a company (without Stripe).
665
+ *
666
+ * Use this for:
667
+ * - Free tiers that don't need Stripe
668
+ * - Trial periods
669
+ * - Manual entitlement grants
670
+ *
671
+ * For paid tiers with Stripe, use `billing.linkSubscription` instead.
672
+ *
673
+ * @param tierCode - Tier code to apply
674
+ * @param options - Company ID and optional feature overrides
675
+ * @returns Result with applied features count
676
+ *
677
+ * @example
678
+ * ```typescript
679
+ * // Apply free tier
680
+ * const result = await os.tiers.apply('free', {
681
+ * companyId: company.id
682
+ * })
683
+ * console.log(`Applied ${result.featuresApplied} features`)
684
+ *
685
+ * // Apply with overrides
686
+ * await os.tiers.apply('trial', {
687
+ * companyId: company.id,
688
+ * features: {
689
+ * ai_credit: { included_quantity: 100 }
690
+ * }
691
+ * })
692
+ * ```
184
693
  */
185
694
  apply: (tierCode, options) => client.tiers.applyByCode.mutate({
186
695
  tierCode,
@@ -189,6 +698,170 @@ export class ChatarminOS {
189
698
  }),
190
699
  };
191
700
  }
701
+ // ==========================================================================
702
+ // Onboarding
703
+ // ==========================================================================
704
+ /**
705
+ * Complete onboarding flow in a single call.
706
+ *
707
+ * This is the recommended way to onboard new customers. It handles:
708
+ * 1. Smart company matching/creation
709
+ * 2. Product linking
710
+ * 3. Subscription claiming/linking
711
+ * 4. Tier feature application
712
+ *
713
+ * **Scenarios:**
714
+ *
715
+ * 1. **Stripe Checkout completed** - Pass `checkoutSessionId`
716
+ * 2. **Free signup** - Pass `tierCode` only
717
+ * 3. **You create Stripe subscription** - Pass `stripeSubscriptionId` + `tierCode`
718
+ *
719
+ * @param input - Onboarding parameters
720
+ * @returns Result with company info and billing status
721
+ *
722
+ * @example Scenario 1: User completed Stripe checkout
723
+ * ```typescript
724
+ * const result = await os.onboard({
725
+ * externalOrgId: 'org_abc123',
726
+ * hints: {
727
+ * companyName: 'Acme Inc',
728
+ * domain: 'acme.com'
729
+ * },
730
+ * checkoutSessionId: 'cs_test_xxx' // From Stripe callback
731
+ * })
732
+ *
733
+ * // result.billingStatus.claimed = true
734
+ * ```
735
+ *
736
+ * @example Scenario 2: Free tier signup
737
+ * ```typescript
738
+ * const result = await os.onboard({
739
+ * externalOrgId: 'org_abc123',
740
+ * hints: {
741
+ * companyName: 'Acme Inc',
742
+ * domain: 'acme.com'
743
+ * },
744
+ * tierCode: 'free',
745
+ * contactEmail: 'admin@acme.com',
746
+ * contactName: 'John Doe'
747
+ * })
748
+ *
749
+ * // result.billingStatus.tierApplied = true
750
+ * ```
751
+ *
752
+ * @example Scenario 3: You create Stripe subscription
753
+ * ```typescript
754
+ * // First, get tier prices
755
+ * const tier = await os.tiers.get('pro')
756
+ *
757
+ * // Create subscription on YOUR Stripe account
758
+ * const stripeSub = await stripe.subscriptions.create({
759
+ * customer: stripeCustomerId,
760
+ * items: tier.stripePriceIds.map(price => ({ price }))
761
+ * })
762
+ *
763
+ * // Then onboard with the subscription
764
+ * const result = await os.onboard({
765
+ * externalOrgId: 'org_abc123',
766
+ * hints: { companyName: 'Acme Inc' },
767
+ * stripeSubscriptionId: stripeSub.id,
768
+ * stripeCustomerId: stripeCustomerId,
769
+ * tierCode: 'pro'
770
+ * })
771
+ *
772
+ * // result.billingStatus.linked = true
773
+ * // result.billingStatus.tierApplied = true
774
+ * ```
775
+ *
776
+ * @example Handle existing customers
777
+ * ```typescript
778
+ * const result = await os.onboard({
779
+ * externalOrgId: 'org_abc123',
780
+ * hints: { companyName: 'Acme Inc' }
781
+ * })
782
+ *
783
+ * if (result.linkStatus === 'already_linked') {
784
+ * console.log('Welcome back!')
785
+ * } else if (result.linkStatus === 'linked') {
786
+ * console.log('Found existing company, linked!')
787
+ * } else {
788
+ * console.log('New company created!')
789
+ * }
790
+ * ```
791
+ */
792
+ async onboard(input) {
793
+ // Step 1: Smart link or create company
794
+ let companyId;
795
+ let companyName;
796
+ let linkStatus;
797
+ const smartLinkResult = await this.companies.smartLink({
798
+ externalOrgId: input.externalOrgId,
799
+ hints: input.hints ?? {},
800
+ });
801
+ if (smartLinkResult.status === "already_linked") {
802
+ companyId = smartLinkResult.customerId;
803
+ companyName = smartLinkResult.customerName;
804
+ linkStatus = "already_linked";
805
+ }
806
+ else if (smartLinkResult.status === "linked") {
807
+ companyId = smartLinkResult.customerId;
808
+ companyName = smartLinkResult.customerName;
809
+ linkStatus = "linked";
810
+ }
811
+ else {
812
+ // No match found - create new company
813
+ const newCompany = await this.companies.create({
814
+ name: input.hints?.companyName ?? `Company ${input.externalOrgId}`,
815
+ domain: input.hints?.domain,
816
+ externalOrgId: input.externalOrgId,
817
+ contactEmail: input.contactEmail,
818
+ contactName: input.contactName,
819
+ });
820
+ companyId = newCompany.id;
821
+ companyName = newCompany.name;
822
+ linkStatus = "created";
823
+ }
824
+ // Step 2: Handle billing
825
+ let billingStatus;
826
+ if (input.checkoutSessionId) {
827
+ // Claim subscription from checkout
828
+ const claimResult = await this.billing.claimCheckout({
829
+ companyId,
830
+ checkoutSessionId: input.checkoutSessionId,
831
+ });
832
+ billingStatus = {
833
+ subscriptionId: claimResult.subscriptionId,
834
+ claimed: !claimResult.alreadyClaimed,
835
+ };
836
+ }
837
+ else if (input.stripeSubscriptionId) {
838
+ // Link existing subscription
839
+ const linkResult = await this.billing.linkSubscription({
840
+ companyId,
841
+ stripeSubscriptionId: input.stripeSubscriptionId,
842
+ stripeCustomerId: input.stripeCustomerId,
843
+ tierCode: input.tierCode,
844
+ });
845
+ billingStatus = {
846
+ subscriptionId: linkResult.subscriptionId,
847
+ linked: !linkResult.alreadyLinked,
848
+ tierApplied: linkResult.tierApplied,
849
+ };
850
+ }
851
+ else if (input.tierCode) {
852
+ // Just apply tier features (no Stripe)
853
+ const applyResult = await this.tiers.apply(input.tierCode, { companyId });
854
+ billingStatus = {
855
+ tierApplied: applyResult.featuresApplied > 0,
856
+ };
857
+ }
858
+ return {
859
+ companyId,
860
+ companyName,
861
+ linkStatus,
862
+ billingStatus,
863
+ };
864
+ }
192
865
  }
193
866
  // Default export
194
867
  export default ChatarminOS;