Skip to main content
POST
/
payment
/
create
Create payment
curl --request POST \
  --url https://pay.gateway.example/opencharge/payment/create \
  --header 'Content-Type: application/json' \
  --header 'X-OC-ID: <x-oc-id>' \
  --header 'X-OC-Nonce: <x-oc-nonce>' \
  --header 'X-OC-Signature: <x-oc-signature>' \
  --header 'X-OC-Timestamp: <x-oc-timestamp>' \
  --data '
{
  "to": 500,
  "amount": "99.99",
  "currency": "USD",
  "reference": "payment_123",
  "memo": "Payment for electronics",
  "expiresAt": 1706503600,
  "order": {
    "id": "ord_abc123",
    "urls": [
      "https://merchant.example/orders/ord_abc123"
    ]
  }
}
'
{
  "status": "pending_settlement",
  "txid": "gateway_payment_456",
  "amount": "99.99",
  "currency": "USD",
  "expiresAt": 1706503600,
  "settlement": {
    "accepts": [
      100,
      101,
      102
    ]
  }
}
Third-party payment apps use this endpoint when they don’t have a direct reserve account with you. They create a pending payment, then settle via an accepted settlement provider.

Two-Step Payment Flow

  1. Create payment - Payment app calls /payment/create to create pending payment
  2. Settle payment - Payment app settles via /payment/settle with a proof from an accepted settlement provider
This enables interoperability between payment apps and merchant gateways that don’t have direct relationships.

Implementation

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

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

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

  // 3. Optionally fetch and store order details
  if (order?.urls?.length > 0) {
    fetchAndStoreOrder(payment.id, order.urls);
  }

  // 4. Return settlement info
  res.json({
    status: 'pending_settlement',
    txid,
    amount,
    currency,
    expiresAt: payment.expiresAt,
    settlement: {
      accepts: ACCEPTED_SETTLEMENT_PROVIDERS // e.g., [100, 101, 102]
    }
  });
});

Settlement Providers

The settlement.accepts array tells the payment app which OCIDs you’ll accept proofs from:
{
  "settlement": {
    "accepts": [100, 101, 102]
  }
}
The payment app must:
  1. Have an account with one of these providers
  2. Execute a transfer to your OCID via that provider
  3. Submit the signed proof to your /payment/settle endpoint

Order Information

If payment is for an order, include order details:
{
  "order": {
    "id": "ord_abc123",
    "urls": ["https://merchant.example/orders/ord_abc123"]
  }
}
You can fetch the signed order from these URLs to validate the amount and merchant.

Headers

X-OC-ID
string
required

Caller's OCID (Opencharge ID)

X-OC-Timestamp
string
required

Unix timestamp in seconds

X-OC-Nonce
string
required

Unique request identifier for replay protection

X-OC-Signature
string
required

secp256k1 ECDSA signature of the canonical request

Body

application/json

Request from 3rd party payment app to create a pending payment

to
integer
required

Merchant OCID to pay

amount
string
required
currency
string
required
reference
string
memo
string
expiresAt
integer
order
object

Response

Payment created, awaiting settlement

Pending payment created, awaiting settlement

status
enum<string>
required
Available options:
pending_settlement
txid
string
required

Your payment transaction ID

amount
string
required
currency
string
required
settlement
object
required
expiresAt
integer