initafricahq/cashier-paystack

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

1.1.1 2023-11-13 18:01 UTC

This package is not auto-updated.

Last update: 2024-09-30 22:10:16 UTC


README

介绍

Cashier Paystack 提供了对 Paystack 订阅计费服务的表达性和流畅的接口。它处理了几乎所有您所畏惧编写的样板订阅计费代码。

Composer

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

composer require initafricahq/cashier-paystack

配置

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

php artisan vendor:publish --provider="InitAfricaHQ\Cashier\CashierServiceProvider"

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

<?php

return [
    /**
     * Public Key From Paystack Dashboard
     *
     */
    'public_key' => env('PAYSTACK_PUBLIC_KEY'),

    /**
     * Secret Key From Paystack Dashboard
     *
     */
    'secret_key' => env('PAYSTACK_SECRET_KEY'),

    /**
     * Paystack Payment URL
     *
     */
    'path' => env('PAYSTACK_PATH'),

    /**
     * Optional email address of the merchant
     *
     */
    'merchant_email' => env('MERCHANT_EMAIL'),

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

];

数据库迁移

在使用 Cashier 之前,我们还需要准备数据库。您可以简单地发布迁移文件并运行迁移命令

可计费模型

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

use InitAfricaHQ\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

货币配置

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

use InitAfricaHQ\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 方法的第一个参数应该是订阅的名称。如果您应用程序仅提供单个订阅,您可以将其称为 main 或 primary。第二个参数是用户订阅的具体 Paystack 代码。此值应与 Paystack 中的代码标识符相对应。

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

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

如果需要指定额外的客户详细信息,可以将它们作为 create 方法的第二个参数传递

$user->newSubscription('default', 'PLN_cgumntiwkkda3cw')->create($auth_token, [
    ',etadata' => json_encode(['pass_through' => 'customer data']),
]);

要了解 Paystack 支持的附加字段,请参阅 Paystack 关于创建客户的文档或相应的 Paystack 文档。

检查订阅状态

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

// Paystack plan name e.g default, main, yakata
if ($user->subscribed('default')) {
    //
}

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

public function handle($request, Closure $next)
{
    if ($request->user() && ! $request->user()->subscribed('default')) {
        // This user is not a paying customer...
        return redirect('billing');
    }

    return $next($request);
}

如果您想确定用户是否仍在试用期,可以使用 onTrial 方法。这个方法对于向用户显示他们仍在试用期警告非常有用

if ($user->subscription('default')->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('default')->cancelled()) {
    //
}

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

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

取消订阅

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

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

当订阅被取消时,Cashier会自动设置数据库中的ends_at列。此列用于知道subscribed方法何时应开始返回false。例如,如果客户在3月1日取消了一个原定于3月5日到期的订阅,则subscribed方法将继续返回true直到3月5日。

您可以使用onGracePeriod方法确定用户是否已取消订阅但仍在“宽限期”内。

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

如果您希望立即取消订阅,请调用用户订阅上的cancelNow方法。

$user->subscription('default')->cancelNow();

恢复订阅

如果用户已取消订阅并且您希望恢复它,请使用resume方法。用户必须仍然处于他们的宽限期才能恢复订阅。

$user->subscription('default')->resume();

如果用户取消订阅然后在订阅完全过期之前恢复该订阅,他们不会立即被收费。相反,他们的订阅将被重新激活,并且将在原始计费周期中收费。

订阅试用期

前端计费

如果您希望在收集客户付款方式信息的同时为客户提供试用期,您应该在创建订阅时使用trialDays方法。

$user = User::find(1);

$user->newSubscription('default', 'PLN_gx2wn530m0i3w3m')
    ->trialDays(10)
    ->create($auth_token);

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

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

您可以使用用户实例的onTrial方法或订阅实例的onTrial方法来确定用户是否处于试用期。以下两个示例是相同的。

if ($user->onTrial('default')) {
    //
}

if ($user->subscription('default')->onTrial()) {
    //
}

前端不计费

如果您希望在收集客户付款方式信息之前提供试用期,您可以将用户记录上的trial_ends_at列设置为所需的试用期结束日期。这通常在用户注册期间完成。

$user = Customer::create([
    // Populate other user properties...
    'billable_id' => $user->getKey(),
    'billable_type' => $user->getMorphClass(),
    '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('default', $plan_code)->create($auth_token);
$user->newSubscription('default', $plan_code)->create();

客户

创建客户

有时,您可能希望创建一个不开始订阅的Paystack客户。您可以使用createAsCustomer方法完成此操作。

$user->createAsCustomer();

一旦客户在Paystack中创建,您可以在稍后日期开始订阅。

支付方式

检索认证支付方式

账单模型实例上的卡片方法返回一个InitAfricaHQ\Cashier\Card实例的集合

$cards = $user->cards();

删除支付方式

要删除一张卡,您首先应该使用卡片方法检索客户的身份验证。然后,您可以调用要删除的实例上的删除方法

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

删除客户的全部卡片支付身份验证

$user->deleteCards();

处理Paystack Webhooks

Paystack可以通过webhooks通知您的应用程序各种事件。要处理Paystack webhooks,定义一个指向Cashier webhook控制器的路由。如果已配置,则默认为/paystack。此控制器将处理所有传入的webhook请求并将它们派发到适当的控制器方法:您可以设置PAYSTACK_PATH环境变量以使用自己的路径,或者创建一个新的完整路由来覆盖此设置。

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

注册您的路由后,请务必在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事件的"camel case"名称开头。

<?php

namespace App\Http\Controllers;

use InitAfricaHQ\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'
);

单次收费

简单收费

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