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

Billplz Laravel Laravel Package

jomweb/billplz-laravel

A Laravel integration for the Billplz payment gateway that simplifies creating bills, handling redirects and webhooks, and managing Billplz payment flows in PHP/Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish config:

composer require jomweb/billplz-laravel
php artisan vendor:publish --provider="JomWeb\Billplz\BillplzServiceProvider"

This drops config/billplz.php (API key, collection id, x_signature, sandbox toggle). Laravel auto-discovers the service provider and the Billplz facade — check the README for the exact namespace/alias your version uses.

Set your env vars before touching code:

BILLPLZ_API_KEY=your_api_key
BILLPLZ_COLLECTION_ID=inbox_collection_id
BILLPLZ_X_SIGNATURE=your_webhook_signature_key
BILLPLZ_SANDBOX=true

Get sandbox keys from Billplz staging (separate from production), and create a collection in the dashboard (or via createCollection) — every bill must belong to one.

Look here first, in order: the published config file → the README's two or three code samples → vendor/jomweb/billplz-laravel/src for the facade methods → the upstream jomweb/billplz SDK for anything the wrapper doesn't expose.

First use case — create a bill for an order:

use JomWeb\Billplz\Facades\Billplz;

$bill = Billplz::createBill([
    'collection_id' => config('billplz.collection_id'),
    'email'         => $order->email,
    'name'          => $order->customer_name,
    'amount'        => $order->total * 100, // cents!
    'description'   => "Order #{$order->id}",
    'callback_url'  => route('billplz.callback'),
    'redirect_url'  => route('billplz.redirect'),
    'reference_1'   => $order->id,
]);

$order->update(['billplz_id' => $bill->id]);
return redirect($bill->url); // hosted payment page

Implementation Patterns

Two-route skeleton. Create two public routes: a callback (server-to-server webhook from Billplz) and a redirect (where the user lands after paying). They fire independently and in no guaranteed order — never treat one as the source of truth for the other.

Webhook handler — verify first, then act.

public function callback(Request $request)
{
    // 1. Recompute HMAC and compare with $request->x_signature
    //    (see Gotchas — field order matters)
    // 2. Locate order via $request->reference_1 (or your bill id)
    // 3. Idempotency guard: if already paid, return 200
    // 4. Mark paid, fire your own event/job
    return response('OK', 200);
}

Exclude this route from CSRF (VerifyCsrfToken::$except) or put it in routes/api.php with the api middleware group.

Redirect handler — don't trust query strings. Billplz appends ?billplz[id]=...&billplz[paid]=... to the redirect. Use the id to call Billplz::getBill($id) and read the authoritative status server-side before showing "Thank you."

Wrap it in a service class. Don't call the facade from controllers directly. A BillplzPaymentService with createBill(Order $order), handleCallback(array $payload), syncStatus(Order $order) keeps your test surface small and lets you swap to Http::fake() later.

Reconciliation job. Webhooks get lost. Schedule a job that pulls pending orders older than N minutes and calls Billplz::getBill($order->billplz_id), updating status. This is your safety net — build it before launch, not after.

Testing pattern. Feature tests should POST a signed fake webhook to your callback route (generate the signature in the test with the same helper) and assert the order flips to paid. For the redirect path, mock the facade or Http::fake() the GET /bills/{id} response. Use sandbox credentials only; Billplz staging has its own dashboard.

Gotchas and Tips

Amounts are in cents (sen). Every bill amount is an integer number of cents. $order->total * 100. Round before multiplying or you'll send 1499.9999.

Signature verification is the #1 source of bugs. Billplz's x_signature is an HMAC-SHA256 of a specific concatenated string of fields (billplzid, billplzpaid_at, billplzpaid, billplzamount, billplztransaction_id — check current docs for exact order and separators). Use the package's helper if it exposes one; otherwise compute it in one place, test it against a real sandbox webhook, and never hand-roll it twice. A mismatch should return 403, not silently pass.

Idempotency. Billplz will retry the callback if you don't return 2xx quickly. Your handler must be safe to run twice: check current order status before marking paid, and return 200 immediately on the duplicate path. Don't do slow work (emails, PDF generation) synchronously — dispatch a job.

paid_at is absent when unpaid. Your handler will receive paid=false events for failed/expired bills. Branch on $request->paid === 'true' (string, not bool) before reading paid_at or transaction_id.

Local development is awkward. Billplz must reach your callback_url over the public internet. Use ngrok http 8000 (or Laravel Herd/Valet share), put the tunnel URL in APP_URL, and keep sandbox mode on. A callback_url of http://localhost will silently never fire and you'll debug the wrong thing.

Sandbox vs production. The sandbox flag switches the base URL to Billplz staging. Flip it in env, then always php artisan config:cache — stale config from a previous config:cache is a classic "why is it still hitting staging" bug.

Collection must be active. Creating a bill against a deleted or inactive collection returns a 4xx you'll see as a generic exception. Create collections via the dashboard for prod and via createCollection in tests, and store the id in env.

Exceptions are raw. The wrapper throws on non-2xx API responses rather than returning a result object. Wrap createBill in try/catch, log the response body, and show the user a friendly failure — never let a Billplz 500 become a 500 in your app after the order is already persisted.

Store more than the id. Persist billplz_id, the returned url, and a status column on your order. The URL is safe to expose to the browser; the API key and x_signature are not — never include them in a JS payload or an Inertia prop.

The wrapper is thin. If you need an endpoint it doesn't expose (e.g. payment methods, refunds), drop to the underlying jomweb/billplz SDK or call the API with Laravel's Http client using basic auth (api_key as username, empty password). Don't fight the facade.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator