Node.js Examples
Complete Node.js example client for integrating with the 123hub API using the built-incrypto module and native fetch.
This is a reference implementation, not an official SDK package. It uses only Node.js built-in
modules (no external dependencies required). Requires Node.js 18+ for native
fetch.Configuration
Store your credentials in environment variables:export PAYMENT_API_APP_ID="1"
export PAYMENT_API_VERSION="2"
export PAYMENT_API_SECRET_KEY="your_secret_key"
PaymentApiClient Class (JavaScript)
const crypto = require('crypto');
class PaymentApiClient {
constructor({
appId = process.env.PAYMENT_API_APP_ID,
apiVersion = Number(process.env.PAYMENT_API_VERSION || '1'),
secretKey = process.env.PAYMENT_API_SECRET_KEY,
baseUrl = 'https://api.bafanglaicai88.com',
timeoutMs = 30000,
} = {}) {
if (!appId || !secretKey) {
throw new Error('appId and secretKey are required');
}
this.appId = appId;
if (![1, 2].includes(apiVersion)) throw new Error('apiVersion must be 1 or 2');
this.apiVersion = apiVersion;
this.secretKey = secretKey;
this.baseUrl = baseUrl;
this.timeoutMs = timeoutMs;
}
async _send(body) {
const bodyStr = JSON.stringify(body);
const path = `/public/api/multihub/v${this.apiVersion}`;
const headers = {
'Content-Type': 'application/json',
'X-Data-Application-Id': String(this.appId),
};
let requestNonce;
if (this.apiVersion === 1) {
headers['X-Data-Hash'] = crypto
.createHash('sha512')
.update(bodyStr + this.secretKey)
.digest('hex');
} else {
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(24).toString('hex');
requestNonce = nonce;
const canonical = [
'quadpay-multihub-request-v2',
String(this.appId),
'POST',
'/public/api/multihub/v2',
timestamp,
nonce,
crypto.createHash('sha256').update(bodyStr).digest('hex'),
].join('\n');
headers['X-Data-Timestamp'] = timestamp;
headers['X-Data-Nonce'] = nonce;
headers['X-Data-Signature'] = crypto
.createHmac('sha512', this.secretKey)
.update(canonical)
.digest('hex');
}
const response = await fetch(this.baseUrl + path, {
method: 'POST',
headers,
body: bodyStr,
signal: AbortSignal.timeout(this.timeoutMs),
});
const responseText = await response.text();
const parsed = JSON.parse(responseText);
if (this.apiVersion === 2) {
const requestId = response.headers.get('x-request-id') || parsed.request_id;
const canonicalResponse = [
'quadpay-multihub-response-v2',
String(this.appId),
requestNonce,
requestId,
String(response.status),
crypto.createHash('sha256').update(responseText).digest('hex'),
].join('\n');
const expected = crypto
.createHmac('sha512', this.secretKey)
.update(canonicalResponse)
.digest('hex');
const received = response.headers.get('x-data-signature') || '';
if (
!requestId ||
!/^[0-9a-f]{128}$/.test(received) ||
!crypto.timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'))
) {
throw new Error('Merchant API v2 response signature verification failed');
}
}
return parsed;
}
async ping() {
return this._send({ method: 'gateway.ping', params: {} });
}
async createDeposit(serviceId, amount, currency, cId, extra = {}) {
return this._send({
method: 'payment.in',
service_id: serviceId,
params: {
payment: {
identifiers: { c_id: cId },
amount: { value: amount, currency },
...extra,
},
},
});
}
async createWithdrawal(serviceId, amount, currency, cId, receiver, extra = {}) {
return this._send({
method: 'payment.out',
service_id: serviceId,
params: {
payment: {
identifiers: { c_id: cId },
amount: { value: amount, currency },
receiver,
...extra,
},
},
});
}
async getStatus({ cId, hId } = {}) {
const identifiers = {};
if (cId) identifiers.c_id = cId;
if (hId) identifiers.h_id = hId;
return this._send({
method: 'payment.status',
params: { payment: { identifiers } },
});
}
async getBalance() {
return this._send({ method: 'balance.get', params: {} });
}
}
module.exports = { PaymentApiClient };
PaymentApiClient Class (TypeScript)
import crypto from 'crypto';
interface PaymentApiResponse {
success: boolean;
result?: Record<string, any>;
error?: { code: number; message: string };
request_id: string;
processing_time: number;
}
interface PaymentApiClientOptions {
appId?: string;
apiVersion?: 1 | 2;
secretKey?: string;
baseUrl?: string;
timeoutMs?: number;
}
interface Receiver {
bank?: {
account: { id: string };
ifsc?: string;
};
email?: string;
phone?: string;
person?: { first_name: string; last_name: string };
[key: string]: any;
}
class PaymentApiClient {
private appId: string;
private apiVersion: 1 | 2;
private secretKey: string;
private baseUrl: string;
private timeoutMs: number;
constructor(options: PaymentApiClientOptions = {}) {
this.appId = options.appId || process.env.PAYMENT_API_APP_ID || '';
this.apiVersion =
options.apiVersion || (Number(process.env.PAYMENT_API_VERSION || '1') as 1 | 2);
this.secretKey = options.secretKey || process.env.PAYMENT_API_SECRET_KEY || '';
this.baseUrl = options.baseUrl || 'https://api.bafanglaicai88.com';
this.timeoutMs = options.timeoutMs || 30000;
if (!this.appId || !this.secretKey) {
throw new Error('appId and secretKey are required');
}
if (![1, 2].includes(this.apiVersion)) {
throw new Error('apiVersion must be 1 or 2');
}
}
private async send(body: Record<string, any>): Promise<PaymentApiResponse> {
const bodyStr = JSON.stringify(body);
const path = `/public/api/multihub/v${this.apiVersion}`;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Data-Application-Id': this.appId,
};
let requestNonce: string | undefined;
if (this.apiVersion === 1) {
headers['X-Data-Hash'] = crypto
.createHash('sha512')
.update(bodyStr + this.secretKey)
.digest('hex');
} else {
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(24).toString('hex');
requestNonce = nonce;
const canonical = [
'quadpay-multihub-request-v2',
this.appId,
'POST',
'/public/api/multihub/v2',
timestamp,
nonce,
crypto.createHash('sha256').update(bodyStr).digest('hex'),
].join('\n');
headers['X-Data-Timestamp'] = timestamp;
headers['X-Data-Nonce'] = nonce;
headers['X-Data-Signature'] = crypto
.createHmac('sha512', this.secretKey)
.update(canonical)
.digest('hex');
}
const response = await fetch(this.baseUrl + path, {
method: 'POST',
headers,
body: bodyStr,
signal: AbortSignal.timeout(this.timeoutMs),
});
const responseText = await response.text();
const parsed = JSON.parse(responseText) as PaymentApiResponse & { request_id?: string };
if (this.apiVersion === 2) {
const requestId = response.headers.get('x-request-id') || parsed.request_id;
const canonicalResponse = [
'quadpay-multihub-response-v2',
this.appId,
requestNonce,
requestId,
String(response.status),
crypto.createHash('sha256').update(responseText).digest('hex'),
].join('\n');
const expected = crypto
.createHmac('sha512', this.secretKey)
.update(canonicalResponse)
.digest('hex');
const received = response.headers.get('x-data-signature') || '';
if (
!requestId ||
!/^[0-9a-f]{128}$/.test(received) ||
!crypto.timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'))
) {
throw new Error('Merchant API v2 response signature verification failed');
}
}
return parsed;
}
async ping(): Promise<PaymentApiResponse> {
return this.send({ method: 'gateway.ping', params: {} });
}
async createDeposit(
serviceId: number,
amount: number,
currency: string,
cId: string,
extra: Record<string, any> = {},
): Promise<PaymentApiResponse> {
return this.send({
method: 'payment.in',
service_id: serviceId,
params: {
payment: {
identifiers: { c_id: cId },
amount: { value: amount, currency },
...extra,
},
},
});
}
async createWithdrawal(
serviceId: number,
amount: number,
currency: string,
cId: string,
receiver: Receiver,
extra: Record<string, any> = {},
): Promise<PaymentApiResponse> {
return this.send({
method: 'payment.out',
service_id: serviceId,
params: {
payment: {
identifiers: { c_id: cId },
amount: { value: amount, currency },
receiver,
...extra,
},
},
});
}
async getStatus(options: { cId?: string; hId?: string } = {}): Promise<PaymentApiResponse> {
const identifiers: Record<string, string> = {};
if (options.cId) identifiers.c_id = options.cId;
if (options.hId) identifiers.h_id = options.hId;
return this.send({
method: 'payment.status',
params: { payment: { identifiers } },
});
}
async getBalance(): Promise<PaymentApiResponse> {
return this.send({ method: 'balance.get', params: {} });
}
}
export { PaymentApiClient, PaymentApiResponse, PaymentApiClientOptions };
Usage Examples
Ping (Health Check)
const { PaymentApiClient } = require('./payment-api');
const client = new PaymentApiClient({
appId: '1',
apiVersion: 2,
secretKey: 'your_secret_key',
});
async function main() {
const result = await client.ping();
console.log(result);
// { success: true, result: { message: "pong" }, ... }
}
main();
Create a Deposit Payment
The examples below use INR (India), but the same client works for all supported currencies (MXN, ARS, TRY, AUD, LKR, UYU) — just change theserviceId, currency, and country values.
const result = await client.createDeposit(
14701, // service_id
10000, // amount
'INR', // currency
'12345', // c_id (your client-side identifier)
{
description: 'Order #12345',
payer: {
email: 'customer@example.com',
person: { first_name: 'John', last_name: 'Doe' },
},
},
);
if (result.success) {
console.log('Payment created successfully');
console.log('Request ID:', result.request_id);
} else {
console.error(`Error: ${result.error.code} - ${result.error.message}`);
}
Create a Withdrawal (Payout)
const result = await client.createWithdrawal(
14701, // service_id
5000, // amount
'INR', // currency
'67890', // c_id
{
// receiver
bank: {
account: { id: '1234567890' },
ifsc: 'SBIN0001234',
},
person: { first_name: 'Jane', last_name: 'Doe' },
},
{
description: 'Payout #67890',
},
);
if (result.success) {
console.log('Withdrawal created successfully');
} else {
console.error(`Error: ${result.error.code} - ${result.error.message}`);
}
Check Payment Status
// Look up by client-side ID
const result = await client.getStatus({ cId: '12345' });
if (result.success) {
const status = result.result.payment.status.status;
console.log(`Payment status: ${status}`);
} else {
console.error(`Error: ${result.error.code} - ${result.error.message}`);
}
// Look up by hub-side ID
const result2 = await client.getStatus({ hId: '999001' });
Get Balance
const result = await client.getBalance();
if (result.success) {
for (const amount of result.result.balance.amounts) {
console.log(`${amount.currency}: ${amount.value}`);
}
} else {
console.error(`Error: ${result.error.code} - ${result.error.message}`);
}
Error Handling
Successful responses return HTTP 200, errors return HTTP 400. Always use thesuccess field to determine the outcome:
const result = await client.createDeposit(14701, 10000, 'INR', '12345');
if (result.success) {
// Operation succeeded
console.log(`Request ID: ${result.request_id}`);
console.log(`Processing time: ${result.processing_time}ms`);
} else {
// Operation failed
const { code, message } = result.error;
console.error(`Error code: ${code}`);
console.error(`Error message: ${message}`);
switch (code) {
case 3000:
console.error('Authentication failed - check your secret key');
break;
case 1005:
console.error('Invalid parameters - check request body');
break;
case 7002:
console.error('Service temporarily unavailable - retry later');
break;
default:
console.error(`Unhandled error code: ${code}`);
}
}
Handling Network Errors
Network-level errors can still occur independently of API responses:const { PaymentApiClient } = require('./payment-api');
const client = new PaymentApiClient({
appId: '1',
apiVersion: 2,
secretKey: 'your_secret_key',
});
async function safePing() {
try {
const result = await client.ping();
if (result.success) {
console.log('API is reachable');
} else {
console.error(`API error: ${result.error.message}`);
}
} catch (error) {
if (error.cause?.code === 'ECONNREFUSED') {
console.error('Cannot reach the API - check network connectivity');
} else if (error.name === 'AbortError') {
console.error('Request timed out - try again');
} else {
console.error(`Request failed: ${error.message}`);
}
}
}
Complete Example
const crypto = require('crypto');
class PaymentApiClient {
constructor({
appId,
apiVersion,
secretKey,
baseUrl = 'https://api.bafanglaicai88.com',
timeoutMs = 30000,
}) {
if (![1, 2].includes(apiVersion)) {
throw new Error('apiVersion must be 1 or 2');
}
this.appId = appId;
this.apiVersion = apiVersion;
this.secretKey = secretKey;
this.baseUrl = baseUrl;
this.timeoutMs = timeoutMs;
}
async _send(body) {
const bodyStr = JSON.stringify(body);
const path = `/public/api/multihub/v${this.apiVersion}`;
const headers = {
'Content-Type': 'application/json',
'X-Data-Application-Id': this.appId,
};
if (this.apiVersion === 1) {
headers['X-Data-Hash'] = crypto
.createHash('sha512')
.update(bodyStr + this.secretKey)
.digest('hex');
} else {
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(24).toString('hex');
const canonical = [
'quadpay-multihub-request-v2',
this.appId,
'POST',
'/public/api/multihub/v2',
timestamp,
nonce,
crypto.createHash('sha256').update(bodyStr).digest('hex'),
].join('\n');
headers['X-Data-Timestamp'] = timestamp;
headers['X-Data-Nonce'] = nonce;
headers['X-Data-Signature'] = crypto
.createHmac('sha512', this.secretKey)
.update(canonical)
.digest('hex');
}
const response = await fetch(this.baseUrl + path, {
method: 'POST',
headers,
body: bodyStr,
signal: AbortSignal.timeout(this.timeoutMs),
});
const responseText = await response.text();
if (this.apiVersion === 2) {
const parsed = JSON.parse(responseText);
const requestId = response.headers.get('x-request-id') || parsed.request_id;
const canonicalResponse = [
'quadpay-multihub-response-v2',
this.appId,
headers['X-Data-Nonce'],
requestId,
String(response.status),
crypto.createHash('sha256').update(responseText).digest('hex'),
].join('\n');
const expected = crypto
.createHmac('sha512', this.secretKey)
.update(canonicalResponse)
.digest('hex');
const received = response.headers.get('x-data-signature') || '';
if (
!/^[0-9a-f]{128}$/.test(received) ||
!requestId ||
!crypto.timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'))
) {
throw new Error('Merchant API v2 response signature verification failed');
}
return parsed;
}
return JSON.parse(responseText);
}
async ping() {
return this._send({ method: 'gateway.ping', params: {} });
}
async getBalance() {
return this._send({ method: 'balance.get', params: {} });
}
async createDeposit(serviceId, amount, currency, cId, extra = {}) {
return this._send({
method: 'payment.in',
service_id: serviceId,
params: {
payment: {
identifiers: { c_id: cId },
amount: { value: amount, currency },
...extra,
},
},
});
}
async getStatus({ cId, hId } = {}) {
const identifiers = {};
if (cId) identifiers.c_id = cId;
if (hId) identifiers.h_id = hId;
return this._send({
method: 'payment.status',
params: { payment: { identifiers } },
});
}
}
async function main() {
const client = new PaymentApiClient({
appId: process.env.PAYMENT_API_APP_ID || '1',
apiVersion: Number(process.env.PAYMENT_API_VERSION || '2'),
secretKey: process.env.PAYMENT_API_SECRET_KEY || 'your_secret_key',
});
// 1. Ping the gateway
const pingResult = await client.ping();
if (!pingResult.success) {
console.error(`Gateway unreachable: ${pingResult.error.message}`);
return;
}
console.log('Gateway is up');
// 2. Check balance
const balanceResult = await client.getBalance();
if (balanceResult.success) {
for (const b of balanceResult.result.balance.amounts) {
console.log(` ${b.currency}: ${b.value}`);
}
} else {
console.error(`Balance check failed: ${balanceResult.error.message}`);
}
// 3. Create a deposit
const orderId = String(Date.now());
const depositResult = await client.createDeposit(14701, 10000, 'INR', orderId, {
description: `Order #${orderId}`,
payer: {
email: 'customer@example.com',
person: { first_name: 'John', last_name: 'Doe' },
},
});
if (depositResult.success) {
console.log(`Deposit created (request_id=${depositResult.request_id})`);
} else {
console.error(`Deposit failed: ${depositResult.error.message}`);
return;
}
// 4. Poll for status
for (let i = 0; i < 5; i++) {
await new Promise((r) => setTimeout(r, 3000));
const statusResult = await client.getStatus({ cId: orderId });
if (statusResult.success) {
const status = statusResult.result.payment.status.status;
console.log(`Payment status: ${status}`);
if (
['success', 'error', 'canceled', 'declined', 'refunded', 'partially_refunded'].includes(
status,
)
)
break;
} else {
console.error(`Status check failed: ${statusResult.error.message}`);
break;
}
}
}
main().catch(console.error);
