potelo/gu-payment

GuPayment 为 iugu.com 提供了一个控制订阅的接口


README

Build status Coverage Status Packagist Downloads Packagist Version

简介

GuPayment 基于Laravel Cashier,为iugu.com提供控制订阅的接口。

兼容Laravel 5.5+,6.x和7.x。

安装

使用composer安装此包

composer require potelo/gu-payment

如果您不使用自动发现,请将GuPaymentServiceProvider添加到config/app.php

Potelo\GuPayment\GuPaymentServiceProvider::class,

现在,配置GuPayment使用的变量在您的.env文件中

IUGU_APIKEY=SUA_CHAVE
IUGU_ID=SEU_ID_IUGU
GUPAYMENT_SIGNATURE_TABLE=subscriptions
IUGU_MODEL=User
IUGU_MODEL_FOREIGN_KEY=user_id
IUGU_USER_MODEL_COLUMN=iugu_id
IUGU_SUBSCRIPTION_MODEL_ID_COLUMN=iugu_id
IUGU_SUBSCRIPTION_MODEL_PLAN_COLUMN=iugu_plan

在开始使用GuPayment之前,您需要准备数据库。首先,您需要发布迁移。

php artisan vendor:publish --tag=migrations

如果需要修改或添加订阅表中的列,只需编辑已发布的迁移。然后,只需运行php artisan migrate命令。

现在,让我们将Trait添加到您的用户模型中。

use Potelo\GuPayment\GuPaymentTrait;

class User extends Authenticatable
{
    use GuPaymentTrait;
}

现在,让我们在config/services.php中添加两个配置。用户类,Iugu提供的API密钥以及用于管理订阅的表名,与创建迁移时选择的相同。

'iugu' => [
    'model'  => App\User::class,
    'key' => env('IUGU_APIKEY'),
    'signature_table' => env('GUPAYMENT_SIGNATURE_TABLE'),
    'model_foreign_key' => env('IUGU_MODEL_FOREIGN_KEY'),
]

订阅

创建订阅

要创建订阅,首先您需要有一个扩展GuPaymentTrait的用户实例。然后,您应该使用newSubscription方法创建订阅

$user = User::find(1);

$user->newSubscription('main', 'gold')->create($creditCardToken);

第一个参数应该是订阅名称。此名称在Iugu.com上不会被使用,仅在您的应用程序中使用。如果您的应用程序只有一种订阅类型,您可以将其称为主要或主要。第二个参数是Iugu.com上的计划标识符。

create方法会自动在Iugu.com上创建订阅,并使用Iugu的客户ID和其他相关信息更新您的数据库。您可以通过不传递任何参数或提供信用卡令牌来调用create,以便用户有默认的支付方式。有关如何生成令牌的详细信息,请参阅iugu.js

如果要仅在支付验证后创建订阅,请在newSubscription之后调用chargeOnSuccess方法。重要:此创建订阅的方式仅适用于具有默认支付方式的客户,不适用于支票。

$user = User::find(1);

$user->newSubscription('main', 'gold')
->chargeOnSuccess()
->create($creditCardToken);

包含子项的订阅

要向客户订阅添加更多收费项目,请使用subItems方法。

$subItems = [
    [
        'description' => 'Desconto recorrente',
        'price_cents' => -900,
        'quantity' => 1,
        'recurrent' => true,
    ],
    [
        'description' => 'Adicional não recorrente',
        'price_cents' => 250,
        'quantity' => 1,
        'recurrent' => false,
    ]
];

// Create Subscription
$user->newSubscription('main', 'gold')
    ->subItems($subItems)
    ->create($creditCardToken);

也可以通过使用addSubItem方法一次添加一个收费项目。

$subItem = [
   'description' => 'Desconto recorrente',
   'price_cents' => -900,
   'quantity' => 1,
   'recurrent' => true,
];

// Create Subscription
$user->newSubscription('main', 'gold')
    ->addSubItem($subItem)
    ->create($creditCardToken);

附加数据

如果您要向订阅添加额外信息,只需在newSubscription方法中将数组作为第三个参数传递,它将通过custom_variables参数传递给Iugu API。

$user = User::find(1);

$user->newSubscription('main', 'gold', [
    'adicional_assinatura' => 'boa assinatura'
])->create(NULL);

其他参数

要自定义发送到API的参数,请将数组作为newSubscription方法的第四个参数传递以创建订阅,或作为create方法的第二个参数创建客户。

$user = User::find(1);

'$user->newSubscription('main', 'gold', [], ['ignore_due_email' => true])
    ->create(NULL, [
        'name' => $user->nome,
        'notes' => 'Anotações gerais'
    ]);

有关Iugu API支持参数的更多信息,请参阅官方文档

错误处理

如果Iugu发生错误,可以通过SubscriptionBuilder的getLastError方法识别这些错误

$user = User::find(1);

$subscriptionBuilder = $user->newSubscription('main', 'gold');

$subscription = $subscriptionBuilder->trialDays(20)->create($creditCardToken);

if ($subscription) {
    // TUDO ok
} else {
    $erros = $subscriptionBuilder->getLastError();
    
    if (is_array($erros)) {
        // array
    } else {
        // string
    }
}

Iugu返回的错误可能是一个数组或一个字符串。

检查订阅状态

一旦用户在您的应用程序中订阅了计划,您可以通过一些方法来检查这个订阅的状态。方法 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()) {
    //
}

onPlan 方法可以用来确定用户是否正在订阅特定的计划。例如,用于检查用户是否订阅了 黄金 计划

if ($user->onPlan('gold')) {
    //
}

要检查订阅是否已取消,只需在订阅中使用方法 cancelled

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

您还可以检查订阅是否已取消但用户仍在“宽限期”。例如,如果一个用户在3月5日取消了订阅,但到期日是3月10日,那么他们将在这段时间内直到3月10日处于宽限期。要检查这一点,请使用方法 onGracePeriod

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

要从订阅中获取 Iugu 对象,请使用方法 asIuguSubscription

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

更改订阅计划

如果用户已经有一个订阅,他们可能想要切换到另一个计划。例如,一个 黄金 计划的用户可能想要节省成本并切换到 银色 计划。要更改订阅中的用户计划,请使用以下方式中的 swap 方法

$user = App\User::find(1);

$user->subscription('main')->swap('silver');

使用 swap 方法时,将为客户生成一张收取计划更改费用的账单。要模拟计划更改的成本,请使用 swapPlanSimulation 方法

$simulation = $user->subscription('main')->swapPlanSimulation('silver');

$cost = $simulation->cost;
$discount = $simulation->discount;
$cycles = $simulation->cycles;
$oldPlan = $simulation->old_plan;
$newPlan = $simulation->new_plan;
$expiresAt = $simulation->expires_at;

要无比例收费地更改计划,只需将第二个参数传递为 true

$user = App\User::find(1);

$skipCharge = true;
$user->subscription('main')->swap('silver', $skipCharge);

如果您想更改到期日(即下次账单生成/收取的日期),请传递一个包含日期的 Carbon 对象作为第三个参数

$user = App\User::find(1);

$skipCharge = true;
$nextDue = Carbon::now()->addDays(10);
$user->subscription('main')->swap('silver', $skipCharge, $nextDue);

取消订阅

要取消订阅,只需在用户订阅中调用方法 cancel

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

取消订阅后,它将继续活跃直到到期日。要立即取消订阅,请使用方法 cancelNow

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

重新激活订阅

如果一个用户有一个已取消的订阅并希望重新激活它,只需使用方法 resume。他们需要在“宽限期”内才能重新激活它

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

试用订阅

如果您想为用户提供试用期,您可以在创建订阅时使用方法 trialDays

$user = User::find(1);

$user->newSubscription('main', 'gold')
            ->trialDays(10)
            ->create($creditCardToken);

用户只有在试用期结束后才会被收取费用。请记住,要检查用户是否处于试用期,只需调用方法 onTrial

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

chargeOnSuccess 方法不适用于带有试用期的订阅创建。如果您想验证用户的信用卡,可以在创建订阅时使用 validateCard 方法。在 iugu 上,这将进行一次 R$1.00 的收费,然后对该收费进行退款。如果支付成功,则创建订阅

$user = $this->createUser();

// Create Subscription
$user->newSubscription('main', 'gold')->validateCard()->create($this->getTestToken());

处理触发器(或 Webhooks)

触发器(或 Webhooks) 是 Iugu 发送通知(通过 POST 方法)到的一组地址(URLs),用于某些在您的账户中发生的事件。例如,如果一个用户的订阅被取消,并且您需要将其记录到您的数据库中,您可以使用触发器。要使用它,您需要指定一个路由到 handleWebhook 方法,这是您在 Iugu 控制面板上配置的相同路由

Route::post('webhook', '\Potelo\GuPayment\Http\Controllers\WebhookController@handleWebhook');

GuPayment提供了更新数据库的方法,以便在订阅被暂停或过期时。指向该方法的路由,这将自动发生。请注意,您需要为该路由禁用CRSF保护。您可以在VerifyCsrfToken中间件的except中放置URL。

protected $except = [
   'webhook',
];

其他触发器

Iugu拥有多个其他触发器,您可以通过扩展WebhookController来创建其他事件。您的方法必须对应于handle + “camelCase”中的事件名称。例如,当创建新发票时,Iugu会发送一个具有以下事件的触发器:invoice.created,然后您只需创建一个名为handleInvoiceCreated的方法。

Route::post('webhook', 'MeuWebhookController@handleWebhook');
<?php

namespace App\Http\Controllers;

use Potelo\GuPayment\Http\Controllers\WebhookController;

class MeuWebhookController extends WebhookController {

    public function handleInvoiceCreated(array $payload)
    {
        return 'Fatura criada: ' . $payload['data']['id'];
    }
}

如果您想在本地上测试webhook,可以使用ngrok

发票

您可以通过invoices方法轻松获取用户的发票。

$invoices = $user->invoices();

此方法将仅返回已付款的发票,如果您想包括待付款的发票,只需将第一个参数作为true传递。

$invoices = $user->invoices(true);

您可以列出用户的发票并提供每个发票的PDF。例如

<table>
  @foreach ($user->invoices() as $invoice)
    <tr>
      <td>{{ $invoice->date() }}</td>
      <td>{{ $invoice->total() }}</td>
      <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
    </tr>
  @endforeach
</table>

要生成PDF,请使用downloadInvoice方法。

return $user->downloadInvoice($invoiceId, [
        'vendor'  => 'Sua Empresa',
        'product' => 'Seu Produto',
    ]);

单独发票

如果您需要创建与订阅无关的单独发票,请使用createInvoice方法。

$invoice = $user->createInvoice($priceCents, $dueDate, $description, $options);

如果您需要为多个项目生成发票,请使用newInvoiceaddItemcreate方法。

$invoiceBuilder = $user->newInvoice($dueDate);
foreach ($itens as $item) {
    $invoiceBuilder->addItem($item['priceCents'], $item['description'], $item['quantity']);
}
$invoice = $invoiceBuilder->create($options);

退款发票

要退款发票,请使用refund方法。

// Iugu aceita cobranças em centavos
$user->refund($invoiceId);

生成发票第二联(仅限BOLETO)

要生成发票的第二个BOLETO联,请使用duplicate方法。

// Envie o id da fatura e as configurações
$config = [
    'due_date' => "2021-12-16",
    'keep_early_payment_discount' => true,
    'current_fines_option' => true,
    'ignore_canceled_email' => true,
    'ignore_due_email' => true
];
$user->duplicate($invoiceId, $config);

客户和支付方式(卡)

要管理支付方式,客户必须在Iugu中存在。当您使用newSubscription方法时,客户将自动创建。但是,要手动创建客户,您可以使用createAsIuguCustomer方法。

// Criar cliente no Iugu
$user->createAsIuguCustomer();

// Criar cliente no Iugu com token do cartão de crédito
$user->createAsIuguCustomer($creditCardToken);

要从用户访问Iugu的客户,请使用asIuguCustomer方法。

$iuguCustomer = $user->asIuguCustomer();

在Iugu中为客户创建客户后,您可以管理他们的支付方式。要创建一张卡,请使用createCard方法。

$user->createCard($creditCardToken);

此方法接受一个数组作为第二个参数,包含创建支付方式时可用选项。卡被创建时被定义为客户的default卡。如果您想更改此行为,请在第二个参数的方法选项中传递具有值false的键set_as_default

$user->createCard($creditCardToken, [
    'set_as_default' => false,
]);

要获取客户的卡,您可以使用cards方法(返回一个Illuminate\Support\Collection的卡),findCard(返回一个Potelo\GuPayment\Card的实例或如果找不到卡则返回null)或findCardOrFail(返回一个Potelo\GuPayment\Card的实例或如果找不到卡则抛出异常)。

// Coleção de cartões
$user->cards();

// Um cartão ou null
$card = $user->findCard($cardId);

try {
    $card = $user->findCardOrFail($cardId);
} catch(Exception $e) {
    //
}

要删除一张卡,只需获取一个Potelo\GuPayment\Card的实例并使用deleteCard方法。

$card = $user->findCard($cardId);

$user->deleteCard($card);

要删除所有卡,请使用deleteCards

$user->deleteCards();

简单收款

如果您想使用信用卡进行简单收款,您可以在使用具有GuPaymentTrait的Trait的用户实例上使用charge方法。要在此包中使用简单收款,需要客户已在Iugu中注册。

// Iugu aceita cobranças em centavos
$user->charge(100);

charge方法接受一个数组作为第二个参数,允许您传递一些用于在Iugu创建收款时所需选项。有关创建收款时可用选项的文档,请参阅Iugu文档

$user->charge(100, [
    'customer_payment_method_id' => $card->id,
]);

默认情况下,将创建具有以下定义的项目

description = 'Nova cobrança'
quantity = 1
price_cents = Valor do primeiro parâmetro

您可以根据自己的喜好在第二个参数中添加自己的项目

$user->charge(null, [
    'items' => [
        ['description' => 'Primeiro Item', 'quantity' => 10, 'price_cents' => 200],
        ['description' => 'Segundo Item', 'quantity' => 2, 'price_cents' => 200],
    ]
]);

注意:如果第二个参数传入了items数组,则默认项将不会添加。