Implement new billing-gateway and update related services
Created a new package named billing-gateway which implements interfaces for different billing providers and provides a centralized service for payments. This will potentially help to maintain cleaner code by reducing direct dependencies on specific payment providers in the core application code. Additionally, made adjustments in existing services, like Stripe, to comply with this change. The relevant interfaces and types have been exported and imported accordingly.
This commit is contained in:
1
packages/stripe/src/components/index.ts
Normal file
1
packages/stripe/src/components/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './stripe-embedded-checkout';
|
||||
86
packages/stripe/src/components/stripe-embedded-checkout.tsx
Normal file
86
packages/stripe/src/components/stripe-embedded-checkout.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
EmbeddedCheckout,
|
||||
EmbeddedCheckoutProvider,
|
||||
} from '@stripe/react-stripe-js';
|
||||
import { loadStripe } from '@stripe/stripe-js';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@kit/ui/dialog';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
const STRIPE_PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
|
||||
|
||||
if (!STRIPE_PUBLISHABLE_KEY) {
|
||||
throw new Error(
|
||||
'Missing NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY environment variable. Did you forget to add it to your .env file?',
|
||||
);
|
||||
}
|
||||
|
||||
const stripePromise = loadStripe(STRIPE_PUBLISHABLE_KEY);
|
||||
|
||||
export function StripeCheckout({
|
||||
checkoutToken,
|
||||
onClose,
|
||||
}: React.PropsWithChildren<{
|
||||
checkoutToken: string;
|
||||
onClose?: () => void;
|
||||
}>) {
|
||||
return (
|
||||
<EmbeddedCheckoutPopup key={checkoutToken} onClose={onClose}>
|
||||
<EmbeddedCheckoutProvider
|
||||
stripe={stripePromise}
|
||||
options={{ clientSecret: checkoutToken }}
|
||||
>
|
||||
<EmbeddedCheckout className={'EmbeddedCheckoutClassName'} />
|
||||
</EmbeddedCheckoutProvider>
|
||||
</EmbeddedCheckoutPopup>
|
||||
);
|
||||
}
|
||||
|
||||
function EmbeddedCheckoutPopup({
|
||||
onClose,
|
||||
children,
|
||||
}: React.PropsWithChildren<{
|
||||
onClose?: () => void;
|
||||
}>) {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
const className = cn({
|
||||
[`bg-white p-4 max-h-[98vh] overflow-y-auto shadow-transparent border border-gray-200 dark:border-dark-700`]:
|
||||
true,
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
defaultOpen
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open && onClose) {
|
||||
onClose();
|
||||
}
|
||||
|
||||
setOpen(open);
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Complete your purchase</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogContent
|
||||
className={className}
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<div>{children}</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,7 @@
|
||||
import type { Stripe } from 'stripe';
|
||||
import { z } from 'zod';
|
||||
|
||||
export interface CreateStripeCheckoutParams {
|
||||
returnUrl: string;
|
||||
organizationUid: string;
|
||||
priceId: string;
|
||||
customerId?: string;
|
||||
trialPeriodDays?: number | undefined;
|
||||
customerEmail?: string;
|
||||
embedded: boolean;
|
||||
}
|
||||
import { CreateBillingCheckoutSchema } from '@kit/billing/schema';
|
||||
|
||||
/**
|
||||
* @name createStripeCheckout
|
||||
@@ -16,43 +9,43 @@ export interface CreateStripeCheckoutParams {
|
||||
* containing the session, which you can use to redirect the user to the
|
||||
* checkout page
|
||||
*/
|
||||
export default async function createStripeCheckout(
|
||||
export async function createStripeCheckout(
|
||||
stripe: Stripe,
|
||||
params: CreateStripeCheckoutParams,
|
||||
params: z.infer<typeof CreateBillingCheckoutSchema>,
|
||||
) {
|
||||
// in MakerKit, a subscription belongs to an organization,
|
||||
// rather than to a user
|
||||
// if you wish to change it, use the current user ID instead
|
||||
const clientReferenceId = params.organizationUid;
|
||||
const clientReferenceId = params.accountId;
|
||||
|
||||
// we pass an optional customer ID, so we do not duplicate the Stripe
|
||||
// customers if an organization subscribes multiple times
|
||||
const customer = params.customerId ?? undefined;
|
||||
|
||||
// if it's a one-time payment
|
||||
// you should change this to "payment"
|
||||
// docs: https://stripe.com/docs/billing/subscriptions/build-subscription
|
||||
const mode: Stripe.Checkout.SessionCreateParams.Mode = 'subscription';
|
||||
const mode: Stripe.Checkout.SessionCreateParams.Mode =
|
||||
params.paymentType === 'recurring' ? 'subscription' : 'payment';
|
||||
|
||||
// TODO: support multiple line items and per-seat pricing
|
||||
const lineItem: Stripe.Checkout.SessionCreateParams.LineItem = {
|
||||
quantity: 1,
|
||||
price: params.priceId,
|
||||
price: params.planId,
|
||||
};
|
||||
|
||||
const subscriptionData: Stripe.Checkout.SessionCreateParams.SubscriptionData =
|
||||
{
|
||||
trial_period_days: params.trialPeriodDays,
|
||||
metadata: {
|
||||
organizationUid: params.organizationUid,
|
||||
accountId: params.accountId,
|
||||
},
|
||||
};
|
||||
|
||||
const urls = getUrls({
|
||||
embedded: params.embedded,
|
||||
returnUrl: params.returnUrl,
|
||||
});
|
||||
|
||||
const uiMode = params.embedded ? 'embedded' : 'hosted';
|
||||
// we use the embedded mode, so the user does not leave the page
|
||||
const uiMode = 'embedded';
|
||||
|
||||
const customerData = customer
|
||||
? {
|
||||
@@ -66,24 +59,17 @@ export default async function createStripeCheckout(
|
||||
mode,
|
||||
ui_mode: uiMode,
|
||||
line_items: [lineItem],
|
||||
client_reference_id: clientReferenceId.toString(),
|
||||
client_reference_id: clientReferenceId,
|
||||
subscription_data: subscriptionData,
|
||||
...customerData,
|
||||
...urls,
|
||||
});
|
||||
}
|
||||
|
||||
function getUrls(params: { returnUrl: string; embedded?: boolean }) {
|
||||
const successUrl = `${params.returnUrl}?success=true`;
|
||||
const cancelUrl = `${params.returnUrl}?cancel=true`;
|
||||
const returnUrl = `${params.returnUrl}/return?session_id={CHECKOUT_SESSION_ID}`;
|
||||
function getUrls(params: { returnUrl: string }) {
|
||||
const returnUrl = `${params.returnUrl}?session_id={CHECKOUT_SESSION_ID}`;
|
||||
|
||||
return params.embedded
|
||||
? {
|
||||
return_url: returnUrl,
|
||||
}
|
||||
: {
|
||||
success_url: successUrl,
|
||||
cancel_url: cancelUrl,
|
||||
};
|
||||
return {
|
||||
return_url: returnUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Stripe } from 'stripe';
|
||||
import { z } from 'zod';
|
||||
|
||||
export interface CreateBillingPortalSessionParams {
|
||||
customerId: string;
|
||||
returnUrl: string;
|
||||
}
|
||||
import { CreateBillingPortalSessionSchema } from '@kit/billing/schema';
|
||||
|
||||
/**
|
||||
* @name createStripeBillingPortalSession
|
||||
@@ -11,7 +9,7 @@ export interface CreateBillingPortalSessionParams {
|
||||
*/
|
||||
export async function createStripeBillingPortalSession(
|
||||
stripe: Stripe,
|
||||
params: CreateBillingPortalSessionParams,
|
||||
params: z.infer<typeof CreateBillingPortalSessionSchema>,
|
||||
) {
|
||||
return stripe.billingPortal.sessions.create({
|
||||
customer: params.customerId,
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { stripe } from './stripe.service';
|
||||
export { StripeBillingStrategyService } from './stripe.service';
|
||||
|
||||
@@ -1,37 +1,59 @@
|
||||
import 'server-only';
|
||||
import type { Stripe } from 'stripe';
|
||||
import { z } from 'zod';
|
||||
|
||||
import createStripeCheckout, {
|
||||
CreateStripeCheckoutParams,
|
||||
} from './create-checkout';
|
||||
import { BillingStrategyProviderService } from '@kit/billing';
|
||||
import {
|
||||
CreateBillingPortalSessionParams,
|
||||
createStripeBillingPortalSession,
|
||||
} from './create-stripe-billing-portal-session';
|
||||
CancelSubscriptionParamsSchema,
|
||||
CreateBillingCheckoutSchema,
|
||||
CreateBillingPortalSessionSchema,
|
||||
RetrieveCheckoutSessionSchema,
|
||||
} from '@kit/billing/schema';
|
||||
|
||||
import { createStripeCheckout } from './create-checkout';
|
||||
import { createStripeBillingPortalSession } from './create-stripe-billing-portal-session';
|
||||
import { createStripeClient } from './stripe-sdk';
|
||||
|
||||
class StripeService {
|
||||
constructor(private readonly stripeProvider: () => Promise<Stripe>) {}
|
||||
|
||||
async createCheckout(params: CreateStripeCheckoutParams) {
|
||||
export class StripeBillingStrategyService
|
||||
implements BillingStrategyProviderService
|
||||
{
|
||||
async createCheckoutSession(
|
||||
params: z.infer<typeof CreateBillingCheckoutSchema>,
|
||||
) {
|
||||
const stripe = await this.stripeProvider();
|
||||
|
||||
return createStripeCheckout(stripe, params);
|
||||
}
|
||||
|
||||
async createBillingPortalSession(params: CreateBillingPortalSessionParams) {
|
||||
async createBillingPortalSession(
|
||||
params: z.infer<typeof CreateBillingPortalSessionSchema>,
|
||||
) {
|
||||
const stripe = await this.stripeProvider();
|
||||
|
||||
return createStripeBillingPortalSession(stripe, params);
|
||||
}
|
||||
|
||||
async cancelSubscription(subscriptionId: string) {
|
||||
async cancelSubscription(
|
||||
params: z.infer<typeof CancelSubscriptionParamsSchema>,
|
||||
) {
|
||||
const stripe = await this.stripeProvider();
|
||||
|
||||
return stripe.subscriptions.cancel(subscriptionId, {
|
||||
invoice_now: true,
|
||||
await stripe.subscriptions.cancel(params.subscriptionId, {
|
||||
invoice_now: params.invoiceNow ?? true,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async retrieveCheckoutSession(
|
||||
params: z.infer<typeof RetrieveCheckoutSessionSchema>,
|
||||
) {
|
||||
const stripe = await this.stripeProvider();
|
||||
|
||||
return await stripe.subscriptions.retrieve(params.sessionId);
|
||||
}
|
||||
|
||||
private async stripeProvider(): Promise<Stripe> {
|
||||
return createStripeClient();
|
||||
}
|
||||
}
|
||||
|
||||
export const stripe = new StripeService(createStripeClient);
|
||||
|
||||
Reference in New Issue
Block a user