Checkout order
curl --request POST \
--url https://pay.gateway.example/opencharge/orders/checkout \
--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 '
{
"order": {
"id": "ord_xyz789",
"ocid": 500,
"reference": "checkout-session-123",
"amount": "99.99",
"currency": "USD",
"items": [
{
"id": "prod_001",
"name": "Premium Widget",
"quantity": 2,
"price": "49.99"
}
],
"memo": "Online purchase",
"createdAt": 1706313600,
"expiresAt": 1706400000,
"accepts": [
100,
101
]
},
"signature": "a1b2c3d4e5f6...merchant_sig",
"urls": {
"order": [
"https://merchant.example/orders/ord_xyz789"
],
"completed": "https://merchant.example/checkout/success",
"cancelled": "https://merchant.example/checkout/cancelled"
}
}
'import requests
url = "https://pay.gateway.example/opencharge/orders/checkout"
payload = {
"order": {
"id": "ord_xyz789",
"ocid": 500,
"reference": "checkout-session-123",
"amount": "99.99",
"currency": "USD",
"items": [
{
"id": "prod_001",
"name": "Premium Widget",
"quantity": 2,
"price": "49.99"
}
],
"memo": "Online purchase",
"createdAt": 1706313600,
"expiresAt": 1706400000,
"accepts": [100, 101]
},
"signature": "a1b2c3d4e5f6...merchant_sig",
"urls": {
"order": ["https://merchant.example/orders/ord_xyz789"],
"completed": "https://merchant.example/checkout/success",
"cancelled": "https://merchant.example/checkout/cancelled"
}
}
headers = {
"X-OC-ID": "<x-oc-id>",
"X-OC-Timestamp": "<x-oc-timestamp>",
"X-OC-Nonce": "<x-oc-nonce>",
"X-OC-Signature": "<x-oc-signature>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-OC-ID': '<x-oc-id>',
'X-OC-Timestamp': '<x-oc-timestamp>',
'X-OC-Nonce': '<x-oc-nonce>',
'X-OC-Signature': '<x-oc-signature>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
order: {
id: 'ord_xyz789',
ocid: 500,
reference: 'checkout-session-123',
amount: '99.99',
currency: 'USD',
items: [{id: 'prod_001', name: 'Premium Widget', quantity: 2, price: '49.99'}],
memo: 'Online purchase',
createdAt: 1706313600,
expiresAt: 1706400000,
accepts: [100, 101]
},
signature: 'a1b2c3d4e5f6...merchant_sig',
urls: {
order: ['https://merchant.example/orders/ord_xyz789'],
completed: 'https://merchant.example/checkout/success',
cancelled: 'https://merchant.example/checkout/cancelled'
}
})
};
fetch('https://pay.gateway.example/opencharge/orders/checkout', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pay.gateway.example/opencharge/orders/checkout",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'order' => [
'id' => 'ord_xyz789',
'ocid' => 500,
'reference' => 'checkout-session-123',
'amount' => '99.99',
'currency' => 'USD',
'items' => [
[
'id' => 'prod_001',
'name' => 'Premium Widget',
'quantity' => 2,
'price' => '49.99'
]
],
'memo' => 'Online purchase',
'createdAt' => 1706313600,
'expiresAt' => 1706400000,
'accepts' => [
100,
101
]
],
'signature' => 'a1b2c3d4e5f6...merchant_sig',
'urls' => [
'order' => [
'https://merchant.example/orders/ord_xyz789'
],
'completed' => 'https://merchant.example/checkout/success',
'cancelled' => 'https://merchant.example/checkout/cancelled'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-OC-ID: <x-oc-id>",
"X-OC-Nonce: <x-oc-nonce>",
"X-OC-Signature: <x-oc-signature>",
"X-OC-Timestamp: <x-oc-timestamp>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://pay.gateway.example/opencharge/orders/checkout"
payload := strings.NewReader("{\n \"order\": {\n \"id\": \"ord_xyz789\",\n \"ocid\": 500,\n \"reference\": \"checkout-session-123\",\n \"amount\": \"99.99\",\n \"currency\": \"USD\",\n \"items\": [\n {\n \"id\": \"prod_001\",\n \"name\": \"Premium Widget\",\n \"quantity\": 2,\n \"price\": \"49.99\"\n }\n ],\n \"memo\": \"Online purchase\",\n \"createdAt\": 1706313600,\n \"expiresAt\": 1706400000,\n \"accepts\": [\n 100,\n 101\n ]\n },\n \"signature\": \"a1b2c3d4e5f6...merchant_sig\",\n \"urls\": {\n \"order\": [\n \"https://merchant.example/orders/ord_xyz789\"\n ],\n \"completed\": \"https://merchant.example/checkout/success\",\n \"cancelled\": \"https://merchant.example/checkout/cancelled\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-OC-ID", "<x-oc-id>")
req.Header.Add("X-OC-Timestamp", "<x-oc-timestamp>")
req.Header.Add("X-OC-Nonce", "<x-oc-nonce>")
req.Header.Add("X-OC-Signature", "<x-oc-signature>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://pay.gateway.example/opencharge/orders/checkout")
.header("X-OC-ID", "<x-oc-id>")
.header("X-OC-Timestamp", "<x-oc-timestamp>")
.header("X-OC-Nonce", "<x-oc-nonce>")
.header("X-OC-Signature", "<x-oc-signature>")
.header("Content-Type", "application/json")
.body("{\n \"order\": {\n \"id\": \"ord_xyz789\",\n \"ocid\": 500,\n \"reference\": \"checkout-session-123\",\n \"amount\": \"99.99\",\n \"currency\": \"USD\",\n \"items\": [\n {\n \"id\": \"prod_001\",\n \"name\": \"Premium Widget\",\n \"quantity\": 2,\n \"price\": \"49.99\"\n }\n ],\n \"memo\": \"Online purchase\",\n \"createdAt\": 1706313600,\n \"expiresAt\": 1706400000,\n \"accepts\": [\n 100,\n 101\n ]\n },\n \"signature\": \"a1b2c3d4e5f6...merchant_sig\",\n \"urls\": {\n \"order\": [\n \"https://merchant.example/orders/ord_xyz789\"\n ],\n \"completed\": \"https://merchant.example/checkout/success\",\n \"cancelled\": \"https://merchant.example/checkout/cancelled\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://pay.gateway.example/opencharge/orders/checkout")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-OC-ID"] = '<x-oc-id>'
request["X-OC-Timestamp"] = '<x-oc-timestamp>'
request["X-OC-Nonce"] = '<x-oc-nonce>'
request["X-OC-Signature"] = '<x-oc-signature>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"order\": {\n \"id\": \"ord_xyz789\",\n \"ocid\": 500,\n \"reference\": \"checkout-session-123\",\n \"amount\": \"99.99\",\n \"currency\": \"USD\",\n \"items\": [\n {\n \"id\": \"prod_001\",\n \"name\": \"Premium Widget\",\n \"quantity\": 2,\n \"price\": \"49.99\"\n }\n ],\n \"memo\": \"Online purchase\",\n \"createdAt\": 1706313600,\n \"expiresAt\": 1706400000,\n \"accepts\": [\n 100,\n 101\n ]\n },\n \"signature\": \"a1b2c3d4e5f6...merchant_sig\",\n \"urls\": {\n \"order\": [\n \"https://merchant.example/orders/ord_xyz789\"\n ],\n \"completed\": \"https://merchant.example/checkout/success\",\n \"cancelled\": \"https://merchant.example/checkout/cancelled\"\n }\n}"
response = http.request(request)
puts response.read_body{
"redirect_url": "https://pay.gateway.example/checkout/session_abc123"
}{
"error": {
"message": "<string>",
"details": {}
}
}{
"error": {
"message": "<string>",
"details": {}
}
}Endpoints
Checkout Order
Accept signed orders from merchants and return a redirect URL for hosted checkout
POST
/
orders
/
checkout
Checkout order
curl --request POST \
--url https://pay.gateway.example/opencharge/orders/checkout \
--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 '
{
"order": {
"id": "ord_xyz789",
"ocid": 500,
"reference": "checkout-session-123",
"amount": "99.99",
"currency": "USD",
"items": [
{
"id": "prod_001",
"name": "Premium Widget",
"quantity": 2,
"price": "49.99"
}
],
"memo": "Online purchase",
"createdAt": 1706313600,
"expiresAt": 1706400000,
"accepts": [
100,
101
]
},
"signature": "a1b2c3d4e5f6...merchant_sig",
"urls": {
"order": [
"https://merchant.example/orders/ord_xyz789"
],
"completed": "https://merchant.example/checkout/success",
"cancelled": "https://merchant.example/checkout/cancelled"
}
}
'import requests
url = "https://pay.gateway.example/opencharge/orders/checkout"
payload = {
"order": {
"id": "ord_xyz789",
"ocid": 500,
"reference": "checkout-session-123",
"amount": "99.99",
"currency": "USD",
"items": [
{
"id": "prod_001",
"name": "Premium Widget",
"quantity": 2,
"price": "49.99"
}
],
"memo": "Online purchase",
"createdAt": 1706313600,
"expiresAt": 1706400000,
"accepts": [100, 101]
},
"signature": "a1b2c3d4e5f6...merchant_sig",
"urls": {
"order": ["https://merchant.example/orders/ord_xyz789"],
"completed": "https://merchant.example/checkout/success",
"cancelled": "https://merchant.example/checkout/cancelled"
}
}
headers = {
"X-OC-ID": "<x-oc-id>",
"X-OC-Timestamp": "<x-oc-timestamp>",
"X-OC-Nonce": "<x-oc-nonce>",
"X-OC-Signature": "<x-oc-signature>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-OC-ID': '<x-oc-id>',
'X-OC-Timestamp': '<x-oc-timestamp>',
'X-OC-Nonce': '<x-oc-nonce>',
'X-OC-Signature': '<x-oc-signature>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
order: {
id: 'ord_xyz789',
ocid: 500,
reference: 'checkout-session-123',
amount: '99.99',
currency: 'USD',
items: [{id: 'prod_001', name: 'Premium Widget', quantity: 2, price: '49.99'}],
memo: 'Online purchase',
createdAt: 1706313600,
expiresAt: 1706400000,
accepts: [100, 101]
},
signature: 'a1b2c3d4e5f6...merchant_sig',
urls: {
order: ['https://merchant.example/orders/ord_xyz789'],
completed: 'https://merchant.example/checkout/success',
cancelled: 'https://merchant.example/checkout/cancelled'
}
})
};
fetch('https://pay.gateway.example/opencharge/orders/checkout', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pay.gateway.example/opencharge/orders/checkout",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'order' => [
'id' => 'ord_xyz789',
'ocid' => 500,
'reference' => 'checkout-session-123',
'amount' => '99.99',
'currency' => 'USD',
'items' => [
[
'id' => 'prod_001',
'name' => 'Premium Widget',
'quantity' => 2,
'price' => '49.99'
]
],
'memo' => 'Online purchase',
'createdAt' => 1706313600,
'expiresAt' => 1706400000,
'accepts' => [
100,
101
]
],
'signature' => 'a1b2c3d4e5f6...merchant_sig',
'urls' => [
'order' => [
'https://merchant.example/orders/ord_xyz789'
],
'completed' => 'https://merchant.example/checkout/success',
'cancelled' => 'https://merchant.example/checkout/cancelled'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-OC-ID: <x-oc-id>",
"X-OC-Nonce: <x-oc-nonce>",
"X-OC-Signature: <x-oc-signature>",
"X-OC-Timestamp: <x-oc-timestamp>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://pay.gateway.example/opencharge/orders/checkout"
payload := strings.NewReader("{\n \"order\": {\n \"id\": \"ord_xyz789\",\n \"ocid\": 500,\n \"reference\": \"checkout-session-123\",\n \"amount\": \"99.99\",\n \"currency\": \"USD\",\n \"items\": [\n {\n \"id\": \"prod_001\",\n \"name\": \"Premium Widget\",\n \"quantity\": 2,\n \"price\": \"49.99\"\n }\n ],\n \"memo\": \"Online purchase\",\n \"createdAt\": 1706313600,\n \"expiresAt\": 1706400000,\n \"accepts\": [\n 100,\n 101\n ]\n },\n \"signature\": \"a1b2c3d4e5f6...merchant_sig\",\n \"urls\": {\n \"order\": [\n \"https://merchant.example/orders/ord_xyz789\"\n ],\n \"completed\": \"https://merchant.example/checkout/success\",\n \"cancelled\": \"https://merchant.example/checkout/cancelled\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-OC-ID", "<x-oc-id>")
req.Header.Add("X-OC-Timestamp", "<x-oc-timestamp>")
req.Header.Add("X-OC-Nonce", "<x-oc-nonce>")
req.Header.Add("X-OC-Signature", "<x-oc-signature>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://pay.gateway.example/opencharge/orders/checkout")
.header("X-OC-ID", "<x-oc-id>")
.header("X-OC-Timestamp", "<x-oc-timestamp>")
.header("X-OC-Nonce", "<x-oc-nonce>")
.header("X-OC-Signature", "<x-oc-signature>")
.header("Content-Type", "application/json")
.body("{\n \"order\": {\n \"id\": \"ord_xyz789\",\n \"ocid\": 500,\n \"reference\": \"checkout-session-123\",\n \"amount\": \"99.99\",\n \"currency\": \"USD\",\n \"items\": [\n {\n \"id\": \"prod_001\",\n \"name\": \"Premium Widget\",\n \"quantity\": 2,\n \"price\": \"49.99\"\n }\n ],\n \"memo\": \"Online purchase\",\n \"createdAt\": 1706313600,\n \"expiresAt\": 1706400000,\n \"accepts\": [\n 100,\n 101\n ]\n },\n \"signature\": \"a1b2c3d4e5f6...merchant_sig\",\n \"urls\": {\n \"order\": [\n \"https://merchant.example/orders/ord_xyz789\"\n ],\n \"completed\": \"https://merchant.example/checkout/success\",\n \"cancelled\": \"https://merchant.example/checkout/cancelled\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://pay.gateway.example/opencharge/orders/checkout")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-OC-ID"] = '<x-oc-id>'
request["X-OC-Timestamp"] = '<x-oc-timestamp>'
request["X-OC-Nonce"] = '<x-oc-nonce>'
request["X-OC-Signature"] = '<x-oc-signature>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"order\": {\n \"id\": \"ord_xyz789\",\n \"ocid\": 500,\n \"reference\": \"checkout-session-123\",\n \"amount\": \"99.99\",\n \"currency\": \"USD\",\n \"items\": [\n {\n \"id\": \"prod_001\",\n \"name\": \"Premium Widget\",\n \"quantity\": 2,\n \"price\": \"49.99\"\n }\n ],\n \"memo\": \"Online purchase\",\n \"createdAt\": 1706313600,\n \"expiresAt\": 1706400000,\n \"accepts\": [\n 100,\n 101\n ]\n },\n \"signature\": \"a1b2c3d4e5f6...merchant_sig\",\n \"urls\": {\n \"order\": [\n \"https://merchant.example/orders/ord_xyz789\"\n ],\n \"completed\": \"https://merchant.example/checkout/success\",\n \"cancelled\": \"https://merchant.example/checkout/cancelled\"\n }\n}"
response = http.request(request)
puts response.read_body{
"redirect_url": "https://pay.gateway.example/checkout/session_abc123"
}{
"error": {
"message": "<string>",
"details": {}
}
}{
"error": {
"message": "<string>",
"details": {}
}
}This is the core endpoint for hosted checkout. Merchants POST their signed orders, and you return a URL to your payment page.
Flow
- Merchant creates and signs an order
- Merchant POSTs order to your
/orders/checkout - You verify the signature and create a checkout session
- You return a
redirect_urlto your payment page - Merchant redirects their customer to your page
- Customer completes payment on your hosted UI
- You send signed proof to merchant’s
/transfer/webhook - You redirect customer back to merchant’s
completedURL
Implementation
app.post('/orders/checkout', verifyAuth, async (req, res) => {
const { order, signature, urls } = req.body;
const merchantOcid = parseInt(req.headers['x-oc-id']);
// 1. Verify merchant is registered
const merchant = await db.getMerchant(merchantOcid);
if (!merchant) {
return res.status(401).json({
error: { code: 'MERCHANT_NOT_REGISTERED', message: 'Unknown merchant' }
});
}
// 2. Verify order signature
const publicKey = merchant.publicKey;
const canonical = JSON.stringify(order, Object.keys(order).sort());
if (!secp256k1_verify(publicKey, signature, SHA256(canonical))) {
return res.status(400).json({
error: { code: 'INVALID_SIGNATURE', message: 'Order signature invalid' }
});
}
// 3. Validate order
if (order.expiresAt && order.expiresAt < Date.now() / 1000) {
return res.status(400).json({
error: { code: 'ORDER_EXPIRED', message: 'Order has expired' }
});
}
// 4. Check we can settle with merchant
if (!order.accepts.includes(YOUR_OCID)) {
return res.status(400).json({
error: { code: 'SETTLEMENT_NOT_SUPPORTED', message: 'Merchant does not accept this gateway' }
});
}
// 5. Create checkout session
const session = await db.createSession({
id: generateSessionId(),
merchantOcid,
order,
signature,
urls,
status: 'pending',
createdAt: Date.now()
});
// 6. Return redirect URL
res.json({
redirect_url: `https://pay.yourgateway.com/checkout/${session.id}`
});
});
Validation Checklist
Before accepting an order:- Merchant is registered with your gateway
- Order signature is valid (signed by merchant’s private key)
- Order has not expired (
expiresAt) - Your OCID is in the
acceptsarray - Currency is supported
- Amount is reasonable (fraud checks)
Hosted Payment Page
On your payment page (/checkout/{sessionId}):
- Retrieve the session and order
- Display order details (items, amount, merchant name)
- Present payment options (cards, wallets, bank transfer)
- Process payment using your internal systems
- On success, call merchant’s webhook and redirect to
urls.completed - On failure/cancel, redirect to
urls.cancelled
Always verify the session hasn’t expired and hasn’t already been paid before processing payment.
Headers
Caller's OCID (Opencharge ID)
Unix timestamp in seconds
Unique request identifier for replay protection
secp256k1 ECDSA signature of the canonical request
Body
application/json
Response
Checkout session created, redirect URL returned
Response with redirect URL for hosted checkout
URL to redirect customer to complete payment
⌘I