Stripe 插件

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

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

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

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

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

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

核心功能

安装

使用任意 JavaScript 包管理器(如 pnpmnpmYarn)安装插件:

  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/restPOSTPayload 访问控制 后代理 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';