Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.paybridgenp.com/llms.txt

Use this file to discover all available pages before exploring further.

This page explains what happens inside the module when a customer pays an invoice, how WHMCS tracks the state transitions, and where each piece of the handshake lives. Read it once and you’ll know exactly which piece to look at when something doesn’t behave as expected.

The payment flow

1

Customer clicks Pay Now on an invoice

The customer opens an unpaid invoice in the client area and clicks the Pay Now button rendered by paybridgenp_link(). WHMCS posts back to the same invoice URL with a hidden flag that tells the gateway to create a checkout session.
2

Module creates a PayBridgeNP checkout session

Inside paybridgenp_link() with the redirect flag set, the module:
  • Reads the invoice total and converts it to paisa (total × 100, rounded) via Amount::toPaisa()
  • Builds the return URL and cancel URL - both point at modules/gateways/callback/paybridgenp.php?invoiceid=<id>, using the Public Callback URL if set or the WHMCS System URL as fallback
  • Calls PayBridge::checkout->create() via the PayBridgeNP PHP SDK with amount, currency=NPR, return_url, cancel_url, and metadata (invoice id, client id, client email, WHMCS version, source=whmcs)
  • Writes a checkout.created entry to the Gateway Log with the session id
If the PayBridgeNP API call fails (bad API key, API down, etc.), the module renders an inline error on the invoice page and logs checkout.api_error to the Gateway Log.On success, it returns HTML that redirects the browser to the hosted checkout URL via <meta http-equiv="refresh"> + <script>window.location.href=…</script> + a manual fallback link.
3

Customer pays on the hosted checkout

The customer lands on https://checkout.paybridgenp.com/checkout/cs_…, sees your merchant name and the invoice total, picks eSewa or Khalti, and completes the payment through the wallet’s OTP flow.
4

Browser return URL - no state change

After the wallet confirms the payment, PayBridgeNP redirects the customer’s browser back to:
https://your-whmcs-url/modules/gateways/callback/paybridgenp.php?invoiceid=<id>&session_id=cs_…&status=success&payment_id=pay_…
The callback file detects this is a browser return (no X-PayBridge-Signature header) and calls paybridgenp_handle_return(), which:
  • Validates the invoice id
  • Redirects the customer to the WHMCS invoice view with a flash query param (?paybridge_status=submitted / cancelled / failed)
  • Does not touch invoice state - the webhook (next step) is the only authoritative confirmation
Why no state change here? Because the browser return is not trustworthy - an attacker could craft a return URL with a fake status=success and a guessed invoice id. Only the HMAC-signed webhook can move the invoice to Paid.
5

Webhook delivery - invoice → Paid

A few seconds after the wallet confirms the payment, PayBridgeNP fires a signed payment.succeeded webhook to the same callback URL:
POST https://your-whmcs-url/modules/gateways/callback/paybridgenp.php
X-PayBridge-Signature: t=<timestamp>,v1=<hmac>
Content-Type: application/json

{ "id": "evt_…", "type": "payment.succeeded", "data": { "id": "pay_…", "amount": 10000, "metadata": { "invoiceid": "42", ... } } }
The callback detects the signature header and calls paybridgenp_handle_webhook(), which:
  • Reads the raw request body and the X-PayBridge-Signature header
  • Looks up the webhook signing secret from gateway settings - refuses with HTTP 400 if the secret is not configured (no unsigned fallback)
  • Verifies the HMAC-SHA256 signature via the PayBridgeNP PHP SDK’s constructEvent() - rejects forged events and replays older than 5 minutes
  • Extracts the invoice id from event.data.metadata.invoiceid
  • Calls WHMCS’s checkCbInvoiceID() to ensure the invoice exists and belongs to this gateway
  • Calls WHMCS’s checkCbTransID() to dedupe by PayBridgeNP payment id - if the same webhook is replayed, this exits early before any double-apply
  • Calls addInvoicePayment() with the amount converted back to rupees, the payment id as the transaction id, and zero fees
  • Writes a webhook.payment_applied entry to the Gateway Log with the provider, payment id, and amount
  • Returns HTTP 200 OK
WHMCS does the rest: the invoice flips to Paid, the balance zeros out, any associated service is provisioned, and WHMCS’s invoice-paid email goes to the customer.

Invoice state transitions

StateWhenWho sets it
DraftInvoice created by admin, not yet visible to clientWHMCS core
UnpaidPublished invoice awaiting paymentWHMCS core
Payment PendingOptional state some WHMCS flows use while awaiting async confirmationWHMCS core
PaidWebhook confirmed payment.succeeded, addInvoicePayment() settled the balanceModule’s webhook handler
RefundedFull refund recorded against the original transactionWHMCS core (after paybridgenp_refund() returns status=success)
CancelledAdmin-cancelled or webhook delivered payment.cancelled before payment clearedWHMCS core

What the webhook handler does for each event type

EventAction (if unpaid)Action (if already paid)
payment.succeededaddInvoicePayment() applies the payment, invoice becomes PaidNo change - checkCbTransID() silently bails on duplicate
payment.failedLogged to Gateway Log as webhook.payment_failed; no invoice state changeSame
payment.cancelledLogged to Gateway Log as webhook.payment_failed; no invoice state changeSame
payment.refundedLogged as webhook.ignored (refunds are driven from WHMCS admin, not webhooks)Same
Any other typeLogged as webhook.ignored with the event type recordedSame
payment.failed / payment.cancelled deliberately don’t move the invoice to a failed state - WHMCS keeps it Unpaid so the customer can retry. The failure is recorded in the Gateway Log for merchant visibility.

The refund path

When an admin clicks Refund on a transaction tied to the PayBridgeNP gateway, WHMCS calls paybridgenp_refund() with the transaction id and the amount to refund. The module:
  1. Maps the refund amount to paisa
  2. Calls PayBridge::refunds->create() with paymentId, amount (paisa), reason=other, and a notes field containing "Refund issued from WHMCS admin (invoice #<id>)"
  3. Logs refund.success (or refund.api_error on failure) to the Gateway Log
  4. Returns {status: "success", transid: "ref_…", rawdata: <response>} to WHMCS on success
WHMCS then records the refund against the original transaction and adjusts the invoice totals accordingly. Partial refunds work the same way - just pass a smaller amount.
PayBridgeNP’s refund API requires reason to be one of customer_request | duplicate | fraudulent | other. The module defaults to other and puts the WHMCS-specific context in the notes field, which shows up on the refund detail page in the PayBridgeNP dashboard.

Where the module lives in your WHMCS install

After extraction, the module’s files are under:
modules/gateways/
├── paybridgenp.php                                   ← main gateway (MetaData, config, link, refund)
├── paybridgenp/
│   ├── init.php                                       ← composer autoloader bootstrap
│   ├── whmcs.json                                     ← manifest for Apps & Integrations modal
│   ├── logo.png                                       ← shown next to the gateway name
│   ├── src/
│   │   ├── Amount.php                                 ← paisa ↔ rupees conversion
│   │   ├── CheckoutParams.php                         ← WHMCS params → checkout request
│   │   ├── Config.php                                 ← normalised settings accessor
│   │   └── Logger.php                                 ← gateway-log wrapper with secret scrubbing
│   └── vendor/                                        ← vendored PayBridgeNP PHP SDK
└── callback/
    └── paybridgenp.php                                ← return URL + webhook handler
The vendor/ directory is bundled inside the ZIP - you do NOT need to run composer install on your server.

The Gateway Log

Every interaction flows through WHMCS’s standard logTransaction() helper, which surfaces under Utilities → Logs → Gateway Log:
WHMCS Gateway Log showing PayBridgeNP events
Event types you’ll see:
EventMeaning
checkout.createdCustomer clicked Pay Now, session created successfully
checkout.request / checkout.responseFull API body (only logged when Debug Logging is on)
checkout.api_errorPayBridgeNP rejected the checkout creation - check the message field
webhook.payment_appliedInvoice successfully marked paid from a verified webhook
webhook.payment_failedpayment.failed or payment.cancelled event delivered
webhook.invalid_signatureHMAC verification failed - either the wrong secret or a forgery attempt
webhook.no_secret_configuredSomeone fired a webhook but no signing secret is saved
webhook.ignoredEvent type we don’t act on (e.g. payment.refunded)
refund.success / refund.api_errorAdmin-initiated refund result
return.hitBrowser return URL was hit (only logged when Debug Logging is on)
API keys, the webhook signing secret, and the X-PayBridge-Signature header value are automatically redacted before they land in the log - safe to share with support without exposing credentials.

Security model

  • Webhook signature verification is mandatory. The module refuses to process webhooks unless the signing secret is configured (HTTP 400 response). There is no unsigned fallback.
  • 5-minute replay window. HMAC verification rejects events older than 5 minutes based on the t= timestamp in the signature header.
  • checkCbInvoiceID + checkCbTransID are WHMCS built-ins that prevent cross-gateway payment application and duplicate postings.
  • logTransaction scrubbing. The module’s Logger class redacts api_key, secretKey, testSecretKey, webhookSecret, signing_secret, authorization, and X-PayBridge-Signature before passing data to logTransaction().
  • No raw SQL anywhere. All invoice operations go through WHMCS’s addInvoicePayment() / helper functions.
  • hash_equals comparisons inside the SDK for signature matching - timing-safe.

What’s next