1. 请求地址
2.请求参数
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
out_biz_no | String | 必选 | 订单号 | 201806300001 | |
amount | Price | 必选 | 20 | 订单总金额,单位为分,不支持千位分隔符,精确到小数点后两位,取值范围[10,1000000000]。 | 23.00 |
identity | String | 必选 | 64 | 支付宝账号 :用于登录支付宝的账号 | 111111 |
name | String | 必选 | 用户姓名 | 张三 | |
signType | String | 必选 | 签名类型 RSA MD5 建议使用MD5 | 201905代发 | |
sign | String | 必选 | 签名 |
3.响应参数
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
code | String | 是 | - | 网关返回码,详见文档 | 40004 |
msg | String | 是 | - | 网关返回码描述,详见文档 | Business Failed |
sub_code | String | 否 | - | 业务返回码,参见具体的API接口文档 | ACQ.TRADE_HAS_SUCCESS |
sub_msg | String | 否 | - | 业务返回码描述,参见具体的API接口文档 | 交易已被支付 |
out_biz_no | String | 商户订单号 | |||
order_id | String | 支付宝转账订单号 | |||
pay_fund_order_id | String | 支付宝支付资金流水号 | |||
status | 状态 | ||||
trans_date | String | 订单支付时间 |
签名:
使用RSA 签名, java 代码示例:
package com.chimera.boomshop.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wiremock.org.apache.commons.lang3.ArrayUtils;
import wiremock.org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
public class SignUtil {
static Logger log = LoggerFactory.getLogger(SignUtil.class);
private static final String SIGN_TYPE_RSA = "RSA";
public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
/**
* 创建签名
*
* @param bean
* @param signKey
* @return
*/
public static String createSign(Object bean, String signKey) {
return createSign(bean2Map(bean), SIGN_TYPE_RSA, signKey);
}
/**
* 创建签名
*
* @param params
* @param signType MD5 和 RSA
* @param signKey
* @return
*/
public static String createSign(Map<String, String> params, String signType, String signKey) {
return createSign(params, signType, signKey, new String[]{"sign"});
}
public static String createSign(Map<String, String> params, String signType, String signKey, String[] ignoredParams) {
String signStr = getSignString(params, signKey, signType, ignoredParams);
log.debug("签名前STR={}", signStr);
if (Objects.equals(SIGN_TYPE_RSA, signType)) {
// 使用RSA 签名
signStr = signRSA(signStr, signKey);
} else {
signStr = DigestUtils.md5Hex(signStr).toUpperCase();
}
log.debug("签名后STR={}", signStr);
return signStr;
}
private static String getSignString(Map<String, String> params, String signType, String signKey, String[] ignoredParams) {
StringBuilder toSign = new StringBuilder();
for (String key : new TreeMap<>(params).keySet()) {
String value = params.get(key);
boolean shouldSign = false;
if (StringUtils.isNotEmpty(value) && !ArrayUtils.contains(ignoredParams, key)) {
shouldSign = true;
}
if (shouldSign) {
toSign.append(key).append("=").append(value).append("&");
}
}
if (Objects.equals(SIGN_TYPE_RSA, signType)) {
toSign.append("key=").append(signType);
} else {
toSign.append("key=").append(signKey);
}
return toSign.toString();
}
private static String signRSA(String data, String signKey) {
String signature = null;
try {
PrivateKey privateKey = getPrivateKey(signKey);
Signature Sign = Signature.getInstance(SIGNATURE_ALGORITHM);
Sign.initSign(privateKey);
Sign.update(data.getBytes());
signature = Base64.encodeBase64String(Sign.sign());
} catch (Exception e) {
log.error("签名失败! msg:{}", e.getMessage(), e);
}
return signature;
}
private static PrivateKey getPrivateKey(String signKey) throws InvalidKeySpecException, NoSuchAlgorithmException {
KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(signKey)));
}
/**
* Bean 转 Map
*
* @param bean
* @return
*/
private static Map<String, String> bean2Map(Object bean) {
Map<String, String> result = new HashMap<>();
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
try {
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
if (field.get(bean) == null) {
field.setAccessible(isAccessible);
continue;
}
//忽略掉静态成员变量
if (!Modifier.isStatic(field.getModifiers())) {
result.put(field.getName(), field.get(bean).toString());
}
field.setAccessible(isAccessible);
} catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
log.error(e.getMessage(), e);
}
}
return result;
}
/**
* 签名
*
* @param bean
* @param signature
* @return
*/
public static boolean checkSign(Object bean, String signature) {
Map<String, String> params = bean2Map(bean);
if (Objects.equals(SIGN_TYPE_RSA, params.get("signType"))) {
return verifySign(params, signature, params.get("sign"));
}
String sign = createSign(params, signature);
return sign.equals(params.get("sign"));
}
private static boolean verifySign(Map<String, String> params, String publicKey, String signature) {
String signString = getSignString(params, SIGN_TYPE_RSA, null, new String[]{"sign"});
return verifySignRSA(signString, publicKey, signature);
}
private static boolean verifySignRSA(String data, String publicKeyStr, String signature) {
boolean verifySignSuccess = false;
try {
PublicKey publicKey = getPublicKey(publicKeyStr);
Signature verifySign = Signature.getInstance(SIGNATURE_ALGORITHM);
verifySign.initVerify(publicKey);
verifySign.update(data.getBytes());
verifySignSuccess = verifySign.verify(Base64.decodeBase64(signature));
} catch (Exception e) {
log.error("签名失败! msg:{}", e.getMessage(), e);
}
return verifySignSuccess;
}
private static PublicKey getPublicKey(String signKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(signKey)));
}
}
调用示例:
TransferReq req = new TransferReq();
req.setName("AA");
req.setAmount(10);
req.setIdentity("XT0001");
req.setOutBizOrder("XT0001");
req.setSignType("RSA");
String signValue = SignUtil.createSign(req, privateKey);
reqreq.setSign(signValue);
HttpCli.post(req);
privateKey 加解密私钥
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClNwjR0If7x3O9fWqmO7Y0g1FpRxq+QDnInRPWiNvkrkJ6fBasPr5Ea9NVcI8i0KVrBaaX9v4aP8pYuqmzw4tTyLNLLD9zJyrTFkIYyj2+XBQpUBwgc8FU9LwsfI05v6LjkU0OmJ/idJHTbOwdowoyxfw6npfqU6k7kF5sc2Tbrs0oYnJYyaFhGIsC74K/wCWlXX9RD3TnPo45zBo3k0I9HmbUEb4c4RfU8qZVVYx02Vd4Ytju857Sw5nzbd+svZD7lFpeFdt7Y8YlQlfxBb5ZeDjLN3zNNhOe9kXXs5r+FVZKi0iJVchBMp0YbJ3OXOZ0nFX/pu3lVnw8DvNjtooNAgMBAAECggEAfZDLSjOT9j7Lb1vvf3HodBbN/BaHc8r6X8dKOYYAodfzPIYs8TIqh3PvxYLrtoke/6zmxZ5511deIXuU2MQokz/5TIvPmWROPuouyAqFTLVMuW1iQyPW7wumLKVo7FgCo7Rd/VbwRTtVTXv+JDQy3w3sOl0olnaJK3T43rIBGJiTYEcZIJcVygNa/M+A0FeRrN8R4pTPTXz457qqaY8KHihzeZ3q9J6mSU5eOVsVAUa1jEDHhVGdl1CSt4G5GRGQ8Twsghl+Oqj/W23fMydNaJ4N10MfiTbKiB6X81vN336ned11h86y41UlUNdmANqyM2wxq9/LboHukAJ7+fjwgQKBgQDd2KGYh1ef3sG5Q9atDiFNbvG7hNDs+NZlWmwSXLqDmZzvPMMM7VlKFLp9Pync4hu3Co4FMFSK2ztZ7YqGD0R3jgz4YmOY1M8YI0rDfJvCfe8Fmdc0wy3f9tOswmQIL1Soo9tWbS71SH6II0GU44QsJt/KpGf+tEfrtTkZ8xPaYQKBgQC+pnYuIAod1Ul0zj2xZ1ctbOmkDqewQAscFjEHK46gsGScKMB5vI3EI9UXYcnbL7DxZiVbJyPpi3SGJdLRYUOTO6iLg+GVm9Z42fWCsnpGCH1sNynb3mUWJ96sFwDGzHKHxPR1zSoitnazdsvYL+zAYMrPvnlunKNI3XawaxCHLQKBgQDAaAp/R6LvvTs3e2bNYNvxVK/De0XYwVjxPqNMqa/6Q1ihBYoFxDUU0zWLOyULte9jqXvTdD9ezxWCwUrLSqKPLlyBcJQBEVEsFi/ZrFFLNf4AVZUY5c772A9uXopzrtk2CidJphh8FAvp9uoCP/F6eZzPHBg2CBX4kIydvP0TAQKBgH07SPvpbdvE45iHdPzYPeqH1T0/pyTeHK4ZYaEtGmYxGBV/q1cL5S+Gvg263WZOAHPBPh6PKkbZpPQNWlrRAhtvn7ntKjwk/o+p1FxCspDYONLhtWxIvKv+CoWOIqNzXnQSiuXqm5frtTMTgLkEWgbQTciCfK+M0rElUDZ4TYYNAoGAQvkGfZr9VbO9TIEXuUsdwyd7qg1azvL83cgo4aennub5S6AMqP8zO9xTnHWF3DVMp7ET6rdyD7vraCH/7Vm4vdwQiK1TCM2JEX46JyEaWPzgwEfDC4oC96jM+V/horQ9BorMnl9QNmF2xrU6mPiJ9+HT6ganW4JdvjE353BCfSE=
publicKey 加密/解密 公钥
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApTcI0dCH+8dzvX1qpju2NINRaUcavkA5yJ0T1ojb5K5CenwWrD6+RGvTVXCPItClawWml/b+Gj/KWLqps8OLU8izSyw/cycq0xZCGMo9vlwUKVAcIHPBVPS8LHyNOb+i45FNDpif4nSR02zsHaMKMsX8Op6X6lOpO5BebHNk267NKGJyWMmhYRiLAu+Cv8AlpV1/UQ905z6OOcwaN5NCPR5m1BG+HOEX1PKmVVWMdNlXeGLY7vOe0sOZ823frL2Q+5RaXhXbe2PGJUJX8QW+WXg4yzd8zTYTnvZF17Oa/hVWSotIiVXIQTKdGGydzlzmdJxV/6bt5VZ8PA7zY7aKDQIDAQAB