Skip to content

Payments

HGN hosts the payment page. You initiate a payment, redirect the customer to the URL you get back, and read the result when they return.

sequenceDiagram
    autonumber
    participant P as Your backend
    participant A as HGN API
    participant C as Customer
    participant G as Gateway

    P->>A: POST /orders/{orderRef}/pay
    A-->>P: paymentUrl
    P-->>C: redirect to paymentUrl
    C->>G: completes payment
    G->>A: callback to /api/external/payment/response
    A-->>C: redirect to returnUrl?paymentRef=...
    C->>P: lands on your returnUrl
    P->>A: GET /orders/{orderRef}/payment-status
    A-->>P: authoritative status

Never trust the redirect as proof of payment

The customer landing back on your returnUrl means only that their browser was redirected. It is not proof that the payment succeeded — the redirect can be replayed, interrupted, or forged.

Always confirm with GET /orders/{orderRef}/payment-status server-side before fulfilling.


POST /orders/{orderRef}/pay

Starts a hosted online payment and returns the URL to send the customer to.

Authentication: required.

Path parameters

Name Type Description
orderRef String Any identifier that resolves to a single order — see resolving order identifiers

Headers

Header Required Description
Authorization Yes Bearer <accessToken>
Idempotency-Key Yes A UUID. Non-UUID values are rejected before anything is charged
Content-Type Yes application/json

Request body

Field Type Required Validation
gateway String Yes cybersource or fonepay. cash is rejected
returnUrl String No Must be an HTTPS URL
{
  "gateway": "fonepay",
  "returnUrl": "https://partner.example.com/payment-result"
}

Response

200 OK

{
  "success": true,
  "message": "Payment initiated",
  "data": {
    "paymentUrl": "https://pay.himalayanguardian.com/pay/session/3f7a1b2c-...",
    "gateway": "fonepay"
  },
  "timestamp": "2026-07-07T03:30:00Z"
}

Redirect the customer to paymentUrl.

Examples

curl -X POST \
  https://api.himalayanguardian.com/api/external/v1/orders/ORD-2026-000058/pay \
  -H "Authorization: Bearer $HGN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "gateway": "fonepay",
    "returnUrl": "https://partner.example.com/payment-result"
  }'
import { randomUUID } from "node:crypto";

// Persist this key against the order BEFORE calling, so a retry
// after a timeout reuses the same key rather than generating a new one.
const idempotencyKey = randomUUID();

const res = await fetch(`${BASE_URL}/orders/${orderRef}/pay`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${await getAccessToken()}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    gateway: "fonepay",
    returnUrl: "https://partner.example.com/payment-result",
  }),
});

const { data } = await res.json();
redirect(data.paymentUrl);
import uuid

idempotency_key = str(uuid.uuid4())  # persist against the order first

res = requests.post(
    f"{BASE_URL}/orders/{order_ref}/pay",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "gateway": "fonepay",
        "returnUrl": "https://partner.example.com/payment-result",
    },
    timeout=15,
)
res.raise_for_status()

payment_url = res.json()["data"]["paymentUrl"]

Idempotency

Idempotency-Key is mandatory and must be a valid UUID. A malformed value fails validation before the payment service is reached.

Generate the key once, per payment attempt

Store the key alongside the order before you make the call. If the request times out and you retry with a freshly generated key, you are making a new payment attempt rather than resuming the previous one.

Errors

Status Cause
400 gateway is cash; invalid or non-HTTPS returnUrl; the order is already paid, closed, completed, or cancelled; malformed UUID in Idempotency-Key; or the identifier is a payment reference covering a multi-order group payment
401 Missing or invalid token
404 No order matches the identifier

Online gateways only

This endpoint initiates hosted online payments. gateway: "cash" is explicitly rejected — cash settlement is recorded through the order's settlementType, not here.


GET /orders/{orderRef}/payment-status

Returns the most recent payment transaction for an order. This is the authoritative source of truth.

Authentication: required.

Path parameters

Name Type Description
orderRef String Any identifier that resolves to a single order — see below

Response

200 OK — two shapes, depending on whether a payment has been attempted.

{
  "success": true,
  "message": "No payment transaction found",
  "data": {
    "orderRef": "ORD-2026-000058",
    "orderStatus": "PENDING",
    "paymentStatus": "NOT_INITIATED"
  },
  "timestamp": "2026-07-07T03:30:00Z"
}

Note this is still 200 OK with success: true — "no payment yet" is a valid state, not an error.

{
  "success": true,
  "message": "Payment status retrieved",
  "data": {
    "orderRef": "ORD-2026-000058",
    "orderStatus": "PAID",
    "paymentRef": "PAY-2026-000001",
    "paymentStatus": "SUCCESS",
    "paymentMethod": "CYBERSOURCE",
    "amount": 590.00,
    "currency": "NPR",
    "completedAt": "2026-07-07T03:30:00Z"
  },
  "timestamp": "2026-07-07T03:30:00Z"
}
Field Type Description
orderRef String The resolved order reference
orderStatus String Order state, e.g. PENDING, PAID
paymentStatus String Payment state, e.g. NOT_INITIATED, SUCCESS
paymentRef String Payment record reference. Absent when no payment exists
paymentMethod String Gateway used, e.g. CYBERSOURCE
amount Decimal Amount transacted
currency String Currency code
completedAt Timestamp When the payment completed

Guard on the field, not the shape

The lean "no payment yet" response omits paymentRef, amount, currency, and completedAt entirely. Read paymentStatus first and only reach for the other fields once it is something other than NOT_INITIATED.

Errors

Status Cause
400 The identifier is a payment reference covering a multi-order group payment, so it does not resolve to a single order
401 Missing or invalid token
403 Your company does not own this order
404 No order matches the identifier

Resolving order identifiers

Both payment endpoints accept any identifier that resolves to exactly one order:

Identifier Example
Order reference ORD-2026-000058
Order number ORDN-2026-000058
Order UUID 3f7a1b2c-...
Gateway payment reference ORD-2026-000247-1786524461302

The paymentRef trap

When the customer returns to your returnUrl, HGN appends a paymentRef query parameter:

https://partner.example.com/payment-result?paymentRef=ORD-2026-000247-1786524461302

paymentRef is not an order reference

Its shape is <orderRef>-<epochMillis>. It looks like an order reference with a suffix, and it is accepted by both payment endpoints — it resolves back to the order it paid for.

But do not store it as an order reference, and do not try to parse the order reference out of it by splitting on the last hyphen. Use it as an opaque lookup key, or record the real orderRef from the order you created.

One case fails: if the payment reference covers a payment across a multi-order group, it cannot resolve to a single order and both endpoints return 400. Query each order's own reference instead.


Gateway callbacks

The payment gateway posts its browser callback to a separate backend path:

/api/external/payment/response

This endpoint exists for CyberSource and browser redirects. Partner systems never call it directly — you only ever see its effect, as the redirect that lands the customer back on your returnUrl.

returnUrl validation

returnUrl must be an HTTPS URL. Plain HTTP is rejected outside of HGN's internal test environment, so use HTTPS even in your own staging setup.


  1. Create the order and store its orderRef.
  2. Generate and persist an Idempotency-Key against that order.
  3. POST /orders/{orderRef}/pay with a returnUrl you control.
  4. Redirect the customer to paymentUrl.
  5. When they return, ignore the query string as evidence and call GET /orders/{orderRef}/payment-status.
  6. Fulfil only when paymentStatus is a success state.

Handle customers who never come back

Customers close tabs mid-payment. Do not rely solely on the redirect to trigger reconciliation — poll payment-status for orders left in PENDING, or reconcile on a schedule, so a completed payment is never missed because the browser never returned.


Next steps

  • Promo Codes — discounts must be applied before payment
  • Errors — every error shape this API returns
  • Examples — the full flow end to end