Signature
DaxPay Payment API uses RSA asymmetric encryption for signing. Requests, responses, and callbacks are all signed to prevent tampering.
Key System
| Key | Held By | Used For |
|---|---|---|
| Merchant private key | Merchant server | Signing requests |
| Merchant public key | DaxPay platform | Platform verifies requests |
| Platform private key | DaxPay platform | Signing responses/callbacks |
| Platform public key | Merchant server | Merchant 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:
| Field | Location | Description |
|---|---|---|
| sign | JSON body | Signature value (Base64) |
| reqId | JSON body | Unique request identifier |
| nonceStr | JSON body | Random string |
| reqTime | JSON body | Request time (yyyy-MM-dd HH:mm:ss, GMT+8) |
Signing Steps
1. Build sign string
- Get all fields from the request JSON (including flattened nested objects).
- Exclude the
signfield. - Exclude empty values (null or empty string).
- Sort remaining fields by key in ASCII order.
- 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:
- Get all fields from the response JSON.
- Remove
sign. - Sort and join the same way as request signing.
- 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
| Rule | Description |
|---|---|
| Sort | ASCII ascending by key |
| Format | key1=value1&key2=value2 (no spaces, no escaping) |
| Empty values | Null or empty string fields excluded |
| sign excluded | The sign field itself is not included (case-insensitive) |
| Encoding | UTF-8 |
| Algorithm | SHA256withRSA |
| Output | Base64 string |
| Location | JSON body sign field (not HTTP header) |