Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xendivel Laravel Package

glennraya/xendivel

Xendivel is a Laravel wrapper for Xendit that simplifies accepting payments and sending payouts. It offers a fluent API for invoices, virtual accounts, e-wallets, retail outlets, QR codes, disbursements, and webhook handling, with straightforward configuration and Laravel-friendly integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Xendivel wraps Xendit's payment APIs for Laravel. As of v3.2.0, QR Code payments are a first-class payment type alongside card, e-wallet, and OTC — built on Xendit's Payments API (POST /payment_requests), which is enabled by default on your account (no channel activation needed).

Minimal setup after upgrading:

  1. Republish config and assets to pull in the new QR keys, event/listener stubs, and updated checkout templates:

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

    Review the diff first — --force overwrites any local edits to config or templates.

  2. Register the webhook listener in app/Providers/EventServiceProvider.php:

    QrPaymentEvents::class => [QrPaymentWebhookListener::class],
    
  3. Add the dashboard webhook in Xendit under the Payment callback type (payment.succeeded), pointing at qr_webhook_url (default /xendit/qr/webhook). The default /xendit/* CSRF exclusion already covers this path.

  4. First use case — create a dynamic QR for a fixed amount and render it:

    $request = Xendivel::createQrCode([
        'amount' => 150.00,
        'type'   => 'DYNAMIC', // ONE_TIME_USE
    ]);
    
    $qrString = $request['payment_method']['qr_code']['channel_properties']['qr_string'];
    

From there, getQrCode($id) polls status (PENDING → SUCCEEDED), and QrPaymentWebhookListener handles asynchronous confirmation.

Look first at the split test files — XendivelQrCodePaymentsTest is the fastest reference for exact request shapes, overrides, and validation rules.

Implementation Patterns

Dynamic vs static QR. DYNAMIC maps to ONE_TIME_USE (fixed amount baked in); STATIC maps to MULTIPLE_USE (payer enters the amount). Choose based on whether the amount is known at creation time — dynamic for checkout totals, static for reusable point-of-sale / donation-style codes.

Webhook-first, poll as fallback. Treat QrPaymentWebhookListener as the source of truth for payment confirmation (data.status === 'SUCCEEDED') and use getQrCode($id) only for explicit "check status now" UI actions or reconciliation jobs. Polling every few seconds in a request cycle is a good way to get rate-limited.

Per-request overrides. qr_channel_code (default QRPH) and qr_currency (default PHP) are config defaults but are overridable per request — useful in multi-region apps or when testing against non-default channels:

Xendivel::createQrCode([
    'amount'   => 99.00,
    'type'     => 'DYNAMIC',
    'currency' => 'IDR',
    'channel'  => 'QRIS',
]);

Test-mode simulation. simulateQrPayment($id, $amount) flips a QR to paid without a real bank app. It accepts either the QR reference or a pr-… payment request id (auto-resolved) — so in tests you can chain createQrCode() straight into simulateQrPayment() using whichever identifier you already have.

Checkout integration. The bundled Blade/JSX/TSX templates now have three isolated tabs (card / e-wallet / QR) that no longer leak fields across tabs. If you've built a custom checkout UI, mirror that separation — render QR generation lazily when the tab is opened, not on page load, so you don't create abandoned payment requests for users who never pick QR.

Listener wiring. The publishable listener stub gives you the happy path; extend it rather than replacing it, and dispatch your own domain event (e.g. OrderPaid) from inside it so business logic stays decoupled from the Xendit payload shape.

Gotchas and Tips

  • The qr_string path is deep. It lives at payment_method.qr_code.channel_properties.qr_string. Use Arr::get() / null-safe access instead of chained array indexing — a static QR or a failed create can leave intermediate keys missing and throw an undefined-index error.
  • Type naming is inverted from intuition. DYNAMIC = one-time-use / fixed amount; STATIC = multi-use / payer-entered amount. Get this backwards and your static code silently requires an amount it shouldn't.
  • simulateQrPayment is test-mode only. Guard it behind app()->isLocal() / a config check — never leave a route that can flip real payments to SUCCEEDED reachable in production.
  • The webhook won't fire events without the listener registered. The route and the CSRF exclusion work out of the box, but QrPaymentEvents is only dispatched if you add the mapping to EventServiceProvider. Silent no-op is the failure mode — check the registration first when debugging "webhook received but nothing happened."
  • Dashboard callback type matters. Register the webhook under Payment (payment.succeeded), not a legacy QR-specific callback type — the listener expects the unified Payments payload (data.status).
  • --force republish overwrites local edits. If you've customised the config or checkout templates, diff before forcing. The React templates in particular lose your publishable key on republish — re-add it, then npm run build.
  • Handle non-terminal statuses. status is PENDING → SUCCEEDED, but expired/failed states exist. Don't treat "not yet SUCCEEDED" as success in a polling loop — bound the loop and surface expiry.
  • No obvious idempotency key in the new API surface. If you retry createQrCode on timeout, you may create duplicate payment requests. Generate your own order reference and reconcile against getQrCode before retrying.
  • Config quirks. qr_channel_code and qr_currency default to QRPH / PHP — non-PHP deployments must set these or override per request, otherwise you'll create valid-looking QR codes for the wrong market.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment