Laravel Cashier (Stripe)
简介
Laravel Cashier Stripe 为 Stripe 的订阅计费服务提供了一个富有表现力、流畅的接口。它处理了几乎所有你害怕编写的订阅计费样板代码。除了基本的订阅管理,Cashier 还可以处理优惠券、交换订阅、订阅 「数量」、取消宽限期,甚至生成发票 PDF。
升级 Cashier
升级到新版本的 Cashier 时,请务必仔细阅读升级指南。
为了防止破坏性变更,Cashier 使用固定的 Stripe API 版本。 Cashier 14 使用 Stripe API 版本 2022-11-15
。Stripe API 版本将在次要版本上更新,以利用新的 Stripe 功能和改进。
安装
首先,使用 Composer 为 Stripe 安装 Cashier 扩展包:
composer require laravel/cashier
为确保 Cashier 正确处理所有 Stripe 事件,请记得设置 Cashier 的 webhook。
数据库迁移
Cashier 的服务提供器注册了自己的数据库迁移目录,因此请记住在安装此包后迁移数据库。Cashier 迁移将向 users
表中添加多个列,并创建一个新的 subscriptions
表来保存客户的所有订阅:
php artisan migrate
如果需要覆盖 Cashier 附带的迁移,可以使用 vendor:publish
Artisan 命令发布它们:
php artisan vendor:publish --tag="cashier-migrations"
如果你想阻止 Cashier 的迁移完全运行,可以使用 Cashier 提供的ignoreMigrations
方法。通常应在 AppServiceProvider
类的 register
方法中调用此方法:
use Laravel\Cashier\Cashier;
/**
* 注册任何应用程序服务。
*
*/
public function register(): void
{
Cashier::ignoreMigrations();
}
Stripe 建议用于存储 Stripe 标识符的任何列都应区分大小写。因此,在使用 MySQL 时,应该确保将 stripe_id
列排序规则设置为 utf8_bin
。更多关于这方面的信息可以在 Stripe 文档中找到。
配置
订单模型
在使用 Cashier 之前,需要将 Billable
trait 添加到可订单模型定义中。通常会放在 App\Models\User
模型中。这个特性提供了多个方法以便执行常用支付任务,如创建订阅、应用优惠券和更新支付方法信息:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
Cashier 默认假设你的 Billable 模型是 Laravel 自带的 App\Models\User
类。如果需要修改可以在 useCustomerModel
方法定义一个不同的模型。通常此方法在 AppServiceProvider
类的boot
方法中被调用:
use App\Models\Cashier\User;
use Laravel\Cashier\Cashier;
/**
* 引导任何应用程序服务。
*/
public function boot(): void
{
Cashier::useCustomerModel(User::class);
}
如果你使用的不是 Laravel 自带的 App\Models\User
模型,需要发布并修改默认的 Cashier 迁移文件以匹配你使用模型对应的表名。
API 秘钥
接下来需要在 .env
文件中配置 Stripe 秘钥,可以在 Stripe 后台控制面板中 获取Stripe API 秘钥:
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
你应该确保在应用程序的.env
文件中定义了STRIPE_WEBHOOK_SECRET
环境变量,因为该变量用于确保传入的 Webhook 确实来自 Stripe。
货币配置
Cashier 默认货币是美元 (USD),可以在 .env
中设置 CASHIER_CURRENCY
环境变量来修改默认的货币配置:
CASHIER_CURRENCY=eur
除了配置 Cashier 的货币之外,还可以在格式化用于显示在发票上的金额时指定本地化配置。在底层,Cashier 使用了 PHP 的 NumberFormatter
类来设置本地货币:
CASHIER_CURRENCY_LOCALE=nl_BE
为了使用本地化配置而不是 en
,需要确保安装了 PHP ext-intl
PHP 扩展并在服务器上启用配置。
税务配置
感谢Stripe 税务,可以自动计算 Stripe 生成的所有发票的税费。 可以通过应用程序的 App\Providers\AppServiceProvider
类的 boot
方法中调用 calculateTaxes
来启用自动税务计算:
use Laravel\Cashier\Cashier;
/**
* 引导任何应用程序服务。
*/
public function boot(): void
{
Cashier::calculateTaxes();
}
启动税务计算后,任何新订阅和生成的一次性发票都会进行自动税务计算。
为了使这个功能正常使用,客户的账单明细中例如客户姓名、住址、发票 ID 需要同步到 Stripe。你可以使用 Cashier 提供的客户数据同步和 Tax ID 方法来完成此操作。
日志
Cashier 允许你指定日志通道来记录所有与 Stripe 相关的异常。可以通过在 .env
中配置 CASHIER_LOGGER
来指定:
CASHIER_LOGGER=stack
对 Stripe 的 API 调用生成的异常将通过应用程序的默认日志通道记录。
使用自定义模型
你可以通过定义自己的模型并扩展相应的 Cashier
模型来自由扩展 Cashier 内部的模型,增加一些方法:
use Laravel\Cashier\Subscription as CashierSubscription;
class Subscription extends CashierSubscription
{
// ...
}
定义模型后,可以通过 Laravel\Cashier\Cashier
类配置 Cashier 使用自定义的模型。通常还需要在 App\Providers\AppServiceProvider
类的 boot
中注册一下:
use App\Models\Cashier\Subscription;
use App\Models\Cashier\SubscriptionItem;
/**
* 引导任何应用程序服务。
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useSubscriptionItemModel(SubscriptionItem::class);
}
消费者
获取消费者
你可以使用 Cashier::findBillable
方法通过 Stripe ID 查询消费者信息。该方法返回的是一个 billable 模型实例:
use Laravel\Cashier\Cashier;
$user = Cashier::findBillable($stripeId);
创建客户
有时,你可能希望在不开始订阅的情况下创建一个 Stripe 客户。 你可以使用 createAsStripeCustomer
方法完成此操作:
$stripeCustomer = $user->createAsStripeCustomer();
在 Stripe 中创建客户后,你可以在以后开始订阅。 你可以提供一个可选的 $options
数组来传递任何额外的 Stripe API 支持的客户创建参数:
$stripeCustomer = $user->createAsStripeCustomer($options);
如果你想为计费模型返回 Stripe 客户对象,你可以使用 asStripeCustomer
方法:
$stripeCustomer = $user->asStripeCustomer();
如果你想为给定的计费模型检索 Stripe 客户对象,但不确定该计费模型是否已经是 Stripe 中的客户,则可以使用 createOrGetStripeCustomer 方法。 如果尚不存在,此方法将在 Stripe 中创建一个新客户:
$stripeCustomer = $user->createOrGetStripeCustomer();
更新客户
有时,你可能希望直接向 Stripe 客户更新其他信息。 你可以使用 updateStripeCustomer
方法完成此操作。 此方法接受一组 Stripe API 支持的客户更新选项:
$stripeCustomer = $user->updateStripeCustomer($options);
余额
Stripe 允许你贷记或借记客户的「余额」。 稍后,此余额将在新发票上贷记或借记。 要检查客户的总余额,你可以使用计费模型上提供的「余额」方法。 balance
方法将返回以客户货币表示的余额的格式化字符串表示形式:
$balance = $user->balance();
要记入客户的余额,可以为该 creditBalance
方法提供一个值。如果你愿意,还可以提供描述:
$user->creditBalance(500, 'Premium customer top-up.');
为该方法提供一个值 debitBalance
将从客户的余额中扣除:
$user->debitBalance(300, 'Bad usage penalty.');
applyBalance
方法会创建一条客户余额流水记录。可以通过调用 balanceTransactions
方法获取余额交易记录,这有助于提供借记或贷记记录给客户查看:
// 检索所有交易...
$transactions = $user->balanceTransactions();
foreach ($transactions as $transaction) {
// 交易量...
$amount = $transaction->amount(); // $2.31
// 在可用的情况下检索相关发票...
$invoice = $transaction->invoice();
}
税号
Cashier 提供了一种管理客户税号的简便方法。taxIds
例如,taxIds
方法可用于检索作为集合分配给客户的所有税号:
$taxIds = $user->taxIds();
你还可以通过标识符检索客户的特定税号:
$taxId = $user->findTaxId('txi_belgium');
你可以通过向 createTaxId
方法提供有效的 type 和值来创建新的税号:
$taxId = $user->createTaxId('eu_vat', 'BE0123456789');
createTaxId
方法将立即将增值税 ID 添加到客户的帐户中。 增值税 ID 的验证也由 Stripe 完成; 然而,这是一个异步的过程。 你可以通过订阅 customer.tax_id.updated
webhook 事件并检查 增值税 ID verification
参数。 有关处理 webhook 的更多信息,请参阅有关定义 webhook 处理程序的文档。
你可以使用 deleteTaxId
方法删除税号:
$user->deleteTaxId('txi_belgium');
使用 Stripe 同步客户数据
通常,当你的应用程序的用户更新他们的姓名、电子邮件地址或其他也由 Stripe 存储的信息时,你应该通知 Stripe 更新。 这样一来,Stripe 的信息副本将与你的应用程序同步。
要自动执行此操作,你可以在计费模型上定义一个事件侦听器,以响应模型的updated
事件。然后,在你的事件监听器中,你可以在模型上调用 syncStripeCustomerDetails
方法:
use App\Models\User;
use function Illuminate\Events\queueable;
/**
* 模型的「引导」方法。
*/
protected static function booted(): void
{
static::updated(queueable(function (User $customer) {
if ($customer->hasStripeId()) {
$customer->syncStripeCustomerDetails();
}
}));
}
现在,每次更新你的客户模型时,其信息都会与 Stripe 同步。 为方便起见,Cashier 会在初始创建客户时自动将你客户的信息与 Stripe 同步。
你可以通过覆盖 Cashier 提供的各种方法来自定义用于将客户信息同步到 Stripe 的列。 例如,当 Cashier 将客户信息同步到 Stripe 时,你可以重写 stripeName
方法来自定义应该被视为客户「姓名」的属性:
/**
* 获取应同步到 Stripe 的客户名称。
*/
public function stripeName(): string|null
{
return $this->company_name;
}
同样,你可以复写 stripeEmail
、stripePhone
和 stripeAddress
方法。 当更新 Stripe 客户对象时,这些方法会将信息同步到其相应的客户参数。 如果你希望完全控制客户信息同步过程,你可以复写 syncStripeCustomerDetails
方法。
订单入口
Stripe 提供了一个简单的方式来设置订单入口以便用户可以管理订阅、支付方法、以及查看历史账单。你可以在控制器或路由中使用 redirectToBillingPortal
方法将用户重定向到账单入口:
use Illuminate\Http\Request;
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal();
});
默认情况下,当用户完成对订阅的管理后,会将能够通过 Stripe 计费门户中的链接返回到应用的 home 路由,你可以通过传递 URL 作为 redirectToBillingPortal
方法的参数来自定义用户返回的 URL:
use Illuminate\Http\Request;
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal(route('billing'));
});
如果你只想要生成订单入口的 URL,可以使用 billingPortalUrl
方法:
$url = $request->user()->billingPortalUrl(route('billing'));
支付方式
存储支付方式
为了使用 Stripe 创建订阅或者进行「一次性」支付,你需要存储支付方法并从 Stripe 中获取对应的标识符。这种方式可用于实现你是否计划使用这个支付方法进行订阅还是单次收费,下面我们分别来介绍这两种方法。
订阅付款方式
当存储客户的信用卡信息以备将来订阅使用时,必须使用 Stripe「Setup Intents」API 来安全地收集客户的支付方式详细信息。 「Setup Intent」向 Stripe 指示向客户的付款方式收费的目的。 Cashier 的 Billable
特性包括 createSetupIntent
方法,可轻松创建新的设置目的。 你应该从将呈现收集客户付款方式详细信息的表单的路由或控制器调用此方法:
return view('update-payment-method', [
'intent' => $user->createSetupIntent()
]);
创建设置目的并将其传递给视图后,你应该将其秘密附加到将收集付款方式的元素。 例如,考虑这个「更新付款方式」表单:
<input id="card-holder-name" type="text">
<!-- Stripe 元素占位符 -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ $intent->client_secret }}">
更新付款方式
</button>
接下来,可以使用 Stripe.js 库将 Stripe 元素 附加到表单并安全地收集客户的付款详细信息:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
接下来,可以验证卡并使用 Stripe 的 confirmCardSetup
方法从 Stripe 检索安全的「支付方式标识符」:
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', async (e) => {
const { setupIntent, error } = await stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: cardHolderName.value }
}
}
);
if (error) {
// 向用户显示「error.message」...
} else {
// 卡已验证成功...
}
});
订阅付款方式
存储客户的银行卡信息以备将来订阅时使用,必须使用 Stripe「Setup Intents」API 来安全地收集客户的支付方式详细信息。 「设置意图」 向Stripe 指示向客户的付款方式收费的目的。 Cashier 的 Billable
特性包括 createSetupIntent
方法,可轻松创建新的设置意图。你应该从路由或控制器调用此方法,该路由或控制器将呈现收集客户付款方法详细信息的表单:
return view('update-payment-method', [
'intent' => $user->createSetupIntent()
]);
创建设置意图并将其传递给视图后,你应该将其秘密附加到将收集付款方式的元素。 例如,考虑这个「更新付款方式」表单:
<input id="card-holder-name" type="text">
<!-- Stripe 元素占位符 -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ $intent->client_secret }}">
更新付款方式
</button>
接下来,可以使用 Stripe.js 库将 Stripe 元素附加到表单并安全地收集客户的付款详细信息:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
接下来,可以从 Stripe 搜索安全的「支付方式标识符」验证银行卡并使用 Stripe 的 confirmCardSetup
方法:
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', async (e) => {
const { setupIntent, error } = await stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: cardHolderName.value }
}
}
);
if (error) {
// 向用户显示「error.message」...
} else {
// 该银行卡已验证成功...
}
});
银行卡通过 Stripe 验证后,你可以将生成的 setupIntent.payment_method
标识符传递给你的 Laravel 应用程序,在那里它可以附加到客户。 付款方式可以添加为新付款方式或用于更新默认付款方式。 你还可以立即使用付款方式标识符来创建新订阅。
如果你想了解有关设置目的和收集客户付款详细信息的更多信息,请查看 Stripe 提供的概述。
单笔费用的支付方式
当然,在针对客户的支付方式进行单笔收费时,我们只需要使用一次支付方式标识符。 由于 Stripe 的限制,你不能使用客户存储的默认付款 方式进行单笔收费。 你必须允许客户使用 Stripe.js 库输入他们的付款方式详细信息。 例如,考虑以下形式:
<input id="card-holder-name" type="text">
<!-- Stripe 元素占位符 -->
<div id="card-element"></div>
<button id="card-button">
处理付款
</button>
定义这样的表单后,可以使用 Stripe.js 库将Stripe 元素附加到表单并安全地收集客户的付款详细信息:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
接下来,可以验证卡并使用 Stripe 的 createPaymentMethod
方法 从 Stripe 检索安全的「支付方式标识符」-方法):
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
cardButton.addEventListener('click', async (e) => {
const { paymentMethod, error } = await stripe.createPaymentMethod(
'card', cardElement, {
billing_details: { name: cardHolderName.value }
}
);
if (error) {
// 向用户显示「error.message」...
} else {
// 卡已验证成功...
}
});
银行卡通过 Stripe 验证后,你可以将生成的 setupIntent.payment_method
标识符传递给你的 Laravel 应用程序,在那里它可以附加到客户。付款方式可以添加为新付款方式或用于更新默认付款方式。 你还可以立即使用付款方式标识符来创建新订阅。
如果你想了解有关设置目的和收集客户付款详细信息的更多信息,请查看 Stripe 提供的概述.
单笔费用的支付方式
当然,在针对客户的支付方式进行单笔收费时,我们只需要使用一次支付方式标识符。 由于 Stripe 的限制,你不能使用客户存储的默认付款方式进行单笔收费。 你必须允许客户使用 Stripe.js 库输入他们的付款方式详细信息。 例如,考虑以下形式:
<input id="card-holder-name" type="text">
<!-- Stripe 元素占位符 -->
<div id="card-element"></div>
<button id="card-button">
付款流程
</button>
在定义了这样一个表单之后,可以使用 Stripe.js 库将 Stripe Element 附加到表单并安全地收集客户的付款详细信息:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
接下来,可以验证银行卡并使用 Stripe 的 createPaymentMethod
方法:
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
cardButton.addEventListener('click', async (e) => {
const { paymentMethod, error } = await stripe.createPaymentMethod(
'card', cardElement, {
billing_details: { name: cardHolderName.value }
}
);
if (error) {
// 向用户显示「error.message」...
} else {
// 该银行卡已验证成功...
}
});
如果卡验证成功,你可以将 paymentMethod.id
传递给你的 Laravel 应用程序并处理单次收费。
检索付款方式
计费模型实例上的 paymentMethods
方法返回 Laravel\Cashier\PaymentMethod
实例的集合:
$paymentMethods = $user->paymentMethods();
默认情况下,此方法将返回 card
类型的支付方式。要检索不同类型的付款方式,你可以将 type
作为参数传递给该方法:
$paymentMethods = $user->paymentMethods('sepa_debit');
要检索客户的默认付款方式,可以使用 defaultPaymentMethod
方法:
$paymentMethod = $user->defaultPaymentMethod();
你可以使用 findPaymentMethod
方法检索附加到计费模型的特定付款方式:
$paymentMethod = $user->findPaymentMethod($paymentMethodId);
确定用户是否有付款方式
要确定计费模型是否有附加到其帐户的默认付款方式,请调用 hasDefaultPaymentMethod
方法:
if ($user->hasDefaultPaymentMethod()) {
// ...
}
你可以使用 hasPaymentMethod
方法来确定计费模型是否至少有一种支付方式附加到他们的账户:
if ($user->hasPaymentMethod()) {
// ...
}
此方法将确定计费模型是否具有 card
类型的支付方式。 要确定该模型是否存在另一种类型的支付方式,你可以将 type
作为参数传递给该方法:
if ($user->hasPaymentMethod('sepa_debit')) {
// ...
}
更新默认付款方式
updateDefaultPaymentMethod
方法可用于更新客户的默认支付方式信息。 此方法接受 Stripe 支付方式标识符,并将新支付方式指定为默认支付方式:
$user->updateDefaultPaymentMethod($paymentMethod);
如果银行卡验证成功,你可以将paymentMethod.id
传递给你的 Laravel 应用程序并处理 单次收费.
搜索付款方式
计费模型实例上的 paymentMethods
方法返回一组 Laravel\Cashier\PaymentMethod
实例:
$paymentMethods = $user->paymentMethods();
默认情况下,此方法将返回 card
类型的支付方式。 要搜索不同类型的付款方式,你可以将 type
作为参数传递给该方法:
$paymentMethods = $user->paymentMethods('sepa_debit');
要搜索客户的默认付款方式,可以使用 defaultPaymentMethod
方法:
$paymentMethod = $user->defaultPaymentMethod();
你可以使用 findPaymentMethod
方法搜索附加到计费模型的特定付款方式:
$paymentMethod = $user->findPaymentMethod($paymentMethodId);
确定用户是否有付款方式
要确定计费模型是否有附加到其帐户的默认付款方式,请调用 hasDefaultPaymentMethod
方法:
if ($user->hasDefaultPaymentMethod()) {
// ...
}
你可以 hasPaymentMethod
方法来确定计费模型是否至少有一种支付方式附加到他们的帐户:
if ($user->hasPaymentMethod()) {
// ...
}
此方法将确定计费模型是否具有 card
类型的支付方式。 要确定该模型是否存在另一种类型的支付方式,你可以将 type
作为参数传递给该方法:
if ($user->hasPaymentMethod('sepa_debit')) {
// ...
}
更新默认付款方式
updateDefaultPaymentMethod
方法可用于更新客户的默认支付方式信息。 此方法接受 Stripe 付款方式标识符,并将新付款方式指定为默认付款方式:
$user->updateDefaultPaymentMethod($paymentMethod);
要将你的默认支付方式信息与客户在 Stripe 中的默认支付方式信息同步,你可以使用 updateDefaultPaymentMethodFromStripe
方法:
$user->updateDefaultPaymentMethodFromStripe();
客户的默认付款方式只能用于开具发票和创建新订阅。 由于 Stripe 施加的限制,它可能无法用于单次收费。
添加付款方式
要添加新的支付方式,你可以在计费模型上调用 addPaymentMethod
方法,并传递支付方式标识符:
$user->addPaymentMethod($paymentMethod);
要了解如何检索付款方式标识符,请查看付款方式存储文档。
删除付款方式
要删除付款方式,你可以在要删除的 Laravel\Cashier\PaymentMethod
实例上调用 delete
方法:
$paymentMethod->delete();
deletePaymentMethod
方法将从计费模型中删除特定的支付方式:
$user->deletePaymentMethod('pm_visa');
deletePaymentMethods
方法将删除计费模型的所有付款方式信息:
$user->deletePaymentMethods();
默认情况下,此方法将删除 card
类型的支付方式。 要删除不同类型的付款方式,你可以将 type
作为参数 传递给该方法:
$user->deletePaymentMethods('sepa_debit');
如果用户有一个有效的订阅,你的应用程序不应该允许他们删除他们的默认支付方式。
订阅
订阅提供了一种为你的客户设置定期付款的方法。 Cashier 管理的 Stripe 订阅支持多种订阅价格、订阅数量、试用等。
创建订阅
要创建订阅,首先检索你的计费模型的实例,通常是 App\Models\User
的实例。 检索到模型实例后,你可以使用 newSubscription
方法创建模型的订阅:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription(
'default', 'price_monthly'
)->create($request->paymentMethodId);
// ...
});
传递给 newSubscription
方法的第一个参数应该是订阅的内部名称。 如果你的应用程序仅提供单一订阅,你可以称其为 default
或 primary
。 此订阅名称仅供内部应用程序使用,无意向用户显示。 此外,它不应包含空格,并且在创建订阅后绝不能更改。 第二个参数是用户订阅的具体价格。 该值应对应于 Stripe 中的价格标识符。
create
方法接受 Stripe 支付方式标识或 Stripe PaymentMethod
对象,将开始订阅并使用计费模型的 Stripe 客户 ID 和其他相关信息更新你的数据库账单信息。
将支付方式标识符直接传递给 create
订阅方法也会自动将其添加到用户存储的支付方式中。