1.银联支付网关demo https://download.csdn.net/download/xinpz/10644012
银联官网 https://open.unionpay.com/ajweb/product/newProDetail?proId=1&cataId=14
步骤:
1.准备整理入参,主要参数,参考
关键入参
参数名称 |
参数说明 |
merId |
商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号 |
orderId |
商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则 |
txnTime |
订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会 报txnTime无效 |
txnAmt |
交易金额,单位分,不要带小数点 |
常用代码,订单号生成+随机数+时间戳
// 时间戳
String currTime = CommonTools.getCurrTime();
// 4位随机数
String strRandom = CommonTools.buildRandom(4) + "";
String orderId = "YTKJ" + currTime + strRandom;
// 获取当前时间戳
Date nowTime = CmUtil.getNowTime("yyyy-MM-dd HH:mm:ss");
用到的部分工具类方法
/**
* 获取当前时间 yyyyMMddHHmmss
* @return String
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
/**
* 取出一个指定长度大小的随机正整数.
*
* @param length
* int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数。
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
/**
* 获取当前date类型时间 cnzh
*
* @return
* @throws ParseException
*/
public static Date getNowTime(String timeFormat) {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat(timeFormat);
String nowTime = sdf.format(calendar.getTime());
Date dateTime = null;
try {
dateTime = sdf.parse(nowTime);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}// 转换为date类型
return dateTime;
}
2.签名处理
报文的签名机制 对于报文的签名处理机制如下:
首先,对报文中出现签名域(signature)之外的所有数据元采用key=value的形式按照名称排序,
然后以&作为连接符拼接成待签名串; 其次对待签名串使用SHA-1算法做摘要,
再使用支付网关系统颁发给商户的签名私钥证书中的私钥对摘要做签名操作; 最后对签名做Base64编码,
将编码后的签名串放在签名(signature)表单域里和其他表单域一起通过HTTP Post的方式传输给支付网关。
// 数据元采用key=value的形式按照名称排序 SortedMap
// 对待签名串使用SHA-1算法做摘要
// TODO 使用陕西信合支付网关系统颁发给商户的签名私钥证书中的私钥对摘要做签名操作
// TODO 对签名做Base64编码
// 将编码后的签名串放在签名(signature)表单域里和其他表单域一起通过HTTP Post的方式传输给陕西信合支付网关。
请求参数在Map
/** 请求参数设置完毕,以下对请求参数进行签名并生成html表单,将表单写入浏览器跳转打开银联页面 **/
Map submitFromData = AcpService.sign(requestData,
DemoBase.encoding); // 报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
AcpService.sign()方法,调用了SDKUtil的两个方法,先去空,再签名
/**
* 请求报文签名(使用配置文件中配置的私钥证书或者对称密钥签名)
* 功能:对请求报文进行签名,并计算赋值certid,signature字段并返回
* @param reqData 请求报文map
* @param encoding 上送请求报文域encoding字段的值
* @return 签名后的map对象
*/
public static Map sign(Map reqData,String encoding) {
reqData = SDKUtil.filterBlank(reqData);
SDKUtil.sign(reqData, encoding);
return reqData;
}
/**
* 过滤请求报文中的空字符串或者空字符串
* @param contentData
* @return
*/
public static Map filterBlank(Map contentData){
LogUtil.writeLog("打印请求报文域 :");
Map submitFromData = new HashMap();
Set keyset = contentData.keySet();
for(String key:keyset){
String value = contentData.get(key);
if (value != null && !"".equals(value.trim())) {
// 对value值进行去除前后空处理
submitFromData.put(key, value.trim());
LogUtil.writeLog(key + "-->" + String.valueOf(value));
}
}
return submitFromData;
}
SDKUtil.sign()工具类签名方法
/**
* 根据signMethod的值,提供三种计算签名的方法
*
* @param data
* 待签名数据Map键值对形式
* @param encoding
* 编码
* @return 签名是否成功
*/
public static boolean sign(Map data, String encoding) {
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
String signMethod = data.get(param_signMethod);
String version = data.get(SDKConstants.param_version);
if (!VERSION_1_0_0.equals(version) && !VERSION_5_0_1.equals(version) && isEmpty(signMethod)) {
LogUtil.writeErrorLog("signMethod must Not null");
return false;
}
if (isEmpty(version)) {
LogUtil.writeErrorLog("version must Not null");
return false;
}
if (SIGNMETHOD_RSA.equals(signMethod)|| VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) {
if (VERSION_5_0_0.equals(version)|| VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) {
// 设置签名证书序列号
data.put(SDKConstants.param_certId, CertUtil.getSignCertId());
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
LogUtil.writeLog("打印排序后待签名请求报文串(交易返回11验证签名失败时可以用来同正确的进行比对):[" + stringData + "]");
byte[] byteSign = null;
String stringSign = null;
try {
// 通过SHA1进行摘要并转16进制
byte[] signDigest = SecureUtil
.sha1X16(stringData, encoding);
LogUtil.writeLog("打印摘要(交易返回11验证签名失败可以用来同正确的进行比对):[" + new String(signDigest)+ "]");
byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft(
CertUtil.getSignCertPrivateKey(), signDigest));
stringSign = new String(byteSign);
// 设置签名域值
data.put(SDKConstants.param_signature, stringSign);
return true;
} catch (Exception e) {
LogUtil.writeErrorLog("Sign Error", e);
return false;
}
} else if (VERSION_5_1_0.equals(version)) {
// 设置签名证书序列号
data.put(SDKConstants.param_certId, CertUtil.getSignCertId());
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
LogUtil.writeLog("打印待签名请求报文串(交易返回11验证签名失败时可以用来同正确的进行比对):[" + stringData + "]");
byte[] byteSign = null;
String stringSign = null;
try {
// 通过SHA256进行摘要并转16进制
byte[] signDigest = SecureUtil
.sha256X16(stringData, encoding);
LogUtil.writeLog("打印摘要(交易返回11验证签名失败可以用来同正确的进行比对):[" + new String(signDigest)+ "]");
byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft256(
CertUtil.getSignCertPrivateKey(), signDigest));
stringSign = new String(byteSign);
// 设置签名域值
data.put(SDKConstants.param_signature, stringSign);
return true;
} catch (Exception e) {
LogUtil.writeErrorLog("Sign Error", e);
return false;
}
}
} else if (SIGNMETHOD_SHA256.equals(signMethod)) {
return signBySecureKey(data, SDKConfig.getConfig()
.getSecureKey(), encoding);
} else if (SIGNMETHOD_SM3.equals(signMethod)) {
return signBySecureKey(data, SDKConfig.getConfig()
.getSecureKey(), encoding);
}
return false;
}
3.请求url
String html = AcpService.createAutoFormHtml(requestFrontUrl,
submitFromData, DemoBase.encoding); // 生成自动跳转的Html表单
报文生成
/**
* 功能:前台交易构造HTTP POST自动提交表单
* @param action 表单提交地址
* @param hiddens 以MAP形式存储的表单键值
* @param encoding 上送请求报文域encoding字段的值
* @return 构造好的HTTP POST交易表单
*/
public static String createAutoFormHtml(String reqUrl, Map hiddens,String encoding) {
StringBuffer sf = new StringBuffer();
sf.append("");
sf.append("");
sf.append("");
sf.append("");
sf.append("");
return sf.toString();
}
http请求
// 将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
resp.getWriter().write(html);
总结:前面只是请求网关接口部分,需配置版本号version,后台通知backUrl,前台通知frontUrl,私钥证书,公钥证书,密码等验签
所有关键代码demo中都有,主要是SDK包
https://download.csdn.net/download/xinpz/10644012