# API 认证 > 🔐 **重要提示**:请严格按照本文档说明进行 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] | ✅ | ## 示例 ### 请求体 ```json { "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" } } ``` ### 响应体 ```json { "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 } } } ``` ## API 地址 ### 沙箱 API 地址 ```bash https://openapi-sandbox.paykka.com ``` ### 生产 API 地址 ```bash https://openapi.paykka.com https://openapi.eu.paykka.com ``` ## 计算签名 ### 添加依赖 在您的 Java 代码中引入依赖: ```xml commons-codec commons-codec 1.15 ``` ### 生成签名 #### 请求签名 开发者需要使用自身的私钥对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` 得到签名值。 ```java 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); ``` ### 设置请求头 1. 设置请求头 `x-paykka-appid`、`x-paykka-nonce`、`x-paykka-timestamp`、`x-paykka-sign-alg`,并确保参数正确 2. 将签名设置到请求头 `x-paykka-sign` 中,并确保签名原始串参数和请求体 `requestBody` 完全一致 ### 请求示例 ```bash 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 公钥 ```java 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)); ```