PHPackages                             hadimazalan/ibayaq-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. hadimazalan/ibayaq-laravel

ActiveLibrary[Payment Processing](/categories/payments)

hadimazalan/ibayaq-laravel
==========================

Laravel SDK and webhook integration for the iBayaq Payment Gateway (cart\_v3 API).

v1.0.0(yesterday)00MITPHPPHP ^8.2

Since Jun 8Pushed yesterdayCompare

[ Source](https://github.com/hadimazalan/ibayaq-laravel)[ Packagist](https://packagist.org/packages/hadimazalan/ibayaq-laravel)[ RSS](/packages/hadimazalan-ibayaq-laravel/feed)WikiDiscussions main Synced yesterday

READMEChangelogDependencies (6)Versions (2)Used By (0)

iBayaq Laravel SDK
==================

[](#ibayaq-laravel-sdk)

Laravel package for integrating with the [iBayaq](https://ibayaq.kedah.gov.my) state payment gateway via the `cart_v3` API.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 10, 11, or 12

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

[](#installation)

```
composer require hadimazalan/ibayaq-laravel
```

Publish configuration (optional):

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

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

[](#configuration)

Add the following to your `.env`:

```
IBAYAQ_USERNAME=your_api_username
IBAYAQ_PASSWORD=your_api_password
IBAYAQ_URL=https://ibayaq.kedah.gov.my
# IBAYAQ_CART_URL=https://ibayaq.kedah.gov.my/api/cart_v3
# IBAYAQ_CALLBACK_URL=https://your-app.test/redirect/ibayaq
IBAYAQ_TIMEOUT=30
IBAYAQ_TIMEZONE=Asia/Kuala_Lumpur
```

VariableDescription`IBAYAQ_USERNAME`iBayaq API username (Basic Auth)`IBAYAQ_PASSWORD`iBayaq API password (Basic Auth)`IBAYAQ_URL`Base gateway URL`IBAYAQ_CART_URL`Optional full override for the cart endpoint`IBAYAQ_CALLBACK_URL`Optional callback base URL (append `attempt_id` when building)Initiating a Payment
--------------------

[](#initiating-a-payment)

```
use Hadimazalan\Ibayaq\Data\AddressData;
use Hadimazalan\Ibayaq\Data\InitiatePaymentData;
use Hadimazalan\Ibayaq\Data\LineItemData;
use Hadimazalan\Ibayaq\Data\PayerData;
use Hadimazalan\Ibayaq\Facades\Ibayaq;
use Hadimazalan\Ibayaq\Support\CallbackUrlBuilder;

$callbackUrl = app(CallbackUrlBuilder::class)->build(
    configuredCallback: config('ibayaq.callback_url'),
    defaultBaseUrl: url('/redirect/ibayaq'),
    attemptId: $attemptId,
);

$result = Ibayaq::initiatePayment(new InitiatePaymentData(
    amount: 10025, // sen (RM 100.25)
    callbackUrl: $callbackUrl,
    payer: new PayerData(
        idNo: '900101025555',
        name: 'Ahmad Bin Ali',
        address: new AddressData(
            line1: 'No 1 Jalan Test',
            postcode: '05000',
            state: 'Kedah',
        ),
    ),
    lineItems: [
        new LineItemData(
            productCode: 'H0171257',
            voteCode: 'G001',
            description: 'License Fee',
            amount: 10025,
            reference: 'ITEM-001',
        ),
    ],
    reference: 'APP-001',
    email: 'payer@example.test',
    phoneNo: '60123456789',
    moduleType: 'FR',
));

return redirect()->away($result->redirectUrl);
```

### Required payment metadata

[](#required-payment-metadata)

iBayaq `cart_v3` requires Kedah state integration fields:

- `payer.id_no` — payer NRIC/passport
- `line_items[]` — each with `product_code`, `vote_code`, `description`, `amount` (sen), `reference`
- Line item total must exactly match `amount`
- Optional `module_type` (defaults to `FR`) appended to `no_rujukan`

Handling Callbacks
------------------

[](#handling-callbacks)

iBayaq sends results via two paths:

1. **Browser redirect** — `GET/POST /redirect/ibayaq`
2. **Server webhook** — `POST /webhook/ibayaq`

Callbacks support two payload formats:

- **Status codes**: `status_code` / `status` (`00` = success, `01` = failed, `PENDING`)
- **Receipt (iHasil)**: `no_resit`, `tarikh_bayar`, `jumlah_amaun`, `cara_bayar`

### Option A: Extend the abstract controller

[](#option-a-extend-the-abstract-controller)

```
use Hadimazalan\Ibayaq\Data\NormalizationContext;
use Hadimazalan\Ibayaq\Data\NormalizedPayment;
use Hadimazalan\Ibayaq\Http\Controllers\AbstractIbayaqCallbackController;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class IbayaqCallbackController extends AbstractIbayaqCallbackController
{
    protected function resolveAttemptId(Request $request): ?string
    {
        return $request->query('attempt_id') ?? $request->input('attempt_id')
            ?? $this->attemptRepository->findByCartReferences(
                $request->input('no_akaun'),
                $request->input('no_rujukan'),
            );
    }

    protected function attemptExists(string $attemptId): bool
    {
        return $this->attemptRepository->exists($attemptId);
    }

    protected function isAttemptAlreadyProcessed(string $attemptId): bool
    {
        return $this->attemptRepository->isProcessed($attemptId);
    }

    protected function normalizationContext(string $attemptId, ?Request $request = null): NormalizationContext
    {
        $order = $this->orderRepository->findByAttemptId($attemptId);

        return new NormalizationContext(
            attemptId: $attemptId,
            amount: $order->amount,
            currency: $order->currency,
            reference: $order->reference,
            processingTimestamp: now(),
        );
    }

    protected function onPaymentSuccess(NormalizedPayment $payment, string $attemptId): void
    {
        // Mark order paid, dispatch events, etc.
    }

    protected function onPaymentFailed(NormalizedPayment $payment, string $attemptId): void
    {
        // Handle failure
    }

    protected function onPaymentPending(NormalizedPayment $payment, string $attemptId): void
    {
        // Handle pending
    }

    protected function redirectAfterPayment(NormalizedPayment $payment, string $attemptId, Request $request): RedirectResponse
    {
        return redirect()->route('payment.thank-you', [
            'status' => $payment->status->value,
            'reference' => $payment->reference,
        ]);
    }
}
```

Register routes in `config/ibayaq.php`:

```
'routes' => [
    'enabled' => true,
    'controller' => \App\Http\Controllers\IbayaqCallbackController::class,
],
```

Or publish routes and wire them manually:

```
php artisan vendor:publish --tag=ibayaq-routes
```

### Option B: Manual callback parsing

[](#option-b-manual-callback-parsing)

```
use Hadimazalan\Ibayaq\CallbackNormalizer;
use Hadimazalan\Ibayaq\Data\CallbackPayload;
use Hadimazalan\Ibayaq\Data\NormalizationContext;

$payment = app(CallbackNormalizer::class)->normalize(
    CallbackPayload::fromArray($request->all()),
    new NormalizationContext(
        attemptId: $attemptId,
        amount: 10025,
        reference: 'APP-001',
        processingTimestamp: now(),
    ),
);
```

### CSRF exemption

[](#csrf-exemption)

iBayaq POSTs to your callback URLs without CSRF tokens. Exempt the routes in your app:

```
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
    'redirect/ibayaq',
    'webhook/ibayaq',
];
```

Known Limitations
-----------------

[](#known-limitations)

- **No requery** — iBayaq does not expose a status inquiry endpoint; `Ibayaq::requery()` returns `Unknown`
- **No refund API** — not supported by this SDK
- **No inbound signature verification** — callbacks are trusted via attempt/reference resolution in your application

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/11846272?v=4)[Hadi Mazalan](/maintainers/hadimazalan)[@hadimazalan](https://github.com/hadimazalan)

---

Top Contributors

[![hadimazalan](https://avatars.githubusercontent.com/u/11846272?v=4)](https://github.com/hadimazalan "hadimazalan (1 commits)")

---

Tags

laravelpayment gatewayMalaysiaibayaqkedah

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/hadimazalan-ibayaq-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/hadimazalan-ibayaq-laravel/health.svg)](https://phpackages.com/packages/hadimazalan-ibayaq-laravel)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k28.4M134](/packages/laravel-cashier)[spatie/laravel-responsecache

Speed up a Laravel application by caching the entire response

2.8k8.7M64](/packages/spatie-laravel-responsecache)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M120](/packages/laravel-pulse)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)[laravel/cashier-paddle

Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.

268880.7k3](/packages/laravel-cashier-paddle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
