Skip to main content
Use this endpoint for both internal transfers (your app paying a merchant) and external partner transfers.

Use Cases

  1. Internal - Your mobile app requests payment after user scans merchant QR
  2. External - Partner gateways with reserve accounts request transfers

Implementation

app.post('/transfer/create', verifyAuth, async (req, res) => {
  const { from, to, amount, currency, memo, order } = req.body;
  const callerOcid = parseInt(req.headers['x-oc-id']);

  // Determine if internal or external
  if (isInternalRequest(callerOcid, from)) {
    return handleInternalTransfer(req, res);
  } else {
    return handleExternalTransfer(req, res);
  }
});

async function handleInternalTransfer(req, res) {
  const { from, to, amount, currency, memo, order } = req.body;

  // Get user from reference
  const user = await db.getUserByReference(from.reference);
  if (!user) {
    return res.status(400).json({
      error: { code: 'ACCOUNT_NOT_FOUND', message: 'User not found' }
    });
  }

  // Check balance
  if (user.balance < parseFloat(amount)) {
    return res.status(402).json({
      error: { code: 'INSUFFICIENT_FUNDS', message: 'Not enough balance' }
    });
  }

  // Process transfer
  const txid = generateTxid();
  await db.debitUser(user.id, amount);

  // Create proof
  const proof = {
    txid,
    issuer: YOUR_OCID,
    from: { ocid: YOUR_OCID, reference: from.reference },
    to: { ocid: to.ocid, reference: to.reference || order?.id },
    amount,
    currency,
    timestamp: Math.floor(Date.now() / 1000),
    memo
  };

  const signature = signProof(proof);

  // Notify merchant
  await sendMerchantWebhook(to.ocid, proof, signature);

  res.json({ proof, signature });
}

Paying After Scanning Merchant QR

When your user scans a merchant’s QR code:
async function payMerchantOrder(user, merchantOrder) {
  // 1. Verify the order
  const merchant = await fetchMerchantMetadata(merchantOrder.ocid);
  if (!verifyOrderSignature(merchantOrder.order, merchantOrder.signature, merchant.config.publicKey)) {
    throw new Error('Invalid order signature');
  }

  // 2. Debit user
  await db.debitUser(user.id, merchantOrder.order.amount);

  // 3. Settle with merchant - see Settlement Guide
  const proof = await settlePayment(user, merchantOrder.order, merchantOrder.ocid);

  return proof;
}

Settlement Guide

Learn about the different ways to settle payments with merchants