JavaScript / TypeScript Sample
This sample runs as Node.js JavaScript and uses JSDoc types for TypeScript checking. It includes signing, POST JSON, GET Query, and examples for creating a collect order, creating a payout order, and querying merchant info.
js
// @ts-check
import crypto from 'node:crypto';
class SkynetClient {
/**
* @param {{ baseUrl: string; mchId: number; apiToken: string }} config
*/
constructor(config) {
this.baseUrl = config.baseUrl.replace(/\/$/, '');
this.mchId = config.mchId;
this.apiToken = config.apiToken;
}
async createCollectOrder() {
return this.post('/api/v1/mch/pmt-orders', {
trans_id: `ORDER-${Date.now()}`,
currency: 'VND',
amount: '100.00',
channel: 'bank',
callback_url: 'https://merchant.example.com/callback/collect',
return_url: 'https://merchant.example.com/payment/result',
remarks: 'collect demo',
});
}
async createPayoutOrder() {
return this.post('/api/v1/mch/wdl-orders', {
trans_id: `WDL-${Date.now()}`,
channel: 'bank',
amount: '100.00',
currency: 'VND',
account_no: '2333667799212341',
account_name: 'NGUYEN XUAN HUNG',
account_org: 'PVCOMBANK',
account_org_code: '970412',
callback_url: 'https://merchant.example.com/callback/payout',
remarks: 'payout demo',
});
}
async getMerchantInfo() {
return this.get('/api/v1/mch/info');
}
/**
* @param {string} path
* @param {Record<string, string | number | null | undefined>} params
*/
async post(path, params) {
const signedParams = this.withSignature(params);
const response = await fetch(`${this.baseUrl}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signedParams),
});
return this.parseResponse(response);
}
/**
* @param {string} path
* @param {Record<string, string | number | null | undefined>} [params]
*/
async get(path, params = {}) {
const signedParams = this.withSignature(params);
const query = new URLSearchParams();
Object.entries(signedParams).forEach(([key, value]) => {
query.set(key, String(value));
});
const response = await fetch(`${this.baseUrl}${path}?${query.toString()}`);
return this.parseResponse(response);
}
/**
* @param {Record<string, string | number | null | undefined>} params
*/
withSignature(params) {
const signedParams = {
...params,
mch_id: this.mchId,
nonce: crypto.randomBytes(8).toString('hex').slice(0, 12),
timestamp: Math.floor(Date.now() / 1000),
};
return {
...signedParams,
sign: this.sign(signedParams),
};
}
/**
* @param {Record<string, string | number | null | undefined>} params
*/
sign(params) {
const source = Object.keys(params)
.filter((key) => key !== 'sign' && params[key] !== null && params[key] !== undefined && params[key] !== '')
.sort()
.reduce((result, key) => `${result}&${key}=${params[key]}`, this.apiToken);
return crypto.createHash('md5').update(source, 'utf8').digest('hex');
}
/**
* @param {Response} response
*/
async parseResponse(response) {
const body = await response.json();
if (!response.ok) {
throw new Error(`HTTP request failed: ${response.status}`);
}
if (body.code !== 200) {
throw new Error(body?.errors?.message || body?.message || 'API request failed');
}
return body.payload ?? {};
}
}
const client = new SkynetClient({
baseUrl: 'https://api.example.com',
mchId: 10001,
apiToken: 'demo_key_123456',
});
const collectOrder = await client.createCollectOrder();
const payoutOrder = await client.createPayoutOrder();
const merchantInfo = await client.getMerchantInfo();
console.log({ collectOrder, payoutOrder, merchantInfo });