mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 23:53:17 -05:00
fix(stripe): preserve freeTrial and internal metadata in getCheckoutSessionParams merge (#9474)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@better-auth/stripe": patch
|
||||
---
|
||||
|
||||
Returning a custom `subscription_data` from `getCheckoutSessionParams` no longer hides the plan's free trial in Stripe Checkout or creates duplicate local subscription rows when the `customer.subscription.created` webhook fires.
|
||||
@@ -1124,8 +1124,12 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
||||
// Additional line items (metered prices, add-ons, etc.)
|
||||
...(plan.lineItems ?? []),
|
||||
],
|
||||
mode: "subscription",
|
||||
client_reference_id: referenceId,
|
||||
...params?.params,
|
||||
subscription_data: {
|
||||
...freeTrial,
|
||||
...params?.params?.subscription_data,
|
||||
metadata: subscriptionMetadata.set(
|
||||
{
|
||||
userId: user.id,
|
||||
@@ -1136,10 +1140,6 @@ export const upgradeSubscription = (options: StripeOptions) => {
|
||||
params?.params?.subscription_data?.metadata,
|
||||
),
|
||||
},
|
||||
mode: "subscription",
|
||||
client_reference_id: referenceId,
|
||||
...params?.params,
|
||||
// metadata should come after spread to protect internal fields
|
||||
metadata: subscriptionMetadata.set(
|
||||
{
|
||||
userId: user.id,
|
||||
|
||||
@@ -8666,4 +8666,120 @@ describe("stripe", () => {
|
||||
expect(meteredItem).not.toHaveProperty("quantity");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/9129
|
||||
* @see https://github.com/better-auth/better-auth/issues/9130
|
||||
*/
|
||||
describe("getCheckoutSessionParams subscription_data merge", () => {
|
||||
const trialOptions = {
|
||||
...stripeOptions,
|
||||
subscription: {
|
||||
enabled: true,
|
||||
plans: [
|
||||
{
|
||||
priceId: process.env.STRIPE_PRICE_ID_1!,
|
||||
name: "starter",
|
||||
lookupKey: "lookup_key_123",
|
||||
freeTrial: { days: 14 },
|
||||
},
|
||||
],
|
||||
getCheckoutSessionParams: async () => ({
|
||||
params: {
|
||||
payment_method_collection: "if_required" as const,
|
||||
subscription_data: {
|
||||
trial_settings: {
|
||||
end_behavior: {
|
||||
missing_payment_method: "cancel" as const,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
} satisfies StripeOptions;
|
||||
|
||||
it("preserves plan freeTrial when getCheckoutSessionParams returns custom subscription_data", async () => {
|
||||
const { client, sessionSetter } = await getTestInstance(
|
||||
{
|
||||
database: memory,
|
||||
plugins: [stripe(trialOptions)],
|
||||
},
|
||||
{
|
||||
disableTestUser: true,
|
||||
clientOptions: {
|
||||
plugins: [stripeClient({ subscription: true })],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const email = "trial-merge@email.com";
|
||||
await client.signUp.email({ ...testUser, email }, { throw: true });
|
||||
const headers = new Headers();
|
||||
await client.signIn.email(
|
||||
{ ...testUser, email },
|
||||
{ throw: true, onSuccess: sessionSetter(headers) },
|
||||
);
|
||||
|
||||
mockStripe.checkout.sessions.create.mockClear();
|
||||
await client.subscription.upgrade({
|
||||
plan: "starter",
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
|
||||
expect(mockStripe.checkout.sessions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
subscription_data: expect.objectContaining({
|
||||
trial_period_days: 14,
|
||||
trial_settings: {
|
||||
end_behavior: { missing_payment_method: "cancel" },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves internal subscription metadata when getCheckoutSessionParams returns custom subscription_data", async () => {
|
||||
const { client, sessionSetter } = await getTestInstance(
|
||||
{
|
||||
database: memory,
|
||||
plugins: [stripe(trialOptions)],
|
||||
},
|
||||
{
|
||||
disableTestUser: true,
|
||||
clientOptions: {
|
||||
plugins: [stripeClient({ subscription: true })],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const email = "metadata-merge@email.com";
|
||||
await client.signUp.email({ ...testUser, email }, { throw: true });
|
||||
const headers = new Headers();
|
||||
await client.signIn.email(
|
||||
{ ...testUser, email },
|
||||
{ throw: true, onSuccess: sessionSetter(headers) },
|
||||
);
|
||||
|
||||
mockStripe.checkout.sessions.create.mockClear();
|
||||
await client.subscription.upgrade({
|
||||
plan: "starter",
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
|
||||
expect(mockStripe.checkout.sessions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
subscription_data: expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
subscriptionId: expect.any(String),
|
||||
userId: expect.any(String),
|
||||
referenceId: expect.any(String),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user