@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.d.ts +904 -123
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +731 -58
- package/package.json +1 -1
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({
|
|
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
|
-
* //
|
|
15
|
-
*
|
|
33
|
+
* console.log(result.companyId) // Use this for all future API calls
|
|
34
|
+
* ```
|
|
16
35
|
*
|
|
17
|
-
*
|
|
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
|
-
*
|
|
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) =>
|
|
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) =>
|
|
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
|
-
* @
|
|
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) =>
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
426
|
+
getStatus: (companyId) => client.billing.getStatus.query({ companyId }),
|
|
96
427
|
};
|
|
97
428
|
}
|
|
429
|
+
// ==========================================================================
|
|
430
|
+
// Links
|
|
431
|
+
// ==========================================================================
|
|
98
432
|
/**
|
|
99
|
-
*
|
|
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
|
-
*
|
|
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) =>
|
|
469
|
+
create: (input) => client.companies.linkToProduct.mutate({
|
|
107
470
|
customerId: input.companyId,
|
|
108
|
-
productCode: "", //
|
|
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
|
|
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
|
-
* //
|
|
121
|
-
* const
|
|
122
|
-
* companyId: "comp_xxx",
|
|
123
|
-
* })
|
|
489
|
+
* // List available tiers
|
|
490
|
+
* const tiers = await os.subscriptions.tiers()
|
|
124
491
|
*
|
|
125
|
-
* //
|
|
126
|
-
* const
|
|
127
|
-
* companyId:
|
|
492
|
+
* // Apply a tier to a company
|
|
493
|
+
* const result = await os.subscriptions.create('pro', {
|
|
494
|
+
* companyId: company.id,
|
|
128
495
|
* features: {
|
|
129
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
144
|
-
*
|
|
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
|
|
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
|
|
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 (
|
|
591
|
+
* Tier operations (convenient access to subscription tiers).
|
|
163
592
|
*
|
|
164
|
-
*
|
|
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
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
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
|
|
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;
|