craftcms/commerce-stripe

为Craft Commerce 5.0+提供的Stripe集成


README

Stripe for Craft Commerce icon

Stripe for Craft Commerce

为您的Craft Commerce商店提供灵活的支付处理,由Stripe提供支持。

此插件提供了一种网关,利用支付意向API来支持流行的支付方式,如...

  • 主要借记卡和信用卡
  • Apple Pay
  • Google Pay
  • Cash App
  • Afterpay、Affirm以及其他分期付款计划
  • ECH和直接银行账户转账

...以及更多内容

注意

需要3.x文档

要求

  • Craft CMS 5.1或更高版本
  • Craft Commerce 5.0或更高版本
  • Stripe API版本 2022-11-15

安装

您可以从插件商店或使用Composer安装此插件。

从插件商店

前往您的项目控制面板中的插件商店,搜索“Stripe for Craft Commerce”,然后在侧边栏中点击安装

使用Composer

打开您的终端,并运行以下命令

# Switch your project’s directory:
cd /path/to/my-project

# Require the package with Composer:
composer require craftcms/commerce-stripe

# Install the plugin with Craft:
php craft install/plugin commerce-stripe

设置

要添加Stripe支付网关,请打开Craft控制面板,导航到Commerce系统设置网关,然后点击+ 新网关

您的网关的名称应使管理员和客户都能理解(特别是如果您正在使用示例模板)。

密钥

网关下拉菜单中选择Stripe,然后提供以下信息

  • 可发布API密钥
  • 密钥API密钥
  • Webhook签名密钥(有关详细信息,请参阅Webhooks

您的可发布API密钥密钥API密钥可以在您的Stripe仪表板中的(或从其中生成)开发者API密钥选项卡中找到。有关Stripe API密钥的更多信息。

注意

为了防止密钥泄露到项目配置中,请将它们放入您的.env文件中,然后在网关设置中使用特殊的环境变量语法

Stripe提供了不同的测试密钥——在准备发布之前使用这些密钥,然后在实时服务器的.env文件中替换测试密钥。

Webhooks

一旦网关已保存(并且它有一个ID),重新访问其编辑屏幕将显示一个可以复制到您的Stripe仪表板中新WebhookWebhook URL。将为您生成一个签名密钥——将其保存到您的.env文件中与其他密钥一起,然后返回网关设置屏幕,并用变量的名称填充Webhook签名密钥字段。

警告

如果签名密钥缺失或无效,Webhooks将不会处理!

我们建议在Stripe中启用所有可用的webhook事件。插件没有使用的事件将被忽略。

请记住,每个环境的webhook URL都将不同!由于项目配置的工作方式,网关在生产环境和开发环境中的ID可能不同。

本地开发

您的本地环境很少暴露在公共互联网上,因此Stripe无法发送测试webhook。您有两个测试webhook的选项

  1. 使用Stripe CLI,将以下命令中的URL替换为网关设置屏幕中的URL

    stripe listen --forward-to "my-project.ddev.site/index.php?action=commerce/webhooks/process-webhook&gateway=1"

    此命令将创建一个临时webhook和签名密钥,您应该将其添加到.env文件中,并查看Stripe的测试webhook文章以获取更多信息。

  2. 使用DDEV的share命令,并在Stripe中手动配置webhook时使用公共Ngrok URL。

从4.x升级到5.x

5.0版本在很大程度上与4.x兼容。查看以下部分以确保您的网站(以及任何自定义)保持功能正常。

支付表单

为了支持通过Stripe(如Apple Pay和Google Pay)提供的所有支付方式,该插件专用于使用支付意向API。

历史上,gateway.getPaymentFormHtml()输出一个用于在客户端标记信用卡的基本表单,然后仅提交结果标记到commerce/payments/pay操作,并在后端捕获付款。使用此过程的自定义支付表单将继续工作。

现在,输出是一个更灵活的支付元素表单,它利用Stripes的现代支付意向API。过程看起来像这样

  1. 向后台提交一个请求到commerce/payments/pay(没有支付方式);
  2. Commerce创建一个包含订单一些信息的支付意向,然后设置一个内部Transaction记录来跟踪其状态;
  3. 支付意向的client_secret返回到前端;
  4. 使用密钥初始化Stripe JS SDK,客户可以从可用的支付方式中选择;

有关如何配置新支付表单的详细信息见下文。

API版本

由于某些支付意向功能的可用性,您的Stripe账户必须配置为使用至少版本2022-11-15的API。

订阅

由于与Stripe处理默认支付方式的兼容性不一致,已在同一请求中创建新支付源以支持订阅的功能已被弃用。在未来版本中,订阅端点将专注于启动订阅,而不是接受付款信息。目前,使用传统费用工作流程的自定义订阅表单将继续工作。

我们建议以下策略之一

  1. 在选择订阅之前设置支付源。设计您的订阅过程以捕获付款细节,然后选择计划。

  2. 在同一页面上使用Ajax创建支付源。 这仅应在配置客户的第一个支付方式时受支持。您可以对Commerce的payment-sources/add操作进行预先检查Ajax请求以设置支付方式,然后使用正常的订阅表单—Stripe将使用该唯一的支付源与订阅一起使用。

  3. 向用户展示订阅将关联的支付方式。 这是一个非常好的想法,无论怎样——确认用户将使用他们现有的哪些支付方式。您可以在Twig中找到客户的默认支付方式,如下所示

    {# Assuming a `plan` variable exists in this context... #}
    {% set paymentSources = craft.commerce.paymentSources.getAllGatewayPaymentSourcesByCustomerId(plan.gatewayId, currentUser.id) %}
    {% set primaryPaymentSource = paymentSources | filter((ps) => ps.getIsPrimary()) | first %}
    
    {% if primaryPaymentSource %}
      {# Show some information about the source: #}
      This subscription will be billed to: {{ primaryPaymentSource.description }}
    
      {# Then, output the form! #}
    {% else %}
      <p>You must set up a payment method to start a subscription!</p>
      {{ tag('a', {
        href: siteUrl('account/payment-sources'),
        text: 'Add a payment method',
      }) }}
    {% endif %}

    [!注意] 如果您的商店使用多个网关,客户的默认支付方式可能不一定属于与计划相同的网关,因此 primaryPaymentSource 可能 为空,即使他们已经选择了一个。

同步

更新后,我们建议运行新的 支付方式同步命令,以确保您的商店数据与Stripe的记录保持最新。

配置设置

这些选项通过 config/commerce-stripe.php 设置。

chargeInvoicesImmediately

对于自动付款的订阅,Stripe会在尝试收费前1-2小时创建账单。通过将此设置为 true,您可以强制Stripe立即收取此账单。

警告

此设置影响 所有 商业安装中的Stripe网关。

订阅

Stripe插件在商业的订阅系统和Stripe的 计费 API之间提供接口。

创建订阅计划

计划必须首先在 Stripe仪表板 中进行配置。

  1. 在Craft控制面板中,转到 Commerce商店设置计划,然后点击 + 新建订阅计划;
  2. 网关 下拉菜单中选择 Stripe
  3. 网关计划 下拉菜单中选择计划名称;

注意

Stripe中的计划分别针对实时和测试模式进行配置!您可能看到的计划列表取决于您正在使用哪些密钥。

订阅选项

除了您POST到商业的 commerce/subscriptions/subscribe 动作 的值之外,Stripe网关还支持以下选项

trialDays

试用期天数到期后,将开始第一个完整的计费周期。默认值为 0

取消选项

除了您POST到商业的 commerce/subscriptions/cancel 动作 的值之外,Stripe网关还支持以下选项

cancelImmediately

如果此参数设置为 true,则订阅将立即取消。Stripe将其视为同时取消和“删除”(就webhook而言)——但订阅记录仍然可用。默认情况下,订阅被标记为已取消,并将与当前计费周期一起结束。默认为 false

注意

立即取消订阅意味着它不能被重新激活。

计划切换选项

除了您POST到商业的 commerce/subscriptions/switch 动作 的值之外,Stripe网关还支持以下选项

prorate

如果此参数设置为 true,则订阅切换将 按比例计算。默认为 false

billImmediately

如果此参数设置为 true,则订阅切换将立即计费。否则,成本(或信用,如果切换到更便宜的计划时将 prorate 设置为 true)将应用于下一个账单。

警告

如果计费周期不同,计划切换将立即计费,此参数将被忽略。

重新激活选项

重新激活订阅时没有可用的自定义选项。

事件

插件提供了您可以使用以修改集成行为的几个事件。

支付事件

buildGatewayRequest

在创建支付意向的过程中与 Stripe 通信时,插件有机会提供额外的元数据。这使您几乎可以完全控制 Stripe 看到的数据,以下是需要考虑的因素:

  • Transaction 模型(通过事件的 transaction 属性获取)的更改将不会保存;
  • 网关自动设置 order_idorder_numberorder_short_numbertransaction_idtransaction_referencedescriptionclient_ip 等元数据键;
  • request 属性下的 amountcurrency 键的更改将被忽略,因为这些是网关以可预测方式运行所必需的;
use craft\commerce\models\Transaction;
use craft\commerce\stripe\events\BuildGatewayRequestEvent;
use craft\commerce\stripe\gateways\PaymentIntents;
use yii\base\Event;

Event::on(
    PaymentIntents::class,
    PaymentIntents::EVENT_BUILD_GATEWAY_REQUEST,
    function(BuildGatewayRequestEvent $e) {
        /** @var Transaction $transaction */
        $transaction = $e->transaction;
        $order = $transaction->getOrder();

        $e->request['metadata']['shipping_method'] = $order->shippingMethodHandle;
    }
);

注意

订阅事件 将单独处理。

receiveWebhook

除了通用的 craft\commerce\services\Webhooks::EVENT_BEFORE_PROCESS_WEBHOOK 事件 之外,您还可以监听 craft\commerce\stripe\gateways\PaymentIntents::EVENT_RECEIVE_WEBHOOK。此事件仅在验证 webhook 的真实性后发出,但它不会对是否采取了对它的响应作出任何指示。

use craft\commerce\stripe\events\ReceiveWebhookEvent;
use craft\commerce\stripe\gateways\PaymentIntents;
use yii\base\Event;

Event::on(
    PaymentIntents::class,
    PaymentIntents::EVENT_RECEIVE_WEBHOOK,
    function(ReceiveWebhookEvent $e) {
        if ($e->webhookData['type'] == 'charge.dispute.created') {
            if ($e->webhookData['data']['object']['amount'] > 1000000) {
                // Be concerned that a USD 10,000 charge is being disputed.
            }
        }
    }
);

webhookData 总是包含一个 type 键,它确定了 data 中所有内容的模式。请参阅 Stripe 文档了解预期的数据类型。

订阅事件

createInvoice

当 Stripe 发票创建时,插件有机会执行某些操作。这通常在处理 webhook 的过程中发出。

use craft\commerce\stripe\events\CreateInvoiceEvent;
use craft\commerce\stripe\gateways\PaymentIntents;
use yii\base\Event;

Event::on(
    PaymentIntents::class, 
    PaymentIntents::EVENT_CREATE_INVOICE,
    function(CreateInvoiceEvent $e) {
        if ($e->invoiceData['billing'] === 'send_invoice') {
            // Forward this invoice to the accounting department.
        }
    }
);

beforeSubscribe

在订阅时,插件有机会调整订阅参数。

use craft\commerce\stripe\events\SubscriptionRequestEvent;
use craft\commerce\stripe\gateways\PaymentIntents;
use yii\base\Event;

Event::on(
    PaymentIntents::class,
    PaymentIntents::EVENT_BEFORE_SUBSCRIBE,
    function(SubscriptionRequestEvent $e) {
        /** @var craft\commerce\base\Plan $plan */
        $plan = $e->plan;

        /** @var craft\elements\User $user */
        $user = $e->user;

        // Add something to the metadata:
        $e->parameters['metadata']['name'] = $user->fullName;
        unset($e->parameters['metadata']['another_property']);
    }
);

Commerce 还有一个 通用订阅事件,该事件在通过 任何 网关创建的订阅中发出。

计费门户

现在可以为客户生成一个链接到 Stripe 计费门户,以便他们管理自己的信用卡和计划。

<a href="{{ gateway.billingPortalUrl(currentUser) }}">Manage your billing account</a>

传递一个 returnUrl 参数,在客户完成操作后将他们返回到特定的站点页面

{{ gateway.billingPortalUrl(currentUser, 'myaccount') }}

可以使用第三个 configurationId 参数选择特定的 Stripe 客户门户配置。此值必须与在关联的 Stripe 账户中通过 API 创建的现有配置值相匹配;

{{ gateway.billingPortalUrl(currentUser, 'myaccount', 'config_12345') }}

注意

登录用户也可以使用 commerce-stripe/customers/billing-portal-redirect 操作进行重定向。当使用此方法时,不支持 configurationId 参数。

同步客户支付方式

现在将直接在 Stripe 客户门户中创建的支付方式同步回 Commerce。客户的默认支付方式也将同步。

注意

必须配置 webhook 才能按预期工作!

要执行初始同步,请运行 commerce-stripe/sync/payment-sources 控制台命令

php craft commerce-stripe/sync/payment-sources

创建 Stripe 支付表单

要渲染 Stripe Elements 支付表单,获取网关的引用,然后调用其 getPaymentFormHtml() 方法

{% set cart = craft.commerce.carts.cart %}
{% set gateway = cart.gateway %}

<form method="POST">
  {{ csrfInput() }}
  {{ actionInput('commerce/payments/pay') }}

  {% namespace gateway.handle|commercePaymentFormNamespace %}
    {{ gateway.getPaymentFormHtml({})|raw }}
  {% endnamespace %}

  <button>Pay</button>
</form>

这假设您已在之前的结账步骤中提供了选择网关的方式。如果您的商店只使用单个网关,您可以在支付时静态地获取网关的引用并设置它

{% set gateway = craft.commerce.gateways.getGatewayByHandle('myStripeGateway') %}

<form method="POST">
  {{ csrfInput() }}
  {{ actionInput('commerce/payments/pay') }}

  {# Include *outside* the namespaced form inputs: #}
  {{ hiddenInput('gatewayId', gateway.id) }}

  {% namespace gateway.handle|commercePaymentFormNamespace %}
    {{ gateway.getPaymentFormHtml({})|raw }}
  {% endnamespace %}

  <button>Pay</button>
</form>

无论您如何使用此输出,它都将自动注册创建支付意向和引导 Stripe Elements 所需的所有必要 JavaScript。

自定义 Stripe 支付表单

getPaymentFormHtml() 接受一个具有以下任一键的数组

paymentFormType

elements(默认)

渲染包含所有支付方式类型的Stripe Elements表单,这些支付方式已在您的Stripe仪表板上启用。(请参阅此处了解详情)某些方法可能因订单总额或货币不符合该方法的要求,或者在该环境中不支持而隐藏。

{% set params = {
  paymentFormType: 'elements',
} %}

{{ cart.gateway.getPaymentFormHtml(params)|raw }}

这是默认的paymentFormType,大多数安装不需要声明或更改它。

结账

这会生成一个可重定向到托管Stripe Checkout页面的表单。此选项只能用于commerce/payments/pay表单内部。此选项忽略所有其他参数。

{% set params = {
  paymentFormType: 'checkout',
} %}

{{ gateway.getPaymentFormHtml(params)|raw }}

外观

您可以传递一个外观选项数组到stripe.elements()配置器函数。该函数需要与Elements Appearance API兼容的数据。

{% set params = {
  appearance: {
    theme: 'stripe'
  }
} %}
{{ cart.gateway.getPaymentFormHtml(params)|raw }}
{% set params = {
  appearance: {
    theme: 'night',
    variables: {
      colorPrimary: '#0570de'
    }
  }
} %}
{{ cart.gateway.getPaymentFormHtml(params)|raw }}

元素选项

修改传递给elements.create()工厂函数的支付元素选项

{% set params = {
  elementOptions: {
    layout: {
      type: 'tabs',
      defaultCollapsed: false,
      radios: false,
      spacedAccordionItems: false
    }
  }
} %}
{{ cart.gateway.getPaymentFormHtml(params)|raw }}

默认的elementOptions值仅定义了布局

{% set params = {
  elementOptions: {
    layout: {
      type: 'tabs'
    }
  }
} %}

order(可选)

order键应该是对Commerce Order模型的引用,这通常是在您的模板中的当前cart变量。

如果提供了,则将账单详情添加到elementOptions的默认defaultValues数组中。

{% set params = {
  order: cart,
} %}

{{ cart.gateway.getPaymentFormHtml(params)|raw }}

如果您没有将order传递给支付表单,您可以选择使用elementOptionsdefaultValues键手动填充账单详情

{% set params = {
  elementOptions: {
    defaultValues: {
      name: 'Jane Doe',
      address: {
        line1: '123 Main St',
        city: 'Anytown',
        state: 'NY',
        postal_code: '12345',
        country: 'US',
      },
    }
    ...

errorMessageClasses

错误消息显示在表单上方的容器中。您可以为此元素添加类以更改其样式。

{% set params = {
  errorMessageClasses: 'bg-red-200 text-red-600 my-2 p-2 rounded',
} %}

{{ cart.gateway.getPaymentFormHtml(params)|raw }}

submitButtonClassessubmitButtonText

自定义用于提交支付表单的按钮的样式和文本。

{% set params = {
  submitButtonClasses: 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white my-2',
  submitButtonText: 'Pay',
} %}

{{ cart.gateway.getPaymentFormHtml(params)|raw }}