业务场景:基本上做业务的话,也是逃不开对接各种支付接口的,比如数字人民币支付、农行免密支付、支付宝支付、微信支付等等。在着手开发时候,也是遇到不少阻力,微信官方提供的接口文档很散乱,如果之前没接触过,一上来就来搞微信支付,即使参考很多文档、材料,也是很令人抓狂的。在这篇文章中,主要记录的就是对接微信支付的流程,以及开发中遇到的问题。
一、了解支付流程
商户自行申请入驻微信支付,无服务商协助。(商户平台申请)成为直连商户
申请APPID
申请mchid
绑定APPID及mchid
配置API key
(这些是需要准备的参数)…
二、开发流程
2.1 添加依赖
<dependency>
<groupId>com.github.liyiorg</groupId>
<artifactId>weixin-popular</artifactId>
<version>2.8.5</version>
</dependency>
2.2 建配置类:WxPayConfig
public class WxPayConfig {
/** APPID */
public static final String APPID = "";
/** 商户号 */
public static final String MCH_ID = "";
/** 密钥 */
public static final String PRIVATE_KEY = "";
/**AppSecret 微信开放平台 对应app的开发密码,app支付不需要 */
public static final String APPSECRET = "";
/** 用户订单支付结果异步通知url */
public static final String NOTIFY_URL = "";
}
2.3 建工具类:WxPayConfig
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.util.*;
public class WXPayUtil {
/**
* 生成微信支付sign
* @return
*/
public static String createSign(SortedMap<String, String> params, String key){
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> es = params.entrySet();
Iterator<Map.Entry<String, String>> it = es.iterator();
//生成 stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
while (it.hasNext()){
Map.Entry<String, String> entry = (Map.Entry<String, String>)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key=").append(key);
String sign = MD5(sb.toString()).toUpperCase();
return sign;
}
/**
* XML格式字符串转换为Map
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
}
return data;
} catch (Exception ex) {
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 生成uuid,即用来标识一笔单,也用做 微信支付的nonce_str
* @return
*/
public static String generateUUID(){
String uuid = UUID.randomUUID().toString().
replaceAll("-","").substring(0,32);
return uuid;
}
/**
* MD
* @param data
* @return
*/
public static String MD5(String data){
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte [] array = md5.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 获取用户请求ip
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
//根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
/**
* 封装post,返回请求的结果
* @return
*/
public static String doPost(String url, String data, int timeout){
CloseableHttpClient httpClient = HttpClients.createDefault();
//超时设置
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout) //连接超时
.setConnectionRequestTimeout(timeout)//请求超时
.setSocketTimeout(timeout)
.setRedirectsEnabled(true) //允许自动重定向
.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-Type","application/json;charset=utf-8");
if(data != null && data instanceof String){ //使用字符串传参
StringEntity stringEntity = new StringEntity(data,"UTF-8");
httpPost.setEntity(stringEntity);
}
try{
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
if(httpResponse.getStatusLine().getStatusCode() == 200){
String result = EntityUtils.toString(httpEntity, "UTF-8");
return result;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
}
2.4 建控制层:WxController
/**
* 微信app支付
* @param
* @return
* @throws Exception
*/
@PostMapping("/WXPayment")
ResultMsg<Object> WXPayment(@RequestBody OrderVO orderVO) throws Exception {
ResultMsg<Object> resultMsg = new ResultMsg<>();
try {
resultMsg = wxService.WXPayment(orderVO);
} catch (Exception e) {
e.printStackTrace();
resultMsg.setSuccess(false);
resultMsg.setResultMsg("系统异常,支付失败!");
return resultMsg;
}
return resultMsg;
}
/**
* 微信支付回调
* @param request
* @param response
* @return
* @throws Exception
*/
@PostMapping(value = "/wechatPay/callback")
public String wechatPayCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
return wxService.wechatPayCallback(request, response);
}
2.4 service实现类:WxServiceImpl
/***
* 微信预支付接口
* @param orderVO
* @return
*/
@Override
public ResultMsg<Object> WXPayment(OrderVO orderVO) {
ResultMsg<Object> resultMsg = new ResultMsg<Object>();
if (StringUtils.isBlank(orderVO.getUserId()) ||
StringUtils.isBlank(orderVO.getOrderId())||
StringUtils.isBlank(orderVO.getType())||
StringUtils.isBlank(orderVO.getTotalPrice())
) {
resultMsg.setSuccess(false);
resultMsg.setResultMsg("信息不完整!");
return resultMsg;
}
Unifiedorder unifiedorder = new Unifiedorder();
unifiedorder.setAppid(WxPayConfig.APPID);
unifiedorder.setMch_id(WxPayConfig.MCH_ID);
unifiedorder.setNonce_str(UUID.randomUUID().toString().replaceAll("-", ""));
unifiedorder.setBody(orderVO.getOrderId());//订单id 拉起后,在微信页面上方显示的数据
unifiedorder.setOut_trade_no(orderVO.getOrderId());//订单id
String receivableAmount = orderVO.getTotalPrice();//自己业务的支付总价,但是微信支付接口以分为单位
String money = new BigDecimal(receivableAmount).multiply(new BigDecimal("100")).stripTrailingZeros().toPlainString();
String fee = money;
unifiedorder.setTotal_fee(fee); // 订单总金额,单位为分;
unifiedorder.setSpbill_create_ip("127.0.0.1");
unifiedorder.setNotify_url(WxPayConfig.NOTIFY_URL);//回调接口地址,用来接收微信通知,以及处理我们自己的业务逻辑
unifiedorder.setTrade_type("APP");
unifiedorder.setAttach(orderVO.getType());//附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用,实际情况下只有支付完成状态才会返回该字段。示例值:自定义数据 说白了就是前端调微信拉起预支付接口的某些参数,需要我们在回调接口处理业务逻辑使用
log.info("微信APP支付--(签名前):" + XMLConverUtil.convertToXML(unifiedorder));
/** 获取签名 */
UnifiedorderResult unifiedorderResult = PayMchAPI.payUnifiedorder(unifiedorder, WxPayConfig.PRIVATE_KEY);
log.info("微信APP支付--支付统一下单接口请求状态(return_code):" + unifiedorderResult.getReturn_code());
log.info("微信APP支付--支付统一下单接口请求状态(return_msg):" + unifiedorderResult.getReturn_msg());
log.info("微信APP支付--支付统一下单接口请求状态(result_code):" + unifiedorderResult.getResult_code());
log.info("微信APP支付--支付请求参数封装(签名后):" + XMLConverUtil.convertToXML(unifiedorder));
log.info("微信APP支付--支付统一下单接口返回数据:" + JsonUtil.toJson(unifiedorderResult));
// 下单结果验签;
if (unifiedorderResult.getSign_status() != null && unifiedorderResult.getSign_status()) {
log.info("微信APP支付验签成功");
MchPayApp generateMchAppData = PayUtil.generateMchAppData(unifiedorderResult.getPrepay_id(), WxPayConfig.APPID, WxPayConfig.MCH_ID,
WxPayConfig.PRIVATE_KEY);
Map<String, Object> map = new HashMap<>();
map.put("payOrderId", orderVO.getOrderId());//订单id
map.put("mchPayApp", generateMchAppData);
log.info(" WXPayment return map" + map);
resultMsg.setData(map);
resultMsg.setSuccess(true);
return resultMsg;
}
return resultMsg;
}
/***
* 微信回调接口
* @return
*/
@Override
public String wechatPayCallback(HttpServletRequest request, HttpServletResponse response) {
ResultMsg<Object> resultMsg = new ResultMsg<>();
// 解析微信支付异步通知请求参数;
String xml = null;
try {
xml = StreamUtils.copyToString(request.getInputStream(), Charset.forName("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
Map<String, String> params = XMLConverUtil.convertToMap(xml);
MchPayNotify payNotify = XMLConverUtil.convertToObject(MchPayNotify.class, xml);
/** 打印日志信息 */
log.info("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$---进入微信支付异步通知请求接口---$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
log.info("微信支付用户订单支付结果异步通知请求参数(xml):" + params);
log.info("微信支付用户订单支付结果异步通知请求参数(map):" + params);
log.info("return_code:" + payNotify.getReturn_code());
log.info("return_msg:" + params.get("return_msg"));
String out_trade_no = payNotify.getOut_trade_no(); // 用户订单号;
String money = String.valueOf(payNotify.getTotal_fee());//支付金额
String total_amount = new BigDecimal(money).multiply(new BigDecimal("0.01")).stripTrailingZeros().toPlainString();//单位换算成元
String trade_no = payNotify.getTransaction_id();//微信交易号
//CampOrder campOrder = campDao.selectOrderDetail(orderId);
/** 校验支付成功还是失败 */
if ("SUCCESS".equals(payNotify.getReturn_code())) {
/** 获取微信后台返回来的异步通知参数 */
String tradeNo = payNotify.getTransaction_id(); // 微信交易号;
String tradeStatus = payNotify.getResult_code(); // 微信支付状态;
Integer totalFee = payNotify.getTotal_fee(); // 支付金额 (单位:分)
String subject = payNotify.getAttach(); // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用,实际情况下只有支付完成状态才会返回该字段。示例值:自定义数据
boolean flag = SignatureUtil.validateSign(params, WxPayConfig.PRIVATE_KEY);
//返回结果return_code 和result_code都为SUCCESS的时候才算成功
if (flag && "SUCCESS".equals(tradeStatus)) {
/** 更新订单支付信息: */
log.info("********************** 支付成功(微信异步通知) **********************");
log.info("* 订单号: " + out_trade_no);
log.info("* 微信交易号: " + trade_no);
log.info("* 实付金额: " + total_amount);
log.info("***************************************************************");
/************ 愣着干嘛,处理业务啊 ************/
log.info("微信支付成功...");
/** 封装通知信息 */
MchBaseResult baseResult = new MchBaseResult();
baseResult.setReturn_code("SUCCESS");
baseResult.setReturn_msg("OK");
xml = XMLConverUtil.convertToXML(baseResult);
} else {
MchBaseResult baseResult = new MchBaseResult();
baseResult.setReturn_code("FAIL");
baseResult.setReturn_msg("FAIL");
xml = XMLConverUtil.convertToXML(baseResult);
//TODO 支付失败;逻辑
}
} else {
MchBaseResult baseResult = new MchBaseResult();
baseResult.setReturn_code("FAIL");
baseResult.setReturn_msg("FAIL");
xml = XMLConverUtil.convertToXML(baseResult);
}
log.info("微信支付--APP支付方式支付用户订单异步通知返回数据:" + xml);
return xml;
}