跳到内容

Laravel Cashier (Paddle)

简介

本文档适用于 Cashier Paddle 2.x 与 Paddle Billing 的集成。如果您仍在使用 Paddle Classic,则应使用 Cashier Paddle 1.x

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

在深入研究 Cashier Paddle 之前,我们建议您也查看 Paddle 的 概念指南API 文档

升级 Cashier

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

安装

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

1composer require laravel/cashier-paddle

接下来,您应该使用 vendor:publish Artisan 命令发布 Cashier 迁移文件

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

然后,您应该运行应用程序的数据库迁移。Cashier 迁移将创建一个新的 customers 表。此外,还将创建新的 subscriptionssubscription_items 表来存储所有客户的订阅。最后,将创建一个新的 transactions 表来存储与您的客户关联的所有 Paddle 交易

1php artisan migrate

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

Paddle 沙盒环境

在本地和暂存开发期间,您应该注册一个 Paddle 沙盒帐户。此帐户将为您提供一个沙盒环境,用于测试和开发您的应用程序,而无需进行实际付款。您可以使用 Paddle 的测试卡号来模拟各种付款场景。

当使用 Paddle 沙盒环境时,您应该在应用程序的 .env 文件中将 PADDLE_SANDBOX 环境变量设置为 true

1PADDLE_SANDBOX=true

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

配置

可计费模型

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

1use Laravel\Paddle\Billable;
2 
3class User extends Authenticatable
4{
5 use Billable;
6}

如果您有非用户的可计费实体,您也可以将该 trait 添加到这些类中

1use Illuminate\Database\Eloquent\Model;
2use Laravel\Paddle\Billable;
3 
4class Team extends Model
5{
6 use Billable;
7}

API 密钥

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

1PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-token
2PADDLE_API_KEY=your-paddle-api-key
3PADDLE_RETAIN_KEY=your-paddle-retain-key
4PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret"
5PADDLE_SANDBOX=true

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

PADDLE_RETAIN_KEY 是可选的,只有当您将 Paddle 与 Retain 一起使用时才应设置。

Paddle JS

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

1<head>
2 ...
3 
4 @paddleJS
5</head>

货币配置

您可以指定一种区域设置,用于在发票上显示的货币值格式化。在内部,Cashier 使用 PHP 的 NumberFormatter来设置货币区域设置

1CASHIER_CURRENCY_LOCALE=nl_BE

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

覆盖默认模型

您可以自由地扩展 Cashier 内部使用的模型,方法是定义您自己的模型并扩展相应的 Cashier 模型

1use Laravel\Paddle\Subscription as CashierSubscription;
2 
3class Subscription extends CashierSubscription
4{
5 // ...
6}

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

1use App\Models\Cashier\Subscription;
2use App\Models\Cashier\Transaction;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Cashier::useSubscriptionModel(Subscription::class);
10 Cashier::useTransactionModel(Transaction::class);
11}

快速入门

销售产品

在使用 Paddle Checkout 之前,您应该在 Paddle 仪表板中定义具有固定价格的产品。此外,您应该配置 Paddle 的 webhook 处理

通过您的应用程序提供产品和订阅计费可能令人望而生畏。但是,借助 Cashier 和 Paddle 的 Checkout Overlay,您可以轻松构建现代、强大的支付集成。

要向客户收取非重复性、单次收费产品的费用,我们将利用 Cashier 通过 Paddle 的 Checkout Overlay 向客户收费,客户将在其中提供其付款详细信息并确认购买。通过 Checkout Overlay 付款后,客户将被重定向到您在应用程序中选择的成功 URL

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $request->user()->checkout('pri_deluxe_album')
5 ->returnTo(route('dashboard'));
6 
7 return view('buy', ['checkout' => $checkout]);
8})->name('checkout');

正如您在上面的示例中看到的,我们将使用 Cashier 提供的 checkout 方法创建一个结账对象,以便为客户呈现给定“价格标识符”的 Paddle Checkout Overlay。当使用 Paddle 时,“价格”是指特定产品的已定义价格

如有必要,checkout 方法将自动在 Paddle 中创建一个客户,并将该 Paddle 客户记录连接到您的应用程序数据库中的相应用户。完成结账会话后,客户将被重定向到专用成功页面,您可以在其中向客户显示信息性消息。

buy 视图中,我们将包含一个按钮来显示 Checkout Overlay。 paddle-button Blade 组件包含在 Cashier Paddle 中;但是,您也可以手动渲染覆盖层结账

1<x-paddle-button :checkout="$checkout" class="px-8 py-4">
2 Buy Product
3</x-paddle-button>

向 Paddle Checkout 提供元数据

在销售产品时,通常通过您自己的应用程序定义的 CartOrder 模型来跟踪已完成的订单和购买的产品。当将客户重定向到 Paddle 的 Checkout Overlay 以完成购买时,您可能需要提供现有的订单标识符,以便在客户重定向回您的应用程序时,可以将已完成的购买与相应的订单关联起来。

为了实现这一点,您可以向 checkout 方法提供自定义数据数组。让我们想象一下,当用户开始结账流程时,在我们的应用程序中创建了一个待处理的 Order。请记住,本例中的 CartOrder 模型是说明性的,并非由 Cashier 提供。您可以根据自己应用程序的需求自由地实现这些概念

1use App\Models\Cart;
2use App\Models\Order;
3use Illuminate\Http\Request;
4 
5Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
6 $order = Order::create([
7 'cart_id' => $cart->id,
8 'price_ids' => $cart->price_ids,
9 'status' => 'incomplete',
10 ]);
11 
12 $checkout = $request->user()->checkout($order->price_ids)
13 ->customData(['order_id' => $order->id]);
14 
15 return view('billing', ['checkout' => $checkout]);
16})->name('checkout');

正如您在上面的示例中看到的,当用户开始结账流程时,我们将向 checkout 方法提供购物车/订单的所有关联 Paddle 价格标识符。当然,您的应用程序负责将这些项目与“购物车”或订单关联起来,因为客户会添加它们。我们还通过 customData 方法向 Paddle Checkout Overlay 提供订单的 ID。

当然,您可能希望在客户完成结账流程后将订单标记为“完成”。为了实现这一点,您可以监听 Paddle 分发的 webhook,并通过 Cashier 引发的事件将其存储在数据库中,以存储订单信息。

首先,监听由 Cashier 分发的 TransactionCompleted 事件。通常,您应该在应用程序的 AppServiceProviderboot 方法中注册事件监听器。

1use App\Listeners\CompleteOrder;
2use Illuminate\Support\Facades\Event;
3use Laravel\Paddle\Events\TransactionCompleted;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Event::listen(TransactionCompleted::class, CompleteOrder::class);
11}

在此示例中,CompleteOrder 监听器可能如下所示:

1namespace App\Listeners;
2 
3use App\Models\Order;
4use Laravel\Paddle\Cashier;
5use Laravel\Paddle\Events\TransactionCompleted;
6 
7class CompleteOrder
8{
9 /**
10 * Handle the incoming Cashier webhook event.
11 */
12 public function handle(TransactionCompleted $event): void
13 {
14 $orderId = $event->payload['data']['custom_data']['order_id'] ?? null;
15 
16 $order = Order::findOrFail($orderId);
17 
18 $order->update(['status' => 'completed']);
19 }
20}

有关 transaction.completed 事件包含的数据的更多信息,请参阅 Paddle 的文档:transaction.completed 事件包含的数据

销售订阅

在使用 Paddle Checkout 之前,您应该在 Paddle 仪表板中定义具有固定价格的产品。此外,您应该配置 Paddle 的 webhook 处理

通过您的应用程序提供产品和订阅计费可能令人望而生畏。但是,借助 Cashier 和 Paddle 的 Checkout Overlay,您可以轻松构建现代、强大的支付集成。

要了解如何使用 Cashier 和 Paddle 的 Checkout Overlay 出售订阅,让我们考虑一个简单的场景,即订阅服务提供基本月度(price_basic_monthly)和年度(price_basic_yearly)计划。这两个价格可以在我们的 Paddle 仪表板中归为“Basic”产品(pro_basic)下。此外,我们的订阅服务可能还提供一个 Expert 计划,即 pro_expert

首先,让我们了解客户如何订阅我们的服务。当然,您可以想象客户可能会在我们应用程序的定价页面上点击“订阅”按钮以选择 Basic 计划。此按钮将为其选择的计划调用 Paddle Checkout Overlay。要开始,让我们通过 checkout 方法启动结账会话。

1use Illuminate\Http\Request;
2 
3Route::get('/subscribe', function (Request $request) {
4 $checkout = $request->user()->checkout('price_basic_monthly')
5 ->returnTo(route('dashboard'));
6 
7 return view('subscribe', ['checkout' => $checkout]);
8})->name('subscribe');

subscribe 视图中,我们将包含一个按钮来显示 Checkout Overlay。paddle-button Blade 组件包含在 Cashier Paddle 中;但是,您也可以手动渲染 overlay checkout

1<x-paddle-button :checkout="$checkout" class="px-8 py-4">
2 Subscribe
3</x-paddle-button>

现在,当点击“订阅”按钮时,客户将能够输入其付款详细信息并开始订阅。要了解他们的订阅何时真正开始(因为某些付款方式需要几秒钟才能处理),您还应该配置 Cashier 的 webhook 处理

既然客户可以开始订阅,我们需要限制我们应用程序的某些部分,以便只有订阅用户才能访问它们。当然,我们始终可以通过 Cashier 的 Billable trait 提供的 subscribed 方法来确定用户的当前订阅状态。

1@if ($user->subscribed())
2 <p>You are subscribed.</p>
3@endif

我们甚至可以轻松确定用户是否订阅了特定产品或价格。

1@if ($user->subscribedToProduct('pro_basic'))
2 <p>You are subscribed to our Basic product.</p>
3@endif
4 
5@if ($user->subscribedToPrice('price_basic_monthly'))
6 <p>You are subscribed to our monthly Basic plan.</p>
7@endif

构建订阅中间件

为了方便起见,您可能希望创建一个中间件,以确定传入的请求是否来自订阅用户。一旦定义了此中间件,您就可以轻松地将其分配给路由,以防止未订阅的用户访问该路由。

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class Subscribed
10{
11 /**
12 * Handle an incoming request.
13 */
14 public function handle(Request $request, Closure $next): Response
15 {
16 if (! $request->user()?->subscribed()) {
17 // Redirect user to billing page and ask them to subscribe...
18 return redirect('/subscribe');
19 }
20 
21 return $next($request);
22 }
23}

一旦定义了中间件,您就可以将其分配给路由。

1use App\Http\Middleware\Subscribed;
2 
3Route::get('/dashboard', function () {
4 // ...
5})->middleware([Subscribed::class]);

允许客户管理他们的账单计划

当然,客户可能希望将其订阅计划更改为其他产品或“层级”。在我们上面的示例中,我们希望允许客户将其计划从每月订阅更改为年度订阅。为此,您需要实现类似于一个按钮的功能,该按钮指向以下路由:

1use Illuminate\Http\Request;
2 
3Route::put('/subscription/{price}/swap', function (Request $request, $price) {
4 $user->subscription()->swap($price); // With "$price" being "price_basic_yearly" for this example.
5 
6 return redirect()->route('dashboard');
7})->name('subscription.swap');

除了更换计划外,您还需要允许客户取消订阅。与更换计划一样,提供一个按钮,该按钮指向以下路由:

1use Illuminate\Http\Request;
2 
3Route::put('/subscription/cancel', function (Request $request, $price) {
4 $user->subscription()->cancel();
5 
6 return redirect()->route('dashboard');
7})->name('subscription.cancel');

现在,您的订阅将在其账单周期结束时被取消。

只要您配置了 Cashier 的 webhook 处理,Cashier 将通过检查来自 Paddle 的传入 webhook 自动保持应用程序的 Cashier 相关数据库表同步。因此,例如,当您通过 Paddle 的仪表板取消客户的订阅时,Cashier 将收到相应的 webhook,并在您应用程序的数据库中将订阅标记为“已取消”。

结账会话

大多数向客户收费的操作都通过 Paddle 的 Checkout Overlay 小部件或使用 inline checkout 执行“结账”。

在使用 Paddle 处理结账付款之前,您应该在 Paddle 结账设置仪表板中定义应用程序的默认付款链接

覆盖层结账

在显示 Checkout Overlay 小部件之前,您必须使用 Cashier 生成结账会话。结账会话将告知结账小部件应执行的账单操作。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

Cashier 包含一个 paddle-button Blade 组件。您可以将结账会话作为“prop”传递给此组件。然后,当点击此按钮时,将显示 Paddle 的结账小部件。

1<x-paddle-button :checkout="$checkout" class="px-8 py-4">
2 Subscribe
3</x-paddle-button>

默认情况下,这将使用 Paddle 的默认样式显示小部件。您可以通过添加 Paddle 支持的属性(例如 data-theme='light' 属性)到组件来定制小部件。

1<x-paddle-button :checkout="$checkout" class="px-8 py-4" data-theme="light">
2 Subscribe
3</x-paddle-button>

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

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

手动渲染 Overlay Checkout

您也可以手动渲染 overlay checkout,而无需使用 Laravel 内置的 Blade 组件。要开始,请按照之前的示例生成结账会话。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

接下来,您可以使用 Paddle.js 初始化结账。在此示例中,我们将创建一个分配了 paddle_button 类的链接。Paddle.js 将检测到此类,并在单击链接时显示 overlay checkout。

1<?php
2$items = $checkout->getItems();
3$customer = $checkout->getCustomer();
4$custom = $checkout->getCustomData();
5?>
6 
7<a
8 href='#!'
9 class='paddle_button'
10 data-items='{!! json_encode($items) !!}'
11 @if ($customer) data-customer-id='{{ $customer->paddle_id }}' @endif
12 @if ($custom) data-custom-data='{{ json_encode($custom) }}' @endif
13 @if ($returnUrl = $checkout->getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif
14>
15 Buy Product
16</a>

内联结账

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

为了让您轻松开始使用 inline checkout,Cashier 包含一个 paddle-checkout Blade 组件。要开始,您应该生成结账会话

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

然后,您可以将结账会话传递给组件的 checkout 属性。

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

要调整 inline checkout 组件的高度,您可以将 height 属性传递给 Blade 组件。

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

有关 inline checkout 的定制选项的更多详细信息,请查阅 Paddle 关于 Inline Checkout 的指南可用的结账设置

手动渲染 Inline Checkout

您也可以手动渲染 inline checkout,而无需使用 Laravel 内置的 Blade 组件。要开始,请按照之前的示例生成结账会话。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

接下来,您可以使用 Paddle.js 初始化结账。在此示例中,我们将使用 Alpine.js 进行演示;但是,您可以随意为自己的前端堆栈修改此示例。

1<?php
2$options = $checkout->options();
3 
4$options['settings']['frameTarget'] = 'paddle-checkout';
5$options['settings']['frameInitialHeight'] = 366;
6?>
7 
8<div class="paddle-checkout" x-data="{}" x-init="
9 Paddle.Checkout.open(@json($options));
10">
11</div>

访客结账

有时,您可能需要为不需要在您的应用程序中创建帐户的用户创建结账会话。为此,您可以使用 guest 方法。

1use Illuminate\Http\Request;
2use Laravel\Paddle\Checkout;
3 
4Route::get('/buy', function (Request $request) {
5 $checkout = Checkout::guest(['pri_34567'])
6 ->returnTo(route('home'));
7 
8 return view('billing', ['checkout' => $checkout]);
9});

然后,您可以将结账会话提供给 Paddle 按钮inline checkout Blade 组件。

价格预览

Paddle 允许您自定义每种货币的价格,实际上允许您为不同的国家/地区配置不同的价格。Cashier Paddle 允许您使用 previewPrices 方法检索所有这些价格。此方法接受您希望检索价格的价格 ID。

1use Laravel\Paddle\Cashier;
2 
3$prices = Cashier::previewPrices(['pri_123', 'pri_456']);

货币将根据请求的 IP 地址确定;但是,您可以选择提供特定的国家/地区来检索价格。

1use Laravel\Paddle\Cashier;
2 
3$prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [
4 'country_code' => 'BE',
5 'postal_code' => '1234',
6]]);

检索价格后,您可以随意显示它们。

1<ul>
2 @foreach ($prices as $price)
3 <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
4 @endforeach
5</ul>

您还可以分别显示小计价格和税额。

1<ul>
2 @foreach ($prices as $price)
3 <li>{{ $price->product['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)</li>
4 @endforeach
5</ul>

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

客户价格预览

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

1use App\Models\User;
2 
3$prices = User::find(1)->previewPrices(['pri_123', 'pri_456']);

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

折扣

您也可以选择显示折扣后的价格。在调用 previewPrices 方法时,您可以通过 discount_id 选项提供折扣 ID。

1use Laravel\Paddle\Cashier;
2 
3$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [
4 'discount_id' => 'dsc_123'
5]);

然后,显示计算出的价格。

1<ul>
2 @foreach ($prices as $price)
3 <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
4 @endforeach
5</ul>

客户

客户默认设置

Cashier 允许您在创建结账会话时为客户定义一些有用的默认值。设置这些默认值允许您预先填写客户的电子邮件地址和姓名,以便他们可以立即进入结账小部件的付款部分。您可以通过覆盖您的 billable 模型上的以下方法来设置这些默认值。

1/**
2 * Get the customer's name to associate with Paddle.
3 */
4public function paddleName(): string|null
5{
6 return $this->name;
7}
8 
9/**
10 * Get the customer's email address to associate with Paddle.
11 */
12public function paddleEmail(): string|null
13{
14 return $this->email;
15}

这些默认值将用于 Cashier 中生成结账会话的每个操作。

检索客户

您可以使用 Cashier::findBillable 方法通过其 Paddle 客户 ID 检索客户。此方法将返回 billable 模型的实例。

1use Laravel\Paddle\Cashier;
2 
3$user = Cashier::findBillable($customerId);

创建客户

有时,您可能希望在不开始订阅的情况下创建 Paddle 客户。您可以使用 createAsCustomer 方法完成此操作。

1$customer = $user->createAsCustomer();

返回 Laravel\Paddle\Customer 的实例。一旦客户在 Paddle 中创建,您可以在稍后的日期开始订阅。您可以提供可选的 $options 数组,以传入 Paddle API 支持的任何其他 客户创建参数

1$customer = $user->createAsCustomer($options);

订阅

创建订阅

要创建订阅,首先从数据库中检索 billable 模型的实例,这通常是 App\Models\User 的实例。检索模型实例后,您可以使用 subscribe 方法创建模型的结账会话。

1use Illuminate\Http\Request;
2 
3Route::get('/user/subscribe', function (Request $request) {
4 $checkout = $request->user()->subscribe($premium = 'pri_123', 'default')
5 ->returnTo(route('home'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

提供给 subscribe 方法的第一个参数是用户正在订阅的特定价格。此值应与 Paddle 中的价格标识符相对应。returnTo 方法接受用户在成功完成结账后将被重定向到的 URL。传递给 subscribe 方法的第二个参数应该是订阅的内部“类型”。如果您的应用程序仅提供单个订阅,您可以将其称为 defaultprimary。此订阅类型仅供内部应用程序使用,不应向用户显示。此外,它不应包含空格,并且在创建订阅后永远不应更改。

您还可以使用 customData 方法提供有关订阅的自定义元数据数组。

1$checkout = $request->user()->subscribe($premium = 'pri_123', 'default')
2 ->customData(['key' => 'value'])
3 ->returnTo(route('home'));

创建订阅结账会话后,可以将结账会话提供给 Cashier Paddle 附带的 paddle-button Blade 组件

1<x-paddle-button :checkout="$checkout" class="px-8 py-4">
2 Subscribe
3</x-paddle-button>

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

检查订阅状态

一旦用户订阅了您的应用程序,您可以使用各种便捷的方法检查其订阅状态。首先,如果用户具有有效订阅,即使该订阅当前处于试用期内,subscribed 方法也会返回 true

1if ($user->subscribed()) {
2 // ...
3}

如果您的应用程序提供多个订阅,您可以在调用 subscribed 方法时指定订阅。

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

subscribed 方法也是 路由中间件 的绝佳选择,允许您根据用户的订阅状态过滤对路由和控制器的访问。

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class EnsureUserIsSubscribed
10{
11 /**
12 * Handle an incoming request.
13 *
14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15 */
16 public function handle(Request $request, Closure $next): Response
17 {
18 if ($request->user() && ! $request->user()->subscribed()) {
19 // This user is not a paying customer...
20 return redirect('/billing');
21 }
22 
23 return $next($request);
24 }
25}

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

1if ($user->subscription()->onTrial()) {
2 // ...
3}

subscribedToPrice 方法可用于根据给定的 Paddle 价格 ID 确定用户是否订阅了给定的计划。在此示例中,我们将确定用户的 default 订阅是否有效订阅了月度价格。

1if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) {
2 // ...
3}

recurring 方法可用于确定用户当前是否处于有效订阅状态,并且不再处于试用期或宽限期。

1if ($user->subscription()->recurring()) {
2 // ...
3}

已取消订阅状态

要确定用户是否曾经是有效订阅者但已取消订阅,您可以使用 canceled 方法。

1if ($user->subscription()->canceled()) {
2 // ...
3}

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

1if ($user->subscription()->onGracePeriod()) {
2 // ...
3}

过期未付状态

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

1if ($user->subscription()->pastDue()) {
2 // ...
3}

当订阅过期未付时,您应该指示用户更新他们的付款信息

如果您希望在订阅处于 past_due 状态时仍然被视为有效,您可以使用 Cashier 提供的 keepPastDueSubscriptionsActive 方法。通常,此方法应在您的 AppServiceProviderregister 方法中调用。

1use Laravel\Paddle\Cashier;
2 
3/**
4 * Register any application services.
5 */
6public function register(): void
7{
8 Cashier::keepPastDueSubscriptionsActive();
9}

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

订阅范围

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

1// Get all valid subscriptions...
2$subscriptions = Subscription::query()->valid()->get();
3 
4// Get all of the canceled subscriptions for a user...
5$subscriptions = $user->subscriptions()->canceled()->get();

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

1Subscription::query()->valid();
2Subscription::query()->onTrial();
3Subscription::query()->expiredTrial();
4Subscription::query()->notOnTrial();
5Subscription::query()->active();
6Subscription::query()->recurring();
7Subscription::query()->pastDue();
8Subscription::query()->paused();
9Subscription::query()->notPaused();
10Subscription::query()->onPausedGracePeriod();
11Subscription::query()->notOnPausedGracePeriod();
12Subscription::query()->canceled();
13Subscription::query()->notCanceled();
14Subscription::query()->onGracePeriod();
15Subscription::query()->notOnGracePeriod();

订阅单次收费

订阅单次收费允许您在订阅之上向订阅者收取一次性费用。在调用 charge 方法时,您必须提供一个或多个价格 ID。

1// Charge a single price...
2$response = $user->subscription()->charge('pri_123');
3 
4// Charge multiple prices at once...
5$response = $user->subscription()->charge(['pri_123', 'pri_456']);

在订阅的下一个账单间隔之前,charge 方法实际上不会向客户收费。如果您想立即向客户收费,可以使用 chargeAndInvoice 方法代替。

1$response = $user->subscription()->chargeAndInvoice('pri_123');

更新付款信息

Paddle 始终为每个订阅保存一种付款方式。如果您想更新订阅的默认付款方式,您应该使用订阅模型上的 redirectToUpdatePaymentMethod 方法将客户重定向到 Paddle 托管的付款方式更新页面。

1use Illuminate\Http\Request;
2 
3Route::get('/update-payment-method', function (Request $request) {
4 $user = $request->user();
5 
6 return $user->subscription()->redirectToUpdatePaymentMethod();
7});

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

更改套餐

在用户订阅您的应用程序后,他们有时可能希望更改为新的订阅计划。要更新用户的订阅计划,您应该将 Paddle 价格的标识符传递给订阅的 swap 方法。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->subscription()->swap($premium = 'pri_456');

如果您想更换套餐并立即向用户开具发票,而不是等到他们的下一个账单周期,您可以使用 swapAndInvoice 方法

1$user = User::find(1);
2 
3$user->subscription()->swapAndInvoice($premium = 'pri_456');

按比例收费

默认情况下,在套餐之间切换时,Paddle 会按比例收取费用。可以使用 noProrate 方法来更新订阅,而无需按比例收费

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

如果您想禁用按比例收费并立即向客户开具发票,您可以将 swapAndInvoice 方法与 noProrate 结合使用

1$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456');

或者,如果您不想向客户收取订阅变更的费用,可以使用 doNotBill 方法

1$user->subscription('default')->doNotBill()->swap($premium = 'pri_456');

有关 Paddle 按比例收费政策的更多信息,请参阅 Paddle 的 按比例收费文档

订阅数量

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

1$user = User::find(1);
2 
3$user->subscription()->incrementQuantity();
4 
5// Add five to the subscription's current quantity...
6$user->subscription()->incrementQuantity(5);
7 
8$user->subscription()->decrementQuantity();
9 
10// Subtract five from the subscription's current quantity...
11$user->subscription()->decrementQuantity(5);

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

1$user->subscription()->updateQuantity(10);

可以使用 noProrate 方法来更新订阅的数量,而无需按比例收费

1$user->subscription()->noProrate()->updateQuantity(10);

具有多个产品的订阅的数量

如果您的订阅是具有多个产品的订阅,您应该将您希望增加或减少数量的价格 ID 作为第二个参数传递给 increment / decrement 方法

1$user->subscription()->incrementQuantity(1, 'price_chat');

具有多个产品的订阅

具有多个产品的订阅允许您将多个计费产品分配给单个订阅。例如,假设您正在构建一个客户服务“服务台”应用程序,该应用程序的基本订阅价格为每月 10 美元,但提供一个额外的实时聊天附加产品,每月额外收费 15 美元。

在创建订阅结账会话时,您可以通过将价格数组作为 subscribe 方法的第一个参数来为给定订阅指定多个产品

1use Illuminate\Http\Request;
2 
3Route::post('/user/subscribe', function (Request $request) {
4 $checkout = $request->user()->subscribe([
5 'price_monthly',
6 'price_chat',
7 ]);
8 
9 return view('billing', ['checkout' => $checkout]);
10});

在上面的示例中,客户的 default 订阅将附加两个价格。这两个价格都将在各自的账单周期中收取。如果需要,您可以传递键/值对的关联数组,以指示每个价格的特定数量

1$user = User::find(1);
2 
3$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]);

如果您想向现有订阅添加另一个价格,您必须使用订阅的 swap 方法。当调用 swap 方法时,您还应该包括订阅的当前价格和数量

1$user = User::find(1);
2 
3$user->subscription()->swap(['price_chat', 'price_original' => 2]);

上面的示例将添加新价格,但直到客户的下一个账单周期才会为此计费。如果您想立即向客户收费,可以使用 swapAndInvoice 方法

1$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]);

您可以使用 swap 方法并省略要删除的价格,从订阅中删除价格

1$user->subscription()->swap(['price_original' => 2]);

您不能删除订阅上的最后一个价格。相反,您应该直接取消订阅。

多个订阅

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

当您的应用程序创建订阅时,您可以将订阅类型作为第二个参数提供给 subscribe 方法。该类型可以是任何表示用户正在启动的订阅类型的字符串

1use Illuminate\Http\Request;
2 
3Route::post('/swimming/subscribe', function (Request $request) {
4 $checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming');
5 
6 return view('billing', ['checkout' => $checkout]);
7});

在本示例中,我们为客户启动了一个每月游泳订阅。但是,他们可能希望稍后切换到年度订阅。当调整客户的订阅时,我们可以简单地交换 swimming 订阅的价格

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

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

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

暂停订阅

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

1$user->subscription()->pause();

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

默认情况下,暂停发生在下一个账单间隔,以便客户可以使用他们已付款的剩余期限。如果您想立即暂停订阅,可以使用 pauseNow 方法

1$user->subscription()->pauseNow();

使用 pauseUntil 方法,您可以将订阅暂停到特定时间

1$user->subscription()->pauseUntil(now()->addMonth());

或者,您可以使用 pauseNowUntil 方法立即暂停订阅,直到给定的时间点

1$user->subscription()->pauseNowUntil(now()->addMonth());

您可以使用 onPausedGracePeriod 方法确定用户是否已暂停其订阅,但仍处于其“宽限期”

1if ($user->subscription()->onPausedGracePeriod()) {
2 // ...
3}

要恢复暂停的订阅,您可以在订阅上调用 resume 方法

1$user->subscription()->resume();

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

取消订阅

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

1$user->subscription()->cancel();

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

您可以使用 onGracePeriod 方法确定用户是否已取消其订阅,但仍处于其“宽限期”

1if ($user->subscription()->onGracePeriod()) {
2 // ...
3}

如果您希望立即取消订阅,可以在订阅上调用 cancelNow 方法

1$user->subscription()->cancelNow();

要停止宽限期内的订阅取消,您可以调用 stopCancelation 方法

1$user->subscription()->stopCancelation();

Paddle 的订阅在取消后无法恢复。如果您的客户希望恢复其订阅,他们将必须创建一个新的订阅。

订阅试用

预先付款方式

如果您想在仍然预先收集付款方式信息的同时为客户提供试用期,您应该在客户订阅的价格的 Paddle 仪表板中设置试用时间。然后,像往常一样启动结账会话

1use Illuminate\Http\Request;
2 
3Route::get('/user/subscribe', function (Request $request) {
4 $checkout = $request->user()
5 ->subscribe('pri_monthly')
6 ->returnTo(route('home'));
7 
8 return view('billing', ['checkout' => $checkout]);
9});

当您的应用程序收到 subscription_created 事件时,Cashier 将在您的应用程序数据库中的订阅记录上设置试用期结束日期,并指示 Paddle 在此日期之后才开始向客户收费。

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

您可以使用用户实例的 onTrial 方法来确定用户是否在其试用期内

1if ($user->onTrial()) {
2 // ...
3}

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

1if ($user->hasExpiredTrial()) {
2 // ...
3}

要确定用户是否正在试用特定订阅类型,您可以将类型提供给 onTrialhasExpiredTrial 方法

1if ($user->onTrial('default')) {
2 // ...
3}
4 
5if ($user->hasExpiredTrial('default')) {
6 // ...
7}

不预先付款方式

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

1use App\Models\User;
2 
3$user = User::create([
4 // ...
5]);
6 
7$user->createAsCustomer([
8 'trial_ends_at' => now()->addDays(10)
9]);

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

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

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

1use Illuminate\Http\Request;
2 
3Route::get('/user/subscribe', function (Request $request) {
4 $checkout = $request->user()
5 ->subscribe('pri_monthly')
6 ->returnTo(route('home'));
7 
8 return view('billing', ['checkout' => $checkout]);
9});

要检索用户的试用期结束日期,您可以使用 trialEndsAt 方法。如果用户正在试用,此方法将返回 Carbon 日期实例,如果用户未试用,则返回 null。如果您想获取除默认订阅之外的特定订阅的试用期结束日期,您还可以传递可选的订阅类型参数

1if ($user->onTrial('default')) {
2 $trialEndsAt = $user->trialEndsAt();
3}

如果您想专门了解用户是否在其“通用”试用期内,并且尚未创建实际订阅,则可以使用 onGenericTrial 方法

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

延长或激活试用期

您可以通过调用 extendTrial 方法并指定试用应结束的时间来延长订阅上现有的试用期

1$user->subscription()->extendTrial(now()->addDays(5));

或者,您可以通过调用订阅上的 activate 方法来结束其试用期,从而立即激活订阅

1$user->subscription()->activate();

处理 Paddle Webhooks

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

默认情况下,此控制器将自动处理取消收费失败次数过多的订阅、订阅更新和付款方式更改;但是,正如我们很快将发现的那样,您可以扩展此控制器以处理您喜欢的任何 Paddle Webhook 事件。

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

  • 客户已更新
  • 交易已完成
  • 交易已更新
  • 订阅已创建
  • 订阅已更新
  • 订阅已暂停
  • 订阅已取消

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

Webhook 和 CSRF 保护

由于 Paddle Webhook 需要绕过 Laravel 的 CSRF 保护,因此您应确保 Laravel 不会尝试验证传入 Paddle Webhook 的 CSRF 令牌。为此,您应该在应用程序的 bootstrap/app.php 文件中从 CSRF 保护中排除 paddle/*

1->withMiddleware(function (Middleware $middleware) {
2 $middleware->validateCsrfTokens(except: [
3 'paddle/*',
4 ]);
5})

Webhook 和本地开发

为了使 Paddle 能够在本地开发期间向您的应用程序发送 Webhook,您需要通过站点共享服务(例如 NgrokExpose)公开您的应用程序。如果您正在使用 Laravel Sail 在本地开发应用程序,则可以使用 Sail 的 站点共享命令

定义 Webhook 事件处理程序

Cashier 自动处理因收费失败和其他常见 Paddle Webhook 而导致的订阅取消。但是,如果您有其他想要处理的 Webhook 事件,您可以通过侦听 Cashier 调度的以下事件来执行此操作

  • Laravel\Paddle\Events\WebhookReceived
  • Laravel\Paddle\Events\WebhookHandled

这两个事件都包含 Paddle Webhook 的完整负载。例如,如果您希望处理 transaction.billed Webhook,您可以注册一个 侦听器,该侦听器将处理该事件

1<?php
2 
3namespace App\Listeners;
4 
5use Laravel\Paddle\Events\WebhookReceived;
6 
7class PaddleEventListener
8{
9 /**
10 * Handle received Paddle webhooks.
11 */
12 public function handle(WebhookReceived $event): void
13 {
14 if ($event->payload['event_type'] === 'transaction.billed') {
15 // Handle the incoming event...
16 }
17 }
18}

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

  • Laravel\Paddle\Events\CustomerUpdated
  • Laravel\Paddle\Events\TransactionCompleted
  • Laravel\Paddle\Events\TransactionUpdated
  • Laravel\Paddle\Events\SubscriptionCreated
  • Laravel\Paddle\Events\SubscriptionUpdated
  • Laravel\Paddle\Events\SubscriptionPaused
  • Laravel\Paddle\Events\SubscriptionCanceled

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

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

验证 Webhook 签名

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

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

单次收费

产品收费

如果您想为客户发起产品购买,您可以使用可计费模型实例上的 checkout 方法来生成购买的结账会话。checkout 方法接受一个或多个价格 ID。如有必要,可以使用关联数组来提供正在购买的产品的数量

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]);
5 
6 return view('buy', ['checkout' => $checkout]);
7});

生成结账会话后,您可以使用 Cashier 提供的 paddle-button Blade 组件,以允许用户查看 Paddle 结账小部件并完成购买

1<x-paddle-button :checkout="$checkout" class="px-8 py-4">
2 Buy
3</x-paddle-button>

结账会话具有 customData 方法,允许您将您希望的任何自定义数据传递到底层交易创建。请参阅 Paddle 文档,以了解有关传递自定义数据时可用的选项的更多信息

1$checkout = $user->checkout('pri_tshirt')
2 ->customData([
3 'custom_option' => $value,
4 ]);

交易退款

退款交易会将退款金额退回到您的客户在购买时使用的付款方式。如果您需要退还 Paddle 购买款项,您可以使用 Cashier\Paddle\Transaction 模型上的 refund 方法。此方法接受一个原因作为第一个参数,一个或多个要退款的价格 ID,以及可选的金额作为关联数组。您可以使用 transactions 方法检索给定可计费模型的交易。

例如,假设我们要退还价格为 pri_123pri_456 的特定交易。我们想全额退还 pri_123,但仅退还 pri_456 两美元

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$transaction = $user->transactions()->first();
6 
7$response = $transaction->refund('Accidental charge', [
8 'pri_123', // Fully refund this price...
9 'pri_456' => 200, // Only partially refund this price...
10]);

上面的示例退还了交易中的特定行项目。如果您想退还整个交易,只需提供一个原因

1$response = $transaction->refund('Accidental charge');

有关退款的更多信息,请参阅 Paddle 的退款文档

退款必须始终经过 Paddle 批准才能完全处理。

交易贷记

与退款一样,您也可以为交易记入贷项。为交易记入贷项会将资金添加到客户的余额中,以便可以将其用于将来的购买。交易记入贷项只能针对手动收取的交易进行,而不能针对自动收取的交易(如订阅)进行,因为 Paddle 会自动处理订阅贷项

1$transaction = $user->transactions()->first();
2 
3// Credit a specific line item fully...
4$response = $transaction->credit('Compensation', 'pri_123');

有关更多信息,请参阅 Paddle 关于贷项的文档

贷项只能应用于手动收取的交易。自动收取的交易由 Paddle 本身记入贷项。

交易

您可以通过 transactions 属性轻松检索可计费模型的交易数组

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$transactions = $user->transactions;

交易代表您的产品和购买的付款,并附带发票。只有已完成的交易才会存储在您应用程序的数据库中。

当列出客户的交易时,您可以使用交易实例的方法来显示相关的付款信息。例如,您可能希望在表格中列出每个交易,允许用户轻松下载任何发票

1<table>
2 @foreach ($transactions as $transaction)
3 <tr>
4 <td>{{ $transaction->billed_at->toFormattedDateString() }}</td>
5 <td>{{ $transaction->total() }}</td>
6 <td>{{ $transaction->tax() }}</td>
7 <td><a href="{{ route('download-invoice', $transaction->id) }}" target="_blank">Download</a></td>
8 </tr>
9 @endforeach
10</table>

download-invoice 路由可能如下所示

1use Illuminate\Http\Request;
2use Laravel\Paddle\Transaction;
3 
4Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) {
5 return $transaction->redirectToInvoicePdf();
6})->name('download-invoice');

过去和即将到来的付款

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

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$subscription = $user->subscription();
6 
7$lastPayment = $subscription->lastPayment();
8$nextPayment = $subscription->nextPayment();

这两种方法都将返回 Laravel\Paddle\Payment 的实例;但是,当 Webhook 尚未同步交易时,lastPayment 将返回 null,而当账单周期结束时(例如当订阅已被取消时),nextPayment 将返回 null

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

测试

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

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

Laravel 是最高效的方式,用于
构建、部署和监控软件。