PHPackages                             izzudin96/billplz-laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. izzudin96/billplz-laravel

ActiveLibrary[Payment Processing](/categories/payments)

izzudin96/billplz-laravel
=========================

Lightweight Billplz client package for modern Laravel versions.

v2.1.1(4w ago)0118↓74.6%MITPHPPHP ^8.2CI passing

Since May 29Pushed 4w agoCompare

[ Source](https://github.com/izzudin96/billplz-laravel)[ Packagist](https://packagist.org/packages/izzudin96/billplz-laravel)[ RSS](/packages/izzudin96-billplz-laravel/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (4)Dependencies (8)Versions (6)Used By (0)

izzudin96/billplz-laravel
=========================

[](#izzudin96billplz-laravel)

A lightweight Billplz package for Laravel 11/12/13.

It returns typed DTOs for bills, redirects, and webhooks so developers can use property access and IDE autocomplete instead of fragile array keys.

It provides a small Billplz client for common payment flow tasks:

- create bills
- fetch bill status
- verify callback/webhook signatures (strict mode)
- parse callback/webhook payloads (best-effort mode)
- use DTO return objects with property access
- configurable HTTP timeout/retry behavior
- safe input/config guards with clear exceptions

Features
--------

[](#features)

- Create bill
- Get bill
- Verify redirect signature
- Verify webhook signature
- Best-effort redirect parsing
- Best-effort webhook parsing
- DTO return objects for autocomplete-friendly access

When to use which method
------------------------

[](#when-to-use-which-method)

- `verifyRedirect()` and `verifyWebhook()`: Use when you want hard security checks and prefer exceptions on invalid signatures. Both return typed payload objects.
- `parseRedirect()`: Use when redirect/callback is for UX only and webhook is your source of truth. The returned DTO exposes `signature_valid`.
- `parseWebhook()`: Use when you want null-on-failure behavior instead of exceptions. The returned DTO exposes `signature_valid`.

Installation
------------

[](#installation)

```
composer require izzudin96/billplz-laravel
```

Configuration
-------------

[](#configuration)

Publish config:

```
php artisan vendor:publish --tag=billplz-config
```

Or set env variables directly:

```
BILLPLZ_API_KEY=
BILLPLZ_X_SIGNATURE=
BILLPLZ_COLLECTION_ID=
BILLPLZ_VERSION=v3
BILLPLZ_SANDBOX=false
BILLPLZ_TIMEOUT_SECONDS=10
BILLPLZ_RETRY_TIMES=1
BILLPLZ_RETRY_SLEEP_MS=200
BILLPLZ_USER_AGENT=billplz-laravel-client
```

Config supports both `x-signature` and `x_signature` keys for compatibility.

Optional `config/services.php` style usage is also supported:

```
return [
    'billplz' => [
        'key' => env('BILLPLZ_API_KEY'),
        'x_signature' => env('BILLPLZ_X_SIGNATURE'),
        'collection_id' => env('BILLPLZ_COLLECTION_ID'),
        'sandbox' => env('BILLPLZ_SANDBOX', false),
        'version' => env('BILLPLZ_VERSION', 'v3'),
        'timeout_seconds' => env('BILLPLZ_TIMEOUT_SECONDS', 10),
        'retry_times' => env('BILLPLZ_RETRY_TIMES', 1),
        'retry_sleep_ms' => env('BILLPLZ_RETRY_SLEEP_MS', 200),
        'user_agent' => env('BILLPLZ_USER_AGENT', 'billplz-laravel-client'),
    ],
];
```

### HTTP behavior defaults

[](#http-behavior-defaults)

- Timeout: 10 seconds
- Retries: 1
- Retry backoff: 200ms

This applies to bill create/get requests.

Usage
-----

[](#usage)

### How BillplzClient is resolved in Laravel

[](#how-billplzclient-is-resolved-in-laravel)

`BillplzClient::class` is only a class name string. To call client methods, you need an instance resolved by Laravel's service container.

Preferred patterns:

1. Method injection (clean and explicit)

```
use Izzudin96\Billplz\BillplzClient;

public function show(string $billId, BillplzClient $billplz)
{
    $bill = $billplz->getBill($billId);
}
```

1. Constructor injection (good when used in multiple methods)

```
use Izzudin96\Billplz\BillplzClient;

class PaymentController extends Controller
{
    public function __construct(private BillplzClient $billplz)
    {
    }

    public function show(string $billId)
    {
        $bill = $this->billplz->getBill($billId);
    }
}
```

1. Facade usage (shortest call style)

```
use Izzudin96\Billplz\Facades\Billplz;

$bill = Billplz::getBill($billId);
```

1. app helper (works, but usually less preferred than DI)

```
use Izzudin96\Billplz\BillplzClient;

$bill = app(BillplzClient::class)->getBill($billId);
```

Why DI/facade is friendlier:

- Better readability in controllers/services
- Easier testing and mocking
- No repeated container lookup calls

### 1) Create a bill

[](#1-create-a-bill)

```
use Izzudin96\Billplz\BillplzClient;

$bill = app(BillplzClient::class)->createBill(
    email: 'user@example.com',
    mobile: '60123456789',
    name: 'User Name',
    amountCents: 25900, // RM259.00 in cents/sen
    callbackUrl: route('payments.billplz.webhook'),
    description: 'Booking BK-10021',
    optional: [
        'redirect_url' => route('payments.billplz.callback'),
        'reference_1_label' => 'Booking Ref',
        'reference_1' => 'BK-10021',
        'reference_2_label' => 'Customer ID',
        'reference_2' => 'CUS-890',
        // Additional Billplz-supported fields can be passed through here.
        // Required fields from method params always take precedence.
    ],
);

// Save for reconciliation and redirect user to Billplz page
$billId = $bill->id;
$paymentUrl = $bill->url;
```

### 2) Get bill status

[](#2-get-bill-status)

```
use Izzudin96\Billplz\BillplzClient;

$bill = app(BillplzClient::class)->getBill($billId);

// Examples of useful fields from Billplz response:
// $bill->paid
// $bill->paid_at
// $bill->state
```

### 3) Strict signature verification

[](#3-strict-signature-verification)

Use this when you want invalid signature payloads to fail immediately.

```
use Izzudin96\Billplz\BillplzClient;
use Izzudin96\Billplz\Exceptions\FailedSignatureVerification;

try {
    $redirect = app(BillplzClient::class)->verifyRedirect(request()->query());
    $webhook = app(BillplzClient::class)->verifyWebhook(request()->all());
} catch (FailedSignatureVerification $e) {
    report($e);
    abort(403, 'Invalid Billplz signature');
}
```

### 4) Best-effort redirect (recommended for UX callback)

[](#4-best-effort-redirect-recommended-for-ux-callback)

Use this when callback/redirect is only for showing payment result to user. Process fulfillment from webhook instead.

```
use Izzudin96\Billplz\BillplzClient;

$redirect = app(BillplzClient::class)->parseRedirect(request()->query());

if ($redirect === null) {
    return redirect()->route('payments.failed')
        ->with('message', 'Payment information is incomplete.');
}

// signature_valid can be true, false, or null (when signature key not configured)
$isPaid = (bool) $redirect->paid;

return redirect()->route('payments.result')->with([
    'bill_id' => $redirect->id,
    'paid' => $isPaid,
    'signature_valid' => $redirect->signature_valid,
]);
```

### 5) Best-effort webhook parser

[](#5-best-effort-webhook-parser)

Use this when you prefer null checks over exception handling.

```
use Izzudin96\Billplz\BillplzClient;

$payload = app(BillplzClient::class)->parseWebhook(request()->all());

if ($payload === null) {
    return response()->json(['message' => 'Invalid payload'], 403);
}

if ($payload->paid === true) {
    // Mark order/booking as paid
}

return response()->json(['ok' => true]);
```

End-to-end controller example
-----------------------------

[](#end-to-end-controller-example)

```
