Laravel 收银员(桨)

Introduction

Laravel 收银台桨 提供一个富有表现力的、流畅的界面Paddle's 订阅计费服务。它几乎可以处理您担心的所有样板订阅计费代码。除了基本的订阅管理,Cashier 还可以处理:优惠券、交换订阅、订阅“数量”、取消宽限期等。

在使用 Cashier 时,我们建议您还查看 Paddle 的用户指南API文档.

升级收银台

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

Installation

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

composer require laravel/cashier-paddle

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

桨沙盒

在本地和分期开发期间,您应该注册一个 Paddle Sandbox 帐户.该帐户将为您提供一个沙盒环境,无需实际付款即可测试和开发您的应用程序。你可以使用 Paddle 的测试卡号 模拟各种支付场景。

使用 Paddle Sandbox 环境时,您应该设置PADDLE_SANDBOX 环境变量到true 在你的应用程序中.env 文件:

PADDLE_SANDBOX=true

完成应用程序开发后,您可以申请 Paddle 供应商帐户.在您的应用程序投入生产之前,Paddle 需要批准您的应用程序的域。

数据库迁移

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

php artisan migrate

如果您需要覆盖 Cashier 包含的迁移,您可以使用vendor:publish 工匠命令:

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

如果你想阻止 Cashier 的迁移完全运行,你可以使用ignoreMigrations 出纳员提供。通常,应在register 你的方法AppServiceProvider:

use Laravel\Paddle\Cashier;

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

Configuration

计费模型

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

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 密钥

接下来,您应该在应用程序的.env 文件。您可以从 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_SANDBOX 环境变量应设置为true 当你使用Paddle 的沙箱环境.这PADDLE_SANDBOX 变量应设置为false 如果您正在将应用程序部署到生产环境并使用 Paddle 的实时供应商环境。

桨JS

Paddle 依靠自己的 JavaScript 库来启动 Paddle 结帐小部件。您可以通过将@paddleJS 应用程序布局关闭之前的 Blade 指令</head> 标签:

<head>
    ...

    @paddleJS
</head>

货币配置

默认出纳货币为美元 (USD)。您可以通过定义一个更改默认货币CASHIER_CURRENCY 应用程序中的环境变量.env 文件:

CASHIER_CURRENCY=EUR

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

CASHIER_CURRENCY_LOCALE=nl_BE

Warning
为了使用除en, 确保ext-intl PHP 扩展已在您的服务器上安装和配置。

覆盖默认模型

您可以通过定义自己的模型并扩展相应的 Cashier 模型来自由扩展 Cashier 内部使用的模型:

use Laravel\Paddle\Subscription as CashierSubscription;

class Subscription extends CashierSubscription
{
    // ...
}

定义模型后,您可以指示 Cashier 通过Laravel\Paddle\Cashier 班级。通常,您应该在 Cashier 中告知您的自定义模型boot 你的应用程序的方法App\Providers\AppServiceProvider 班级:

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

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    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]);
});

收银台包括一个paddle-button 刀片组件.我们可能会将付费链接 URL 作为“道具”传递给此组件。单击此按钮时,将显示 Paddle 的结帐小部件:

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

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

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

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

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

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

手动渲染支付链接

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

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

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

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

需要额外确认的付款

有时需要额外的验证以确认和处理付款。发生这种情况时,Paddle 将显示付款确认屏幕。 Paddle 或 Cashier 显示的支付确认屏幕可能会根据特定银行或发卡机构的支付流程进行定制,并且可能包括额外的卡确认、临时小额收费、单独的设备身份验证或其他形式的验证。

在线结帐

如果您不想使用 Paddle 的“覆盖”样式结帐小部件,Paddle 还提供了内联显示小部件的选项。虽然此方法不允许您调整结帐的任何 HTML 字段,但它允许您将小部件嵌入到您的应用程序中。

为了让您轻松开始在线结账,Cashier 包含一个paddle-checkout刀片组件。要开始,您应该生成付费链接 并将付费链接传递给组件的override 属性:

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

要调整内联结帐组件的高度,您可以通过height Blade 组件的属性:

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

没有支付链接的在线结帐

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

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

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

请咨询Paddle的在线结帐指南 以及他们的参数参考 有关在线结帐可用选项的更多详细信息。

Warning
如果您还想使用passthrough option 在指定自定义选项时,你应该提供一个键/值数组作为它的值。 Cashier 将自动处理将数组转换为 JSON 字符串的过程。除此之外customer_id 直通选项保留供内部收银台使用。

手动呈现内联结账

你也可以在不使用 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 更新正确的用户。

Prices

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 }} - Initial: {{ $price->initialPrice()->gross() }} - Recurring: {{ $price->recurringPrice()->gross() }}</li>
    @endforeach
</ul>

了解更多信息,查看 Paddle 关于价格的 API 文档.

Customers

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

use App\Models\User;

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

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

Coupons

您也可以选择在优惠券减少后显示价格。当调用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>

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

Customers

客户违约

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

/**
 * Get the customer's email address to associate with Paddle.
 */
public function paddleEmail(): string|null
{
    return $this->email;
}

/**
 * Get the customer's country to associate with Paddle.
 *
 * This needs to be a 2 letter code. See the link below for supported countries.
 *
 * @link https://developer.paddle.com/reference/platform-parameters/supported-countries
 */
public function paddleCountry(): string|null
{
    // ...
}

/**
 * Get the customer's postal code to associate with Paddle.
 *
 * See the link below for countries which require this.
 *
 * @link https://developer.paddle.com/reference/platform-parameters/supported-countries#countries-requiring-postcode
 */
public function paddlePostcode(): string|null
{
    // ...
}

这些默认值将用于 Cashier 中生成付费链接.

Subscriptions

创建订阅

要创建订阅,首先从数据库中检索可计费模型的实例,这通常是App\Models\User.检索到模型实例后,您可以使用newSubscription 创建模型订阅支付链接的方法:

use Illuminate\Http\Request;

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

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

第一个参数传递给newSubscription method 应该是订阅的内部名称。如果您的应用程序只提供一个订阅,您可以调用它default 或者primary.此订阅名称仅供内部应用程序使用,无意向用户显示。此外,它不应包含空格,并且在创建订阅后绝不能更改。给的第二个参数newSubscription method 是用户订阅的具体计划。该值应与 Paddle 中计划的标识符相对应。这returnTo 方法接受一个 URL,您的用户在成功完成结帐后将被重定向到该 URL。

create 方法将创建一个支付链接,您可以使用该链接生成支付按钮。支付按钮可以使用paddle-button 刀片组件 包含在 Cashier Paddle 中:

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

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

额外细节

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

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

Coupons

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

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

Metadata

您还可以使用withMetadata 方法:

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

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

检查订阅状态

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

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

subscribed 方法也使一个伟大的候选人路由中间件,允许您根据用户的订阅状态过滤对路由和控制器的访问:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureUserIsSubscribed
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->user() && ! $request->user()->subscribed('default')) {
            // This user is not a paying customer...
            return redirect('billing');
        }

        return $next($request);
    }
}

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

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

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

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

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

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()) {
    // ...
}

当订阅过期时,您应该指示用户更新他们的付款信息.您可以配置如何在您的桨订阅设置.

如果您希望订阅在订阅时仍被视为有效past_due, 你可以使用keepPastDueSubscriptionsActive Cashier 提供的方法。通常,应在register 你的方法AppServiceProvider:

use Laravel\Paddle\Cashier;

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

Warning
当订阅处于past_due 声明在更新付款信息之前无法更改。因此,swapupdateQuantity 当订阅处于past_due 状态。

订阅范围

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

// Get all active subscriptions...
$subscriptions = Subscription::query()->active()->get();

// Get all of the cancelled subscriptions for a user...
$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 订阅模式的方法:

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>

当用户完成更新他们的信息时,subscription_updated webhook 将由 Paddle 调度,订阅详细信息将在您的应用程序数据库中更新。

改变计划

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

use App\Models\User;

$user = User::find(1);

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

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

$user = User::find(1);

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

Warning
试用期间不得更换计划。有关此限制的更多信息,请参阅桨文档.

Prorations

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

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

认购数量

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

$user = User::find(1);

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

// Add five to the subscription's current quantity...
$user->subscription('default')->incrementQuantity(5);

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

// Subtract five from the subscription's current quantity...
$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; // New text message.
}

删除修饰符

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

$modifier->delete();

多个订阅

Paddle 允许您的客户同时拥有多个订阅。例如,您可能经营一家提供游泳订阅和举重订阅的健身房,并且每个订阅可能有不同的定价。当然,客户应该能够订阅其中一个或两个计划。

当您的应用程序创建订阅时,您可以将订阅的名称提供给newSubscription 方法。该名称可以是表示用户正在启动的订阅类型的任何字符串:

use Illuminate\Http\Request;

Route::post('/swimming/subscribe', function (Request $request) {
    $request->user()
        ->newSubscription('swimming', $swimmingMonthly = 12345)
        ->create($request->paymentMethodId);

    // ...
});

在此示例中,我们为客户发起了每月一次的游泳订阅。但是,他们以后可能想换成按年订阅。在调整客户的订阅时,我们可以简单地交换价格swimming 订阅:

$user->subscription('swimming')->swap($swimmingYearly = 34567);

当然,您也可以完全取消订阅:

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

暂停订阅

要暂停订阅,请调用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();

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

取消订阅

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

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

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

您可以使用onGracePeriod 方法:

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

如果您想立即取消订阅,您可以致电cancelNow 用户订阅的方法:

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

Warning
取消后无法恢复 Paddle 的订阅。如果您的客户希望恢复订阅,他们将必须订阅新的订阅。

订阅试用

预先支付方式

Warning
在预先试用和收集付款方式详细信息时,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 在该日期之前不要开始向客户收费。

Warning
如果客户的订阅在试用结束日期之前没有取消,他们将在试用期满后立即收费,因此您应该确保通知您的用户他们的试用结束日期。

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

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

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

要确定现有试用版是否已过期,您可以使用hasExpiredTrial 方法:

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

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

在 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 将这种类型的试用称为“通用试用”,因为它不附加到任何现有订阅。这onTrial 上的方法User 实例将返回true 如果当前日期没有超过trial_ends_at:

if ($user->onTrial()) {
    // User is within their trial period...
}

准备好为用户创建实际订阅后,您可以使用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 方法。如果用户正在试用或null 如果他们不是。如果您想获取特定订阅而非默认订阅的试用结束日期,您还可以传递一个可选的订阅名称参数:

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

您可以使用onGenericTrial 如果您想具体了解用户是否处于“通用”试用期并且尚未创建实际订阅,请使用以下方法:

if ($user->onGenericTrial()) {
    // User is within their "generic" trial period...
}

Warning
Paddle 订阅创建后无法延长或修改试用期。

处理 Paddle Webhook

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

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

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

Warning
确保使用收银员保护收到的请求webhook 签名验证 中间件。

Webhook 和 CSRF 保护

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

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

Webhooks 和本地开发

为了使 Paddle 能够在本地开发期间发送您的应用程序 webhook,您需要通过站点共享服务公开您的应用程序,例如Ngrok 或者Expose.如果您正在使用本地开发应用程序Laravel 风帆, 你可以使用 Sail 的站点共享命令.

定义 Webhook 事件处理程序

Cashier 自动处理失败收费和其他常见 Paddle webhooks 的订阅取消。但是,如果您有其他想要处理的 webhook 事件,您可以通过收听以下由 Cashier 调度的事件来实现:

这两个事件都包含 Paddle webhook 的完整负载。例如,如果您希望处理invoice.payment_succeeded webhook,你可以注册一个listener 将处理事件:

<?php

namespace App\Listeners;

use Laravel\Paddle\Events\WebhookReceived;

class PaddleEventListener
{
    /**
     * Handle received Paddle webhooks.
     */
    public function handle(WebhookReceived $event): void
    {
        if ($event->payload['alert_name'] === 'payment_succeeded') {
            // Handle the incoming event...
        }
    }
}

一旦你的监听器被定义,你可以在你的应用程序中注册它EventServiceProvider:

<?php

namespace App\Providers;

use App\Listeners\PaddleEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Paddle\Events\WebhookReceived;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        WebhookReceived::class => [
            PaddleEventListener::class,
        ],
    ];
}

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

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

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

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

验证 Webhook 签名

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

要启用 webhook 验证,请确保PADDLE_PUBLIC_KEY 环境变量在您的应用程序中定义.env 文件。可以从您的 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 widget 并完成充电:

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

charge 方法接受一个数组作为它的第三个参数,允许你将你想要的任何选项传递给底层的 Paddle 支付链接创建。请咨询桨文档 要详细了解创建费用时可用的选项:

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

费用以指定的货币发生cashier.currency 配置选项。默认情况下,这设置为美元。您可以通过定义CASHIER_CURRENCY 应用程序中的环境变量.env 文件:

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 允许用户初始化 Paddle 小部件的组件:

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

chargeProduct 方法接受一个数组作为它的第二个参数,允许您将您希望的任何选项传递给底层 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'
);

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

Receipts

您可以通过以下方式轻松检索可计费模型的收据数组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 仪表板中。

或者,您可以执行更精确的自定义listening 为了subscription_payment_failed 桨事件通过WebhookReceived 收银员发送的事件。您还应确保在 Paddle 仪表板的 Webhook 设置中启用“订阅付款失败”选项:

<?php

namespace App\Listeners;

use Laravel\Paddle\Events\WebhookReceived;

class PaddleEventListener
{
    /**
     * Handle received Paddle webhooks.
     */
    public function handle(WebhookReceived $event): void
    {
        if ($event->payload['alert_name'] === 'subscription_payment_failed') {
            // Handle the failed subscription payment...
        }
    }
}

一旦你的监听器被定义,你应该在你的应用程序中注册它EventServiceProvider:

<?php

namespace App\Providers;

use App\Listeners\PaddleEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Paddle\Events\WebhookReceived;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        WebhookReceived::class => [
            PaddleEventListener::class,
        ],
    ];
}

Testing

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

对于自动化测试,包括在 CI 环境中执行的测试,您可以使用Laravel 的 HTTP 客户端 伪造对 Paddle 的 HTTP 调用。虽然这不会测试来自 Paddle 的实际响应,但它确实提供了一种无需实际调用 Paddle API 即可测试应用程序的方法。

豫ICP备18041297号-2