Skip to content
赞助商
虚位以待
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

Laravel Cashier Paddle

介绍

Laravel Cashier Paddle 提供了一个对 Paddle 订阅计费服务的表达性、流畅的接口。它处理了几乎所有你不想编写的订阅计费样板代码。除了基本的订阅管理,Cashier 还可以处理:优惠券、交换订阅、订阅“数量”、取消宽限期等。

在使用 Cashier 时,我们建议你也参考 Paddle 的用户指南API 文档

升级 Cashier

在升级到新版本的 Cashier 时,务必仔细查看升级指南

安装

首先,通过 Composer 安装 Paddle 的 Cashier 包:

php
composer require laravel/cashier-paddle

NOTE

为确保 Cashier 正确处理所有 Paddle 事件,请记得设置 Cashier 的 webhook 处理

数据库迁移

Cashier 服务提供者注册了自己的数据库迁移目录,因此在安装包后记得迁移数据库。Cashier 迁移将创建一个新的 customers 表。此外,还将创建一个新的 subscriptions 表来存储所有客户的订阅。最后,将创建一个新的 receipts 表来存储所有的收据信息:

php
php artisan migrate

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

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

如果希望完全阻止 Cashier 的迁移运行,可以使用 Cashier 提供的 ignoreMigrations。通常,此方法应在 AppServiceProviderregister 方法中调用:

php
use Laravel\Paddle\Cashier;

Cashier::ignoreMigrations();

配置

计费模型

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

php
use Laravel\Paddle\Billable;

class User extends Authenticatable
{
    use Billable;
}

如果有不是用户的计费实体,也可以将 trait 添加到这些类中:

php
use Laravel\Paddle\Billable;

class Team extends Model
{
    use Billable;
}

API 密钥

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

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

Paddle JS

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

php
<head>
    ...

    @paddleJS
</head>

货币配置

默认的 Cashier 货币是美元 (USD)。可以通过设置 CASHIER_CURRENCY 环境变量来更改默认货币:

php
CASHIER_CURRENCY=EUR

除了配置 Cashier 的货币外,还可以指定一个用于在发票上显示货币值时的区域设置。内部,Cashier 使用 PHP 的 NumberFormatter 来设置货币区域设置:

php
CASHIER_CURRENCY_LOCALE=nl_BE

NOTE

为了使用 en 以外的区域设置,请确保在服务器上安装并配置了 ext-intl PHP 扩展。

核心概念

支付链接

Paddle 缺乏一个广泛的 CRUD API 来执行状态更改。因此,大多数与 Paddle 的交互都是通过其结账小部件完成的。在我们可以显示结账小部件之前,我们将使用 Cashier 生成一个“支付链接”:

php
$user = User::find(1);

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

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

Cashier 包含一个 paddle-button Blade 组件。我们可以将支付链接 URL 作为“属性”传递给此组件。当单击此按钮时,将显示 Paddle 的结账小部件:

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

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

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

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

在订阅状态更改后,接收相应 webhook 的延迟通常是最小的,但你应该在应用程序中考虑到这一点,因为用户的订阅可能在完成结账后不会立即可用。

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

内嵌结账

如果不想使用“覆盖”样式的结账小部件,Paddle 还提供了一个选项,可以内嵌显示小部件。虽然这种方法不允许你调整结账的任何 HTML 字段,但它允许你在应用程序中嵌入小部件。

为了让你更轻松地开始使用内嵌结账,Cashier 包含一个 paddle-checkout Blade 组件。要开始使用,应该生成一个支付链接并将支付链接传递给组件的 override 属性:

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

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

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

无支付链接的内嵌结账

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

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

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

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

NOTE

如果希望在指定自定义选项时也使用 passthrough 选项,应提供一个键/值数组,因为 Cashier 将自动处理将数组转换为 JSON 字符串。此外,customer_id passthrough 选项是为 Cashier 内部使用保留的。

用户识别

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

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

因此,在显示订阅时,应该始终告知用户与订阅相关联的电子邮件地址或支付方式信息是基于每个订阅的。可以使用 Subscription 模型上的以下方法检索此信息:

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

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

目前无法通过 Paddle API 修改用户的电子邮件地址。当用户希望在 Paddle 中更新其电子邮件地址时,唯一的方法是联系 Paddle 客户支持。在与 Paddle 通信时,他们需要提供订阅的 paddleEmail 值,以帮助 Paddle 更新正确的用户。

价格

Paddle 允许你根据货币自定义价格,实质上允许你为不同国家配置不同的价格。Cashier Paddle 允许你使用 productPrices 方法检索给定产品的所有价格:

php
use Laravel\Paddle\Cashier;

// 检索两个产品的价格...
$prices = Cashier::productPrices([123, 456]);

货币将根据请求的 IP 地址确定;但是,你可以选择性地提供一个特定的国家来检索价格:

php
use Laravel\Paddle\Cashier;

// 检索两个产品的价格...
$prices = Cashier::productPrices([123, 456], ['customer_country' => 'BE']);

在检索价格后,可以随意显示它们:

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

你还可以显示净价(不含税)并单独显示税额:

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

如果你检索了订阅计划的价格,可以分别显示其初始和经常性价格:

php
<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product_title }} - Initial: {{ $price->initialPrice()->gross() }} - Recurring: {{ $price->recurringPrice()->gross() }}</li>
    @endforeach
</ul>

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

客户

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

php
use App\User;

// 检索两个产品的价格...
$prices = User::find(1)->productPrices([123, 456]);

在内部,Cashier 将使用用户的 paddleCountry 方法 来检索其货币的价格。因此,例如,居住在美国的用户将看到美元 (USD) 的价格,而比利时的用户将看到欧元 (EUR) 的价格。如果找不到匹配的货币,将使用产品的默认货币。可以在 Paddle 控制面板中自定义产品或订阅计划的所有价格。

优惠券

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

php
use Laravel\Paddle\Cashier;

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

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

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

可以使用 listPrice 方法显示原始列出的价格(不含优惠券折扣):

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

NOTE

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

客户

客户默认值

Cashier 允许你在创建支付链接时为客户设置一些有用的默认值。设置这些默认值可以让你预先填写客户的电子邮件地址、国家和邮政编码,以便他们可以立即进入结账小部件的支付部分。可以通过覆盖计费用户上的以下方法来设置这些默认值:

php
/**
 * 获取要与 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\User 的实例。一旦检索到模型实例,可以使用 newSubscription 方法创建模型的订阅支付链接:

php
$user = User::find(1);

$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 组件生成:

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

用户完成结账后,Paddle 将发送一个 subscription_created webhook。Cashier 将接收此 webhook 并为客户设置订阅。为了确保所有 webhooks 都能被你的应用程序正确接收和处理,请确保你已正确设置 webhook 处理

额外细节

如果希望指定额外的客户或订阅详细信息,可以通过将它们作为键/值数组传递给 create 方法来实现:

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

要了解 Paddle 支持的其他字段,请查看 Paddle 关于生成支付链接的文档。

优惠券

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

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

元数据

你还可以使用 withMetadata 方法传递一个元数据数组:

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

NOTE

提供元数据时,请避免使用 subscription_name 作为元数据键。此键是为 Cashier 内部使用保留的。

检查订阅状态

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

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

subscribed 方法也是一个很好的路由中间件候选者,允许你根据用户的订阅状态过滤对路由和控制器的访问:

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

    return $next($request);
}

如果希望确定用户是否仍在其试用期内,可以使用 onTrial 方法。此方法可用于向用户显示警告,告知他们仍在试用期内:

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

subscribedToPlan 方法可用于根据给定的 Paddle 计划 ID 确定用户是否订阅了给定计划。在此示例中,我们将确定用户的 default 订阅是否积极订阅了月度计划:

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

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

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

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

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

取消的订阅状态

要确定用户是否曾经是活跃的订阅者,但已取消其订阅,可以使用 cancelled 方法:

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

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

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

要确定用户是否已取消其订阅并且不再在其“宽限期”内,可以使用 ended 方法:

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

订阅范围

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

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

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

以下是可用范围的完整列表:

php
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();

逾期状态

如果订阅的付款失败,它将被标记为 past_due。当你的订阅处于此状态时,它将不会激活,直到客户更新其支付信息。可以使用订阅实例上的 pastDue 方法确定订阅是否逾期:

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

当订阅逾期时,应该指示用户更新其支付信息。可以在你的 Paddle 订阅设置中配置如何处理逾期订阅。

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

php
use Laravel\Paddle\Cashier;

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

NOTE

当订阅处于 past_due 状态时,无法更改它,直到支付信息已更新。因此,当订阅处于 past_due 状态时,swapupdateQuantity 方法将抛出异常。

订阅单次收费

订阅单次收费允许你对订阅者进行一次性收费:

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

单次收费不同,此方法将立即对订阅的客户存储的支付方式进行收费。收费金额始终以订阅当前设置的货币为单位。

更新支付信息

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

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

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

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

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

当用户完成更新其信息后,Paddle 将发送一个 subscription_updated webhook,并且订阅详细信息将在你的应用程序数据库中更新。

更改计划

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

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

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

如果用户处于试用期,试用期将保持不变。此外,如果订阅存在“数量”,该数量也将保持不变。

如果希望在切换计划时取消用户当前的试用期,可以使用 skipTrial 方法:

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

如果希望在切换计划时立即向用户开具发票,而不是等待他们的下一个计费周期,可以使用 swapAndInvoice 方法:

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

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

按比例收费

默认情况下,Paddle 在计划之间切换时按比例收费。noProrate 方法可用于在不按比例收费的情况下更新订阅:

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

订阅数量

有时订阅会受到“数量”的影响。例如,你的应用程序可能会按每个账户每月收费 10 美元。要轻松增加或减少订阅数量,请使用 incrementQuantitydecrementQuantity 方法:

php
$user = User::find(1);

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

// 在订阅的当前数量上增加五个...
$user->subscription('default')->incrementQuantity(5);

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

// 在订阅的当前数量上减少五个...
$user->subscription('default')->decrementQuantity(5);

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

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

noProrate 方法可用于在不按比例收费的情况下更新订阅数量:

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

暂停订阅

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

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

当订阅暂停时,Cashier 将自动在数据库中设置 paused_from 列。此列用于知道何时 paused 方法应开始返回 true。例如,如果客户在 3 月 1 日暂停订阅,但订阅原定于 3 月 5 日续订,则 paused 方法将继续返回 false,直到 3 月 5 日。

可以使用 onPausedGracePeriod 方法确定用户是否已暂停其订阅但仍在其“宽限期”内:

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

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

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

NOTE

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

取消订阅

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

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

当订阅被取消时,Cashier 将自动在数据库中设置 ends_at 列。此列用于知道何时 subscribed 方法应开始返回 false。例如,如果客户在 3 月 1 日取消订阅,但订阅原定于 3 月 5 日结束,则 subscribed 方法将继续返回 true,直到 3 月 5 日。

可以使用 onGracePeriod 方法确定用户是否已取消其订阅但仍在其“宽限期”内:

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

如果希望立即取消订阅,可以调用用户订阅上的 cancelNow 方法:

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

NOTE

Paddle 的订阅在取消后无法恢复。如果你的客户希望恢复其订阅,他们将不得不订阅一个新的订阅。

订阅试用

提前提供支付方式

NOTE

在试用并提前收集支付方式详细信息时,Paddle 阻止任何订阅更改,例如切换计划或更新数量。如果希望在试用期间允许客户切换计划,必须取消并重新创建订阅。

如果希望在仍然收集支付方式信息的情况下向客户提供试用期,应在创建订阅支付链接时使用 trialDays 方法:

php
$user = User::find(1);

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

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

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

NOTE

如果客户的订阅在试用期结束日期之前未被取消,他们将在试用期到期后立即被收费,因此应确保通知用户其试用期结束日期。

可以使用用户实例上的 onTrial 方法或订阅实例上的 onTrial 方法确定用户是否在其试用期内。以下两个示例具有相同的行为:

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

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

在 Paddle / Cashier 中定义试用天数

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

不提前提供支付方式

如果希望在不提前收集用户支付方式信息的情况下提供试用期,可以在用户注册期间将附加到用户的客户记录上的 trial_ends_at 列设置为所需的试用期结束日期:

php
$user = User::create([
    // 其他用户属性...
]);

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

Cashier 将此类型的试用称为“通用试用”,因为它不附加到任何现有订阅。User 实例上的 onTrial 方法将在当前日期未超过 trial_ends_at 值时返回 true

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

如果希望知道用户是否在其“通用”试用期内并且尚未创建实际订阅,可以使用 onGenericTrial 方法:

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

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

php
$user = User::find(1);

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

NOTE

在 Paddle 订阅创建后,无法扩展或修改试用期。

处理 Paddle Webhooks

NOTE

可以使用 Valet 的 share 命令来帮助在本地开发期间测试 webhooks。

Paddle 可以通过 webhooks 通知你的应用程序各种事件。默认情况下,通过 Cashier 服务提供者配置了指向 Cashier 的 webhook 控制器的路由。此控制器将处理所有传入的 webhook 请求。

默认情况下,此控制器将自动处理取消订阅的过多失败收费(如你的 Paddle 订阅设置中定义的),订阅更新和支付方式更改;但是,正如我们将很快发现的那样,你可以扩展此控制器以处理任何你喜欢的 webhook 事件。

为了确保你的应用程序可以处理 Paddle webhooks,请确保在 Paddle 控制面板中配置 webhook URL。默认情况下,Cashier 的 webhook 控制器监听 /paddle/webhook URL 路径。你应该在 Paddle 控制面板中配置的所有 webhooks 的完整列表如下:

  • 订阅创建
  • 订阅更新
  • 订阅删除
  • 付款成功
  • 订阅付款成功

NOTE

确保使用 Cashier 附带的webhook 签名验证中间件保护传入请求。

Webhooks 和 CSRF 保护

由于 Paddle webhooks 需要绕过 Laravel 的 CSRF 保护,请确保在 VerifyCsrfToken 中间件中将 URI 列为例外,或将路由列在 web 中间件组之外:

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

定义 Webhook 事件处理程序

Cashier 自动处理失败收费的订阅取消,但如果你有其他想要处理的 webhook 事件,应扩展 WebhookController。你的方法名称应符合 Cashier 的预期约定,具体来说,方法应以 handle 和你希望处理的 webhook 的“驼峰式”名称为前缀。例如,如果希望处理 payment_succeeded webhook,应在控制器中添加一个 handlePaymentSucceeded 方法:

php
<?php

namespace App\Http\Controllers;

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

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

接下来,在 routes/web.php 文件中定义指向 Cashier 控制器的路由。这将覆盖 Cashier 附带的路由:

php
Route::post(
    'paddle/webhook',
    '\App\Http\Controllers\WebhookController'
);

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

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

  • PaymentSucceeded
  • SubscriptionPaymentSucceeded
  • SubscriptionCreated
  • SubscriptionUpdated
  • SubscriptionCancelled

你还可以选择通过在 .env 文件中设置 CASHIER_WEBHOOK 环境变量来覆盖默认的内置 webhook 路由。此值应为你的 webhook 路由的完整 URL,并需要与 Paddle 控制面板中设置的 URL 匹配:

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

处理失败的订阅

如果客户的信用卡过期怎么办?不用担心 - Cashier 的 Webhook 控制器将为你取消客户的订阅。失败的付款将自动被控制器捕获和处理。控制器将在 Paddle 确定订阅失败时(通常在三次付款尝试失败后)取消客户的订阅。

验证 Webhook 签名

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

要启用 webhook 验证,请确保在 .env 文件中设置了 PADDLE_PUBLIC_KEY 环境变量。可以从你的 Paddle 账户仪表板中检索公钥。

单次收费

简单收费

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

php
$payLink = $user->charge(12.99, 'Product Title');

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

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

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

charge 方法接受一个数组作为第三个参数,允许你将任何选项传递给底层的 Paddle 支付链接创建。请查阅 Paddle 文档以了解创建收费时可用的选项:

php
$payLink = $user->charge(12.99, 'Product Title', [
    'custom_option' => $value,
]);

收费以 cashier.currency 配置选项中指定的货币进行。默认情况下,设置为 USD。可以通过在 .env 文件中设置 CASHIER_CURRENCY 来覆盖默认货币:

php
CASHIER_CURRENCY=EUR

你还可以使用 Paddle 的动态定价匹配系统覆盖每种货币的价格。为此,请传递一个价格数组而不是固定金额:

php
$payLink = $user->charge([
    'USD:19.99',
    'EUR:15.99',
], 'Product Title');

产品收费

如果希望对 Paddle 中配置的特定产品进行“一次性”收费,可以使用计费模型实例上的 chargeProduct 方法生成支付链接:

php
$payLink = $user->chargeProduct($productId);

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

然后,可以将支付链接提供给 paddle-button 组件,以允许用户初始化 Paddle 小部件:

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

chargeProduct 方法接受一个数组作为第二个参数,允许你将任何选项传递给底层的 Paddle 支付链接创建。请查阅 Paddle 文档以了解创建收费时可用的选项:

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

订单退款

如果需要退款 Paddle 订单,可以使用 refund 方法。此方法接受 Paddle 订单 ID 作为第一个参数。可以使用 receipts 方法检索给定计费实体的收据:

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

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

你还可以选择性地指定要退款的特定金额以及退款原因:

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

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

NOTE

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

收据

可以使用 receipts 方法轻松检索计费模型的收据数组:

php
$receipts = $user->receipts();

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

php
<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 方法显示客户的过去或即将到来的经常性订阅付款:

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

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

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

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

处理失败的付款

订阅付款因各种原因而失败,例如卡过期或卡余额不足。当这种情况发生时,我们建议你让 Paddle 为你处理付款失败。具体来说,可以在 Paddle 仪表板中设置 Paddle 的自动计费电子邮件

或者,可以通过捕获 subscription_payment_failed webhook 并在 Paddle 仪表板的 Webhook 设置中启用“订阅付款失败”选项来进行更精细的自定义:

php
<?php

namespace App\Http\Controllers;

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

class WebhookController extends CashierController
{
    /**
     * 处理订阅付款失败。
     *
     * @param  array  $payload
     * @return void
     */
    public function handleSubscriptionPaymentFailed($payload)
    {
        // 处理失败的订阅付款...
    }
}

测试

Paddle 目前缺乏一个适当的 CRUD API,因此你需要手动测试你的计费流程。Paddle 也缺乏一个沙盒开发环境,因此你进行的任何卡收费都是实时收费。为了绕过这一点,我们建议在测试期间使用 100% 折扣的优惠券或免费产品。