@stackbe/sdk 0.1.0 → 0.2.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
@@ -20,10 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AuthClient: () => AuthClient,
24
+ CheckoutClient: () => CheckoutClient,
23
25
  CustomersClient: () => CustomersClient,
24
26
  EntitlementsClient: () => EntitlementsClient,
25
27
  StackBE: () => StackBE,
26
28
  StackBEError: () => StackBEError,
29
+ SubscriptionsClient: () => SubscriptionsClient,
27
30
  UsageClient: () => UsageClient
28
31
  });
29
32
  module.exports = __toCommonJS(index_exports);
@@ -43,6 +46,9 @@ var HttpClient = class {
43
46
  constructor(config) {
44
47
  this.config = config;
45
48
  }
49
+ get baseUrl() {
50
+ return this.config.baseUrl;
51
+ }
46
52
  async request(method, path, options = {}) {
47
53
  const url = new URL(path, this.config.baseUrl);
48
54
  if (options.params) {
@@ -409,6 +415,379 @@ var CustomersClient = class {
409
415
  }
410
416
  };
411
417
 
418
+ // src/checkout.ts
419
+ var CheckoutClient = class {
420
+ constructor(http, appId) {
421
+ this.http = http;
422
+ this.appId = appId;
423
+ }
424
+ /**
425
+ * Create a checkout session for a customer to subscribe to a plan.
426
+ * Returns a URL to redirect the customer to Stripe checkout.
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * // With existing customer ID
431
+ * const { url } = await stackbe.checkout.createSession({
432
+ * customer: 'cust_123',
433
+ * planId: 'plan_pro_monthly',
434
+ * successUrl: 'https://myapp.com/success',
435
+ * cancelUrl: 'https://myapp.com/pricing',
436
+ * });
437
+ *
438
+ * // Redirect to checkout
439
+ * res.redirect(url);
440
+ * ```
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * // With new customer (will be created)
445
+ * const { url } = await stackbe.checkout.createSession({
446
+ * customer: { email: 'user@example.com', name: 'John' },
447
+ * planId: 'plan_pro_monthly',
448
+ * successUrl: 'https://myapp.com/success',
449
+ * trialDays: 14,
450
+ * });
451
+ * ```
452
+ */
453
+ async createSession(options) {
454
+ const body = {
455
+ planId: options.planId,
456
+ successUrl: options.successUrl,
457
+ cancelUrl: options.cancelUrl,
458
+ allowPromotionCodes: options.allowPromotionCodes,
459
+ trialDays: options.trialDays,
460
+ metadata: options.metadata
461
+ };
462
+ if (typeof options.customer === "string") {
463
+ body.customerId = options.customer;
464
+ } else {
465
+ body.customerEmail = options.customer.email;
466
+ body.customerName = options.customer.name;
467
+ }
468
+ return this.http.post("/v1/checkout/session", body);
469
+ }
470
+ /**
471
+ * Get an existing checkout session by ID.
472
+ *
473
+ * @example
474
+ * ```typescript
475
+ * const session = await stackbe.checkout.getSession('cs_123');
476
+ * if (session.status === 'complete') {
477
+ * // Payment successful
478
+ * }
479
+ * ```
480
+ */
481
+ async getSession(sessionId) {
482
+ return this.http.get(`/v1/checkout/session/${sessionId}`);
483
+ }
484
+ /**
485
+ * Generate a checkout URL for a plan.
486
+ * Convenience method that creates a session and returns just the URL.
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * const checkoutUrl = await stackbe.checkout.getCheckoutUrl({
491
+ * customer: 'cust_123',
492
+ * planId: 'plan_pro_monthly',
493
+ * successUrl: 'https://myapp.com/success',
494
+ * });
495
+ *
496
+ * // Send to frontend
497
+ * res.json({ checkoutUrl });
498
+ * ```
499
+ */
500
+ async getCheckoutUrl(options) {
501
+ const session = await this.createSession(options);
502
+ return session.url;
503
+ }
504
+ };
505
+
506
+ // src/subscriptions.ts
507
+ var SubscriptionsClient = class {
508
+ constructor(http) {
509
+ this.http = http;
510
+ }
511
+ /**
512
+ * Get a customer's current active subscription.
513
+ *
514
+ * @example
515
+ * ```typescript
516
+ * const subscription = await stackbe.subscriptions.get('cust_123');
517
+ *
518
+ * if (subscription) {
519
+ * console.log(`Plan: ${subscription.plan.name}`);
520
+ * console.log(`Status: ${subscription.status}`);
521
+ * console.log(`Renews: ${subscription.currentPeriodEnd}`);
522
+ * } else {
523
+ * console.log('No active subscription');
524
+ * }
525
+ * ```
526
+ */
527
+ async get(customerId) {
528
+ try {
529
+ return await this.http.get(
530
+ `/v1/subscriptions/current`,
531
+ { customerId }
532
+ );
533
+ } catch (error) {
534
+ if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
535
+ return null;
536
+ }
537
+ throw error;
538
+ }
539
+ }
540
+ /**
541
+ * Get a subscription by ID.
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * const subscription = await stackbe.subscriptions.getById('sub_123');
546
+ * ```
547
+ */
548
+ async getById(subscriptionId) {
549
+ return this.http.get(`/v1/subscriptions/${subscriptionId}`);
550
+ }
551
+ /**
552
+ * List all subscriptions for a customer.
553
+ *
554
+ * @example
555
+ * ```typescript
556
+ * const subscriptions = await stackbe.subscriptions.list('cust_123');
557
+ * ```
558
+ */
559
+ async list(customerId) {
560
+ return this.http.get("/v1/subscriptions", { customerId });
561
+ }
562
+ /**
563
+ * Cancel a subscription.
564
+ *
565
+ * @example
566
+ * ```typescript
567
+ * // Cancel at end of billing period (default)
568
+ * await stackbe.subscriptions.cancel('sub_123');
569
+ *
570
+ * // Cancel immediately
571
+ * await stackbe.subscriptions.cancel('sub_123', { immediate: true });
572
+ *
573
+ * // Cancel with reason
574
+ * await stackbe.subscriptions.cancel('sub_123', {
575
+ * reason: 'Too expensive'
576
+ * });
577
+ * ```
578
+ */
579
+ async cancel(subscriptionId, options = {}) {
580
+ return this.http.post(
581
+ `/v1/subscriptions/${subscriptionId}/cancel`,
582
+ {
583
+ immediate: options.immediate ?? false,
584
+ reason: options.reason
585
+ }
586
+ );
587
+ }
588
+ /**
589
+ * Update a subscription (change plan).
590
+ *
591
+ * @example
592
+ * ```typescript
593
+ * // Upgrade/downgrade to a different plan
594
+ * await stackbe.subscriptions.update('sub_123', {
595
+ * planId: 'plan_enterprise_monthly',
596
+ * prorate: true,
597
+ * });
598
+ * ```
599
+ */
600
+ async update(subscriptionId, options) {
601
+ return this.http.patch(
602
+ `/v1/subscriptions/${subscriptionId}`,
603
+ options
604
+ );
605
+ }
606
+ /**
607
+ * Reactivate a canceled subscription (before it ends).
608
+ *
609
+ * @example
610
+ * ```typescript
611
+ * // Customer changed their mind
612
+ * await stackbe.subscriptions.reactivate('sub_123');
613
+ * ```
614
+ */
615
+ async reactivate(subscriptionId) {
616
+ return this.http.post(
617
+ `/v1/subscriptions/${subscriptionId}/reactivate`
618
+ );
619
+ }
620
+ /**
621
+ * Check if a customer has an active subscription.
622
+ *
623
+ * @example
624
+ * ```typescript
625
+ * const isActive = await stackbe.subscriptions.isActive('cust_123');
626
+ * if (!isActive) {
627
+ * return res.redirect('/pricing');
628
+ * }
629
+ * ```
630
+ */
631
+ async isActive(customerId) {
632
+ const subscription = await this.get(customerId);
633
+ return subscription !== null && (subscription.status === "active" || subscription.status === "trialing");
634
+ }
635
+ };
636
+
637
+ // src/auth.ts
638
+ var AuthClient = class {
639
+ constructor(http, appId) {
640
+ this.http = http;
641
+ this.appId = appId;
642
+ }
643
+ /**
644
+ * Send a magic link email to a customer for passwordless authentication.
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * // Send magic link
649
+ * await stackbe.auth.sendMagicLink('user@example.com');
650
+ *
651
+ * // With redirect URL
652
+ * await stackbe.auth.sendMagicLink('user@example.com', {
653
+ * redirectUrl: 'https://myapp.com/dashboard',
654
+ * });
655
+ *
656
+ * // For localhost development
657
+ * await stackbe.auth.sendMagicLink('user@example.com', {
658
+ * useDev: true,
659
+ * });
660
+ * ```
661
+ */
662
+ async sendMagicLink(email, options = {}) {
663
+ return this.http.post(
664
+ `/v1/apps/${this.appId}/auth/magic-link`,
665
+ {
666
+ email,
667
+ redirectUrl: options.redirectUrl,
668
+ useDev: options.useDev
669
+ }
670
+ );
671
+ }
672
+ /**
673
+ * Verify a magic link token and get a session token.
674
+ * Call this when the user clicks the magic link.
675
+ *
676
+ * @example
677
+ * ```typescript
678
+ * // In your /verify route handler
679
+ * const { token } = req.query;
680
+ *
681
+ * const result = await stackbe.auth.verifyToken(token);
682
+ *
683
+ * if (result.valid) {
684
+ * // Set session cookie
685
+ * res.cookie('session', result.token, { httpOnly: true });
686
+ * res.redirect('/dashboard');
687
+ * } else {
688
+ * res.redirect('/login?error=invalid_token');
689
+ * }
690
+ * ```
691
+ */
692
+ async verifyToken(token) {
693
+ return this.http.post(
694
+ `/v1/apps/${this.appId}/auth/verify`,
695
+ { token }
696
+ );
697
+ }
698
+ /**
699
+ * Get the current session for an authenticated customer.
700
+ * Use this to validate session tokens and get customer data.
701
+ *
702
+ * @example
703
+ * ```typescript
704
+ * // Validate session on each request
705
+ * const sessionToken = req.cookies.session;
706
+ *
707
+ * const session = await stackbe.auth.getSession(sessionToken);
708
+ *
709
+ * if (session) {
710
+ * req.customer = session.customer;
711
+ * req.subscription = session.subscription;
712
+ * req.entitlements = session.entitlements;
713
+ * }
714
+ * ```
715
+ */
716
+ async getSession(sessionToken) {
717
+ try {
718
+ const response = await fetch(
719
+ `${this.http.baseUrl}/v1/apps/${this.appId}/auth/session?appId=${this.appId}`,
720
+ {
721
+ headers: {
722
+ "Authorization": `Bearer ${sessionToken}`,
723
+ "Content-Type": "application/json"
724
+ }
725
+ }
726
+ );
727
+ if (!response.ok) {
728
+ if (response.status === 401) {
729
+ return null;
730
+ }
731
+ throw new Error("Failed to get session");
732
+ }
733
+ return await response.json();
734
+ } catch {
735
+ return null;
736
+ }
737
+ }
738
+ /**
739
+ * Create Express middleware that validates session tokens.
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * // Protect routes with authentication
744
+ * app.use('/dashboard', stackbe.auth.middleware({
745
+ * getToken: (req) => req.cookies.session,
746
+ * onUnauthenticated: (req, res) => res.redirect('/login'),
747
+ * }));
748
+ *
749
+ * app.get('/dashboard', (req, res) => {
750
+ * // req.customer is available
751
+ * res.json({ email: req.customer.email });
752
+ * });
753
+ * ```
754
+ */
755
+ middleware(options) {
756
+ return async (req, res, next) => {
757
+ const token = options.getToken(req);
758
+ if (!token) {
759
+ if (options.onUnauthenticated) {
760
+ return options.onUnauthenticated(req, res);
761
+ }
762
+ return res.status(401).json({ error: "Not authenticated" });
763
+ }
764
+ const session = await this.getSession(token);
765
+ if (!session) {
766
+ if (options.onUnauthenticated) {
767
+ return options.onUnauthenticated(req, res);
768
+ }
769
+ return res.status(401).json({ error: "Invalid session" });
770
+ }
771
+ req.customer = session.customer;
772
+ req.subscription = session.subscription;
773
+ req.entitlements = session.entitlements;
774
+ next();
775
+ };
776
+ }
777
+ /**
778
+ * Check if a session token is valid.
779
+ *
780
+ * @example
781
+ * ```typescript
782
+ * const isValid = await stackbe.auth.isAuthenticated(sessionToken);
783
+ * ```
784
+ */
785
+ async isAuthenticated(sessionToken) {
786
+ const session = await this.getSession(sessionToken);
787
+ return session !== null;
788
+ }
789
+ };
790
+
412
791
  // src/client.ts
413
792
  var DEFAULT_BASE_URL = "https://api.stackbe.io";
414
793
  var DEFAULT_TIMEOUT = 3e4;
@@ -431,8 +810,18 @@ var StackBE = class {
431
810
  * // Check entitlements
432
811
  * const { hasAccess } = await stackbe.entitlements.check('customer_123', 'premium');
433
812
  *
434
- * // Get customer
435
- * const customer = await stackbe.customers.get('customer_123');
813
+ * // Create checkout session
814
+ * const { url } = await stackbe.checkout.createSession({
815
+ * customer: 'cust_123',
816
+ * planId: 'plan_pro',
817
+ * successUrl: 'https://myapp.com/success',
818
+ * });
819
+ *
820
+ * // Get subscription
821
+ * const subscription = await stackbe.subscriptions.get('cust_123');
822
+ *
823
+ * // Send magic link
824
+ * await stackbe.auth.sendMagicLink('user@example.com');
436
825
  * ```
437
826
  */
438
827
  constructor(config) {
@@ -442,6 +831,7 @@ var StackBE = class {
442
831
  if (!config.appId) {
443
832
  throw new StackBEError("appId is required", 400, "INVALID_CONFIG");
444
833
  }
834
+ this.appId = config.appId;
445
835
  this.http = new HttpClient({
446
836
  baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
447
837
  apiKey: config.apiKey,
@@ -451,6 +841,9 @@ var StackBE = class {
451
841
  this.usage = new UsageClient(this.http);
452
842
  this.entitlements = new EntitlementsClient(this.http);
453
843
  this.customers = new CustomersClient(this.http);
844
+ this.checkout = new CheckoutClient(this.http, config.appId);
845
+ this.subscriptions = new SubscriptionsClient(this.http);
846
+ this.auth = new AuthClient(this.http, config.appId);
454
847
  }
455
848
  /**
456
849
  * Create a middleware for Express that tracks usage automatically.
@@ -584,9 +977,12 @@ var StackBE = class {
584
977
  };
585
978
  // Annotate the CommonJS export names for ESM import in node:
586
979
  0 && (module.exports = {
980
+ AuthClient,
981
+ CheckoutClient,
587
982
  CustomersClient,
588
983
  EntitlementsClient,
589
984
  StackBE,
590
985
  StackBEError,
986
+ SubscriptionsClient,
591
987
  UsageClient
592
988
  });