[GH-ISSUE #2929] Can't upgrade an incomplete Stripe subscription #26724

Closed
opened 2026-04-17 17:22:26 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @bmichotte on GitHub (Jun 6, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2929

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  • Better-Auth configured with Drizzle adapter
  • Stripe plugin configured
  • User logged
  • Click on the "subscribe button" and call
        await auth.subscription.upgrade({
            plan,
            successUrl: `/dashboard`,
            cancelUrl: `/pricing`,
            annual,
        })
  • On Stripe, cancel the subscription and return to the pricing page.
  • You now have an incomplete subscription on your db
  • Click again on the "subscribe button"

Current vs. Expected behavior

It should "upgrade" the incomplete subscription

What version of Better Auth are you using?

1.2.8

Provide environment information

- macOS 15.5

Which area(s) are affected? (Select all that apply)

Package

Auth config (if applicable)

export const auth = betterAuth({
    database: drizzleAdapter(db, {
        provider: 'pg',
        usePlural: true,
    }),
    emailAndPassword: {
        enabled: true,
        sendResetPassword: async ({ user, url, token }, _request) => {
            // ...
        },
    },
    socialProviders: {
        google: {
            clientId: env.GOOGLE_CLIENT_ID,
            clientSecret: env.GOOGLE_CLIENT_SECRET,
            mapProfileToUser: profile => {
                return {
                    firstname: profile.given_name,
                    lastname: profile.family_name,
                }
            },
        },
    },
    plugins: [
        nextCookies(),
        admin(),
        stripe({
            stripeClient,
            stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET,
            createCustomerOnSignUp: true,
            onEvent: async event => {
                console.log(`Stripe event received: ${event.type}`, { event })
            },
            subscription: {
                enabled: true,
                authorizeReference: async ({ user, session, referenceId, action }) => {
                    return true
                },
                plans: [
                    {
                        name: 'basic',
                        priceId: 'price_xxxxx',
                        annualDiscountPriceId: 'price_xxxx',
                        freeTrial: {
                            days: 14,
                            onTrialStart: async subscription => {
                                // Called when a trial starts
                                console.log(`Trial started for subscription ${subscription.id}`, { subscription })
                            },
                            onTrialEnd: async ({ subscription }, request) => {
                                // Called when a trial ends
                                console.log(`Trial ended for subscription ${subscription.id}`, {
                                    subscription,
                                    request,
                                })
                            },
                            onTrialExpired: async subscription => {
                                // Called when a trial expires without conversion
                                console.log(`Trial expired for subscription ${subscription.id}`, { subscription })
                            },
                        },
                    },
                    {
                        name: 'ia',
                        priceId: 'price_xxxx',
                        annualDiscountPriceId: 'price_xxxx',
                        freeTrial: {
                            days: 14,
                            onTrialStart: async subscription => {
                                // Called when a trial starts
                                console.log(`Trial started for subscription ${subscription.id}`, { subscription })
                            },
                            onTrialEnd: async ({ subscription }, request) => {
                                // Called when a trial ends
                                console.log(`Trial ended for subscription ${subscription.id}`, {
                                    subscription,
                                    request,
                                })
                            },
                            onTrialExpired: async subscription => {
                                // Called when a trial expires without conversion
                                console.log(`Trial expired for subscription ${subscription.id}`, { subscription })
                            },
                        },
                    },
                ],
                onSubscriptionComplete: async ({ event, subscription, stripeSubscription, plan }) => {
                    // Called when a subscription is successfully created
                    console.log(`Subscription ${subscription.id} created for plan ${plan.name}`, {
                        event,
                        subscription,
                        stripeSubscription,
                        plan,
                    })
                },
                onSubscriptionUpdate: async ({ event, subscription }) => {
                    // Called when a subscription is updated
                    console.log(`Subscription ${subscription.id} updated`, { event, subscription })
                },
                onSubscriptionCancel: async ({ event, subscription, stripeSubscription, cancellationDetails }) => {
                    // Called when a subscription is canceled
                    console.log(`subscription ${subscription.id} canceled`, {
                        event,
                        subscription,
                        stripeSubscription,
                        cancellationDetails,
                    })
                },
                onSubscriptionDeleted: async ({ event, subscription, stripeSubscription }) => {
                    // Called when a subscription is deleted
                    console.log(`Subscription ${subscription.id} deleted`, { event, subscription, stripeSubscription })
                },
            },
        }),
    ],
    user: {
        additionalFields: {
            firstname: {
                type: 'string',
                required: true,
            },
            lastname: {
                type: 'string',
                required: true,
            },
            role: {
                type: 'string',
                required: false,
                defaultValue: 'user',
                input: false,
            },
            locale: {
                type: 'string',
                required: false,
                defaultValue: routing.defaultLocale,
            },
        },
    },
    account: {
        accountLinking: {
            enabled: true,
            allowDifferentEmails: true,
            trustedProviders: ['google', 'email-password'],
        },
    },
})

Additional context

The error is the following

# SERVER_ERROR:  [Error: Failed query: insert into "subscriptions" ("id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at") values ($1, $2, $3, $4, default, $5, default, default, default, $6, default, default) returning "id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at"
params: kuLwXXXX,basic,phDhsbXXXXX,cus_SRXXXX,incomplete,1] {
  query: 'insert into "subscriptions" ("id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at") values ($1, $2, $3, $4, default, $5, default, default, default, $6, default, default) returning "id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at"',
  params: [Array],
  [cause]: [error: duplicate key value violates unique constraint "subscriptions_reference_id_unique"] {
    length: 275,
    severity: 'ERROR',
    code: '23505',
    detail: 'Key (reference_id)=(phDhXXXXNTl) already exists.',
    hint: undefined,
    position: undefined,
    internalPosition: undefined,
    internalQuery: undefined,
    where: undefined,
    schema: 'public',
    table: 'subscriptions',
    column: undefined,
    dataType: undefined,
    constraint: 'subscriptions_reference_id_unique',
    file: 'nbtinsert.c',
    line: '673',
    routine: '_bt_check_unique'
  }
}
Originally created by @bmichotte on GitHub (Jun 6, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2929 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce - Better-Auth configured with **Drizzle adapter** - **Stripe plugin** configured - User logged - Click on the "subscribe button" and call ```ts await auth.subscription.upgrade({ plan, successUrl: `/dashboard`, cancelUrl: `/pricing`, annual, }) ``` - On Stripe, cancel the subscription and return to the pricing page. - You now have an `incomplete` subscription on your db - Click again on the "subscribe button" ### Current vs. Expected behavior It should "upgrade" the `incomplete` subscription ### What version of Better Auth are you using? 1.2.8 ### Provide environment information ```bash - macOS 15.5 ``` ### Which area(s) are affected? (Select all that apply) Package ### Auth config (if applicable) ```typescript export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', usePlural: true, }), emailAndPassword: { enabled: true, sendResetPassword: async ({ user, url, token }, _request) => { // ... }, }, socialProviders: { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, mapProfileToUser: profile => { return { firstname: profile.given_name, lastname: profile.family_name, } }, }, }, plugins: [ nextCookies(), admin(), stripe({ stripeClient, stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET, createCustomerOnSignUp: true, onEvent: async event => { console.log(`Stripe event received: ${event.type}`, { event }) }, subscription: { enabled: true, authorizeReference: async ({ user, session, referenceId, action }) => { return true }, plans: [ { name: 'basic', priceId: 'price_xxxxx', annualDiscountPriceId: 'price_xxxx', freeTrial: { days: 14, onTrialStart: async subscription => { // Called when a trial starts console.log(`Trial started for subscription ${subscription.id}`, { subscription }) }, onTrialEnd: async ({ subscription }, request) => { // Called when a trial ends console.log(`Trial ended for subscription ${subscription.id}`, { subscription, request, }) }, onTrialExpired: async subscription => { // Called when a trial expires without conversion console.log(`Trial expired for subscription ${subscription.id}`, { subscription }) }, }, }, { name: 'ia', priceId: 'price_xxxx', annualDiscountPriceId: 'price_xxxx', freeTrial: { days: 14, onTrialStart: async subscription => { // Called when a trial starts console.log(`Trial started for subscription ${subscription.id}`, { subscription }) }, onTrialEnd: async ({ subscription }, request) => { // Called when a trial ends console.log(`Trial ended for subscription ${subscription.id}`, { subscription, request, }) }, onTrialExpired: async subscription => { // Called when a trial expires without conversion console.log(`Trial expired for subscription ${subscription.id}`, { subscription }) }, }, }, ], onSubscriptionComplete: async ({ event, subscription, stripeSubscription, plan }) => { // Called when a subscription is successfully created console.log(`Subscription ${subscription.id} created for plan ${plan.name}`, { event, subscription, stripeSubscription, plan, }) }, onSubscriptionUpdate: async ({ event, subscription }) => { // Called when a subscription is updated console.log(`Subscription ${subscription.id} updated`, { event, subscription }) }, onSubscriptionCancel: async ({ event, subscription, stripeSubscription, cancellationDetails }) => { // Called when a subscription is canceled console.log(`subscription ${subscription.id} canceled`, { event, subscription, stripeSubscription, cancellationDetails, }) }, onSubscriptionDeleted: async ({ event, subscription, stripeSubscription }) => { // Called when a subscription is deleted console.log(`Subscription ${subscription.id} deleted`, { event, subscription, stripeSubscription }) }, }, }), ], user: { additionalFields: { firstname: { type: 'string', required: true, }, lastname: { type: 'string', required: true, }, role: { type: 'string', required: false, defaultValue: 'user', input: false, }, locale: { type: 'string', required: false, defaultValue: routing.defaultLocale, }, }, }, account: { accountLinking: { enabled: true, allowDifferentEmails: true, trustedProviders: ['google', 'email-password'], }, }, }) ``` ### Additional context The error is the following ``` # SERVER_ERROR: [Error: Failed query: insert into "subscriptions" ("id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at") values ($1, $2, $3, $4, default, $5, default, default, default, $6, default, default) returning "id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at" params: kuLwXXXX,basic,phDhsbXXXXX,cus_SRXXXX,incomplete,1] { query: 'insert into "subscriptions" ("id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at") values ($1, $2, $3, $4, default, $5, default, default, default, $6, default, default) returning "id", "plan", "reference_id", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats", "created_at", "updated_at"', params: [Array], [cause]: [error: duplicate key value violates unique constraint "subscriptions_reference_id_unique"] { length: 275, severity: 'ERROR', code: '23505', detail: 'Key (reference_id)=(phDhXXXXNTl) already exists.', hint: undefined, position: undefined, internalPosition: undefined, internalQuery: undefined, where: undefined, schema: 'public', table: 'subscriptions', column: undefined, dataType: undefined, constraint: 'subscriptions_reference_id_unique', file: 'nbtinsert.c', line: '673', routine: '_bt_check_unique' } } ```
GiteaMirror added the locked label 2026-04-17 17:22:26 -05:00
Author
Owner

@Kinfe123 commented on GitHub (Jun 7, 2025):

can you please use this

npm i https://pkg.pr.new/better-auth/better-auth/@better-auth/stripe@2930
<!-- gh-comment-id:2951949701 --> @Kinfe123 commented on GitHub (Jun 7, 2025): can you please use this ```bash npm i https://pkg.pr.new/better-auth/better-auth/@better-auth/stripe@2930 ```
Author
Owner

@bmichotte commented on GitHub (Jun 7, 2025):

Hello @Kinfe123, this PR fixes my issue, thanks

<!-- gh-comment-id:2952302085 --> @bmichotte commented on GitHub (Jun 7, 2025): Hello @Kinfe123, this PR fixes my issue, thanks
Author
Owner

@bmichotte commented on GitHub (Jun 22, 2025):

What's the current status of this issue ?

We had two release of better-auth since @Kinfe123 fixed it and it still not merged and released. This is a critical issue since users can't create a subscription because of this issue.

<!-- gh-comment-id:2994096937 --> @bmichotte commented on GitHub (Jun 22, 2025): What's the current status of this issue ? We had two release of better-auth since @Kinfe123 fixed it and it still not merged and released. This is a critical issue since users can't create a subscription because of this issue.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#26724