This commit is contained in:
Franjo Mindek
2025-11-14 17:06:33 +01:00
parent adb8bb62e6
commit 467139d79c
3 changed files with 38 additions and 29 deletions

View File

@@ -10,16 +10,16 @@ import { stripeClient } from "./stripeClient";
export async function ensureStripeCustomer(
userEmail: NonNullable<User["email"]>,
): Promise<Stripe.Customer> {
const stripeCustomers = await stripeClient.customers.list({
const customers = await stripeClient.customers.list({
email: userEmail,
});
if (stripeCustomers.data.length === 0) {
if (customers.data.length === 0) {
return stripeClient.customers.create({
email: userEmail,
});
} else {
return stripeCustomers.data[0];
return customers.data[0];
}
}

View File

@@ -36,13 +36,13 @@ export const stripePaymentProcessor: PaymentProcessor = {
prismaUserDelegate,
);
const stripeSession = await createStripeCheckoutSession({
const checkoutSession = await createStripeCheckoutSession({
customerId: customer.id,
priceId: paymentPlan.getPaymentProcessorPlanId(),
mode: paymentPlanEffectToStripeCheckoutSessionMode(paymentPlan.effect),
});
if (!stripeSession.url) {
if (!checkoutSession.url) {
throw new Error(
"Stripe checkout session URL is missing. Checkout session might not be active.",
);
@@ -50,8 +50,8 @@ export const stripePaymentProcessor: PaymentProcessor = {
return {
session: {
url: stripeSession.url,
id: stripeSession.id,
url: checkoutSession.url,
id: checkoutSession.id,
},
};
},

View File

@@ -35,38 +35,30 @@ export const stripeWebhook: PaymentsWebhook = async (
) => {
const prismaUserDelegate = context.entities.User;
try {
const stripeEvent = constructStripeEvent(request);
const event = constructStripeEvent(request);
// If you'd like to handle more events, you can add more cases below.
// When deploying your app, you configure your webhook in the Stripe dashboard
// to only send the events that you're handling above.
// See: https://docs.opensaas.sh/guides/deploying/#setting-up-your-stripe-webhook
switch (stripeEvent.type) {
switch (event.type) {
case "invoice.paid":
await handleInvoicePaid(stripeEvent, prismaUserDelegate);
await handleInvoicePaid(event, prismaUserDelegate);
break;
case "customer.subscription.updated":
await handleCustomerSubscriptionUpdated(
stripeEvent,
prismaUserDelegate,
);
await handleCustomerSubscriptionUpdated(event, prismaUserDelegate);
break;
case "customer.subscription.deleted":
await handleCustomerSubscriptionDeleted(
stripeEvent,
prismaUserDelegate,
);
await handleCustomerSubscriptionDeleted(event, prismaUserDelegate);
break;
default:
throw new UnhandledWebhookEventError(stripeEvent.type);
throw new UnhandledWebhookEventError(event.type);
}
return response.status(204).send();
} catch (error) {
if (error instanceof UnhandledWebhookEventError) {
// In development, it is likely that we will receive events that we are not handling.
// E.g. via the `stripe trigger` command.
// While these can be ignored safely in development, it's good to be aware of them.
// For production we shouldn't have any extra webhook events.
if (process.env.NODE_ENV === "development") {
console.info("Unhandled Stripe webhook event in development: ", error);
} else if (process.env.NODE_ENV === "production") {
@@ -82,7 +74,7 @@ export const stripeWebhook: PaymentsWebhook = async (
return response.status(400).json({ error: error.message });
} else {
return response
.status(400)
.status(500)
.json({ error: "Error processing Stripe webhook event" });
}
}
@@ -190,14 +182,31 @@ async function handleCustomerSubscriptionUpdated(
function getOpenSaasSubscriptionStatus(
subscription: Stripe.Subscription,
): SubscriptionStatus | undefined {
if (subscription.status === SubscriptionStatus.Active) {
if (subscription.cancel_at_period_end) {
return SubscriptionStatus.CancelAtPeriodEnd;
}
return SubscriptionStatus.Active;
} else if (subscription.status === SubscriptionStatus.PastDue) {
return SubscriptionStatus.PastDue;
const stripeToOpenSaasSubscriptionStatusMap: Record<
Stripe.Subscription.Status,
SubscriptionStatus | undefined
> = {
trialing: SubscriptionStatus.Active,
active: SubscriptionStatus.Active,
past_due: SubscriptionStatus.PastDue,
canceled: SubscriptionStatus.Deleted,
unpaid: SubscriptionStatus.Deleted,
incomplete_expired: SubscriptionStatus.Deleted,
paused: undefined,
incomplete: undefined,
};
const subscriptionStauts =
stripeToOpenSaasSubscriptionStatusMap[subscription.status];
if (
subscriptionStauts === SubscriptionStatus.Active &&
subscription.cancel_at_period_end
) {
return SubscriptionStatus.CancelAtPeriodEnd;
}
return subscriptionStauts;
}
function getSubscriptionPriceId(