Signature Convention
About 1681 wordsAbout 6 min
2025-03-07
PingPongCheckout API v4 ensures the authenticity of requests and integrity of data by verifying signatures.
Warning
In PingPongCheckout API v4, accId, clientId, signType, version, and bizContent all participate in the signature. If you were previously integrating with v2 or v3 versions and need to upgrade to v4, you must integrate according to the new signature rules.
Signature Types
| Signature Type | Description |
|---|---|
| MD5 | Indicates selection of MD5 algorithm, merchants use Salt to perform message digest signing and verification |
| SHA256 | Indicates selection of SHA256 algorithm, merchants use Salt to perform message digest signing and verification |
Assembly of String to be Signed
- Obtain all POST request content, excluding the sign field;
- Sort by the first character's key ASCII code in ascending order (alphabetical ascending order);
- Combine the sorted parameters with their corresponding values in the format Parameter=ParameterValue, then connect these parameters with & characters, and place the signature secret (salt) at the beginning of the string to be signed, i.e., signContent = {salt}key1=val2&key2=val2&key3=val3, at which point the complete string to be signed is obtained;
Important Notice
Please avoid passing special characters in request parameters
The current system will trigger XSS protection interception for requests containing < and >, causing the request to be rejected.
Additionally, although the system does not actively intercept the following special characters, considering that upstream channels, card organizations, and banks may have different risk control rules, it is strongly recommended to avoid using these characters in business:
\ " ' { } [ ] : , < > & / ( ) ; = % + \n \r
To ensure transaction success rate and stability, please ensure that the merchant system validates and cleans parameter values at the business layer before initiating requests, avoiding including the above special characters.
Calculating Signature Value
It is recommended to use SHA256 signature method, which has higher security than MD5
The signature process generates a signature value based on the data in the request body and adds it to the request body. The signature value is a string calculated using salt, request parameters, and signature method, depending on the specific signature method (MD5 or SHA256). The signature value will be used to verify the source and integrity of the request.
Signature Utility Class
Code Examples
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* This class is used to sign request content to ensure request security.
*/
public class PingPongCheckoutClient {
private final String salt; // Salt value, used to increase signature complexity
private final SignAlgorithm signAlgorithm; // Signature algorithm enumeration
/**
* Constructor, used to initialize salt value and signature algorithm.
*
* @param salt Salt value
* @param signAlgorithm Signature algorithm
*/
public PingPongCheckoutClient(String salt, SignAlgorithm signAlgorithm) {
this.salt = salt;
this.signAlgorithm = signAlgorithm;
}
/**
* Signs the request content and adds the signature result to the request parameters.
*
* @param requestBody Request content
* @return Request parameters with added signature result
*/
public JSONObject signRequest(JSONObject requestBody) {
String sign = getSign(salt, signAlgorithm, requestBody);
requestBody.put("sign", sign);
return requestBody;
}
/**
* Gets the signature result of the request content.
*
* @param salt Salt value
* @param signAlgorithm Signature algorithm
* @param requestBody Request content
* @return Signature result of the request content
*/
public static String getSign(String salt, SignAlgorithm signAlgorithm, JSONObject requestBody) {
StringBuilder stringBuilder = new StringBuilder();
List<String> keys = new ArrayList<>(requestBody.keySet());
Collections.sort(keys); // Sort request parameter keys in ascending order
for (String key : keys) {
Object valueObject = requestBody.get(key);
// Exclude null values
if (valueObject == null) {
continue;
}
// Exclude non-string type values
if (!(valueObject instanceof String)) {
throw new IllegalArgumentException("request body illegal");
}
String value = (String) valueObject;
if (StringUtils.isNotBlank(value)) {
stringBuilder.append(key).append("=").append(value).append("&"); // Concatenate request parameter key and value into string
}
}
String needSignStr = stringBuilder.toString();
if (needSignStr.endsWith("&")) {
needSignStr = needSignStr.substring(0, needSignStr.length() - 1); // Remove last & symbol
}
String sign = null;
if (signAlgorithm == SignAlgorithm.MD5) {
sign = md5Sign(salt, needSignStr);
} else if (signAlgorithm == SignAlgorithm.SHA256) {
sign = sha256(salt, needSignStr);
} else {
throw new IllegalArgumentException("Signature algorithm not supported");
}
return sign;
}
/**
* Performs MD5 signature on request content.
*
* @param salt Salt value
* @param content Request content
* @return MD5 signature result of request content
*/
public static String md5Sign(String salt, String content) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(salt.getBytes());
md.update(content.getBytes());
byte[] digest = md.digest();
return byteToHexString(digest);
} catch (Exception e) {
throw new RuntimeException("md5 signature failed", e);
}
}
/**
* Converts byte array to hexadecimal string.
*
* @param bytes Byte array
* @return Hexadecimal string
*/
public static String byteToHexString(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
hexString.append(hex.toUpperCase()); // Concatenate converted hexadecimal strings
}
return hexString.toString(); // Return converted hexadecimal string
}
/**
* Performs SHA256 signature on request content.
*
* @param salt Salt value
* @param content Request content
* @return SHA256 signature result of request content
*/
public static String sha256(String salt, String content) {
try {
String contentStr = salt.concat(content);
return DigestUtils.sha256Hex(contentStr.getBytes("UTF-8")).toUpperCase();
} catch (Exception e) {
throw new RuntimeException("sha256 signature failed", e);
}
}
/**
* Signature algorithm enumeration class.
*/
public enum SignAlgorithm {
MD5,
SHA256
}
}<?php
namespace App\Service;
/**
* This class is used to sign request content to ensure request security.
*/
class PingPongCheckoutClient
{
const SignAlgorithm = ['MD5', 'SHA256']; // Signature encryption methods
/**
* Signs the request content and adds the signature result to the request parameters
*
* @param string $salt
* @param array $requestBody
*
* @return array
*/
public function signRequest(string $salt, array $requestBody)
{
// Parameter validation
$this->validate($requestBody);
// Concatenate request parameters
$signContent = $this->signContent($salt, $requestBody);
$sign = $this->getSign($signContent, $requestBody);
$requestBody['sign'] = strtoupper($sign);
if (is_array($requestBody['bizContent'])) {
$requestBody['bizContent'] = json_encode($requestBody['bizContent'], JSON_UNESCAPED_SLASHES);
}
return $requestBody;
}
/**
* Concatenate request parameters
*
* @param string $salt
* @param array $requestBody
* @return array|string
*/
public function signContent(string $salt, array $requestBody)
{
unset($requestBody['sign']);
if (is_array($requestBody['bizContent'])) {
$requestBody['bizContent'] = json_encode($requestBody['bizContent'],JSON_UNESCAPED_SLASHES);
}
// Sort by key
ksort($requestBody);
$signContent = urldecode(http_build_query($requestBody));
$signContent = $salt . $signContent;
return $signContent;
}
/**
* Gets the signature result of the request content.
*
* @param string $signContent
* @param array $requestBody
* @return array|string
*/
public function getSign(string $signContent, array $requestBody)
{
switch ($requestBody['signType']) {
case "MD5":
$sign = $this->md5Sign($signContent);
break;
case "SHA256":
$sign = $this->sha256($signContent);
break;
}
return $sign;
}
/**
* md5 encryption
*
* @param string $signContent
* @return string
*/
public function md5Sign(string $signContent)
{
return md5($signContent);
}
/**
* sha256 encryption
*
* @param string $signContent
* @return string
*/
public function sha256(string $signContent)
{
return hash("sha256", $signContent);
}
/**
* Validates the signature parameters involved
*
* @param array $requestBody
* @return true
* @throws \Exception
*/
public function validate(array $requestBody)
{
$keys = ['accId', 'clientId', 'signType', 'version', 'bizContent'];
foreach ($keys as $key) {
if (!isset($requestBody[$key]) || empty($requestBody[$key])) {
throw new \Exception('invalid -' . $key);
}
}
if (!in_array($requestBody['signType'], self::SignAlgorithm)) {
throw new \Exception('invalid - signType' );
}
if (!is_array($requestBody['bizContent']) && !is_string($requestBody['bizContent'])) {
throw new \Exception('invalid - bizContent' );
}
return true;
}
}Signature Verification Utility Class
<?php
namespace App\Service;
/**
* This class is used to verify the signature of request content to ensure request security.
*/
class PingPongCheckoutDecrypt
{
/**
* Verifies the signature of asynchronous notification content
*
* @param string $salt
* @param array $requestBody {"clientId":"2023120712300910285","code":"000000","bizContent":"{\"exchangedCurrency\":\"USD\",\"amount\":\"1.080000\",\"authenticationInfo\":{\"avsResult\":\"Unknown\",\"cvvResult\":\"Y\",\"threeDSecure\":\"N\"},\"cardInfo\":{\"firstName\":\"James\",\"isoCountryA2\":\"RU\",\"lastName\":\"LeBron\",\"lastFourDigits\":\"1112\",\"cardLevel\":\"CLASSIC\",\"paymentBrand\":\"VISA\",\"cardType\":\"CREDIT\",\"issuringBank\":\"\",\"ipCountry\":\"CN\",\"firstSixDigits\":\"401200\",\"isoCountry\":\"RUSSIAN FEDERATION\"},\"issuerInfo\":{\"issuerResultMsg\":\"Successful approval/completion or V .I.P .PIN\\n verification is successful\",\"issuerResultCode\":\"00\"},\"threeDSecure\":\"\",\"remark\":\"Remark customer defined txt\",\"transactionTime\":\"1702349526000\",\"transactionId\":\"2023121250013357\",\"notifyType\":\"RECHARGE\",\"requestId\":\"3931b66e-78ba-4107-d75d-098a005f7bc3\",\"merchantTransactionId\":\"PShop2023121210510ES\",\"paymentMethod\":{\"type\":\"VISA\"},\"currency\":\"USD\",\"exchangedAmount\":\"1.080000\",\"captureDelayHours\":0,\"status\":\"SUCCESS\"}","sign":"B9BFE38FFE24B81FCAA41FC46F5560CB","accId":"2023120712300910285520","description":"Transaction succeeded","signType":"MD5"}
*
* @return boolean
*/
public function decryptRequest(string $salt, string $requestBody)
{
// Concatenate request parameters
$signData = $this->signContent($salt, $requestBody);
$sign = $this->getSign($signData['signContent'], $signData['data']);
if ($signData['sign'] == strtoupper($sign)) {
return true;
} else {
return false;
}
}
/**
* Concatenate request parameters
*
* @param string $salt
* @param array $requestBody
* @return array|string
*/
public function signContent(string $salt, string $requestBody)
{
// Replace special characters
$requestBody = str_replace("\\n", '\\\\n', $requestBody);
$requestBody = str_replace("\\r", '\\\\r', $requestBody);
$requestBody = str_replace("\\f", '\\\\f', $requestBody);
$requestBody = str_replace("\\t", '\\\\t', $requestBody);
$requestBody = str_replace("\\v", '\\\\v', $requestBody);
$requestBody = str_replace('\\\\"', '\\\\\\"', $requestBody);
$data = json_decode($requestBody,true);
if (!$data) {
if (function_exists('json_last_error') && json_last_error() != 0) {
$error = json_last_error_msg();
} else {
$error = 'data does not meet the requirements';
}
throw new \Exception($error);
}
$sign = $data['sign'];
unset($data['sign']);
// Sort by key
ksort($data);
$signContent = urldecode(http_build_query($data));
$signContent = $salt . $signContent;
return ['sign' => $sign, 'data' => $data, 'signContent' => $signContent];
}
/**
* Gets the signature result of the request content.
*
* @param string $signContent
* @param array $requestBody
* @return array|string
*/
public function getSign(string $signContent, array $requestBody)
{
switch ($requestBody['signType']) {
case "MD5":
$sign = $this->md5Sign($signContent);
break;
case "SHA256":
$sign = $this->sha256($signContent);
break;
}
return $sign;
}
/**
* md5 encryption
*
* @param string $signContent
* @return string
*/
public function md5Sign(string $signContent)
{
return md5($signContent);
}
/**
* sha256 encryption
*
* @param string $signContent
* @return string
*/
public function sha256(string $signContent)
{
return hash("sha256", $signContent);
}
}