Skip to main content
To start with, the merchant gateway is solely focused on managing merchant accounts and ensuring they get paid. This process involves you the gateway accepting settlement of some sort (cash,visa, bank, balances, other apps, etc) and crediting the merchant account. This guide covers the different ways collections can be settled on the merchant gateway.

Payment Methods

Your gateway can receive payment through several channels:
MethodEndpointSource
Checkout redirect/orders/checkoutRedirect to a Payment Gateway
Internal transfer/transfer/createYour own payment apps / mobile apps
Partner transfer/transfer/create3rd party with reserve account
Third-party settlement/payment/create + /payment/settle3rd party payment app
Direct webhook/transfer/webhookSettlement provider

Checkout Redirect

The most common flow for e-commerce merchants. The merchant sends an order to your /orders/checkout endpoint, and you return a redirect URL to a Payment Gateway’s checkout page. The Payment Gateway handles collecting payment from the end user.
The Payment Gateway can be your own service or a third-party. See Payment Gateway API to learn how Payment Gateways work.
Customer → Merchant → Merchant Gateway → Customer → Payment Gateway → Merchant Gateway → Merchant

Implement a redirect mechanism

// 1. Receive order from merchant
app.post('/orders/checkout', signatureAuth, async (req, res) => {
  const { order, signature, urls } = req.body;

  // Store the order
  const merchantOrder = await db.storeOrder(order, signature, urls, req.merchant);

  // Forward to a Payment Gateway
  // This could be your own Payment Gateway service, or a third-party
  const paymentGateway = await selectPaymentGateway(order);
  const checkoutSession = await paymentGateway.createSession(merchantOrder);

  res.json({
    redirect_url: checkoutSession.checkout_url
  });
});

// 2. Receive payment proof from Payment Gateway
app.post('/transfer/webhook', signatureAuth, async (req, res) => {
  const { proof, signature } = req.body;
  const paymentGatewayOcid = req.headers['x-oc-id'];

  // Verify the Payment Gateway's signature
  if (!verifyProof(proof, signature, paymentGatewayOcid)) {
    return res.status(400).json({ error: { code: 'INVALID_PROOF' } });
  }

  // Credit merchant and forward the proof
  const merchant = await db.getMerchantByOcid(proof.to.ocid);
  await creditMerchant(merchant, proof);
  await notifyMerchant(merchant, proof);

  res.json({ status: 'accepted' });
});

Internal Transfers

Process transfer requests from your own mobile app or web wallet. When your webapp or mobile app calls this endpoint, ensure the payment is or has been collected, eg by deduction balance, visa etc. The example below assumes we deduct the users balance.,
// Your webapp backend calls this endpoint.
// Your merchant backend handles the request as shown below.
app.post('/transfer/create', signatureAuth, async (req, res) => {
  const { from, to, amount, currency, order } = req.body;

  // Check if this is an internal request (from your own app)
  // Typically check if its your OCID that signed the request.
  const isInternal = isInternalRequest(req);

  if (isInternal) {
    // Get user from your database, 
    // We assume your webapp set the userId in the from field.
    const user = await db.getUser(from.userId);
    const merchant = await db.getMerchantByOcid(to.ocid);

    // If your webapp did not debit the user already.
    await debitUser(user, amount, currency);

    // credit merchant
    await creditMerchant(user, merchant, amount, currency);

    // Create and sign proof. 
    // To avoid exposing user data, you could ommit the from field.
    const proof = createProof(from, to, amount, currency);

    // Notify merchant
    if (merchant?.webhookUrl) {
      await sendWebhook(merchant.webhookUrl, proof);
    }

    return res.json({ proof, signature: signProof(proof) });
  }

  // Handle external partner transfers...
});

Third-Party Partner payment apps

When a partner payment app doesn’t have a direct account with you, they use the two-step process:

Step 1: Create Pending Payment


// your partner payment app calls this endpoint

app.post('/payment/create', signatureAuth, async (req, res) => {
  const { to, amount, currency, order } = req.body;
   // Verify partner exists
  const partner = await db.getPartnerByOcid(req.header['X-OC-ID']);
  if (!partner) {
    return res.status(400).json({
      error: { code: 'MERCHANT_NOT_REGISTERED', message: 'Unknown partner' }
    });
  }

  // Verify merchant exists
  const merchant = await db.getMerchantByOcid(to);
  if (!merchant) {
    return res.status(400).json({
      error: { code: 'MERCHANT_NOT_REGISTERED', message: 'Unknown merchant' }
    });
  }

  // Create pending payment
  const payment = await db.createPayment({
    txid: generateTxid(),
    merchantOcid: to,
    amount,
    currency,
    orderId: order?.id,
    status: 'pending_settlement',
    expiresAt: Math.floor(Date.now() / 1000) + 3600
  });

  res.json({
    status: 'pending_settlement',
    txid: payment.txid,
    amount,
    currency,
    expiresAt: payment.expiresAt,
    settlement: {
      accepts: [100, 101, 102] // OCIDS of your accepted settlement providers
    }
  });
});

Step 2: Settle with Proof


// After paying say your bank, your partner payment app calls this endpoint 

app.post('/payment/settle', signatureAuth, async (req, res) => {
  const { txid, proof, signature } = req.body;

  // Verify and complete payment
  // Check that the transfer was signed by your accepted ocid eg your bank
  const payment = await verifyAndSettle(txid, proof, signature);

  // Credit merchant
  await creditMerchant(payment);

  // Notify merchant
  await notifyMerchant(payment);

  // Return our proof
  const ourProof = createProofForMerchant(payment);
  res.json({
    status: 'completed',
    txid: payment.txid,
    proof: ourProof,
    signature: signProof(ourProof)
  });
});

Crediting Merchants

After any successful payment, credit the merchant’s account:
async function creditMerchant(session) {
  const merchant = await db.getMerchantByOcid(session.merchantOcid);

  // Update balance
  await db.creditAccount(merchant.id, {
    amount: session.order.amount,
    currency: session.order.currency,
    reference: session.order.id,
    timestamp: Date.now()
  });

  // Log transaction
  await db.logTransaction({
    merchantOcid: session.merchantOcid,
    orderId: session.order.id,
    amount: session.order.amount,
    currency: session.order.currency,
    status: 'completed'
  });
}

Notifying Merchants

Always send a signed proof to the merchant’s webhook:
async function notifyMerchant(session) {
  const proof = {
    txid: session.txid || generateTxid(),
    issuer: YOUR_OCID,
    from: { ocid: session.customerOcid || 0, reference: session.paymentRef },
    to: { ocid: session.merchantOcid, reference: session.order.id },
    amount: session.order.amount,
    currency: session.order.currency,
    timestamp: Math.floor(Date.now() / 1000),
    memo: `Payment for ${session.order.id}`
  };

  const signature = signProof(proof);
  const merchant = await db.getMerchantByOcid(session.merchantOcid);

  try {
    await fetch(`${merchant.endpoint}/transfer/webhook`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...createAuthHeaders(YOUR_OCID, YOUR_PRIVATE_KEY)
      },
      body: JSON.stringify({ proof, signature })
    });
  } catch (err) {
    // Queue for retry
    await queueWebhookRetry(merchant.ocid, proof, signature);
  }
}

Next Steps