@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.d.mts +339 -3
- package/dist/index.d.ts +339 -3
- package/dist/index.js +398 -2
- package/dist/index.mjs +395 -2
- package/package.json +1 -1
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
|
-
* //
|
|
435
|
-
* const
|
|
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
|
});
|