hadimazalan/n9pay-laravel

Laravel SDK and webhook integration for the N9Pay Payment Gateway API (v1.3).

Maintainers

Package info

github.com/hadimazalan/n9pay-laravel

pkg:composer/hadimazalan/n9pay-laravel

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-06-08 16:32 UTC

This package is auto-updated.

Last update: 2026-06-08 16:34:49 UTC


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 — raw WebhookPayload DTO
  • $event->payment — normalized NormalizedPayment DTO with reference, 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