[Stripe] TrialStart and TrialEnd fields not being updated on checkout completed with free trial #1723

Closed
opened 2026-03-13 08:58:45 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @Ooscaar on GitHub (Aug 16, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Install stripe plugin https://www.better-auth.com/docs/plugins/stripe and set up database schema
  2. Set up basic product with free trial
const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2025-07-30.basil",
});

export const auth = betterAuth({
  // ... your existing config
  subscription: {
    enabled: true,
    plans: [
      {
        name: "pro",
        priceId: "price_0987654321",
        freeTrial: {
          days: 30,
        },
      },
    ],
  },
});
  1. Set up the client and create a subscription for current product

Current vs. Expected behavior

Once a checkout is completed with a free trial, subscription is correctly created but without trial_start and trial_end values.

postgres=# select * from subscription order by status desc limit 1;
            id            | plan |       reference_id       | stripe_customer_id |    stripe_subscription_id    |  status  |    period_start     |     period_end      | cancel_at_period_end | seats | trial_start | trial_end
--------------------------+------+--------------------------+--------------------+------------------------------+----------+---------------------+---------------------+----------------------+-------+-------------+-----------
 xnwc702a749xs90syy56dgyw | pro  | i0t2scxcdhfurooi4fe48ocm | cus_SsXQkATquKpCq6 | sub_1RwmMGDinHkwIOhRIkHFY0p5 | trialing | 2025-08-16 15:49:27 | 2025-09-15 15:49:27 |                      |     1 |             |
(1 row)

I have extracted the subscription data obtained during the process as it is currently used on https://github.com/better-auth/better-auth/blob/canary/packages/stripe/src/hooks.ts#L17:

{
  "id": "sub_1RwmMGDinHkwIOhRIkHFY0p5",
  "object": "subscription",
  "application": null,
  "application_fee_percent": null,
  "automatic_tax": {
    "disabled_reason": null,
    "enabled": false,
    "liability": null
  },
  "billing_cycle_anchor": 1757951367,
  "billing_cycle_anchor_config": null,
  "billing_mode": {
    "type": "classic"
  },
  "billing_thresholds": null,
  "cancel_at": null,
  "cancel_at_period_end": false,
  "canceled_at": null,
  "cancellation_details": {
    "comment": null,
    "feedback": null,
    "reason": null
  },
  "collection_method": "charge_automatically",
  "created": 1755359367,
  "currency": "usd",
  "customer": "cus_SsXQkATquKpCq6",
  "days_until_due": null,
  "default_payment_method": "pm_1RwmMEDinHkwIOhRI0ROJXtk",
  "default_source": null,
  "default_tax_rates": [],
  "description": null,
  "discounts": [],
  "ended_at": null,
  "invoice_settings": {
    "account_tax_ids": null,
    "issuer": {
      "type": "self"
    }
  },
  "items": {
    "object": "list",
    "data": [
      {
        "id": "si_SsXRVygbctyH4m",
        "object": "subscription_item",
        "billing_thresholds": null,
        "created": 1755359367,
        "current_period_end": 1757951367,
        "current_period_start": 1755359367,
        "discounts": [],
        "metadata": {},
        "plan": {
          "id": "price_1RtQibDinHkwIOhRoGfSDQwK",
          "object": "plan",
          "active": true,
          "amount": 2000,
          "amount_decimal": "2000",
          "billing_scheme": "per_unit",
          "created": 1754561201,
          "currency": "usd",
          "interval": "month",
          "interval_count": 1,
          "livemode": false,
          "metadata": {},
          "meter": null,
          "nickname": "PRO Monthly",
          "product": "prod_Sp4r47E3GhHnnC",
          "tiers_mode": null,
          "transform_usage": null,
          "trial_period_days": null,
          "usage_type": "licensed"
        },
        "price": {
          "id": "price_1RtQibDinHkwIOhRoGfSDQwK",
          "object": "price",
          "active": true,
          "billing_scheme": "per_unit",
          "created": 1754561201,
          "currency": "usd",
          "custom_unit_amount": null,
          "livemode": false,
          "lookup_key": null,
          "metadata": {},
          "nickname": "PRO Monthly",
          "product": "prod_Sp4r47E3GhHnnC",
          "recurring": {
            "interval": "month",
            "interval_count": 1,
            "meter": null,
            "trial_period_days": null,
            "usage_type": "licensed"
          },
          "tax_behavior": "unspecified",
          "tiers_mode": null,
          "transform_quantity": null,
          "type": "recurring",
          "unit_amount": 2000,
          "unit_amount_decimal": "2000"
        },
        "quantity": 1,
        "subscription": "sub_1RwmMGDinHkwIOhRIkHFY0p5",
        "tax_rates": []
      }
    ],
    "has_more": false,
    "total_count": 1,
    "url": "/v1/subscription_items?subscription=sub_1RwmMGDinHkwIOhRIkHFY0p5"
  },
  "latest_invoice": "in_1RwmMFDinHkwIOhRuObVmCQB",
  "livemode": false,
  "metadata": {},
  "next_pending_invoice_item_invoice": null,
  "on_behalf_of": null,
  "pause_collection": null,
  "payment_settings": {
    "payment_method_options": {
      "acss_debit": null,
      "bancontact": null,
      "card": {
        "network": null,
        "request_three_d_secure": "automatic"
      },
      "customer_balance": null,
      "konbini": null,
      "sepa_debit": null,
      "us_bank_account": null
    },
    "payment_method_types": null,
    "save_default_payment_method": "off"
  },
  "pending_invoice_item_interval": null,
  "pending_setup_intent": null,
  "pending_update": null,
  "plan": {
    "id": "price_1RtQibDinHkwIOhRoGfSDQwK",
    "object": "plan",
    "active": true,
    "amount": 2000,
    "amount_decimal": "2000",
    "billing_scheme": "per_unit",
    "created": 1754561201,
    "currency": "usd",
    "interval": "month",
    "interval_count": 1,
    "livemode": false,
    "metadata": {},
    "meter": null,
    "nickname": "PRO Monthly",
    "product": "prod_Sp4r47E3GhHnnC",
    "tiers_mode": null,
    "transform_usage": null,
    "trial_period_days": null,
    "usage_type": "licensed"
  },
  "quantity": 1,
  "schedule": null,
  "start_date": 1755359367,
  "status": "trialing",
  "test_clock": null,
  "transfer_data": null,
  "trial_end": 1757951367,
  "trial_settings": {
    "end_behavior": {
      "missing_payment_method": "create_invoice"
    }
  },
  "trial_start": 1755359367
}

where we can see that the trial information is there:

{
  "trial_end": 1757951367,
  "trial_settings": {
    "end_behavior": {
      "missing_payment_method": "create_invoice"
    }
  },
  "trial_start": 1755359367
}

Just as an extra step I have enabled logs for drizzle in order to see which query is being made for updating the subscription:

Query: update "subscription" set "plan" = $1, "stripe_subscription_id" = $2, "status" = $3, "period_start" = $4, "period_end" = $5, "seats" = $6 where "subscription"."id" = $7 returning "id", "plan", "reference_id", "trial_start", "trial_end", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats" -- params: ["pro", "sub_1RwmMGDinHkwIOhRIkHFY0p5", "trialing", "2025-08-16T15:49:27.000Z", "2025-09-15T15:49:27.000Z", 1, "xnwc702a749xs90syy56dgyw"]

where trial_start and trial_end values are not being updated.

What version of Better Auth are you using?

better-auth@^1.3.5

System info

- Using bun `1.2.20`
- drizzle adapter

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

Package

Auth config (if applicable)


Additional context

No response

Originally created by @Ooscaar on GitHub (Aug 16, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Install stripe plugin https://www.better-auth.com/docs/plugins/stripe and set up database schema 2. Set up basic product with free trial ```typescript const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: "2025-07-30.basil", }); export const auth = betterAuth({ // ... your existing config subscription: { enabled: true, plans: [ { name: "pro", priceId: "price_0987654321", freeTrial: { days: 30, }, }, ], }, }); ``` 3. Set up the client and create a subscription for current product ### Current vs. Expected behavior Once a checkout is completed with a free trial, subscription is correctly created but without `trial_start` and `trial_end` values. ``` postgres=# select * from subscription order by status desc limit 1; id | plan | reference_id | stripe_customer_id | stripe_subscription_id | status | period_start | period_end | cancel_at_period_end | seats | trial_start | trial_end --------------------------+------+--------------------------+--------------------+------------------------------+----------+---------------------+---------------------+----------------------+-------+-------------+----------- xnwc702a749xs90syy56dgyw | pro | i0t2scxcdhfurooi4fe48ocm | cus_SsXQkATquKpCq6 | sub_1RwmMGDinHkwIOhRIkHFY0p5 | trialing | 2025-08-16 15:49:27 | 2025-09-15 15:49:27 | | 1 | | (1 row) ``` I have extracted the subscription data obtained during the process as it is currently used on https://github.com/better-auth/better-auth/blob/canary/packages/stripe/src/hooks.ts#L17: ```json { "id": "sub_1RwmMGDinHkwIOhRIkHFY0p5", "object": "subscription", "application": null, "application_fee_percent": null, "automatic_tax": { "disabled_reason": null, "enabled": false, "liability": null }, "billing_cycle_anchor": 1757951367, "billing_cycle_anchor_config": null, "billing_mode": { "type": "classic" }, "billing_thresholds": null, "cancel_at": null, "cancel_at_period_end": false, "canceled_at": null, "cancellation_details": { "comment": null, "feedback": null, "reason": null }, "collection_method": "charge_automatically", "created": 1755359367, "currency": "usd", "customer": "cus_SsXQkATquKpCq6", "days_until_due": null, "default_payment_method": "pm_1RwmMEDinHkwIOhRI0ROJXtk", "default_source": null, "default_tax_rates": [], "description": null, "discounts": [], "ended_at": null, "invoice_settings": { "account_tax_ids": null, "issuer": { "type": "self" } }, "items": { "object": "list", "data": [ { "id": "si_SsXRVygbctyH4m", "object": "subscription_item", "billing_thresholds": null, "created": 1755359367, "current_period_end": 1757951367, "current_period_start": 1755359367, "discounts": [], "metadata": {}, "plan": { "id": "price_1RtQibDinHkwIOhRoGfSDQwK", "object": "plan", "active": true, "amount": 2000, "amount_decimal": "2000", "billing_scheme": "per_unit", "created": 1754561201, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "meter": null, "nickname": "PRO Monthly", "product": "prod_Sp4r47E3GhHnnC", "tiers_mode": null, "transform_usage": null, "trial_period_days": null, "usage_type": "licensed" }, "price": { "id": "price_1RtQibDinHkwIOhRoGfSDQwK", "object": "price", "active": true, "billing_scheme": "per_unit", "created": 1754561201, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": "PRO Monthly", "product": "prod_Sp4r47E3GhHnnC", "recurring": { "interval": "month", "interval_count": 1, "meter": null, "trial_period_days": null, "usage_type": "licensed" }, "tax_behavior": "unspecified", "tiers_mode": null, "transform_quantity": null, "type": "recurring", "unit_amount": 2000, "unit_amount_decimal": "2000" }, "quantity": 1, "subscription": "sub_1RwmMGDinHkwIOhRIkHFY0p5", "tax_rates": [] } ], "has_more": false, "total_count": 1, "url": "/v1/subscription_items?subscription=sub_1RwmMGDinHkwIOhRIkHFY0p5" }, "latest_invoice": "in_1RwmMFDinHkwIOhRuObVmCQB", "livemode": false, "metadata": {}, "next_pending_invoice_item_invoice": null, "on_behalf_of": null, "pause_collection": null, "payment_settings": { "payment_method_options": { "acss_debit": null, "bancontact": null, "card": { "network": null, "request_three_d_secure": "automatic" }, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null }, "payment_method_types": null, "save_default_payment_method": "off" }, "pending_invoice_item_interval": null, "pending_setup_intent": null, "pending_update": null, "plan": { "id": "price_1RtQibDinHkwIOhRoGfSDQwK", "object": "plan", "active": true, "amount": 2000, "amount_decimal": "2000", "billing_scheme": "per_unit", "created": 1754561201, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "meter": null, "nickname": "PRO Monthly", "product": "prod_Sp4r47E3GhHnnC", "tiers_mode": null, "transform_usage": null, "trial_period_days": null, "usage_type": "licensed" }, "quantity": 1, "schedule": null, "start_date": 1755359367, "status": "trialing", "test_clock": null, "transfer_data": null, "trial_end": 1757951367, "trial_settings": { "end_behavior": { "missing_payment_method": "create_invoice" } }, "trial_start": 1755359367 } ``` where we can see that the trial information is there: ```json { "trial_end": 1757951367, "trial_settings": { "end_behavior": { "missing_payment_method": "create_invoice" } }, "trial_start": 1755359367 } ``` Just as an extra step I have enabled logs for drizzle in order to see which query is being made for updating the subscription: ``` Query: update "subscription" set "plan" = $1, "stripe_subscription_id" = $2, "status" = $3, "period_start" = $4, "period_end" = $5, "seats" = $6 where "subscription"."id" = $7 returning "id", "plan", "reference_id", "trial_start", "trial_end", "stripe_customer_id", "stripe_subscription_id", "status", "period_start", "period_end", "cancel_at_period_end", "seats" -- params: ["pro", "sub_1RwmMGDinHkwIOhRIkHFY0p5", "trialing", "2025-08-16T15:49:27.000Z", "2025-09-15T15:49:27.000Z", 1, "xnwc702a749xs90syy56dgyw"] ``` where `trial_start` and `trial_end` values are not being updated. ### What version of Better Auth are you using? better-auth@^1.3.5 ### System info ```bash - Using bun `1.2.20` - drizzle adapter ``` ### Which area(s) are affected? (Select all that apply) Package ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
GiteaMirror added the plugin label 2026-03-13 08:58:45 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 16, 2025):

Hi @Ooscaar! I'm Dosu and I’m helping the better-auth team.

The root cause is that trial_start and trial_end are omitted from the update payload in the onSubscriptionUpdated function, so they aren't updated in the database after checkout or during Stripe webhook events—even though Stripe provides these values and your schema supports them. The update logic for these fields is present in onCheckoutSessionCompleted, but missing in onSubscriptionUpdated source.

To fix this, update the onSubscriptionUpdated handler to safely extract and map these fields from the Stripe subscription object. For example:

const trial =
  subscriptionUpdated.trial_start != null && subscriptionUpdated.trial_end != null
    ? {
        trialStart: new Date(subscriptionUpdated.trial_start * 1000),
        trialEnd: new Date(subscriptionUpdated.trial_end * 1000),
      }
    : {};

await ctx.context.adapter.update({
  model: "subscription",
  update: {
    // ...other fields
    ...trial,
  },
  where: [{ field: "id", value: subscription.id }],
});

This ensures trialStart and trialEnd are updated when present, and avoids errors if Stripe sends null or undefined source.

Also, make sure you extract period fields from subscription.items.data[0] for Stripe API compatibility, and test with both trial and non-trial subscriptions to confirm the fix source.

If you patch this logic, your trial fields should update as expected.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 16, 2025): <!-- Greeting --> Hi @Ooscaar! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The root cause is that `trial_start` and `trial_end` are omitted from the update payload in the `onSubscriptionUpdated` function, so they aren't updated in the database after checkout or during Stripe webhook events—even though Stripe provides these values and your schema supports them. The update logic for these fields is present in `onCheckoutSessionCompleted`, but missing in `onSubscriptionUpdated` [source](https://github.com/better-auth/better-auth/blob/ef481d8dcce32d377b412ad1a96ecb8e604c1787/packages/stripe/src/hooks.ts). To fix this, update the `onSubscriptionUpdated` handler to safely extract and map these fields from the Stripe subscription object. For example: ```typescript const trial = subscriptionUpdated.trial_start != null && subscriptionUpdated.trial_end != null ? { trialStart: new Date(subscriptionUpdated.trial_start * 1000), trialEnd: new Date(subscriptionUpdated.trial_end * 1000), } : {}; await ctx.context.adapter.update({ model: "subscription", update: { // ...other fields ...trial, }, where: [{ field: "id", value: subscription.id }], }); ``` This ensures `trialStart` and `trialEnd` are updated when present, and avoids errors if Stripe sends `null` or `undefined` [source](https://github.com/better-auth/better-auth/issues/2345). Also, make sure you extract period fields from `subscription.items.data[0]` for Stripe API compatibility, and test with both trial and non-trial subscriptions to confirm the fix [source](https://github.com/better-auth/better-auth/issues/2269). If you patch this logic, your trial fields should update as expected. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/bde6ea5b-d4bd-44d4-9be9-905a0e2569f1?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/bde6ea5b-d4bd-44d4-9be9-905a0e2569f1?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/bde6ea5b-d4bd-44d4-9be9-905a0e2569f1?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/bde6ea5b-d4bd-44d4-9be9-905a0e2569f1?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/bde6ea5b-d4bd-44d4-9be9-905a0e2569f1?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/bde6ea5b-d4bd-44d4-9be9-905a0e2569f1?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/bde6ea5b-d4bd-44d4-9be9-905a0e2569f1?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4046)
Author
Owner

@claudesortwell commented on GitHub (Aug 20, 2025):

Here is a patch package that fixes this issue

diff --git a/node_modules/@better-auth/stripe/dist/index.cjs b/node_modules/@better-auth/stripe/dist/index.cjs
index ef6e8ec..147bd7d 100644
--- a/node_modules/@better-auth/stripe/dist/index.cjs
+++ b/node_modules/@better-auth/stripe/dist/index.cjs
@@ -280,6 +280,14 @@ const subscriptions = {
       seats: {
         type: "number",
         required: false
+      },
+      trialStart: {
+        type: "date",
+        required: false
+      },
+      trialEnd: {
+        type: "date",
+        required: false
       }
     }
   }
diff --git a/node_modules/@better-auth/stripe/dist/index.mjs b/node_modules/@better-auth/stripe/dist/index.mjs
index 625c0e4..a169964 100644
--- a/node_modules/@better-auth/stripe/dist/index.mjs
+++ b/node_modules/@better-auth/stripe/dist/index.mjs
@@ -264,6 +264,14 @@ const subscriptions = {
       seats: {
         type: "number",
         required: false
+      },
+      trialStart: {
+        type: "date",
+        required: false
+      },
+      trialEnd: {
+        type: "date",
+        required: false
       }
     }
   }
diff --git a/node_modules/@better-auth/stripe/src/schema.ts b/node_modules/@better-auth/stripe/src/schema.ts
index 40a42f2..dedaeb3 100644
--- a/node_modules/@better-auth/stripe/src/schema.ts
+++ b/node_modules/@better-auth/stripe/src/schema.ts
@@ -42,6 +42,14 @@ export const subscriptions = {
 				type: "number",
 				required: false,
 			},
+			trialStart: {
+				type: "date",
+				required: false,
+			},
+			trialEnd: {
+				type: "date",
+				required: false,
+			},
 		},
 	},
 } satisfies AuthPluginSchema;

@claudesortwell commented on GitHub (Aug 20, 2025): Here is a patch package that fixes this issue ``` diff --git a/node_modules/@better-auth/stripe/dist/index.cjs b/node_modules/@better-auth/stripe/dist/index.cjs index ef6e8ec..147bd7d 100644 --- a/node_modules/@better-auth/stripe/dist/index.cjs +++ b/node_modules/@better-auth/stripe/dist/index.cjs @@ -280,6 +280,14 @@ const subscriptions = { seats: { type: "number", required: false + }, + trialStart: { + type: "date", + required: false + }, + trialEnd: { + type: "date", + required: false } } } diff --git a/node_modules/@better-auth/stripe/dist/index.mjs b/node_modules/@better-auth/stripe/dist/index.mjs index 625c0e4..a169964 100644 --- a/node_modules/@better-auth/stripe/dist/index.mjs +++ b/node_modules/@better-auth/stripe/dist/index.mjs @@ -264,6 +264,14 @@ const subscriptions = { seats: { type: "number", required: false + }, + trialStart: { + type: "date", + required: false + }, + trialEnd: { + type: "date", + required: false } } } diff --git a/node_modules/@better-auth/stripe/src/schema.ts b/node_modules/@better-auth/stripe/src/schema.ts index 40a42f2..dedaeb3 100644 --- a/node_modules/@better-auth/stripe/src/schema.ts +++ b/node_modules/@better-auth/stripe/src/schema.ts @@ -42,6 +42,14 @@ export const subscriptions = { type: "number", required: false, }, + trialStart: { + type: "date", + required: false, + }, + trialEnd: { + type: "date", + required: false, + }, }, }, } satisfies AuthPluginSchema; ```
Author
Owner

@Ooscaar commented on GitHub (Aug 20, 2025):

@claudesortwell That's very nice.
Do you mind If I create a PR with the fixes? Or if you want to do it no problem

@Ooscaar commented on GitHub (Aug 20, 2025): @claudesortwell That's very nice. Do you mind If I create a PR with the fixes? Or if you want to do it no problem
Author
Owner

@claudesortwell commented on GitHub (Aug 23, 2025):

@claudesortwell That's very nice.
Do you mind If I create a PR with the fixes? Or if you want to do it no problem

Happy for you to do the pr, sorry just don't have much time lately

@claudesortwell commented on GitHub (Aug 23, 2025): > @claudesortwell That's very nice. > Do you mind If I create a PR with the fixes? Or if you want to do it no problem Happy for you to do the pr, sorry just don't have much time lately
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1723