Payment Infrastructure
for You
Integrate payin and payout capabilities with a single, unified API. RSA-secured, real-time webhooks.
Core Capabilities
Payin (Collection)
Accept payments via bank transfer, QRIS, and e-wallets. Hosted cashier page with configurable redirect.
Payout (Disbursement)
Send funds to 150+ Indonesian banks and e-wallets. Flexible fee handling with real-time status tracking.
RSA Encryption
Every request is signed with RSA-2048. Callback verification ensures data integrity end-to-end.
Authentication
All API requests must include two headers for authentication. The signature is generated using RSA-2048 with SHA-256 hashing.
| Header | Type | Description |
|---|---|---|
SIGN |
string | RSA signature of the request body (Base64 encoded) |
MCODE |
string | Your merchant code (e.g. MT182504LVhvaB) |
Signature Generation
key=value pairs joined by &// Sorted parameter string:
amount=1000000&email=t.wcvji@ynnid.bn&merchantOid=7438bb79-...&name=test1¬ifyUrl=http://...&phone=18144528972&redirectUrl=https://...×tamp=12312311&type=payin&uid=28835218241114223x
// SHA-256 hash:
383fb0663e87ebae7c5e1debb68746eab0f73a16ec82be3d5ca388bec0254691
// RSA-signed (Base64):
a/WAwsxJQvYbfN18DHmK93RQbBQAQXLpETCBzrkK8a1cy+6M9BDX...=
Quick Start
Create your first payin order in minutes.
curl -X POST https://{domain}/api/v1/payin/create \
-H "Content-Type: application/json" \
-H "SIGN: <your_rsa_signature>" \
-H "MCODE: MT182504LVhvaB" \
-d '{
"uid": "user_2883",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": 1745723235,
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"passage": "",
"type": "payin"
}'
import crypto from 'crypto';
const createPayin = async () => {
const body = {
uid: "user_2883",
merchantOid: crypto.randomUUID(),
amount: 1000000,
notifyUrl: "https://your-server.com/callback/payin",
redirectUrl: "https://your-app.com/payment/success",
timestamp: Math.floor(Date.now() / 1000),
name: "Taylor",
email: "taylor@example.com",
phone: "08123456789",
passage: "",
type: "payin"
};
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://{domain}/api/v1/payin/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
"SIGN": sign,
"MCODE": "MT182504LVhvaB"
},
body: JSON.stringify(body)
});
return res.json(); // { code: 0, data: { oid, merchantOid, url } }
};
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"net/http"
)
func createPayin() {
body := map[string]interface{}{
"uid": "user_2883",
"merchantOid": uuid.New().String(),
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": time.Now().Unix(),
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"type": "payin",
}
sorted := jsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sign, _ := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sign)
req, _ := http.NewRequest("POST",
"https://{domain}/api/v1/payin/create", body)
req.Header.Set("SIGN", signB64)
req.Header.Set("MCODE", "MT182504LVhvaB")
}
import hashlib, base64, json, time, uuid, requests
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
def create_payin():
body = {
"uid": "user_2883",
"merchantOid": str(uuid.uuid4()),
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": int(time.time()),
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"type": "payin"
}
# Sort, hash, sign
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post(
"https://{domain}/api/v1/payin/create",
json=body,
headers={
"SIGN": sign_b64,
"MCODE": "MT182504LVhvaB"
}
)
return resp.json()
import java.security.*;
import java.util.*;
public class PaymentAggregationSystem {
public static String createPayin() throws Exception {
Map<String, Object> body = new TreeMap<>();
body.put("uid", "user_2883");
body.put("merchantOid", UUID.randomUUID().toString());
body.put("amount", 1000000);
body.put("notifyUrl", "https://your-server.com/callback/payin");
body.put("redirectUrl", "https://your-app.com/payment/success");
body.put("timestamp", System.currentTimeMillis() / 1000);
body.put("name", "Taylor");
body.put("email", "taylor@example.com");
body.put("phone", "08123456789");
body.put("type", "payin");
// Sign: sort → SHA256 → RSA PKCS1v15 → Base64
String sorted = body.entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST with headers SIGN + MCODE
return httpPost("https://{domain}/api/v1/payin/create",
body, signB64, "MT182504LVhvaB");
}
}
$body = [
"uid" => "user_2883",
"merchantOid" => sprintf("%s-%s-%s-%s-%s", ...), // UUID
"amount" => 1000000,
"notifyUrl" => "https://your-server.com/callback/payin",
"redirectUrl" => "https://your-app.com/payment/success",
"timestamp" => time(),
"name" => "Taylor", "email" => "taylor@example.com",
"phone" => "08123456789", "type" => "payin"
];
$sign = PaymentAggregationSystem::sign($body, $privateKeyPem);
$ch = curl_init("https://{domain}/api/v1/payin/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"SIGN: " . $sign,
"MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
{
"code": 0,
"data": {
"oid": "CLN270425tR53wb2W",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"url": "https://cashier.xxx.xxx/index.html?oid=CLN270425tR53wb2W"
},
"msg": "Operation successful"
}
API Reference
Base URL: https://{domain}/api/v1
/payin/create
Create a payin (collection) order. Returns a cashier URL for the user to complete payment.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
uid | string | Yes | User identifier |
merchantOid | string | Yes | Unique merchant order ID (UUID recommended) |
amount | integer | Yes | Amount in cents (e.g. 1000000 = IDR 10,000) |
notifyUrl | string | Yes | Webhook URL for async notifications |
redirectUrl | string | Yes | Redirect URL after payment |
timestamp | integer | Yes | Unix timestamp (seconds) |
name | string | Yes | Customer name |
email | string | Yes | Customer email |
phone | string | Yes | Customer phone |
passage | string | No | Payment channel code. If set, skips channel selection on cashier |
type | string | Yes | Fixed: "payin" |
Response
| Field | Type | Description |
|---|---|---|
code | integer | 0 on success |
data.oid | string | PaymentAggregationSystem system order ID |
data.merchantOid | string | Your merchant order ID (echo back) |
data.url | string | Cashier page URL — redirect the user here |
msg | string | "Operation successful" |
Code Examples
curl -X POST https://{domain}/api/v1/payin/create \
-H "Content-Type: application/json" \
-H "SIGN: <your_rsa_signature>" \
-H "MCODE: YOUR_MERCHANT_CODE" \
-d '{
"uid": "user_2883",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": 1745723235,
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"passage": "",
"type": "payin"
}'
const createPayin = async () => {
const body = {
uid: "user_2883",
merchantOid: crypto.randomUUID(),
amount: 1000000,
notifyUrl: "https://your-server.com/callback/payin",
redirectUrl: "https://your-app.com/payment/success",
timestamp: Math.floor(Date.now() / 1000),
name: "Taylor", email: "taylor@example.com",
phone: "08123456789", passage: "", type: "payin"
};
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://{domain}/api/v1/payin/create", {
method: "POST",
headers: { "Content-Type": "application/json", "SIGN": sign, "MCODE": MCODE },
body: JSON.stringify(body)
});
return res.json();
};
body := map[string]interface{}{
"uid": "user_2883", "merchantOid": uuid.New().String(),
"amount": 1000000, "notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": time.Now().Unix(), "name": "Taylor",
"email": "taylor@example.com", "phone": "08123456789",
"type": "payin",
}
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sig)
req, _ := http.NewRequest("POST", "https://{domain}/api/v1/payin/create", jsonBody)
req.Header.Set("SIGN", signB64)
req.Header.Set("MCODE", mcode)
body = {
"uid": "user_2883", "merchantOid": str(uuid.uuid4()),
"amount": 1000000, "notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": int(time.time()), "name": "Taylor",
"email": "taylor@example.com", "phone": "08123456789",
"type": "payin"
}
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post("https://{domain}/api/v1/payin/create",
json=body, headers={"SIGN": sign_b64, "MCODE": MCODE})
Map<String, Object> body = new TreeMap<>();
body.put("uid", "user_2883");
body.put("merchantOid", UUID.randomUUID().toString());
body.put("amount", 1000000);
body.put("notifyUrl", "https://your-server.com/callback/payin");
body.put("redirectUrl", "https://your-app.com/payment/success");
body.put("timestamp", System.currentTimeMillis() / 1000);
body.put("name", "Taylor"); body.put("email", "taylor@example.com");
body.put("phone", "08123456789"); body.put("type", "payin");
String sorted = body.entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey); sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST with headers SIGN + MCODE
$body = [
"uid" => "user_2883",
"merchantOid" => sprintf("%s-%s-%s-%s-%s", ...), // UUID
"amount" => 1000000,
"notifyUrl" => "https://your-server.com/callback/payin",
"redirectUrl" => "https://your-app.com/payment/success",
"timestamp" => time(),
"name" => "Taylor", "email" => "taylor@example.com",
"phone" => "08123456789", "type" => "payin"
];
$sign = PaymentAggregationSystem::sign($body, $privateKeyPem);
$ch = curl_init("https://{domain}/api/v1/payin/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"SIGN: " . $sign,
"MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
{
"code": 0,
"data": {
"oid": "CLN270425tR53wb2W",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"url": "https://cashier.xxx.xxx/index.html?oid=CLN270425tR53wb2W"
},
"msg": "Operation successful"
}
/payout/create
Create a payout (disbursement) order to send funds to a bank account or e-wallet.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
uid | string | Yes | User identifier |
name | string | Yes | Beneficiary name |
merchantOid | string | Yes | Unique merchant order ID |
amount | integer | Yes | Amount in cents |
bankCode | string | Yes | Bank code (see Bank Codes) |
accountNo | string | Yes | Beneficiary account number |
email | string | No | Beneficiary email |
notifyUrl | string | Yes | Webhook URL for status updates |
note | string | No | Remark / description |
feeType | string | Yes | "Additional" (fee on top) or "Include" (fee deducted from amount) |
passage | string | Yes | Payment channel code |
type | string | Yes | Fixed: "payout" |
Code Examples
curl -X POST https://{domain}/api/v1/payout/create \
-H "Content-Type: application/json" \
-H "SIGN: <your_rsa_signature>" \
-H "MCODE: YOUR_MERCHANT_CODE" \
-d '{
"uid": "3800",
"name": "John Doe",
"notifyUrl": "https://your-server.com/callback/payout",
"amount": 1500000,
"merchantOid": "d27eb86d-ada8-43cb-9d31-fa87dd578ab7",
"bankCode": "014",
"accountNo": "1234567890",
"email": "john@example.com",
"note": "Withdrawal request",
"type": "payout",
"feeType": "Additional",
"passage": "your_passage_code"
}'
const createPayout = async () => {
const body = {
uid: "3800", name: "John Doe",
notifyUrl: "https://your-server.com/callback/payout",
amount: 1500000,
merchantOid: crypto.randomUUID(),
bankCode: "014", // BCA
accountNo: "1234567890",
email: "john@example.com",
note: "Withdrawal request",
type: "payout",
feeType: "Additional",
passage: "your_passage_code"
};
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://{domain}/api/v1/payout/create", {
method: "POST",
headers: { "Content-Type": "application/json", "SIGN": sign, "MCODE": MCODE },
body: JSON.stringify(body)
});
return res.json();
};
body := map[string]interface{}{
"uid": "3800", "name": "John Doe",
"notifyUrl": "https://your-server.com/callback/payout",
"amount": 1500000,
"merchantOid": uuid.New().String(),
"bankCode": "014", // BCA
"accountNo": "1234567890",
"email": "john@example.com",
"note": "Withdrawal request",
"type": "payout",
"feeType": "Additional",
"passage": "your_passage_code",
}
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sig)
req, _ := http.NewRequest("POST", "https://{domain}/api/v1/payout/create", jsonBody)
req.Header.Set("SIGN", signB64)
req.Header.Set("MCODE", mcode)
body = {
"uid": "3800", "name": "John Doe",
"notifyUrl": "https://your-server.com/callback/payout",
"amount": 1500000,
"merchantOid": str(uuid.uuid4()),
"bankCode": "014", # BCA
"accountNo": "1234567890",
"email": "john@example.com",
"note": "Withdrawal request",
"type": "payout",
"feeType": "Additional",
"passage": "your_passage_code"
}
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post("https://{domain}/api/v1/payout/create",
json=body, headers={"SIGN": sign_b64, "MCODE": MCODE})
Map<String, Object> body = new TreeMap<>();
body.put("uid", "3800"); body.put("name", "John Doe");
body.put("notifyUrl", "https://your-server.com/callback/payout");
body.put("amount", 1500000);
body.put("merchantOid", UUID.randomUUID().toString());
body.put("bankCode", "014"); // BCA
body.put("accountNo", "1234567890");
body.put("email", "john@example.com");
body.put("note", "Withdrawal request");
body.put("type", "payout");
body.put("feeType", "Additional");
body.put("passage", "your_passage_code");
String sorted = body.entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey); sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST with headers SIGN + MCODE
$body = [
"uid" => "3800", "name" => "John Doe",
"notifyUrl" => "https://your-server.com/callback/payout",
"amount" => 1500000,
"merchantOid" => uniqid("", true),
"bankCode" => "014", // BCA
"accountNo" => "1234567890",
"email" => "john@example.com",
"note" => "Withdrawal request",
"type" => "payout",
"feeType" => "Additional",
"passage" => "your_passage_code"
];
$sign = PaymentAggregationSystem::sign($body, $privateKeyPem);
$ch = curl_init("https://{domain}/api/v1/payout/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"SIGN: " . $sign,
"MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
{
"code": 0,
"data": {
"oid": "PAY200325mb7gKfOI",
"merchantOid": "d27eb86d-ada8-43cb-9d31-fa87dd578ab7"
},
"msg": "Operation successful"
}
/query/order
Query the status of a payin or payout order. Provide either oid or merchantOid.
| Field | Type | Description |
|---|---|---|
type | string | "payin" or "payout" |
oid | string | PaymentAggregationSystem system order ID (either oid or merchantOid) |
merchantOid | string | Your merchant order ID (either oid or merchantOid) |
Code Examples
curl -X POST https://{domain}/api/v1/query/order \
-H "Content-Type: application/json" \
-H "SIGN: <your_rsa_signature>" \
-H "MCODE: YOUR_MERCHANT_CODE" \
-d '{
"type": "payin",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101"
}'
const queryOrder = async (type, merchantOid) => {
const body = { type, merchantOid };
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://{domain}/api/v1/query/order", {
method: "POST",
headers: { "Content-Type": "application/json", "SIGN": sign, "MCODE": MCODE },
body: JSON.stringify(body)
});
return res.json();
};
// Usage
const result = await queryOrder("payin", "9d6e7671-64b9-408b-abad-1ef4601af101");
body := map[string]interface{}{
"type": "payin",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
}
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
req, _ := http.NewRequest("POST", "https://{domain}/api/v1/query/order", jsonBody)
req.Header.Set("SIGN", base64.StdEncoding.EncodeToString(sig))
req.Header.Set("MCODE", mcode)
def query_order(order_type, merchant_oid):
body = {"type": order_type, "merchantOid": merchant_oid}
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post("https://{domain}/api/v1/query/order",
json=body, headers={"SIGN": sign_b64, "MCODE": MCODE})
return resp.json()
# Usage
result = query_order("payin", "9d6e7671-64b9-408b-abad-1ef4601af101")
Map<String, Object> body = new TreeMap<>();
body.put("type", "payin");
body.put("merchantOid", "9d6e7671-64b9-408b-abad-1ef4601af101");
String sorted = body.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey); sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST to /query/order with headers SIGN + MCODE
$body = [
"type" => "payin",
"merchantOid" => "9d6e7671-64b9-408b-abad-1ef4601af101"
];
$sign = PaymentAggregationSystem::sign($body, $privateKeyPem);
$ch = curl_init("https://{domain}/api/v1/query/order");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"SIGN: " . $sign,
"MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
// Payin order query response
{
"code": 0,
"data": {
"amount": 1000000,
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"orderOid": "CLN270425tR53wb2W",
"timestamp": 1745723300,
"status": "order_success",
"settleStatus": "settle_success"
},
"msg": "Operation successful"
}
/query/balance
Query merchant account balance. For this endpoint, sign the MCODE value itself.
Response
| Field | Type | Description |
|---|---|---|
code | integer | Status code (0 = success) |
amount | integer | Available balance (cents) |
settleAmount | integer | Pending settlement balance (cents) |
freezeAmount | integer | Frozen balance (cents) |
Code Examples
# Note: SIGN is the RSA signature of your MCODE value itself
curl -X GET https://{domain}/api/v1/query/balance \
-H "SIGN: <rsa_sign_of_mcode>" \
-H "MCODE: YOUR_MERCHANT_CODE"
const queryBalance = async () => {
// For balance query, sign the MCODE value itself
const hash = crypto.createHash("sha256").update(MCODE).digest("hex");
const sign = crypto.sign("sha256", Buffer.from(hash), privateKey);
const signB64 = sign.toString("base64");
const res = await fetch("https://{domain}/api/v1/query/balance", {
headers: { "SIGN": signB64, "MCODE": MCODE }
});
return res.json();
// { code: 0, amount: 50000000, settleAmount: 10000000, freezeAmount: 0 }
};
// For balance query, sign the MCODE value itself
hash := sha256.Sum256([]byte(mcode))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sig)
req, _ := http.NewRequest("GET", "https://{domain}/api/v1/query/balance", nil)
req.Header.Set("SIGN", signB64)
req.Header.Set("MCODE", mcode)
def query_balance():
# For balance query, sign the MCODE value itself
hash_hex = hashlib.sha256(MCODE.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.get("https://{domain}/api/v1/query/balance",
headers={"SIGN": sign_b64, "MCODE": MCODE})
return resp.json()
# {"code": 0, "amount": 50000000, "settleAmount": 10000000, "freezeAmount": 0}
// For balance query, sign the MCODE value itself
String hash = sha256Hex(MCODE);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
HttpURLConnection conn = (HttpURLConnection)
new URL("https://{domain}/api/v1/query/balance").openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("SIGN", signB64);
conn.setRequestProperty("MCODE", MCODE);
// For balance query, sign the MCODE value itself
$hashHex = hash("sha256", $mcode);
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_sign($hashHex, $signature, $privKey, OPENSSL_ALGO_SHA256);
$signB64 = base64_encode($signature);
$ch = curl_init("https://{domain}/api/v1/query/balance");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"SIGN: " . $signB64,
"MCODE: " . $mcode
],
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
// {"code": 0, "amount": 50000000, "settleAmount": 10000000, "freezeAmount": 0}
{
"code": 0,
"amount": 50000000,
"settleAmount": 10000000,
"freezeAmount": 0
}
Webhooks
PaymentAggregationSystem sends asynchronous notifications to your notifyUrl when order status changes. You must respond with the string SUCCESS, otherwise PaymentAggregationSystem will retry up to 5 times.
SIGN header. The decrypted value should match the SHA-256 hash of the sorted callback body parameters. Ensure idempotent handling — the same notification may arrive multiple times.
Payin Callback
Sent when a payin order is paid or settled. Non-realtime settlement orders may trigger two separate callbacks.
| Field | Type | Description |
|---|---|---|
amount | integer | Order amount (cents) |
merchantOid | string | Your merchant order ID |
orderOid | string | PaymentAggregationSystem system order ID |
timestamp | integer | Unix timestamp |
status | string | Order status (see below) |
settleStatus | string | Settlement status (see below) |
order_success | Payment received |
order_fail | Payment failed |
order_await | In progress |
settle_success | Settled |
settle_await | Pending settlement |
settle_unknown | N/A (order not successful) |
{
"amount": 1000000,
"merchantOid": "cf75c3a8-6028-4a62-9722-2cd5bc4b820c",
"orderOid": "CLN250325iFgO5Ibt",
"timestamp": 1742895347,
"status": "order_success",
"settleStatus": "settle_success"
}
# Verification steps:
# 1. Receive POST with headers SIGN and MCODE
# 2. Sort body params by key → concatenate as key=value&key=value
# 3. SHA-256 hash the sorted string
# 4. Decrypt SIGN with your RSA private key (PKCS1v15)
# 5. Compare decrypted value with your hash
# 6. If match → process order → respond with "SUCCESS"
# 7. If mismatch → reject the callback
// Express.js handler
app.post("/callback/payin", async (req, res) => {
const sign = req.headers["SIGN"];
const body = req.body;
// Sort params, hash, verify
const sorted = Object.keys(body).sort()
.filter(k => body[k] !== "")
.map(k => `${k}=${body[k]}`).join("&");
const hash = crypto.createHash("sha256").update(sorted).digest("hex");
// Decrypt signature with private key
const decrypted = crypto.privateDecrypt(
{ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(sign, "base64")
).toString();
if (decrypted !== hash) return res.status(400).send("FAIL");
// Process order (idempotent!)
if (body.status === "order_success") {
await updateOrderStatus(body.merchantOid, body.status, body.settleStatus);
}
res.send("SUCCESS");
});
func PayinCallback(c *gin.Context) {
sign := c.GetHeader("SIGN")
var body map[string]interface{}
c.ShouldBindJSON(&body)
// Sort params, hash
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
// Decrypt SIGN with private key
decrypted, err := DecryptWithPrivateKey(privKey, sign)
if err != nil || decrypted != hashHex {
c.String(400, "FAIL")
return
}
// Process order (idempotent)
status := body["status"].(string)
if status == "order_success" {
updateOrderStatus(body["merchantOid"].(string), status)
}
c.String(200, "SUCCESS")
}
# Flask handler
@app.route("/callback/payin", methods=["POST"])
def payin_callback():
sign = request.headers.get("SIGN")
body = request.get_json()
# Sort params, hash
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if str(v))
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
# Decrypt signature with private key
key = RSA.import_key(open("private.pem").read())
cipher = PKCS1_v1_5.new(key)
decrypted = cipher.decrypt(base64.b64decode(sign), None).decode()
if decrypted != hash_hex:
return "FAIL", 400
# Process order (idempotent)
if body["status"] == "order_success":
update_order(body["merchantOid"], body["status"], body["settleStatus"])
return "SUCCESS"
// Spring Boot handler
@PostMapping("/callback/payin")
public String payinCallback(
@RequestHeader("SIGN") String sign,
@RequestBody Map<String, Object> body) {
// Sort params, hash
String sorted = new TreeMap<>(body).entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
// Decrypt signature with private key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
String decrypted = new String(cipher.doFinal(Base64.getDecoder().decode(sign)));
if (!decrypted.equals(hash)) return "FAIL";
// Process order (idempotent)
if ("order_success".equals(body.get("status"))) {
updateOrder(body.get("merchantOid"), body.get("status"));
}
return "SUCCESS";
}
// Laravel/raw PHP callback handler
$sign = $_SERVER['HTTP_PaymentAggregationSystem_SIGN'] ?? '';
$body = json_decode(file_get_contents('php://input'), true);
// Sort params, hash
ksort($body);
$pairs = [];
foreach ($body as $k => $v) {
if ((string)$v !== '') $pairs[] = "$k=$v";
}
$sorted = implode('&', $pairs);
$hashHex = hash('sha256', $sorted);
// Decrypt signature with private key
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_private_decrypt(base64_decode($sign), $decrypted, $privKey);
if ($decrypted !== $hashHex) {
http_response_code(400);
echo "FAIL"; exit;
}
// Process order (idempotent)
if ($body['status'] === 'order_success') {
updateOrder($body['merchantOid'], $body['status'], $body['settleStatus']);
}
echo "SUCCESS";
Payout Callback
Sent when a payout order status changes.
| Field | Type | Description |
|---|---|---|
amount | integer | Order amount (cents) |
merchantOid | string | Your merchant order ID |
orderOid | string | PaymentAggregationSystem system order ID |
timestamp | integer | Unix timestamp |
status | string | order_success / order_fail / order_await |
{
"amount": 1500000,
"merchantOid": "6a6ab062-9a35-4a07-87cd-5ca0492c338c",
"orderOid": "PAY250325DoNhh8AY",
"timestamp": 1742899401,
"status": "order_success"
}
# Same verification flow as payin callback:
# 1. Sort body params by key → key=value&key=value
# 2. SHA-256 hash the sorted string
# 3. Decrypt SIGN with RSA private key
# 4. Compare → if match, process and respond "SUCCESS"
# Note: payout callback has no settleStatus field
app.post("/callback/payout", async (req, res) => {
const sign = req.headers["SIGN"];
const body = req.body;
const sorted = Object.keys(body).sort()
.filter(k => body[k] !== "")
.map(k => `${k}=${body[k]}`).join("&");
const hash = crypto.createHash("sha256").update(sorted).digest("hex");
const decrypted = crypto.privateDecrypt(
{ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(sign, "base64")
).toString();
if (decrypted !== hash) return res.status(400).send("FAIL");
if (body.status === "order_success") {
await completeWithdrawal(body.merchantOid, body.amount);
} else if (body.status === "order_fail") {
await refundWithdrawal(body.merchantOid);
}
res.send("SUCCESS");
});
func PayoutCallback(c *gin.Context) {
sign := c.GetHeader("SIGN")
var body map[string]interface{}
c.ShouldBindJSON(&body)
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
decrypted, err := DecryptWithPrivateKey(privKey, sign)
if err != nil || decrypted != hashHex {
c.String(400, "FAIL")
return
}
status := body["status"].(string)
switch status {
case "order_success":
completeWithdrawal(body["merchantOid"].(string))
case "order_fail":
refundWithdrawal(body["merchantOid"].(string))
}
c.String(200, "SUCCESS")
}
@app.route("/callback/payout", methods=["POST"])
def payout_callback():
sign = request.headers.get("SIGN")
body = request.get_json()
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if str(v))
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
cipher = PKCS1_v1_5.new(key)
decrypted = cipher.decrypt(base64.b64decode(sign), None).decode()
if decrypted != hash_hex:
return "FAIL", 400
if body["status"] == "order_success":
complete_withdrawal(body["merchantOid"])
elif body["status"] == "order_fail":
refund_withdrawal(body["merchantOid"])
return "SUCCESS"
@PostMapping("/callback/payout")
public String payoutCallback(
@RequestHeader("SIGN") String sign,
@RequestBody Map<String, Object> body) {
String sorted = new TreeMap<>(body).entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
String decrypted = new String(cipher.doFinal(Base64.getDecoder().decode(sign)));
if (!decrypted.equals(hash)) return "FAIL";
switch (body.get("status").toString()) {
case "order_success": completeWithdrawal(body); break;
case "order_fail": refundWithdrawal(body); break;
}
return "SUCCESS";
}
$sign = $_SERVER['HTTP_PaymentAggregationSystem_SIGN'] ?? '';
$body = json_decode(file_get_contents('php://input'), true);
ksort($body);
$pairs = [];
foreach ($body as $k => $v) {
if ((string)$v !== '') $pairs[] = "$k=$v";
}
$sorted = implode('&', $pairs);
$hashHex = hash('sha256', $sorted);
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_private_decrypt(base64_decode($sign), $decrypted, $privKey);
if ($decrypted !== $hashHex) {
http_response_code(400);
echo "FAIL"; exit;
}
switch ($body['status']) {
case 'order_success': completeWithdrawal($body); break;
case 'order_fail': refundWithdrawal($body); break;
}
echo "SUCCESS";
Signature Utilities
Complete signing and verification implementations. Copy these utility functions into your project — no SDK installation required.
package PaymentAggregationSystem
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"sort"
"strings"
)
// JsonToSortedString converts a JSON string to a sorted key=value&key=value string
func JsonToSortedString(jsonStr string) (string, error) {
var data map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
return "", err
}
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
pairs := make([]string, 0, len(keys))
for _, k := range keys {
var v string
switch val := data[k].(type) {
case string:
v = val
case float64:
v = fmt.Sprintf("%v", int64(val))
default:
v = fmt.Sprintf("%v", val)
}
if v != "" {
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
}
}
return strings.Join(pairs, "&"), nil
}
// StringToSHA256 returns the hex-encoded SHA-256 hash
func StringToSHA256(s string) string {
hash := sha256.Sum256([]byte(s))
return hex.EncodeToString(hash[:])
}
// ImportPrivateKey imports an RSA private key from PEM (PKCS#1 or PKCS#8)
func ImportPrivateKey(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse private key PEM")
}
switch block.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(block.Bytes)
case "PRIVATE KEY":
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil { return nil, err }
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok { return nil, errors.New("not an RSA private key") }
return rsaKey, nil
default:
return nil, errors.New("unknown private key format")
}
}
// SignWithPrivateKey signs a message with RSA PKCS1v15 and returns Base64
func SignWithPrivateKey(privateKey *rsa.PrivateKey, message string) (string, error) {
hashed := sha256.Sum256([]byte(message))
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:])
if err != nil { return "", err }
return base64.StdEncoding.EncodeToString(signature), nil
}
// DecryptWithPrivateKey decrypts Base64-encoded ciphertext with RSA PKCS1v15
func DecryptWithPrivateKey(privateKey *rsa.PrivateKey, ciphertextBase64 string) (string, error) {
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64)
if err != nil { return "", err }
plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext)
if err != nil { return "", err }
return string(plaintext), nil
}
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;
import javax.crypto.Cipher;
public class PaymentAggregationSystem {
/** Sort JSON params → key=value&key=value */
public static String jsonToSortedString(Map<String, Object> data) {
return new TreeMap<>(data).entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
}
/** SHA-256 hex digest */
public static String stringToSHA256(String input) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes());
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
String h = Integer.toHexString(0xff & b);
if (h.length() == 1) hex.append('0');
hex.append(h);
}
return hex.toString();
}
/** Import RSA private key from PKCS#8 PEM */
public static PrivateKey importPrivateKey(String privPEM) throws Exception {
String cleaned = privPEM
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] encoded = Base64.getDecoder().decode(cleaned);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
return KeyFactory.getInstance("RSA").generatePrivate(spec);
}
/** RSA PKCS1v15 sign → Base64 */
public static String signWithPrivateKey(PrivateKey key, String message) throws Exception {
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(key);
sig.update(message.getBytes());
return Base64.getEncoder().encodeToString(sig.sign());
}
/** RSA PKCS1v15 decrypt → plaintext */
public static String decryptWithPrivateKey(PrivateKey key, String ciphertextB64) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(ciphertextB64));
return new String(decrypted);
}
/** Complete: sort → SHA256 → RSA sign → Base64 */
public static String generateSign(Map<String, Object> body, String privPEM) throws Exception {
String sorted = jsonToSortedString(body);
String hash = stringToSHA256(sorted);
PrivateKey pk = importPrivateKey(privPEM);
return signWithPrivateKey(pk, hash);
}
/** Verify callback: sort → SHA256 → decrypt sign → compare */
public static boolean verifyCallback(Map<String, Object> body, String signHeader, String privPEM) throws Exception {
String sorted = jsonToSortedString(body);
String hash = stringToSHA256(sorted);
PrivateKey pk = importPrivateKey(privPEM);
String decrypted = decryptWithPrivateKey(pk, signHeader);
return hash.equals(decrypted);
}
}
<?php
class PaymentAggregationSystem {
/** Sort params → key=value&key=value */
public static function jsonToSortedString($data) {
ksort($data);
$pairs = [];
foreach ($data as $k => $v) {
if ((string)$v !== '') $pairs[] = "$k=$v";
}
return implode('&', $pairs);
}
/** SHA-256 hex digest */
public static function stringToSHA256($str) {
return hash('sha256', $str);
}
/** RSA PKCS1v15 sign → Base64 */
public static function signWithPrivateKey($privateKeyPem, $message) {
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_sign($message, $signature, $privKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
/** RSA PKCS1v15 decrypt → plaintext */
public static function decryptWithPrivateKey($privateKeyPem, $ciphertextB64) {
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_private_decrypt(base64_decode($ciphertextB64), $plaintext, $privKey);
return $plaintext;
}
/** Complete: sort → SHA256 → RSA sign → Base64 */
public static function sign($body, $privateKeyPem) {
$sorted = self::jsonToSortedString($body);
$hash = self::stringToSHA256($sorted);
return self::signWithPrivateKey($privateKeyPem, $hash);
}
/** Verify callback: sort → SHA256 → decrypt sign → compare */
public static function verifyCallback($body, $signHeader, $privateKeyPem) {
$sorted = self::jsonToSortedString($body);
$hash = self::stringToSHA256($sorted);
$decrypted = self::decryptWithPrivateKey($privateKeyPem, $signHeader);
return $hash === $decrypted;
}
}
import hashlib, base64, json
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.Cipher import PKCS1_v1_5
def json_to_sorted_string(data: dict) -> str:
"""Sort params by key, join as key=value&key=value"""
return "&".join(
f"{k}={v}" for k, v in sorted(data.items())
if str(v) != ""
)
def string_to_sha256(s: str) -> str:
"""SHA-256 hex digest"""
return hashlib.sha256(s.encode()).hexdigest()
def sign_with_private_key(private_key_pem: str, message: str) -> str:
"""RSA PKCS1v15 sign -> Base64"""
key = RSA.import_key(private_key_pem)
h = SHA256.new(message.encode())
signature = pkcs1_15.new(key).sign(h)
return base64.b64encode(signature).decode()
def decrypt_with_private_key(private_key_pem: str, ciphertext_b64: str) -> str:
"""RSA PKCS1v15 decrypt -> plaintext"""
key = RSA.import_key(private_key_pem)
cipher = PKCS1_v1_5.new(key)
ciphertext = base64.b64decode(ciphertext_b64)
return cipher.decrypt(ciphertext, None).decode()
def generate_sign(body: dict, private_key_pem: str) -> str:
"""Complete: sort -> SHA256 -> RSA sign -> Base64"""
sorted_str = json_to_sorted_string(body)
hash_hex = string_to_sha256(sorted_str)
return sign_with_private_key(private_key_pem, hash_hex)
def verify_callback(body: dict, sign_header: str, private_key_pem: str) -> bool:
"""Verify callback: sort -> SHA256 -> decrypt sign -> compare"""
sorted_str = json_to_sorted_string(body)
hash_hex = string_to_sha256(sorted_str)
decrypted = decrypt_with_private_key(private_key_pem, sign_header)
return hash_hex == decrypted
import crypto from 'crypto';
function jsonToSortedString(data) {
return Object.keys(data).sort()
.filter(k => String(data[k]) !== "")
.map(k => `${k}=${data[k]}`)
.join("&");
}
function stringToSHA256(str) {
return crypto.createHash("sha256").update(str).digest("hex");
}
function signWithPrivateKey(privateKeyPem, message) {
const sign = crypto.createSign("SHA256");
sign.update(message);
return sign.sign(privateKeyPem, "base64");
}
function decryptWithPrivateKey(privateKeyPem, ciphertextB64) {
return crypto.privateDecrypt(
{ key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(ciphertextB64, "base64")
).toString();
}
function generateSign(body, privateKeyPem) {
const sorted = jsonToSortedString(body);
const hashHex = stringToSHA256(sorted);
return signWithPrivateKey(privateKeyPem, hashHex);
}
function verifyCallback(body, signHeader, privateKeyPem) {
const sorted = jsonToSortedString(body);
const hashHex = stringToSHA256(sorted);
const decrypted = decryptWithPrivateKey(privateKeyPem, signHeader);
return hashHex === decrypted;
}
Error Codes
All API responses include a code field. 0 indicates success.
| Code | Name | Description |
|---|---|---|
0 | Success | Request completed successfully |
7 | Failed | General failure |
101 | Merchant Not Found | Invalid or inactive merchant code |
102 | Insufficient Balance | Merchant balance too low for payout |
103 | Signature Error | RSA signature verification failed |
404 | Not Found | Resource not found |
4001 | Invalid Params | One or more parameters are invalid |
4002 | Missing Params | Required parameter is missing |
4003 | Invalid Format | Parameter format is incorrect |
5001 | Network Error | Upstream network issue |
5002 | Request Timeout | Upstream request timed out |
5003 | Service Unavailable | Service temporarily down |
9001 | Header Error | Missing or invalid request headers |
10001 | Order Error | General order processing error |
10002 | Order Not Found | Order does not exist |
10003 | Order Status Error | Invalid order status transition |
10004 | Order Closed | Order has been finalized |
Bank Codes
Supported banks and e-wallets for payout disbursements. Use the bankCode value in your payout request.
| Code | Short Name | Full Name |
|---|
Showing popular banks. Full list includes 150+ institutions. Contact support for the complete bank code table.
For AI Agents
Structured API reference for LLMs and coding assistants. Copy this block and paste into your AI agent's context window for automatic integration.
# PaymentAggregationSystem Payment API — Machine-Readable Reference
# Version: 1.0 | Format: YAML
# Usage: Paste this entire block into your AI agent / LLM context window.
api:
base_url: https://{domain}/api/v1
auth:
type: rsa2048_sha256
headers:
MCODE: "<merchant_code>" # e.g. MT182504LVhvaB
SIGN: "<base64_rsa_signature>"
Content-Type: application/json
signature_steps:
- sort request body keys alphabetically
- concatenate non-empty values as "key=value" joined by "&"
- compute SHA-256 hex digest of that string
- sign digest with RSA-2048 PKCS#1 v1.5 private key
- base64-encode the signature → SIGN header
endpoints:
- id: create_payin
method: POST
path: /payin/create
content_type: application/json
description: Create a collection (payin) order
params:
- { name: uid, type: string, required: true, desc: "User identifier" }
- { name: merchantOid, type: string, required: true, desc: "Unique merchant order ID (UUID recommended)" }
- { name: amount, type: integer, required: true, desc: "Amount in cents (e.g. 1000000 = IDR 10,000)" }
- { name: notifyUrl, type: string, required: true, desc: "Webhook URL for async notifications" }
- { name: redirectUrl, type: string, required: true, desc: "Redirect URL after payment" }
- { name: timestamp, type: integer, required: true, desc: "Unix timestamp (seconds)" }
- { name: name, type: string, required: true, desc: "Customer name" }
- { name: email, type: string, required: true, desc: "Customer email" }
- { name: phone, type: string, required: true, desc: "Customer phone" }
- { name: passage, type: string, required: false, desc: "Payment channel code (skips cashier selection if set)" }
- { name: type, type: string, required: true, desc: 'Fixed value: "payin"' }
response:
- { name: code, type: integer, desc: "0 = success" }
- { name: data.oid, type: string, desc: "PaymentAggregationSystem system order ID" }
- { name: data.merchantOid,type: string, desc: "Echo of your merchant order ID" }
- { name: data.url, type: string, desc: "Cashier page URL — redirect user here" }
- { name: msg, type: string, desc: "Human-readable message" }
- id: create_payout
method: POST
path: /payout/create
content_type: application/json
description: Create a disbursement (payout) order
params:
- { name: uid, type: string, required: true, desc: "User identifier" }
- { name: name, type: string, required: true, desc: "Beneficiary name" }
- { name: merchantOid, type: string, required: true, desc: "Unique merchant order ID" }
- { name: amount, type: integer, required: true, desc: "Amount in cents" }
- { name: bankCode, type: string, required: true, desc: "Bank code (see bank_codes)" }
- { name: accountNo, type: string, required: true, desc: "Beneficiary account number" }
- { name: email, type: string, required: false, desc: "Beneficiary email" }
- { name: notifyUrl, type: string, required: true, desc: "Webhook URL for status updates" }
- { name: note, type: string, required: false, desc: "Remark / description" }
- { name: feeType, type: string, required: true, desc: '"Additional" (fee on top) or "Include" (fee deducted)' }
- { name: passage, type: string, required: true, desc: "Payment channel code" }
- { name: type, type: string, required: true, desc: 'Fixed value: "payout"' }
response:
- { name: code, type: integer, desc: "0 = success" }
- { name: data.oid, type: string, desc: "PaymentAggregationSystem system order ID" }
- { name: data.merchantOid,type: string, desc: "Echo of your merchant order ID" }
- { name: msg, type: string, desc: "Human-readable message" }
- id: query_order
method: POST
path: /query/order
content_type: application/json
description: Query order status (payin or payout)
params:
- { name: type, type: string, required: true, desc: '"payin" or "payout"' }
- { name: oid, type: string, required: false, desc: "PaymentAggregationSystem order ID (provide oid or merchantOid)" }
- { name: merchantOid, type: string, required: false, desc: "Your merchant order ID (provide oid or merchantOid)" }
response:
- { name: code, type: integer, desc: "0 = success" }
- { name: data.amount, type: integer, desc: "Order amount (cents)" }
- { name: data.merchantOid, type: string, desc: "Merchant order ID" }
- { name: data.orderOid, type: string, desc: "PaymentAggregationSystem order ID" }
- { name: data.timestamp, type: integer, desc: "Unix timestamp" }
- { name: data.status, type: string, desc: "order_success / order_fail / order_await" }
- { name: data.settleStatus, type: string, desc: "settle_success / settle_await / settle_unknown (payin only)" }
- { name: msg, type: string, desc: "Human-readable message" }
- id: query_balance
method: GET
path: /query/balance
description: Query merchant balance (sign the MCODE value instead of body)
params: []
response:
- { name: code, type: integer, desc: "0 = success" }
- { name: amount, type: integer, desc: "Available balance (cents)" }
- { name: settleAmount, type: integer, desc: "Pending settlement (cents)" }
- { name: freezeAmount, type: integer, desc: "Frozen balance (cents)" }
webhooks:
- event: payin_callback
description: Sent when a payin order is paid or settled
headers:
SIGN: RSA signature (verify with your private key)
MCODE: Merchant code
fields:
- { name: amount, type: integer, desc: "Order amount (cents)" }
- { name: merchantOid, type: string, desc: "Your merchant order ID" }
- { name: orderOid, type: string, desc: "PaymentAggregationSystem order ID" }
- { name: timestamp, type: integer, desc: "Unix timestamp" }
- { name: status, type: string, desc: "order_success / order_fail / order_await" }
- { name: settleStatus, type: string, desc: "settle_success / settle_await / settle_unknown" }
expected_response: 'plain text "SUCCESS" with HTTP 200'
- event: payout_callback
description: Sent when a payout order status changes
headers:
SIGN: RSA signature (verify with your private key)
MCODE: Merchant code
fields:
- { name: amount, type: integer, desc: "Order amount (cents)" }
- { name: merchantOid, type: string, desc: "Your merchant order ID" }
- { name: orderOid, type: string, desc: "PaymentAggregationSystem order ID" }
- { name: timestamp, type: integer, desc: "Unix timestamp" }
- { name: status, type: string, desc: "order_success / order_fail / order_await" }
expected_response: 'plain text "SUCCESS" with HTTP 200'
error_codes:
- { code: 0, name: Success, desc: "Request completed successfully" }
- { code: 7, name: Failed, desc: "General failure" }
- { code: 101, name: Merchant Not Found, desc: "Invalid or inactive merchant code" }
- { code: 102, name: Insufficient Balance, desc: "Balance too low for payout" }
- { code: 103, name: Signature Error, desc: "RSA signature verification failed" }
- { code: 404, name: Not Found, desc: "Resource not found" }
- { code: 4001, name: Invalid Params, desc: "One or more parameters are invalid" }
- { code: 4002, name: Missing Params, desc: "Required parameter is missing" }
- { code: 4003, name: Invalid Format, desc: "Parameter format is incorrect" }
- { code: 5001, name: Network Error, desc: "Upstream network issue" }
- { code: 5002, name: Request Timeout, desc: "Upstream request timed out" }
- { code: 5003, name: Service Unavailable, desc: "Service temporarily down" }
- { code: 9001, name: Header Error, desc: "Missing or invalid request headers" }
- { code: 10001, name: Order Error, desc: "General order processing error" }
- { code: 10002, name: Order Not Found, desc: "Order does not exist" }
- { code: 10003, name: Order Status Error, desc: "Invalid order status transition" }
- { code: 10004, name: Order Closed, desc: "Order has been finalized" }
example:
description: "Create a payin order (curl)"
curl: |
curl -X POST https://{domain}/api/v1/payin/create \
-H "Content-Type: application/json" \
-H "MCODE: MT182504LVhvaB" \
-H "SIGN: <base64_signature>" \
-d '{
"uid": "user_001",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"amount": 1000000,
"notifyUrl": "https://yoursite.com/webhook/payin",
"redirectUrl": "https://yoursite.com/payment/done",
"timestamp": 1745723300,
"name": "John Doe",
"email": "john@example.com",
"phone": "081234567890",
"type": "payin"
}'