[PR #7392] Add support for Stripe Elements (Embedded) #7279

Open
opened 2026-03-13 13:30:35 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/7392
Author: @Jumaron
Created: 1/15/2026
Status: 🔄 Open

Base: canaryHead: feat/stripe-elements-support1


📝 Commits (10+)

📊 Changes

7 files changed (+1256 additions, -25 deletions)

View changed files

📝 packages/stripe/src/client.ts (+2 -0)
packages/stripe/src/embedded-checkout.ts (+438 -0)
📝 packages/stripe/src/index.ts (+2 -0)
📝 packages/stripe/src/routes.ts (+113 -9)
packages/stripe/test/embedded-checkout.test.ts (+321 -0)
📝 packages/stripe/test/stripe-organization.test.ts (+5 -3)
📝 packages/stripe/test/stripe.test.ts (+375 -13)

📄 Description

Here is the updated PR description, adjusted to reflect the dedicated endpoints and the new client-side helper (embedded-checkout.ts) that handles the Stripe.js loading dynamically.

PR Description

Summary

This Pull Request adds full support for Stripe Embedded Checkout (ui_mode: 'embedded') to the @better-auth/stripe plugin.

It introduces server-side endpoints to generate embedded sessions and, significantly, a client-side helper that dynamically handles Stripe.js loading and mounting. This allows developers to integrate embedded payments without manually managing script tags or installing the heavy @stripe/stripe-js dependency.

Changes

  • New Endpoint: POST /subscription/create-embedded-checkout
    • Generates a Stripe Checkout Session with ui_mode: 'embedded'.
    • Returns the clientSecret required to mount the form on the client.
    • Handles subscription upgrades, seat adjustments, and metadata inheritance.
  • New Endpoint: GET /subscription/checkout-status
    • Retrieves the status of a session (open, complete, expired) to verify payments on the return page without relying solely on webhooks.
  • New Client Helper: createStripeEmbeddedCheckout (embedded-checkout.ts)
    • A lightweight wrapper that dynamically loads stripe.js only when needed.
    • Provides a clean API (mount, unmount, destroy) to manage the checkout lifecycle.
    • Fully typed configuration for appearance and locale.
  • Client SDK Update:
    • Updated stripeClient definition to include type inference for the new routes.
    • Exported the new client helpers.
  • Tests:
    • Added comprehensive unit tests for embedded-checkout.ts to ensure lifecycle management (mount/unmount) works correctly.

Motivation

Previously, the plugin only supported the hosted UI mode, which forces a redirect. Embedded Checkout is the modern standard for Stripe integrations.

Benefits:

  1. Better UX: The user never leaves the application.
  2. Zero-Dependency Client: The new helper functions allow usage without adding @stripe/stripe-js to the project's package.json.
  3. Higher Conversion: Fewer redirects reduce drop-off.
  4. Modern Security: Handles 3D Secure and SCA authentication natively within the iframe.

Example Usage

1. Client Side (Mounting the Checkout)

Using the new built-in helper:

import { authClient } from "@/lib/auth-client";
import { createStripeEmbeddedCheckout } from "@better-auth/stripe/client";

// 1. Initialize the helper (loads Stripe.js automatically)
const stripeCheckout = createStripeEmbeddedCheckout({
    publishableKey: "pk_test_...",
    onComplete: () => console.log("Checkout complete!"),
});

// 2. Get the Client Secret from Better Auth
const { data } = await authClient.subscription.createEmbeddedCheckout({
    plan: "pro",
    annual: true,
    returnUrl: `${window.location.origin}/return?session_id={CHECKOUT_SESSION_ID}`,
});

// 3. Mount the form
if (data) {
    await stripeCheckout.mount({
        clientSecret: data.clientSecret,
        container: "#checkout-container", // ID of your div
    });
}

2. Client Side (Return Page Verification)

const sessionId = searchParams.get("session_id");

const { data } = await authClient.subscription.getCheckoutStatus({
    query: { sessionId }
});

if (data?.status === 'complete') {
    console.log("Payment successful for:", data.customerEmail);
}

Checklist

  • Added embedded-checkout.ts helper and types.
  • Added new routes to stripeClient type definition.
  • Implemented Zod schemas for input validation.
  • Added unit tests for the embedded checkout client wrapper.
  • Tested with user and organization customer types.
  • Verified trial logic and duplicate subscription prevention.

Closes Nothing, this is personal.

Disclosure: Opus was cooking.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/7392 **Author:** [@Jumaron](https://github.com/Jumaron) **Created:** 1/15/2026 **Status:** 🔄 Open **Base:** `canary` ← **Head:** `feat/stripe-elements-support1` --- ### 📝 Commits (10+) - [`89392d3`](https://github.com/better-auth/better-auth/commit/89392d3ddf9318e082383d591c593801e73ec184) init - [`12477a3`](https://github.com/better-auth/better-auth/commit/12477a3cf43441aa68b5df0bfa2f015210fb28d4) oops - [`0ae3dc7`](https://github.com/better-auth/better-auth/commit/0ae3dc76bd5fa1092bd28faf6c31eb3b7d25e7b5) oops2 - [`106434e`](https://github.com/better-auth/better-auth/commit/106434ef4369e6856a6610dc64514ff5cf564103) fix lint - [`c198a8e`](https://github.com/better-auth/better-auth/commit/c198a8ee5411885987323b4c9f3db529c80fa57e) Add better embedded-checkout integration - [`86bb1b7`](https://github.com/better-auth/better-auth/commit/86bb1b7a7c2365ea6ba3ffc38bb9aa08f0afbcd5) use Upgrade instead - [`1dae8cf`](https://github.com/better-auth/better-auth/commit/1dae8cf0004e823a7c56a6aa1f1eabfa4682e027) Merge branch 'canary' into feat/stripe-elements-support1 - [`27ebe63`](https://github.com/better-auth/better-auth/commit/27ebe636a5261ce209d14156f14d79aae72567d4) lingint - [`601464f`](https://github.com/better-auth/better-auth/commit/601464f4aacbb8f68feb02cbec160c0d93539b96) Merge branch 'feat/stripe-elements-support1' of https://github.com/Jumaron/better-auth into feat/stripe-elements-support1 - [`810d083`](https://github.com/better-auth/better-auth/commit/810d083b8f23a605aa39f9819874c558f1573fd1) Merge branch 'canary' into feat/stripe-elements-support1 ### 📊 Changes **7 files changed** (+1256 additions, -25 deletions) <details> <summary>View changed files</summary> 📝 `packages/stripe/src/client.ts` (+2 -0) ➕ `packages/stripe/src/embedded-checkout.ts` (+438 -0) 📝 `packages/stripe/src/index.ts` (+2 -0) 📝 `packages/stripe/src/routes.ts` (+113 -9) ➕ `packages/stripe/test/embedded-checkout.test.ts` (+321 -0) 📝 `packages/stripe/test/stripe-organization.test.ts` (+5 -3) 📝 `packages/stripe/test/stripe.test.ts` (+375 -13) </details> ### 📄 Description Here is the updated PR description, adjusted to reflect the dedicated endpoints and the new client-side helper (`embedded-checkout.ts`) that handles the Stripe.js loading dynamically. ### PR Description ## Summary This Pull Request adds full support for **Stripe Embedded Checkout** (`ui_mode: 'embedded'`) to the `@better-auth/stripe` plugin. It introduces server-side endpoints to generate embedded sessions and, significantly, a **client-side helper** that dynamically handles Stripe.js loading and mounting. This allows developers to integrate embedded payments without manually managing script tags or installing the heavy `@stripe/stripe-js` dependency. ## Changes - **New Endpoint:** `POST /subscription/create-embedded-checkout` - Generates a Stripe Checkout Session with `ui_mode: 'embedded'`. - Returns the `clientSecret` required to mount the form on the client. - Handles subscription upgrades, seat adjustments, and metadata inheritance. - **New Endpoint:** `GET /subscription/checkout-status` - Retrieves the status of a session (open, complete, expired) to verify payments on the return page without relying solely on webhooks. - **New Client Helper:** `createStripeEmbeddedCheckout` (`embedded-checkout.ts`) - A lightweight wrapper that dynamically loads `stripe.js` only when needed. - Provides a clean API (`mount`, `unmount`, `destroy`) to manage the checkout lifecycle. - Fully typed configuration for appearance and locale. - **Client SDK Update:** - Updated `stripeClient` definition to include type inference for the new routes. - Exported the new client helpers. - **Tests:** - Added comprehensive unit tests for `embedded-checkout.ts` to ensure lifecycle management (mount/unmount) works correctly. ## Motivation Previously, the plugin only supported the `hosted` UI mode, which forces a redirect. Embedded Checkout is the modern standard for Stripe integrations. **Benefits:** 1. **Better UX:** The user never leaves the application. 2. **Zero-Dependency Client:** The new helper functions allow usage without adding `@stripe/stripe-js` to the project's `package.json`. 3. **Higher Conversion:** Fewer redirects reduce drop-off. 4. **Modern Security:** Handles 3D Secure and SCA authentication natively within the iframe. ## Example Usage ### 1. Client Side (Mounting the Checkout) Using the new built-in helper: ```typescript import { authClient } from "@/lib/auth-client"; import { createStripeEmbeddedCheckout } from "@better-auth/stripe/client"; // 1. Initialize the helper (loads Stripe.js automatically) const stripeCheckout = createStripeEmbeddedCheckout({ publishableKey: "pk_test_...", onComplete: () => console.log("Checkout complete!"), }); // 2. Get the Client Secret from Better Auth const { data } = await authClient.subscription.createEmbeddedCheckout({ plan: "pro", annual: true, returnUrl: `${window.location.origin}/return?session_id={CHECKOUT_SESSION_ID}`, }); // 3. Mount the form if (data) { await stripeCheckout.mount({ clientSecret: data.clientSecret, container: "#checkout-container", // ID of your div }); } ``` ### 2. Client Side (Return Page Verification) ```typescript const sessionId = searchParams.get("session_id"); const { data } = await authClient.subscription.getCheckoutStatus({ query: { sessionId } }); if (data?.status === 'complete') { console.log("Payment successful for:", data.customerEmail); } ``` ## Checklist - [x] Added `embedded-checkout.ts` helper and types. - [x] Added new routes to `stripeClient` type definition. - [x] Implemented Zod schemas for input validation. - [x] Added unit tests for the embedded checkout client wrapper. - [x] Tested with `user` and `organization` customer types. - [x] Verified trial logic and duplicate subscription prevention. --- Closes Nothing, this is personal. Disclosure: Opus was cooking. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-03-13 13:30:35 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#7279