{"id":27079113,"url":"https://github.com/glennraya/xendivel","last_synced_at":"2026-04-26T08:14:55.502Z","repository":{"id":217069907,"uuid":"721414350","full_name":"glennraya/xendivel","owner":"glennraya","description":"A Laravel package for Xendit payment gateway. It currently supports credit cards, debit cards, and e-wallet payments. Future support for subscriptions, etc.","archived":false,"fork":false,"pushed_at":"2025-03-31T06:33:57.000Z","size":5626,"stargazers_count":32,"open_issues_count":1,"forks_count":8,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-31T07:03:39.061Z","etag":null,"topics":["gcash-xendit-laravel","grabpay-xendit-laravel","laravel","laravel-billing-package","laravel-payment-package","laravel-xendit","laravel-xendit-package","maya-xendit-laravel","paymaya-xendit-laravel","shopeepay-xendit-laravel","xendit","xendit-api","xendit-php"],"latest_commit_sha":null,"homepage":"https://packagist.org/packages/glennraya/xendivel","language":"Blade","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/glennraya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-21T02:26:12.000Z","updated_at":"2025-03-31T06:31:06.000Z","dependencies_parsed_at":"2024-11-16T11:18:04.229Z","dependency_job_id":"ef08ebf0-7bc9-42b0-b0c3-bed7600d82d0","html_url":"https://github.com/glennraya/xendivel","commit_stats":null,"previous_names":["glennraya/xendivel"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glennraya%2Fxendivel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glennraya%2Fxendivel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glennraya%2Fxendivel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glennraya%2Fxendivel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glennraya","download_url":"https://codeload.github.com/glennraya/xendivel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247423477,"owners_count":20936621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gcash-xendit-laravel","grabpay-xendit-laravel","laravel","laravel-billing-package","laravel-payment-package","laravel-xendit","laravel-xendit-package","maya-xendit-laravel","paymaya-xendit-laravel","shopeepay-xendit-laravel","xendit","xendit-api","xendit-php"],"created_at":"2025-04-06T01:32:22.334Z","updated_at":"2026-04-18T08:09:48.879Z","avatar_url":"https://github.com/glennraya.png","language":"Blade","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n![Project Logo](artwork/xendivel.jpg)\n\n# Xendivel — A Laravel package for Xendit payment gateway\n\nA Laravel package designed for the seamless integration of the [Xendit](https://xendit.co/) payment gateway into your Laravel-powered applications or websites. It facilitates payments through credit cards, debit cards, and eWallets. Additionally, the package provides support for custom invoicing, queued invoice or refund email notifications, webhook event listeners and verification.\n\n## Video Demo\n\nI've created a short video demo to showcase the package's features. You can find it [here](https://youtu.be/oxdE3Y6viAE).\n\n## Roadmap\n\nThe following features, while not currently supported by the Xendivel, are planned for inclusion in upcoming updates.\n\n- Direct Bank Debit\n- Promotions (coupon/discount codes)\n- Subscription services\n- Real-time push notifications for payment status (Laravel Reverb)\n- Disbursement APIs (for mass payment processing like employee payroll)\n- PayLater\n- QR Code payments\n\n## Table of Contents\n\n1. [Features](#features)\n2. [Pre-requisites](#pre-requisites)\n3. [Installation](#installation)\n    - [PDF Runtime Requirements](#pdf-runtime-requirements)\n    - [Install Xendivel](#install-xendivel)\n4. [Initial Setup](#initial-setup)\n    - [Xendit API keys](#xendit-api-keys)\n    - [Configure Mail (Optional)](#configure-mail-optional)\n    - [Queues (Optional)](#queues-optional)\n    - [Publish Config and Assets](#publish-config-and-assets)\n\t    - [Publish Individual Assets](#publish-individual-assets)\n    - [Browsershot Runtime Options](#browsershot-runtime-options)\n    - [Laravel Herd and Valet Node Paths](#laravel-herd-and-valet-node-paths)\n5. [Checkout Templates](#checkout-templates)\n6. [Usage](#usage)\n    - [Card Payments](#card-payments)\n        - [Card Details Tokenization](#card-details-tokenization)\n        - [Charge Credit Or Debit Cards](#charge-credit-or-debit-cards)\n\t        - [External ID](#external-id)\n        - [Get Card Charge Transaction](#get-card-charge-transaction)\n        - [Multi-Use Card Token](#multi-use-card-token)\n    - [eWallet Payments](#ewallet-payments)\n        - [Charge eWallet](#charge-ewallet)\n        - [Get eWallet Charge](#get-ewallet-charge)\n        - [Void eWallet Charge](#void-ewallet-charge)\n    - [PDF Invoicing](#pdf-invoicing)\n        - [Generate PDF Invoice](#generate-pdf-invoice)\n        - [Download PDF Invoice](#download-pdf-invoice)\n        - [Paper Size](#invoice-paper-size)\n        - [Change Invoice Paper Size](#change-invoice-paper-size)\n        - [Change Invoice Orientation](#change-invoice-orientation)\n        - [Invoice Filenaming](#invoice-filename)\n        - [Customizing PDF Invoice](#customizing-pdf-invoice-template)\n        - [Sending PDF Invoice As Email Attachment](#sending-pdf-invoice-as-email-attachment)\n\t        - [Sending PDF Invoice for Card Payments](#sending-pdf-invoice-for-card-payments)\n\t        - [Sending PDF Invoice for eWallet Payments](#sending-pdf-invoice-for-ewallet-payments)\n        - [Queue Invoice Email](#queue-invoice-email)\n    - [Refunds](#refunds)\n        - [Refund for Card Payments](#refund-for-card-payments)\n        - [Refund for ewallet Payments](#refund-for-ewallet-payments)\n        - [Get Refund Details](#get-refund-details)\n        - [List All eWallet Refunds](#list-all-ewallet-refunds)\n        - [Email Refund Confirmation](#email-refund-confirmation)\n    - [Webhook](#webhook)\n        - [Listen to Webhook Event](#listen-to-webhook-event)\n        - [Webhook Verification](#webhook-verification)\n7. [Deploying to Production](#deploying-to-production)\n8. [Tests](#tests)\n\n## Features\n\n- **Credit/Debit Cards** - Easily process payments through major credit or debit cards.\n- **eWallet Payments** - Accepts a diverse range of eWallet payments based on your region (GCash, ShopeePay, PayMaya, GrabPay, etc.).\n- **Custom Invoicing** - Provides built-in, highly customizable, and professional-looking invoice templates.\n- **Queued Email Notifications** - Enables the use of markdown email templates and the option to schedule email notifications for background processing.\n- **Webhooks** - Comes with built-in webhook event listeners from Xendit and ensures secure webhook verification.\n\n### Pre-requisites\n\n- PHP 8.2 or higher\n- Laravel 10 or higher\n- Node 22.0 LTS or higher\n- npm and Puppeteer 23.0 or higher\n- Chrome or Chromium for Browsershot PDF generation\n\n### Supported Versions\n\n| Target | Supported versions |\n| --- | --- |\n| PHP runtime | 8.2, 8.3, 8.4, 8.5 |\n| Laravel runtime | 10, 11, 12, 13 |\n| Default contributor test stack | PHP 8.3+, Laravel 12, Pest 4, Orchestra Testbench 10 |\n\nLaravel 13 is officially supported and requires PHP 8.3+. Laravel 10 through 12 remain installable on PHP 8.2+.\n\n## Installation\n\n### PDF Runtime Requirements\n\nXendivel uses [Spatie Browsershot](https://spatie.be/docs/browsershot) for generating PDF invoices from HTML or Blade templates. Browsershot runs Puppeteer behind the scenes, so the Laravel application needs Node, npm, Puppeteer, and Chrome or Chromium available on the server that generates invoices.\n\n\u003e [!IMPORTANT]\n\u003e Browsershot does not require a paid Typeset.sh subscription or a private Composer repository. It does require a working browser runtime.\n\nInstall Puppeteer in the Laravel application:\n\n```bash\nnpm install puppeteer\n```\n\nInstall Chrome for Puppeteer when your deployment image does not already include it:\n\n```bash\nnpx puppeteer browsers install chrome\n```\n\nXendivel requires the Node and npm binary paths in the Laravel application's `.env` file. Browsershot is executed by PHP, queue workers, and web server processes that may not use the same shell `PATH` as your terminal, so set these values after installing Puppeteer:\n\n```dotenv\nXENDIVEL_BROWSERSHOT_NODE_BINARY=\"/absolute/path/to/node\"\nXENDIVEL_BROWSERSHOT_NPM_BINARY=\"/absolute/path/to/npm\"\n```\n\nYou can find the paths from the application terminal:\n\n```bash\ncommand -v node\ncommand -v npm\n```\n\nOn Linux servers, install the system libraries Chrome needs. The exact package names vary by distribution; Spatie's Browsershot requirements page includes examples for current Ubuntu and Laravel Forge servers.\n\n### Install Xendivel\n\nXendivel utilizes Composer's package auto-discovery. Install Xendivel via Composer and it will automatically register itself.\n\n```bash\ncomposer require glennraya/xendivel\n```\n\n\n## Initial Setup\n\n### Xendit API Keys\n\nPrior to using Xendivel, it's essential to have a Xendit account with properly configured API keys. Activation of your Xendit account for production is not necessary to test Xendivel's features. Test mode will be automatically enabled upon signing up for a Xendit account. Obtain your API keys from the following URLs:\n\n- Secret Key/Public Key: https://dashboard.xendit.co/settings/developers#api-keys\n- Webhook Verification Token: https://dashboard.xendit.co/settings/developers#webhooks\n\nGenerate `Money-In` `secret key` with `read` and `write` permissions from your dashboard API keys section.\n\nAfter you acquired all these keys, please make sure you include them to your Laravel's `.env` file:\n\n```ini\nXENDIT_SECRET_KEY=your-secret-key\nXENDIT_PUBLIC_KEY=your-public-key\nXENDIT_WEBHOOK_VERIFICATION_TOKEN=your-webhook-verification-token\n```\n\n### Configure Mail (Optional)\n\nXendivel can send invoices to your customers as email attachments. To utilize this feature, ensure your [Laravel Mail](https://laravel.com/docs/13.x/mail#main-content) is correctly set up. Before Xendivel dispatches invoice or refund email notifications, ensure your mail credentials are filled in your `.env` file.\n\n```ini\nMAIL_MAILER=smtp\nMAIL_HOST=your-mailer-host\nMAIL_PORT=your-mailer-port\nMAIL_USERNAME=your-mailer-username\nMAIL_PASSWORD=your-mailer-password\nMAIL_ENCRYPTION=tls\nMAIL_FROM_ADDRESS=\"fromaddress@example.com\"\nMAIL_FROM_NAME=\"${APP_NAME}\"\n```\n\n### Queues (Optional)\n\nXendivel facilitates the queueing of email processes for background execution. If you intend to employ queued emails for tasks such as invoicing or refund notifications, ensure that you have properly configured [Laravel Queues](https://laravel.com/docs/13.x/queues#main-content).\n\nThen, make sure you have a queue worker running:\n\n```bash\nphp artisan queue:work\n```\n\nFinally, please ensure that `queue_email` is set to `true` from your `.env` file:\n\n```php\n'queue_email' =\u003e true,\n```\n\nOnce you have successfully configured Laravel's queues and enabled `queue_email` to `true`, Xendivel is now capable of dispatching invoice or refund emails to the queue for background execution, enabling your app to respond to other requests or do other tasks without waiting for the jobs to finish. **This will improve overall user experience!**\n\n### Publish Config and Assets\n\nAll assets and configuration file must be published to its proper directory for Xendivel to function properly:\n\n```bash\nphp artisan vendor:publish --tag=xendivel\n```\n\nExecuting this command will publish Xendivel's assets to the following directories:\n\n- Config file - `config` directory.\n- Invoice template - `resources/views/vendor/xendivel` directory.\n- Email templates - `resources/views/vendor/xendivel/emails` directory.\n- Blade checkout template - `resources/views/vendor/xendivel` directory.\n- Webhook Event and Listener - `app/Events` and `app/Listeners` directory respectively.\n\n#### Publish Individual Assets\n\n##### Configuration File\n```bash\nphp artisan vendor:publish --tag=xendivel-config\n```\n\n##### Invoice Template\n```bash\nphp artisan vendor:publish --tag=xendivel-invoice\n```\n\n##### Checkout (Blade)\n```bash\nphp artisan vendor:publish --tag=xendivel-checkout-blade\n```\n\n##### Checkout (ReactJS)\n```bash\nphp artisan vendor:publish --tag=xendivel-checkout-react\n```\n\n##### Checkout (ReactJS + TypeScript)\n```bash\nphp artisan vendor:publish --tag=xendivel-checkout-react-typescript\n```\n\n##### Webhook Event Listener\n```bash\nphp artisan vendor:publish --tag=xendivel-webhook-listener\n```\n\n### Browsershot Runtime Options\n\nXendivel exposes Browsershot runtime options through `config/xendivel.php`. The Node and npm binary paths are required in `.env`; the remaining values are optional environment-specific overrides:\n\n```php\n'browsershot' =\u003e [\n    'timeout' =\u003e 60,\n    'node_binary' =\u003e env('XENDIVEL_BROWSERSHOT_NODE_BINARY'),\n    'npm_binary' =\u003e env('XENDIVEL_BROWSERSHOT_NPM_BINARY'),\n    'chrome_path' =\u003e env('XENDIVEL_BROWSERSHOT_CHROME_PATH'),\n    'node_module_path' =\u003e env('XENDIVEL_BROWSERSHOT_NODE_MODULE_PATH'),\n    'include_path' =\u003e env('XENDIVEL_BROWSERSHOT_INCLUDE_PATH'),\n    'content_url' =\u003e env('XENDIVEL_BROWSERSHOT_CONTENT_URL', env('APP_URL')),\n    'no_sandbox' =\u003e env('XENDIVEL_BROWSERSHOT_NO_SANDBOX', false),\n],\n```\n\n- `timeout`: max Browsershot runtime in seconds.\n- `node_binary` / `npm_binary`: required Node and npm executable paths.\n- `chrome_path`: custom Chrome or Chromium executable path.\n- `node_module_path`: custom `node_modules` path when Puppeteer is installed outside the app root.\n- `include_path`: custom shell include path for resolving Node/npm.\n- `content_url`: base URL used when invoice HTML contains relative asset paths.\n- `no_sandbox`: enables Browsershot's `noSandbox()` option for Linux environments that require it.\n\nSet at least these values in the Laravel application's `.env` file:\n\n```dotenv\nXENDIVEL_BROWSERSHOT_NODE_BINARY=\"/absolute/path/to/node\"\nXENDIVEL_BROWSERSHOT_NPM_BINARY=\"/absolute/path/to/npm\"\n```\n\nOnly non-empty optional path values are applied to Browsershot.\n\n#### Laravel Herd and Valet Node Paths\n\nWhen running a Laravel app through Herd or Valet on macOS, the PHP process may not inherit the same shell `PATH` as your terminal. Point Xendivel to the exact Node and npm binaries in the application's `.env` file so Browsershot can run reliably.\n\nFor Laravel Herd, the paths usually live under `~/Library/Application Support/Herd/config/nvm/versions/node/{version}/bin`. Use `command -v node` and `command -v npm` from the same terminal profile you use for the project, then add those paths to `.env`:\n\n```dotenv\nAPP_URL=http://your-app.test\n\nXENDIVEL_BROWSERSHOT_NODE_BINARY=\"/Users/your-user/Library/Application Support/Herd/config/nvm/versions/node/v24.14.1/bin/node\"\nXENDIVEL_BROWSERSHOT_NPM_BINARY=\"/Users/your-user/Library/Application Support/Herd/config/nvm/versions/node/v24.14.1/bin/npm\"\n```\n\nAfter changing these values, clear Laravel's cached config:\n\n```bash\nphp artisan config:clear\n```\n\n## Checkout Templates\n\n![Checkout Template](docs/image_assets/checkout-template.png)\n\nXendivel ships with a complete, fully working checkout template for cards and eWallet payments. The template include various variants such as **ReactJS component**, **ReactJS+TypeScript** component, and a regular **Blade** template and **VanillaJS**.\n\nYou can choose between the currently available template variants, you can even create your own.\n\n### Blade Template\n\nWe offer a standard Blade template for the checkout example, using VanillaJS. There's a built-in route allowing you to test this template at `/xendivel/checkout/blade`. You can access it through a URL like `https://your-domain.test/xendivel/checkout/blade`.\n\n\u003e [!Note]\n\u003e When you run the command `php artisan vendor:publish --tag=xendivel` the checkout blade template will be on your `/resources/views/vendor/xendivel/checkout.blade.php` directory.\n\n### ReactJS + TypeScript component\n\nXendivel also have a checkout template component for **ReactJS** or **React+TypeScript** for those who are using front-end frameworks like React instead of regular Blade template.\n\n```bash\nphp artisan vendor:publish --tag=xendivel-checkout-react-typescript\n\nphp artisan vendor:publish --tag=xendivel-checkout-react\n```\n\nThese will be published under `/resources/js/vendor/xendivel/Checkout.tsx` for React+TypeScript or  `/resources/js/vendor/xendivel/Checkout.jsx` for plain ReactJS.\n\n\u003e [!IMPORTANT]\n\u003e After publishing either one of these templates, please make sure you filled up the `public key` section on these React templates. Since this is a public key, it's perfectly safe to publish it directly on your templates.\n\n```javascript\n// Set your 'public' key here.\nXendit.setPublishableKey(\n    'your-public-key',\n)\n```\n\nThese templates demonstrate card tokenization, credit/debit card, and eWallet payments. They serve to guide your payment collection process for implementation in your front-end stack. Alternatively, use them as fully functional standalone templates if you wish.\n\n## Usage\n\n### Card Payments\n\n#### Card Details Tokenization\n\n\nXendit uses tokenization to securely handle credit or debit card details. This process involves using Xendit's JavaScript library to convert sensitive card information like the number, expiry date, and CVV into secure tokens before they are sent to your back-end. This method ensures that the actual card details are not transmitted, enhancing the safety and confidentiality of your customer's card information.\n\nFor more details, refer to Xendit's documentation below:\n\nhttps://docs.xendit.co/credit-cards/integrations/tokenization\n\nXendivel offers easy-to-use templates in **ReactJS, React+TypeScript, and Blade**, providing ready-to-use checkout components for card/eWallet transactions. These templates offer a robust foundation for payment processing. For further information, see the [Checkout templates](#checkout-templates) section.\n\n#### Charge Credit Or Debit Cards\n\nThe `Xendivel::payWithCard` function accepts the incoming request payload with the `token_id`, `amount`, and `authentication_id`:\n\nExample Front-end POST Request Using Axios\n\n```javascript\naxios.post('/pay-with-card', {\n    amount: 1200,\n    token_id: 'card-token', // From card tokenization process.\n    authentication_id: 'auth-id', // From authentication process.\n    // Additional optional parameters:\n\n    // external_id: 'your-custom-external-id',\n\n    // descriptor: \"Merchant Business Name\",\n\n    // currency: 'PHP',\n\n    // metadata: {\n    //     store_owner: 'Glenn Raya',\n    //     nationality: 'Filipino',\n    //     product: 'MacBook Pro 16\" M3 Pro',\n    //     other_details: {\n    //         purpose: 'Work laptop',\n    //         issuer: 'Xendivel LTD',\n    //         manufacturer: 'Apple',\n    //         color: 'Silver'\n    //     }\n    // }\n\n    // billing_details: {\n    //     given_names: 'Glenn',\n    //     surname: 'Raya',\n    //     email: 'glenn@example.com',\n    //     mobile_number: '+639171234567',\n    //     phone_number: '+63476221234',\n    //     address:{\n    //         street_line1: 'Ivory St. Greenfield Subd.',\n    //         street_line2: 'Brgy. Coastal Ridge',\n    //         city: 'Balanga City',\n    //         province_state: 'Bataan',\n    //         postal_code: '2100',\n    //         country: 'PH'\n    //     }\n    // },\n})\n// ...\n```\n\nThen, in your Laravel route/controller\n\n`POST` Request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::post('/pay-with-card', function (Request $request) {\n    $payment = Xendivel::payWithCard($request)\n        -\u003egetResponse();\n\n    return $payment;\n});\n```\n\nThe `getResponse()` function ensures that you get a JSON response:\n\n```json\n{\n  \"status\": \"CAPTURED\",\n  \"authorized_amount\": 5198,\n  \"capture_amount\": 5198,\n  \"currency\": \"PHP\",\n  \"metadata\": {},\n  \"credit_card_token_id\": \"656ed874edab5300169c3092\",\n  \"business_id\": \"6551f678273a62fd8d86e25a\",\n  \"merchant_id\": \"104019905\",\n  \"merchant_reference_code\": \"656ed874edab5300169c3091\",\n  \"external_id\": \"43565633-dd58-47ae-bbe6-648f78d6652c\",\n  \"eci\": \"02\",\n  \"charge_type\": \"SINGLE_USE_TOKEN\",\n  \"masked_card_number\": \"520000XXXXXX1005\",\n  \"card_brand\": \"MASTERCARD\",\n  \"card_type\": \"CREDIT\",\n  \"ucaf\": \"AJkBBkhgQQAAAE4gSEJydQAAAAA=\",\n  \"descriptor\": \"XDT*JSON FAKERY\",\n  \"authorization_id\": \"656ed87c23f3c20015e2fb95\",\n  \"bank_reconciliation_id\": \"7017631974056110603955\",\n  \"issuing_bank_name\": \"PT BANK NEGARA INDONESIA TBK\",\n  \"cvn_code\": \"M\",\n  \"approval_code\": \"831000\",\n  \"created\": \"2023-12-05T07:59:58.453Z\",\n  \"id\": \"656ed87e23f3c20015e2fb96\",\n  \"card_fingerprint\": \"61d6ed632aa321002350e0b2\"\n}\n\n```\n\nXendit accepts optional parameters such as **`billing_details`**, **`metadata`**,  **`external_id`**, **`currency`**,  and **`descriptor`** as demonstrated in the Axios request above. Please refer to Xendit's documentation to learn more about these parameters:\n\nhttps://developers.xendit.co/api-reference/#create-charge\n\n\u003e You can also forward an invoice in PDF format as an email attachment to your customer's email address. Details about this process are covered in the [PDF Invoicing](#pdf-invoicing) section.\n\n##### External ID\nXendit requires the inclusion of an `external_id` parameter in each credit/debit card charge. By default, Xendivel simplifies this process by generating a unique external ID using Ordered UUID v4 automatically for you.\n\nhttps://laravel.com/docs/13.x/strings#method-str-ordered-uuid\n\nNevertheless, if you choose to create your own `external_id` for some reason, you can achieve this by setting the `auto_id` option in the `xendivel.php` config file to `false`.\n\nConfig file: `config/xendivel.php`\n\n```php\n 'auto_id' =\u003e false,\n```\n\nSubsequently, ensure that you manually provide your custom `external_id` for each card charge request.\n\n```javascript\naxios.post('/pay-with-card', {\n    amount: 1200,\n    token_id: 'card-token', // From card tokenization process.\n    authentication_id: 'auth-id', // From authentication process.\n+   external_id: 'your-custom-external-id', // Provide your own external id.\n})\n```\n\n#### Get Card Charge Transaction\n\nTo retrieve the details of the card charge object, you must provide the `id` of the card charge (which should come from your database or your Xendit dashboard) as the first parameter, and the string `card` as the second parameter.\n\n`GET` Request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::get('/payment', function () {\n    // card charge id example: 659518586a863f003659b718\n    $response = Xendivel::getPayment('card-charge-id', 'card')\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\nThis endpoint will return a JSON response that shows important details like the `status` of the card charge, `charge_type`, `card_type`, `card_brand`, etc.\n\n```json\n{\n  \"created\": \"2020-01-08T04:49:08.815Z\",\n  \"status\": \"CAPTURED\",\n  \"business_id\": \"5848fdf860053555135587e7\",\n  \"authorized_amount\": 10000,\n  \"external_id\": \"test-pre-auth\",\n  \"merchant_id\": \"xendit\",\n  \"merchant_reference_code\": \"598942aabb91a4ec309e9a35\",\n  \"card_type\": \"CREDIT\",\n  \"masked_card_number\": \"400000XXXXXX0002\",\n  \"charge_type\": \"SINGLE_USE_TOKEN\",\n  \"card_brand\": \"VISA\",\n  \"bank_reconciliation_id\": \"5132390610356134503009\",\n  \"capture_amount\": 9900,\n  \"descriptor\": \"My new store\",\n  \"id\": \"659518586a863f003659b718\"\n}\n```\n\n#### Multi-Use Card Token\n\nIt's a common practice in e-commerce platforms to offer customers the convenience of saving their credit/debit card details for future use, eliminating the need for repetitive data entry during subsequent payments.\n\nThis functionality is achieved through the card tokenization process. If you've examined the [checkout templates](#checkout-templates) included with Xendivel, you'll find that this process has already been implemented for you.\n\nExample JSON response for multi-use card token:\n\n```json\n{\n  \"status\": \"CAPTURED\",\n  \"authorized_amount\": 5198,\n  \"capture_amount\": 5198,\n  \"currency\": \"PHP\",\n  \"metadata\": {},\n  \"credit_card_token_id\": \"65715e52689dc6001715bc57\",\n  \"business_id\": \"6551f678273a62fd8d86e25a\",\n  \"merchant_id\": \"104019905\",\n  \"merchant_reference_code\": \"65715e530e502a00161aa2d9\",\n  \"external_id\": \"f4270ddb-650d-4973-8786-1f5b4c048c76\",\n  \"eci\": \"02\",\n  \"charge_type\": \"MULTIPLE_USE_TOKEN\",\n  \"masked_card_number\": \"520000XXXXXX1005\",\n  \"card_brand\": \"MASTERCARD\",\n  \"card_type\": \"CREDIT\",\n  \"ucaf\": \"AJkBBkhgQQAAAE4gSEJydQAAAAA=\",\n  \"descriptor\": \"XDT*JSON FAKERY\",\n  \"authorization_id\": \"65715e5d689dc6001715bc5b\",\n  \"bank_reconciliation_id\": \"7019285426096226603954\",\n  \"issuing_bank_name\": \"PT BANK NEGARA INDONESIA TBK\",\n  \"cvn_code\": \"M\",\n  \"approval_code\": \"831000\",\n  \"created\": \"2023-12-07T05:55:43.603Z\",\n  \"id\": \"65715e5f689dc6001715bc60\",\n  \"card_fingerprint\": \"61d6ed632aa321002350e0b2\"\n}\n\n```\n\u003e[!IMPORTANT ]\n\u003eWhen `charge_type` is `MULTIPLE_USE_TOKEN`, you should make sure that you save the `credit_card_token_id` to your database. You will use this token to charge the card again in the future without re-entering the card details again using the same endpoint use the initially charge the card.\n\n### eWallet Payments\nXendivel is compatible with all eWallet payment channels supported by Xendit. For further details, refer to the documentation at https://docs.xendit.co/ewallet, and explore Xendit's API reference at https://developers.xendit.co/api-reference/#create-ewallet-charge.\n\n#### Charge eWallet\n\nExample Axios post request:\n\n```javascript\naxios\n    .post('/pay-via-ewallet', {\n        // You can test different failure scenarios by using the 'magic amount' from Xendit.\n        amount: parseInt(amount),\n        currency: 'PHP',\n        checkout_method: 'ONE_TIME_PAYMENT',\n        channel_code: 'PH_GCASH',\n        channel_properties: {\n            success_redirect_url:\n                'https://your-domain.test/xendivel/payment/success',\n            failure_redirect_url:\n                'https://your-domain.test/xendivel/payment/failed',\n        },\n    })\n    .then(response =\u003e {\n        // Upon successful request, you will be redirected to the eWallet's checkout url.\n        console.log(response.data)\n        window.location.href =\n            response.data.actions.desktop_web_checkout_url\n    })\n    /// ...\n```\n\nThen, on your Laravel route or controller:\n\n`POST` Request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::post('/pay-via-ewallet', function (Request $request) {\n    $response = Xendivel::payWithEwallet($request)\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\nIn the example Axios request above you will be redirected to the eWallet payment provider's checkout page to complete the payment authorization there. If you are on development mode, you will see something like this:\n\n![eWallet Payment Authorization Page](docs/image_assets/ewallet-authorization.png)\n\n\nThe resulting JSON response would look like this:\n\n```json\n{\n    \"created\": \"2023-12-09T07:51:17.926Z\",\n    \"business_id\": \"6551f678273a62fd8d86e25a\",\n    \"event\": \"ewallet.capture\",\n    \"data\": {\n        \"id\": \"ewc_5b2ad2c6-11a3-410a-b5ab-b41d16e39879\",\n        \"basket\": null,\n        \"status\": \"SUCCEEDED\",\n        \"actions\": {\n            \"qr_checkout_string\": null,\n            \"mobile_web_checkout_url\": \"https://ewallet-mock-connector.xendit.co/v1/ewallet_connector/checkouts?token=clq1oqg032dn7a8hko1g\",\n            \"desktop_web_checkout_url\": \"https://ewallet-mock-connector.xendit.co/v1/ewallet_connector/checkouts?token=clq1oqg032dn7a8hko1g\",\n            \"mobile_deeplink_checkout_url\": null\n        },\n        \"created\": \"2023-12-09T07:51:06.63582Z\",\n        \"updated\": \"2023-12-09T07:51:17.780894Z\",\n        \"currency\": \"PHP\",\n        \"customer\": null,\n        \"metadata\": null,\n        \"voided_at\": null,\n        \"capture_now\": true,\n        \"customer_id\": null,\n        \"void_status\": null,\n        \"callback_url\": \"https://pktuw9nrxn.sharedwithexpose.com/xendit/webhook\",\n        \"channel_code\": \"PH_GCASH\",\n        \"failure_code\": null,\n        \"reference_id\": \"90c0c5f5-c6f0-4f2e-bf6c-f23763911f8a\",\n        \"charge_amount\": 1000,\n        \"capture_amount\": 1000,\n        \"checkout_method\": \"ONE_TIME_PAYMENT\",\n        \"refunded_amount\": null,\n        \"payment_method_id\": null,\n        \"channel_properties\": {\n            \"failure_redirect_url\": \"https://package.test/xendivel/payment/failed\",\n            \"success_redirect_url\": \"https://package.test/xendivel/payment/success\"\n        },\n        \"is_redirect_required\": true,\n        \"payer_charged_amount\": null,\n        \"shipping_information\": null,\n        \"payer_charged_currency\": null\n    },\n    \"api_version\": null\n}\n\n```\n\nUpon the successful completion of the payment, you will be redirected to the designated success or failure page URL as specified in your axios request parameters (`success_redirect_url` or `failure_redirect_url`). If you do not provide those URLs, Xendivel will use its built-in return pages at `/xendivel/payment/success` and `/xendivel/payment/failed`.\n\nYou may also set default return URLs in your `.env` file:\n\n```ini\nXENDIVEL_SUCCESS_REDIRECT_URL=https://your-domain.test/payment/success\nXENDIVEL_FAILURE_REDIRECT_URL=https://your-domain.test/payment/failed\n```\n\nThese return pages are only for the customer's browser after payment authorization. Your application should still treat Xendit's webhook event, or an explicit payment status lookup, as the source of truth for the final payment status.\n\n#### eWallet Charge Reference ID\n\nLike the card charge, Xendit requires the inclusion of `reference_id` on the eWallet charge payload. Xendivel also handles this for you automatically by including Ordered UUID V4 on each payload upon request.\n\nIf you wish to add your own implementation for `reference_id`, like in the card payment, set the `auto_id` to `false` from your config file:\n\nConfig file: `config/xendivel.php`\n\n```php\n 'auto_id' =\u003e false,\n```\n\nAnd make sure you provide your own `reference_id` for every eWallet charge request:\n\n```javascript\naxios\n    .post('/pay-via-ewallet', {\n        // You can test different failure scenarios by using the 'magic amount' from Xendit.\n        reference_id: 'your-own-reference-id',\n        amount: parseInt(amount),\n        currency: 'PHP',\n        // Other params...\n    })\n    .then(response =\u003e {\n        // Upon successful request, you will be redirected to the eWallet's checkout url.\n        console.log(response.data)\n        window.location.href =\n            response.data.actions.desktop_web_checkout_url\n    })\n    /// ...\n```\n\n#### Responding to eWallet Charge Webhook Event\n\nBefore your app can receive webhook callbacks from Xendit. Please make sure that you properly setup a webhook endpoint from your Xendit's dashboard under **eWallet Payment Status**:\n\nhttps://dashboard.xendit.co/settings/developers#webhooks\n\nThis is required for both development and production. For this purpose you can use tools like [Ngrok](https://ngrok.com) or [Expose](https://expose.dev) so your local project (`localhost`) can receive webhook callbacks from Xendit.\n\nBy default, Xendivel will listen to `xendit/webhook` URL for callbacks as defined in Xendivel's config file whenever you make an eWallet charge, refund, or void transactions. You have the option to change the default webhook URL if you prefer:\n\n`config/xendivel.php`\n\n```php\n'webhook_url' =\u003e '/xendit/webhook', // You can change this to whatever you like.\n```\n\nThen, after you published Xendivel's webhook event listeners from [here](#publish-config-and-assets),  you should register the events and listener to your event service provider located in `app\\Providers\\EventServiceProvider.php`:\n\n```php\nuse App\\Events\\eWalletEvents;\nuse App\\Listeners\\eWalletWebhookListener;\n\nprotected $listen = [\n    // ...\n\n    eWalletEvents::class =\u003e [\n        eWalletWebhookListener::class,\n    ],\n];\n```\n\nAfter this, you can now respond to the callback event from Xendit after a successful eWallet charge from the webhook listener located in `app/Listener/eWalletWebhookListener.php`:\n\n```php\npublic function handle(eWalletEvents $event)\n{\n    // You can inspect the returned data from the webhoook in your logs file\n    // storage/logs/laravel.log\n    logger('Webhook data received: ', $event-\u003ewebhook_data);\n\n    // if($event-\u003ewebhook_data['data']['status'] === 'SUCCEEDED') {\n    //     $invoice_data = [\n    //         // Invoice data...\n    //     ];\n\n    //     $email_invoice = new Xendivel();\n    //     $email_invoice-\u003eemailInvoiceTo('glenn@example.com', $invoice_data)\n    //         -\u003esend();\n    // }\n}\n```\n\nYou can now perform other tasks based on the payload of the callback such as interacting with your database, call other APIs, send an email, etc.\n\n\u003e [!IMPORTANT]\n\u003e Xendit will send a webhook event everytime you perform an eWallet charge, refund, or void transaction to the same webhook endpoint.\n\n#### Exclude Xendit's Webhook Callback from CSRF Protection\n\nYou should also make sure you allow Xendit's callback from your CSRF protection, so any webhook callback Xendit sends to your application will be accepted by your routes. You can exclude the routes by adding their URIs to the `$except` property of the `VerifyCsrfToken` middleware:\n\n```php\n\u003c?php\n\nnamespace App\\Http\\Middleware;\n\nuse Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken as Middleware;\n\nclass VerifyCsrfToken extends Middleware\n{\n  /**\n    * The URIs that should be excluded from CSRF verification.\n    *\n    * @var  array\n    */\n\n    protected  $except  = [\n        '/xendit/*',\n        'https://your-domain.com/xendit/*',\n    ];\n}\n```\n\n#### Get eWallet Charge\n\nFetch the details of an eWallet charge. The `Xendivel::getPayment` function accepts the **eWallet charge ID** as the first parameter, and the type of charge which is **ewallet** as the second parameter.\n\n`GET` Request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::get('/get-ewallet-charge', function (Request $request) {\n\t$response = Xendivel::getPayment('ewc_65cbfb33-a1ea-4c32-a6f3-6f8202de9d6e', 'ewallet')\n\t    -\u003egetResponse();\n\n\treturn $response;\n});\n```\n\nThe JSON response would look similar to this:\n\n```json\n{\n  \"id\": \"ewc_bb8c3po-c3po-r2d2-c3po-r2d2c3por2d2\",\n  \"business_id\": \"5f218745736e619164dc8608\",\n  \"reference_id\": \"test-reference-id\",\n  \"status\": \"PENDING\",\n  \"currency\": \"IDR\",\n  \"charge_amount\": 1000,\n  \"capture_amount\": 1000,\n  \"refunded_amount\": null,\n  \"checkout_method\": \"ONE_TIME_PAYMENT\",\n  \"channel_code\": \"ID_SHOPEEPAY\",\n  \"channel_properties\": {\n    \"success_redirect_url\": \"https://dashboard.xendit.co/register/1\"\n  },\n  \"actions\": {\n    \"desktop_web_checkout_url\": null,\n    \"mobile_web_checkout_url\": null,\n    \"mobile_deeplink_checkout_url\": \"https://deeplinkcheckout.this/\",\n    \"qr_checkout_string\": \"ID123XenditQRTest321DI\"\n  },\n  \"is_redirect_required\": true,\n  \"callback_url\": \"https://calling-back.com/xendit/shopeepay\",\n  \"created\": \"2017-07-21T17:32:28Z\",\n  \"updated\": \"2017-07-21T17:32:28Z\",\n  \"void_status\": null,\n  \"voided_at\": null,\n  \"capture_now\": true,\n  \"customer_id\": null,\n  \"payment_method_id\": null,\n  \"failure_code\": null,\n  \"basket\": null,\n  \"metadata\": {\n    \"branch_code\": \"tree_branch\"\n  }\n}\n```\n\n#### Void eWallet Charge\n\n`POST` Request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::post('/ewallet/void', function(Request $request) {\n    // Example eWallet charge ID: ewc_e743d499-baa1-49f1-96c0-cc810890739b\n    $response = Xendivel::void($request-\u003eewallet_charge_id)\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\nWith this Void API, you can nullify a successfully processed eWallet payment, ensuring that the entire original amount is refunded to the end user.\n\nVoiding an eWallet charge is defined as the cancellation of eWallet payments created within the same day and before the **cutoff time of 23:50:00 (UTC+07:00 for Indonesia eWallets/ UTC+08:00 for Philippines eWallets)**.\n\n-   Void API will only work for charges created via the `/ewallets/charges` API with `SUCCEEDED` status\n-   Void API will return `PENDING` `void_status` in API response upon execution. A follow-up webhook will be sent to your system's URL when void has been processed successfully.\n\n**To cancel eWallet payments after the aforementioned cutoff time, the [Refund API](#refunds) should be used.**\n\n### PDF Invoicing\n\n![Invoice Template](docs/image_assets/invoice.png)\n\nXendivel has the ability to generate professional and customizable PDF Invoices. You can preview the default invoice template by going to the route `/xendivel/invoice/template`.\n\n```\nhttps://your-domain.test/xendivel/invoice/template\n```\n\n\u003e [!Note]\n\u003e Remember to replace the `your-domain.test` with your domain.\n\nPDF invoices are generated using standard **Laravel Blade** templates and Xendivel will convert this to PDF invoice for you. Since invoices are just regular Blade templates, you can pass data to the template just like you would on a [Laravel Blade](https://laravel.com/docs/13.x/blade#displaying-data) file. The default invoice template uses plain, print-friendly CSS for reliable rendering with Browsershot and Chrome.\n\n#### Generate PDF Invoice\n\n```php\nuse GlennRaya\\Xendivel\\Invoice;\n\nRoute::get('/xendivel/invoice/generate', function () {\n    $invoice_data = [\n        'invoice_number' =\u003e 1000023,\n        'card_type' =\u003e 'VISA',\n        'masked_card_number' =\u003e '400000XXXXXX0002',\n        'merchant' =\u003e [\n            'name' =\u003e 'Xendivel LLC',\n            'address' =\u003e '152 Maple Avenue Greenfield, New Liberty, Arcadia USA 54331',\n            'phone' =\u003e '+63 971-444-1234',\n            'email' =\u003e 'xendivel@example.com',\n        ],\n        'customer' =\u003e [\n            'name' =\u003e 'Victoria Marini',\n            'address' =\u003e 'Alex Johnson, 4457 Pine Circle, Rivertown, Westhaven, 98765, Silverland',\n            'email' =\u003e 'victoria@example.com',\n            'phone' =\u003e '+63 909-098-654',\n        ],\n        'items' =\u003e [\n            ['item' =\u003e 'iPhone 15 Pro Max', 'price' =\u003e 1099, 'quantity' =\u003e 5],\n            ['item' =\u003e 'MacBook Pro 16\" M3 Max', 'price' =\u003e 2499, 'quantity' =\u003e 3],\n            ['item' =\u003e 'Apple Pro Display XDR', 'price' =\u003e 5999, 'quantity' =\u003e 2],\n            ['item' =\u003e 'Pro Stand', 'price' =\u003e 999, 'quantity' =\u003e 2],\n        ],\n        'tax_rate' =\u003e .12,\n        'tax_id' =\u003e '123-456-789',\n        'footer_note' =\u003e '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.',\n    ];\n\n    return Invoice::make($invoice_data)\n        -\u003esave();\n});\n```\n\nAs you can see, the `Invoice::make` function accepts an associative array that contains the information you want to appear on the invoice, typically coming from your database. By default, it will be stored in the `/storage/app/invoices` directory of your Laravel app. You can change the location where you want to save your invoices by modifying the `invoice_storage_path` option in your Xendivel config file.\n\n```\n'invoice_storage_path' =\u003e storage_path('/app/invoices/')\n```\n\n\u003e [!IMPORTANT]\n\u003e You should ensure the proper permission is set to the directory of your choice so Xendivel can store the invoice there.\n\n#### Download PDF Invoice\n\nYou can immediately download the invoice to your customer's local machine instead of storing it your Laravel app's storage directory by calling the `download()` function:\n\n```php\nuse GlennRaya\\Xendivel\\Invoice;\n\nRoute::get('/xendivel/invoice/download', function () {\n    $invoice_data = [\n        // Invoice data...\n    ];\n\n    return Invoice::make($invoice_data)\n\t    -\u003edownload();\n});\n```\n\n#### Invoice Paper Size\n\nBy default, Xendivel will generate PDF invoices in standard **A4** paper size. Xendivel passes the `paperSize()` value to Browsershot and also writes the matching dimensions to the PDF template's `@page size` rule.\n\nXendivel supports these invoice paper sizes:\n\n```\nLetter: 8.5in  x  11in\nLegal: 8.5in  x  14in\nA4: 8.27in  x  11.7in\n```\n\n#### Change Invoice Paper Size\n\nYou can change the invoice size by invoking the `paperSize()` function when generating or downloading an invoice and specify the name of the paper size as the parameter:\n\n```php\nuse GlennRaya\\Xendivel\\Invoice;\n\nRoute::get('/xendivel/invoice/download', function () {\n    $invoice_data = [\n        // Invoice data...\n    ];\n\n    return Invoice::make($invoice_data)\n        -\u003epaperSize('A4')\n        -\u003edownload();\n});\n```\n\nIn this example, we can modify the invoice's paper size by invoking the `paperSize('A4')` function and indicating the desired paper size as its parameter.\n\nIf the `paperSize()` value includes an orientation word, `orientation()` still wins. For example, `paperSize('A4 landscape')-\u003eorientation('portrait')` generates a portrait A4 invoice.\n\n#### Change Invoice Orientation\n\nYour sentence is already well-constructed, but here's a slightly refined version:\n\nYou can also modify the orientation of the invoice; by default, it's in `portrait`. You can change it to `landscape` using the `orientation()` function.\n\n```php\nuse GlennRaya\\Xendivel\\Invoice;\n\nRoute::get('/xendivel/invoice/download', function () {\n    $invoice_data = [\n        // Invoice data...\n    ];\n\n    return Invoice::make($invoice_data)\n        -\u003epaperSize('A4')\n        -\u003eorientation('landscape')\n        -\u003edownload();\n});\n```\n\n#### Invoice Filename\n\nWhenever Xendivel generates, downloads or email an invoice to your customers, Xendivel will assign a unique filename using UUID v4 and appending the `-invoice.pdf` at the end of the filename. Here's an example:\n\n```\nc7ff9fa5-b629-4fc9-8e61-bd203c91ca65-invoice.pdf\n```\n\nIf you want to customize the filenaming convention of Xendivel, you can easily do so by using the `fileName()` function of the `Invoice` class:\n\n```php\nuse GlennRaya\\Xendivel\\Invoice;\n\nRoute::get('/xendivel/invoice/download', function () {\n    $invoice_data = [\n        // Invoice data...\n    ];\n\n    return Invoice::make($invoice_data)\n        -\u003epaperSize('A4')\n        -\u003eorientation('landscape')\n        -\u003efileName('my-awesome-invoice-filename')\n        -\u003edownload();\n});\n```\n\nNow the generated invoice will have a filename that looks like:\n\n```\nmy-awesome-invoice-filename-invoice.pdf\n```\n\n#### Customizing PDF Invoice Template\n\nAs previously mentioned, the PDF invoice template is essentially a standard **Laravel Blade** component. It is a conventional HTML/PHP file styled with plain, print-friendly CSS. Browsershot renders with Chrome, so browser-supported CSS and image assets can be used as long as the browser runtime can access them.\n\nPublish the invoice template to your `views` directory:\n\n```bash\nphp artisan vendor:publish --tag=xendivel-invoice\n```\n\n\u003e [!NOTE]\n\u003e When you published Xendivel's assets from the **Publish Assets** section, your invoice template is already published in `resources/views/vendor/xendivel/invoice.blade.php`.\n\nThis command will publish the `invoice.blade.php` to your `resources/views/vendor/xendivel` directory. Upon inspecting the file, you will notice the `$invoice_data` variable. This variable contains the associative array that you passed to the view from previous examples.\n\nExample section from the invoice template:\n\n```php\n{{-- Other data... --}}\n\u003ctable class=\"invoice-items\"\u003e\n    \u003cthead\u003e\n        \u003ctr\u003e\n            \u003cth class=\"invoice-col-description\"\u003eDescription\u003c/th\u003e\n            \u003cth class=\"invoice-col-qty\"\u003eQty\u003c/th\u003e\n            \u003cth class=\"invoice-col-price\"\u003eUnit Price\u003c/th\u003e\n            \u003cth class=\"invoice-col-subtotal\"\u003eSubtotal\u003c/th\u003e\n        \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n        @php\n            $total_price = 0;\n        @endphp\n        @foreach ($invoice_data['items'] as $item)\n            @php\n                $total_price += $item['price'] * $item['quantity'];\n            @endphp\n            \u003ctr\u003e\n                \u003ctd class=\"invoice-col-description\"\u003e{{ $item['item']}}\u003c/td\u003e\n                \u003ctd class=\"invoice-col-qty\"\u003e{{ $item['quantity'] }}\u003c/td\u003e\n                \u003ctd class=\"invoice-col-price\"\u003e${{ number_format($item['price'], 2) }}\u003c/td\u003e\n                \u003ctd class=\"invoice-col-subtotal\"\u003e\n                    ${{ number_format($item['price'] * $item['quantity'], 2) }}\n                \u003c/td\u003e\n            \u003c/tr\u003e\n        @endforeach\n    \u003c/tbody\u003e\n\u003c/table\u003e\n{{-- Other data... --}}\n```\n\nSince this is just a regular HTML/Blade template, there's no limit to the customizations you can make. You can define your own plain CSS styles, modify the data being rendered, and add images to the template. Xendivel will automatically convert this template into a PDF file upon generation, downloading, or when sent via email attachment.\n\n#### Sending PDF Invoice As Email Attachment\n\nIt's a common practice that, following a purchase on an e-commerce website or app, customers receive an email detailing their transaction, accompanied by an attached invoice. Xendivel makes it easy to send an invoice to your customers after completing the purchase.\n\n#### Sending PDF Invoice For Card Payments\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::post('/checkout-email-invoice', function (Request $request) {\n    $invoice_data = [\n        'invoice_number' =\u003e 1000023,\n        'card_type' =\u003e 'VISA',\n        'masked_card_number' =\u003e '400000XXXXXX0002',\n        'merchant' =\u003e [\n            'name' =\u003e 'Stark Industries',\n            'address' =\u003e '152 Maple Avenue Greenfield, New Liberty, Arcadia USA 54331',\n            'phone' =\u003e '+63 971-444-1234',\n            'email' =\u003e 'xendivel@example.com',\n        ],\n        'customer' =\u003e [\n            'name' =\u003e 'Mr. Glenn Raya',\n            'address' =\u003e 'Alex Johnson, 4457 Pine Circle, Rivertown, Westhaven, 98765, Silverland',\n            'email' =\u003e 'victoria@example.com',\n            'phone' =\u003e '+63 909-098-654',\n        ],\n        'items' =\u003e [\n            ['item' =\u003e 'MacBook Pro 16\" M3 Max', 'price' =\u003e $request-\u003eamount, 'quantity' =\u003e 1],\n        ],\n        'tax_rate' =\u003e .12,\n        'tax_id' =\u003e '123-456-789',\n        'footer_note' =\u003e '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.',\n    ];\n\n    $payment = Xendivel::payWithCard($request)\n        -\u003eemailInvoiceTo('glenn@example.com', $invoice_data)\n        -\u003esend()\n        -\u003egetResponse();\n\n    return $payment;\n});\n```\n\nIn this example, the `emailInvoiceTo()` function accepts the email address where you want to send the invoice as the first parameter, and the `$invoice_data` that holds the details about the invoice as the second parameter. The `send()` function will instruct Xendivel to send the email.\n\n##### Email Subject and Message\n\n![Email Invoice](docs/image_assets/email-invoice.png)\n\nThe above image is an example of the email with the PDF invoice attached that Xendivel will send by default. You can customize the subject and the email message itself:\n\n##### Customize Subject\n\nTo change the default email's subject, you can use the `subject()` function:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::post('/checkout-email-invoice', function (Request $request) {\n    $invoice_data = [\n        // Invoice data...\n    ];\n\n    $payment = Xendivel::payWithCard($request)\n        -\u003eemailInvoiceTo('glenn@example.com', $invoice_data)\n        -\u003esubject('Thank you for your purchase!')\n        -\u003esend()\n        -\u003egetResponse();\n    });\n```\n\n##### Customize Message\nTo change the default email's message, you can use the `message()` function:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::post('/checkout-email-invoice', function (Request $request) {\n    $invoice_data = [\n        // Invoice data...\n    ];\n\n    $payment = Xendivel::payWithCard($request)\n        -\u003eemailInvoiceTo('glenn@example.com', $invoice_data)\n        -\u003esubject('Thank you for your purchase!')\n        -\u003emessage('We appreciate your business and look forward to serving you again. We have attached your invoice.')\n        -\u003esend()\n        -\u003egetResponse();\n});\n```\n\n#### Sending PDF Invoice For eWallet Payments\n\nWhen employing eWallet payments, the process of sending email invoices differs slightly. As your application must respond to a webhook callback for eWallet payments, it becomes necessary to incorporate the email invoice logic directly within the webhook listener.\n\nNavigate to the `App/Listeners/eWalletWebhookListener.php` file and locate the `handle()` method. Within this method, implement the email invoice logic to ensure seamless integration with eWallet payments.\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\npublic function handle(eWalletEvents $event)\n{\n    // You can inspect the returned data from the webhoook in your logs file\n    // storage/logs/laravel.log\n    logger('Webhook data received: ', $event-\u003ewebhook_data);\n\n    // $invoice_data = [\n        // Invoice data...\n    // ];\n\n    if($event-\u003ewebhook_data['data']['status'] === 'SUCCEEDED') {\n        $email_invoice = new Xendivel();\n        $email_invoice-\u003eemailInvoiceTo('glenn@example.com', $invoice_data)\n            -\u003esend();\n    }\n}\n```\n\nRemember, when initiating an eWallet payment charge request, you have the option to include a `metadata` property.\n\nExample:\n\n```javascript\naxios.post('/charge-ewallet', {\n    amount: 1200,\n    currency: 'PHP',\n    checkout_method: 'ONE_TIME_PAYMENT',\n    channel_code: 'PH_GCASH',\n    channel_properties: {\n        success_redirect_url: 'https://your-domain.test/xendivel/payment/success',\n        failure_redirect_url: 'https://your-domain.test/xendivel/payment/failed',\n    },\n\n    metadata: {\n        customer_id: 17,\n        name: 'Glenn Raya',\n        email: 'glenn@example.com'\n    }\n})\n```\n\nThis allows you to include supplementary information with the payment. Meaning you can include your customer's ID, email address, phone numbers, etc. Enabling you to leverage it later when processing the webhook data.\n\nXendit API Reference:\nhttps://developers.xendit.co/api-reference/#create-ewallet-charge\n\n### Queue Invoice Email\n\nXendivel has the ability to queue email jobs for background processing, enhancing the responsiveness of your Laravel app by handling email tasks seamlessly in the background.\n\nAll you need to do is simple set the `queue_email` option from your `xendivel.php` config file to `true`.\n\n```php\n'queue_email' =\u003e true,\n```\n\nOf course, you need to make sure that you properly setup your Laravel queue driver and there's a queue worker running:\n\nhttps://laravel.com/docs/13.x/queues#main-content\n\n#### Run Queue Worker\n\n```ini\nphp artisan queue:work\n```\n\n\u003e [!IMPORTANT]\n\u003e Always make sure you run a **queue worker** when you configure Xendivel to send the email jobs to queue, otherwise no email will be sent.\n\n\u003e [!IMPORTANT]\n\u003e Also remember, whenever you change the email templates that comes with Xendivel, Please be sure that you restart your queue workers so it could use your newly updated email templates.\n\n### Refunds\n\nXendivel supports the refund API for both cards and eWallet payments and also has the ability to notify your customers about successful refunds.\n\n#### Refund for Card Payments\n\nThe make refunds for payments via credit or debit cards, first you must get the charge transaction using the `getPayment()` method. The first parameter is the `id` of the charge, and the second parameter should be `card`.\n\nThen, the `refund()` method's parameter value is the amount to be refunded. And of course, the `getResponse()` method is used to return the response from the API.\n\n`POST` request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\nuse Illuminate\\Http\\Request;\n\nRoute::post('/refund', function (Request $request) {\n\t// Example charge id: 6593a0fb82742f0056f779fd\n\n    $response = Xendivel::getPayment($request-\u003echarge_id, 'card')\n        -\u003erefund(3500)\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\nThe response from this call should looks like this:\n\n```json\n{\n    \"credit_card_charge_id\": \"656eb63c23f3c20015e2f4eb\",\n    \"amount\": 5198,\n    \"external_id\": \"375b897a-8b75-4b94-a802-29a60febf589\",\n    \"status\": \"REQUESTED\",\n    \"merchant_reference_code\": \"656eb63123f3c20015e2f4e6\",\n    \"uuid\": \"70941a1a-a2d4-4547-bb30-e3d4c163cf04\",\n    \"currency\": \"PHP\",\n    \"client_type\": \"API_GATEWAY\",\n    \"created\": \"2023-12-05T05:50:38.701Z\",\n    \"updated\": \"2023-12-05T05:50:38.701Z\",\n    \"id\": \"656eba2eedab5300169c2b19\",\n    \"fee_refund_amount\": 0,\n    \"user_id\": \"6551f678273a62fd8d86e25a\"\n}\n```\n\nYou can always check your Xendit's dashboard for all transactions made: https://dashboard.xendit.co/home\n\n#### Refund for eWallet Payments\n\nRequesting refunds for eWallet payments is almost the same as for the card refunds API. The only difference is the eWallet charge ID of course and the type of the refund which is `ewallet`\n\n`POST` request\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\nuse Illuminate\\Http\\Request;\n\nRoute::post('/refund', function (Request $request) {\n\t// Example charge id: ewc_b5baef87-d7b5-4d5c-803b-b31e80529147\n\n    $response = Xendivel::getPayment($request-\u003echarge_id, 'ewallet')\n+       -\u003erefund(3500)\n+       -\u003egetResponse();\n\n    return $response;\n});\n```\n\n#### Get Refund Details\n\nWhether the refunds are successful or not, the details of the transactions are still recorded on your Xendit's admin account.\n\n##### Get eWallet Refund Details\n\nGet the status and details of a specific eWallet refund by its charge and refund ID:\n\n`GET` request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::get('/get-ewallet-refund', function () {\n    $response = Xendivel::getEwalletRefund('ewc_65cbfb33-a1ea-4c32-a6f3-6f5202dx9d6e', 'ewr_a96f9a27-8838-43bf-88f0-c0ade0aeeee3')\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\nTypically, the charge and refund ID should be stored to your database. This can be done when you received the webhook callback from Xendit.\n\n#### List All eWallet Refunds\n\nGet the details of all eWallet refunds associated with a single eWallet charge transaction by the charge ID by using the `getListOfEwalletRefunds()` method and passing the eWallet `charge_id`:\n\n`GET` request:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::get('/ewallet-refund-list', function () {\n    $response = Xendivel::getListOfEwalletRefunds('ewc_65cbfb33-a1ea-4c32-a6f3-9f8201de9d6a')\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\nThis will output a JSON response with a collection of refund transactions and status of each refund:\n\n```json\n{\n    \"data\": [\n        {\n            \"id\": \"ewr_a96f9a27-8838-43bf-88f0-c0ade0aeeee3\",\n            \"charge_id\": \"ewc_65cbfb33-a1ea-4c32-a6f3-6f8202de9d6e\",\n            \"status\": \"SUCCEEDED\",\n            \"currency\": \"PHP\",\n            \"channel_code\": \"PH_GCASH\",\n            \"capture_amount\": 1000,\n            \"refund_amount\": 1000,\n            \"failure_code\": null,\n            \"reason\": \"OTHERS\",\n            \"refund_amount_to_payer\": null,\n            \"payer_captured_amount\": null,\n            \"payer_captured_currency\": null,\n            \"created\": \"2023-12-28T07:47:40.24517Z\",\n            \"updated\": \"2023-12-28T07:47:45.253443Z\"\n        }\n    ]\n}\n```\n\n#### Email Refund Confirmation\n\n##### Card Refund Email Notification\n\nXendivel has the capability to automatically send an email to your customers following a refund request. This is achieved by invoking the `refund()` function first, and subsequently triggering the `emailRefundConfirmationTo()` function.\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::get('/refund', function () {\n    $response = Xendivel::getPayment('6595d0fg82741f0011f778fd', 'card')\n        -\u003erefund(3500)\n        -\u003eemailRefundConfirmationTo('glenn@example.com')\n        -\u003esend()\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\nYou can also customize the `subject` and `message`:\n\n```php\nuse GlennRaya\\Xendivel\\Xendivel;\n\nRoute::get('/refund', function () {\n    $response = Xendivel::getPayment('6595d0fg82741f0011f778fd', 'card')\n        -\u003erefund(3500)\n        -\u003eemailRefundConfirmationTo('glenn@example.com')\n        -\u003esubject('Your refund is on the way!')\n        -\u003emessage('We have successfully processed your refund! It should reflect on your account within 3 banking days.')\n        -\u003esend()\n        -\u003egetResponse();\n\n    return $response;\n});\n```\n\n### Webhook\n\n#### Listen to Webhook Event\n\nAs of the moment only the eWallet **charges**, **refund**, and **void** transactions can receive a webhook callback event from Xendit and are discussed in these sections [Responding to eWallet Charge Webhook Event](#responding-to-ewallet-charge-webhook-event)\n\n\u003e [!Note]\n\u003e When a webhook callback failed to reach your server, Xendit will retry sending the webhook again. Please refer to Xendit's docs regarding \"Delivery attempts and Retries\": https://developers.xendit.co/api-reference/#delivery-attempts-and-retries\n\n#### Webhook Verification\n\nXendivel handles webhook verification for you automatically everytime Xendit sends a callback to your webhook endpoints. All you need to do is simply include your accounts unique webhook verification token on your `.env` file:\n\n```ini\nXENDIT_WEBHOOK_VERIFICATION_TOKEN=your-webhook-verification-token\n```\n\nYou can obtain your webhook verification token from your dashboard under **Webhooks** section.\n\nhttps://dashboard.xendit.co/settings/developers#webhooks\n\nIf the value of your webhook verification token doesn't match with the one Xendit attached in the request header of the webhook named `x-callback-token`, Xendivel will reject the webhook callback and throw a `403` access denied http exception. This means that the request doesn't originate from Xendit.\n\nIf you don't want to verify if the webhook callback is from Xendit, you can disable this feature by setting the `verify_webhook_signature` to `false` in Xendivel's config file:\n\nConfig file `config/xendivel.php`\n\n```php\n'verify_webhook_signature' =\u003e false,\n```\n\n\u003e[!IMPORTANT]\n\u003eVerifying webhook origin is optional but it is **HIGHLY RECOMMENDED** for security reasons. This is to ensure that the webhook callback event legitimately comes from Xendit and not from third-parties or illegitimate services.\n\n## Deploying to Production\n\nWhen it's time to deploy your Laravel app, setting the `APP_ENV` from your `.env` file to `production` will disable the following Xendivel routes:\n\n- `/xendivel/invoice/template` — The example invoice template.\n- `/xendivel/checkout/blade` — The example checkout page.\n- `/xendivel/invoice/generate` — Generate example PDF invoice.\n- `/xendivel/invoice/download` — Download example PDF invoice.\n\nThese built-in Xendivel routes are meant for development purposes only. You should publish the checkout and invoice template to your views directory to use them on your Laravel app.\n\n### Xendit Production Keys\n\nWhen deploying to production, you should switch your Xendit's `secret_key`, `public_key`, and `webhook_verification_token` to production keys.\n\n```ini\nXENDIT_SECRET_KEY=your-production-secret-key\nXENDIT_PUBLIC_KEY=your-production-public-key\nXENDIT_WEBHOOK_VERIFICATION_TOKEN=your-production-webhook-verification-token\n```\n\n## Tests\n\nXendivel uses a package-local Pest and Orchestra Testbench setup so the package can be tested directly from its own repository.\n\nRun the package test suite from `packages/glennraya/xendivel`:\n\n```bash\ncomposer validate --strict\ncomposer test\n```\n\nTo run the focused PDF tests only:\n\n```bash\n./vendor/bin/pest --configuration phpunit.xml.dist tests/Unit/InvoicePdfTest.php\n```\n\nXendivel now supports Laravel 13 officially while still supporting Laravel 10 through 12. Laravel 13 installability can be validated with Composer dry-runs in a temporary consumer project using a path repository for `glennraya/xendivel`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglennraya%2Fxendivel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglennraya%2Fxendivel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglennraya%2Fxendivel/lists"}