137 lines
3.6 KiB
TypeScript
137 lines
3.6 KiB
TypeScript
import type { Stripe } from 'stripe';
|
|
import { z } from 'zod';
|
|
|
|
import type { CreateBillingCheckoutSchema } from '@kit/billing/schema';
|
|
|
|
/**
|
|
* @description If set to true, users can start a trial without entering their credit card details
|
|
*/
|
|
const enableTrialWithoutCreditCard =
|
|
process.env.STRIPE_ENABLE_TRIAL_WITHOUT_CC === 'true';
|
|
|
|
/**
|
|
* @name createStripeCheckout
|
|
* @description Creates a Stripe Checkout session, and returns an Object
|
|
* containing the session, which you can use to redirect the user to the
|
|
* checkout page
|
|
*/
|
|
export async function createStripeCheckout(
|
|
stripe: Stripe,
|
|
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.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;
|
|
|
|
// docs: https://stripe.com/docs/billing/subscriptions/build-subscription
|
|
const mode: Stripe.Checkout.SessionCreateParams.Mode =
|
|
params.plan.paymentType === 'recurring' ? 'subscription' : 'payment';
|
|
|
|
const isSubscription = mode === 'subscription';
|
|
|
|
let trialDays: number | null | undefined = params.plan.trialDays;
|
|
|
|
// if the customer already exists, we do not set a trial period
|
|
if (customer) {
|
|
trialDays = undefined;
|
|
}
|
|
|
|
const trialSettings =
|
|
trialDays && enableTrialWithoutCreditCard
|
|
? {
|
|
trial_settings: {
|
|
end_behavior: {
|
|
missing_payment_method: 'cancel' as const,
|
|
},
|
|
},
|
|
}
|
|
: {};
|
|
|
|
// this should only be set if the mode is 'subscription'
|
|
const subscriptionData:
|
|
| Stripe.Checkout.SessionCreateParams.SubscriptionData
|
|
| undefined = isSubscription
|
|
? {
|
|
trial_period_days: trialDays,
|
|
metadata: {
|
|
accountId: params.accountId,
|
|
...(params.metadata ?? {}),
|
|
},
|
|
...trialSettings,
|
|
}
|
|
: {};
|
|
|
|
const urls = getUrls({
|
|
returnUrl: params.returnUrl,
|
|
});
|
|
|
|
// we use the embedded mode, so the user does not leave the page
|
|
const uiMode = 'embedded';
|
|
|
|
const customerData = customer
|
|
? {
|
|
customer,
|
|
}
|
|
: {
|
|
customer_email: params.customerEmail,
|
|
};
|
|
|
|
const customerCreation =
|
|
isSubscription || customer
|
|
? ({} as Record<string, string>)
|
|
: { customer_creation: 'always' };
|
|
|
|
const lineItems = params.plan.lineItems.map((item) => {
|
|
if (item.type === 'metered') {
|
|
return {
|
|
price: item.id,
|
|
};
|
|
}
|
|
|
|
// if we pass a custom quantity for the item ID
|
|
// we use that - otherwise we set it to 1 by default
|
|
const quantity =
|
|
params.variantQuantities.find((variant) => {
|
|
return variant.variantId === item.id;
|
|
})?.quantity ?? 1;
|
|
|
|
return {
|
|
price: item.id,
|
|
quantity,
|
|
};
|
|
});
|
|
|
|
const paymentCollectionMethod =
|
|
enableTrialWithoutCreditCard && params.plan.trialDays
|
|
? {
|
|
payment_method_collection: 'if_required' as const,
|
|
}
|
|
: {};
|
|
|
|
return stripe.checkout.sessions.create({
|
|
mode,
|
|
allow_promotion_codes: params.enableDiscountField,
|
|
ui_mode: uiMode,
|
|
line_items: lineItems,
|
|
client_reference_id: clientReferenceId,
|
|
subscription_data: subscriptionData,
|
|
...customerCreation,
|
|
...customerData,
|
|
...urls,
|
|
...paymentCollectionMethod,
|
|
});
|
|
}
|
|
|
|
function getUrls(params: { returnUrl: string }) {
|
|
const returnUrl = `${params.returnUrl}?session_id={CHECKOUT_SESSION_ID}`;
|
|
|
|
return {
|
|
return_url: returnUrl,
|
|
};
|
|
}
|