glennraya / xendivel
一个Laravel包,用于轻松集成Xendit支付网关。它支持信用卡和借记卡支付、电子钱包支付和自定义发票,队列通知、webhook监听器等。
Requires
- php: ~8.2.0|~8.3.0
- guzzlehttp/guzzle: ^7.0
- laravel/framework: ^11.0
- nesbot/carbon: ^3.4.0
- spatie/browsershot: ^4.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- pestphp/pest: ^2.31
README
Xendivel — Xendit支付网关的Laravel包
一个设计用于将Xendit支付网关无缝集成到您的Laravel应用程序或网站中的Laravel包。它通过信用卡、借记卡和电子钱包进行支付。此外,该包还提供对自定义发票、队列发票或退款电子邮件通知、webhook事件监听器和验证的支持。
视频演示
我创建了一个简短的视频演示来展示该包的功能。您可以在这里找到它。
路线图
以下功能目前尚未由Xendivel支持,但计划在未来更新中包含。
- 直接银行借记
- 促销(优惠券/折扣代码)
- 订阅服务
- 实时推送支付状态通知(Laravel Reverb)
- 付款分配API(用于大量支付处理,如员工工资)
- PayLater
- 二维码支付
目录
功能
- 信用卡/借记卡 - 通过主要信用卡或借记卡轻松处理支付。
- 电子钱包支付 - 根据您的地区接受多种电子钱包支付(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密钥
- 密钥/公钥:https://dashboard.xendit.co/settings/developers#api-keys
- Webhook验证令牌:https://dashboard.xendit.co/settings/developers#webhooks
从您的仪表板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_email
为true
,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/Events
和app/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
结账模板
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_id
、amount
和authentication_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_details
、metadata
、external_id
、currency
和descriptor
。请参阅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响应,显示了重要的详细信息,如卡收费的status
、charge_type
、card_type
、card_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_type
为MULTIPLE_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 请求示例中,您将被重定向到电子钱包支付提供商的结账页面以完成支付授权。如果在开发模式下,您将看到如下内容
生成的 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_url
或 failure_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
此操作既适用于开发模式也适用于生产模式。为此,您可以使用如 Ngrok 或 Expose 等工具,以便您的本地项目(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响应将返回
PENDING
的void_status
。当取消操作成功处理后,将向您的系统URL发送后续webhook。
要在上述截止时间之后取消eWallet支付,应使用退款API。
PDF发票
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发送电子邮件。
电子邮件主题和信息
上图是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; });
您还可以自定义subject
和message
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_key
、public_key
和 webhook_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