@nextsparkjs/core 0.1.0-beta.100 → 0.1.0-beta.102
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/components/auth/forms/LoginForm.d.ts.map +1 -1
- package/dist/components/auth/forms/LoginForm.js +15 -10
- package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
- package/dist/components/auth/forms/SignupForm.js +27 -24
- package/dist/components/auth/pages/AuthErrorPage.d.ts +2 -0
- package/dist/components/auth/pages/AuthErrorPage.d.ts.map +1 -0
- package/dist/components/auth/pages/AuthErrorPage.js +44 -0
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +13 -3
- package/dist/lib/auth/registration-guard-plugin.d.ts +14 -0
- package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -0
- package/dist/lib/auth/registration-guard-plugin.js +37 -0
- package/dist/lib/auth/registration-helpers.d.ts +65 -0
- package/dist/lib/auth/registration-helpers.d.ts.map +1 -0
- package/dist/lib/auth/registration-helpers.js +51 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +54 -1
- package/dist/lib/billing/config-types.d.ts +5 -0
- package/dist/lib/billing/config-types.d.ts.map +1 -1
- package/dist/lib/billing/gateways/factory.d.ts +25 -0
- package/dist/lib/billing/gateways/factory.d.ts.map +1 -0
- package/dist/lib/billing/gateways/factory.js +34 -0
- package/dist/lib/billing/gateways/interface.d.ts +20 -0
- package/dist/lib/billing/gateways/interface.d.ts.map +1 -0
- package/dist/lib/billing/gateways/polar.d.ts +47 -0
- package/dist/lib/billing/gateways/polar.d.ts.map +1 -0
- package/dist/lib/billing/gateways/polar.js +150 -0
- package/dist/lib/billing/gateways/stripe.d.ts +40 -65
- package/dist/lib/billing/gateways/stripe.d.ts.map +1 -1
- package/dist/lib/billing/gateways/stripe.js +134 -62
- package/dist/lib/billing/gateways/types.d.ts +52 -0
- package/dist/lib/billing/gateways/types.d.ts.map +1 -0
- package/dist/lib/billing/gateways/types.js +0 -0
- package/dist/lib/billing/types.d.ts +1 -1
- package/dist/lib/billing/types.d.ts.map +1 -1
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +24 -0
- package/dist/lib/config/config-sync.d.ts +15 -0
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +15 -0
- package/dist/lib/config/types.d.ts +67 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/email/factory.d.ts.map +1 -1
- package/dist/lib/email/factory.js +0 -3
- package/dist/lib/media/utils.d.ts.map +1 -1
- package/dist/lib/services/plan.service.d.ts +6 -3
- package/dist/lib/services/plan.service.d.ts.map +1 -1
- package/dist/lib/services/plan.service.js +13 -4
- package/dist/lib/services/subscription.service.js +4 -4
- package/dist/lib/services/team.service.d.ts.map +1 -1
- package/dist/lib/services/team.service.js +1 -0
- package/dist/messages/en/auth.json +11 -0
- package/dist/messages/en/index.d.ts +11 -0
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/es/auth.json +11 -0
- package/dist/messages/es/index.d.ts +11 -0
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/migrations/090_sample_data.sql +1 -1
- package/dist/styles/classes.json +1 -1
- package/dist/templates/app/(auth)/auth-error/page.tsx +26 -0
- package/dist/templates/app/(auth)/signup/page.tsx +24 -2
- package/dist/templates/app/api/v1/billing/cancel/route.ts +5 -8
- package/dist/templates/app/api/v1/billing/checkout/route.ts +3 -3
- package/dist/templates/app/api/v1/billing/portal/route.ts +2 -2
- package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +410 -0
- package/dist/templates/contents/themes/starter/config/app.config.ts +21 -0
- package/migrations/090_sample_data.sql +1 -1
- package/package.json +39 -2
- package/scripts/build/registry/generators/billing-registry.mjs +6 -3
- package/templates/app/(auth)/auth-error/page.tsx +26 -0
- package/templates/app/(auth)/signup/page.tsx +24 -2
- package/templates/app/api/v1/billing/cancel/route.ts +5 -8
- package/templates/app/api/v1/billing/checkout/route.ts +3 -3
- package/templates/app/api/v1/billing/portal/route.ts +2 -2
- package/templates/app/api/v1/billing/webhooks/polar/route.ts +410 -0
- package/templates/contents/themes/starter/config/app.config.ts +21 -0
- /package/dist/lib/billing/gateways/{stripe.d.js → interface.js} +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BILLING_REGISTRY } from "@nextsparkjs/registries/billing-registry";
|
|
2
|
+
let gatewayInstance = null;
|
|
3
|
+
function getBillingGateway() {
|
|
4
|
+
if (!gatewayInstance) {
|
|
5
|
+
const provider = BILLING_REGISTRY.provider;
|
|
6
|
+
switch (provider) {
|
|
7
|
+
case "stripe": {
|
|
8
|
+
const { StripeGateway } = require("./stripe");
|
|
9
|
+
gatewayInstance = new StripeGateway();
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
case "polar": {
|
|
13
|
+
const { PolarGateway } = require("./polar");
|
|
14
|
+
gatewayInstance = new PolarGateway();
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
// Future providers:
|
|
18
|
+
// case 'paddle': { ... }
|
|
19
|
+
// case 'lemonsqueezy': { ... }
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Unsupported billing provider: "${provider}". Supported providers: stripe, polar. Check your billing.config.ts provider setting.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return gatewayInstance;
|
|
27
|
+
}
|
|
28
|
+
function resetBillingGateway() {
|
|
29
|
+
gatewayInstance = null;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
getBillingGateway,
|
|
33
|
+
resetBillingGateway
|
|
34
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing Gateway Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that all payment provider implementations must satisfy.
|
|
5
|
+
* Consumers interact with this interface via the factory (getBillingGateway()),
|
|
6
|
+
* making provider switching a configuration change rather than a code change.
|
|
7
|
+
*/
|
|
8
|
+
import type { CheckoutSessionResult, PortalSessionResult, SubscriptionResult, CustomerResult, WebhookEventResult, CreateCheckoutParams, CreatePortalParams, CreateCustomerParams, UpdateSubscriptionParams } from './types';
|
|
9
|
+
export interface BillingGateway {
|
|
10
|
+
createCheckoutSession(params: CreateCheckoutParams): Promise<CheckoutSessionResult>;
|
|
11
|
+
createPortalSession(params: CreatePortalParams): Promise<PortalSessionResult>;
|
|
12
|
+
getCustomer(customerId: string): Promise<CustomerResult>;
|
|
13
|
+
createCustomer(params: CreateCustomerParams): Promise<CustomerResult>;
|
|
14
|
+
updateSubscriptionPlan(params: UpdateSubscriptionParams): Promise<SubscriptionResult>;
|
|
15
|
+
cancelSubscriptionAtPeriodEnd(subscriptionId: string): Promise<SubscriptionResult>;
|
|
16
|
+
cancelSubscriptionImmediately(subscriptionId: string): Promise<SubscriptionResult>;
|
|
17
|
+
reactivateSubscription(subscriptionId: string): Promise<SubscriptionResult>;
|
|
18
|
+
verifyWebhookSignature(payload: string | Buffer, signatureOrHeaders: string | Record<string, string>): WebhookEventResult;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../../src/lib/billing/gateways/interface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,cAAc;IAE7B,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACnF,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAG7E,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACxD,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IAGrE,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACrF,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAClF,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAClF,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAG3E,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,kBAAkB,CAAA;CAC1H"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polar.sh Payment Gateway Implementation
|
|
3
|
+
*
|
|
4
|
+
* Implements BillingGateway interface for Polar.sh.
|
|
5
|
+
* Wraps Polar SDK types into provider-agnostic result types.
|
|
6
|
+
*
|
|
7
|
+
* Key differences from Stripe:
|
|
8
|
+
* - Checkout uses `products: [productId]` (Polar product IDs, not Stripe price IDs)
|
|
9
|
+
* - Cancel = "revoke" in Polar terminology (immediate)
|
|
10
|
+
* - Customer portal via "customer sessions" (not a hosted portal page)
|
|
11
|
+
* - Webhook verification requires ALL headers, not just a signature string
|
|
12
|
+
* - Uses `validateEvent` from @polar-sh/sdk/webhooks
|
|
13
|
+
*
|
|
14
|
+
* NOTE: In billing.config.ts, Polar's `providerPriceIds` should contain Polar **product IDs**
|
|
15
|
+
* (not price IDs). Polar associates prices with products, so the product ID is used for checkout
|
|
16
|
+
* and subscription plan changes.
|
|
17
|
+
*
|
|
18
|
+
* NOTE: Polar also offers @polar-sh/better-auth for direct Better Auth integration.
|
|
19
|
+
* That is an ALTERNATIVE approach to this gateway. If you want the simpler plugin-based
|
|
20
|
+
* approach (auto-create customer on signup, client-side checkout helpers), use the
|
|
21
|
+
* Better Auth plugin instead of this gateway. The two approaches are mutually exclusive
|
|
22
|
+
* for the same project - pick one.
|
|
23
|
+
*/
|
|
24
|
+
import { Polar } from '@polar-sh/sdk';
|
|
25
|
+
import type { BillingGateway } from './interface';
|
|
26
|
+
import type { CheckoutSessionResult, PortalSessionResult, SubscriptionResult, CustomerResult, WebhookEventResult, CreateCheckoutParams, CreatePortalParams, CreateCustomerParams, UpdateSubscriptionParams } from './types';
|
|
27
|
+
/**
|
|
28
|
+
* Polar.sh implementation of the BillingGateway interface.
|
|
29
|
+
* Maps Polar SDK types to provider-agnostic result types.
|
|
30
|
+
*/
|
|
31
|
+
export declare class PolarGateway implements BillingGateway {
|
|
32
|
+
createCheckoutSession(params: CreateCheckoutParams): Promise<CheckoutSessionResult>;
|
|
33
|
+
createPortalSession(params: CreatePortalParams): Promise<PortalSessionResult>;
|
|
34
|
+
verifyWebhookSignature(payload: string | Buffer, signatureOrHeaders: string | Record<string, string>): WebhookEventResult;
|
|
35
|
+
getCustomer(customerId: string): Promise<CustomerResult>;
|
|
36
|
+
createCustomer(params: CreateCustomerParams): Promise<CustomerResult>;
|
|
37
|
+
updateSubscriptionPlan(params: UpdateSubscriptionParams): Promise<SubscriptionResult>;
|
|
38
|
+
cancelSubscriptionAtPeriodEnd(subscriptionId: string): Promise<SubscriptionResult>;
|
|
39
|
+
cancelSubscriptionImmediately(subscriptionId: string): Promise<SubscriptionResult>;
|
|
40
|
+
reactivateSubscription(subscriptionId: string): Promise<SubscriptionResult>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get raw Polar instance for advanced usage (e.g., webhook handlers that need full types).
|
|
44
|
+
* Prefer using getBillingGateway() for all other operations.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getPolarInstance(): Polar;
|
|
47
|
+
//# sourceMappingURL=polar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polar.d.ts","sourceRoot":"","sources":["../../../../src/lib/billing/gateways/polar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAKrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,SAAS,CAAA;AAiBhB;;;GAGG;AACH,qBAAa,YAAa,YAAW,cAAc;IAC3C,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA0BnF,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAcnF,sBAAsB,CACpB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClD,kBAAkB;IA4Bf,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAUxD,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAsBrE,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAkBrF,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiBlF,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAalF,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAelF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,KAAK,CAExC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Polar } from "@polar-sh/sdk";
|
|
2
|
+
import { validateEvent, WebhookVerificationError } from "@polar-sh/sdk/webhooks";
|
|
3
|
+
import { PlanService } from "../../services/plan.service.js";
|
|
4
|
+
let polarInstance = null;
|
|
5
|
+
function getPolar() {
|
|
6
|
+
if (!polarInstance) {
|
|
7
|
+
if (!process.env.POLAR_ACCESS_TOKEN) {
|
|
8
|
+
throw new Error("POLAR_ACCESS_TOKEN is not configured");
|
|
9
|
+
}
|
|
10
|
+
polarInstance = new Polar({
|
|
11
|
+
accessToken: process.env.POLAR_ACCESS_TOKEN
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return polarInstance;
|
|
15
|
+
}
|
|
16
|
+
class PolarGateway {
|
|
17
|
+
async createCheckoutSession(params) {
|
|
18
|
+
const { teamId, planSlug, billingPeriod, successUrl, cancelUrl, customerEmail } = params;
|
|
19
|
+
const productId = PlanService.getPriceId(planSlug, billingPeriod);
|
|
20
|
+
if (!productId) {
|
|
21
|
+
throw new Error(`No product ID configured for ${planSlug} ${billingPeriod}`);
|
|
22
|
+
}
|
|
23
|
+
const checkoutParams = {
|
|
24
|
+
products: [productId],
|
|
25
|
+
successUrl,
|
|
26
|
+
metadata: { teamId, planSlug, billingPeriod },
|
|
27
|
+
...customerEmail && { customerEmail },
|
|
28
|
+
// Polar uses returnUrl for back navigation (equivalent to Stripe's cancel_url)
|
|
29
|
+
...cancelUrl && { returnUrl: cancelUrl }
|
|
30
|
+
};
|
|
31
|
+
const result = await getPolar().checkouts.create(checkoutParams);
|
|
32
|
+
return {
|
|
33
|
+
id: result.id,
|
|
34
|
+
url: result.url ?? null
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async createPortalSession(params) {
|
|
38
|
+
const { customerId, returnUrl } = params;
|
|
39
|
+
const result = await getPolar().customerSessions.create({
|
|
40
|
+
customerId
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
url: result.customerPortalUrl
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
verifyWebhookSignature(payload, signatureOrHeaders) {
|
|
47
|
+
const webhookSecret = process.env.POLAR_WEBHOOK_SECRET;
|
|
48
|
+
if (!webhookSecret) {
|
|
49
|
+
throw new Error("POLAR_WEBHOOK_SECRET is not configured");
|
|
50
|
+
}
|
|
51
|
+
const headers = typeof signatureOrHeaders === "string" ? { "webhook-id": "", "webhook-timestamp": "", "webhook-signature": signatureOrHeaders } : signatureOrHeaders;
|
|
52
|
+
const body = typeof payload === "string" ? payload : payload.toString("utf-8");
|
|
53
|
+
try {
|
|
54
|
+
const event = validateEvent(body, headers, webhookSecret);
|
|
55
|
+
return {
|
|
56
|
+
id: event.id || crypto.randomUUID(),
|
|
57
|
+
type: event.type,
|
|
58
|
+
data: event.data
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof WebhookVerificationError) {
|
|
62
|
+
throw new Error(`Polar webhook verification failed: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async getCustomer(customerId) {
|
|
68
|
+
const customer = await getPolar().customers.get({ id: customerId });
|
|
69
|
+
return {
|
|
70
|
+
id: customer.id,
|
|
71
|
+
email: customer.email ?? null,
|
|
72
|
+
name: customer.name ?? null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async createCustomer(params) {
|
|
76
|
+
const { email, name, metadata } = params;
|
|
77
|
+
const customerParams = {
|
|
78
|
+
email,
|
|
79
|
+
...name && { name },
|
|
80
|
+
...metadata && { metadata },
|
|
81
|
+
// Add organization ID if available
|
|
82
|
+
...process.env.POLAR_ORGANIZATION_ID && {
|
|
83
|
+
organizationId: process.env.POLAR_ORGANIZATION_ID
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const customer = await getPolar().customers.create(customerParams);
|
|
87
|
+
return {
|
|
88
|
+
id: customer.id,
|
|
89
|
+
email: customer.email ?? null,
|
|
90
|
+
name: customer.name ?? null
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async updateSubscriptionPlan(params) {
|
|
94
|
+
const { subscriptionId, newPriceId } = params;
|
|
95
|
+
const result = await getPolar().subscriptions.update({
|
|
96
|
+
id: subscriptionId,
|
|
97
|
+
subscriptionUpdate: {
|
|
98
|
+
productId: newPriceId
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
id: result.id,
|
|
103
|
+
status: result.status,
|
|
104
|
+
cancelAtPeriodEnd: result.cancelAtPeriodEnd ?? false
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async cancelSubscriptionAtPeriodEnd(subscriptionId) {
|
|
108
|
+
const result = await getPolar().subscriptions.update({
|
|
109
|
+
id: subscriptionId,
|
|
110
|
+
subscriptionUpdate: {
|
|
111
|
+
cancelAtPeriodEnd: true
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
id: result.id,
|
|
116
|
+
status: result.status,
|
|
117
|
+
cancelAtPeriodEnd: true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async cancelSubscriptionImmediately(subscriptionId) {
|
|
121
|
+
const result = await getPolar().subscriptions.revoke({
|
|
122
|
+
id: subscriptionId
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
id: result.id,
|
|
126
|
+
status: result.status ?? "canceled",
|
|
127
|
+
cancelAtPeriodEnd: false
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async reactivateSubscription(subscriptionId) {
|
|
131
|
+
const result = await getPolar().subscriptions.update({
|
|
132
|
+
id: subscriptionId,
|
|
133
|
+
subscriptionUpdate: {
|
|
134
|
+
cancelAtPeriodEnd: false
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
id: result.id,
|
|
139
|
+
status: result.status,
|
|
140
|
+
cancelAtPeriodEnd: false
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function getPolarInstance() {
|
|
145
|
+
return getPolar();
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
PolarGateway,
|
|
149
|
+
getPolarInstance
|
|
150
|
+
};
|
|
@@ -1,76 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stripe Payment Gateway
|
|
2
|
+
* Stripe Payment Gateway Implementation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Implements BillingGateway interface for Stripe.
|
|
5
|
+
* Wraps Stripe SDK types into provider-agnostic result types.
|
|
6
6
|
*
|
|
7
7
|
* P2: Stripe Integration
|
|
8
8
|
*/
|
|
9
9
|
import Stripe from 'stripe';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
import type { BillingGateway } from './interface';
|
|
11
|
+
import type { CheckoutSessionResult, PortalSessionResult, SubscriptionResult, CustomerResult, WebhookEventResult, CreateCheckoutParams, CreatePortalParams, CreateCustomerParams, UpdateSubscriptionParams } from './types';
|
|
12
|
+
export type { CreateCheckoutParams, CreatePortalParams, UpdateSubscriptionParams } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Stripe implementation of the BillingGateway interface.
|
|
15
|
+
* Maps Stripe SDK types to provider-agnostic result types.
|
|
16
|
+
*/
|
|
17
|
+
export declare class StripeGateway implements BillingGateway {
|
|
18
|
+
createCheckoutSession(params: CreateCheckoutParams): Promise<CheckoutSessionResult>;
|
|
19
|
+
createPortalSession(params: CreatePortalParams): Promise<PortalSessionResult>;
|
|
20
|
+
verifyWebhookSignature(payload: string | Buffer, signatureOrHeaders: string | Record<string, string>): WebhookEventResult;
|
|
21
|
+
getCustomer(customerId: string): Promise<CustomerResult>;
|
|
22
|
+
createCustomer(params: CreateCustomerParams): Promise<CustomerResult>;
|
|
23
|
+
updateSubscriptionPlan(params: UpdateSubscriptionParams): Promise<SubscriptionResult>;
|
|
24
|
+
cancelSubscriptionAtPeriodEnd(subscriptionId: string): Promise<SubscriptionResult>;
|
|
25
|
+
cancelSubscriptionImmediately(subscriptionId: string): Promise<SubscriptionResult>;
|
|
26
|
+
reactivateSubscription(subscriptionId: string): Promise<SubscriptionResult>;
|
|
18
27
|
}
|
|
19
|
-
export interface CreatePortalParams {
|
|
20
|
-
customerId: string;
|
|
21
|
-
returnUrl: string;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Create Stripe Checkout session for subscription
|
|
25
|
-
*/
|
|
26
|
-
export declare function createCheckoutSession(params: CreateCheckoutParams): Promise<Stripe.Checkout.Session>;
|
|
27
|
-
/**
|
|
28
|
-
* Create Stripe Customer Portal session for self-service billing
|
|
29
|
-
*/
|
|
30
|
-
export declare function createPortalSession(params: CreatePortalParams): Promise<Stripe.BillingPortal.Session>;
|
|
31
|
-
/**
|
|
32
|
-
* Verify Stripe webhook signature (MANDATORY for security)
|
|
33
|
-
*/
|
|
34
|
-
export declare function verifyWebhookSignature(payload: string | Buffer, signature: string): Stripe.Event;
|
|
35
|
-
/**
|
|
36
|
-
* Get Stripe customer by ID
|
|
37
|
-
*/
|
|
38
|
-
export declare function getCustomer(customerId: string): Promise<Stripe.Customer>;
|
|
39
|
-
/**
|
|
40
|
-
* Create Stripe customer
|
|
41
|
-
*/
|
|
42
|
-
export declare function createCustomer(params: {
|
|
43
|
-
email: string;
|
|
44
|
-
name?: string;
|
|
45
|
-
metadata?: Record<string, string>;
|
|
46
|
-
}): Promise<Stripe.Customer>;
|
|
47
28
|
/**
|
|
48
|
-
* Get Stripe instance for advanced usage (
|
|
29
|
+
* Get raw Stripe instance for advanced usage (webhook handlers that need Stripe.Event types).
|
|
30
|
+
* Prefer using getBillingGateway() for all other operations.
|
|
49
31
|
*/
|
|
50
32
|
export declare function getStripeInstance(): Stripe;
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*/
|
|
60
|
-
export declare function
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
prorationBehavior?: 'create_prorations' | 'none' | 'always_invoice';
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Update subscription to a new plan/price
|
|
73
|
-
* Stripe handles proration automatically based on prorationBehavior
|
|
74
|
-
*/
|
|
75
|
-
export declare function updateSubscriptionPlan(params: UpdateSubscriptionParams): Promise<Stripe.Subscription>;
|
|
33
|
+
/** @deprecated Use getBillingGateway().createCheckoutSession() instead */
|
|
34
|
+
export declare function createCheckoutSession(params: CreateCheckoutParams): Promise<CheckoutSessionResult>;
|
|
35
|
+
/** @deprecated Use getBillingGateway().createPortalSession() instead */
|
|
36
|
+
export declare function createPortalSession(params: CreatePortalParams): Promise<PortalSessionResult>;
|
|
37
|
+
/** @deprecated Use getBillingGateway().verifyWebhookSignature() instead */
|
|
38
|
+
export declare function verifyWebhookSignature(payload: string | Buffer, signatureOrHeaders: string | Record<string, string>): WebhookEventResult;
|
|
39
|
+
/** @deprecated Use getBillingGateway().getCustomer() instead */
|
|
40
|
+
export declare function getCustomer(customerId: string): Promise<CustomerResult>;
|
|
41
|
+
/** @deprecated Use getBillingGateway().createCustomer() instead */
|
|
42
|
+
export declare function createCustomer(params: CreateCustomerParams): Promise<CustomerResult>;
|
|
43
|
+
/** @deprecated Use getBillingGateway().cancelSubscriptionAtPeriodEnd() instead */
|
|
44
|
+
export declare function cancelSubscriptionAtPeriodEnd(subscriptionId: string): Promise<SubscriptionResult>;
|
|
45
|
+
/** @deprecated Use getBillingGateway().cancelSubscriptionImmediately() instead */
|
|
46
|
+
export declare function cancelSubscriptionImmediately(subscriptionId: string): Promise<SubscriptionResult>;
|
|
47
|
+
/** @deprecated Use getBillingGateway().reactivateSubscription() instead */
|
|
48
|
+
export declare function reactivateSubscription(subscriptionId: string): Promise<SubscriptionResult>;
|
|
49
|
+
/** @deprecated Use getBillingGateway().updateSubscriptionPlan() instead */
|
|
50
|
+
export declare function updateSubscriptionPlan(params: UpdateSubscriptionParams): Promise<SubscriptionResult>;
|
|
76
51
|
//# sourceMappingURL=stripe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stripe.d.ts","sourceRoot":"","sources":["../../../../src/lib/billing/gateways/stripe.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"stripe.d.ts","sourceRoot":"","sources":["../../../../src/lib/billing/gateways/stripe.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,SAAS,CAAA;AAGhB,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAkBjG;;;GAGG;AACH,qBAAa,aAAc,YAAW,cAAc;IAC5C,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA6CnF,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAQnF,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,kBAAkB;IAiBnH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAYxD,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IASrE,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA2BrF,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAWlF,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IASlF,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAUlF;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAQD,0EAA0E;AAC1E,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,kCAEvE;AAED,wEAAwE;AACxE,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,gCAEnE;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,sBAEnH;AAED,gEAAgE;AAChE,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,2BAEnD;AAED,mEAAmE;AACnE,wBAAsB,cAAc,CAAC,MAAM,EAAE,oBAAoB,2BAEhE;AAED,kFAAkF;AAClF,wBAAsB,6BAA6B,CAAC,cAAc,EAAE,MAAM,+BAEzE;AAED,kFAAkF;AAClF,wBAAsB,6BAA6B,CAAC,cAAc,EAAE,MAAM,+BAEzE;AAED,2EAA2E;AAC3E,wBAAsB,sBAAsB,CAAC,cAAc,EAAE,MAAM,+BAElE;AAED,2EAA2E;AAC3E,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,+BAE5E"}
|
|
@@ -13,90 +13,162 @@ function getStripe() {
|
|
|
13
13
|
}
|
|
14
14
|
return stripeInstance;
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
class StripeGateway {
|
|
17
|
+
async createCheckoutSession(params) {
|
|
18
|
+
const { teamId, planSlug, billingPeriod, successUrl, cancelUrl, customerEmail, customerId } = params;
|
|
19
|
+
const planConfig = BILLING_REGISTRY.plans.find((p) => p.slug === planSlug);
|
|
20
|
+
if (!planConfig) {
|
|
21
|
+
throw new Error(`Plan ${planSlug} not found in BILLING_REGISTRY`);
|
|
22
|
+
}
|
|
23
|
+
const priceId = billingPeriod === "yearly" ? planConfig.stripePriceIdYearly : planConfig.stripePriceIdMonthly;
|
|
24
|
+
if (!priceId) {
|
|
25
|
+
throw new Error(`No Stripe price configured for ${planSlug} ${billingPeriod}`);
|
|
26
|
+
}
|
|
27
|
+
const sessionParams = {
|
|
28
|
+
mode: "subscription",
|
|
29
|
+
payment_method_types: ["card"],
|
|
30
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
31
|
+
success_url: successUrl,
|
|
32
|
+
cancel_url: cancelUrl,
|
|
33
|
+
metadata: { teamId, planSlug, billingPeriod },
|
|
34
|
+
client_reference_id: teamId
|
|
35
|
+
};
|
|
36
|
+
if (customerId) {
|
|
37
|
+
sessionParams.customer = customerId;
|
|
38
|
+
} else if (customerEmail) {
|
|
39
|
+
sessionParams.customer_email = customerEmail;
|
|
40
|
+
}
|
|
41
|
+
if (planConfig.trialDays && planConfig.trialDays > 0 && !customerId) {
|
|
42
|
+
sessionParams.subscription_data = {
|
|
43
|
+
trial_period_days: planConfig.trialDays
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const session = await getStripe().checkout.sessions.create(sessionParams);
|
|
47
|
+
return { id: session.id, url: session.url };
|
|
48
|
+
}
|
|
49
|
+
async createPortalSession(params) {
|
|
50
|
+
const session = await getStripe().billingPortal.sessions.create({
|
|
51
|
+
customer: params.customerId,
|
|
52
|
+
return_url: params.returnUrl
|
|
53
|
+
});
|
|
54
|
+
return { url: session.url };
|
|
55
|
+
}
|
|
56
|
+
verifyWebhookSignature(payload, signatureOrHeaders) {
|
|
57
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
58
|
+
if (!webhookSecret) {
|
|
59
|
+
throw new Error("STRIPE_WEBHOOK_SECRET is not configured");
|
|
60
|
+
}
|
|
61
|
+
const signature = typeof signatureOrHeaders === "string" ? signatureOrHeaders : signatureOrHeaders["stripe-signature"] || "";
|
|
62
|
+
const event = getStripe().webhooks.constructEvent(payload, signature, webhookSecret);
|
|
63
|
+
return {
|
|
64
|
+
id: event.id,
|
|
65
|
+
type: event.type,
|
|
66
|
+
data: event.data
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async getCustomer(customerId) {
|
|
70
|
+
const customer = await getStripe().customers.retrieve(customerId);
|
|
71
|
+
if ("deleted" in customer && customer.deleted) {
|
|
72
|
+
throw new Error(`Customer ${customerId} has been deleted`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
id: customer.id,
|
|
76
|
+
email: customer.email ?? null,
|
|
77
|
+
name: customer.name ?? null
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async createCustomer(params) {
|
|
81
|
+
const customer = await getStripe().customers.create(params);
|
|
82
|
+
return {
|
|
83
|
+
id: customer.id,
|
|
84
|
+
email: customer.email ?? null,
|
|
85
|
+
name: customer.name ?? null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async updateSubscriptionPlan(params) {
|
|
89
|
+
var _a;
|
|
90
|
+
const { subscriptionId, newPriceId, prorationBehavior = "create_prorations" } = params;
|
|
91
|
+
const stripe = getStripe();
|
|
92
|
+
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
|
|
93
|
+
const itemId = (_a = subscription.items.data[0]) == null ? void 0 : _a.id;
|
|
94
|
+
if (!itemId) {
|
|
95
|
+
throw new Error("Subscription has no items");
|
|
96
|
+
}
|
|
97
|
+
const updated = await stripe.subscriptions.update(subscriptionId, {
|
|
98
|
+
items: [{
|
|
99
|
+
id: itemId,
|
|
100
|
+
price: newPriceId
|
|
101
|
+
}],
|
|
102
|
+
proration_behavior: prorationBehavior
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
id: updated.id,
|
|
106
|
+
status: updated.status,
|
|
107
|
+
cancelAtPeriodEnd: updated.cancel_at_period_end
|
|
108
|
+
};
|
|
21
109
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
110
|
+
async cancelSubscriptionAtPeriodEnd(subscriptionId) {
|
|
111
|
+
const updated = await getStripe().subscriptions.update(subscriptionId, {
|
|
112
|
+
cancel_at_period_end: true
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
id: updated.id,
|
|
116
|
+
status: updated.status,
|
|
117
|
+
cancelAtPeriodEnd: updated.cancel_at_period_end
|
|
118
|
+
};
|
|
25
119
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
client_reference_id: teamId
|
|
34
|
-
};
|
|
35
|
-
if (customerId) {
|
|
36
|
-
sessionParams.customer = customerId;
|
|
37
|
-
} else if (customerEmail) {
|
|
38
|
-
sessionParams.customer_email = customerEmail;
|
|
120
|
+
async cancelSubscriptionImmediately(subscriptionId) {
|
|
121
|
+
const canceled = await getStripe().subscriptions.cancel(subscriptionId);
|
|
122
|
+
return {
|
|
123
|
+
id: canceled.id,
|
|
124
|
+
status: canceled.status,
|
|
125
|
+
cancelAtPeriodEnd: canceled.cancel_at_period_end
|
|
126
|
+
};
|
|
39
127
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
128
|
+
async reactivateSubscription(subscriptionId) {
|
|
129
|
+
const updated = await getStripe().subscriptions.update(subscriptionId, {
|
|
130
|
+
cancel_at_period_end: false
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
id: updated.id,
|
|
134
|
+
status: updated.status,
|
|
135
|
+
cancelAtPeriodEnd: updated.cancel_at_period_end
|
|
43
136
|
};
|
|
44
137
|
}
|
|
45
|
-
|
|
138
|
+
}
|
|
139
|
+
function getStripeInstance() {
|
|
140
|
+
return getStripe();
|
|
141
|
+
}
|
|
142
|
+
const _defaultGateway = new StripeGateway();
|
|
143
|
+
async function createCheckoutSession(params) {
|
|
144
|
+
return _defaultGateway.createCheckoutSession(params);
|
|
46
145
|
}
|
|
47
146
|
async function createPortalSession(params) {
|
|
48
|
-
return
|
|
49
|
-
customer: params.customerId,
|
|
50
|
-
return_url: params.returnUrl
|
|
51
|
-
});
|
|
147
|
+
return _defaultGateway.createPortalSession(params);
|
|
52
148
|
}
|
|
53
|
-
function verifyWebhookSignature(payload,
|
|
54
|
-
|
|
55
|
-
if (!webhookSecret) {
|
|
56
|
-
throw new Error("STRIPE_WEBHOOK_SECRET is not configured");
|
|
57
|
-
}
|
|
58
|
-
return getStripe().webhooks.constructEvent(payload, signature, webhookSecret);
|
|
149
|
+
function verifyWebhookSignature(payload, signatureOrHeaders) {
|
|
150
|
+
return _defaultGateway.verifyWebhookSignature(payload, signatureOrHeaders);
|
|
59
151
|
}
|
|
60
152
|
async function getCustomer(customerId) {
|
|
61
|
-
return
|
|
153
|
+
return _defaultGateway.getCustomer(customerId);
|
|
62
154
|
}
|
|
63
155
|
async function createCustomer(params) {
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
function getStripeInstance() {
|
|
67
|
-
return getStripe();
|
|
156
|
+
return _defaultGateway.createCustomer(params);
|
|
68
157
|
}
|
|
69
158
|
async function cancelSubscriptionAtPeriodEnd(subscriptionId) {
|
|
70
|
-
return
|
|
71
|
-
cancel_at_period_end: true
|
|
72
|
-
});
|
|
159
|
+
return _defaultGateway.cancelSubscriptionAtPeriodEnd(subscriptionId);
|
|
73
160
|
}
|
|
74
161
|
async function cancelSubscriptionImmediately(subscriptionId) {
|
|
75
|
-
return
|
|
162
|
+
return _defaultGateway.cancelSubscriptionImmediately(subscriptionId);
|
|
76
163
|
}
|
|
77
164
|
async function reactivateSubscription(subscriptionId) {
|
|
78
|
-
return
|
|
79
|
-
cancel_at_period_end: false
|
|
80
|
-
});
|
|
165
|
+
return _defaultGateway.reactivateSubscription(subscriptionId);
|
|
81
166
|
}
|
|
82
167
|
async function updateSubscriptionPlan(params) {
|
|
83
|
-
|
|
84
|
-
const { subscriptionId, newPriceId, prorationBehavior = "create_prorations" } = params;
|
|
85
|
-
const stripe = getStripe();
|
|
86
|
-
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
|
|
87
|
-
const itemId = (_a = subscription.items.data[0]) == null ? void 0 : _a.id;
|
|
88
|
-
if (!itemId) {
|
|
89
|
-
throw new Error("Subscription has no items");
|
|
90
|
-
}
|
|
91
|
-
return stripe.subscriptions.update(subscriptionId, {
|
|
92
|
-
items: [{
|
|
93
|
-
id: itemId,
|
|
94
|
-
price: newPriceId
|
|
95
|
-
}],
|
|
96
|
-
proration_behavior: prorationBehavior
|
|
97
|
-
});
|
|
168
|
+
return _defaultGateway.updateSubscriptionPlan(params);
|
|
98
169
|
}
|
|
99
170
|
export {
|
|
171
|
+
StripeGateway,
|
|
100
172
|
cancelSubscriptionAtPeriodEnd,
|
|
101
173
|
cancelSubscriptionImmediately,
|
|
102
174
|
createCheckoutSession,
|