Request & Response
There are some basic rules for PTS request and response. Please check these rules before starting your implementation.
- All request and data are using
UTF-8
format. - The payment request is based on
HTTPS
protocol, onlyPOST
form is supported. - All parameters & participate in signature must be sorted in ASCII. Null values still need to be added to signature.
- All requests have specified header. Please follow the setting for the request header.
- Please ask our customer service team for API Host URL
- Please let us know your
Host IP
, so we can add your IP into whitelist
Request Format
Please following guide to build your request:
- Prepare your request data and generate the signature.
- Convert request body into JSON string.
- Encrypt request JSON string by
AES-ECB-PKCS7
with your merchant safecode. - Encode your encrypted string via
base64_encode
and get the final request string. - Content-Type of HTTPS requests should be
text/plain
// Prepare your request data and generate the signature
{
"order_id": "20210514183849",
"amount": "40000",
"currency": "CNY",
"timestamp": "1612245402",
"callback_url": "http://my.service/callback",
"redirect_url": "https://my.service/redirect",
"channel": "alipay",
"bank_code": "",
"remark": "test",
"user_id": "1",
"sign": "lOp6SoczkquxzYTDufsVTIjjTdKuCGZnGEa7…."
}
// Convert request body into JSON string
{"order_id":"20210514184046","amount":"40000","currency":"CNY","timestamp":"1612245402","callback_url":"http://my.service/callback","redirect_url":"https://my.service/redirect","channel":"alipay","bank_code":"","remark":"test","user_id":"1","sign":"lOp6SoczkquxzYTDufsVTIjjTdKuCGZnGEa7..."}
// Encrypt JSON string by AES-ECB-PKCS7 with your merchant safecode, and convert it into base64_encode
wbTkX4OdkK8xqvrnqqKalTp/XiC+svRLvgu6UGQ5gDPx9iTRSS3ng8cRkLwfrxnN3Ba4YZAtMtb2PahMj0KNz56ovbuctKsMWMjztpIn2eLCHWNzVHRrU8eJ/aG0OgDztdceON2xBGYEtzpyf1Lc9jycfnd35tANhZgWFlNvCPrTNsbTjrVA3fH1gOKzn35CfHsuyWertBQjp/FqMkDWa7G1gRxXa2L1s...
// Then, now you can send request via HTTPS Post in text/plain
// Or you can send request via HTTPS Post in application/json with data parameter
{
"data": "wbTkX4OdkK8xqvrnqqKalTp/XiC+svRLvgu6UGQ5gDPx9iTRSS3ng8cRkLwfrxnN3Ba4YZAtMtb2PahMj0KNz56ovbuctKsMWMjztpIn2eLCHWNzVHRrU8eJ/aG0OgDztdceON2xBGYEtzpyf1Lc9jycfnd35tANhZgWFlNvCPrTNsbTjrVA3fH1gOKzn35CfHsuyWertBQjp/FqMkDWa7G1gRxXa2L1s..."
}
- PHP
- Java
- Python
$user_id = ''; // '1'
$api_url = ''; // 'http://service.local.test/merchant/payment'
$sign_type = ''; // 'payment'
$token = ''; // ''
$base_url = ''; // 'http://service.local.test'
$safecode = ''; // '164ddce3d0b13d21fbc4cd02e14d9f98'
$params = [
'user_id' => $user_id,
'order_id' => 'TEST'.$user_id.time(),
'amount' => rand(10, 1000),
'currency' => 'PHP',
'channel' => 'QRPH',
'callback_url' => 'http://payment.local.test/callback',
'redirect_url' => 'http://payment.local.test/redirect',
'remark' => 'test',
'timestamp' => time(),
'email' => '[email protected]',
'phone' => '09175123213',
];
try {
$response = send_request($api_url, $params, $sign_type);
echo $response;
if ($response['code'] !== '1000') {
throw new Exception($response['message']);
}
} catch (Exception $e) {
$token = get_token();
$response = send_request($api_url, $params, $sign_type);
echo $response;
}
function send_request(string $api_url, array $params, string $sign_type): string
{
global $safecode, $token;
$header = [
'Content-Type: text/plain',
'Authorization: '.$token
];
$params['sign'] = generate_signature($params, $safecode, $sign_type);
$json_string = json_encode($params);
$encrypt = openssl_encrypt($json_string, 'AES-256-ECB', $safecode, OPENSSL_RAW_DATA);
$params = base64_encode($encrypt);
$response = send_curl_request($api_url, $params, $header);
return $response;
}
function send_curl_request($url, $body, $header): string
{
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => 'utf-8',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $header,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0',
]);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.sql.Timestamp;
static String safecode = ""; // "164ddce3d0b13d21fbc4cd02e14d9f98"
static String requestUrl = ""; // "http://service.local.test"
static String userId = ""; // "1"
static String token = ""; // ""
static String signType = ""; // "payment"
public static void main(String[] args) throws UnirestException {
Map<String, String> params = new HashMap<>();
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
params.put("user_id", userId);
params.put("order_id", "TEST" + userId + timestamp);
params.put("amount", String.valueOf(Math.floor(Math.random() * (1000 - 100 + 1) + 100)));
params.put("currency", "PHP");
params.put("channel", "QRPH");
params.put("callback_url", "http://my.service/callback");
params.put("redirect_url", "https://my.service/redirect");
params.put("timestamp", String.valueOf((int) Math.floor(timestamp.getTime() / 1000)));
params.put("remark", "test");
params.put("email", "[email protected]");
try {
JSONObject jsonBody = sendRequest("/merchant/payment", params, signType);
int code = jsonBody.getInt("code");
if (code != 1000) {
throw new Exception(jsonBody.getString("message"));
}
} catch (Exception e) {
token = getToken();
sendRequest("/merchant/payment", params, "payment");
}
}
public static JSONObject sendRequest(String urlPath, Map<String, String> params, String signType)
throws UnirestException {
JSONObject jsonObject = new JSONObject();
SortedSet<String> parameter = new TreeSet<>(params.keySet());
for (String key : parameter) {
jsonObject.put(key, params.get(key));
}
jsonObject.put("sign", generateSignature(params, signType));
String aesString = aes256Encode(jsonObject.toString(), safecode);
Unirest.setTimeouts(0, 0);
HttpResponse<JsonNode> jsonResponse = Unirest.post(requestUrl + urlPath)
.header("Content-Type", "text/plain")
.header("Authorization", token)
.body(aesString)
.asJson();
System.out.println("sendRequest response:: " + jsonResponse.getBody());
return jsonResponse.getBody().getObject();
}
public static String aes256Encode(String str, String code) {
byte[] key = code.getBytes();
byte[] result = null;
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
result = cipher.doFinal(str.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
return Base64.getEncoder().encodeToString(result);
}
import base64
import json
import hashlib
import random
import time
import calendar
import requests
from aes import AESCrypt
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
user_id = '' # '1'
url = '' # 'http://service.local.test'
safecode = '' # '164ddce3d0b13d21fbc4cd02e14d9f98'
def send_request(url_path, params, sign_type):
token = ''
# Get Before Sign string
serialize_sign_string = serialize_sign(params, safecode, sign_type)
print('serialize sign string:::' + serialize_sign_string)
# Assign sign to params
#
sign = generate_signature(serialize_sign_string)
print('sign base64 encode string:::' + sign)
params['sign'] = sign
json_string = json.dumps(params, indent=4, ensure_ascii=False)
# AES encrypt
aes = AESCrypt(safecode)
aes_string = aes.encrypt(json_string).decode('utf-8')
print('AES string::' + aes_string)
# Request
try:
headers = {'content-type': 'text/plain', 'Authorization': token}
response = requests.post(url=(url + url_path), data=aes_string, headers=headers)
dict_obj = json.loads(response.text)
print('response result::' + response.text)
if dict_obj.get('code') != 1000:
raise Exception(dict_obj.get('message'))
except Exception as e:
# Get token
token = get_token()
headers = {'content-type': 'text/plain', 'Authorization': token}
response = requests.post(url=(url + url_path), data=aes_string, headers=headers)
dict_obj = json.loads(response.text)
print('response result::' + response.text)
if response.status_code == 200:
return response.text
else:
return None
post_params = {
'user_id': user_id,
'order_id': f'TEST{user_id}{calendar.timegm(time.gmtime())}',
'amount': random.randint(10, 1000),
'currency': 'PHP',
'channel': 'QRPH',
'callback_url': 'http://my.service/callback',
'redirect_url': 'https://my.service/redirect',
'timestamp': calendar.timegm(time.gmtime()),
'email': '[email protected]',
'remark': 'test'
}
send_request('/merchant/payment', post_params, 'payment')
# aes.py
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
class AESCrypt:
def __init__(self, key):
self.key = key.encode('utf-8')
self.block_size = 16 # AES block size is 16 bytes
def encrypt(self, data):
cipher = AES.new(self.key, AES.MODE_ECB)
padded_data = pad(data.encode('utf-8'), self.block_size)
ciphertext = cipher.encrypt(padded_data)
return base64.b64encode(ciphertext)
def decrypt(self, encrypted_data):
encrypted_data = base64.b64decode(encrypted_data)
cipher = AES.new(self.key, AES.MODE_ECB)
decrypted_data = unpad(cipher.decrypt(encrypted_data), self.block_size)
return decrypted_data.decode('utf-8')
Response format
The responses and callbacks from PTS will follow this format.
Header | Content |
---|---|
Content-Type | application/json |
Param | Type | Required | Description |
---|---|---|---|
code | string | ✅ | Success return 1000 and else are failure indexes |
message | string | ✅ | Result description |
data | array | ❌ | Provides when code is 1000 |
// Example
{
"code": "1000",
"message": "Accepted",
"data": {
// available while code=1000
}
}