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.
Xendivel is a Laravel package wrapping the Xendit payment gateway; v3.2.0 extends it from card/e-wallet/OTC into QR-based payments. Architecturally this is a thin, opinionated SDK-plus-scaffolding package: facades (Xendivel::createQrCode) over Xendit REST endpoints, publishable config, publishable event/listener stubs, and bundled checkout demo templates. That is a good fit for a Laravel monolith or a small service where payments are a supporting capability rather than a core domain — the package does not attempt to own payment state, which keeps it out of our domain model but also means reconciliation is our problem.
Integration feasibility is high. The critical detail is that QR rides on Xendit's Payments API (POST /payment_requests), active by default with no separate channel activation — no account-manager round trip, no sandbox enablement ticket. Config keys (qr_webhook_url, qr_channel_code defaulting to QRPH, qr_currency defaulting to PHP) are sensible defaults for a Philippine market and are per-request overridable, which matters if we ever operate multi-currency or route through a non-QRPH channel. Auto-resolution of pr-… payment request IDs vs. QR strings in simulateQrPayment is a small ergonomic win that removes a class of test-harness bugs.
Technical risk concentrates in four areas. First, STATIC QR maps to MULTIPLE_USE — the payer supplies the amount, so the amount and payer identity are unknown at creation time. Any downstream order-matching must reconcile on webhook payload, not on our own initiation record; that is a genuine design constraint, not a bug, but it will surface late if the team assumes DYNAMIC semantics everywhere. Second, asynchronous confirmation via webhook makes the webhook the source of truth, so signature verification, idempotency on replay, and ordering are load-bearing. The release notes mention a webhook auth-guard test but do not describe signature verification of the Xendit callback token; I would want that confirmed before go-live. Third, the upgrade command uses --force on the xendivel tag, which will overwrite any locally customized config, templates, or previously published event/listener stubs. Fourth, the checkout templates were restructured for the third tab ("fields no longer leak across tabs"), which is an improvement but will conflict with any team that has forked those templates.
Key questions: (1) Does the shipped listener verify the Xendit callback token, or only rely on the CSRF exclusion? (2) Is getQrCode polling intended as a production fallback or test-only convenience — and what are Xendit's rate limits on pr-… polling? (3) How long are QR payment requests valid, and is expiry surfaced in the response or only via status? (4) For STATIC QR, does the webhook payload carry enough payer/amount detail for reconciliation, and is there a duplicate-suppression key? (5) What is the supported-version and PHP/Laravel matrix for this release?
Stack fit is straightforward: standard Laravel service provider, facade, config file, publishable assets, and Blade/JSX/TSX templates. No queue, cache, or database requirements are introduced — the package remains stateless, which means it will slot into an existing Laravel app without schema migrations or infrastructure change. That is the single biggest reason to accept it.
Migration path: this is additive, not a rewrite. Existing card and e-wallet integrations are untouched by the API surface; the only breaking-adjacent item is the template restructure. I would sequence as: (1) take the dependency bump in a branch and run the full suite to confirm the OTC/e-wallet/card tests still pass after the per-type test file split; (2) republish config into a diff-reviewed commit rather than blind --force — diff the published config against our committed copy, merge the three new QR keys by hand, and explicitly confirm nothing else in our config was clobbered; (3) publish event/listener stubs into app/Events and app/Listeners (non-destructive) and register the mapping in EventServiceProvider; (4) only then wire the Xendit dashboard webhook for payment.succeeded to /xendit/qr/webhook; (5) finally bring up the demo route in staging and use simulateQrPayment to exercise the PENDING → SUCCEEDED transition end-to-end.
Compatibility notes: the /xendit/* CSRF exclusion already covers the new path, so middleware work is nil — but that also means the security posture rests entirely on the listener. qr_channel_code/qr_currency defaults mean a naive install is PHP/QRPH only; confirm that is correct before shipping to a non-PH market. For teams on the React templates, budget a frontend task: npm run build plus re-entering the publishable key, which is easy to forget and produces a silent, confusing failure in staging.
Recommendation: adopt behind a feature flag for one release cycle, with QR enabled in test mode only, until the webhook verification path is confirmed.
Maintenance load is modest but not zero. The package's velocity (v3.2.0 already) suggests frequent minor releases; each one that touches publishable config or templates will re-trigger the diff-and-merge ritual. I would assign ownership of "the published config is ours, not theirs" to one engineer and treat vendor:publish --force as a forbidden command in the runbook.
Support surface: the demo templates are a double-edged gift. They make the first integration fast and generate strong documentation value, but customers and internal stakeholders will see the demo and expect that UX in production, and the demo's QR rendering/polling loop is not production-hardened. Expect questions about why polling exists at all when webhooks are the real confirmation path.
Scaling: the package itself does not scale-limit us — it is stateless and defers to Xendit. The real limits are Xendit's API rate limits (relevant if getQrCode polling is used at checkout volume), webhook delivery throughput on our ingress, and the fact that webhook handling is synchronous in a listener by default. If a burst of payment.succeeded callbacks arrives, the listener runs in the request lifecycle; that is acceptable at low volume and a liability at high volume. I would plan to queue the listener body from day one rather than retrofit it.
Failure modes to instrument: (a) webhook never arrives — need a reconciliation job that polls open pr-… requests on a schedule and closes the gap; (b) duplicate/replayed webhooks — need idempotency keyed on the payment request id; (c) --force republish reverting config in a deploy; (d) STATIC QR payments arriving with amounts that match no open order; (e) CSRF exclusion scope quietly widening beyond intent. Each of these deserves an alert, and (a) and (b) deserve tests we write ourselves — the package's own tests validate the happy path, not our reconciliation.
Ramp-up is short: a Laravel-fluent engineer should be productive in a day using the demo route and the new per-type test files as living documentation. The split into XendivelQrCodePaymentsTest et al. is a genuine contributor-experience improvement and makes the codebase easier to reason about than the former monolith. Budget half a day of that ramp-up specifically on the async/webhook model and on the DYNAMIC-vs-STATIC distinction, since those are the two concepts most likely to be misapplied.
Overall: recommended to adopt for QR, gated on webhook verification confirmation and a controlled config-merge process.
How can I help you explore Laravel packages today?