craftcms/stripe

Craft CMS 的 Stripe 集成

安装: 47

依赖关系: 0

建议者: 0

安全: 0

星标: 5

关注者: 6

分支: 0

开放问题: 13

类型:craft-plugin

1.1.0 2024-06-14 16:22 UTC

README

将您的 Craft 内容连接到 Stripe 强大的计费工具,并构建一个流畅的店面。

要求

此插件需要 Craft CMS 5.1.0 或更高版本,以及具有访问开发者功能的 Stripe 账户。

提示

从 Craft Commerce 转换?请查看专门的迁移部分。

安装

您可以通过应用内的插件商店或使用命令行安装此插件。

插件商店

访问您的安装控制面板的 插件商店 屏幕,然后搜索 Stripe

点击 安装 按钮,然后查看配置说明!

Composer

这些说明假设您正在使用 DDEV,但在其他环境中也可以运行类似命令。打开终端并运行...

# Navigate to the project directory:
cd /path/to/my-project

# Require the plugin with Composer:
ddev composer require craftcms/stripe

# Install the plugin with Craft:
ddev craft plugin/install stripe

配置

Stripe 插件从三个来源构建其配置

  • 项目配置 — 通过 Craft 控制面板中的“Stripe”→“设置”屏幕进行管理。
  • 插件配置文件 — 将 config/stripe.php 文件添加到您的项目中,并返回一个以 craft\stripe\models\Settings 类属性为键的选项映射。
  • 环境变量 — 一些选项可以直接作为环境变量设置。

API 密钥

Stripe 使用一对“可发布”和“秘密”密钥与他们的 API 通信。在您的 Stripe 账户中,切换到 测试模式,然后访问 开发者 部分,并获取您的开发密钥。

注意

有关 Stripe API 密钥 的更多信息。

将这些密钥添加到您的项目的 .env 文件中

STRIPE_PUBLISHABLE_KEY="pk_test_************************"
STRIPE_SECRET_KEY="sk_test_************************"

然后,在控制面板中,访问 Stripe设置,并在相应的字段中输入您选择的变量名称。Craft 将根据其在环境中的发现提供建议。

Webhooks

Webhooks 对于插件正确工作至关重要 — 它们允许将产品数据和非平台客户活动快速同步到您的站点。

提示

请确保执行初始的 同步 以导入现有的 Stripe 数据。

要在本地开发环境中测试 webhooks,我们建议使用 Stripe CLI 创建隧道并转发事件。按照您平台上的安装说明进行操作,然后运行

stripe listen --forward-to https://my-craft-project.ddev.site/stripe/webhooks/handle

注意

此处提供的主机名应与您访问项目的本地方式一致 — Stripe 不需要在公共互联网上解析它以测试 webhook 的投递!

CLI 工具将在准备就绪时通知您,并输出以 whsec_ 开头的 webhook 签名密钥。将此值添加到您的 .env 文件中,然后返回控制面板中的 Stripe设置

同步

Webhook 会在 Stripe 和 Craft 之间同步产品和客户数据,但它们只会报告 更改

您有两种方式用于初始导入 Stripe 数据

  • 小型产品目录通常可以使用控制面板实用工具:访问 实用工具Stripe 同步所有,然后点击 同步所有数据
  • 大型目录应通过命令行执行同步:如果您刚开始,请运行 ddev craft stripe/sync/all,或者使用更细粒度的 CLI 工具 导入特定类型的记录。

内容 + 字段

Stripe 的 产品价格订阅 都存储在 Craft 中的 元素 中。这意味着它们可以访问您期望的完整内容建模工具套件!

每个元素类型的字段布局在插件的 设置 屏幕中管理。

产品 URL

除了字段布局外,产品元素还支持 URI 格式模板 设置,它们在其他元素类型上的工作方式相同:当请求产品的 URL 时,Craft 会加载元素并将其传递到指定的模板,在 product 变量下。

注意

价格和订阅没有自己的 URL。您可以使用查询参数或 自定义路由 来根据特定的 URI 模式加载这些元素。

Craft Commerce 迁移

使用我们功能齐全的电子商务系统 Craft Commerce 的用户可以将现有订阅迁移到独立的 Stripe 插件,而不会丢失任何客户数据。

一旦您已完全升级到 Craft 5.1 和 Craft Commerce 5.0,请按照上述正常的 安装配置 指示进行操作。然后,运行以下这对控制台命令

# Pre-populate plugin tables with existing Stripe data:
ddev craft stripe/commerce/migrate

# Perform a synchronization to bring in additional records:
ddev craft stripe/sync/all

API 变更

由于它们更接近于 Stripe 的计费架构而不是传统的单一物品“计划”,因此您将不同于在 Craft Commerce 中与订阅交互

  • 在 Craft 中不配置计划。相反,可以在 Stripe 中将产品(或更准确地说是 价格)设置为 周期性。您将在控制面板中单个产品元素的 价格 表中看到这一点,作为一个价格和间隔的组合(例如 $5.00/天)。
  • 一些与网关无关的元素查询方法没有被转换到 Stripe 插件中
    • dateExpired():不作为本地属性跟踪。您可以通过 subscription.data.ended_at 访问订阅结束的日期戳。
    • isExpired():与上述类似,非过期订阅将有一个 nullsubscription.data.ended_at 值。
    • trialDays():使用 subscription.data.trial_starttrial_end,或访问订阅的底层 items 数组以获取每个周期性物品的价格和配置信息。
    • status():状态可能不会以与 Craft Commerce 中的定义一致的方式表现。

店面

一旦您已通过(通过 同步 和/或 webhook)将 Stripe 的数据填充到 Craft 项目中,您就可以开始构建内容和店面。

提示

参考部分包含关于系统中的各种对象的详细信息。

列出产品

单个产品会自动根据其URI格式获取URL,但如何收集和显示由您决定。

要获取产品列表,请使用craft.stripeProducts元素查询工厂元素查询

{% set products = craft.stripeProducts.all() %}

<ul>
  {% for product in products %}
    {% set image = product.featureImage.eagerly().one() %}

    <li>
      <figure>
        {{ image.getImg() }}
      </figure>

      <strong>{{ product.getLink() }}</strong>
    </li>
  {% endfor %}
</ul>

产品模板

在单个产品页面上,Craft通过product变量提供当前产品

<h1>{{ product.title }}</h1>

您为产品配置的任何自定义字段都将作为属性提供,就像其他元素类型一样

{{ product.customDescriptionField|md }}

价格

与Craft Commerce类似,Stripe使用“产品”作为逻辑分组商品和服务的手段——客户实际购买的东西被称为“价格”。

Stripe插件使用嵌套元素来处理这种关系。每个产品元素将拥有一个或多个价格元素,并通过prices属性或getPrices()方法公开它们

<h1>{{ product.title }}</h1>

<ul>
  {% for price in product.prices %}
    <li>
      {{ price.data|unitAmount }}
      {{ tag('a', {
        text: "Buy now",
        href: price.getCheckoutUrl(
          currentUser ?? false,
          'shop/thank-you?session={CHECKOUT_SESSION_ID}',
          product.url,
          {}
        ),
      }) }}
    </li>
  {% endfor %}
</ul>

结账链接

当客户准备好购买产品或开始订阅时,您将提供结账链接。结账链接是特殊的、安全的、参数化的URL,作为预配置项目列表的一部分存在于结账会话中。Stripe没有所谓的“购物车”;相反,产品是逐个购买的。

点击结账链接会将客户带到Stripe的托管结账页面,在那里他们可以使用账户中可用和启用的任何方法完成付款。

要输出结账链接,请使用stripeCheckoutUrl()函数

{% set price = product.prices.one() %}

{{ tag('a', {
  href: stripeCheckoutUrl(
    [
      {
        price: price.stripeId,
        quantity: 1,
      },
    ],
    currentUser ?? false,
    'shop/thank-you?session={CHECKOUT_SESSION_ID}',
    product.url,
    {}
  ),
  text: 'Checkout',
}) }}

提示

false作为stripeCheckoutUrl()的第二个参数传递允许您创建匿名结账URL。

结账表单

作为生成静态结账链接的替代方案,您可以构建一个表单,该表单将项目列表和其他参数发送到Craft,Craft将动态创建结账会话,然后重定向客户到Stripe托管的结账页面

{% set prices = product.prices.all() %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('stripe/checkout') }}
  {{ hiddenInput('successUrl', 'shop/thank-you?session={CHECKOUT_SESSION_ID}'|hash) }}
  {{ hiddenInput('cancelUrl', 'shop'|hash) }}
  {% if not currentUser %}
    {{ hiddenInput('customer', 'false') }}
  {% endif %}

  <select name="lineItems[0][price]">
    {% for price in prices %}
      <option value="{{ price.stripeId }}">{{ price.data|unitAmount }}</option>
    {% endfor %}
  </select>

  <input type="text" name="lineItems[0][quantity]" value="1">

  <button>Buy now</button>
</form>

提示

默认情况下,将使用当前登录用户。

要允许匿名结账,您可以将{{ hiddenInput('customer', 'false') }}添加到表单中。

账单门户

客户可以通过Stripe的托管账单门户管理他们的订阅和支付方式。您可以使用currentUser.getStripeBillingPortalSessionUrl()方法生成指向客户账单门户的URL

{{ tag('a', {
  text: "Billing Portal",
  href: currentUser.getStripeBillingPortalSessionUrl('shop'),
}) }}

该方法接受一个returnUrl参数,指定客户完成管理订阅和支付方式后要重定向到的URL。

除了此方法外,还有currentUser.getStripeBillingPortalSessionPaymentMethodUpdateUrl(),该方法为客户端生成一个更新默认支付方式的URL。

{{ tag('a', {
  text: "Update Payment Method",
  href: currentUser.getStripeBillingPortalSessionPaymentMethodUpdateUrl('shop'),
}) }}

它使用Stripe的流程类型直接链接到支付方式更新屏幕。

元素API

我们的元素API插件与Stripe配合得很好!所有三个插件提供的元素类型(产品、价格和订阅)都可以在您的element-api.php配置文件中使用

return [
    'endpoints' => [
        'api/products' => function() {
            return [
                'elementType' => craft\stripe\elements\Product::class,
                // ...
            ];
        },
    ],
];

其他功能

产品字段

创建一个 Stripe 产品 字段,并将其添加到字段布局中,以关联产品元素到系统中其他内容。

直接API访问

该插件公开其Stripe API客户端以供高级使用。在Twig中,您可以通过craft.stripe.api.client访问它。

{% set client = craft.stripe.api.client %}
{% set checkout = client
  .getService('checkout')
  .getService('sessions')
  .retrieve('cs_test_****************************************') %}

在PHP中,您可以像这样进行等效调用

$client = craft\stripe\Plugin::getInstance()->getApi()->getClient();

$checkout = $client
    ->checkout
    ->sessions
    ->retrieve('cs_test_****************************************');

警告

我们无法提供涉及直接使用Stripe API的自定义支持。如果在项目过程中需要访问特定API,请考虑发起讨论

技巧、故障排除、常见问题解答

我在哪里更改产品的标题?

产品标题和价格与Stripe保持同步,以便在两个空间中轻松识别。

如果您想自定义Craft中的产品名称,请创建一个纯文本字段并将其添加到产品字段布局。Stripe将始终在结账或发票上显示规范标题,因此您需要一种方式让客户能够识别不同的产品——不要为其他商品重复使用产品定义。

我无法创建webhook。

如果Craft无法写入项目根目录下的.env文件,您可能需要在Stripe仪表板中手动创建webhook,然后将其暴露给环境

STRIPE_WH_ID="we_************************"
STRIPE_WH_KEY="whsec_**************************************************************"

警告

在这种情况下,环境变量名称是严格的!

参考

Twig过滤器

该插件提供了四个新的Twig过滤器

  • unitPrice —— 接受一个价格元素的Stripe data数组,并输出其成本和间隔的格式化表达式:每单位10.50英镑/月
  • pricePerUnit —— 与上述类似,但仅输出成本组件,不包含间隔:每单位5.00美元 + 20.00美元起价
  • unitAmount —— 与上述类似,但仅输出单位组件,例如:每10组13.00美元
  • interval —— 与上述类似,但仅输出间隔组件,例如:一次性每月一次

在大多数情况下,您将想要使用unitPrice过滤器,因为它将提供有关价格的完整信息。所有过滤器都应该传递价格的data属性,它是Stripe的原始价格对象

{{ price.data|unitPrice }}

CLI

要查看所有可用的控制台命令,请运行ddev craft help。Stripe插件添加了两个主要命令组

Craft Commerce迁移

将现有的Craft Commerce订阅迁移到与Stripe插件兼容的记录。

ddev craft stripe/commerce/migrate

原子同步

您可以一次性同步所有内容…

ddev craft stripe/sync/all

…或者只同步其中的一部分

  • 客户ddev craft stripe/sync/customers
  • 发票ddev craft stripe/sync/invoices
  • 支付方式ddev craft stripe/sync/payment-methods
  • 产品价格ddev craft stripe/sync/products-and-prices
  • 订阅ddev craft stripe/sync/subscriptions

扩展

同步事件

同步过程中,Stripe插件会在更新每个产品、价格或订阅元素之前发出事件。同步可能通过CLI、控制面板实用程序或响应webhook来执行。

craft\base\Event::on(
    craft\stripe\services\Products::class,
    craft\stripe\services\Products::EVENT_BEFORE_SYNCHRONIZE_PRODUCT,
    function(craft\stripe\events\StripeProductSyncEvent $event) {
        // Set a custom field value when a product looks “shippable”:
        if ($event->source->package_dimensions !== null) {
            $event->element->setFieldValue('requiresShipping', true);
        }
    },
);

您可以将$event->isValid设置为在同步过程中防止更新持久化。

结账事件

通过监听craft\stripe\services\Checkout::EVENT_BEFORE_START_CHECKOUT_SESSION事件,在生成Checkout会话时自定义发送到Stripe的参数。craft\stripe\events\CheckoutSessionEvent将包含一个params属性,该属性包含即将通过Stripe API客户端发送的请求数据。您可以修改或扩展这些数据以适应您的应用程序——所有处理程序调用后属性的值将原封不动地传递给API客户端

craft\base\Event::on(
    craft\stripe\services\Checkout::class,
    craft\stripe\services\Checkout::EVENT_BEFORE_START_CHECKOUT_SESSION,
    function(craft\stripe\events\CheckoutSessionEvent $event) {
        // Add metadata if the customer is a logged-in “member”:
        $currentUser = Craft::$app->getUser()->getIdentity();

        // Nothing to do:
        if (!$currentUser) {
            return;
        }

        if ($currentUser->isInGroup('members')) {
            // Memoize + assign values:
            $data = $event->params;
            $data['metadata']['is_member'] = true;

            // Set back onto the event:
            $event->params = $data;
        }
    },
);