mcpField
B
better-auth-integration

better-auth-integration

github.com/dodopayments开发13184 次安装3 周前更新
代码仓库

关于 better-auth-integration

一个将 Better Auth 与 Dodo Payments 连接的集成包,面向需要在认证流程中加入支付能力的应用开发者。它通过已登录用户的会话提供结账、客户门户、订阅、用量和签名验证的 Webhook 端点。

主要功能

  1. 01注册时同步 Dodo 客户
  2. 02提供认证结账与客户门户
  3. 03列出订阅和支付记录
  4. 04提交计量用量事件
  5. 05验证并处理 Webhook 签名

适用场景

  1. 01用户注册时创建 Dodo 客户
  2. 02让已登录用户启动结账
  3. 03允许用户查看订阅和付款
  4. 04接收并处理 Dodo Webhook

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dodopayments/skills --skill better-auth-integration

安装后在 Claude Code 中输入/better-auth-integration确认已加载。

详细介绍

Better Auth 集成

使用 @dodopayments/better-auth 将 Better Auth 用户与 Dodo Payments 同步,并提供经过身份验证的结账、客户门户、订阅、支付、用量和 Webhook 端点。

适用场景

  • Better Auth 用户注册时,自动创建 Dodo 客户。
  • 为已登录用户发起结账,而不暴露 Dodo API 密钥。
  • 让用户打开客户门户,或列出其订阅和支付记录。
  • 为已登录的客户提交计量用量。
  • 通过 Better Auth 接收经过签名验证的 Dodo Webhook。

安装

npm install @dodopayments/better-auth dodopayments better-auth zod

需要 Better Auth 1.4 或更高的 1.x 版本。

此软件包导出小写服务端函数 dodopayments、checkout、portal、usage 和 webhooks。其客户端入口点导出 dodopaymentsClient。

服务端设置

每个端点都由 use 中的一个功能注册。请包含应用调用的每项功能;省略任何一项都会导致其路由返回 404。

import { betterAuth } from "better-auth";
import DodoPayments from "dodopayments";
import { checkout, dodopayments, portal, usage, webhooks } from "@dodopayments/better-auth";

const dodoPayments = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: "test_mode",
});

export const auth = betterAuth({
  database: {
    // your database config
  },
  plugins: [
    dodopayments({
      client: dodoPayments,
      createCustomerOnSignUp: true,
      // `user` here is Better Auth's base user: id, name, email, emailVerified,
      // image, createdAt, updatedAt. Anything else (phone number, company, plan)
      // must first be declared via `user.additionalFields` in your Better Auth
      // config, otherwise it does not exist on the type and will be undefined.
      getCustomerParams: (user) => ({
        metadata: { better_auth_user_id: user.id },
      }),
      use: [
        checkout({
          products: [
            { productId: "pdt_premium", slug: "premium-plan" },
          ],
          successUrl: "https://app.example.com/billing/success",
          authenticatedUsersOnly: true,
        }),
        portal(),
        usage(),
        webhooks({
          webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
          onPayload: async (payload) => {
            console.log("Verified Dodo webhook:", payload.type);
          },
        }),
      ],
    }),
  ],
});

products 将应用中的 slug 映射到 Dodo 产品 ID。authenticatedUsersOnly: true 要求用户必须拥有 Better Auth 会话才能结账。getCustomerParams 会在客户同步时添加应用元数据。

此插件会在 Better Auth 的 user 表中添加 dodoCustomerId 字段。添加插件后,请使用 Better Auth CLI 更新数据库架构:

npx auth generate   # generate the ORM schema or SQL migration, then apply it yourself
npx auth migrate    # or apply it directly (built-in Kysely adapter only)

浏览器客户端设置

import { createAuthClient } from "better-auth/react";
import { dodopaymentsClient } from "@dodopayments/better-auth/client";

export const authClient = createAuthClient({
  plugins: [dodopaymentsClient()],
});

这些是浏览器方法。它们会自动发送 Better Auth 会话 Cookie;不要向它们传递 request.headers。

创建结账会话

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

async function startCheckout() {
  const { data: session, error } = await authClient.dodopayments.checkoutSession({
    slug: "premium-plan",
    referenceId: "order_123",
  });

  if (error) {
    console.error("Checkout error:", error);
    return;
  }

  if (session) {
    window.location.href = session.url;
  }
}

slug 必须与 checkout({ products: [...] }) 中配置的产品匹配。referenceId 是可选的对账元数据。

打开客户门户

const { data: portalSession, error } =
  await authClient.dodopayments.customer.portal();

if (error) {
  console.error("Portal error:", error);
} else if (portalSession?.redirect) {
  window.location.href = portalSession.url;
}

门户端点要求用户已登录且已验证,并会解析该用户同步后的 Dodo 客户。

列出订阅和支付记录

const { data: subscriptions, error: subscriptionsError } =
  await authClient.dodopayments.customer.subscriptions.list({
    query: { page: 1, limit: 10, status: "active" },
  });

const { data: payments, error: paymentsError } =
  await authClient.dodopayments.customer.payments.list({
    query: { page: 1, limit: 10, status: "succeeded" },
  });

if (subscriptionsError || paymentsError) {
  console.error("Could not load billing history");
} else {
  console.log("Subscriptions:", subscriptions?.items);
  console.log("Payments:", payments?.items);
}

两个列表响应都使用 items 封装。订阅筛选条件接受 page、limit 和 status;支付筛选条件使用相同的分页字段和支付状态。

提交计量用量

在服务端启用 usage(),然后为已登录的客户提交事件:

const { data, error } = await authClient.dodopayments.usage.ingest({
  event_id: "evt_usage_123",
  event_name: "api_calls",
  metadata: { requests: 100 },
  timestamp: new Date(),
});

if (error) {
  console.error("Usage ingestion failed:", error);
} else {
  console.log("Ingested events:", data?.ingested_count);
}

usage.ingest 和 usage.meters.list 都要求用户已登录且电子邮件地址已验证。插件会从经过身份验证的会话中获取 customer_id。event_id 是事件的幂等键;重试同一事件时请重复使用它。

Webhook 端点

webhooks({ webhookKey, onPayload }) 会自行验证并处理请求。使用 Better Auth 默认的 /api/auth 基础路径时,请在 Dodo 控制面板中配置此 URL:

https://app.example.com/api/auth/dodopayments/webhooks

不要创建第二个路由,也不要调用 auth.api.dodopayments.verifyWebhook();该方法不存在。插件会读取原始请求,使用 webhookKey 验证 Standard Webhooks 签名,并调用 onPayload 以及任何已配置的特定事件回调。

已注册的端点

  • POST /dodopayments/checkout
  • POST /dodopayments/checkout-session
  • GET /dodopayments/customer/portal
  • GET /dodopayments/customer/subscriptions/list
  • GET /dodopayments/customer/payments/list
  • POST /dodopayments/usage/ingest
  • GET /dodopayments/usage/meters/list
  • POST /dodopayments/webhooks

Better Auth 会在这些插件路径前加上配置的 API 基础路径。

常见错误

仅启用 portal()。 除非 use 中也包含 checkout()、usage() 和 webhooks(),否则结账、用量和 Webhook 路由都不存在。

混用浏览器和服务端 API。 在浏览器代码中调用 authClient.dodopayments... 时,不要设置 headers 选项。处理服务端请求时,请单独使用 Better Auth 的服务端 API。

创建重复客户。 启用 createCustomerOnSignUp 后,不要再为同一用户调用 client.customers.create()。

根据浏览器状态授予访问权限。 应将已验证的 Webhook 事件或服务端 Dodo 查询结果作为权益的真实来源。

添加自定义 Webhook 验证器。 插件负责 /dodopayments/webhooks、签名验证和回调分发。

资源

评论

支持 Markdown