devtobi/cashier-paystack

A Laravel Cashier Wrapper that provides an expressive, fluent interface to Paystack's subscription invoicing services.

v1.0.0 2020-03-23 23:05 UTC

This package is not auto-updated.

Last update: 2024-09-27 07:55:25 UTC


README

Build Status Latest Stable Version Total Downloads License

简介

Cashier Paystack provides an expressive, fluent interface to Paystack's subscription billing services. It handles almost all of the boilerplate subscription billing code you are dreading writing.

Composer

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

composer require devtobi/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 特性添加到你的模型定义中。这个特性提供了各种方法,允许你执行常见的计费任务,如创建订阅、应用优惠券和更新信用卡信息

use Wisdomanthoni\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

货币配置

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

use Wisdomanthoni\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方法返回一组Wisdomanthoni\Cashier\Card实例。

$cards = $user->cards();

删除支付方式

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

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

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

$user->deleteCards();

处理Paystack Webhooks

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

Route::post(
    'paystack/webhook',
    '\Wisdomanthoni\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 Wisdomanthoni\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收费,可以使用refund方法。此方法仅接受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',
    ]);
});