airondev/laravel-cashier-paystack

一个Laravel Cashier包装器,它提供了对Paystack订阅发票服务的高度表达性和流畅的接口。

v1 2023-12-27 11:01 UTC

This package is auto-updated.

Last update: 2024-09-29 18:04:06 UTC


README

Build Status Latest Stable Version Total Downloads License

介绍

Cashier Paystack提供了一个高度表达性和流畅的接口,用于访问Paystack的订阅计费服务。它处理了您所担心的几乎所有样板订阅计费代码。

Composer

首先,将Paystack的Cashier包添加到您的依赖项中

composer require airondev/laravel-cashier-paystack

配置

您可以使用此命令发布配置文件

php artisan vendor:publish --provider="Unicodeveloper\Paystack\PaystackServiceProvider"

一个名为paystack.php的配置文件,其中包含一些合理的默认值,将被放置在您的配置目录中

<?php

return [

    /**
     * Public Key From Paystack Dashboard
     *
     */
    'publicKey' => getenv('PAYSTACK_PUBLIC_KEY'),

    /**
     * Secret Key From Paystack Dashboard
     *
     */
    'secretKey' => getenv('PAYSTACK_SECRET_KEY'),

    /**
     * Paystack Payment URL
     *
     */
    'paymentUrl' => getenv('PAYSTACK_PAYMENT_URL'),

    /**
     * Optional email address of the merchant
     *
     */
    'merchantEmail' => getenv('MERCHANT_EMAIL'),

    /**
     * User model for customers
     *
     */
    'model' => getenv('PAYSTACK_MODEL'),

];

更新您的.env文件中的用户模型

PAYSTACK_MODEL='App\Model\User'

数据库迁移

在使用Cashier之前,我们还需要准备数据库。我们需要向您的用户表添加几个列,并创建一个新的订阅表来保存所有客户的订阅

Schema::table('users', function ($table) {
    $table->string('paystack_id')->nullable();
    $table->string('paystack_code')->nullable();
    $table->string('card_brand')->nullable();
    $table->string('card_last_four', 4)->nullable();
    $table->timestamp('trial_ends_at')->nullable();
});
Schema::create('subscriptions', function ($table) {
    $table->increments('id');
    $table->unsignedInteger('user_id');
    $table->string('name');
    $table->string('paystack_id')->nullable();
    $table->string('paystack_code')->nullable();
    $table->string('paystack_plan');
    $table->integer('quantity');
    $table->timestamp('trial_ends_at')->nullable();
    $table->timestamp('ends_at')->nullable();
    $table->timestamps();
});

一旦创建迁移,运行migrate Artisan命令。

可计费模型

接下来,将Billable trait添加到您的模型定义中。此trait提供了各种方法,允许您执行常见的计费任务,例如创建订阅、应用优惠券和更新信用卡信息

use Airondev\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

货币配置

Cashier的默认货币是尼日利亚奈拉(NGN)。您可以通过在您的服务提供商的boot方法中调用Cashier::useCurrency方法来更改默认货币。useCurrency方法接受两个字符串参数:货币和货币的符号

use Airondev\Cashier\Cashier;

Cashier::useCurrency('ngn', '');
Cashier::useCurrency('ghs', 'GH₵');

订阅

创建订阅

要创建订阅,首先检索您的可计费模型实例,通常是App\Model\User的实例。一旦检索到模型实例,您可以使用newSubscription方法创建模型订阅

$user = User::find(1);
$plan_name = // Paystack plan name e.g default, main, yakata
$plan_code = // Paystack plan code  e.g PLN_gx2wn530m0i3w3m
$auth_token = // Paystack card auth token for customer
// Accepts an card authorization authtoken for the customer
$user->newSubscription($plan_name, $plan_code)->create($auth_token);
// The customer's most recent authorization would be used to charge subscription
$user->newSubscription($plan_name, $plan_code)->create(); 
// Initialize a new charge for a subscription
$user->newSubscription($plan_name, $plan_code)->charge(); 

传递给newSubscription方法的第一个参数是订阅的名称。如果您的应用程序仅提供单个订阅,您可能将其称为主或主要。第二个参数是用户订阅的具体Paystack代码。此值应与Paystack中的代码标识符相对应。

接受Paystack授权令牌的create方法将开始订阅,并更新您的数据库,包括客户/用户ID和其他相关计费信息。

charge方法初始化一个事务,该事务返回一个包含付款授权URL和访问码的响应。

如果需要指定额外的客户信息,可以在create方法的第二个参数中传递

$user->newSubscription('main', 'PLN_cgumntiwkkda3cw')->create($auth_token, [
    'data' => 'More Customer Data',
],[
    'data' => 'More Subscription Data',
]);

有关Paystack支持的额外字段的信息,请参阅Paystack关于客户创建的文档或相应的Paystack文档。

检查订阅状态

一旦用户订阅了您的应用程序,您可以使用各种方便的方法轻松检查其订阅状态。首先,subscribed方法返回true,如果用户有一个活跃的订阅,即使该订阅目前处于试用期内

// Paystack plan name e.g default, main, yakata
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()) {
    //
}

可以使用 subscribedToPaystack 方法根据给定的 Paystack 代码确定用户是否订阅了特定 Paystack。在本例中,我们将确定用户的主要订阅是否已主动订阅了每月的 Paystack

$plan_name = // Paystack plan name e.g default, main, yakata
$plan_code = // Paystack Paystack Code  e.g PLN_gx2wn530m0i3w3m
if ($user->subscribedToPlan($plan_code, $plan_code)) {
    //
}

已取消订阅状态

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

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

您还可以确定用户是否已取消订阅,但仍在他们的“宽限期”内,直到订阅完全过期。例如,如果用户在3月5日取消了原计划于3月10日到期的订阅,那么用户将在3月10日之前处于“宽限期”。请注意,在此期间 subscribed 方法仍然返回 true

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

取消订阅

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

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

当订阅被取消时,Cashier 将自动设置数据库中的 ends_at 列。此列用于确定 subscribed 方法何时开始返回 false。例如,如果客户在3月1日取消了原计划于3月5日到期的订阅,则 subscribed 方法将继续返回 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', 'PLN_gx2wn530m0i3w3m')
            ->trialDays(10)
            ->create($auth_token);

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

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

您可以使用用户实例的 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);
$plan_code = // Paystack Paystack Code  e.g PLN_gx2wn530m0i3w3m
// With Paystack card auth token for customer
$user->newSubscription('main', $plan_code)->create($auth_token);
$user->newSubscription('main', $plan_code)->create();

客户

创建客户

有时,您可能希望创建一个不开始订阅的Paystack客户。您可以使用createAsPaystackCustomer方法实现这一点。

$user->createAsPaystackCustomer();

一旦客户在Paystack中创建完成,您可以在以后开始订阅。

支付方式

检索认证支付方式

账单模型实例上的cards方法返回一个Airondev\Cashier\Card实例集合。

$cards = $user->cards();

删除支付方式

要删除一张卡,您应首先使用cards方法检索客户的认证。然后,您可以对要删除的实例调用delete方法。

foreach ($user->cards() as $card) {
    $card->delete();
}

要删除客户的全部卡支付认证

$user->deleteCards();

处理Paystack Webhooks

Paystack可以通过webhooks通知您的应用程序各种事件。要处理Paystack webhooks,定义一个指向Cashier webhook控制器的路由。该控制器将处理所有传入的webhook请求并将它们分发到适当的控制器方法。

Route::post(
    'paystack/webhook',
    '\Airondev\Cashier\Http\Controllers\WebhookController@handleWebhook'
);

注册您的路由后,请确保在Paystack仪表板设置中配置webhook URL。

默认情况下,此控制器将自动处理由于失败次数过多而取消订阅(如您的paystack设置定义的)、扣费成功、转账成功或失败、发票更新和订阅更改;然而,正如我们将很快发现的,您可以扩展此控制器以处理任何您喜欢的webhook事件。

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

Webhooks & CSRF保护

由于Paystack webhooks需要绕过Laravel的CSRF保护,请确保在VerifyCsrfToken中间件中将URI列为异常或在web中间件组之外列出路由。

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

定义Webhook事件处理器

如果您想处理额外的Paystack webhook事件,扩展Webhook控制器。您的方法名称应与Cashier的预期约定相符,具体来说,方法应以前缀handle和您要处理的Paystack webhook事件的“骆驼式”名称开头。

<?php

namespace App\Http\Controllers;

use Airondev\Cashier\Http\Controllers\WebhookController as CashierController;

class WebhookController extends CashierController
{
    /**
     * Handle invoice payment succeeded.
     *
     * @param  array  $payload
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function handleInvoiceUpdate($payload)
    {
        // Handle The Event
    }
}

接下来,在routes/web.php文件中定义一个指向Cashier控制器的路由。

Route::post(
    'paystack/webhook',
    '\App\Http\Controllers\WebhookController@handleWebhook'
);

单次扣费

简单扣费

使用Paystack时,charge方法接受您希望以应用使用的货币的最小单位计费的数量。

如果您想对订阅客户的信用卡进行一次性扣费,您可以在账单模型实例上使用charge方法。

// Paystack Accepts Charges In Kobo for Naira...
$PaystackCharge = $user->charge(10000);

charge方法接受一个数组作为其第二个参数,允许您将任何选项传递给底层的Paystack扣费创建。有关创建扣费时可用选项的详细信息,请参阅Paystack文档。

$user->charge(100, [
    'more_option' => $value,
]);

如果扣费失败,charge方法将抛出异常。如果扣费成功,该方法将返回完整的Paystack响应。

try {
    // Paystack Accepts Charges In Kobo for Naira...
    $response = $user->charge(10000);
} catch (Exception $e) {
    //
}

带有发票的扣费

有时您可能需要执行一次性扣费,但同时也需要为扣费生成发票,以便您可以为客户提供PDF收据。invoiceFor方法允许您这样做。例如,让我们为客户开具一张2000.00奈拉的“一次性费用”发票。

// Paystack Accepts Charges In Kobo for Naira...
$user->invoiceFor('One Time Fee', 200000);

发票将立即对用户的信用卡扣费。invoiceFor方法还接受一个数组作为第三个参数。此数组包含发票项目的计费选项。该方法接受的第四个参数也是一个数组。此最终参数接受发票本身的计费选项。

$user->invoiceFor('Stickers', 50000, [
    'line_items' => [ ],
    'tax' => [{"name":"VAT", "amount":2000}]
]);

有关Paystack支持的额外字段的信息,请参阅Paystack关于客户创建的文档或相应的Paystack文档。

退款费用 如果您需要退还 Paystack 的费用,可以使用退款方法。此方法只接受 Paystack 费用 ID 作为其唯一参数

$paystackCharge = $user->charge(100);

$user->refund($paystackCharge->reference);

发票 您可以使用 invoices 方法轻松检索一个可计费模型的发票数组

$invoices = $user->invoices();

// Include only pending invoices in the results...
$invoices = $user->invoicesOnlyPending();

// Include only paid invoices in the results...
$invoices = $user->invoicesOnlyPaid();

在列出客户的发票时,您可以使用发票的帮助方法来显示相关的发票信息。例如,您可能希望将每个发票列在表格中,使用户能够轻松下载其中的任何一个

<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',
    ]);
});