@stackbe/sdk 0.1.0 → 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.mts CHANGED
@@ -7,6 +7,7 @@ interface HttpClientConfig {
7
7
  declare class HttpClient {
8
8
  private config;
9
9
  constructor(config: HttpClientConfig);
10
+ get baseUrl(): string;
10
11
  private request;
11
12
  get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
12
13
  post<T>(path: string, body?: unknown, params?: Record<string, string | number | undefined>): Promise<T>;
@@ -109,31 +110,216 @@ interface UpdateCustomerOptions {
109
110
  name?: string;
110
111
  metadata?: Record<string, unknown>;
111
112
  }
113
+ interface MagicLinkOptions {
114
+ /** URL to redirect to after authentication */
115
+ redirectUrl?: string;
116
+ /** Use development callback URL (for localhost testing) */
117
+ useDev?: boolean;
118
+ }
112
119
  interface MagicLinkResponse {
113
120
  success: boolean;
114
121
  message: string;
115
122
  }
116
123
  interface VerifyTokenResponse {
124
+ success: boolean;
117
125
  valid: boolean;
118
- customer?: Customer;
119
- token?: string;
120
- expiresAt?: string;
126
+ /** Customer ID */
127
+ customerId: string;
128
+ /** Customer email */
129
+ email: string;
130
+ /** Session JWT token - use this for subsequent authenticated requests */
131
+ sessionToken: string;
132
+ /** Tenant ID (your StackBE tenant) */
133
+ tenantId?: string;
134
+ /** Organization ID if in org context */
135
+ organizationId?: string;
136
+ /** Role in the organization */
137
+ orgRole?: 'owner' | 'admin' | 'member';
138
+ /** URL to redirect to after verification */
139
+ redirectUrl?: string;
121
140
  }
122
141
  interface SessionResponse {
123
- customer: Customer;
142
+ valid: boolean;
143
+ /** Customer ID */
144
+ customerId: string;
145
+ /** Customer email */
146
+ email: string;
147
+ /** Session expiration time */
148
+ expiresAt?: string;
149
+ /** Tenant ID (your StackBE tenant) */
150
+ tenantId?: string;
151
+ /** Organization ID if in org context */
152
+ organizationId?: string;
153
+ /** Role in the organization */
154
+ orgRole?: 'owner' | 'admin' | 'member';
155
+ /** Customer details (if included) */
156
+ customer?: Customer;
157
+ /** Current subscription (if any) */
124
158
  subscription?: Subscription;
125
- entitlements: Record<string, boolean | number | string>;
159
+ /** Feature entitlements */
160
+ entitlements?: Record<string, boolean | number | string>;
161
+ }
162
+ interface CreateCheckoutOptions {
163
+ /** Customer ID or email */
164
+ customer: string | {
165
+ email: string;
166
+ name?: string;
167
+ };
168
+ /** Plan ID to subscribe to */
169
+ planId: string;
170
+ /** URL to redirect after successful checkout */
171
+ successUrl: string;
172
+ /** URL to redirect if checkout is canceled */
173
+ cancelUrl?: string;
174
+ /** Allow promotion codes */
175
+ allowPromotionCodes?: boolean;
176
+ /** Trial period in days */
177
+ trialDays?: number;
178
+ /** Metadata to attach to the subscription */
179
+ metadata?: Record<string, string>;
180
+ }
181
+ interface CheckoutSessionResponse {
182
+ /** Checkout session ID */
183
+ id: string;
184
+ /** URL to redirect customer to for checkout */
185
+ url: string;
186
+ /** Session status */
187
+ status: 'open' | 'complete' | 'expired';
188
+ /** Customer ID */
189
+ customerId: string;
190
+ /** Expiration time */
191
+ expiresAt: string;
192
+ }
193
+ interface SubscriptionWithPlan extends Subscription {
194
+ plan: {
195
+ id: string;
196
+ name: string;
197
+ price: number;
198
+ interval: 'month' | 'year';
199
+ currency: string;
200
+ };
201
+ }
202
+ interface CancelSubscriptionOptions {
203
+ /** Cancel immediately or at end of billing period */
204
+ immediate?: boolean;
205
+ /** Reason for cancellation */
206
+ reason?: string;
207
+ }
208
+ interface CancelSubscriptionResponse {
209
+ success: boolean;
210
+ subscription: Subscription;
211
+ /** When the subscription will actually end */
212
+ endsAt: string;
213
+ }
214
+ interface UpdateSubscriptionOptions {
215
+ /** New plan ID to switch to */
216
+ planId?: string;
217
+ /** Proration behavior */
218
+ prorate?: boolean;
126
219
  }
220
+ /**
221
+ * Error codes returned by StackBE API.
222
+ * Use these to handle specific error cases in your application.
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * try {
227
+ * await stackbe.auth.verifyToken(token);
228
+ * } catch (error) {
229
+ * if (error instanceof StackBEError) {
230
+ * switch (error.code) {
231
+ * case 'TOKEN_EXPIRED':
232
+ * return res.redirect('/login?error=expired');
233
+ * case 'TOKEN_ALREADY_USED':
234
+ * return res.redirect('/login?error=used');
235
+ * case 'SESSION_EXPIRED':
236
+ * return res.redirect('/login');
237
+ * default:
238
+ * return res.status(500).json({ error: 'Authentication failed' });
239
+ * }
240
+ * }
241
+ * }
242
+ * ```
243
+ */
244
+ type StackBEErrorCode = 'TOKEN_EXPIRED' | 'TOKEN_ALREADY_USED' | 'TOKEN_INVALID' | 'SESSION_EXPIRED' | 'SESSION_INVALID' | 'UNAUTHORIZED' | 'NOT_FOUND' | 'CUSTOMER_NOT_FOUND' | 'SUBSCRIPTION_NOT_FOUND' | 'PLAN_NOT_FOUND' | 'APP_NOT_FOUND' | 'VALIDATION_ERROR' | 'MISSING_REQUIRED_FIELD' | 'INVALID_EMAIL' | 'USAGE_LIMIT_EXCEEDED' | 'METRIC_NOT_FOUND' | 'FEATURE_NOT_AVAILABLE' | 'NO_ACTIVE_SUBSCRIPTION' | 'TIMEOUT' | 'NETWORK_ERROR' | 'UNKNOWN_ERROR';
127
245
  interface StackBEErrorResponse {
128
246
  statusCode: number;
129
247
  message: string;
130
248
  error: string;
249
+ code?: StackBEErrorCode;
131
250
  }
132
251
  declare class StackBEError extends Error {
133
252
  readonly statusCode: number;
134
- readonly code: string;
135
- constructor(message: string, statusCode: number, code: string);
253
+ readonly code: StackBEErrorCode;
254
+ constructor(message: string, statusCode: number, code: StackBEErrorCode | string);
255
+ /** Check if this is a specific error type */
256
+ is(code: StackBEErrorCode): boolean;
257
+ /** Check if this is an auth-related error */
258
+ isAuthError(): boolean;
259
+ /** Check if this is a "not found" error */
260
+ isNotFoundError(): boolean;
136
261
  }
262
+ /**
263
+ * Webhook event types emitted by StackBE.
264
+ * Subscribe to these events to react to changes in your customers' subscriptions.
265
+ */
266
+ type WebhookEventType = 'subscription_created' | 'subscription_updated' | 'subscription_cancelled' | 'subscription_renewed' | 'trial_started' | 'trial_ended' | 'payment_succeeded' | 'payment_failed' | 'customer_created' | 'customer_updated';
267
+ /** Base webhook event structure */
268
+ interface WebhookEvent<T extends WebhookEventType = WebhookEventType, P = unknown> {
269
+ /** Event ID */
270
+ id: string;
271
+ /** Event type */
272
+ type: T;
273
+ /** Tenant ID */
274
+ tenantId: string;
275
+ /** App ID */
276
+ appId: string;
277
+ /** Event payload */
278
+ data: P;
279
+ /** When the event was created */
280
+ createdAt: string;
281
+ }
282
+ /** Subscription webhook payload */
283
+ interface SubscriptionWebhookPayload {
284
+ id: string;
285
+ customerId: string;
286
+ planId: string;
287
+ planName: string;
288
+ status: 'active' | 'canceled' | 'past_due' | 'trialing' | 'paused';
289
+ currentPeriodStart: string;
290
+ currentPeriodEnd: string;
291
+ cancelAtPeriodEnd: boolean;
292
+ trialEnd?: string;
293
+ }
294
+ /** Customer webhook payload */
295
+ interface CustomerWebhookPayload {
296
+ id: string;
297
+ email: string;
298
+ name?: string;
299
+ metadata?: Record<string, unknown>;
300
+ }
301
+ /** Payment webhook payload */
302
+ interface PaymentWebhookPayload {
303
+ id: string;
304
+ subscriptionId: string;
305
+ customerId: string;
306
+ amount: number;
307
+ currency: string;
308
+ status: 'succeeded' | 'failed';
309
+ failureReason?: string;
310
+ }
311
+ type SubscriptionCreatedEvent = WebhookEvent<'subscription_created', SubscriptionWebhookPayload>;
312
+ type SubscriptionUpdatedEvent = WebhookEvent<'subscription_updated', SubscriptionWebhookPayload>;
313
+ type SubscriptionCancelledEvent = WebhookEvent<'subscription_cancelled', SubscriptionWebhookPayload>;
314
+ type SubscriptionRenewedEvent = WebhookEvent<'subscription_renewed', SubscriptionWebhookPayload>;
315
+ type TrialStartedEvent = WebhookEvent<'trial_started', SubscriptionWebhookPayload>;
316
+ type TrialEndedEvent = WebhookEvent<'trial_ended', SubscriptionWebhookPayload>;
317
+ type PaymentSucceededEvent = WebhookEvent<'payment_succeeded', PaymentWebhookPayload>;
318
+ type PaymentFailedEvent = WebhookEvent<'payment_failed', PaymentWebhookPayload>;
319
+ type CustomerCreatedEvent = WebhookEvent<'customer_created', CustomerWebhookPayload>;
320
+ type CustomerUpdatedEvent = WebhookEvent<'customer_updated', CustomerWebhookPayload>;
321
+ /** Union of all webhook event types */
322
+ type AnyWebhookEvent = SubscriptionCreatedEvent | SubscriptionUpdatedEvent | SubscriptionCancelledEvent | SubscriptionRenewedEvent | TrialStartedEvent | TrialEndedEvent | PaymentSucceededEvent | PaymentFailedEvent | CustomerCreatedEvent | CustomerUpdatedEvent;
137
323
 
138
324
  declare class UsageClient {
139
325
  private http;
@@ -347,14 +533,287 @@ declare class CustomersClient {
347
533
  getSession(token: string): Promise<SessionResponse>;
348
534
  }
349
535
 
536
+ declare class CheckoutClient {
537
+ private http;
538
+ private appId;
539
+ constructor(http: HttpClient, appId: string);
540
+ /**
541
+ * Create a checkout session for a customer to subscribe to a plan.
542
+ * Returns a URL to redirect the customer to Stripe checkout.
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * // With existing customer ID
547
+ * const { url } = await stackbe.checkout.createSession({
548
+ * customer: 'cust_123',
549
+ * planId: 'plan_pro_monthly',
550
+ * successUrl: 'https://myapp.com/success',
551
+ * cancelUrl: 'https://myapp.com/pricing',
552
+ * });
553
+ *
554
+ * // Redirect to checkout
555
+ * res.redirect(url);
556
+ * ```
557
+ *
558
+ * @example
559
+ * ```typescript
560
+ * // With new customer (will be created)
561
+ * const { url } = await stackbe.checkout.createSession({
562
+ * customer: { email: 'user@example.com', name: 'John' },
563
+ * planId: 'plan_pro_monthly',
564
+ * successUrl: 'https://myapp.com/success',
565
+ * trialDays: 14,
566
+ * });
567
+ * ```
568
+ */
569
+ createSession(options: CreateCheckoutOptions): Promise<CheckoutSessionResponse>;
570
+ /**
571
+ * Get an existing checkout session by ID.
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * const session = await stackbe.checkout.getSession('cs_123');
576
+ * if (session.status === 'complete') {
577
+ * // Payment successful
578
+ * }
579
+ * ```
580
+ */
581
+ getSession(sessionId: string): Promise<CheckoutSessionResponse>;
582
+ /**
583
+ * Generate a checkout URL for a plan.
584
+ * Convenience method that creates a session and returns just the URL.
585
+ *
586
+ * @example
587
+ * ```typescript
588
+ * const checkoutUrl = await stackbe.checkout.getCheckoutUrl({
589
+ * customer: 'cust_123',
590
+ * planId: 'plan_pro_monthly',
591
+ * successUrl: 'https://myapp.com/success',
592
+ * });
593
+ *
594
+ * // Send to frontend
595
+ * res.json({ checkoutUrl });
596
+ * ```
597
+ */
598
+ getCheckoutUrl(options: CreateCheckoutOptions): Promise<string>;
599
+ }
600
+
601
+ declare class SubscriptionsClient {
602
+ private http;
603
+ constructor(http: HttpClient);
604
+ /**
605
+ * Get a customer's current active subscription.
606
+ *
607
+ * @example
608
+ * ```typescript
609
+ * const subscription = await stackbe.subscriptions.get('cust_123');
610
+ *
611
+ * if (subscription) {
612
+ * console.log(`Plan: ${subscription.plan.name}`);
613
+ * console.log(`Status: ${subscription.status}`);
614
+ * console.log(`Renews: ${subscription.currentPeriodEnd}`);
615
+ * } else {
616
+ * console.log('No active subscription');
617
+ * }
618
+ * ```
619
+ */
620
+ get(customerId: string): Promise<SubscriptionWithPlan | null>;
621
+ /**
622
+ * Get a subscription by ID.
623
+ *
624
+ * @example
625
+ * ```typescript
626
+ * const subscription = await stackbe.subscriptions.getById('sub_123');
627
+ * ```
628
+ */
629
+ getById(subscriptionId: string): Promise<SubscriptionWithPlan>;
630
+ /**
631
+ * List all subscriptions for a customer.
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * const subscriptions = await stackbe.subscriptions.list('cust_123');
636
+ * ```
637
+ */
638
+ list(customerId: string): Promise<Subscription[]>;
639
+ /**
640
+ * Cancel a subscription.
641
+ *
642
+ * @example
643
+ * ```typescript
644
+ * // Cancel at end of billing period (default)
645
+ * await stackbe.subscriptions.cancel('sub_123');
646
+ *
647
+ * // Cancel immediately
648
+ * await stackbe.subscriptions.cancel('sub_123', { immediate: true });
649
+ *
650
+ * // Cancel with reason
651
+ * await stackbe.subscriptions.cancel('sub_123', {
652
+ * reason: 'Too expensive'
653
+ * });
654
+ * ```
655
+ */
656
+ cancel(subscriptionId: string, options?: CancelSubscriptionOptions): Promise<CancelSubscriptionResponse>;
657
+ /**
658
+ * Update a subscription (change plan).
659
+ *
660
+ * @example
661
+ * ```typescript
662
+ * // Upgrade/downgrade to a different plan
663
+ * await stackbe.subscriptions.update('sub_123', {
664
+ * planId: 'plan_enterprise_monthly',
665
+ * prorate: true,
666
+ * });
667
+ * ```
668
+ */
669
+ update(subscriptionId: string, options: UpdateSubscriptionOptions): Promise<Subscription>;
670
+ /**
671
+ * Reactivate a canceled subscription (before it ends).
672
+ *
673
+ * @example
674
+ * ```typescript
675
+ * // Customer changed their mind
676
+ * await stackbe.subscriptions.reactivate('sub_123');
677
+ * ```
678
+ */
679
+ reactivate(subscriptionId: string): Promise<Subscription>;
680
+ /**
681
+ * Check if a customer has an active subscription.
682
+ *
683
+ * @example
684
+ * ```typescript
685
+ * const isActive = await stackbe.subscriptions.isActive('cust_123');
686
+ * if (!isActive) {
687
+ * return res.redirect('/pricing');
688
+ * }
689
+ * ```
690
+ */
691
+ isActive(customerId: string): Promise<boolean>;
692
+ }
693
+
694
+ declare class AuthClient {
695
+ private http;
696
+ private appId;
697
+ constructor(http: HttpClient, appId: string);
698
+ /**
699
+ * Send a magic link email to a customer for passwordless authentication.
700
+ *
701
+ * @example
702
+ * ```typescript
703
+ * // Send magic link
704
+ * await stackbe.auth.sendMagicLink('user@example.com');
705
+ *
706
+ * // With redirect URL
707
+ * await stackbe.auth.sendMagicLink('user@example.com', {
708
+ * redirectUrl: 'https://myapp.com/dashboard',
709
+ * });
710
+ *
711
+ * // For localhost development
712
+ * await stackbe.auth.sendMagicLink('user@example.com', {
713
+ * useDev: true,
714
+ * });
715
+ * ```
716
+ */
717
+ sendMagicLink(email: string, options?: MagicLinkOptions): Promise<MagicLinkResponse>;
718
+ /**
719
+ * Verify a magic link token and get a session token.
720
+ * Call this when the user clicks the magic link.
721
+ *
722
+ * Returns the session token along with tenant and organization context.
723
+ *
724
+ * @example
725
+ * ```typescript
726
+ * // In your /verify route handler
727
+ * const { token } = req.query;
728
+ *
729
+ * try {
730
+ * const result = await stackbe.auth.verifyToken(token);
731
+ *
732
+ * // result includes: customerId, email, sessionToken, tenantId, organizationId
733
+ * res.cookie('session', result.sessionToken, { httpOnly: true });
734
+ * res.redirect('/dashboard');
735
+ * } catch (error) {
736
+ * if (error instanceof StackBEError) {
737
+ * if (error.code === 'TOKEN_EXPIRED') {
738
+ * res.redirect('/login?error=expired');
739
+ * } else if (error.code === 'TOKEN_ALREADY_USED') {
740
+ * res.redirect('/login?error=used');
741
+ * }
742
+ * }
743
+ * }
744
+ * ```
745
+ */
746
+ verifyToken(token: string): Promise<VerifyTokenResponse>;
747
+ /**
748
+ * Get the current session for an authenticated customer.
749
+ * Use this to validate session tokens and get customer data.
750
+ *
751
+ * Returns session info including tenant and organization context extracted from the JWT.
752
+ *
753
+ * @example
754
+ * ```typescript
755
+ * // Validate session on each request
756
+ * const sessionToken = req.cookies.session;
757
+ *
758
+ * const session = await stackbe.auth.getSession(sessionToken);
759
+ *
760
+ * if (session) {
761
+ * console.log(session.customerId);
762
+ * console.log(session.tenantId); // Tenant context
763
+ * console.log(session.organizationId); // Org context (if applicable)
764
+ * console.log(session.subscription);
765
+ * console.log(session.entitlements);
766
+ * }
767
+ * ```
768
+ */
769
+ getSession(sessionToken: string): Promise<SessionResponse | null>;
770
+ /**
771
+ * Create Express middleware that validates session tokens.
772
+ *
773
+ * @example
774
+ * ```typescript
775
+ * // Protect routes with authentication
776
+ * app.use('/dashboard', stackbe.auth.middleware({
777
+ * getToken: (req) => req.cookies.session,
778
+ * onUnauthenticated: (req, res) => res.redirect('/login'),
779
+ * }));
780
+ *
781
+ * app.get('/dashboard', (req, res) => {
782
+ * // req.customer is available
783
+ * res.json({ email: req.customer.email });
784
+ * });
785
+ * ```
786
+ */
787
+ middleware(options: {
788
+ getToken: (req: any) => string | undefined;
789
+ onUnauthenticated?: (req: any, res: any) => void;
790
+ }): (req: any, res: any, next: any) => Promise<any>;
791
+ /**
792
+ * Check if a session token is valid.
793
+ *
794
+ * @example
795
+ * ```typescript
796
+ * const isValid = await stackbe.auth.isAuthenticated(sessionToken);
797
+ * ```
798
+ */
799
+ isAuthenticated(sessionToken: string): Promise<boolean>;
800
+ }
801
+
350
802
  declare class StackBE {
351
803
  private http;
804
+ private appId;
352
805
  /** Usage tracking and limits */
353
806
  readonly usage: UsageClient;
354
807
  /** Feature entitlements */
355
808
  readonly entitlements: EntitlementsClient;
356
809
  /** Customer management */
357
810
  readonly customers: CustomersClient;
811
+ /** Checkout sessions for Stripe */
812
+ readonly checkout: CheckoutClient;
813
+ /** Subscription management */
814
+ readonly subscriptions: SubscriptionsClient;
815
+ /** Customer authentication (magic links) */
816
+ readonly auth: AuthClient;
358
817
  /**
359
818
  * Create a new StackBE client.
360
819
  *
@@ -373,8 +832,18 @@ declare class StackBE {
373
832
  * // Check entitlements
374
833
  * const { hasAccess } = await stackbe.entitlements.check('customer_123', 'premium');
375
834
  *
376
- * // Get customer
377
- * const customer = await stackbe.customers.get('customer_123');
835
+ * // Create checkout session
836
+ * const { url } = await stackbe.checkout.createSession({
837
+ * customer: 'cust_123',
838
+ * planId: 'plan_pro',
839
+ * successUrl: 'https://myapp.com/success',
840
+ * });
841
+ *
842
+ * // Get subscription
843
+ * const subscription = await stackbe.subscriptions.get('cust_123');
844
+ *
845
+ * // Send magic link
846
+ * await stackbe.auth.sendMagicLink('user@example.com');
378
847
  * ```
379
848
  */
380
849
  constructor(config: StackBEConfig);
@@ -445,4 +914,4 @@ declare class StackBE {
445
914
  }): (req: any, res: any, next: any) => Promise<any>;
446
915
  }
447
916
 
448
- export { type CheckEntitlementResponse, type CheckUsageResponse, type CreateCustomerOptions, type Customer, type CustomerUsageResponse, type CustomerWithSubscription, CustomersClient, EntitlementsClient, type EntitlementsResponse, type MagicLinkResponse, type SessionResponse, StackBE, type StackBEConfig, StackBEError, type StackBEErrorResponse, type Subscription, type TrackUsageOptions, type TrackUsageResponse, type UpdateCustomerOptions, UsageClient, type UsageMetric, type VerifyTokenResponse };
917
+ export { type AnyWebhookEvent, AuthClient, type CancelSubscriptionOptions, type CancelSubscriptionResponse, type CheckEntitlementResponse, type CheckUsageResponse, CheckoutClient, type CheckoutSessionResponse, type CreateCheckoutOptions, type CreateCustomerOptions, type Customer, type CustomerCreatedEvent, type CustomerUpdatedEvent, type CustomerUsageResponse, type CustomerWebhookPayload, type CustomerWithSubscription, CustomersClient, EntitlementsClient, type EntitlementsResponse, type MagicLinkOptions, type MagicLinkResponse, type PaymentFailedEvent, type PaymentSucceededEvent, type PaymentWebhookPayload, type SessionResponse, StackBE, type StackBEConfig, StackBEError, type StackBEErrorCode, type StackBEErrorResponse, type Subscription, type SubscriptionCancelledEvent, type SubscriptionCreatedEvent, type SubscriptionRenewedEvent, type SubscriptionUpdatedEvent, type SubscriptionWebhookPayload, type SubscriptionWithPlan, SubscriptionsClient, type TrackUsageOptions, type TrackUsageResponse, type TrialEndedEvent, type TrialStartedEvent, type UpdateCustomerOptions, type UpdateSubscriptionOptions, UsageClient, type UsageMetric, type VerifyTokenResponse, type WebhookEvent, type WebhookEventType };