glennraya/xendivel

一个Laravel包,用于轻松集成Xendit支付网关。它支持信用卡和借记卡支付、电子钱包支付和自定义发票,队列通知、webhook监听器等。


README

Project Logo

Xendivel — Xendit支付网关的Laravel包

一个设计用于将Xendit支付网关无缝集成到您的Laravel应用程序或网站中的Laravel包。它通过信用卡、借记卡和电子钱包进行支付。此外,该包还提供对自定义发票、队列发票或退款电子邮件通知、webhook事件监听器和验证的支持。

视频演示

我创建了一个简短的视频演示来展示该包的功能。您可以在这里找到它。

路线图

以下功能目前尚未由Xendivel支持,但计划在未来更新中包含。

  • 直接银行借记
  • 促销(优惠券/折扣代码)
  • 订阅服务
  • 实时推送支付状态通知(Laravel Reverb)
  • 付款分配API(用于大量支付处理,如员工工资)
  • PayLater
  • 二维码支付

目录

  1. 功能
  2. 先决条件
  3. 安装
  4. 初始设置
  5. 结账模板
  6. 用法
  7. 部署到生产环境
  8. 测试

功能

  • 信用卡/借记卡 - 通过主要信用卡或借记卡轻松处理支付。
  • 电子钱包支付 - 根据您的地区接受多种电子钱包支付(GCash、ShopeePay、PayMaya、GrabPay等)。
  • 自定义发票 - 提供内置的、高度可定制的、看起来专业的发票模板。
  • 队列电子邮件通知 - 允许使用markdown电子邮件模板,并可以选择在后台处理中安排电子邮件通知。
  • Webhooks - 内置Xendit的webhook事件监听器,并确保安全的webhook验证。

先决条件

  • PHP 8.0或更高版本
  • Laravel 9或更高版本
  • Node 18
  • NPM或Yarn

安装

Composer

Xendivel利用Composer的包自动发现。您只需通过Composer安装Xendivel,它将自动注册自己。

composer require glennraya/xendivel

安装Puppeteer

Xendivel依赖于Puppeteer从HTML或Blade模板生成PDF发票。

npm install puppeteer

或者,您也可以全局安装它

npm install puppeteer --location=global

初始设置

Xendit API密钥

在开始使用Xendivel之前,您必须拥有一个配置了API密钥的Xendit账户。激活您的Xendit账户用于生产不是测试Xendivel功能所必需的。在注册Xendit账户时,将自动启用测试模式。您可以从以下URL获取API密钥

从您的仪表板API密钥部分生成具有读取和写入权限的Money-In 密钥

在获取所有这些密钥后,请确保将它们包含在Laravel的.env文件中

XENDIT_SECRET_KEY=your-secret-key
XENDIT_PUBLIC_KEY=your-public-key
XENDIT_WEBHOOK_VERIFICATION_TOKEN=your-webhook-verification-token

配置邮件(可选)

Xendivel可以将发票作为电子邮件附件发送给您的客户。要使用此功能,请确保您的Laravel Mail已正确设置。在Xendivel发送发票或退款电子邮件通知之前,请确保您的邮件凭据已填写在.env文件中。

MAIL_MAILER=smtp
MAIL_HOST=your-mailer-host
MAIL_PORT=your-mailer-port
MAIL_USERNAME=your-mailer-username
MAIL_PASSWORD=your-mailer-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="fromaddress@example.com"
MAIL_FROM_NAME="${APP_NAME}"

队列(可选)

Xendivel简化了电子邮件处理的后台执行队列。如果您打算使用队列电子邮件执行任务,如开票或退款通知,请确保您已正确配置Laravel队列

然后,请确保您有一个正在运行的队列工作进程

php artisan queue:work

最后,请确保从您的.env文件中将queue_email设置为true

'queue_email' => true,

一旦您成功配置了Laravel的队列并启用了queue_emailtrue,Xendivel现在就可以将发票或退款电子邮件发送到队列以进行后台执行,使您的应用能够响应对其他请求或执行其他任务,而无需等待作业完成。 这将提高整体用户体验!

发布配置和资源

所有资产和配置文件都必须发布到正确的目录,以便Xendivel能够正常工作

php artisan vendor:publish --tag=xendivel

执行此命令将发布Xendivel的资产到以下目录

  • 配置文件 - config目录。
  • 发票模板 - resources/views/vendor/xendivel目录。
  • 电子邮件模板 - resources/views/vendor/xendivel/emails目录。
  • Blade结账模板 - resources/views/vendor/xendivel目录。
  • Webhook事件和监听器 - 分别位于app/Eventsapp/Listeners目录。

发布单个资源

配置文件
php artisan vendor:publish --tag=xendivel-config
发票模板
php artisan vendor:publish --tag=xendivel-invoice
结账(Blade)
php artisan vendor:publish --tag=xendivel-checkout-blade
结账(ReactJS)
php artisan vendor:publish --tag=xendivel-checkout-react
结账(ReactJS + TypeScript)
php artisan vendor:publish --tag=xendivel-checkout-react-typescript
Webhook事件监听器
php artisan vendor:publish --tag=xendivel-webhook-listener

结账模板

Checkout Template

Xendivel附带了一个完整的、完全工作的结账模板,用于卡支付和电子钱包支付。该模板包括各种变体,例如ReactJS组件ReactJS+TypeScript组件,以及常规的Blade模板和VanillaJS

您可以选择目前可用的模板变体,您甚至可以创建自己的。

Blade模板

我们提供了一个标准的Blade模板作为结账示例,使用VanillaJS。有一个内置的路由,允许您在/xendivel/checkout/blade测试此模板。您可以通过类似https://your-domain.test/xendivel/checkout/blade的URL访问它。

注意

当您运行命令php artisan vendor:publish --tag=xendivel时,结账Blade模板将位于您的/resources/views/vendor/xendivel/checkout.blade.php目录中。

ReactJS + TypeScript组件

Xendivel还为使用React等前端框架而不是常规Blade模板的用户提供了结账模板组件。

php artisan vendor:publish --tag=xendivel-checkout-react-typescript

php artisan vendor:publish --tag=xendivel-checkout-react

这些组件将发布在/resources/js/vendor/xendivel/Checkout.tsx(对于React+TypeScript)或/resources/js/vendor/xendivel/Checkout.jsx(对于纯ReactJS)。

重要

发布这些模板中的任何一个后,请确保您已填写这些React模板中的公钥部分。由于这是一个公钥,直接在模板上发布它是完全安全的。

// Set your 'public' key here.
Xendit.setPublishableKey(
    'your-public-key',
)

这些模板展示了卡片令牌化、信用卡/借记卡和电子钱包支付。它们旨在指导您的支付收集过程,以便在前端堆栈中实施。或者,如果您愿意,也可以使用它们作为完全功能性的独立模板。

用法

卡支付

卡详情令牌化

Xendit使用令牌化来安全地处理信用卡或借记卡信息。这个过程涉及使用Xendit的JavaScript库将敏感的卡信息(如卡号、到期日期和CVV)转换为安全令牌,然后再发送到您的后端。这种方法确保实际的卡信息不会被传输,从而提高了客户卡信息的安全性和保密性。

更多详情请参阅以下Xendit文档

https://docs.xendit.co/credit-cards/integrations/tokenization

Xendivel提供了易于使用的模板,包括ReactJS、React+TypeScript和Blade,提供用于卡/电子钱包交易的现成结算组件。这些模板为支付处理提供了一个坚实的基础。更多信息请参阅结算模板部分。

收取信用卡或借记卡费用

Xendivel::payWithCard函数接受带有token_idamountauthentication_id的传入请求负载

使用Axios的示例前端POST请求

axios.post('/pay-with-card', {
    amount: 1200,
    token_id: 'card-token', // From card tokenization process.
    authentication_id: 'auth-id', // From authentication process.
    // Additional optional parameters:

    // external_id: 'your-custom-external-id',

    // descriptor: "Merchant Business Name",

    // currency: 'PHP',

    // metadata: {
    //     store_owner: 'Glenn Raya',
    //     nationality: 'Filipino',
    //     product: 'MacBook Pro 16" M3 Pro',
    //     other_details: {
    //         purpose: 'Work laptop',
    //         issuer: 'Xendivel LTD',
    //         manufacturer: 'Apple',
    //         color: 'Silver'
    //     }
    // }

    // billing_details: {
    //     given_names: 'Glenn',
    //     surname: 'Raya',
    //     email: 'glenn@example.com',
    //     mobile_number: '+639171234567',
    //     phone_number: '+63476221234',
    //     address:{
    //         street_line1: 'Ivory St. Greenfield Subd.',
    //         street_line2: 'Brgy. Coastal Ridge',
    //         city: 'Balanga City',
    //         province_state: 'Bataan',
    //         postal_code: '2100',
    //         country: 'PH'
    //     }
    // },
})
// ...

然后在您的Laravel路由/控制器中

POST请求

use GlennRaya\Xendivel\Xendivel;

Route::post('/pay-with-card', function (Request $request) {
    $payment = Xendivel::payWithCard($request)
        ->getResponse();

    return $payment;
});

getResponse()函数确保您得到一个JSON响应

{
  "status": "CAPTURED",
  "authorized_amount": 5198,
  "capture_amount": 5198,
  "currency": "PHP",
  "metadata": {},
  "credit_card_token_id": "656ed874edab5300169c3092",
  "business_id": "6551f678273a62fd8d86e25a",
  "merchant_id": "104019905",
  "merchant_reference_code": "656ed874edab5300169c3091",
  "external_id": "43565633-dd58-47ae-bbe6-648f78d6652c",
  "eci": "02",
  "charge_type": "SINGLE_USE_TOKEN",
  "masked_card_number": "520000XXXXXX1005",
  "card_brand": "MASTERCARD",
  "card_type": "CREDIT",
  "ucaf": "AJkBBkhgQQAAAE4gSEJydQAAAAA=",
  "descriptor": "XDT*JSON FAKERY",
  "authorization_id": "656ed87c23f3c20015e2fb95",
  "bank_reconciliation_id": "7017631974056110603955",
  "issuing_bank_name": "PT BANK NEGARA INDONESIA TBK",
  "cvn_code": "M",
  "approval_code": "831000",
  "created": "2023-12-05T07:59:58.453Z",
  "id": "656ed87e23f3c20015e2fb96",
  "card_fingerprint": "61d6ed632aa321002350e0b2"
}

Xendit接受如Axios请求中演示的可选参数,例如billing_detailsmetadataexternal_idcurrencydescriptor。请参阅Xendit的文档了解这些参数的更多信息

https://developers.xendit.co/api-reference/#create-charge

您也可以将发票作为PDF格式的电子邮件附件转发到客户的电子邮件地址。有关此过程的详细信息,请参阅PDF发票部分。

外部ID

Xendit要求在每次信用卡/借记卡收费中包含一个external_id参数。默认情况下,Xendivel通过自动生成一个唯一的Ordered UUID v4 ID来简化此过程。

https://laravel.net.cn/docs/10.x/strings#method-str-ordered-uuid

然而,如果您出于某种原因选择创建自己的external_id,您可以通过将配置文件xendivel.php中的auto_id选项设置为false来实现这一点。

配置文件:config/xendivel.php

 'auto_id' => false,

随后,请确保您为每次卡收费请求手动提供自定义的external_id

axios.post('/pay-with-card', {
    amount: 1200,
    token_id: 'card-token', // From card tokenization process.
    authentication_id: 'auth-id', // From authentication process.
+   external_id: 'your-custom-external-id', // Provide your own external id.
})

获取卡费用交易

要检索卡收费对象的详细信息,您必须提供卡收费的id(这应来自您的数据库或您的Xendit仪表板)作为第一个参数,以及字符串card作为第二个参数。

GET请求

use GlennRaya\Xendivel\Xendivel;

Route::get('/payment', function () {
    // card charge id example: 659518586a863f003659b718
    $response = Xendivel::getPayment('card-charge-id', 'card')
        ->getResponse();

    return $response;
});

此端点将返回一个JSON响应,显示了重要的详细信息,如卡收费的statuscharge_typecard_typecard_brand等。

{
  "created": "2020-01-08T04:49:08.815Z",
  "status": "CAPTURED",
  "business_id": "5848fdf860053555135587e7",
  "authorized_amount": 10000,
  "external_id": "test-pre-auth",
  "merchant_id": "xendit",
  "merchant_reference_code": "598942aabb91a4ec309e9a35",
  "card_type": "CREDIT",
  "masked_card_number": "400000XXXXXX0002",
  "charge_type": "SINGLE_USE_TOKEN",
  "card_brand": "VISA",
  "bank_reconciliation_id": "5132390610356134503009",
  "capture_amount": 9900,
  "descriptor": "My new store",
  "id": "659518586a863f003659b718"
}

多用途卡令牌

在电子商务平台上,为客户提供保存他们的信用卡/借记卡信息以供将来使用的便利性是一种常见的做法,从而在后续支付中消除重复输入数据的需要。

此功能通过卡片令牌化过程实现。如果您检查了Xendivel中包含的结算模板,您会发现此过程已经为您实现。

多用途卡令牌的示例JSON响应

{
  "status": "CAPTURED",
  "authorized_amount": 5198,
  "capture_amount": 5198,
  "currency": "PHP",
  "metadata": {},
  "credit_card_token_id": "65715e52689dc6001715bc57",
  "business_id": "6551f678273a62fd8d86e25a",
  "merchant_id": "104019905",
  "merchant_reference_code": "65715e530e502a00161aa2d9",
  "external_id": "f4270ddb-650d-4973-8786-1f5b4c048c76",
  "eci": "02",
  "charge_type": "MULTIPLE_USE_TOKEN",
  "masked_card_number": "520000XXXXXX1005",
  "card_brand": "MASTERCARD",
  "card_type": "CREDIT",
  "ucaf": "AJkBBkhgQQAAAE4gSEJydQAAAAA=",
  "descriptor": "XDT*JSON FAKERY",
  "authorization_id": "65715e5d689dc6001715bc5b",
  "bank_reconciliation_id": "7019285426096226603954",
  "issuing_bank_name": "PT BANK NEGARA INDONESIA TBK",
  "cvn_code": "M",
  "approval_code": "831000",
  "created": "2023-12-07T05:55:43.603Z",
  "id": "65715e5f689dc6001715bc60",
  "card_fingerprint": "61d6ed632aa321002350e0b2"
}

【重要】当 charge_typeMULTIPLE_USE_TOKEN 时,您应确保将 credit_card_token_id 保存到您的数据库中。您将使用此令牌在未来再次对卡进行扣款,而无需再次输入卡详情,并使用与最初扣款相同的端点。

电子钱包支付

Xendivel 与 Xendit 支持的所有电子钱包支付渠道兼容。有关更多详细信息,请参阅 https://docs.xendit.co/ewallet 上的文档,并探索 Xendit 的 API 参考 https://developers.xendit.co/api-reference/#create-ewallet-charge

收取电子钱包费用

示例 Axios POST 请求

axios
    .post('/pay-via-ewallet', {
        // You can test different failure scenarios by using the 'magic amount' from Xendit.
        amount: parseInt(amount),
        currency: 'PHP',
        checkout_method: 'ONE_TIME_PAYMENT',
        channel_code: 'PH_GCASH',
        channel_properties: {
            success_redirect_url:
                'https://your-domain.test/ewallet/success',
            failure_redirect_url: 'https://your-domain.test/ewallet/failed',
        },
    })
    .then(response => {
        // Upon successful request, you will be redirected to the eWallet's checkout url.
        console.log(response.data)
        window.location.href =
            response.data.actions.desktop_web_checkout_url
    })
    /// ...

然后,在您的 Laravel 路由或控制器中

POST请求

use GlennRaya\Xendivel\Xendivel;

Route::post('/pay-via-ewallet', function (Request $request) {
    $response = Xendivel::payWithEwallet($request)
        ->getResponse();

    return $response;
});

在上面的 Axios 请求示例中,您将被重定向到电子钱包支付提供商的结账页面以完成支付授权。如果在开发模式下,您将看到如下内容

eWallet Payment Authorization Page

生成的 JSON 响应将如下所示

{
    "created": "2023-12-09T07:51:17.926Z",
    "business_id": "6551f678273a62fd8d86e25a",
    "event": "ewallet.capture",
    "data": {
        "id": "ewc_5b2ad2c6-11a3-410a-b5ab-b41d16e39879",
        "basket": null,
        "status": "SUCCEEDED",
        "actions": {
            "qr_checkout_string": null,
            "mobile_web_checkout_url": "https://ewallet-mock-connector.xendit.co/v1/ewallet_connector/checkouts?token=clq1oqg032dn7a8hko1g",
            "desktop_web_checkout_url": "https://ewallet-mock-connector.xendit.co/v1/ewallet_connector/checkouts?token=clq1oqg032dn7a8hko1g",
            "mobile_deeplink_checkout_url": null
        },
        "created": "2023-12-09T07:51:06.63582Z",
        "updated": "2023-12-09T07:51:17.780894Z",
        "currency": "PHP",
        "customer": null,
        "metadata": null,
        "voided_at": null,
        "capture_now": true,
        "customer_id": null,
        "void_status": null,
        "callback_url": "https://pktuw9nrxn.sharedwithexpose.com/xendit/webhook",
        "channel_code": "PH_GCASH",
        "failure_code": null,
        "reference_id": "90c0c5f5-c6f0-4f2e-bf6c-f23763911f8a",
        "charge_amount": 1000,
        "capture_amount": 1000,
        "checkout_method": "ONE_TIME_PAYMENT",
        "refunded_amount": null,
        "payment_method_id": null,
        "channel_properties": {
            "failure_redirect_url": "https://package.test/ewallet/failed",
            "success_redirect_url": "https://package.test/ewallet/success"
        },
        "is_redirect_required": true,
        "payer_charged_amount": null,
        "shipping_information": null,
        "payer_charged_currency": null
    },
    "api_version": null
}

支付成功完成后,您将被重定向到您在 axios 请求参数中指定的成功或失败页面 URL(success_redirect_urlfailure_redirect_url)。

电子钱包扣款参考 ID

与卡扣款类似,Xendit 要求在电子钱包扣款有效载荷中包含 reference_id。Xendivel 还会自动为您处理此操作,在请求每个有效载荷时包含有序 UUID V4。

如果您希望为 reference_id 添加自己的实现,类似于卡支付,请在配置文件中将 auto_id 设置为 false

配置文件:config/xendivel.php

 'auto_id' => false,

并确保为每个电子钱包扣款请求提供自己的 reference_id

axios
    .post('/pay-via-ewallet', {
        // You can test different failure scenarios by using the 'magic amount' from Xendit.
        reference_id: 'your-own-reference-id',
        amount: parseInt(amount),
        currency: 'PHP',
        // Other params...
    })
    .then(response => {
        // Upon successful request, you will be redirected to the eWallet's checkout url.
        console.log(response.data)
        window.location.href =
            response.data.actions.desktop_web_checkout_url
    })
    /// ...

响应电子钱包扣款 Webhook 事件

在您的应用程序能够接收来自 Xendit 的 webhook 回调之前,请确保您已正确设置 Xendit 仪表板中的 webhook 端点,位于 电子钱包支付状态 下。

https://dashboard.xendit.co/settings/developers#webhooks

此操作既适用于开发模式也适用于生产模式。为此,您可以使用如 NgrokExpose 等工具,以便您的本地项目(localhost)可以接收来自 Xendit 的 webhook 回调。

默认情况下,Xendivel 将监听 xendit/webhook URL 以获取回调,如 Xendivel 的配置文件中定义的,每次您进行电子钱包扣款时。您可以更改默认的 webhook URL,如果您愿意的话。

config/xendivel.php

'webhook_url' => '/xendit/webhook', // You can change this to whatever you like.

然后,在您从 此处 发布了 Xendivel 的 webhook 事件监听器之后,您应将事件和监听器注册到位于 app\Providers\EventServiceProvider.php 的事件服务提供者。

use App\Events\eWalletEvents;
use App\Listeners\eWalletWebhookListener;

protected $listen = [
    // ...

    eWalletEvents::class => [
        eWalletWebhookListener::class,
    ],
];

之后,您现在可以通过位于 app/Listener/eWalletWebhookListener.php 的 webhook 监听器对 Xendit 成功完成电子钱包扣款后的回调事件进行响应。

public function handle(eWalletEvents $event)
{
    // You can inspect the returned data from the webhoook in your logs file
    // storage/logs/laravel.log
    logger('Webhook data received: ', $event->webhook_data);

    // if($event->webhook_data['data']['status'] === 'SUCCEEDED') {
    //     $invoice_data = [
    //         // Invoice data...
    //     ];

    //     $email_invoice = new Xendivel();
    //     $email_invoice->emailInvoiceTo('glenn@example.com', $invoice_data)
    //         ->send();
    // }
}

您现在可以根据回调的有效载荷执行其他任务,例如与数据库交互、调用其他 API、发送电子邮件等。

重要

每次您进行电子钱包扣款、退款或取消交易时,Xendit 都会将 webhook 事件发送到相同的 webhook 端点。

排除 Xendit 的 Webhook 回调的 CSRF 保护

您还应确保允许 Xendit 的回调通过 CSRF 保护,以便 Xendit 发送到您的应用程序的任何 webhook 回调都将被您的路由接受。您可以通过将它们的 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
    */

    protected  $except  = [
        '/xendit/*',
        'https://your-domain.com/xendit/*',
    ];
}

获取电子钱包费用

获取电子钱包扣款的详细信息。`Xendivel::getPayment` 函数接受 电子钱包扣款 ID 作为第一个参数,以及扣款类型,即 ewallet 作为第二个参数。

GET请求

use GlennRaya\Xendivel\Xendivel;

Route::get('/get-ewallet-charge', function (Request $request) {
	$response = Xendivel::getPayment('ewc_65cbfb33-a1ea-4c32-a6f3-6f8202de9d6e', 'ewallet')
	    ->getResponse();

	return $response;
});

JSON 响应将类似于以下内容

{
  "id": "ewc_bb8c3po-c3po-r2d2-c3po-r2d2c3por2d2",
  "business_id": "5f218745736e619164dc8608",
  "reference_id": "test-reference-id",
  "status": "PENDING",
  "currency": "IDR",
  "charge_amount": 1000,
  "capture_amount": 1000,
  "refunded_amount": null,
  "checkout_method": "ONE_TIME_PAYMENT",
  "channel_code": "ID_SHOPEEPAY",
  "channel_properties": {
    "success_redirect_url": "https://dashboard.xendit.co/register/1"
  },
  "actions": {
    "desktop_web_checkout_url": null,
    "mobile_web_checkout_url": null,
    "mobile_deeplink_checkout_url": "https://deeplinkcheckout.this/",
    "qr_checkout_string": "ID123XenditQRTest321DI"
  },
  "is_redirect_required": true,
  "callback_url": "https://calling-back.com/xendit/shopeepay",
  "created": "2017-07-21T17:32:28Z",
  "updated": "2017-07-21T17:32:28Z",
  "void_status": null,
  "voided_at": null,
  "capture_now": true,
  "customer_id": null,
  "payment_method_id": null,
  "failure_code": null,
  "basket": null,
  "metadata": {
    "branch_code": "tree_branch"
  }
}

取消电子钱包费用

POST请求

use GlennRaya\Xendivel\Xendivel;

Route::post('/ewallet/void', function(Request $request) {
    // Example eWallet charge ID: ewc_e743d499-baa1-49f1-96c0-cc810890739b
    $response = Xendivel::void($request->ewallet_charge_id)
        ->getResponse();

    return $response;
});

使用这个Void API,您可以取消已成功处理的eWallet支付,确保将全部原始金额退还给最终用户。

取消eWallet费用定义为在当天内创建并在截止时间23:50:00(印尼eWallets为UTC+07:00/菲律宾eWallets为UTC+08:00)之前创建的eWallet支付。

  • Void API只适用于通过/ewallets/charges API创建且状态为SUCCEEDED的费用。
  • 执行Void API后,API响应将返回PENDINGvoid_status。当取消操作成功处理后,将向您的系统URL发送后续webhook。

要在上述截止时间之后取消eWallet支付,应使用退款API

PDF发票

Invoice Template

Xendivel能够生成专业且可定制的PDF发票。您可以通过访问路由/xendivel/invoice/template来预览默认发票模板。

https://your-domain.test/xendivel/invoice/template

注意

请记住将your-domain.test替换为您的域名。

PDF发票使用标准的Laravel Blade模板生成,Xendivel将为您将其转换为PDF发票。由于发票只是常规的Blade模板,您可以将数据传递给模板,就像在Laravel Blade文件中一样。

生成PDF发票

use GlennRaya\Xendivel\Invoice;

Route::get('/xendivel/invoice/generate', function () {
    $invoice_data = [
        'invoice_number' => 1000023,
        'card_type' => 'VISA',
        'masked_card_number' => '400000XXXXXX0002',
        'merchant' => [
            'name' => 'Xendivel LLC',
            'address' => '152 Maple Avenue Greenfield, New Liberty, Arcadia USA 54331',
            'phone' => '+63 971-444-1234',
            'email' => 'xendivel@example.com',
        ],
        'customer' => [
            'name' => 'Victoria Marini',
            'address' => 'Alex Johnson, 4457 Pine Circle, Rivertown, Westhaven, 98765, Silverland',
            'email' => 'victoria@example.com',
            'phone' => '+63 909-098-654',
        ],
        'items' => [
            ['item' => 'iPhone 15 Pro Max', 'price' => 1099, 'quantity' => 5],
            ['item' => 'MacBook Pro 16" M3 Max', 'price' => 2499, 'quantity' => 3],
            ['item' => 'Apple Pro Display XDR', 'price' => 5999, 'quantity' => 2],
            ['item' => 'Pro Stand', 'price' => 999, 'quantity' => 2],
        ],
        'tax_rate' => .12,
        'tax_id' => '123-456-789',
        'footer_note' => 'Thank you for your recent purchase with us! We are thrilled to have the opportunity to serve you and hope that your new purchase brings you great satisfaction.',
    ];

    return Invoice::make($invoice_data)
        ->save();
});

如您所见,Invoice::make函数接受一个包含您希望在发票上显示的信息的关联数组,通常来自您的数据库。默认情况下,它将存储在您的Laravel应用的/storage/app/invoices目录中。您可以通过修改Xendivel配置文件中的invoice_storage_path选项来更改保存发票的位置。

'invoice_storage_path' => storage_path('/app/invoices/')

重要

您应确保为所选目录设置适当的权限,以便Xendivel可以将其存储在那里。

下载PDF发票

您可以通过调用download()函数立即将发票下载到客户的本地机器,而不是将其存储在Laravel应用的存储目录中。

use GlennRaya\Xendivel\Invoice;

Route::get('/xendivel/invoice/download', function () {
    $invoice_data = [
        // Invoice data...
    ];

    return Invoice::make($invoice_data);
	    ->download();
});

发票纸张大小

默认情况下,Xendivel将生成标准的Letter纸张大小的PDF发票。Xendivel支持以下尺寸:

Letter: 8.5in  x  11in
Legal: 8.5in  x  14in
Tabloid: 11in  x  17in
Ledger: 17in  x  11in
A0: 33.1in  x  46.8in
A1: 23.4in  x  33.1in
A2: 16.54in  x  23.4in
A3: 11.7in  x  16.54in
A4: 8.27in  x  11.7in
A5: 5.83in  x  8.27in
A6: 4.13in  x  5.83in

更改发票纸张大小

您可以通过在生成或下载发票时调用paperSize()函数并指定纸张尺寸名称作为参数来更改发票大小。

use GlennRaya\Xendivel\Invoice;

Route::get('/xendivel/invoice/download', function () {
    $invoice_data = [
        // Invoice data...
    ];

    return Invoice::make($invoice_data)
        ->paperSize('A4')
        ->download();
});

在这个示例中,我们可以通过调用paperSize('A4')函数并指定所需的纸张尺寸来修改发票的纸张大小。

更改发票方向

您的句子已经结构良好,但这里有一个稍微改进的版本

您还可以修改发票的方向;默认情况下,它为portrait。您可以使用orientation()函数将其更改为landscape

use GlennRaya\Xendivel\Invoice;

Route::get('/xendivel/invoice/download', function () {
    $invoice_data = [
        // Invoice data...
    ];

    return Invoice::make($invoice_data)
        ->paperSize('A4')
        ->orientation('landscape')
        ->download();
});

发票文件名

每当Xendivel生成、下载或通过电子邮件向您的客户发送发票时,Xendivel都将使用UUID v4生成一个唯一的文件名,并在文件名末尾附加-invoice.pdf。以下是一个示例:

c7ff9fa5-b629-4fc9-8e61-bd203c91ca65-invoice.pdf

如果您想自定义Xendivel的文件命名约定,可以很容易地通过使用Invoice类的fileName()函数来完成。

use GlennRaya\Xendivel\Invoice;

Route::get('/xendivel/invoice/download', function () {
    $invoice_data = [
        // Invoice data...
    ];

    return Invoice::make($invoice_data)
        ->paperSize('A4')
        ->orientation('landscape')
        ->fileName('my-awesome-invoice-filename')
        ->download();
});

现在生成的发票将具有如下文件名:

my-awesome-invoice-filename-invoice.pdf

定制PDF发票模板

如前所述,PDF发票模板本质上是一个标准的Laravel Blade组件。这意味着它是一个传统的HTML/PHP文件,带有TailwindCSS样式。因此,调整发票的样式和内容的工作非常简单,就像处理常规的HTML文件一样。

发布发票模板到您的views目录

php artisan vendor:publish --tag=xendivel-invoice

注意

当您从Publish Assets部分发布Xendivel的资产时,您的发票模板已经发布在resources/views/vendor/xendivel/invoice.blade.php中。

该命令会将 invoice.blade.php 发布到您的 resources/views/vendor/xendivel 目录。检查文件后,您会注意到 $invoice_data 变量。该变量包含您从上一个示例传递到视图中的关联数组。

发票模板示例部分

{{-- Other data... --}}
<table class="border-collapse w-full">
    <thead>
        <tr class="text-left">
            <th class="pb-2">Description</th>
            <th class="pb-2">Qty</th>
            <th class="pb-2 text-right">Unit Price</th>
            <th class="px-0 pb-2 text-right">Subtotal</th>
        </tr>
    </thead>
    <tbody class="divide-y divide-gray-200">
        @php
            $total_price = 0;
        @endphp
        @foreach ($invoice_data['items'] as $item)
            @php
                $total_price += $item['price'] * $item['quantity'];
            @endphp
            <tr>
                <td class="py-1">{{ $item['item']}}</td>
                <td class="py-1">{{ $item['quantity'] }}</td>
                <td class="py-1 text-right">${{ number_format($item['price'], 2) }}</td>
                <td class="py-1 text-right">
                    ${{ number_format($item['price'] * $item['quantity'], 2) }}
                </td>
            </tr>
        @endforeach
    </tbody>
</table>
{{-- Other data... --}}

由于这是一个普通的HTML/Blade模板,您可以根据需要对其进行自定义。您可以定义自己的样式,修改正在渲染的数据,甚至向模板添加图片。Xendivel会在生成、下载或通过电子邮件附件发送时自动将此模板转换为PDF文件。

将PDF发票作为电子邮件附件发送

在电子商务网站或应用上完成购买后,客户通常会收到一封包含其交易详情的电子邮件,并附有发票。Xendivel使您在购买完成后轻松地向客户发送发票变得容易。

为信用卡支付发送PDF发票

use GlennRaya\Xendivel\Xendivel;

Route::post('/checkout-email-invoice', function (Request $request) {
    $invoice_data = [
        'invoice_number' => 1000023,
        'card_type' => 'VISA',
        'masked_card_number' => '400000XXXXXX0002',
        'merchant' => [
            'name' => 'Stark Industries',
            'address' => '152 Maple Avenue Greenfield, New Liberty, Arcadia USA 54331',
            'phone' => '+63 971-444-1234',
            'email' => 'xendivel@example.com',
        ],
        'customer' => [
            'name' => 'Mr. Glenn Raya',
            'address' => 'Alex Johnson, 4457 Pine Circle, Rivertown, Westhaven, 98765, Silverland',
            'email' => 'victoria@example.com',
            'phone' => '+63 909-098-654',
        ],
        'items' => [
            ['item' => 'MacBook Pro 16" M3 Max', 'price' => $request->amount, 'quantity' => 1],
        ],
        'tax_rate' => .12,
        'tax_id' => '123-456-789',
        'footer_note' => 'Thank you for your recent purchase with us! We are thrilled to have the opportunity to serve you and hope that your new purchase brings you great satisfaction.',
    ];

    $payment = Xendivel::payWithCard($request)
        ->emailInvoiceTo('glenn@example.com', $invoice_data)
        ->send()
        ->getResponse();

    return $payment;
});

在此示例中,emailInvoiceTo() 函数接受您希望发送发票的电子邮件地址作为第一个参数,以及包含发票详情的 $invoice_data 作为第二个参数。send() 函数将指示Xendivel发送电子邮件。

电子邮件主题和信息

Email Invoice

上图是Xendivel默认发送的包含PDF发票附件的电子邮件示例。您可以自定义主题和电子邮件信息本身。

自定义主题

要更改默认电子邮件的主题,您可以使用 subject() 函数。

use GlennRaya\Xendivel\Xendivel;

Route::post('/checkout-email-invoice', function (Request $request) {
    $invoice_data = [
        // Invoice data...
    ];

    $payment = Xendivel::payWithCard($request)
        ->emailInvoiceTo('glenn@example.com', $invoice_data)
        ->subject('Thank you for your purchase!')
        ->send()
        ->getResponse();
    });
自定义信息

要更改默认电子邮件的信息,您可以使用 message() 函数。

use GlennRaya\Xendivel\Xendivel;

Route::post('/checkout-email-invoice', function (Request $request) {
    $invoice_data = [
        // Invoice data...
    ];

    $payment = Xendivel::payWithCard($request)
        ->emailInvoiceTo('glenn@example.com', $invoice_data)
        ->subject('Thank you for your purchase!')
        ->message('We appreciate your business and look forward to serving you again. We have attached your invoice.')
        ->send()
        ->getResponse();
});

为电子钱包支付发送PDF发票

在采用电子钱包支付时,发送电子邮件发票的过程略有不同。由于您的应用程序必须响应对电子钱包支付的webhook回调,因此必须在webhook监听器中直接集成电子邮件发票逻辑。

导航到 App/Listeners/eWalletWebhookListener.php 文件,并找到 handle() 方法。在此方法中,实现电子邮件发票逻辑以确保与电子钱包支付的无缝集成。

use GlennRaya\Xendivel\Xendivel;

public function handle(eWalletEvents $event)
{
    // You can inspect the returned data from the webhoook in your logs file
    // storage/logs/laravel.log
    logger('Webhook data received: ', $event->webhook_data);

    // $invoice_data = [
        // Invoice data...
    // ];

    if($event->webhook_data['data']['status'] === 'SUCCEEDED') {
        $email_invoice = new Xendivel();
        $email_invoice->emailInvoiceTo('glenn@example.com', $invoice_data)
            ->send();
    }
}

记住,在发起电子钱包支付收费请求时,您可以选择包括一个 metadata 属性。

示例

axios.post('/charge-ewallet', {
    amount: 1200,
    currency: 'PHP',
    checkout_method: 'ONE_TIME_PAYMENT',
    channel_code: 'PH_GCASH',
    channel_properties: {
        success_redirect_url: 'https://your-domain.test/ewallet/success',
        failure_redirect_url: 'https://your-domain.test/ewallet/failed',
    },

    metadata: {
        customer_id: 17,
        name: 'Glenn Raya',
        email: 'glenn@example.com'
    }
})

这允许您在支付中包含补充信息。这意味着您可以将客户的ID、电子邮件地址、电话号码等包含在内。这使得您可以在处理webhook数据时利用这些信息。

Xendit API参考: https://developers.xendit.co/api-reference/#create-ewallet-charge

队列发票邮件

Xendivel具有对电子邮件作业进行队列处理的能力,通过在后台无缝处理电子邮件任务来提高Laravel应用的响应能力。

您只需简单地将您的 xendivel.php 配置文件中的 queue_email 选项设置为 true 即可。

'queue_email' => true,

当然,您需要确保正确设置您的Laravel队列驱动程序,并且有一个队列工作者正在运行

https://laravel.net.cn/docs/10.x/queues#main-content

运行队列工作者

php artisan queue:work

重要

始终确保在将电子邮件任务发送到队列中配置Xendivel时运行一个 队列工作者,否则将不会发送电子邮件。

重要

另外,请记住,每次您更改Xendivel附带电子邮件模板时,请确保重启您的队列工作者,以便它可以使用您最新更新的电子邮件模板。

退款

Xendivel支持信用卡和电子钱包支付的退款API,并且还可以通知客户关于成功的退款。

为卡支付退款

要为通过信用卡或借记卡支付的款项进行退款,首先您必须使用 getPayment() 方法获取收费交易。第一个参数是收费的 id,第二个参数应该是 card

然后,refund() 方法的参数值是要退款的金额。当然,使用 getResponse() 方法返回API的响应。

POST 请求

use GlennRaya\Xendivel\Xendivel;
use Illuminate\Http\Request;

Route::post('/refund', function (Request $request) {
	// Example charge id: 6593a0fb82742f0056f779fd

    $response = Xendivel::getPayment($request->charge_id, 'card')
        ->refund(3500)
        ->getResponse();

    return $response;
});

此调用的响应应如下所示

{
    "credit_card_charge_id": "656eb63c23f3c20015e2f4eb",
    "amount": 5198,
    "external_id": "375b897a-8b75-4b94-a802-29a60febf589",
    "status": "REQUESTED",
    "merchant_reference_code": "656eb63123f3c20015e2f4e6",
    "uuid": "70941a1a-a2d4-4547-bb30-e3d4c163cf04",
    "currency": "PHP",
    "client_type": "API_GATEWAY",
    "created": "2023-12-05T05:50:38.701Z",
    "updated": "2023-12-05T05:50:38.701Z",
    "id": "656eba2eedab5300169c2b19",
    "fee_refund_amount": 0,
    "user_id": "6551f678273a62fd8d86e25a"
}

您始终可以检查您的Xendit仪表板以查看所有已进行的交易: https://dashboard.xendit.co/home

电子钱包支付退款

请求电子钱包支付的退款几乎与卡片退款API相同。唯一的不同之处在于电子钱包的收费ID,当然还有退款类型,即 ewallet

POST 请求

use GlennRaya\Xendivel\Xendivel;
use Illuminate\Http\Request;

Route::post('/refund', function (Request $request) {
	// Example charge id: ewc_b5baef87-d7b5-4d5c-803b-b31e80529147

    $response = Xendivel::getPayment($request->charge_id, 'ewallet')
+       ->refund(3500)
+       ->getResponse();

    return $response;
});

获取退款详情

无论退款是否成功,交易详情都会记录在您的Xendit管理员账户上。

获取电子钱包退款详情

通过其收费和退款ID获取特定电子钱包退款的状态和详情

GET请求

use GlennRaya\Xendivel\Xendivel;

Route::get('/get-ewallet-refund', function () {
    $response = Xendivel::getEwalletRefund('ewc_65cbfb33-a1ea-4c32-a6f3-6f5202dx9d6e', 'ewr_a96f9a27-8838-43bf-88f0-c0ade0aeeee3')
        ->getResponse();

    return $response;
});

通常,收费和退款ID应存储到您的数据库中。这可以在您收到Xendit的webhook回调时完成。

列出所有电子钱包退款

使用getListOfEwalletRefunds()方法和电子钱包charge_id获取与单个电子钱包收费交易关联的所有电子钱包退款详情

GET请求

use GlennRaya\Xendivel\Xendivel;

Route::get('/ewallet-refund-list', function () {
    $response = Xendivel::getListOfEwalletRefunds('ewc_65cbfb33-a1ea-4c32-a6f3-9f8201de9d6a')
        ->getResponse();

    return $response;
});

这将输出一个包含退款交易集合和每个退款状态的JSON响应

{
    "data": [
        {
            "id": "ewr_a96f9a27-8838-43bf-88f0-c0ade0aeeee3",
            "charge_id": "ewc_65cbfb33-a1ea-4c32-a6f3-6f8202de9d6e",
            "status": "SUCCEEDED",
            "currency": "PHP",
            "channel_code": "PH_GCASH",
            "capture_amount": 1000,
            "refund_amount": 1000,
            "failure_code": null,
            "reason": "OTHERS",
            "refund_amount_to_payer": null,
            "payer_captured_amount": null,
            "payer_captured_currency": null,
            "created": "2023-12-28T07:47:40.24517Z",
            "updated": "2023-12-28T07:47:45.253443Z"
        }
    ]
}

发送退款确认电子邮件

卡片退款电子邮件通知

Xendivel具有在退款请求后自动向您的客户发送电子邮件的功能。这是通过首先调用refund()函数,然后触发emailRefundConfirmationTo()函数来实现的。

use GlennRaya\Xendivel\Xendivel;

Route::get('/refund', function () {
    $response = Xendivel::getPayment('6595d0fg82741f0011f778fd', 'card')
        ->refund(3500)
        ->emailRefundConfirmationTo('glenn@example.com')
        ->send()
        ->getResponse();

    return $response;
});

您还可以自定义subjectmessage

use GlennRaya\Xendivel\Xendivel;

Route::get('/refund', function () {
    $response = Xendivel::getPayment('6595d0fg82741f0011f778fd', 'card')
        ->refund(3500)
        ->emailRefundConfirmationTo('glenn@example.com')
        ->subject('Your refund is on the way!')
        ->message('We have successfully processed your refund! It should reflect on your account within 3 banking days.')
        ->send()
        ->getResponse();

    return $response;
});

Webhook

监听Webhook事件

截至目前,只有电子钱包的收费退款取消交易可以接收到Xendit的webhook回调事件,并在这些部分中讨论响应电子钱包收费webhook事件

注意

当webhook回调未成功到达您的服务器时,Xendit将再次尝试发送webhook。请参阅Xendit的关于“投递尝试和重试”的文档:https://developers.xendit.co/api-reference/#delivery-attempts-and-retries

Webhook验证

Xendivel会自动为您处理每次Xendit向您的webhook端点发送回调时的webhook验证。您只需在.env文件中简单地包含您的账户独特的webhook验证令牌即可。

XENDIT_WEBHOOK_VERIFICATION_TOKEN=your-webhook-verification-token

您可以从仪表板中的Webhooks部分获取您的webhook验证令牌。

https://dashboard.xendit.co/settings/developers#webhooks

如果您的webhook验证令牌的值与Xendit在webhook请求头中附加的x-callback-token不同,Xendivel将拒绝webhook回调并抛出403访问拒绝http异常。这意味着请求并非来自Xendit。

如果您不希望验证webhook回调是否来自Xendit,您可以通过将Xendivel的配置文件中的verify_webhook_signature设置为false来禁用此功能。

配置文件 config/xendivel.php

'verify_webhook_signature' => false,

重要

验证webhook来源是可选的,但出于安全原因,强烈建议这样做。这是为了确保webhook回调事件确实来自Xendit,而不是来自第三方或非法服务。

部署到生产环境

当您准备部署Laravel应用程序时,将.env文件中的APP_ENV设置为production将禁用以下Xendivel路由

  • /xendivel/invoice/template — 示例发票模板。
  • /xendivel/checkout/blade — 示例结账页面。
  • /xendivel/invoice/generate — 生成示例PDF发票。
  • /xendivel/invoice/download — 下载示例PDF发票。

这些内置的Xendivel路由仅用于开发目的。您应将结账和发票模板发布到视图目录,以便在Laravel应用程序中使用它们。

Xendit 生产密钥

当部署到生产环境时,您应该将 Xendit 的 secret_keypublic_keywebhook_verification_token 替换为生产密钥。

XENDIT_SECRET_KEY=your-production-secret-key
XENDIT_PUBLIC_KEY=your-production-public-key
XENDIT_WEBHOOK_VERIFICATION_TOKEN=your-production-webhook-verification-token