Stripe 插件

https://www.npmjs.com/package/@payloadcms/plugin-stripe

通过这个插件,你可以轻松将 Stripe 集成到 Payload 中。只需提供你的 Stripe 凭证,该插件就会在两个平台之间建立双向通信通道。这使你能够轻松地在两者之间同步数据,并通过 Payload 的 访问控制 代理 Stripe REST API。使用此插件可以完全将计费工作交给 Stripe,同时保留对应用程序数据的完全控制。

例如,你可能正在构建一个电子商务或 SaaS 应用程序,其中有一个 products 或 plans 集合,需要一次性付款或订阅。你可以将这些产品与 Stripe 关联,然后轻松订阅与计费相关的事件来执行业务逻辑,例如处理有效购买或订阅取消。

要在前端构建结账流程,你可以使用 Stripe Checkout,也可以使用 Stripe Web Elements 从头开始构建完全自定义的结账体验。要构建完全自定义的安全客户仪表板,你可以利用 Payload 的访问控制来限制对 Stripe 资源的访问,这样用户就无需离开你的网站来管理他们的账户。

这个插件的优势在于,你的应用程序的全部内容和业务逻辑都可以在 Payload 中处理,而 Stripe 只负责计费和支付处理。你可以构建一个完全专有的应用程序,它可以在你拥有的 API 和数据库上无限定制和扩展。像 Shopify 或 BigCommerce 这样的托管服务可能会分割你的应用程序内容,然后向你收取访问费用。

该插件是完全开源的,源代码可以在这里找到。 如果需要帮助,请查看我们的 社区帮助。如果发现错误, 请 提交新问题 并提供尽可能多的细节。

核心功能

安装

使用任意 JavaScript 包管理器(如 pnpm、npm 或 Yarn)安装插件:

  pnpm add @payloadcms/plugin-stripe

基本用法

在你的 Payload 配置 的 plugins 数组中,调用插件并传入 选项:

import { buildConfig } from 'payload'
import { stripePlugin } from '@payloadcms/plugin-stripe'

const config = buildConfig({
  plugins: [
    stripePlugin({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
    }),
  ],
})

export default config

选项

选项类型默认值描述
stripeSecretKey *stringundefined你的 Stripe 密钥
stripeWebhooksEndpointSecretstringundefined你的 Stripe webhook 端点密钥
restbooleanfalse当设为 true 时,开启 /api/stripe/rest 端点
webhooksobject or functionundefined可以是一个处理所有 webhook 事件的函数,也可以是一个按事件名称组织的 Stripe webhook 处理器对象
syncarrayundefined同步配置数组
logsbooleanfalse当设为 true 时,在控制台实时输出同步事件日志

* 星号表示该属性为必填项。

端点

以下自定义端点会自动为你开启:

端点方法描述
/api/stripe/restPOST在 Payload 访问控制 后代理 Stripe REST API 并返回结果。详情请参阅 REST 代理 部分。
/api/stripe/webhooksPOST处理所有 Stripe webhook 事件
Stripe REST 代理

如果 rest 设置为 true,会将 Stripe REST API 代理到 Payload 访问控制 之后并返回结果。此标志仅应用于本地开发环境,更多安全注意事项请参阅下方的安全提示。

const res = await fetch(`/api/stripe/rest`, {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    // Authorization: `JWT ${token}` // 注意:如果不在浏览器环境中(如使用 curl 或 Postman)需要添加此项
  },
  body: JSON.stringify({
    stripeMethod: 'stripe.subscriptions.list',
    stripeArgs: [
      {
        customer: 'abc',
      },
    ],
  }),
})

如需在服务端代理 API,请使用 stripeProxy 函数。

注意:

这些路由中的 /api 部分可能会根据你的 Payload 配置设置而有所不同。

警告:

在生产环境中开放 REST 代理端点存在潜在安全风险。认证用户将拥有对 Stripe REST API 的开放访问权限。在生产环境中,请创建自己的端点并使用 stripeProxy 函数在服务端代理 Stripe API。

Webhooks

Stripe webhooks 用于从 Stripe 同步数据到 Payload。Webhooks 会监听你的 Stripe 账户事件,以便你可以触发相应的操作。按照以下步骤启用 webhooks。

开发环境:

  1. 使用 Stripe CLI 登录 stripe login
  2. 将事件转发到本地 stripe listen --forward-to localhost:3000/api/stripe/webhooks
  3. 将生成的密钥粘贴到 .env 文件中,命名为 STRIPE_WEBHOOKS_ENDPOINT_SECRET

生产环境:

  1. 登录 Stripe 仪表盘并创建新的 webhook
  2. 将 YOUR_DOMAIN_NAME/api/stripe/webhooks 粘贴为 "Webhook Endpoint URL"
  3. 选择要广播的事件类型
  4. 将生成的密钥粘贴到 .env 文件中,命名为 STRIPE_WEBHOOKS_ENDPOINT_SECRET
  5. 然后,使用插件配置中的 webhooks 部分来处理这些事件:
import { buildConfig } from 'payload'
import stripePlugin from '@payloadcms/plugin-stripe'

const config = buildConfig({
  plugins: [
    stripePlugin({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
      stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
      webhooks: {
        'customer.subscription.updated': ({ event, stripe, stripeConfig }) => {
          // 执行操作...
        },
      },
      // 注意:你也可以捕获所有 Stripe webhook 事件并自行处理事件类型
      // webhooks: (event, stripe, stripeConfig) => {
      //   switch (event.type): {
      //     case 'customer.subscription.updated': {
      //       // 执行操作...
      //       break;
      //     }
      //     default: {
      //       break;
      //     }
      //   }
      // }
    }),
  ],
})

export default config

完整的可用 webhooks 列表,请参见此处。

Node 节点

在服务器端,你应该直接使用 stripe npm 模块与 Stripe 交互。代码示例如下:

import Stripe from 'stripe'

const stripeSecretKey = process.env.STRIPE_SECRET_KEY
const stripe = new Stripe(stripeSecretKey, {
  apiVersion: '2022-08-01',
})

export const MyFunction = async () => {
  try {
    const customer = await stripe.customers.create({
      email: data.email,
    })

    // 执行操作...
  } catch (error) {
    console.error(error.message)
  }
}

或者,你也可以使用 stripeProxy 与 Stripe 交互,这正是 /api/stripe/rest 端点内部实现的方式。以下是相同的示例,但通过代理实现:

import { stripeProxy } from '@payloadcms/plugin-stripe'

export const MyFunction = async () => {
  try {
    const customer = await stripeProxy({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
      stripeMethod: 'customers.create',
      stripeArgs: [
        {
          email: data.email,
        },
      ],
    })

    if (customer.status === 200) {
      // 执行操作...
    }

    if (customer.status >= 400) {
      throw new Error(customer.message)
    }
  } catch (error) {
    console.error(error.message)
  }
}

同步功能

此选项会自动在 Payload 集合和 Stripe 资源之间建立基本同步。它会创建所有必要的钩子和 webhook 处理器,你唯一需要做的就是将 Payload 字段映射到对应的 Stripe 属性。当文档在 Stripe 或 Payload 中被创建、更新或删除时,变更会在两端同步反映。

注意:

如需启用双向同步,请确保设置 webhooks 并在配置中传入 stripeWebhooksEndpointSecret。

import { buildConfig } from 'payload'
import stripePlugin from '@payloadcms/plugin-stripe'

const config = buildConfig({
  plugins: [
    stripePlugin({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
      stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
      sync: [
        {
          collection: 'customers',
          stripeResourceType: 'customers',
          stripeResourceTypeSingular: 'customer',
          fields: [
            {
              fieldPath: 'name', // 这是你 Payload 配置中的字段
              stripeProperty: 'name', // 如需访问嵌套属性,可使用点表示法
            },
          ],
        },
      ],
    }),
  ],
})

export default config

注意:

由于 Stripe API 的限制,目前仅支持顶级字段的同步。这是因为每个 Stripe 对象都是独立实体, 难以抽象成简单的可重用库。未来我们可能会找到解决方案,但目前这类情况需要硬编码处理。

使用 sync 功能将实现以下效果:

  • 在每个集合上添加并维护一个 stripeID 只读字段,该字段由 Stripe 生成并用作交叉引用
  • 添加直接跳转到 Stripe.com 资源页面的链接
  • 在每个集合上添加并维护一个 skipSync 只读标志,防止钩子触发 webhook 时产生无限循环同步
  • 为每个集合添加以下钩子:
    • beforeValidate: createNewInStripe
    • beforeChange: syncExistingWithStripe
    • afterDelete: deleteFromStripe
  • 处理以下 Stripe webhook 事件:
    • STRIPE_TYPE.created: handleCreatedOrUpdated
    • STRIPE_TYPE.updated: handleCreatedOrUpdated
    • STRIPE_TYPE.deleted: handleDeleted

TypeScript

所有类型都可以直接导入:

import {
  StripeConfig,
  StripeWebhookHandler,
  StripeProxy,
  ...
} from '@payloadcms/plugin-stripe/types';