hadimazalan / n9pay-laravel
Laravel SDK and webhook integration for the N9Pay Payment Gateway API (v1.3).
Requires
- php: ^8.2
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/routing: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
Laravel SDK and webhook integration for the N9Pay Payment Gateway API (v1.3).
This package provides:
- HTTP client to create payment links and requery payment status
- Webhook signature verification (documented HMAC + legacy fallback)
- Optional webhook route that dispatches Laravel events
- DTOs for requests, responses, and normalized payment data
Your application owns order/payment persistence — listen to events and update your models.
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
Installation
composer require hadimazalan/n9pay-laravel
Publish the configuration:
php artisan vendor:publish --tag=n9pay-config
Configuration
Add these variables to your .env:
N9PAY_API_KEY=your-bearer-token N9PAY_SIGNATURE_KEY=your-webhook-hmac-key N9PAY_MODE=live N9PAY_URL=https://apigate.n9pay.ns.gov.my/api N9PAY_TEST_URL=https://stg-apigate.n9pay.ns.gov.my/api N9PAY_WEBHOOK_ENABLED=true N9PAY_WEBHOOK_ROUTE=webhook/n9pay
| Variable | Description |
|---|---|
N9PAY_API_KEY |
Bearer token for outbound API requests |
N9PAY_SIGNATURE_KEY |
HMAC key for verifying inbound webhooks |
N9PAY_MODE |
live or test — selects API base URL |
N9PAY_URL |
Live API base URL |
N9PAY_TEST_URL |
Sandbox API base URL |
N9PAY_WEBHOOK_ENABLED |
Register the webhook route (default: true) |
N9PAY_WEBHOOK_ROUTE |
Webhook path (default: webhook/n9pay) |
Disable the built-in webhook route and handle verification yourself:
// config/n9pay.php 'webhook' => [ 'enabled' => false, ],
Payment Flow
sequenceDiagram
participant App as Your App
participant Pkg as n9pay-laravel
participant N9 as N9Pay API
participant Payer
App->>Pkg: createPaymentLink()
Pkg->>N9: POST /payment-links
N9-->>Pkg: payment_url, _id
Pkg-->>App: PaymentLinkResult
App->>Payer: Redirect to payment_url
Payer->>N9: Complete payment
N9->>Pkg: POST /webhook/n9pay
Pkg->>App: PaymentSucceeded event
App->>App: Mark order paid
Loading
1. Create a payment link
use Hadimazalan\N9Pay\Data\CreatePaymentLinkData; use Hadimazalan\N9Pay\Facades\N9Pay; $result = N9Pay::createPaymentLink(new CreatePaymentLinkData( amount: 10.00, reference: $order->id, // echoed back as payment_link_reference in webhook redirectUrl: route('orders.return', $order), eventUrl: route('n9pay.webhook'), // or url('/webhook/n9pay') email: $order->customer_email, title: 'Order #'.$order->number, description: 'Payment for order #'.$order->number, reference2: $order->invoice_number, )); // Store the N9Pay payment link ID for requery $order->update(['n9pay_payment_link_id' => $result->paymentLinkId]); return redirect()->away($result->paymentUrl);
Reference field mapping: Pass your order/attempt ID as reference. N9Pay returns it in webhooks as data.payment_link_reference.
Amounts are in MYR decimal (e.g. 10.00 for RM 10.00), not cents.
2. Handle the payer return URL
After payment, N9Pay redirects the payer to your redirect_url. Optionally requery status:
use Hadimazalan\N9Pay\Facades\N9Pay; $result = N9Pay::getPaymentLinkStatus($order->n9pay_payment_link_id); if ($result->success && $result->status->value === 1) { $order->markAsPaid($result->data); }
3. Listen to webhook events
When N9PAY_WEBHOOK_ENABLED=true, the package registers:
POST /webhook/n9pay
Register listeners in your EventServiceProvider or bootstrap/app.php:
use Hadimazalan\N9Pay\Events\PaymentSucceeded; use Hadimazalan\N9Pay\Events\PaymentFailed; use Hadimazalan\N9Pay\Events\PaymentPending; Event::listen(PaymentSucceeded::class, function (PaymentSucceeded $event) { $order = Order::find($event->payment->reference); $order?->markAsPaid($event->payment); }); Event::listen(PaymentFailed::class, function (PaymentFailed $event) { $order = Order::find($event->payment->reference); $order?->markAsFailed($event->payment->message); }); Event::listen(PaymentPending::class, function (PaymentPending $event) { $order = Order::find($event->payment->reference); $order?->markAsPending(); });
Each event carries:
$event->payload— rawWebhookPayloadDTO$event->payment— normalizedNormalizedPaymentDTO withreference,status,message, etc.
4. Manual webhook verification
If you disable the built-in route:
use Hadimazalan\N9Pay\Webhook\WebhookSignatureVerifier; use Hadimazalan\N9Pay\Data\WebhookPayload; use Hadimazalan\N9Pay\Data\NormalizedPayment; public function handleWebhook(Request $request, WebhookSignatureVerifier $verifier) { if (! $verifier->verify($request)) { abort(403); } $payload = WebhookPayload::fromArray($request->all()); $payment = NormalizedPayment::fromWebhook($payload); // Handle $payment->status, $payment->reference, etc. }
Sandbox Mode
Set N9PAY_MODE=test to use the sandbox API base URL (N9PAY_TEST_URL).
API Reference
Based on Dokumentasi Payment Gateway N9PAY v1.3.
| Operation | Method | Endpoint |
|---|---|---|
| Create payment link | POST |
{base_url}/payment-links |
| Requery status | GET |
{base_url}/payment-links/{id} |
| Webhook notification | POST |
Your event_url |
Authentication: Authorization: Bearer {N9PAY_API_KEY}
Webhook signature: HMAC-SHA256 verified against N9PAY_SIGNATURE_KEY (see WebhookSignatureVerifier for field order).
Testing
composer test
License
MIT