> ## Documentation Index
> Fetch the complete documentation index at: https://docs.c2cpayment.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Hosted Payment Page

> The C2C-hosted page your customer uses to pay a deposit, or to receive a withdrawal.

## Overview

Every [deposit order](/api-reference/endpoints/create-deposit) comes with a **hosted payment page**. You don't build any payment UI: redirect your customer to the `paymentPageUrl` from the create-order response, and C2C walks them through paying the assigned member. This works the same for orders accepted as `Awaiting` (no eligible member at creation): the page assigns a member at checkout.

<Info>
  C2C has no separate "initiate checkout" endpoint. Creating a deposit order **is** creating the checkout session: one call returns both the order and its `paymentPageUrl`.
</Info>

## The payment page URL

```text theme={null}
https://<payment-page-host>/pay?order_id=788fcae0-...&expires=1791462080&signature=Xk3...&wallet=jazzcash
```

| Property | Detail |
| - | - |
| Pre-signed | C2C signs `order_id` and `expires`. Any modification makes the link invalid. |
| Lifetime | **15 minutes** from order creation (`expiresAt` in the response) |
| Wallet | `wallet` is the [payment method](/guides/payment-methods) (`walletType`) you requested: `jazzcash`, `jazzcashtillid`, `jazzcashqr`, `easypaisa`, `tillid` or `easypaisaqr`. The page opens directly on that method. A `BankAccount` deposit carries `wallet=bank`; bank accounts aren't offered on the page for now, so that link opens on the list of available methods instead. `wallet` isn't part of the signature. |
| Audience | Only the paying customer. Anyone with the link can see the payment instructions for that order. |
| Withdrawals | A URL is returned for withdrawals too. The page never asks the customer to pay: it collects their receiving account when you didn't send one, and shows the payout's progress. See [Withdrawals](#withdrawals). |

<Warning>
  Redirect the customer as soon as you receive the URL. Don't cache it, email it hours later, or reuse it for another order. If the link expires, the order ends `Expired` (or `CancelledTimeout` if no member was ever assigned) and you receive an `order.failed` webhook. Create a **new** order with a new `Idempotency-Key`; you can reuse the same `merchantOrderRef`.
</Warning>

## What your customer sees

<Steps>
  <Step title="Open on your payment method">
    The page shows your merchant name, the amount and the C2C order reference, and opens directly on the `walletType` you requested (for example JazzCash QR). The customer doesn't pick a method first. C2C reserves a receiving account of exactly that method, and the order moves to `AwaitingCustomerPayment`. If other methods are enabled for your account and available for the order, the customer can tap **Change** to pay with one of those instead.

    If the order is `Awaiting`, an online member is assigned at this point. If nobody with an account of that method is online yet, the page shows **"Finding an available agent…"** and re-checks automatically every 10 seconds until it can show where to pay or the link expires. The customer must not send money until an account number, Till ID or QR code is shown.
  </Step>

  <Step title="Pay and confirm">
    The customer sends exactly the order amount from their own wallet app and confirms on the page. What the page shows and what the customer submits depend on the method: see [Account number and Till ID methods](#account-number-and-till-id-methods) and [QR methods](#qr-methods). In both cases the order moves to `AwaitingMemberConfirmation`.

    Whatever the customer submits is their claim, not proof: the order completes only when the member confirms the money arrived.
  </Step>

  <Step title="Wait for verification">
    The page waits while the member verifies the payment, and updates by itself to a success or a cancelled/expired screen. If you set `successUrl` / `failureUrl`, it then sends the customer back to your site. If the customer reloads or reopens the link meanwhile, the page resumes here instead of asking them to confirm again.
  </Step>
</Steps>

<Note>
  If the assigned member goes offline before the customer opens the page, C2C can reassign the order to another available member at checkout. This is transparent to you: the `orderId` doesn't change. If the customer pays with a different method than you requested, your fee is re-quoted for the method used.
</Note>

### Account number and Till ID methods

`JazzCash`, `JazzCashTillID`, `Easypaisa` and `TillID`.

The page shows the provider, the receiving **account number** or **Till ID** with a copy button, the exact amount to send, numbered steps for that provider's app and a **How to Pay** guide. The account holder's name isn't shown.

1. The customer copies the account number or Till ID.
2. They pay exactly the order amount in their JazzCash or Easypaisa app.
3. They enter the **last digits of the transaction ID** (TID) of that payment. The order's `txnRefDigitsRequired` sets how many digits (4 by default). The confirm button stays disabled until they have entered them.

They can also attach a **screenshot of the payment** (optional; JPEG, PNG or WebP up to 5 MB), which the member sees when verifying.

### QR methods

`JazzCashQR` and `EasypaisaQR`. These methods never ask for a transaction ID, and `txnRefDigitsRequired` has no effect on them.

1. The customer first submits the **account they pay from**. For JazzCash QR that is their JazzCash account number (`03XXXXXXXXX`); for Easypaisa QR it is the last 4 digits of their Easypaisa IBAN. The member uses it to find the incoming payment.
2. Only then does the page show the member's **QR code**, with a **Save QR** button. A button that opens the JazzCash or Easypaisa app is shown only when C2C has configured a verified app link for that provider.
3. The customer scans the code in their JazzCash or Easypaisa app, or loads the saved image there, and pays exactly the order amount.
4. They tap **I've Completed Payment**. They can attach an optional payment screenshot first, as above.

Until they tap **I've Completed Payment**, the customer can correct the account they entered. Afterwards it is final.

The page remembers the submitted account in that browser (local storage, separately for JazzCash and Easypaisa) and prefills it on the customer's next QR payment. Nothing is sent to C2C until the customer submits it, and they can clear it from the page.

## Withdrawals

For a [withdrawal](/api-reference/endpoints/create-withdrawal) the page works the other way round: the customer **receives** money, so it never shows an account to pay and never asks for a transaction ID.

<Steps>
  <Step title="Enter the receiving account">
    Only when you created the withdrawal **without** `customerAccountNumber`. The page is titled **Receive Your Withdrawal** and shows your merchant name, the amount and the order reference. The customer enters:

    * **Wallet Account Number** — checked against the order's `walletType` (JazzCash / Easypaisa: an 11-digit mobile account number such as `03001234567`; `+92…` is accepted and normalised)
    * **Account Holder Name**

    The wallet provider is the order's `walletType` and the amount is the order's `amount`; the customer can change neither. They must submit before the link expires (15 minutes), otherwise the order ends `CancelledTimeout`.
  </Step>

  <Step title="Processing">
    After submitting — or immediately, when you sent the account yourself — the page shows that the withdrawal is being processed, with the wallet, the account (all but the last 4 digits hidden) and the account holder name. It updates by itself. Reloading the page returns here; the submitted account cannot be edited, and submitting the same details again has no effect.
  </Step>

  <Step title="Outcome">
    When the member has sent the payout the page shows **Withdrawal sent**. If the withdrawal is rejected, cancelled or expires it shows that no payment was sent. If you set `successUrl` / `failureUrl`, the customer is then returned to your site.
  </Step>
</Steps>

<Note>
  An account entered on the page is exactly as authoritative as one you send in `customerAccountNumber`: the member pays that account. Send the customer to the page only from a session you trust to be theirs.
</Note>

## Returning the customer to your site

Set `successUrl` and `failureUrl` when you create the [deposit](/api-reference/endpoints/create-deposit) or [withdrawal](/api-reference/endpoints/create-withdrawal). When the order reaches a final status while the customer still has the page open, the page shows the result, then sends the customer back to you after **5 seconds**. A "Return now" link lets them go back straight away.

| Final status | Customer is sent to |
| - | - |
| `Completed` (or legacy `Confirmed`) | `successUrl` |
| `Rejected`, `Expired`, `Cancelled`, `CancelledAuto`, `CancelledTimeout` | `failureUrl` |

C2C appends the outcome to the URL, keeping your existing query parameters:

```text theme={null}
https://merchant.example.com/payment/done?c2c_order_id=788fcae0-5214-403c-baa2-f318c0eb1b5b&c2c_order_reference=ORD-20261008120620-3f6b&c2c_status=Completed
```

<Warning>
  These parameters come through the customer's browser and can be edited. Use `c2c_order_id` only to know **which** order to show. Before fulfilling, confirm the status from your backend with a [webhook](/guides/webhooks) or a [status query](/api-reference/endpoints/query-order-status).
</Warning>

Both URLs must be absolute `http://` or `https://` URLs. Any other scheme, such as `javascript:`, is rejected with `400` when the order is created.

The redirect only happens if the customer keeps the page open until the order is final. A member can take a while to confirm, so also give customers a way back of their own: for example a "Check my order" page on your site, or open `paymentPageUrl` in a new tab while your page polls your backend.

## Integration example

```javascript Node.js (Express) theme={null}
app.post('/checkout/c2c', async (req, res) => {
  const cart = await loadCart(req.session.cartId);
  const idempotencyKey = `${cart.id}-c2c-1`;              // persisted with the cart

  const order = await createC2COrder({                     // signs and sends POST /api/merchant/orders
    orderType: 'Deposit',
    amount: cart.total,
    currency: 'PKR',
    walletType: req.body.walletType ?? 'JazzCash',
    merchantOrderRef: cart.id,
    customerReference: req.user.id,
    callbackUrl: 'https://merchant.example.com/webhooks/c2c',
  }, idempotencyKey);

  await saveC2COrderId(cart.id, order.orderId);           // needed for status checks
  res.json({ paymentPageUrl: order.paymentPageUrl, statusPage: `/orders/${cart.id}` });
});
```

## Security notes

* The page and its backend endpoints are operated by C2C. Your API key and secrets are never involved, and the customer never sees them.
* The page never asks for the customer's wallet PIN or OTP. Customers pay from their own wallet app.
* A payment screenshot is stored by C2C and shown only to the member assigned to the order and to C2C operations. It has no public link.
* On the QR methods, the account the customer says they pay from is stored with the order for the member who verifies the payment. It isn't returned by the Merchant API or sent in webhooks.
* On a withdrawal the page never shows a member's account, and shows the customer's own account masked after it was submitted.
* Never build or modify payment page URLs yourself. Always use the `paymentPageUrl` returned by the API.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.