交易工具包 (Paddle)

未匹配的标注

Laravel 交易工具包 (Paddle)

简介

Laravel Cashier Paddle 为 Paddle's 订阅计费服务提供 一种高效流畅的接口。 它能够处理几乎所有你所担心的订阅计费代码。除了基础的订阅管理之外,Cashier 还可以处理:优惠券,交换订阅,订阅量, 取消宽限期,等等。

在使用 Cashier 时,我门推荐你也回顾一下 Paddle's 用户手册 and API 文档.

升级 Cashier

在升级到一个新版本的 Cashier 时,仔细回顾 升级指南将非常重要。

安装

首先,使用 Composer 包管理器安装 Paddle 的 Cashier 包:

composer require laravel/cashier-paddle

注意:为了确保 Cashier 正确处理所有 Paddle 事件,请记得配置 Cashier 的 webhook 处理.

Paddle 沙盒

在本地和预发布开发中,你应该 注册一个 Paddle 沙盒账号。这个账号将为你提供一个沙盒环境来测试和开发你的应用,而不会产生真实的交易。你也许会使用 Paddle 的 测试卡号 来模拟各种交易场景。

在你已经完成你的应用开发之后,你也许会 申请一个 Paddle 正式账号.

数据迁移

Cashier 服务提供者注册它自己的数据迁移目录,所以你记得在安装扩展包之后执行数据迁移。 Cashier 数据迁移将生成新的 customers 表。另外,新的subscriptions 表将被创建,来存储所有你的用户的订阅。最后,新的 receipts 表也将被创建,来存储所有你的收据信息:

php artisan migrate

如果你需要重写 Cashier 中的数据迁移,你可以使用 vendor:publish Artisan 命令来发布它们:

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

如果你想阻止 Cashier 的数据迁移全部执行,你可以使用 Cashier 提供的 ignoreMigrations。通常,这个方法会在 AppServiceProviderregister 方法中被调用:

use Laravel\Paddle\Cashier;

/**
 * Register any application services.
 *
 * @return void
 */
public function register()
{
    Cashier::ignoreMigrations();
}

配置

Billable 模型

在使用 Cashier 之前,您必须将 Billable trait 添加到您的用户模型定义中。 此 trait 提供了多种方法来允许您执行常见的计费任务,例如创建订阅、应用优惠券和更新付款方式信息:

use Laravel\Paddle\Billable;

class User extends Authenticatable
{
    use Billable;
}

如果您有非用户的计费实体,您还可以将特征添加到这些类中:

use Illuminate\Database\Eloquent\Model;
use Laravel\Paddle\Billable;

class Team extends Model
{
    use Billable;
}

API Keys

接下来,您应该在应用程序的 .env 文件中配置您的 Paddle 。 您可以从 Paddle 控制面板检索您的 Paddle API 密钥:

PADDLE_VENDOR_ID=your-paddle-vendor-id
PADDLE_VENDOR_AUTH_CODE=your-paddle-vendor-auth-code
PADDLE_PUBLIC_KEY="your-paddle-public-key"
PADDLE_SANDBOX=true

当您使用 Paddle 的沙箱环境 时,PADDLE_SANDBOX 环境变量应该设置为 true。 如果您将应用程序部署到生产环境并使用 Paddle 的实时供应商环境,则 PADDLE_SANDBOX 变量应该设置为 false

Paddle JS

Paddle 依赖其自己的 JavaScript 库来启动 Paddle 结账小部件。 您可以通过在应用程序布局的关闭 </head> 标签之前放置 @paddleJS Blade 指令来加载 JavaScript 库:

<head>
    ...

    @paddleJS
</head>

货币配置

默认 Cashier 货币是美元 (USD)。 您可以在 .env 文件中定义 CASHIER_CURRENCY 环境变量来更改默认货币:

CASHIER_CURRENCY=EUR

除了配置 Cashier 的货币之外,您还可以指定在格式化货币值以显示在发票上时要使用的区域。Cashier 内部利用 PHP 的 NumberFormatter 来设置货币区域:

CASHIER_CURRENCY_LOCALE=nl_BE

注意:为了使用 en 以外的语言环境,请确保在您的服务器上安装并配置了 ext-intl PHP 扩展。

覆盖默认模型

您可以通过定义自己的模型并继承相应的 Cashier 模型来自由扩展 Cashier 模型:

use Laravel\Paddle\Subscription as CashierSubscription;

class Subscription extends CashierSubscription
{
    // ...
}

定义模型后,您可以通过 Laravel\Paddle\Cashier 类指示 Cashier 使用您的自定义模型。 通常,您应该在应用的 App\Providers\AppServiceProvider 类的 boot 方法中通知 Cashier 关于你的自定义模型:

use App\Models\Cashier\Receipt;
use App\Models\Cashier\Subscription;

/**
 * 引导应用服务
 *
 * @return void
 */
public function boot()
{
    Cashier::useReceiptModel(Receipt::class);
    Cashier::useSubscriptionModel(Subscription::class);
}

核心概念

支付链接

Paddle 缺乏广泛的 CRUD API 来执行订阅状态更改。 因此,与 Paddle 的大多数交互都是通过其 结帐小部件 完成的。 在使用结账小部件之前,我们必须使用 Cashier 生成一个 「支付链接」。 「支付链接」将通知结账小部件我们希望执行的计费操作:

use App\Models\User;
use Illuminate\Http\Request;

Route::get('/user/subscribe', function (Request $request) {
    $payLink = $request->user()->newSubscription('default', $premium = 34567)
        ->returnTo(route('home'))
        ->create();

    return view('billing', ['payLink' => $payLink]);
});

Cashier 包括一个 paddle-button Blade 组件。 我们可以将支付链接 URL 作为 「prop」传递给该组件。 单击此按钮时,将显示 Paddle 的结帐小部件:

<x-paddle-button :url="$payLink" class="px-8 py-4">
    Subscribe
</x-paddle-button>

默认情况下,这将显示一个具有标准 Paddle 样式的按钮。 您可以通过向组件添加 data-theme="none" 属性来删除所有 Paddle 样式:

<x-paddle-button :url="$payLink" class="px-8 py-4" data-theme="none">
    Subscribe
</x-paddle-button>

Paddle 结账小部件是异步的。 一旦用户在小部件中创建或更新订阅,Paddle 将发送您的应用程序 webhook,以便您可以在我们自己的数据库中正确更新订阅状态。 因此,正确 设置 webhooks 以同步 Paddle 的状态变化非常重要。

有关支付链接的更多信息,您可以查看 有关支付链接生成的 Paddle API 文档

注意:订阅状态更改后,接收相应 webhook 的延迟通常很小,但您应该在应用程序中考虑到这一点,因为您的用户订阅在完成结帐后可能不会立即生效。

手动呈现支付链接

你也可以在不使用 Laravel 内置的 Blade 组件的情况下手动渲染支付链接。 首先,生成支付链接 URL,如先前所示:

$payLink = $request->user()->newSubscription('default', $premium = 34567)
    ->returnTo(route('home'))
    ->create();

接下来,只需将支付链接 URL 附加到 HTML 中的 a 元素:

<a href="#!" class="ml-4 paddle_button" data-override="{{ $payLink }}">
    Paddle Checkout
</a>

内联结账

如果您不想使用 Paddle 的 「叠加」样式结帐小部件,Paddle 还提供了内嵌显示小部件的选项。 虽然这种方法不允许您调整任何结帐的 HTML 字段,但它允许您将小部件嵌入到您的应用中。

为了让您轻松开始内联结账,Cashier 包含一个 paddle-checkout Blade 组件。 首先,您应该 生成支付链接 并将支付链接传递给组件的override 属性:

<x-paddle-checkout :override="$payLink" class="w-full" />

要调整内联结帐组件的高度,您可以将 height 属性传递给 Blade 组件:

<x-paddle-checkout :override="$payLink" class="w-full" height="500" />

没有支付链接的内联结账

或者,您可以使用自定义选项而不是使用支付链接来自定义小部件:

$options = [
    'product' => $productId,
    'title' => 'Product Title',
];

<x-paddle-checkout :options="$options" class="w-full" />

请参阅 Paddle 的 Inline Checkout 指南 以及他们的 参数参考 以获取有关内联结帐可用选项的更多详细信息。

注意:如果您想在指定自定义选项时也使用 passthrough 选项,您应该提供一个键/值数组作为其值。 Cashier 将自动处理将数组转换为 JSON 字符串。 此外,customer_id passthrough 选项保留供内部 Cashier 使用。

手动呈现内联结账

你也可以在不使用 Laravel 的内置 Blade 组件的情况下手动渲染内联结账。 首先,生成支付链接 URL 如前面示例中所示

接下来,您可以使用 Paddle.js 来初始化结帐。 为了让这个例子简单,我们将使用 Alpine.js 来演示; 但是,您可以自由地将此示例转换为您自己的前端技术栈:

<div class="paddle-checkout" x-data="{}" x-init="
    Paddle.Checkout.open({
        override: {{ $payLink }},
        method: 'inline',
        frameTarget: 'paddle-checkout',
        frameInitialHeight: 366,
        frameStyle: 'width: 100%; background-color: transparent; border: none;'
    });
">
</div>

用户识别

与 Stripe 相比,Paddle 用户在所有 Paddle 中都是独一无二的,而不是每个 Paddle 帐户都是独一无二的。因此,Paddle 的 API 目前不提供更新用户详细信息(例如电子邮件地址)的方法。在生成支付链接时,Paddle 使用 customer_email 参数识别用户。创建订阅时,Paddle 将尝试将用户提供的电子邮件与现有 Paddle 用户进行匹配。

鉴于这种行为,在使用 Cashier 和 Paddle 时需要记住一些重要的事情。首先,您应该知道,即使 Cashier 中的订阅绑定到同一个应用程序用户,它们也可能绑定到 Paddle 内部系统中的不同用户。其次,每个订阅都有自己的连接支付方式信息,并且在 Paddle 的内部系统中也可能有不同的电子邮件地址(取决于创建订阅时分配给用户的电子邮件)。

因此,在显示订阅时,您应该始终告知用户哪些电子邮件地址或付款方式信息与订阅相关联。可以使用 Laravel\Paddle\Subscription 模型提供的以下方法检索这些信息:

$subscription = $user->subscription('default');

$subscription->paddleEmail();
$subscription->paymentMethod();
$subscription->cardBrand();
$subscription->cardLastFour();
$subscription->cardExpirationDate();

当前,没有办法通过Paddle API修改用户的电子邮件地址。当用户想在Paddle内更新他们的电子邮件地址时,他们唯一的方法是联系Paddle客户支持。在与Paddle沟通时,他们需要提供订阅的paddleEmail,这样Paddle就可以更新正确的用户。

定价

Paddle允许你自定义每种货币对应的价格,也就是说Paddle允许你为不同国家和地区配置不同的价格。Cashier Paddle允许你使用productPrices方法检索一个特定产品的所有价格。这个方法接受你希望检索价格的产品的产品ID:

use Laravel\Paddle\Cashier;

$prices = Cashier::productPrices([123, 456]);

货币将根据请求的IP地址来确定,当然你也可以传入一个可选的国家和地区参数来检索特定国家和地区的价格:

use Laravel\Paddle\Cashier;

$prices = Cashier::productPrices([123, 456], ['customer_country' => 'BE']);

检索出价格后,你可以以任何方式展示它们:

<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product_title }} - {{ $price->price()->gross() }}</li>
    @endforeach
</ul>

你也可以显示净价(不含税)并将税额显示分离:

<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product_title }} - {{ $price->price()->net() }} (+ {{ $price->price()->tax() }} tax)</li>
    @endforeach
</ul>

如果你检索了订阅的价格,你可以分别显示其原始价格和连续订阅价格:

<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product_title }} - 原始价格: {{ $price->initialPrice()->gross() }} - 连续订阅: {{ $price->recurringPrice()->gross() }}</li>
    @endforeach
</ul>

更多相关信息,请 查看 Paddle 的价格 API 文档

客户

如果用户已经是客户并且您希望显示适用于该客户的价格,您可以通过直接从客户实例检索价格来实现:

use App\Models\User;

$prices = User::find(1)->productPrices([123, 456]);

在内部,Cashier 将使用用户的 paddleCountry 方法 来检索以他们的货币表示的价格。 例如,居住在美国的用户将看到以美元为单位的价格,而位于比利时的用户将看到以欧元为单位的价格。 如果找不到匹配的货币,则将使用产品的默认货币。 您可以在 Paddle 控制面板中自定义产品或订阅计划的所有价格。

优惠券

您也可以展示选择优惠券后的折扣价。 在调用 productPrices 方法时,优惠券可以作为逗号分隔的字符串传递:

use Laravel\Paddle\Cashier;

$prices = Cashier::productPrices([123, 456], [
    'coupons' => 'SUMMERSALE,20PERCENTOFF'
]);

然后,使用 price 方法显示计算出的价格:

<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product_title }} - {{ $price->price()->gross() }}</li>
    @endforeach
</ul>

您可以使用 listPrice 方法显示原价(没有优惠券折扣):

<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product_title }} - {{ $price->listPrice()->gross() }}</li>
    @endforeach
</ul>

注意:使用价格 API 时,Paddle 仅允许将优惠券应用于一次性购买的产品,而不允许应用于订阅计划。

客户

客户默认值

Cashier 允许您在创建支付链接时为您的客户定义一些默认值。 设置这些默认值允许您预先填写客户的电子邮件地址、国家 / 地区和邮政编码,以便他们可以立即转到结帐小部件的付款部分。 您可以通过覆盖计费模型上的以下方法来设置这些默认值:

/**
 * 获取客户的电子邮件地址以与 Paddle 关联。
 *
 * @return string|null
 */
public function paddleEmail()
{
    return $this->email;
}

/**
 * 获取客户的国家与 Paddle 关联。
 *
 * 这需要一个 2 个字母的代码。 有关支持的国家 / 地区,请参阅以下链接。
 *
 * @return string|null
 * @link https://developer.paddle.com/reference/platform-parameters/supported-countries
 */
public function paddleCountry()
{
    //
}

/**
 * 获取客户的邮政编码以与 Paddle 关联。
 *
 * 有关需要此功能的国家 / 地区,请参阅以下链接。
 *
 * @return string|null
 * @link https://developer.paddle.com/reference/platform-parameters/supported-countries#countries-requiring-postcode
 */
public function paddlePostcode()
{
    //
}

这些默认值将用于 Cashier 中生成 支付链接 的每个操作。

订阅

创建订阅

要创建订阅,请首先检索计费模型的实例,该实例通常是 App\Models\User 的实例。 检索模型实例后,您可以使用 newSubscription 方法来创建模型的订阅支付链接:

use Illuminate\Http\Request;

Route::get('/user/subscribe', function (Request $request) {
    $payLink = $user->newSubscription('default', $premium = 12345)
        ->returnTo(route('home'))
        ->create();

    return view('billing', ['payLink' => $payLink]);
});

传递给 newSubscription 方法的第一个参数应该是订阅的名称。 如果您的应用只提供一个订阅,您可以将其称为 defaultprimary。 第二个参数是用户订阅的特定计划。 该值应对应于 Paddle 中的计划标识符。 returnTo 方法接受一个 URL,您的用户在成功完成结帐后将被重定向到该 URL。

create 方法将创建一个支付链接,您可以使用它来生成一个支付按钮。 可以使用 Cashier Paddle 附带的 paddle-button Blade 组件 生成支付按钮:

<x-paddle-button :url="$payLink" class="px-8 py-4">
    Subscribe
</x-paddle-button>

用户完成结帐后,将从 Paddle 发送一个 subscription_created webhook。 Cashier 将收到此 webhook 并为您的客户设置订阅。 为了确保您的应用程序正确接收和处理所有 webhook,请确保您正确地 设置 webhook 处理

额外细节

如果您想指定额外的客户或订阅详细信息,您可以通过将它们作为键 / 值对数组传递给 create 方法来实现。 要了解有关 Paddle 支持的其他字段的更多信息,请查看 Paddle 关于 生成支付链接 的文档:

$payLink = $user->newSubscription('default', $monthly = 12345)
    ->returnTo(route('home'))
    ->create([
        'vat_number' => $vatNumber,
    ]);

优惠券

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

$payLink = $user->newSubscription('default', $monthly = 12345)
    ->returnTo(route('home'))
    ->withCoupon('code')
    ->create();

元数据

您还可以使用 withMetadata 方法传递元数据数组:

$payLink = $user->newSubscription('default', $monthly = 12345)
    ->returnTo(route('home'))
    ->withMetadata(['key' => 'value'])
    ->create();

注意:提供元数据时,请避免使用 subscription_name 作为元数据键。 此密钥保留供 Cashier 内部使用。

检查订阅状态

一旦用户订阅了您的应用程序,您就可以使用各种便利的方法检查他们的订阅状态。 首先,如果用户有活动订阅,subscribed 方法返回 true,即使订阅当前处于试用期:

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

subscribed 方法也非常适合 路由中间件,允许您根据用户的订阅状态过滤对路由和控制器的访问:

<?php

namespace App\Http\Middleware;

use Closure;

class EnsureUserIsSubscribed
{
    /**
     * 处理请求
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->user() && ! $request->user()->subscribed('default')) {
            // 此用户非付费客户...
            return redirect('billing');
        }

        return $next($request);
    }
}

如果您想确定用户是否仍在试用期内,您可以使用 onTrial 方法。 此方法可用于确定是否应向用户显示他们仍在试用期的警告:

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

subscribedToPlan 方法可用于根据给定的 Paddle 计划 ID 确定用户是否订阅了给定的计划。 在这个例子中,我们将确定用户的 default 订阅是否订阅包月计划:

if ($user->subscribedToPlan($monthly = 12345, 'default')) {
    //
}

通过将数组传递给 subscribedToPlan 方法,您可以确定用户的 default 订阅是积极订阅月度或年度计划:

if ($user->subscribedToPlan([$monthly = 12345, $yearly = 54321], 'default')) {
    //
}

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

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

已取消订阅状态

要确定用户是否曾经是订阅者但现在已取消订阅,您可以使用 cancelled 方法:

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

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

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

要确定用户是否已取消订阅并且不再处于「宽限期」内,您可以使用 ended 方法:

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

逾期状态

如果订阅的付款失败,它将被标记为 past_due。 当您的订阅处于此状态时,在客户更新其付款信息之前,它不会处于活动状态。 您可以使用订阅实例上的 pastDue 方法来确定订阅是否过期:

if ($user->subscription('default')->pastDue()) {
    //
}

当订阅过期时,您应该指示用户 更新他们的付款信息。 您可以在 Paddle 订阅设置 中配置逾期订阅的处理方式。

如果您希望订阅在 past_due 时仍被视为活动,您可以使用 Cashier 提供的 keepPastDueSubscriptionsActive 方法。 通常,此方法应在您的 AppServiceProviderregister 方法中调用:

use Laravel\Paddle\Cashier;

/**
 * 注册应用服务
 *
 * @return void
 */
public function register()
{
    Cashier::keepPastDueSubscriptionsActive();
}

注意:当订阅处于 past_due 状态时,在付款信息更新之前无法更改。 因此,当订阅处于 past_due 状态时,swapupdateQuantity 方法将抛出异常。

订阅范围

大多数订阅状态也可用作查询范围,以便您可以轻松查询数据库中处于给定状态的订阅:

// 获取所有有效订阅...
$subscriptions = Subscription::query()->active()->get();

// 获取给定用户的所有已取消订阅...
$subscriptions = $user->subscriptions()->cancelled()->get();

可用范围的完整列表如下:

Subscription::query()->active();
Subscription::query()->onTrial();
Subscription::query()->notOnTrial();
Subscription::query()->pastDue();
Subscription::query()->recurring();
Subscription::query()->ended();
Subscription::query()->paused();
Subscription::query()->notPaused();
Subscription::query()->onPausedGracePeriod();
Subscription::query()->notOnPausedGracePeriod();
Subscription::query()->cancelled();
Subscription::query()->notCancelled();
Subscription::query()->onGracePeriod();
Subscription::query()->notOnGracePeriod();

订阅单次收费

订阅单次收费允许您在订阅的基础上向订阅者收取一次性费用:

$response = $user->subscription('default')->charge(12.99, 'Support Add-on');

单一费用 相比,此方法将立即向客户存储的订阅付款方式收费。 收费金额应始终以订阅的货币定义。

更新付款信息

Paddle 始终为每个订阅保存一种付款方式。 如果要更新订阅的默认付款方式,则应首先使用订阅模型上的 updateUrl 方法生成订阅 「更新 URL」:

use App\Models\User;

$user = User::find(1);

$updateUrl = $user->subscription('default')->updateUrl();

然后,您可以将生成的 URL 与 Cashier 提供的 paddle-button Blade 组件结合使用,以允许用户启动 Paddle 小部件并更新他们的付款信息:

<x-paddle-button :url="$updateUrl" class="px-8 py-4">
    Update Card
</x-paddle-button>

当用户更新完他们的信息后,Paddle 将发送一个 subscription_updated webhook,订阅详细信息将在您的应用数据库中更新。

改变计划

用户订阅您的应用程序后,他们可能偶尔想要更改为新的订阅计划。 要为用户更新订阅计划,您应该将 Paddle 计划的标识符传递给订阅的 swap 方法:

use App\Models\User;

$user = User::find(1);

$user->subscription('default')->swap($premium = 34567);

如果用户正在试用,试用期将保持不变。 此外,如果订阅存在 「数量」,则该数量也将保持不变。

如果您想改变计划并取消用户当前所处的试用期,您可以使用 skipTial 方法:

$user->subscription('default')
        ->skipTrial()
        ->swap($premium = 34567);

如果您想改变计划并立即向用户开具发票,而不是等待他们的下一个计费周期,您可以使用 swapAndInvoice 方法:

$user = User::find(1);

$user->subscription('default')->swapAndInvoice($premium = 34567);

按比例分配

默认情况下,Paddle 在计划变更时按比例分配费用。 noProrate 方法可用于在不按比例分配费用的情况下更新订阅:

$user->subscription('default')->noProrate()->swap($premium = 34567);

订阅数量

有时订阅会受到 「数量」的影响。 例如,项目管理应用可能对每个项目每月收费 10 美元。 要增加或减少订阅数量,请使用 incrementQuantitydecrementQuantity 方法:

$user = User::find(1);

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

// 订阅增加 5 个...
$user->subscription('default')->incrementQuantity(5);

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

// 订阅减少 5 个...
$user->subscription('default')->decrementQuantity(5);

或者,您可以使用 updateQuantity 方法设置特定数量:

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

noProrate 方法可用于更新订阅数量而不按比例分配费用:

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

订阅修饰符

订阅修饰符允许您实施 计量计费 或使用附加组件扩展订阅。

例如,您可能想为标准订阅提供 「高级支持」附加组件。 你可以像这样创建这个修饰符:

$modifier = $user->subscription('default')->newModifier(12.99)->create();

上例将向订阅添加 $12.99 的附加组件。 默认情况下,此费用将在您为订阅配置的每个时间周期内重复收取。 如果您愿意,可以使用修饰符的 description 方法向修饰符添加可读的描述:

$modifier = $user->subscription('default')->newModifier(12.99)
    ->description('Premium Support')
    ->create();

为了说明如何使用修饰符实现计量计费,假设您的应用程序对用户发送的每条 SMS 消息收费。 首先,您应该在 Paddle 仪表板中创建一个 $0 的计划。 用户订阅此计划后,您可以向订阅添加代表每个单独费用的修饰符:

$modifier = $user->subscription('default')->newModifier(0.99)
    ->description('New text message')
    ->oneTime()
    ->create();

如您所见,我们在创建此修饰符时调用了 oneTime 方法。 此方法将确保修改器只收费一次,并且不会在每个计费周期重复。

检索修饰符

您可以通过 modifiers 方法检索订阅的所有修饰符的列表:

$modifiers = $user->subscription('default')->modifiers();

foreach ($modifiers as $modifier) {
    $modifier->amount(); // $0.99
    $modifier->description; // 新短信
}

删除修饰符

修改器可以通过调用 Laravel\Paddle\Modifier 实例上的 delete 方法来删除:

$modifier->delete();

暂停订阅

要暂停订阅,请调用用户订阅的 pause 方法:

$user->subscription('default')->pause();

当订阅暂停时,Cashier 将自动在您的数据库中设置 paused_from 列。 此列用于确定 paused 方法何时应该开始返回 true。 例如,如果客户在 3 月 1 日暂停订阅,但该订阅直到 3 月 5 日才计划重复发生,则 paused 方法将继续返回 false,直到 3 月 5 日。 这样做是因为通常允许用户继续使用应用程序,直到他们的计费周期结束。

您可以使用 onPausedGracePeriod 方法确定用户是否已暂停订阅但仍处于 「宽限期」:

if ($user->subscription('default')->onPausedGracePeriod()) {
    //
}

要恢复暂停的订阅,您可以调用用户订阅的 unpause 方法:

$user->subscription('default')->unpause();

注意:订阅暂停时无法修改。 如果您想切换到不同的计划或更新数量,您必须先恢复订阅。

取消订阅

要取消订阅,请调用用户订阅的 cancel 方法:

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

当订阅被取消时,Cashier 将自动在您的数据库中设置 ends_at 列。 此列用于确定 subscribed 方法应该何时开始返回 false。 例如,如果客户在 3 月 1 日取消订阅,但订阅计划在 3 月 5 日之前结束,则 subscribed 方法将在 3 月 5 日之前继续返回 true。 这样做是因为通常允许用户继续使用应用程序,直到他们的计费周期结束。

您可以使用 onGracePeriod 方法确定用户是否已取消订阅但仍处于「宽限期」:

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

如果你想立即取消订阅,你可以调用用户订阅的 cancelNow 方法:

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

注意:取消后无法恢复 Paddle 的订阅。 如果您的客户希望恢复订阅,则他们必须重新订阅。

订阅试用

预先收集付费方式

注意:在预先试用和收集付款方式详细信息时,Paddle 会阻止任何订阅更改,例如更换计划或更新数量。 如果您想允许客户在试用期间更换计划,则必须取消并重新创建订阅。

如果您想为您的客户提供试用期,同时仍然预先收集付款方式信息,您应该在创建订阅付款链接时使用 trialDays 方法:

use Illuminate\Http\Request;

Route::get('/user/subscribe', function (Request $request) {
    $payLink = $request->user()->newSubscription('default', $monthly = 12345)
                ->returnTo(route('home'))
                ->trialDays(10)
                ->create();

    return view('billing', ['payLink' => $payLink]);
});

此方法将在您的应用数据库中的订阅记录上设置试用期结束日期,并指示 Paddle 在此日期之后才开始向客户收费。

注意:如果客户的订阅未在试用结束日期之前取消,他们将在试用到期后立即收费,因此您务必将试用结束日期通知您的用户。

您可以使用用户实例的 onTrial 方法或订阅实例的 onTrial 方法来确定用户是否在试用期内。 下面的两个例子是等价的:

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

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

在 Paddle / Cashier 中定义试用天数

您可以选择在 Paddle 仪表板中定义您的计划接收的试用天数,或者始终使用 Cashier 明确传递它们。 如果您选择在 Paddle 中定义计划的试用天数,您应该知道新订阅,包括过去订阅过的客户的新订阅,将始终获得试用期,除非您明确调用 trialDays(0) 方法。

未预先收集付款方式

如果您想提供试用期而不预先收集用户的付款方式信息,您可以将附加到您的用户的客户记录上的 trial_ends_at 列设置为您想要的试用结束日期。 这通常在用户注册期间完成:

use App\Models\User;

$user = User::create([
    // ...
]);

$user->createAsCustomer([
    'trial_ends_at' => now()->addDays(10)
]);

Cashier 将这种类型的试用称为「通用试用」,因为它不附属于任何现有订阅。 如果当前日期未超过 trial_ends_at 的值,则 User 实例上的 onTrial 方法将返回 true

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

一旦你准备好为用户创建一个实际的订阅,你可以像往常一样使用 newSubscription 方法:

use Illuminate\Http\Request;

Route::get('/user/subscribe', function (Request $request) {
    $payLink = $user->newSubscription('default', $monthly = 12345)
        ->returnTo(route('home'))
        ->create();

    return view('billing', ['payLink' => $payLink]);
});

要检索用户的试用结束日期,您可以使用 trialEndsAt 方法。 如果用户正在试用,则此方法将返回一个 Carbon 日期实例,否则将返回 null。 如果您想获取特定订阅而不是默认订阅的试用结束日期,您还可以传递一个可选的订阅名称参数:

if ($user->onTrial()) {
    $trialEndsAt = $user->trialEndsAt('main');
}

如果您希望明确知道用户处于 「通用」试用期内并且尚未创建实际订阅,则可以使用 onGenericTrial 方法:

if ($user->onGenericTrial()) {
    // 用户在通用试用期内...
}

注意:创建 Paddle 订阅后,无法延长或修改其试用期。

处理 Paddle Webhooks

Paddle 可以通过 webhook 通知您的应用各种事件。默认情况下,指向 Cashier 的 webhook 控制器的路由由 Cashier 服务提供商注册。该控制器将处理所有传入的 webhook 请求。

默认情况下,此控制器将自动处理付费失败过多的取消订阅(由您的 Paddle 订阅设置定义)、订阅更新和付款方式更改;但是,我们很快就会发现,您可以扩展这个控制器来处理您喜欢的任何 Paddle webhook 事件。

为确保您的应用可以处理 Paddle webhooks,请务必 在 Paddle 控制面板中配置 webhook URL。默认情况下,Cashier 的 webhook 控制器响应 /paddle/webhook URL 路径。您应该在 Paddle 控制面板中启用的所有 webhook 的完整列表是:

  • 订阅创建
  • 订阅更新
  • 订阅取消
  • 付款成功
  • 订阅付款成功

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

Webhook 和 CSRF 保护

由于 Paddle webhooks 需要绕过 Laravel 的 CSRF 保护,请务必在你的 App\Http\Middleware\VerifyCsrfToken 中间件中将 URI 作为例外列出或列出外面的路由 web 中间件组的:

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

定义 Webhook 事件处理程序

Cashier 会自动处理失败收费和其他常见 Paddle webhook 的订阅取消,但如果您有其他想要处理的 webhook 事件,您应该扩展 Cashier 的 WebhookController

您的控制器的方法名称应与 Cashier 的控制器方法约定相对应。 具体来说,方法应该以 handle 和你希望处理的 webhook 的 「驼峰」名称为前缀。 例如,如果你想处理 payment_succeeded webhook,你应该向控制器添加一个 handlePaymentSucceeded 方法:

<?php

namespace App\Http\Controllers;

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

class WebhookController extends CashierController
{
    /**
     * 处理支付成功 webhook.
     *
     * @param  array  $payload
     * @return void
     */
    public function handlePaymentSucceeded($payload)
    {
        // 处理事件...
    }
}

接下来,在应用程序的 routes/web.php 文件中定义一个到 Cashier webhook 控制器的路由。 这将覆盖 Cashier 服务提供商注册的默认路由:

use App\Http\Controllers\WebhookController;

Route::post('/paddle/webhook', WebhookController::class);

当接收到 webhook 时,Cashier 会发出一个 Laravel\Paddle\Events\WebhookReceived 事件,并在处理 webhook 时发出一个 Laravel\Paddle\Events\WebhookHandled 事件。 这两个事件都包含 Paddle webhook 的完整负载。

Cashier 还发出专用于接收到的 webhook 类型的事件。 除了来自 Paddle 的完整负载之外,它们还包含用于处理 webhook 的相关模型,例如计费模型、订阅或收据:

  • Laravel\Paddle\Events\PaymentSucceeded
  • Laravel\Paddle\Events\SubscriptionPaymentSucceeded
  • Laravel\Paddle\Events\SubscriptionCreated
  • Laravel\Paddle\Events\SubscriptionUpdated
  • Laravel\Paddle\Events\SubscriptionCancelled

您还可以通过在应用程序的 .env 文件中定义 CASHIER_WEBHOOK 环境变量来覆盖默认的内置 webhook 路由。 该值应该是您的 webhook 路由的完整 URL,并且需要与您在 Paddle 控制面板中设置的 URL 匹配:

CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url

验证 Webhook 签名

为了保护您的 webhook,您可以使用 Paddle 的 webhook 签名。为方便起见,Cashier 自动包含一个中间件,用于验证传入的 Paddle webhook 请求是否有效。

要启用 webhook 验证,请确保在应用程序的 .env 文件中定义了 PADDLE_PUBLIC_KEY 环境变量。公钥可以从您的 Paddle 帐户仪表板中检索。

单次收费

简单收费

如果您想对客户进行一次性收费,您可以在计费模型实例上使用 charge 方法来生成收费的支付链接。 charge 方法接受费用金额(浮点数)作为它的第一个参数和一个费用描述作为它的第二个参数:

use Illuminate\Http\Request;

Route::get('/store', function (Request $request) {
    return view('store', [
        'payLink' => $user->charge(12.99, 'Action Figure')
    ]);
});

生成支付链接后,您可以使用 Cashier 提供的 paddle-button Blade 组件来让用户启动 Paddle 小部件并完成收费:

<x-paddle-button :url="$payLink" class="px-8 py-4">
    Buy
</x-paddle-button>

charge 方法接受一个数组作为它的第三个参数,允许您将任意选项传递给 Paddle 底层支付链接创建。请查阅 Paddle 文档 以了解有关创建费用时可用选项的更多信息:

$payLink = $user->charge(12.99, 'Action Figure', [
    'custom_option' => $value,
]);

费用以 cashier.currency 配置选项中指定的货币进行。默认设置为美元。您可以通过在应用的 .env 文件中定义 CASHIER_CURRENCY 环境变量来覆盖默认货币:

CASHIER_CURRENCY=EUR

你也可以通 Paddle 的动态价格匹配系统 重写每种货币的价格。 这样做,使用一个价格数组替换固定的金额:

$payLink = $user->charge([
    'USD:19.99',
    'EUR:15.99',
], 'Action Figure');

充值商品

如果你想针对通过 Paddle 配置的特定商品进行一次性的充值,你可以在一个计费的模型实例上使用 chargeProduct 来生成支付链接:

use Illuminate\Http\Request;

Route::get('/store', function (Request $request) {
    return view('store', [
        'payLink' => $request->user()->chargeProduct($productId = 123)
    ]);
});

然后,你可以将这个支付链接放在 paddle-button 组件中,让用户来初始化 Panddle 部件:

<x-paddle-button :url="$payLink" class="px-8 py-4">
    Buy
</x-paddle-button>

chargeProduct 方法接收一个数组作为它的第二个参数,让你可以传任何你想传给底层 Paddle 支付链接创建器的配置参数。 请参考 Paddle 文档 关于你可以在创建充值时能够使用的配置:

$payLink = $user->chargeProduct($productId, [
    'custom_option' => $value,
]);

订单退款

如果你需要对一个 Paddle 订单进行退款,你可以使用 refund 方法。 这个方法接收 Paddle 订单的 ID 作为对一个参数。你可以使用 receipts 方法从一个计费模型上拿到收据:

use App\Models\User;

$user = User::find(1);

$receipt = $user->receipts()->first();

$refundRequestId = $user->refund($receipt->order_id);

你可以针对退款灵活地设置退款的金额和原因:

$receipt = $user->receipts()->first();

$refundRequestId = $user->refund(
    $receipt->order_id, 5.00, 'Unused product time'
);

技巧:在联系 Paddle 支持时,您可以使用 $refundRequestId 作为退款的参考。

收据

您可以通过 receipts 属性轻松检索计费模型的收据数组:

use App\Models\User;

$user = User::find(1);

$receipts = $user->receipts;

在为客户列出收据时,您可以使用收据的实例的方法来显示相关信息。 例如,您可能希望在表格中列出每个收据,以便客户轻松下载任何收据:

<table>
    @foreach ($receipts as $receipt)
        <tr>
            <td>{{ $receipt->paid_at->toFormattedDateString() }}</td>
            <td>{{ $receipt->amount() }}</td>
            <td><a href="{{ $receipt->receipt_url }}" target="_blank">Download</a></td>
        </tr>
    @endforeach
</table>

过去和未来的付款

您可以使用 lastPaymentnextPayment 方法来检索和显示客户过去或即将进行的定期订阅付款:

use App\Models\User;

$user = User::find(1);

$subscription = $user->subscription('default');

$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();

这两个方法都会返回一个 Laravel\Paddle\Payment 实例; 但是当计费周期结束时(例如订阅已取消),nextPayment 将返回 null

Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}

处理失败的付款

订阅付款失败有很多原因,例如卡过期或卡内资金不足。 发生这种情况时,我们建议您让 Paddle 为您处理付款失败。 具体您可以在 Paddle 仪表板中设置 Paddle 的自动计费电子邮件

或者您可以通过捕获 subscription_payment_failed Webhook,并在 Paddle 仪表板的 Webhook 设置中启用 「Subscription Payment Failed」选项:

<?php

namespace App\Http\Controllers;

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

class WebhookController extends CashierController
{
    /**
     * Handle subscription payment failed.
     *
     * @param  array  $payload
     * @return void
     */
    public function handleSubscriptionPaymentFailed($payload)
    {
        // Handle the failed subscription payment...
    }
}

测试

在测试时,你应该手动测试您的计费流程,以确保你的集成按预期工作。

对于自动化测试,以及在 CI (持续集成)环境中执行的测试,你可以使用 Laravel 的 HTTP 客户端 来模拟 HTTP 调用 Paddle。 虽然这不能测试 Paddle 的真实的响应,但是它提供了一种方式来测试你的应用,从而避免调用真实的 Paddle API。

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

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
贡献者:6
讨论数量: 0
发起讨论 只看当前版本


暂无话题~