uni-app微信、支付宝小程序-银联支付
准备材料
1、跟银联支持人员开通的账号信息
商户号:898330671212
终端号:00000001
消息来源(msgSrc):WWW.ZJABC.COM
来源编号(msgSrcId):12Q5
通讯密钥: 3r4bNXdbdjbJT4x4SDFD89S0DFSDF890SDF8SD90
机构商户号(instMid): MINIDENSDFD
交易地址:https://qr.chinaums.com/netpay-route-server/api/
Java端代码编写
1、配置类编写
@Getter
@Setter
public static class UnionPay {
// 交易地址
private String gateway = "https://qr.chinaums.com/netpay-route-server/api/";
// 商户号
private String mchId;
// 终端号
private String terminalId;
// 消息来源
private String msgSrc;
// 来源编号
private String msgSrcId;
// 机构商户号
private String instMid;
// 通讯密钥
private String communicationKey;
// 支付回调地址
private String notifyUrl;
}
2、dev.yml配置

3、Util工具类
这个类目前主要用于签名
package com.nis.smart_charge.utils.unionpay;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class UnionPayUtils {
/**
* 构建签名
*
* @param md5Key
* @param params
* @return
*/
public static String makeSign(String md5Key, Map<String, Object> params) {
String preStr = buildSignString(params); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
String text = preStr + md5Key;
return DigestUtils.sha256Hex(getContentBytes(text)).toUpperCase();
}
/**
* 构建签名字符串
*
* @param params
* @return
*/
private static String buildSignString(Map<String, Object> 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).toString())) {
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).toString();
if (i == keys.size() - 1) {
// 拼接时,不包括最后一个&字符
buf.append(key).append("=").append(value);
} else {
buf.append(key).append("=").append(value).append("&");
}
}
return buf.toString();
}
/**
* 根据编码类型获得签名内容byte[]
*
* @param content 签名内容
* @return 返回签名内容字节数组
*/
private static byte[] getContentBytes(String content) {
return content.getBytes(StandardCharsets.UTF_8);
}
}
4、支付下单
package com.nis.smart_charge.modules.consumer.service.unionpay.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.nis.smart_charge.config.AppProperties;
import com.nis.smart_charge.exception.problem.BadCredentialProblem;
import com.nis.smart_charge.exception.problem.BusinessErrorProblem;
import com.nis.smart_charge.modules.consumer.domain.vo.wechat.MicroPayVo;
import com.nis.smart_charge.modules.consumer.service.unionpay.IUnionPayService;
import com.nis.smart_charge.security.CurrentUser;
import com.nis.smart_charge.utils.security.SecurityUtils;
import com.nis.smart_charge.utils.unionpay.UnionPayUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* 银联支付相关接口
*
* @author lwj
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UnionPayServiceImpl implements IUnionPayService {
private final AppProperties appProperties;
@Override
public MicroPayVo microAppPayOrder(Long code, Long merchantId, BigDecimal amount, String description) {
final CurrentUser currentUser = SecurityUtils.getCurrentLogin().orElseThrow(BadCredentialProblem::new);
final AppProperties.UnionPay unionPay = appProperties.getUnionPay();
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("mid", unionPay.getMchId());
paramsMap.put("tid", unionPay.getTerminalId());
// 区分支付方式(微信wx.unifiedOrder;支付宝trade.create;云闪付uac.miniOrder)
paramsMap.put("msgType", "wx.unifiedOrder");
paramsMap.put("msgSrc", unionPay.getMsgSrc());
paramsMap.put("instMid", unionPay.getInstMid());
paramsMap.put("merOrderId", unionPay.getMsgSrcId() + code);
// 获取订单总金额,单位为分,系统默认金额单位为元,需要进行转换,amount必须为string类型,
Integer amountStr = amount.multiply(new BigDecimal(100)).intValue();
paramsMap.put("totalAmount", amountStr);
paramsMap.put("tradeType", "MINI");
// 微信和支付宝小程序的用户id
paramsMap.put("subOpenId", currentUser.getWxOpenId());
paramsMap.put("userId", currentUser.getAlipayOpenId());
//是否要在商户系统下单,看商户需求 createBill()
paramsMap.put("requestTimestamp", DateUtil.now());
paramsMap.put("signType", "SHA256");
// 增加回调地址
paramsMap.put("notifyUrl", unionPay.getNotifyUrl());
paramsMap.put("sign", UnionPayUtils.makeSign(unionPay.getCommunicationKey(), paramsMap));
System.out.println("paramsMap:" + paramsMap);
String strReqJsonStr = JSON.toJSONString(paramsMap);
System.out.println("strReqJsonStr:" + strReqJsonStr);
// 发送下单请求
String result = HttpRequest.post(unionPay.getGateway()).body(JSONUtil.toJsonStr(paramsMap)).execute().body();
final JSONObject resultJsonObj = JSONUtil.parseObj(result);
System.out.println("resultJsonObj:" + resultJsonObj.toString());
if (!"SUCCESS".equals(resultJsonObj.get("errCode"))) {
throw new BusinessErrorProblem("银联下单失败!");
}
final JSONObject miniPayRequest = JSONUtil.parseObj(resultJsonObj.get("miniPayRequest"));
return MicroPayVo.builder()
.signType(miniPayRequest.get("signType").toString())
.packageStr(miniPayRequest.get("package").toString()).timeStamp(miniPayRequest.get("timeStamp").toString())
.nonceStr(miniPayRequest.get("nonceStr").toString()).paySign(miniPayRequest.get("paySign").toString())
.build();
}
}

注意点1:Hutool的工具类请求发送
String result = HttpRequest._post_**_(_**unionPay.getGateway**_())_**.body**_(_**JSONUtil._toJsonStr_**_(_**paramsMap**_))_**.execute**_()_**.body**_()_**;
这里需要注意的是:post请求,如果要把参数放在body中,需要使用HttpRequest,而不能使用HttpUtil。参数传递的时候,paramsMap是一个map,这边如果要把map转json字符串,需要使用JSONUtil去转换。不能通过Strings.valueOf这类的进行转换。
注意点2:下单请求传给微信支付部分signType问题
微信小程序调起支付API
微信小程序支付,调起支付Api的时候,signType为MD5,但是银联微信支付的时候signTyp为RSA。这里需要动态传递给前端。


5、退款

注意点1:请求发起IP绑定白名单
这里需要注意的是,退款请求发起的服务器的公网IP,需要配置到银联的IP白名单中,不然会报错。
前端部分(uniapp)
apiChargePay(chargeData).then(res => {
uni.hideLoading()
let orderId = res.data.orderId
let orderPayId = res.data.orderPayId
let timeStamp = res.data.timeStamp
let nonceStr = res.data.nonceStr
let pk = res.data.package
let paySign = res.data.paySign
let signType = res.data.signType
let _this = this
if (nonceStr == null) {
// 跳转到充电中页面
this.redirectToChargeingPage(orderId, orderPayId)
return
}
//用户微信端确认支付,执行支付操作
uni.requestPayment({
"timeStamp": timeStamp,
"nonceStr": nonceStr,
"signType": signType,
"package": pk,
"paySign": paySign,
success: (res) => {
// 用户支付后,跳转到充电中的页面O
this.redirectToChargeingPage(orderId, orderPayId)
},
fail: (err) => {
uni.showToast({
icon: 'none',
title: "支付失败"
})
// 取消订单的问题。
apiDeleteChargeOrder(orderId)
}
})
})
下单成功消息通知
微信下单成功消息通知

支付宝下单成功消息通知

测试
由于测试需要在手机上展示,暂不放测试效果图了。