Skip to main content
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 PayBridgeNP::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:
The callback file detects this is a browser return (no X-PayBridgeNP-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:
The callback detects the signature header and calls paybridgenp_handle_webhook(), which:
  • Reads the raw request body and the X-PayBridgeNP-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

What the webhook handler does for each event type

payment.failed deliberately doesn’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. PayBridgeNP also emits payment.cancelled when the buyer backs out on the provider page; the module logs it without changing the invoice state, so subscribing to it is optional.

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 PayBridgeNP::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.
eSewa refunds are not instant. eSewa refunds are accepted by PayBridgeNP into a requires_action state and finalized out of band, but the module reports status=success to WHMCS as soon as the API accepts the request. So WHMCS may show the invoice as Refunded before eSewa has actually returned the money. Always confirm the refund reached a completed state in the PayBridgeNP dashboard before treating it as settled.
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:
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

Gateway Log entries - checkout creation, webhook delivery, refund attempts all visible with structured payloads.

Event types you’ll see: API keys, the webhook signing secret, and the X-PayBridgeNP-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-PayBridgeNP-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