对接完银联的公众号支付,趁现在记忆还比较深刻,赶紧记录一下。
<!--okhttp3-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.6.0</version>
</dependency>
商户需自行生成merOrderId。此时merOrderId需要符合银商规范,以银商分配的4位来源编号(msgSrcId)作为订单号的前4位,且在商户系统中此订单号保证唯一。总长度需大于6位,小于32位。
签名支持 MD5 方式与 SHA256 方式(不上送 signType 字段时,默认 md5 方式),
计算sign 的输入数据为待签名字符串加上 key(即:通讯密钥),key 由网付前置平台分配。
在请求参数列表中,除去 sign 参数外,其他需要使用到的参数均为要签名的参数
/***
* 获取统一下单支付URL
* @param merOrderId 订单编号
* @param totalAmount 支付金额
* @param isSeparate 是否分账
* @param subMchId 分账商户号
* @param proportion 分账比例
* @return
*/
public String getPayUrl(int totalAmount, int isSeparate, String subMchId, Float proportion) {
logger.info("获取支付url");
// 组织请求报文,具体参数请参照接口文档,以下仅作模拟
JSONObject json = new JSONObject();
json.put("mid", mid);
json.put("tid", tid);
json.put("msgType", "WXPay.jsPay");
json.put("msgSrc", msgSrc);
json.put("instMid", instMid);
String merOrderId = Util.genMerOrderId(msgSrcId); // 支付订单号需根据银联提供的规则生成
json.put("merOrderId", merOrderId); // 支付成功后订单编号会原样返回
json.put("totalAmount", totalAmount);
json.put("requestTimestamp", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
// 当需要给子账号分账时,需拼接上分账信息
json = separate(totalAmount, isSeparate, subMchId, proportion, json);
json.put("notifyUrl", notifyUrl);
json.put("returnUrl", returnUrl);
// 验签,并拼接URL
String url = Util.makeOrderRequest(json, md5Key, apiUrl_makeOrder);
logger.info(url);
return url;
}
/**
* 支付结果查询方法
* @param merOrderId
* @return
*/
public JSONObject query(String merOrderId){
JSONObject json = new JSONObject();
json.put("mid", mid);
json.put("tid", tid);
json.put("msgType", "query");
json.put("msgSrc", msgSrc);
json.put("instMid", instMid);
json.put("requestTimestamp", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
json.put("merOrderId", merOrderId);
Map<String,String> map = Util.jsonToMap(json);
json.put("sign", Util.makeSign(md5Key,map));
try {
Response reResponse = OkHttpUtil.post(APIurl,json.toString());
String StringTemp = reResponse.body().string();
log.info("StringTemp:"+StringTemp);
JSONObject jsonObject = JSONObject.fromObject(StringTemp);
if("SUCCESS".equals(jsonObject.get("errCode"))){
log.info("查询成功");
} else {
log.info("查询失敗");
log.info(String.valueOf(jsonObject.get("errMsg")));
}
return jsonObject;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 支付成功退款方法
* @param merOrderId
* @param totalAmount
* @param isSeparate
* @param subMchId
* @param proportion
*/
private void refund( String merOrderId, Integer totalAmount,
Integer isSeparate, String subMchId, Float proportion){
JSONObject json = new JSONObject();
json.put("mid", mid);
json.put("tid", tid);
json.put("msgType", "refund");
json.put("msgSrc", msgSrc);
json.put("instMid", instMid);
String refundOrderId = Util.genMerOrderId(msgSrcId);// 退款订单号需根据银联提供的规则生成
json.put("requestTimestamp", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
json.put("refundOrderId", refundOrderId);
json.put("merOrderId", merOrderId);
json.put("totalAmount", totalAmount);
json.put("refundAmount", totalAmount);
// 当需要给子账号分账时,需拼接上分账信息
json = separate(totalAmount, isSeparate, subMchId, proportion, json);
Map<String,String> map = Util.jsonToMap(json);
json.put("sign", Util.makeSign(md5Key,map));
log.info("退款:"+json);
try {
Response reResponse = OkHttpUtil.post(APIurl,json.toString());
String StringTemp = reResponse.body().string();
log.info("StringTemp:"+StringTemp);
JSONObject jsonObject = JSONObject.fromObject(StringTemp);
if("SUCCESS".equals(jsonObject.get("errCode"))){
log.info("退款成功");
} else {
log.info("退款失敗");
log.info(String.valueOf(jsonObject.get("errMsg")));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 多商户号分账(无需分账请忽略)
* @param totalAmount
* @param isSeparate
* @param subMchId
* @param proportion
* @param json
* @return
*/
private JSONObject separate(int totalAmount, int isSeparate, String subMchId, Float proportion, JSONObject json) {
logger.info("是否启用分账功能(1-是,2-否):");
logger.info(String.valueOf(isSeparate));
logger.info("分账商户号:");
logger.info(subMchId);
logger.info("分账比例:");
logger.info(String.valueOf(proportion));
// 开启分账
if(isSeparate == 1){
json.put("divisionFlag", true); // 分账标记
Float proportionFlaot = Float.valueOf(proportion);
// 单位为分,不可以有小数点,最终的值要为INT
Float subTotalAmountFloat = totalAmount * proportionFlaot / 100;
Integer subTotalAmount = subTotalAmountFloat.intValue();
Integer platformAmount = totalAmount - subTotalAmount;
json.put("platformAmount", platformAmount); // 主商户号分账金额
List<Map<String,Object>> subOrders = new ArrayList<>();
Map<String,Object> map = new HashMap<>();
map.put("mid",subMchId); // 子商户号
map.put("totalAmount",subTotalAmount); //子商户号分账金额
subOrders.add(map);
json.put("subOrders", subOrders); // 子商户号
}
return json;
}
/**
* Created by myinsert on 2019/8/25.
* 验签方式有MD5和SHA256
*/
import net.sf.json.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
public class Util {
public static String makeOrderRequest(JSONObject json, String md5Key, String apiUrl) {
Map<String, String> params = jsonToMap(json);
params.put("sign", makeSign(md5Key, params));
return apiUrl + "?" + buildUrlParametersStr(params);
}
public static String makeSign(String md5Key, Map<String, String> params) {
String preStr = buildSignString(params); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
String text = preStr + md5Key;
if("SHA256".equalsIgnoreCase(params.get("signType"))){
return DigestUtils.sha256Hex(getContentBytes(text));
} else {
return DigestUtils.md5Hex(getContentBytes(text)).toUpperCase();
}
}
public static boolean checkSign(String md5Key, Map<String, String> params) {
String sign = params.get("sign");
if (StringUtils.isBlank(sign)) {
return false;
}
String signV = makeSign(md5Key, params);
return StringUtils.equalsIgnoreCase(sign, signV);
}
// 获取HttpServletRequest里面的参数,并decode
public static Map<String, String> getRequestParams(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
Map<String, String> params2 = new HashMap<>();
for (String key : params.keySet()) {
String[] values = params.get(key);
if (values.length > 0) {
params2.put(key, values[0]);
}
}
return params2;
}
public static String genMerOrderId(String msgId) {
String date = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS");
String rand = RandomStringUtils.randomNumeric(7);
return msgId + date + rand;
}
private static String buildUrlParametersStr(Map<String, String> paramMap) {
Map.Entry entry;
StringBuffer buffer = new StringBuffer();
for (Iterator iterator = paramMap.entrySet().iterator(); iterator.hasNext(); ) {
entry = (Map.Entry) iterator.next();
buffer.append(entry.getKey().toString()).append("=");
try {
if (entry.getValue() != null && StringUtils.isNotBlank(entry.getValue().toString())) {
buffer.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
}
} catch (UnsupportedEncodingException e) {
}
buffer.append(iterator.hasNext() ? "&" : "");
}
return buffer.toString();
}
// 使json-lib来进行json到map的转换,fastjson有排序问题,不能用
public static Map<String, String> jsonToMap(JSONObject json) {
Map<String, String> map = new HashMap<>();
for (Object key : json.keySet()) {
String value = json.optString((String) key);
map.put((String) key, value);
}
return map;
}
// 构建签名字符串
private static String buildSignString(Map<String, String> params) {
if (params == null || params.size() == 0) {
return "";
}
List<String> keys = new ArrayList<>(params.size());
for (String key : params.keySet()) {
if ("sign".equals(key))
continue;
if (StringUtils.isEmpty(params.get(key)))
continue;
keys.add(key);
}
Collections.sort(keys);
StringBuilder buf = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
buf.append(key + "=" + value);
} else {
buf.append(key + "=" + value + "&");
}
}
return buf.toString();
}
// 根据编码类型获得签名内容byte[]
private static byte[] getContentBytes(String content) {
try {
return content.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("签名过程中出现错误");
}
}
}