mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-22 22:32:01 -05:00
fix(stripe): allow re-subscribing to the same plan when subscription has expired (#7459)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Taesu <bytaesu@gmail.com> Co-authored-by: Taesu <166604494+bytaesu@users.noreply.github.com>
This commit is contained in:
@@ -502,7 +502,10 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
||||
activeOrTrialingSubscription &&
|
||||
activeOrTrialingSubscription.status === "active" &&
|
||||
activeOrTrialingSubscription.plan === ctx.body.plan &&
|
||||
activeOrTrialingSubscription.seats === (ctx.body.seats || 1)
|
||||
activeOrTrialingSubscription.seats === (ctx.body.seats || 1) &&
|
||||
// Skip if periodEnd has passed, in case status is stale
|
||||
(!activeOrTrialingSubscription.periodEnd ||
|
||||
activeOrTrialingSubscription.periodEnd > new Date())
|
||||
) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN,
|
||||
|
||||
@@ -1915,6 +1915,72 @@ describe("stripe", () => {
|
||||
expect(upgradeRes.error?.message).toContain("already subscribed");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "past",
|
||||
periodEnd: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||
shouldAllow: true,
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
periodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
||||
shouldAllow: false,
|
||||
},
|
||||
])("should handle re-subscribing when periodEnd is in the $name", async ({
|
||||
periodEnd,
|
||||
shouldAllow,
|
||||
}) => {
|
||||
const { client, auth, sessionSetter } = await getTestInstance(
|
||||
{
|
||||
database: memory,
|
||||
plugins: [stripe(stripeOptions)],
|
||||
},
|
||||
{
|
||||
disableTestUser: true,
|
||||
clientOptions: {
|
||||
plugins: [stripeClient({ subscription: true })],
|
||||
},
|
||||
},
|
||||
);
|
||||
const ctx = await auth.$context;
|
||||
|
||||
const userRes = await client.signUp.email(
|
||||
{ ...testUser, email: `periodend-${periodEnd.getTime()}@email.com` },
|
||||
{ throw: true },
|
||||
);
|
||||
|
||||
const headers = new Headers();
|
||||
await client.signIn.email(
|
||||
{ ...testUser, email: `periodend-${periodEnd.getTime()}@email.com` },
|
||||
{ throw: true, onSuccess: sessionSetter(headers) },
|
||||
);
|
||||
|
||||
await client.subscription.upgrade({
|
||||
plan: "starter",
|
||||
seats: 1,
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
|
||||
await ctx.adapter.update({
|
||||
model: "subscription",
|
||||
update: { status: "active", seats: 1, periodEnd },
|
||||
where: [{ field: "referenceId", value: userRes.user.id }],
|
||||
});
|
||||
|
||||
const upgradeRes = await client.subscription.upgrade({
|
||||
plan: "starter",
|
||||
seats: 1,
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
|
||||
if (shouldAllow) {
|
||||
expect(upgradeRes.error).toBeNull();
|
||||
expect(upgradeRes.data?.url).toBeDefined();
|
||||
} else {
|
||||
expect(upgradeRes.error?.message).toContain("already subscribed");
|
||||
}
|
||||
});
|
||||
|
||||
it("should only call Stripe customers.create once for signup and upgrade", async () => {
|
||||
const { client, sessionSetter } = await getTestInstance(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user