genealabs / laravel-cashier-braintree
Laravel Cashier 为 Braintree 的订阅计费服务提供了一个表达性强、流畅的接口。
Requires
- php: ^7.2
- braintree/braintree_php: ^4.0
- dompdf/dompdf: ^0.8.0
- illuminate/contracts: ~5.7.0|~5.8.0|~5.9.0|^6.0
- illuminate/database: ~5.7.0|~5.8.0|~5.9.0|^6.0
- illuminate/http: ~5.7.0|~5.8.0|~5.9.0|^6.0
- illuminate/routing: ~5.7.0|~5.8.0|~5.9.0|^6.0
- illuminate/support: ~5.7.0|~5.8.0|~5.9.0|^6.0
- illuminate/view: ~5.7.0|~5.8.0|~5.9.0|^6.0
- nesbot/carbon: ^1.26.3|^2.0
- symfony/http-kernel: ~4.0
Requires (Dev)
- ext-json: *
- fzaninotto/faker: ^1.4
- mockery/mockery: ~1.0
- orchestra/testbench: ^4.1
- phpunit/phpunit: ^8.0
This package is auto-updated.
Last update: 2024-09-13 23:28:10 UTC
README
简介
Laravel Cashier Braintree 为 Braintree 的订阅计费服务提供了一个表达性强、流畅的接口。它处理了您害怕编写的几乎所有样板订阅计费代码。除了基本的订阅管理外,Cashier Braintree 还可以处理优惠券、订阅交换、取消宽限期,甚至可以生成发票 PDF。
该仓库由 Mike Bronner (GeneaLabs) 维护,并基于已停止使用的 Laravel 包。
测试
为了运行库的测试,您需要在本地和您的 Braintree 账户中设置以下详细信息。
本地
环境变量
BRAINTREE_MERCHANT_ID=
BRAINTREE_PUBLIC_KEY=
BRAINTREE_PRIVATE_KEY=
BRAINTREE_MODEL=Laravel\Cashier\Tests\User
您可以在 phpunit.xml.dist
文件中设置这些变量。
Braintree
计划
* Plan ID: monthly-10-1, Price: $10, Billing cycle of every month
* Plan ID: monthly-10-2, Price: $10, Billing cycle of every month
* Plan ID: yearly-100-1, Price: $100, Billing cycle of every 12 months
折扣
* Discount ID: coupon-1, Price: $5
* Discount ID: plan-credit, Price $1
设置
- 禁用重复检查:
设置 > 处理 > 交易
:取消选中重复交易检查
。
官方文档
Cashier Braintree 的文档可以在 Laravel 网站 上找到。
许可证
Laravel Cashier Braintree 是开源软件,许可协议为 MIT 许可协议。
文档
Laravel Cashier 为 Stripe 和 Braintree 的订阅计费服务提供了一个表达性强、流畅的接口。它处理了您害怕编写的几乎所有样板订阅计费代码。除了基本的订阅管理外,Cashier 还可以处理优惠券、订阅交换、订阅“数量”、取消宽限期,甚至可以生成发票 PDF。
如果您只进行“一次性”收费而不提供订阅,则不应使用 Cashier。相反,应直接使用 Stripe 和 Braintree SDK。升级 Cashier
在升级到 Cashier 的新主要版本时,仔细查看升级指南非常重要。
配置
Braintree
Braintree 注意事项
对于许多操作,Cashier 的 Stripe 和 Braintree 实现功能相同。两种服务都提供使用信用卡的订阅计费,但 Braintree 还支持通过 PayPal 进行支付。然而,Braintree 也缺少 Stripe 支持的一些功能。在决定使用 Stripe 或 Braintree 时,您应考虑以下事项
Braintree 支持 PayPal,而 Stripe 不支持。Braintree 不支持订阅的增量和减量方法。这是 Braintree 的限制,而不是 Cashier 的限制。Braintree 不支持基于百分比的折扣。这是 Braintree 的限制,而不是 Cashier 的限制。
Composer
首先,将 Cashier Braintree 包添加到您的依赖项
composer require "laravel/cashier-braintree":"^4.0"
计划信用优惠券
在将 Cashier 与 Braintree 一起使用之前,您需要在 Braintree 控制面板中定义一个计划信用折扣。此折扣将用于正确处理从年费到月费或从月费到年费的订阅更改。
在 Braintree 控制面板中配置的折扣金额可以是任何您希望的价值,因为 Cashier 每次应用优惠券时都会用我们自己的自定义金额覆盖定义的金额。由于 Braintree 不会原封不动地支持在订阅频率之间按比例分配订阅,因此需要此优惠券。
数据库迁移
在使用 Cashier 之前,我们需要准备数据库。我们需要在您的用户表中添加几个列,并创建一个新的订阅表来保存所有客户的订阅
Schema::table('users', function ($table) { $table->string('braintree_id')->nullable(); $table->string('paypal_email')->nullable(); $table->string('card_brand')->nullable(); $table->string('card_last_four')->nullable(); $table->timestamp('trial_ends_at')->nullable(); }); Schema::create('subscriptions', function ($table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('name'); $table->string('braintree_id'); $table->string('braintree_plan'); $table->integer('quantity'); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); });
一旦创建迁移,请运行 Artisan 的 migrate 命令。
可收费模型
接下来,将 Billable 特性添加到你的模型定义中
use Laravel\Cashier\Billable; class User extends Authenticatable { use Billable; }
API 密钥
接下来,你应该在你的 services.php 文件中配置以下选项
'braintree' => [ 'model' => App\User::class, 'environment' => env('BRAINTREE_ENV'), 'merchant_id' => env('BRAINTREE_MERCHANT_ID'), 'public_key' => env('BRAINTREE_PUBLIC_KEY'), 'private_key' => env('BRAINTREE_PRIVATE_KEY'), ],
然后,你应该在你的 AppServiceProvider 服务提供者的 boot 方法中添加以下 Braintree SDK 调用
\Braintree_Configuration::environment(config('services.braintree.environment')); \Braintree_Configuration::merchantId(config('services.braintree.merchant_id')); \Braintree_Configuration::publicKey(config('services.braintree.public_key')); \Braintree_Configuration::privateKey(config('services.braintree.private_key'));
货币配置
Cashier 的默认货币是美元(USD)。你可以通过在某个服务提供者的 boot 方法中调用 Cashier::useCurrency 方法来更改默认货币。useCurrency 方法接受两个字符串参数:货币和货币的符号
use Laravel\Cashier\Cashier; Cashier::useCurrency('eur', '€');
订阅
创建订阅
要创建订阅,首先检索你的可收费模型的一个实例,这通常将是一个 App\User 的实例。一旦检索到模型实例,你可以使用 newSubscription 方法来创建模型的订阅
$user = User::find(1); $user->newSubscription('main', 'premium')->create($stripeToken);
传递给 newSubscription 方法的第一个参数应该是订阅的名称。如果你的应用程序只提供单个订阅,你可能将其称为主或主要。第二个参数是用户订阅的具体 Stripe / Braintree 计划。此值应与 Stripe 或 Braintree 中计划的标识符相对应
接受 Stripe 信用卡/源令牌的 create 方法将开始订阅,并更新你的数据库中的客户 ID 和其他相关账单信息
其他用户详细信息
如果你想指定其他客户详细信息,你可以通过将它们作为 create 方法的第二个参数传递来做到这一点
$user->newSubscription('main', 'monthly')->create($stripeToken, [ 'email' => $email, ]);
要了解更多关于 Stripe 或 Braintree 支持的附加字段的信息,请查看 Stripe 关于创建客户的文档或相应的 Braintree 文档。
优惠券
如果你想在创建订阅时应用优惠券,可以使用 withCoupon 方法
$user->newSubscription('main', 'monthly') ->withCoupon('code') ->create($stripeToken);
检查订阅状态
一旦用户订阅了你的应用程序,你可以使用各种方便的方法轻松检查他们的订阅状态。首先,subscribed 方法如果用户有一个活跃的订阅(即使订阅目前正在试用期内),则返回 true
if ($user->subscribed('main')) { // }
subscribed 方法也是一个很好的路由中间件候选者,允许你根据用户的订阅状态过滤对路由和控制器访问
public function handle($request, Closure $next) { if ($request->user() && ! $request->user()->subscribed('main')) { // This user is not a paying customer... return redirect('billing'); } return $next($request); }
如果你想确定用户是否仍在试用期内,可以使用 onTrial 方法。此方法对于向用户显示他们仍在试用期内警告非常有用
if ($user->subscription('main')->onTrial()) { // }
subscribedToPlan 方法可用于根据给定的 Stripe / Braintree 计划 ID 确定用户是否订阅了特定的计划。在这个例子中,我们将确定用户的主要订阅是否正在订阅月度计划
if ($user->subscribedToPlan('monthly', 'main')) { // }
已取消订阅状态
要确定用户曾经是活跃的订阅者,但现在已取消订阅,你可以使用 cancelled 方法
if ($user->subscription('main')->cancelled()) { // }
你也可以确定用户已取消订阅,但仍在他们的“宽限期”内,直到订阅完全到期。例如,如果用户在 3 月 5 日取消了原定于 3 月 10 日到期的订阅,用户将在 3 月 10 日之前处于“宽限期”。请注意,在此期间 subscribed 方法仍返回 true
if ($user->subscription('main')->onGracePeriod()) { // }
更改计划
用户订阅了你的应用程序后,他们偶尔可能想要切换到新的订阅计划。要将用户切换到新的订阅,将计划的标识符传递给 swap 方法
$user = App\User::find(1); $user->subscription('main')->swap('provider-plan-id');
如果用户在试用期内,试用期间将保持不变。此外,如果订阅存在“数量”,该数量也将保持不变。
如果你想切换计划并取消用户当前所在的任何试用期,可以使用 skipTrial 方法
$user->subscription('main') ->skipTrial() ->swap('provider-plan-id');
订阅数量
订阅数量仅由Cashier的Stripe版本支持。Braintree没有与Stripe的“数量”对应的特性。有时订阅会受到“数量”的影响。例如,您的应用程序可能会对每个账户每月按用户收取10美元的费用。要轻松增加或减少订阅数量,请使用incrementQuantity和decrementQuantity方法。
$user = User::find(1); $user->subscription('main')->incrementQuantity(); // Add five to the subscription's current quantity... $user->subscription('main')->incrementQuantity(5); $user->subscription('main')->decrementQuantity(); // Subtract five to the subscription's current quantity... $user->subscription('main')->decrementQuantity(5);
或者,您可以使用updateQuantity方法设置特定的数量。
$user->subscription('main')->updateQuantity(10);
可以使用noProrate方法更新订阅数量,而无需对收费进行比例调整。
$user->subscription('main')->noProrate()->updateQuantity(10);
有关订阅数量的更多信息,请参阅Stripe文档。
订阅税费
要指定用户在订阅上支付的税费百分比,请在您的账单模型上实现taxPercentage方法,并返回一个介于0和100之间的数值,小数点后不超过两位。
public function taxPercentage() { return 20; }
taxPercentage方法允许您根据模型应用税率,这对于用户遍布多个国家和税率的情况可能很有帮助。
taxPercentage方法仅适用于订阅费用。如果您使用Cashier进行“一次性”收费,您需要在该时刻手动指定税率。
同步税费百分比
当更改taxPercentage方法返回的硬编码值时,用户现有订阅的税费设置将保持不变。如果您希望使用返回的taxPercentage值更新现有订阅的税费,您应该在用户的订阅实例上调用syncTaxPercentage方法。
$user->subscription('main')->syncTaxPercentage();
订阅锚定日期
修改订阅锚定日期仅由Cashier的Stripe版本支持。默认情况下,计费周期锚点是订阅创建的日期,或者如果使用了试用期,则是试用期结束的日期。如果您想修改计费锚定日期,可以使用anchorBillingCycleOn方法。
use App\User; use Carbon\Carbon; $user = User::find(1); $anchor = Carbon::parse('first day of next month'); $user->newSubscription('main', 'premium') ->anchorBillingCycleOn($anchor->startOfDay()) ->create($stripeToken);
有关管理订阅计费周期的更多信息,请参阅Stripe计费周期文档。
取消订阅
要取消订阅,请在用户的订阅上调用cancel方法。
$user->subscription('main')->cancel();
当取消订阅时,Cashier将自动设置数据库中的ends_at列。该列用于确定订阅的方法何时开始返回false。例如,如果客户在3月1日取消了订阅,但订阅原定于3月5日结束,则订阅方法将继续返回true直到3月5日。
您可以使用onGracePeriod方法确定用户是否已取消订阅但仍处于“宽限期”。
if ($user->subscription('main')->onGracePeriod()) { // }
如果您希望立即取消订阅,请在用户的订阅上调用cancelNow方法。
$user->subscription('main')->cancelNow();
恢复订阅
如果用户已取消订阅并且您希望恢复,请使用resume方法。用户必须仍然处于宽限期才能恢复订阅。
$user->subscription('main')->resume();
如果用户取消订阅然后在订阅完全过期之前恢复该订阅,他们不会立即被收费。相反,他们的订阅将被重新激活,并且他们将按照原始计费周期进行收费。
订阅试用期
预先收取信用卡费用
如果您希望在收集付款方式信息的同时为客户提供试用期,请在创建订阅时使用trialDays方法。
$user = User::find(1); $user->newSubscription('main', 'monthly') ->trialDays(10) ->create($stripeToken);
此方法将在数据库中的订阅记录上设置试用期结束日期,并指示Stripe / Braintree在此次日期之后不对客户进行收费。
如果在试用结束日期之前未取消客户的订阅,他们将一试用期满立即被收费,因此您务必通知用户他们的试用结束日期。trialUntil 方法允许您提供一个 DateTime 实例以指定试用期何时结束。
use Carbon\Carbon; $user->newSubscription('main', 'monthly') ->trialUntil(Carbon::now()->addDays(10)) ->create($stripeToken);
您可以使用用户实例的 onTrial 方法或订阅实例的 onTrial 方法来确定用户是否处于试用期内。以下两个示例是相同的。
if ($user->onTrial('main')) { // } if ($user->subscription('main')->onTrial()) { // }
无需预付信用卡
如果您想在不收集用户支付方式信息的情况下提供试用期限,您可以设置用户记录上的 trial_ends_at 列为您期望的试用结束日期。这通常在用户注册期间完成。
$user = User::create([ // Populate other user properties... 'trial_ends_at' => now()->addDays(10), ]);
务必在模型定义中添加 trial_ends_at 的日期修改器。Cashier 将这种试用称为“通用试用”,因为它没有附加到任何现有的订阅上。如果当前日期没有超过 trial_ends_at 的值,则用户实例上的 onTrial 方法将返回 true。
if ($user->onTrial()) { // User is within their trial period... }
如果您想特别知道用户是否处于“通用”试用期内且尚未创建实际订阅,您可以使用 onGenericTrial 方法。
if ($user->onGenericTrial()) { // User is within their "generic" trial period... }
一旦您准备好为用户创建实际订阅,您就可以像往常一样使用 newSubscription 方法。
$user = User::find(1); $user->newSubscription('main', 'monthly')->create($stripeToken);
客户
创建客户
有时,您可能希望在开始订阅之前创建 Stripe 客户。您可以使用 createAsStripeCustomer 方法实现此目的。
$user->createAsStripeCustomer();
一旦在 Stripe 中创建了客户,您可以在稍后的日期开始订阅。
此方法的 Braintree 等价方法是 createAsBraintreeCustomer 方法。
卡
检索信用卡
在可收费的模型实例上的 cards 方法返回 Laravel\Cashier\Card 实例的集合。
$cards = $user->cards();
要检索默认卡,可以使用 defaultCard 方法;
$card = $user->defaultCard();
确定卡是否已保存
您可以使用 hasCardOnFile 方法检查客户是否在其账户中附加了信用卡。
if ($user->hasCardOnFile()) { // }
更新信用卡
可以使用 updateCard 方法来更新客户的信用卡信息。此方法接受一个 Stripe 令牌并将新信用卡指定为默认计费来源。
$user->updateCard($stripeToken);
要将您的卡信息与客户在 Stripe 中的默认卡信息同步,您可以使用 updateCardFromStripe 方法。
$user->updateCardFromStripe();
删除信用卡
要删除卡,您应首先使用 cards 方法检索客户的卡。然后,您可以调用要删除的卡实例上的 delete 方法。
foreach ($user->cards() as $card) { $card->delete(); }
如果您删除了默认卡,请确保使用 updateCardFromStripe 方法与您的数据库同步新的默认卡。deleteCards 方法将删除您的应用程序存储的所有卡信息。
$user->deleteCards();
如果用户有一个有效的订阅,您应该考虑阻止他们删除最后剩下的支付来源。
处理 Braintree Webhooks
Stripe 和 Braintree 都可以通过 Webhooks 通知您的应用程序各种事件。要处理 Braintree Webhooks,定义一个指向 Cashier 的 webhook 控制器的路由。此控制器将处理所有传入的 webhook 请求并将它们调度到适当的控制器方法。
Route::post( 'braintree/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
注册您的路由后,请务必在 Braintree 控制面板设置中配置 webhook URL。默认情况下,此控制器将自动处理由于失败收费次数过多(如您的 Braintree 设置所定义)而取消的订阅;然而,正如我们很快就会发现的,您可以扩展此控制器以处理任何您喜欢的 webhook 事件。
Webhooks & CSRF 保护
由于 Braintree Webhooks 需要绕过 Laravel 的 CSRF 保护,请确保将 URI 列为 VerifyCsrfToken 中间件的异常或在 web 中间件组之外列出路由。
protected $except = [ 'braintree/*', ];
定义 Webhook 事件处理器
收银员会自动处理失败的充值情况下的订阅取消,但如果您想要处理其他 Braintree webhook 事件,请扩展 webhook 控制器。您的函数名称应与 Cashier 期望的约定一致,具体来说,函数名称应以 "handle" 为前缀,后跟您想要处理的 Braintree webhook 的 "camel case" 名称。例如,如果您想处理 dispute_opened webhook,您应该在控制器中添加一个 handleDisputeOpened 函数。
<?php namespace App\Http\Controllers; use Braintree\WebhookNotification; use Laravel\Cashier\Http\Controllers\WebhookController as CashierController; class WebhookController extends CashierController { /** * Handle a new dispute. * * @param \Braintree\WebhookNotification $webhook * @return \Symfony\Component\HttpFoundation\Responses */ public function handleDisputeOpened(WebhookNotification $webhook) { // Handle The Webhook... } }
失败的订阅
如果客户的信用卡过期怎么办?不用担心 - Cashier 包含一个 Webhook 控制器,可以轻松取消客户的订阅。只需将路由指向该控制器即可。
Route::post( 'braintree/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
就这样!失败的支付将被控制器捕获和处理。当 Braintree 确定订阅失败(通常在三次失败的支付尝试后)时,控制器将取消客户的订阅。别忘了:您需要在 Braintree 控制面板设置中配置 webhook URI。
单次收费
简单收费
当使用 Stripe 时,充值方法接受您希望以应用程序使用的货币最低分母充值的金额。然而,当使用 Braintree 时,您应该将完整的美金金额传递给充值方法:如果您想对已订阅客户的信用卡进行一次性收费,您可以在可收费模型实例上使用充值方法。
// Stripe Accepts Charges In Cents... $stripeCharge = $user->charge(100); // Braintree Accepts Charges In Dollars... $user->charge(1);
充值方法接受一个数组作为其第二个参数,允许您将您希望传递给底层 Stripe / Braintree 充值创建的任何选项传递过去。有关创建充值时可用选项的说明,请参阅 Stripe 或 Braintree 文档。
$user->charge(100, [ 'custom_option' => $value, ]);
如果充值失败,充值方法将抛出异常。如果充值成功,方法将返回完整的 Stripe / Braintree 响应。
try { $response = $user->charge(100); } catch (Exception $e) { // }
带有发票的收费
有时您可能需要一次性收费,同时也需要为这次收费生成一张发票,以便您向客户提供 PDF 收据。invoiceFor 方法让您做到这一点。例如,让我们为客户生成一张 5.00 美元的“一次性费用”发票。
// Stripe Accepts Charges In Cents... $user->invoiceFor('One Time Fee', 500); // Braintree Accepts Charges In Dollars... $user->invoiceFor('One Time Fee', 5);
发票将立即对用户的信用卡进行收费。invoiceFor 方法也接受一个数组作为其第三个参数。此数组包含发票项目的计费选项。方法接受的第四个参数也是一个数组。最后一个参数接受发票本身的计费选项。
$user->invoiceFor('Stickers', 500, [ 'quantity' => 50, ], [ 'tax_percent' => 21, ]);
如果您使用 Braintree 作为您的计费提供商,您必须在调用 invoiceFor 方法时包含一个描述选项。
$user->invoiceFor('One Time Fee', 500, [ 'description' => 'your invoice description here', ]);
invoiceFor 方法将创建一个 Stripe 发票,该发票将重试失败的计费尝试。如果您不想发票重试失败的充值,您需要在第一次失败充值后使用 Stripe API 关闭它们。
退款收费
如果您需要退款 Stripe 充值,您可以使用退款方法。此方法将 Stripe 充值 ID 作为其唯一参数。
$stripeCharge = $user->charge(100); $user->refund($stripeCharge->id);
发票
您可以使用 invoices 方法轻松检索一个可收费模型的发票数组。
$invoices = $user->invoices(); // Include pending invoices in the results... $invoices = $user->invoicesIncludingPending();
在列出客户的发票时,您可以使用发票的辅助方法显示相关的发票信息。例如,您可能希望在一个表格中列出每个发票,以便用户可以轻松下载任何一个。
<table> @foreach ($invoices as $invoice) <tr> <td>{{ $invoice->date()->toFormattedDateString() }}</td> <td>{{ $invoice->total() }}</td> <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td> </tr> @endforeach </table>
生成发票 PDF
在路由或控制器中,使用 downloadInvoice 方法生成发票的 PDF 下载。此方法将自动生成正确的 HTTP 响应,将下载发送到浏览器。
use Illuminate\Http\Request; Route::get('user/invoice/{invoice}', function (Request $request, $invoiceId) { return $request->user()->downloadInvoice($invoiceId, [ 'vendor' => 'Your Company', 'product' => 'Your Product', ]); });