🔐 重要提示:请严格按照本文档说明进行 API 认证与签名,确保安全通信。
| 参数名 | 含义描述 | 类型 | 必填 |
|---|---|---|---|
x-paykka-appid | 调用方唯一标识 | String[1,64] | ✅ |
x-paykka-timestamp | 时间戳,单位毫秒,与当前时间误差不超过 5 分钟 | String[1,11] | ✅ |
x-paykka-nonce | 防重放随机数,每次请求唯一 | String[10,100] | ✅ |
x-paykka-sign | 请求签名 | String[1,500] | ✅ |
x-paykka-sign-alg | 签名类型,固定传 SHA256_WITH_RSA | String[1,10] | ✅ |
| 参数名 | 含义描述 | 类型 | 必填 |
|---|---|---|---|
x-paykka-timestamp | 时间戳 | String[1,11] | ✅ |
x-paykka-nonce | 防重放随机数,每次响应唯一 | String[10,100] | ✅ |
x-paykka-sign | 响应签名 | String[1,500] | ✅ |
{
"merchant_id": "18356675194960",
"payment_type": "PURCHASE",
"authorisation_type": "FINAL_AUTH",
"capture_method": "AUTOMATIC",
"trans_id": "t202311081113",
"amount": 445,
"currency": "EUR",
"return_url": "https://www.baidu.com/returnUrl",
"payment": {
"payment_method": "BankCard",
"store_payment_method": false,
"token_usage": "CARD_ON_FILE",
"shopper_reference": "user1234567890",
"encrypted_card_no": "string",
"encrypted_exp_year": "string",
"encrypted_exp_month": "string",
"encrypted_cvv": "string"
}
}{
"ret_code": "000000",
"ret_msg": "Success",
"data": {
"merchant_id": "18356675194960",
"trans_id": "t202311081113",
"order_id": "GW20598371023658327",
"status": "AUTHORIZED",
"authorisation_type": "FINAL_AUTH",
"capture_method": "AUTOMATIC",
"amount": 445,
"currency": "EUR",
"payment": {
"payment_method": "BANKCARD"
},
"card_info": {
"bin": "424242",
"last4": "4242",
"card_brand": "VISA"
},
"balances": {
"authed_amount": 445,
"captured_amount": 0,
"able_to_capture_amount": 445,
"voided_amount": 0,
"able_to_void_amount": 445,
"refunded_amount": 0,
"able_to_refund_amount": 0
}
}
}https://openapi-sandbox.paykka.comhttps://openapi.paykka.com
https://openapi.eu.paykka.com在您的 Java 代码中引入依赖:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>开发者需要使用自身的私钥对API URL、消息体等关键数据的组合进行 SHA256_WITH_RSA 签名。请求的签名信息通过HTTP头传递,没有携带签名或者签名验证不通过的请求,都不会被执行,并返回401 Unauthorized 。
对于签名验证成功的请求,会使用开放平台的平台私钥对响应进行签名。签名的信息包含在HTTP头部中,开发者应使用开放平台的公钥进行验签。
当调用开发者的接口时,开放平台会使用本平台私钥对回调请求进行签名。签名的方法同响应签名的方式一致,开发者必须使用开放平台的公钥验证回调的签名。
签名串一共有五行,每一行为一个参数。结尾以\n(换行符,ASCII编码值为0x0A)结束。
如果参数为空,也需要附加一个空白字符和\n。
如果参数本身以\n结束,也需要附加一个\n。
https请求方法\n
URL\n
请求时间戳\n
请求随机串\n
请求报文主体- 1、获取HTTP请求的方法(POST)
- 2、获取请求的URL部分:即请求的绝对URL,并去除域名部分得到参与签名的URL。如果请求中有查询参数,URL末尾应附加有'?'和对应的查询字符串。URL中的参数部分应该进行URLEncode编码,以便对中文等特殊符号进行处理。
- 3、获取发起请求时的系统当前时间戳(Unix Timestamp),作为请求时间戳,时间戳精确到毫秒。
- 4、生成一个32位请求随机串,推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。
- 5、获取请求中的请求报文主体(request body)。
示例
POST\n
/api/pay/demo?id=1537\n
1705544961000\n
326425780571035424362645\n
{"merch":"123"}1、使用商户私钥对签名串进行 SHA256_WITH_RSA 签名; 2、并对签名结果进行 Base64编码 + URLEncode 得到签名值。
private static final String FORMAT = "%s\n%s\n%s\n%s\n%s";
String content = String.format(FORMAT,
Optional.ofNullable(req.getMethod()).orElse(""),
Optional.ofNullable(req.getUri()).orElse(""),
req.getTimestamp(),
Optional.ofNullable(req.getNonce()).orElse(""),
Optional.ofNullable(req.getBody()).orElse(""));
Signature signature = Signature.getInstance("SHA256withRSA");
byte[] privateKeys = Base64Utils.decodeFromString(privateKey);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeys);
PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(pkcs8EncodedKeySpec);
signature.initSign(priKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
String result = Base64Utils.encodeToString(signed);
String sign = URLEncoder.encode(result);
log.info("生成签名:{}", sign);- 设置请求头
x-paykka-appid、x-paykka-nonce、x-paykka-timestamp、x-paykka-sign-alg,并确保参数正确 - 将签名设置到请求头
x-paykka-sign中,并确保签名原始串参数和请求体requestBody完全一致
curl 'https://openapi-sandbox.paykka.com/payments' -X POST \
-H 'Content-Type: application/json'\
-H 'Content-Type: application/json' \
-H 'x-paykka-appid: 978594372956732' \
-H 'x-paykka-nonce: 4326048250346354435' \
-H 'x-paykka-sign: Mif3gh48xxxxxxxxx' \
-H 'x-paykka-sign-alg: SHA256_WITH_RSA' \
-H 'x-paykka-timestamp: 1757387467986' \
-d '{"merchant_id": "18356675194960"}'⚠️ 注意: 验证签名时使用的公钥是 PayKKa 公钥
private static final String FORMAT = "%s\n%s\n%s\n%s\n%s";
String content = String.format(FORMAT,
Optional.ofNullable(req.getMethod()).orElse(""),
Optional.ofNullable(req.getUri()).orElse(""),
resp.getTimestamp(),
Optional.ofNullable(resp.getNonce()).orElse(""),
Optional.ofNullable(resp.getBody()).orElse(""));
Signature signature = Signature.getInstance("SHA256withRSA");
byte[] publicKeys = Base64Utils.decodeFromString(publicKey);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeys);
PublicKey pubKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
signature.initVerify(pubKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
String channelSignature = URLDecoder.decode("signatureDataFromPayKKa", StandardCharsets.UTF_8);
boolean verified = signature.verify(Base64Utils.decodeFromString(channelSignature));