Cashier 交易工具包

Laravel Cashier

简介

Laravel Cashier 提供了直观而流畅的接口来接入 Stripe 付费订阅服务,它可以处理几乎所有的让你头疼的付费订阅代码。除了基本的订阅管理之外,Cashier 还可以帮你处理优惠券、交换订阅、订阅数量、取消宽限期,甚至还可以生成 PDF 电子发票。

{注意} 为防止破坏性的更改,Cashier 使用固定的 Stripe API 版本。Cashier 10.1 利用 Stripe API 2019-08-14 版本。Stripe API 版本将在次要分发上更新以便使用新的 Stripe 特性和改进。

升级 Cashier

当你升级到新版本 Cashier 时,,请务必仔细阅读 Cashier 升级指南

安装

首先,使用 composer 将 Stripe 的 Cashier 包添加到项目依赖中:

composer require laravel/cashier

{注意} 为确保 Cashier 正确的处理所有的 Stripe 事件,请记得 配置 Cashier webhook 处理

数据库迁移

Cashier 服务提供者注册了自己的数据库迁移目录,请记住在安装完包后迁移你的数据库。Cashier 迁移会在你的 users 表中增加几列并创建一个新的 subscriptions 表来保存你所有客户的订阅:

php artisan migrate

如果你需要覆盖 Cashier 包附带的迁移,可以使用 vendor:publish Artisan 命令发布它们:

php artisan vendor:publish --tag="cashier-migrations"

如果你想阻止 Cashier 迁移完全运行,可以使用 Cashier 提供的 ignoreMigrations 。通常,你应该在 AppServiceProviderregister 方法中调用这个方法:

use Laravel\Cashier\Cashier;

Cashier::ignoreMigrations();

{注意} Stripe 建议用来存储 stripe 标识符的所有列应该大小写敏感。因此,以 MySQL 数据库为例,你应该确保 stripe_id 列的列排序规则设置为 utf8_bin ,更多信息可以在 Stripe 文档 中找到。

配置

Billable 模型

在使用 Cashier 之前, 添加 Billable Trait 到你的模型定义中。 这个 Trait 提供了多种方法以便你执行常见的支付任务,如创建订阅、申请优惠券以及更新支付方式信息:

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

Cashier 暂定你的 Billable 模型是 Laravel 附带的 App\User 类,如果你想修改请在你的 .env 文件中指定一个不同的模型:

CASHIER_MODEL=App\User

{注意} 如果你使用的不是 Laravel 提供的 App\User 模型,你应该发布并更改提供的 migrations 以匹配你的代替模型数据表名。

API 密钥

接下来,您需要在 .env 中配置您的 Stripe 密钥。您可以在 Stripe 的控制面板中获取 Stripe API 密钥.

STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret

货币配置

Cashier 的默认货币是美元 (USD)。您可以设置 CASHIER_CURRENCY 来更改默认的货币:

CASHIER_CURRENCY=eur

为了配置 Cashier 使用的货币,您也许需要指定一个额外的 Locale(本地化) 参数,用于格式化在账单上面显示的金额。Cashier 内部使用 PHP 的 NumberFormatter 来格式化数字:

CASHIER_CURRENCY_LOCALE=nl_BE

注意:要使用除了 en 之外的 Locale,确保 ext-intl 扩展在服务器上安装并配置好。

客户

创建客户

有时,您可能希望在未订阅的情况下创建 Stripe 客户。您可以使用 createAsStripeCustomer 方法完成此操作:

$user->createAsStripeCustomer();

一旦在 Stripe 中创建了客户,您可以稍后便可开始订阅。

支付方式

储存支付方式

为了使用 Stripe 创建订阅或是一次性扣费,您将需要从 Stripe 获取支付方式并储存他的识别码。基于您计划使用的支付方式是用来一次性扣费还是订阅,我们有两种方案来达成,接下来我们一起来看一下这两种方法。

用于订阅制的支付方法

为了储存用户的信用卡供以后使用,Stripe 的 “Setup Intent” API 需要安全的获取用户的信用卡信息。“Setup Intent” 指示 Stripe 用户支付方式的意象。Cashier 的Blillable trait 包含了 createSetupIntent 方法来快速创建一个 “Setup Intent”。
您应该在路由或者控制器调用这个方法来获取用户的支付方式意象:

return view('update-payment-method', [
    'intent' => $user->createSetupIntent()
]);

当您创建完 “Setup Intent” 并传递给视图后,您应该将它的密钥元素附加到获取用户支付信息的表单中。如下面这个“更新支付方式”表单:

<input id="card-holder-name" type="text">

<!-- Stripe Elements Placeholder -->
<div id="card-element"></div>

<button id="card-button" data-secret="{{ $intent->client_secret }}">
    Update Payment Method
</button>

接下来,Stripe.js 库会调用这个密钥并安全地抓取用户的支付信息:

<script src="https://js.stripe.com/v3/"></script>

<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements();
    const cardElement = elements.create('card');

    cardElement.mount('#card-element');
</script>

然后,卡片会被校验且您可以使用 [Stripe 的 handleCardSetup 方法] (https://stripe.com/docs/stripe-js/reference#stripe-handle-card-setup)来获取一个安全的“支付方式识别码”:

const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;

cardButton.addEventListener('click', async (e) => {
    const { setupIntent, error } = await stripe.handleCardSetup(
        clientSecret, cardElement, {
            payment_method_data: {
                billing_details: { name: cardHolderName.value }
            }
        }
    );

    if (error) {
        // 显示错误信息
    } else {
        // 卡片已成功验证
    }
});

当 Stripe 完成对客户支付方式的校验后,您可以将setupIntent.payment_method 识别码返回给您的应用并附加给客户。这次支付使用的支付方式可 被添加为新的支付方式 或者 用于更新默认支付方式。您可以立即使用这个识别码来 创建一个订阅.

Tip:如果您希望了解更多有关 “Setup Intent” 与获取收集用户支付详情的信息,请参阅 Stripe 的文档.

用于一次性收费的支付方式

当然,用于一次性收费的支付识别码我们只需要使用一次。由于 Stripe 的限制,您无法使用客户的一次性收费的支付方式。您需要用 Stripe.js 库来让客户填写他们的支付详情。让我们来看一下下面这个支付表单:

<input id="card-holder-name" type="text">

<!-- Stripe Elements Placeholder -->
<div id="card-element"></div>

<button id="card-button">
    处理支付流程
</button>

接下来,Stripe.js 可用来附加一个 Stripe 元素用于加密用户的支付方式详情:

<script src="https://js.stripe.com/v3/"></script>

<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements();
    const cardElement = elements.create('card');

    cardElement.mount('#card-element');
</script>

然后,卡片会被校验且您可以使用 Stripe 的 createPaymentMethod 方法: 来获取一个安全的“支付方式识别码”:

const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');

cardButton.addEventListener('click', async (e) => {
    const { paymentMethod, error } = await stripe.createPaymentMethod(
        'card', cardElement, {
            billing_details: { name: cardHolderName.value }
        }
    );

    if (error) {
        // 错误信息
    } else {
        // 卡片验证成功
    }
});

如果卡片验证成功,您可以将 paymentMethod.id 传递给您的应用并进行 一次性付费.

获取支付方式

Billable 模块实例上的 paymentMethods 方法将返回一组 Laravel\Cashier\PaymentMethod 实例:

$paymentMethods = $user->paymentMethods();

要获取默认的支付方式,可以使用 defaultPaymentMethod方法:

$paymentMethod = $user->defaultPaymentMethod();

确认用户是否拥有支付方式

要确认一个Billable 模型是否拥有一个支付方式,使用 hasPaymentMethod 方法:

if ($user->hasPaymentMethod()) {
    //
}

更新默认的支付方式

updateDefaultPaymentMethod 可用来更新用户的默认支付方式。这个方法接受 Stripe 的支付方式识别码并且会将新分配的识别码设为默认的支付方式:

$user->updateDefaultPaymentMethod($paymentMethod);

要在 Stripe 内同步您与客户的默认支付方式,您可以使用
updateDefaultPaymentMethodFromStripe 方法:

$user->updateDefaultPaymentMethodFromStripe();

注意:客户的默认支付方式只能用户创建发票或订阅,由于 Stripe 的限制,某些支付方式不适用于一次性收费

添加支付方式

要添加新的支付方式,您可以在(可收费的) Billable 用户上调用 addPaymentMethod 方法,并传递支付方式识别码:

$user->addPaymentMethod($paymentMethod);

Tip:要学习如何获取并储存支付方式识别码,请参阅 支付方式储存.

删除支付方式

要删除一个支付方式,你可以调用 Laravel\Cashier\PaymentMethod 上的 delete 方法进行删除:

$paymentMethod->delete();

deletePaymentMethods 方法将删除Billable模型的所有相关付款方式的信息:

$user->deletePaymentMethods();

注意:如果用户的订阅比较活跃,那么应该阻止他们删除默认的付款方式。

订阅

创建订阅

创建订阅,首先需要获取到一个 Billable 模型实例,这通常是 App\User 的一个实例。一旦您获取了模型实例,您可以使用 newSubscription 方法创建模型的订阅:

$user = User::find(1);

$user->newSubscription('main', 'premium')->create($paymentMethod);

newSubscription 方法的第一个参数应该是订阅的名称。如果您的应用程序只提供一个订阅,那么您可以将其设置为 main or primary。第二个参数是用户订阅的 Stripe 计划。这个值应该与 Stripe 或 Braintree 中的标识符对应。

create 方法接受一个 支付方法标识符或者 Stripe PaymentMethod 对象后将开始订阅, 并使用客户 ID 和其他相关的账单信息更新数据库。

注意:直接传递支付方法标识符到 create() 方法也可以将用户的付款方式储存到数据库。

用户其他的详细信息

如果您想要指定用户其他的详细信息,您可以通过将它们作为第二个参数传递给 create 方法:

$user->newSubscription('main', 'monthly')->create($paymentMethod, [
    'email' => $email,
]);

要了解更多关于 Stripe 支持的额外字段,请查看 Stripe 的内容创建客户文档

优惠券

如果您想在创建订阅时使用优惠券,您可以使用 withCoupon 方法:

$user->newSubscription('main', 'monthly')
     ->withCoupon('code')
     ->create($paymentMethod);

检查订阅状态

一旦用户在您的应用程序订阅了,您可以使用各种方便的方法轻松地检查他们的订阅状态。首先,如果用户有一个激活的订阅,那么 subscribed 的方法将返回 true ,即使订阅当前处于试用阶段:

if ($user->subscribed('main')) {
    //
}

这个 subscribed 方法还可以在 路由中间件使用,允许您根据用户的订阅状态对路由和控制器进行访问:

public function handle($request, Closure $next)
{
    if ($request->user() && ! $request->user()->subscribed('main')) {
        // 该用户不是付费客户......
        return redirect('billing');
    }

    return $next($request);
}

如果您想要确定用户是否仍然处于试用阶段,您可以使用 onTrial 方法。这个方法对于向用户显示他们仍然处于试用期的警告是很有用的:

if ($user->subscription('main')->onTrial()) {
    //
}

基于给定的 Stripe 计划 ID,可以使用 subscribedToPlan 方法来确定用户是否订阅了该计划。在本例中,我们将确定用户的 main 订阅是否激活了 monthly 计划:

if ($user->subscribedToPlan('monthly', 'main')) {
    //
}

recurring 方法可用于确定用户当前是否已订阅,并且不再处于试用阶段:

if ($user->subscription('main')->recurring()) {
    //
}

取消订阅状态

为了确定用户是否曾经订阅,但是已经取消了他们的订阅,您可以使用 cancelled 方法:

if ($user->subscription('main')->cancelled()) {
    //
}

您还可以确定用户是否已经取消了订阅,但是仍然处于订阅的「宽限期」,直到订阅完全过期为止。例如,如果用户在 3 月 5 日取消了原定于 3 月 10 日到期的订阅,那么用户将在 3 月 10 日之前进行「宽限期」。请注意,在此期间 subscribed 方法仍然返回 true

if ($user->subscription('main')->onGracePeriod()) {
    //
}

如果要确定用户取消订阅的时间是否已不在其「宽限期」 内,可以使用 ended 方法:

if ($user->subscription('main')->ended()) {
    //
}

不完整和过期状态

如果订阅需要在创建后进行二次付款操作,则订阅将被标记为 incomplete 。订阅状态存储在 stripe_status Cashier subscriptions 数据库表的列中。

同样,如果在交换计划时需要进行二次付款操作,则订阅将被标记为 past_due 。当您的订阅处于这两种状态时,在客户确认付款之前,它将不会处于活动状态。检查订阅是否有不完整的付款可以使用 hasIncompletePayment Billable模型或订阅实例上的方法完成:

if ($user->hasIncompletePayment('main')) {
    //
}

if ($user->subscription('main')->hasIncompletePayment()) {
    //
}

如果订阅的付款不完整,您应该将用户定向到收银员的付款确认页面,并传递latestPayment 标识符。您可以使用 latestPayment 订阅实例上的可用方法来检索此标识符:

<a href="{{ route('cashier.payment', $subscription->latestPayment()->id) }}">
    Please confirm your payment.
</a>

注意:订阅处于某种 incomplete 状态时,在确认付款之前无法更改。因此,当订阅处于某种状态时, swapupdateQuantity 方法将抛出异常 incomplete

修改订阅计划

用户在您的应用程序中订阅了之后,他们可能会偶尔想要更改一个新的订阅计划。要将一个用户切换到一个新的订阅,需将订阅计划的标识符传递给 swap 方法:

$user = App\User::find(1);

$user->subscription('main')->swap('provider-plan-id');

如果用户在试用期,试用期的期限会被保留。另外,如果订阅的数量存在「份额」,那么该份额也将保持。

如果你想在更改用户订阅计划的时候取消用户当前订阅的试用期,可以使用 skipTrial 方法:

$user->subscription('main')
        ->skipTrial()
        ->swap('provider-plan-id');

如果你想在更改用户订阅计划的时候就为用户开具发票信息,而不是等待下一个结算周期, 你可以使用 swapAndInvoice 方法:

$user = App\User::find(1);

$user->subscription('main')->swapAndInvoice('provider-plan-id');

订阅量

有些时候订阅是会受「数量」影响的。举个例子,你的应用程序的付费方式可能是每个账户 $10 / 月。你可以使用 incrementQuantitydecrementQuantity 方法轻松地增加或减少你的订阅量:

$user = User::find(1);

$user->subscription('main')->incrementQuantity();

// 对当前的订阅量加5...
$user->subscription('main')->incrementQuantity(5);

$user->subscription('main')->decrementQuantity();

// 对当前的订阅量减5...
$user->subscription('main')->decrementQuantity(5);

或者,你可以使用 updateQuantity 方法设定一个特定的数量:

$user->subscription('main')->updateQuantity(10);

noProrate 方法可用于更新订阅的数量,而不会对收费进行定价:

$user->subscription('main')->noProrate()->updateQuantity(10);

要获得更多关于订阅量的信息,请参考 Stripe 文档.

订阅税额

在计费模式上实现 taxPercentage 方法,并且返回一个 0 到 100 不超过 2 位小数的数字,用来指定用户在订阅中支付的税率百分比。

public function taxPercentage()
{
    return 20;
}

taxPercentage 方法使你能够在模型的基础上应用税率,这对于一个跨越多个国家和税率的用户群可能有帮助。

注意:taxPercentage 方法只适用于付费订阅模式。如果你用 charges 来做「一次性」收费,你需要同时手工指定税率。

同步税率百分比

当更改 taxPercentage 方法返回的硬编码值时,用户的任何现有订阅的税率设置将保持不变。如果要用返回的 taxPercentage 值更新现有订阅的税率,应在用户的订阅实例上调用 syncTaxPercentage 方法:

$user->subscription('main')->syncTaxPercentage();

订阅锚定日期

默认情况下,计费周期锚定是创建订阅的日期,如果使用试用期,则是试用结束的日期。如果要修改账单锚定日期,可以使用 anchorBillingCycleOn 方法:

use App\User;
use Carbon\Carbon;

$user = User::find(1);

$anchor = Carbon::parse('first day of next month');

$user->newSubscription('main', 'premium')
            ->anchorBillingCycleOn($anchor->startOfDay())
            ->create($paymentMethod);

有关管理订阅计费周期的详细信息,请参阅 Stripe 计划周期文档

取消订阅

在用户订阅上调用 cancel 方法用来取消订阅:

$user->subscription('main')->cancel();

当一个订阅被取消时,Cashier 将会自动在你的数据库中设置 ends_at 列。这个列经常被用来获悉 subscribed 字段何时应该开始返回 false 。例如,如果客户在 3 月 1 日取消订阅,但是订阅计划直到 3 月 5 日才结束,subscribed 方法将会继续返回 true 一直到 3 月 5 日。

你可以使用 onGracePeriod 方法确定用户是否确定订阅,但是仍然存在一个「宽限期」:

if ($user->subscription('main')->onGracePeriod()) {
    //
}

如果你想马上取消订阅,请在用户的订阅中调用 cancelNow 方法:

$user->subscription('main')->cancelNow();

恢复订阅

如果一个用已经取消订阅,你可以在你希望恢复它的时候使用 resume 方法。用户 必须 仍然在他们的宽限期内才可以恢复订阅:

$user->subscription('main')->resume();

如果用户已取消订阅,然后在订阅宽限期前恢复该订阅,他们将不会被立即计费。相反,他们的订阅将会被重新激活,需要按照原来的支付流程再次进行支付。

试用订阅

以信用卡订阅

如果你想给你的顾客提供试用期,同时收集支付方法信息,那么你应该在创建订阅使用 trialDays 方法:

$user = User::find(1);

$user->newSubscription('main', 'monthly')
            ->trialDays(10)
            ->create($paymentMethod);

该方法会在数据库订阅记录上设置订阅期结束时间,以便告知 Sripe 在此之前不要计算用户的账单信息。当使用 trialDays 方法时,Cashier将覆盖 Stripe 中配置的所有默认试用期。

注意:如果顾客没有在试用期结束前取消订阅,订阅会被自动结算,所以你应该确保告知你的用户他们的试用结束期。

trialUntil 方法允许提供 DateTime 实例指定试用结束期:

use Carbon\Carbon;

$user->newSubscription('main', 'monthly')
            ->trialUntil(Carbon::now()->addDays(10))
            ->create($paymentMethod);

你可以使用用户实例的 onTrial 方法或者订阅实例的 onTrial 方法判断用户是否处于试用期。下面两个示例等价:

if ($user->onTrial('main')) {
    //
}

if ($user->subscription('main')->onTrial()) {
    //
}

非信用卡订阅

如果你不想在提供试用期的时候收集用户支付方式信息,只需设置用户记录的 trial_ends_at 列为期望的试用期结束日期即可,这通常在用户注册期间完成:

$user = User::create([
    // 填充其他用户属性……
    'trial_ends_at' => now()->addDays(10),
]);

注意:确保已添加 trial_ends_at 日期修改器 到模型定义。

Cashier 把这种类型的引用称为「一般体验」,因为它没有关联任何已存在的订阅。如果当前的日期没有超过 trail_ends_at 值, User 实例的 onTrial 方法将会返回 true

if ($user->onTrial()) {
    // 用户在他们的试用期内...
}

如果你希望明确的知道用户处于「一般」试用期,并且还未创建实际的订阅,那么你可以使用 onGenericTrial 方法:

if ($user->onGenericTrial()) {
    // 用户在他们「一般」试用期...
}

如果你准备给用户创建实际的订阅,通常你可以使用 newSubsription 方法:

$user = User::find(1);

$user->newSubscription('main', 'monthly')->create($paymentMethod);

处理 Stripe Webhooks

提示:你可以使用 Laravel Valet's中的 valet share 命令来帮助你在本地开发期间测试webhook。

Stripe可以通过webhooks通知应用各种各样的事件。 默认情况下,需要定义一个 Cashier 的 webhook 控制器的路由。这个控制器用来处理所有传入的webhook请求。

默认情况下,这个控制器将会自动对支付失败次数过多(这个次数可以在 Stripe 设置中定义)的订阅进行取消;此外,我们很快会发现,你可以扩展这个控制器去处理任何你想要处理的 webhook 事件。

为了确保您的应用可以处理Stripe webhook,请务必在Stripe控制面板中配置webhook URL。Stripe 控制面板中应该配置的所有webhook完整列表如下:

  • customer.subscription.updated
  • customer.subscription.deleted
  • customer.updated
  • customer.deleted
  • invoice.payment_action_required

注意:请确保使用 Cashier 的 webhook 验签 中间件来保护传入请求。

Webhooks & CSRF 保护

因为 Stripe webhooks 需要绕过 Laraval 的 CSRF 保护, 请确保在您的 VerifyCsrfToken 中间件含有 URI ,或者将其置于 web 中间件组之外:

protected $except = [
    'stripe/*',
];

定义 Webhook 事件处理程序

Cashier 对于失败支付自动进行取消订阅,但是如果您有其他的 Stripe Webhook 事件希望去处理,可以扩展 Webhook 控制器。您的方法名应该与 Cashier 期望的约定相符,更具体的说,您希望处理 Stripe webhook 的方法应该以 handle 和 「驼峰」 名为前缀。举例来说,如果您希望处理 invoice.payment_succeeded 的 webhook,您应该在控制器添加handleInvoicePaymentSucceeded 方法:

<?php

namespace App\Http\Controllers;

use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;

class WebhookController extends CashierController
{
    /**
     * Handle invoice payment succeeded.
     *
     * @param  array  $payload
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function handleInvoicePaymentSucceeded($payload)
    {
        // 此处处理事件
    }
}

接下来,在 routes/web.php 文件中定义 Cashier 控制器的路由, 这将会覆盖掉默认的路由:

Route::post(
    'stripe/webhook',
    '\App\Http\Controllers\WebhookController@handleWebhook'
);

订阅失败

如果用户的信用卡过期怎么办?不用担心 - Cashier 包含了一个 Webhook 控制器可以轻松为您取消用户的订阅。 该控制器会在 Stripe 判断订阅失败后(通常尝试支付失败 3 次及以上)取消用户的订阅。

Webhook 验签

为了保护 Webhook,您需要使用 Stripe 的 Webhook 签名。为了方便,Cashier 包含一个中间件,用于验证传入 Stripe webhook 的请求是否有效。

如果要启用 Webhook 验证,请确保在 .env 配置文件中设置了 STRIPE_WEBHOOK_SECRET 的值。 Webhook的 secret 可以从 Stripe 用户控制面板中找到。

一次性支付

简单支付

注意: charge 方法接收您想支付于 应用程序使用的货币的最小单位 的金额

如果您想对订阅客户的信用卡收取「一次性」费用,可以在可计费模型实例上使用 charge 方法。 您需要将 支付方式识别码 作为第二个参数:

// Stripe 接收分为单位的费用...
$stripeCharge = $user->charge(100, $paymentMethod);

charge 方法接受一个数组作为它的第三个参数, 允许您创建支付时将任何您想要的选项传递给底层的 Stripe。有关在创建支付时可用的选项,请参阅 Stripe 文档:

$user->charge(100, $paymentMethod, [
    'custom_option' => $value,
]);

如果支付失败, charge 方法将会抛出异常。如果支付成功,一个 Laravel\Cashier\Payment 实例会从该方法返回:

try {
    $payment = $user->charge(100, $paymentMethod);
} catch (Exception $e) {
    //
}

发票

有时您可能需要支付一次性费用同时也需要生成费用发票,以便可以向客户提供 PDF 文件格式的收据。 invoiceFor 方法可以让您做到这一点。 例如,向客户开具 5.00 美元的「一次性费用」发票:

// Stripe 接收分为单位的费用...
$user->invoiceFor('One Time Fee', 500);

账单会立刻向用户的默认支付方式收取费用。 invoiceFor 方法接收一个数组作为第三个参数,这个数组包含了所购内容的账单选项。接收的第四个参数也是一个数组, 这最后一个参数包含了发票自身的一些选项:

$user->invoiceFor('Stickers', 500, [
    'quantity' => 50,
], [
    'tax_percent' => 21,
]);

注意: invoiceFor 方法将会创建 Stripe 发票,该发票将会在支付失败后重试。如果您不想失败后重试,您需要在第一次支付失败后调用 Stripe API 关闭它。

关于退款

如果您需要处理退款,您可以使用 refund 方法。此方法接受 Stripe Payment Intent ID 作为其第一个参数:

$payment = $user->charge(100, $paymentMethod);

$user->refund($payment->id);

发票

您可以使用 invoices 方法轻松获取账单模型的发票数组:

$invoices = $user->invoices();

// 结果包含处理中的发票...
$invoices = $user->invoicesIncludingPending();

当列出客户发票清单时,可以使用发票辅助函数来显示相关的发票信息。例如,您可能希望在表格中列出每张发票,从而方便客户下载它们:

<table>
    @foreach ($invoices as $invoice)
        <tr>
            <td>{{ $invoice->date()->toFormattedDateString() }}</td>
            <td>{{ $invoice->total() }}</td>
            <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
        </tr>
    @endforeach
</table>

生成 PDF 发票

在路由或控制器中,使用 downloadInvoice 方法生成一个发票的 PDF 下载。这个方法会自动给浏览器生成合适的 HTTP 下载响应:

use Illuminate\Http\Request;

Route::get('user/invoice/{invoice}', function (Request $request, $invoiceId) {
    return $request->user()->downloadInvoice($invoiceId, [
        'vendor' => 'Your Company',
        'product' => 'Your Product',
    ]);
});

Strong Customer Authentication

如果您的业务立足于欧洲,那么您就需要遵守 Strong Customer Authentication (SCA) 法规。这些法规由欧盟发布于 2019 年 9 月,意在预防支付欺诈。 幸运的是, Stripe 与 Cashier 已经准备好构建符合 SCA 的应用。

注意:在您开始之前, 请先阅读 Stripe's guide on PSD2 and SCA新 SCA API 文档.

需要额外验证的支付

SCA 通常在处理支付时需要额外的验证步骤。当这种情况出现时, Cashier 会抛出 IncompletePayment 来提示您需要额外的验证步骤。 当异常抛出时,您有两个选择。

您可以引导用户跳转到 Cashier 自带的支付确认页面完成验证。这个页面已经关联由 Cashier 的服务提供者注册的路由。因此,您可以捕获 IncompletePayment 后跳转到支付确认界面:

use Laravel\Cashier\Exceptions\IncompletePayment;

try {
    $subscription = $user->newSubscription('default', $planId)
                            ->create($paymentMethod);
} catch (IncompletePayment $exception) {
    return redirect()->route(
        'cashier.payment',
        [$exception->payment->id, 'redirect' => route('home')]
    );
}

在支付确认界面,用户则需要再次输入他们的信用卡信息以及进行任何 Stripe 要求的操作。例如输入信用卡的“3D安全信息”来确认支付。当他们完成确认后,用户会被重定向到上方 redirect 定义的URL。

或者,您可以让 Stripe 来为您处理此次支付确认。在这种情况下,您可以在 Stripe 仪表板设置 自动账单邮箱 。如果捕获到IncompletePayment,您还是要告知用户他们会在邮箱内收到处理支付的引导邮件。

IncompletePayment 异常会由以下方法抛出:
charge, invoiceFor, 和在 Billable(可收费)用户的 invoice 方法。 当处理订阅时, 在 IncompletePayment 中的 create方法,与在 IncompletePayment 中的 incrementAndInvoiceswapAndInvoice 会抛出异常。

账单未完成与已逾期

每当账单需要额外确认时,订阅在数据库中的 stripe_status 列留下 incompletepast_due 状态。当 Webhook 确认支付已完成时,Cashier 会立即激活用户的订阅。

如果您要了解更多关于 incompletepast_due 状态的信息,请参考额外文档.

非会话支付通知

尽管用户订阅已经激活,但 SCA 要求用户时不时验证其支付信息。 Cashier 可以发送一封邮件提醒用户需要非会话支付通知。例如,当用户订阅需要续费时, Cashier 的支付通知可以在环境变量中将 CASHIER_PAYMENT_NOTIFICATION 分配给一个类来启用。这默认是禁用的。当然,如果您不想使用 Cashier 提供的这个通知类,您可以使用自己的类来完成:

CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment

为了确保非会话支付通知送达, 您需要确认已经在 Stripe 仪表盘完成 Stripe Webhook 配置和已启用invoice.payment_action_required Webhook。 您的 Billable 模型应使用 Laravel 的 Illuminate\Notifications\Notifiable trait。

注意:当用户手动支付了一个需要额外确认的订单时,通知仍然会被发送。不幸的是,Stripe 无法确认用户是手动支付的还是使用非会话(Off-Session)方式支付。但是,用户确认支付以后返回支付页面,他们可以看见一个简单的 “支付成功” 提示。这样用户就无法再意外情况下再次确认支付,导致二次付费的情况发生。

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:6
讨论数量: 0
发起讨论 只看当前版本


暂无话题~