mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20:27:44 +00:00
fix(stripe): update subscription webhook handling should use customer id as a fallback
This commit is contained in:
@@ -20,6 +20,7 @@ import { nextCookies } from "better-auth/next-js";
|
|||||||
import { passkey } from "better-auth/plugins/passkey";
|
import { passkey } from "better-auth/plugins/passkey";
|
||||||
import { stripe } from "@better-auth/stripe";
|
import { stripe } from "@better-auth/stripe";
|
||||||
import { Stripe } from "stripe";
|
import { Stripe } from "stripe";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
|
||||||
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
|
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
|
||||||
const to = process.env.TEST_EMAIL || "";
|
const to = process.env.TEST_EMAIL || "";
|
||||||
@@ -50,10 +51,7 @@ const STARTER_PRICE_ID = {
|
|||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
appName: "Better Auth Demo",
|
appName: "Better Auth Demo",
|
||||||
database: {
|
database: new Database("stripe-1.db"),
|
||||||
dialect,
|
|
||||||
type: process.env.USE_MYSQL ? "mysql" : "sqlite",
|
|
||||||
},
|
|
||||||
emailVerification: {
|
emailVerification: {
|
||||||
async sendVerificationEmail({ user, url }) {
|
async sendVerificationEmail({ user, url }) {
|
||||||
const res = await resend.emails.send({
|
const res = await resend.emails.send({
|
||||||
|
|||||||
@@ -93,19 +93,32 @@ export async function onSubscriptionUpdated(
|
|||||||
const subscriptionUpdated = event.data.object as Stripe.Subscription;
|
const subscriptionUpdated = event.data.object as Stripe.Subscription;
|
||||||
const priceId = subscriptionUpdated.items.data[0].price.id;
|
const priceId = subscriptionUpdated.items.data[0].price.id;
|
||||||
const plan = await getPlanByPriceId(options, priceId);
|
const plan = await getPlanByPriceId(options, priceId);
|
||||||
const stripeId = subscriptionUpdated.id;
|
|
||||||
const subscription = await ctx.context.adapter.findOne<Subscription>({
|
const referenceId = subscriptionUpdated.metadata?.referenceId;
|
||||||
|
const subscriptionId = subscriptionUpdated.id;
|
||||||
|
const customerId = subscriptionUpdated.customer.toString();
|
||||||
|
let subscription = await ctx.context.adapter.findOne<Subscription>({
|
||||||
model: "subscription",
|
model: "subscription",
|
||||||
where: [
|
where: referenceId
|
||||||
{
|
? [{ field: "referenceId", value: referenceId }]
|
||||||
field: "stripeSubscriptionId",
|
: subscriptionId
|
||||||
value: stripeId,
|
? [{ field: "stripeSubscriptionId", value: subscriptionId }]
|
||||||
},
|
: [],
|
||||||
],
|
|
||||||
});
|
});
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
|
const subs = await ctx.context.adapter.findMany<Subscription>({
|
||||||
|
model: "subscription",
|
||||||
|
where: [{ field: "stripeCustomerId", value: customerId }],
|
||||||
|
});
|
||||||
|
if (subs.length > 1) {
|
||||||
|
logger.warn(
|
||||||
|
`Stripe webhook error: Multiple subscriptions found for customerId: ${customerId} and no referenceId or subscriptionId is provided`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
subscription = subs[0];
|
||||||
|
}
|
||||||
|
|
||||||
const seats = subscriptionUpdated.items.data[0].quantity;
|
const seats = subscriptionUpdated.items.data[0].quantity;
|
||||||
await ctx.context.adapter.update({
|
await ctx.context.adapter.update({
|
||||||
model: "subscription",
|
model: "subscription",
|
||||||
@@ -125,8 +138,8 @@ export async function onSubscriptionUpdated(
|
|||||||
},
|
},
|
||||||
where: [
|
where: [
|
||||||
{
|
{
|
||||||
field: "stripeSubscriptionId",
|
field: "referenceId",
|
||||||
value: subscriptionUpdated.id,
|
value: subscription.referenceId,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -170,7 +183,7 @@ export async function onSubscriptionUpdated(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(`Stripe webhook failed. Error: ${error.message}`);
|
logger.error(`Stripe webhook failed. Error: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +230,6 @@ export async function onSubscriptionDeleted(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(`Stripe webhook failed. Error: ${error.message}`);
|
logger.error(`Stripe webhook failed. Error: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,7 +366,6 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
console.log({ subscription });
|
|
||||||
if (
|
if (
|
||||||
!subscription ||
|
!subscription ||
|
||||||
subscription.cancelAtPeriodEnd ||
|
subscription.cancelAtPeriodEnd ||
|
||||||
@@ -382,7 +381,6 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|||||||
const currentSubscription = stripeSubscription.data.find(
|
const currentSubscription = stripeSubscription.data.find(
|
||||||
(sub) => sub.id === subscription.stripeSubscriptionId,
|
(sub) => sub.id === subscription.stripeSubscriptionId,
|
||||||
);
|
);
|
||||||
console.log({ currentSubscription });
|
|
||||||
if (currentSubscription?.cancel_at_period_end === true) {
|
if (currentSubscription?.cancel_at_period_end === true) {
|
||||||
await ctx.context.adapter.update({
|
await ctx.context.adapter.update({
|
||||||
model: "subscription",
|
model: "subscription",
|
||||||
|
|||||||
@@ -592,7 +592,6 @@ describe("stripe", async () => {
|
|||||||
|
|
||||||
mockStripeForEvents.webhooks.constructEvent.mockReturnValue(updateEvent);
|
mockStripeForEvents.webhooks.constructEvent.mockReturnValue(updateEvent);
|
||||||
await eventTestAuth.handler(updateRequest);
|
await eventTestAuth.handler(updateRequest);
|
||||||
|
|
||||||
expect(onSubscriptionUpdate).toHaveBeenCalledWith(
|
expect(onSubscriptionUpdate).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
event: expect.any(Object),
|
event: expect.any(Object),
|
||||||
|
|||||||
Reference in New Issue
Block a user