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