Skip to content

Signature

Updated: 7/26/26, 6:55:04 PM

DaxPay Payment API uses RSA asymmetric encryption for signing. Requests, responses, and callbacks are all signed to prevent tampering.

Key System

KeyHeld ByUsed For
Merchant private keyMerchant serverSigning requests
Merchant public keyDaxPay platformPlatform verifies requests
Platform private keyDaxPay platformSigning responses/callbacks
Platform public keyMerchant serverMerchant verifies responses/callbacks

Key Generation

Use 2048-bit RSA keys. Generate with OpenSSL or Java KeyPairGenerator.

Signature Field Location

Signature-related fields are in the request/response JSON body, not in HTTP headers:

FieldLocationDescription
signJSON bodySignature value (Base64)
reqIdJSON bodyUnique request identifier
nonceStrJSON bodyRandom string
reqTimeJSON bodyRequest time (yyyy-MM-dd HH:mm:ss, GMT+8)

Signing Steps

1. Build sign string

  1. Get all fields from the request JSON (including flattened nested objects).
  2. Exclude the sign field.
  3. Exclude empty values (null or empty string).
  4. Sort remaining fields by key in ASCII order.
  5. Join as key1=value1&key2=value2.
Nested object & list flattening
  • Nested objects use dot notation: terminal.terminalNo=value
  • List elements use bracket index: limitPay[0]=value
  • Flattened entries participate in sorting and joining

2. RSA sign

Sign the string with merchant private key:

  • Algorithm: SHA256withRSA
  • Encoding: UTF-8
  • Output: Base64 string

3. Set sign field

Set the Base64 result as the sign field in the request JSON.

Response Verification

Use platform public key to verify the sign field:

  1. Get all fields from the response JSON.
  2. Remove sign.
  3. Sort and join the same way as request signing.
  4. Verify with platform public key using SHA256withRSA.

Code Examples

Java — Sign Request

java
import java.security.*;
import java.util.*;
import java.util.Base64;

// 1. Build params (TreeMap auto-sorts by key)
Map<String, String> params = new TreeMap<>();
params.put("appId", "APP001");
params.put("bizOrderNo", "ORDER20241201001");
params.put("method", "wechat_qr");
params.put("mchNo", "M200000001");
params.put("nonceStr", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS");
params.put("notifyUrl", "https://your-domain.com/notify");
params.put("amount", "100");
params.put("reqId", "REQ20241201001");
params.put("reqTime", "2024-12-01 12:00:00");
params.put("title", "Test Product");

// 2. Build sign string (exclude sign, ASCII sorted)
String signStr = params.entrySet().stream()
    .map(e -> e.getKey() + "=" + e.getValue())
    .collect(Collectors.joining("&"));

// 3. RSA sign
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey); // merchant private key
signature.update(signStr.getBytes(StandardCharsets.UTF_8));
String sign = Base64.getEncoder().encodeToString(signature.sign());

Java — Verify Response/Callback

java
// 1. Remove sign, sort by key, join
String verifyStr = sortedParams.entrySet().stream()
    .filter(e -> !"sign".equalsIgnoreCase(e.getKey()))
    .filter(e -> e.getValue() != null && !e.getValue().isEmpty())
    .map(e -> e.getKey() + "=" + e.getValue())
    .collect(Collectors.joining("&"));

// 2. Verify with platform public key
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey); // platform public key
signature.update(verifyStr.getBytes(StandardCharsets.UTF_8));
byte[] signBytes = Base64.getDecoder().decode(receivedSign);
boolean valid = signature.verify(signBytes);

if (!valid) {
    throw new RuntimeException("Signature verification failed");
}

Rules Summary

RuleDescription
SortASCII ascending by key
Formatkey1=value1&key2=value2 (no spaces, no escaping)
Empty valuesNull or empty string fields excluded
sign excludedThe sign field itself is not included (case-insensitive)
EncodingUTF-8
AlgorithmSHA256withRSA
OutputBase64 string
LocationJSON body sign field (not HTTP header)

Released under the GNU LGPL v3.0