sebdesign/laravel-viva-payments

Laravel 集成 Viva Payments 通道的包

v6.1.3 2024-09-02 10:25 UTC

README

Latest Version on Packagist Software License Build Status Quality Score Scrutinizer Coverage

VivaPayments logo

此包提供了 Viva Wallet 支付 API 的接口。它处理 智能结账 集成、ISV 支付 APIWebhooks

请访问 Viva Wallet 开发者门户以获取有关 API 和更多信息的相关说明: https://developer.vivawallet.com

注意:此项目不是官方包,并且我与 Viva Payments 没有任何关联。

设置

安装

通过 Composer 安装此包。

此包需要 PHP 8.1 和 Laravel 9.0 或更高版本,并使用 Guzzle 7.0 进行 API 调用。根据您的依赖项使用适当的版本。

composer require sebdesign/laravel-viva-payments

服务提供者

该包将自动注册其服务提供者。

配置

将以下数组添加到您的 config/services.php

'viva' => [
    'api_key' => env('VIVA_API_KEY'),
    'merchant_id' => env('VIVA_MERCHANT_ID'),
    'environment' => env('VIVA_ENVIRONMENT', 'production'),
    'client_id' => env('VIVA_CLIENT_ID'),
    'client_secret' => env('VIVA_CLIENT_SECRET'),
    'isv_partner_id' => env('VIVA_ISV_PARTNER_ID'),
    'isv_partner_api_key' => env('VIVA_ISV_PARTNER_API_KEY'),
],

api_keymerchant_id 可在您的个人资料 设置 > API 访问 部分中找到。

client_idclient_secret 用于 智能结账。您可以在个人资料 设置 > API 访问 部分中生成 智能结账凭据

isv_partner_idisv_partner_api_key 是使用基本身份验证使用 ISV 支付 API 所必需的。

有关 API 身份验证的更多信息,请参阅开发者门户: https://developer.vivawallet.com/getting-started/find-your-account-credentials/client-smart-checkout-credentials/

环境 可以是 生产演示

智能结账

有关智能结账过程的更多信息,请参阅开发者门户: https://developer.vivawallet.com/smart-checkout/

\Sebdesign\VivaPayments\Facades\Viva 门面提供了与智能结账集成交互所需的所有方法。

以下指南将引导您完成必要的步骤

创建支付订单

所需的请求金额以分为单位。所有其他参数都是可选的。请参阅 请求体架构

use Sebdesign\VivaPayments\Facades\Viva;

$orderCode = Viva::orders()->create(
    order: new CreatePaymentOrder(amount: 1000),
);

重定向到智能结账

use Sebdesign\VivaPayments\Facades\Viva;

$redirectUrl = Viva::orders()->redirectUrl(
    ref: $orderCode,
    color: '0000ff',
    paymentMethod: 23,
);

return redirect()->away(path: $redirectUrl);

验证支付

use Sebdesign\VivaPayments\Facades\Viva;

$response = Viva::transactions()->retrieve(transactionId: request('t'));

完整示例

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Sebdesign\VivaPayments\Enums\TransactionStatus;
use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreatePaymentOrder;
use Sebdesign\VivaPayments\Requests\Customer;
use Sebdesign\VivaPayments\VivaException;

class CheckoutController extends Controller
{
    /**
     * Create a payment order and redirect to the checkout page.
     */
    public function checkout(): RedirectResponse
    {
        try {
            $orderCode = Viva::orders()->create(new CreatePaymentOrder(
                amount: 1000,
                customerTrns: 'Short description of purchased items/services to display to your customer',
                customer: new Customer(
                    email: 'johdoe@vivawallet.com',
                    fullName: 'John Doe',
                    countryCode: 'GB',
                    requestLang: 'en-GB',
                ),
            ));
        } catch (VivaException $e) {
            report($e);

            return back()->withErrors($e->getMessage());
        }

        $redirectUrl = Viva::orders()->redirectUrl(
            ref: $orderCode,
            color: '0000ff',
            paymentMethod: 23,
        );

        return redirect()->away($redirectUrl);
    }

    /**
     * Redirect from the checkout page and get the order details from the API.
     */
    public function confirm(Request $request): RedirectResponse
    {
        try {
            $transaction = Viva::transactions()->retrieve($request->input('t'));
        } catch (VivaException $e) {
            report($e);

            return back()->withErrors($e->getMessage());
        }

        $status = match ($transaction->statusId) {
            case TransactionStatus::PaymentPending: 'The order is pending.',
            case TransactionStatus::PaymentSuccessful: 'The order is paid.',
            case TransactionStatus::Error: 'The order was not paid.',
        }

        return view('order/success', compact('status'));
    }
}

处理 Webhooks

Viva Payments 支持 Webhooks,此包提供了一种控制器,用于验证和处理传入的通知事件。

有关 Webhooks 的更多信息,请参阅开发者门户: https://developer.vivawallet.com/webhooks-for-payments/

定义路由

在您的 routes/web.php 中,为您的个人资料中每个 Webhooks 定义以下路由,相应地替换 URI 和控制器。

Route::get('viva/webhooks', [WebhookController::class, 'verify']);
Route::post('viva/webhooks', [WebhookController::class, 'handle']);

排除 CSRF 保护

不要忘记将您的 Webhooks URI 添加到 VerifyCsrfToken 中间件的 $except 数组中。

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array<int, string>
     */
    protected $except = ['viva/webhooks'];
}

处理 Webhooks 事件

要处理来自 Viva Wallet 的任何请求,您可能需要监听 WebhookEvent。根据 EventTypeId,您可以处理任何事件。

use Sebdesign\VivaPayments\Enums\WebhookEventType;
use Sebdesign\VivaPayments\Events\WebhookEvent;

class EventServiceProvider
{
    protected $listen = [
        WebhookEvent::class => [WebhookEventListener::class],
    ];
}

class WebhookEventListener
{
    public function handle(WebhookEvent $event): void
    {
        match ($event->EventTypeId) {
            WebhookEventType::TransactionPaymentCreated => ...,
            WebhookEventType::TransactionFailed => ...,
            WebhookEventType::TransactionReversalCreated => ...,
            default => ...,
        };
    }
}

EventData 属性包含一个包含实际通知的对象。对象的类取决于通知类型

此外,还派发了 TransactionPaymentCreatedTransactionFailed 事件。您可以选择监听这些特定事件而不是监听 WebhookEvent

use Sebdesign\VivaPayments\Enums\WebhookEventType;
use Sebdesign\VivaPayments\Events\TransactionFailed;
use Sebdesign\VivaPayments\Events\TransactionPaymentCreated;

class EventServiceProvider
{
    protected $listen = [
        TransactionPaymentCreated::class => [
            ConfirmOrder::class,
        ],
        TransactionFailed::class => [
            CancelOrder::class,
        ],
    ];
}

class ConfirmOrder
{
    public function handle(TransactionPaymentCreated $event): void
    {
        //
    }
}

class CancelOrder
{
    public function handle(TransactionFailed $event): void
    {
        //
    }
}

支付 API 参考

所有方法都接受一个$guzzleOptions数组作为最后一个参数。此参数完全可选,允许您向Guzzle客户端指定额外的请求选项。

订单

创建付款订单

参见:https://developer.vivawallet.com/api-reference-guide/payment-api/#tag/Payments/paths/~1api~1orders~1{orderCode}/get

use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreatePaymentOrder;
use Sebdesign\VivaPayments\Requests\Customer;

$orderCode = Viva::orders()->create(
    order: new CreatePaymentOrder(
        amount: 1000,
        customerTrns: 'Short description of purchased items/services to display to your customer',
        customer: new Customer(
            email: 'johdoe@vivawallet.com',
            fullName: 'John Doe',
            phone: '+30999999999',
            countryCode: 'GB',
            requestLang: 'en-GB',
        ),
        paymentTimeOut: 300,
        preauth: false,
        allowRecurring: false,
        maxInstallments: 12,
        paymentNotification: true,
        tipAmount: 100,
        disableExactAmount: false,
        disableCash: true,
        disableWallet: true,
        sourceCode: '1234',
        merchantTrns: 'Short description of items/services purchased by customer',
        tags: [
            'tags for grouping and filtering the transactions',
            'this tag can be searched on VivaWallet sales dashboard',
            'Sample tag 1',
            'Sample tag 2',
            'Another string',
        ],
        cardTokens: ['ct_5d0a4e3a7e04469f82da228ca98fd661'],
    ),
    guzzleOptions: [],
);
获取重定向URL

参见:https://developer.vivawallet.com/smart-checkout/smart-checkout-integration/#step-2-redirect-the-customer-to-smart-checkout-to-pay-the-payment-order

use Sebdesign\VivaPayments\Facades\Viva;

$url = Viva::orders()->redirectUrl(
    ref: $orderCode,
    color: '0000ff',
    paymentMethod: 23,
);

交易

检索交易

参见:https://developer.vivawallet.com/apis-for-payments/payment-api/#tag/Transactions/paths/~1checkout~1v2~1transactions~1{transactionId}/get

use Sebdesign\VivaPayments\Facades\Viva;

$transaction = Viva::transactions()->retrieve(
    transactionId: 'c90d4902-6245-449f-b2b0-51d99cd09cfe',
    guzzleOptions: [],
);

创建周期性交易

参见:https://developer.vivawallet.com/api-reference-guide/payment-api/#tag/Transactions/paths/~1api~1transactions~1{Id}/post

use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreateRecurringTransaction;

$response = Viva::transactions()->createRecurring(
    transactionId: '252b950e-27f2-4300-ada1-4dedd7c17904',
    transaction: new CreateRecurringTransaction(
        amount: 100,
        installments: 1,
        customerTrns: 'A description of products / services that is displayed to the customer',
        merchantTrns: 'Your merchant reference',
        sourceCode: '6054',
        tipAmount: 0,
    ),
    guzzleOptions: [],
);

OAuth

请求访问令牌

参见:https://developer.vivawallet.com/authentication/#step-2-request-access-token

您不需要调用此方法,因为客户端在需要时会自动请求访问令牌。但是,如果您想的话,可以在运行时指定客户端凭据。

use Sebdesign\VivaPayments\Facades\Viva;

Viva::withOAuthCredentials(
    clientId: 'client_id',
    clientSecret: 'client_secret',
);

如果您需要手动请求访问令牌,可以使用requestToken方法。此方法将令牌作为AccessToken对象返回。

use Sebdesign\VivaPayments\Facades\Viva;

// Using `client_id` and `client_secret` from `config/services.php`:
$token = Viva::oauth()->requestToken();

// Using custom client credentials
$token = Viva::oauth()->requestToken(
    clientId: 'client_id',
    clientSecret: 'client_secret',
    guzzleOptions: [],
);

使用现有的访问令牌

如果您将令牌存储在某个地方,例如数据库或缓存中,您可以设置客户端的访问令牌字符串,将其用作Bearer令牌。

use Sebdesign\VivaPayments\Facades\Viva;

Viva::withToken(token: 'eyJhbGciOiJSUzI1...');

创建卡令牌

参见:https://developer.vivawallet.com/apis-for-payments/payment-api/#tag/Transactions/paths/~1acquiring~1v1~1cards~1tokens/post

use Sebdesign\VivaPayments\Facades\Viva;

$cardToken = Viva::cards()->createToken(
    transactionId: '6cffe5bf-909c-4d69-b6dc-2bef1a6202f7',
    guzzleOptions: [],
);

Webhooks

获取授权代码

参见:https://developer.vivawallet.com/webhooks-for-payments/#generate-a-webhook-verification-key

use Sebdesign\VivaPayments\Facades\Viva;

$key = Viva::webhooks()->getVerificationKey(
    guzzleOptions: [],
);

ISV付款API参考

ISV付款API方法可通过Viva::isv()服务获取。

订单

创建付款订单

参见:https://developer.vivawallet.com/isv-partner-program/payment-isv-api/#tag/Payments/paths/~1checkout~1v2~1isv~1orders/post

use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreatePaymentOrder;
use Sebdesign\VivaPayments\Requests\Customer;

$orderCode = Viva::isv()->orders()->create(
    order: new CreatePaymentOrder(
        amount: 1000,
        customerTrns: 'Short description of purchased items/services to display to your customer',
        customer: new Customer(
            email: 'johdoe@vivawallet.com',
            fullName: 'John Doe',
            phone: '+30999999999',
            countryCode: 'GB',
            requestLang: 'en-GB',
        ),
        paymentTimeOut: 300,
        preauth: false,
        allowRecurring: false,
        maxInstallments: 12,
        paymentNotification: true,
        tipAmount: 100,
        disableExactAmount: false,
        disableCash: true,
        disableWallet: true,
        sourceCode: '1234',
        merchantTrns: 'Short description of items/services purchased by customer',
        tags: [
            'tags for grouping and filtering the transactions',
            'this tag can be searched on VivaWallet sales dashboard',
            'Sample tag 1',
            'Sample tag 2',
            'Another string',
        ],
        isvAmount: 10,
        resellerSourceCode: '2345',
    ),
    guzzleOptions: [],
);

交易

检索交易

参见:https://developer.vivawallet.com/isv-partner-program/payment-isv-api/#tag/Retrieve-Transactions/paths/~1checkout~1v2~1isv~1transactions~1{transactionId}?merchantId={merchantId}/get

use Sebdesign\VivaPayments\Facades\Viva;

$transaction = Viva::isv()->transactions()->retrieve(
    transactionId: 'c90d4902-6245-449f-b2b0-51d99cd09cfe',
    guzzleOptions: [],
);

创建周期性交易

参见:https://developer.vivawallet.com/isv-partner-program/payment-isv-api/#tag/Recurring-Payments/paths/~1api~1transactions~1{id}/post

use Sebdesign\VivaPayments\Facades\Viva;
use Sebdesign\VivaPayments\Requests\CreateRecurringTransaction;

Viva::withBasicAuthCredentials(
    config('services.viva.isv_partner_id').':'.config('services.viva.merchant_id'),
    config('services.viva.isv_partner_api_key'),
);

$transaction = Viva::isv()->transactions()->createRecurring(
    transactionId: 'c90d4902-6245-449f-b2b0-51d99cd09cfe',
    transaction: new CreateRecurringTransaction(
        amount: 100,
        isvAmount: 1,
        customerTrns: 'A description of products / services that is displayed to the customer',
        merchantTrns: 'Your merchant reference',
        sourceCode: '4929333',
        resellerSourceCode: '1565',
    ),
    guzzleOptions: [],
);

异常

当VivaPayments API返回错误时,会抛出Sebdesign\VivaPayments\VivaException

对于任何其他HTTP错误,会抛出GuzzleHttp\Exception\ClientException

测试

通过运行phpunit --group unit触发单元测试。

要运行功能测试,您必须在根目录中包含一个.env文件,其中包含凭据(VIVA_API_KEYVIVA_MERCHANT_IDVIVA_CLIENT_IDVIVA_CLIENT_SECRET),以便连接到VivaPayments演示API。然后运行phpunit --group functional来触发测试。