1.前戏
1.1请先完成微信APP支付接入商户服务中心
1.2详情请参考微信官方文档:https://open.weixin.qq.com/
2.application.yml文件的配置如下
#微信支付配置 tenpayconfig: #商户APPID appId: asdfg12345 #商户号 mchId: 12345678 #商户的key(API密匙) key: qwertyuiop #API支付请求地址 payUrl: https://api.mch.weixin.qq.com/pay/unifiedorder #API查询请求地址 queryUrl: https://api.mch.weixin.qq.com/pay/orderquery #package packageValue: Sign=WXPay
3.配置文件对应的TenpayConfig,若没集成lombok请自行生成get/set方法
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import lombok.Data; /** * @Description: 微信支付配置类 * @Param: * @return: * @Author: zengXianKang * @Date: 2019/7/28 */ @Data @Component @ConfigurationProperties(prefix = "tenpayconfig") public class TenpayConfig { //appId private String appId; //商户号 private String mchId; //商户的key(API密匙) private String key; //API支付请求地址 private String payUrl; //API查询请求地址 private String queryUrl; //Sign=WXPay private String packageValue; }
3.1新建一个TenPayVO
import java.math.BigDecimal; import lombok.Data; /** * @description: TenPayVO * @author: zengXianKang * @create: 2019-07-28 **/ @Data public class TenPayVO { //商户订单号 private String outTradeNo; //业务结果 private String resultCode; //签名方式 private String signType; //签名 private String sign; //交易类型 private String tradeType; //交易状态 private String tradeState; //商户号 private String mchId; //付款银行 private String bankType; //支付金额 private BigDecimal totalFee; //币种 private String feeType; //微信支付订单号 private String transactionId; //支付完成时间 private String timeEnd; }
3.2由于微信支付和回调的报文都是xml,先在maven中添加xstream的jar依赖
<!--xstream--> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.7</version> </dependency>
3.3TenPayUtils工具类,直接拿去用吧,该用到的我基本都写好了,拿走不谢
import com.github.pagehelper.util.StringUtil; import com.huaku.ecom.common.config.TenpayConfig; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.DomDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.net.ssl.HttpsURLConnection; import java.io.*; import java.net.URL; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.Map.Entry; /** * TenPayUtils * @author :zengXianKang */ @Component public class TenPayUtils { private static TenPayUtils tenPayUtils; @Autowired private TenpayConfig tenpayConfig; @PostConstruct public void init(){ tenPayUtils = this; tenPayUtils.tenpayConfig = this.tenpayConfig; } /** * @Description: 微信支付签名 * * @Param: [paramsMap, charSetName] * @return: java.lang.String * @Author: zengXianKang * @Date: 2019/7/28 */ public String createSign(SortedMap<String, Object> paramsMap, String charSetName) throws UnsupportedEncodingException, NoSuchAlgorithmException { StringBuffer buffer = new StringBuffer(); //参数按照ACCSII排序(升序) Set set = paramsMap.entrySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()){ Map.Entry entry = (Map.Entry) iterator.next(); String key = (String) entry.getKey(); String value = (String) entry.getValue(); if(!key.equals("sign") && StringUtil.isNotEmpty(value)){ buffer.append(key + "=" + value + "&"); } } buffer.append("key=" + tenPayUtils.tenpayConfig.getKey()); String sign = MDUtils.MD5EncodeForHex(buffer.toString(), charSetName).toUpperCase(); return sign; } /** * @Description: 组装微信支付请求报文 * * @Param: [paramsMap] * @return: java.lang.String * @Author: zengXianKang * @Date: 2019/7/28 */ public static String tenPayXmlInfo(SortedMap<String, Object> paramsMap){ StringBuffer buffer = new StringBuffer(); if(paramsMap != null){ buffer.append("<xml>"); for(Map.Entry<String, Object> entry : paramsMap.entrySet()){ buffer.append("<").append(entry.getKey()).append("><![CDATA[").append(entry.getValue()).append("]]></").append(entry.getKey()).append(">"); } buffer.append("</xml>"); } return buffer.toString(); } /** * @Description: 请求调用URL * * @Param: [requestUrl, requestMethod, output] * @return: java.lang.String * @Author: zengXianKang * @Date: 2019/7/28 */ public static String httpsRequest(String requestUrl, String requestMethod, String output) throws Exception { URL url = new URL(requestUrl); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setUseCaches(false); connection.setRequestMethod(requestMethod); if(StringUtil.isNotEmpty(output)){ OutputStream outputStream = connection.getOutputStream(); outputStream.write(output.getBytes("UTF-8")); outputStream.close(); } InputStream inputStream = connection.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); inputStream.close(); connection.disconnect(); return buffer.toString(); } /** * @Description: 解析xml * * @Param: [xml, rootName, rowName] * @return: java.lang.Object * @Author: zengXianKang * @Date: 2019/7/28 */ public static Object readXml(String xml, String rootName, String rowName){ XStream xStream = new XStream(new DomDriver()); xStream.alias(rootName, Map.class); xStream.registerConverter(new TenPayUtils.MapEntryConverter(rowName)); Object object = xStream.fromXML(xml); return object; } /** * @Description: 内部类,readXml专用 * * @Param: * @return: * @Author: zengXianKang * @Date: 2019/7/28 */ public static class MapEntryConverter implements Converter { private String rowName; public MapEntryConverter(String rowName) { this.rowName = rowName; } public boolean canConvert(Class clazz) { return Map.class.isAssignableFrom(clazz) || LinkedHashMap.class.isAssignableFrom(clazz); } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { this._marshal(value, writer, context); } private void _marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Iterator i$; Object object; if (value instanceof Map) { Map map = (Map) value; for (i$ = map.entrySet().iterator(); i$.hasNext(); writer.endNode()) { object = i$.next(); Entry entry = (Entry) object; Object _key = entry.getKey(); Object _value = entry.getValue(); writer.startNode(entry.getKey().toString()); if (_value instanceof Map) { this._marshal(_value, writer, context); } else if (_value instanceof List) { this._marshal(_value, writer, context); } else { writer.setValue(entry.getValue().toString()); } } } else if (value instanceof List) { List list = (List) value; for (i$ = list.iterator(); i$.hasNext(); writer.endNode()) { object = i$.next(); writer.startNode(this.rowName); if (!(object instanceof Map) && !(object instanceof List)) { writer.setValue(object.toString()); } else { this._marshal(object, writer, context); } } } } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { return this._unmarshal(reader, context); } public Object _unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Map map = new HashMap(); List list = new ArrayList(); boolean isList; for (isList = false; reader.hasMoreChildren(); reader.moveUp()) { reader.moveDown(); String nodeName = reader.getNodeName(); if (reader.hasMoreChildren()) { if (isList) { list.add(this._unmarshal(reader, context)); } else if (map.containsKey(nodeName)) { isList = true; list.add(map.remove(nodeName)); list.add(this._unmarshal(reader, context)); } else if (this.rowName.equals(nodeName)) { isList = true; list.add(this._unmarshal(reader, context)); } else { map.put(nodeName, this._unmarshal(reader, context)); } } else { String value = reader.getValue(); if (isList) { list.add(value); } else if (map.containsKey(nodeName)) { isList = true; list.add(map.remove(nodeName)); list.add(value); } else if (this.rowName.equals(nodeName)) { isList = true; list.add(value); } else { map.put(nodeName, value); } } } return isList ? list : map; } } }
4.请求微信支付
在很多电商的项目中都有类似保存订单,充值等一系列微信支付场景;我们需要发起支付返回给前端支付sdk;该篇博文为微信支付的博文,支付宝支付的博文我也已编写,若有兴趣的话可以去我的博文中看看,希望能帮到你。
4.1发起支付请求
/** * @Description: 支付请求 * @Param: [payType, outTradeNo, totalAmount] * @return: java.util.Map<java.lang.String,java.lang.String> * @Author: zengXianKang * @Date: 2019/7/28 */ @Override public Map<String, String> payRequest(String payType, String outTradeNo, BigDecimal totalAmount) throws Exception { Map<String, String> map = new HashMap<String, String>(); switch (PayTypeEnum.valueOf(payType)) { case TENPAY://财付通 SortedMap<String, Object> paramsMap = new TreeMap<String, Object>(); //公众账号ID paramsMap.put("appid", tenpayConfig.getAppId()); //商户号 paramsMap.put("mch_id", tenpayConfig.getMchId()); //随机字符串 paramsMap.put("nonce_str", Convert.getUUID()); //描述 paramsMap.put("body", "名绘优家订单支付"); //商户订单号(支付编号) paramsMap.put("out_trade_no", outTradeNo); //支付金额,金额单位为 分 double price = totalAmount.doubleValue(); int totalFee = (int) (price * 100); paramsMap.put("total_fee", String.valueOf(totalFee)); //回调地址 paramsMap.put("notify_url", ConstantInfo.TENPAY_ORDER_CALLBACK); //交易类型 paramsMap.put("trade_type", "APP"); //用户端ip String spbillCreateIp = ""; InetAddress inetAddress = InetAddress.getLocalHost(); if (inetAddress != null) { spbillCreateIp = inetAddress.getHostAddress(); } paramsMap.put("spbill_create_ip", spbillCreateIp); TenPayUtils tenPayUtils = new TenPayUtils(); //sign签名 String sign = tenPayUtils.createSign(paramsMap, "UTF-8"); paramsMap.put("sign", sign); //请求报文 String requestXml = TenPayUtils.tenPayXmlInfo(paramsMap); //logger.info("微信支付请求报文: " + requestXml); //发送微信支付post请求 String tenPayPost = TenPayUtils.httpsRequest(tenpayConfig.getPayUrl(), "POST", requestXml); //获取返回 Map<String, String> tenPayMap = (Map<String, String>) TenPayUtils.readXml(tenPayPost, "xml", ""); //微信返回状态码 if (!tenPayMap.get("return_code").equals("SUCCESS")) { logger.error("微信支付请求连接失败: " + tenPayMap.get("return_msg")); throw new RRException(AppWSConstant.RS_MSG_TENPAY_FALL); } //业务结果 if (!tenPayMap.get("result_code").equals("SUCCESS")) { logger.error("err_code: " + tenPayMap.get("err_code"), "err_code_des: " + tenPayMap.get("err_code_des")); throw new RRException(AppWSConstant.RS_MSG_TENPAY_FALL); } //APPID map.put("appid", tenPayMap.get("appid")); //商户号 map.put("partnerid", tenPayMap.get("mch_id")); //预支付交易会话ID map.put("prepayid", tenPayMap.get("prepay_id")); //扩展字段 map.put("package", tenpayConfig.getPackageValue()); //随机字符串 map.put("noncestr", tenPayMap.get("nonce_str")); //时间戳 map.put("timestamp", String.valueOf(new Date().getTime()).substring(0, 10)); SortedMap<String, Object> signMap = new TreeMap<>(map); String newSign = tenPayUtils.createSign(signMap, "UTF-8"); //签名 map.put("sign", newSign); break; default: break; } return map; }
4.1.1ConstantInfo中的内容为
/** * 常量 */ public class ConstantInfo { //订单支付财付通回调地址 public static String TENPAY_ORDER_CALLBACK = "http://mall.gzmhyj.com:8085/huakuEComBuyer/pay/tenPayOrderCallBack"; }
4.2订单微信支付回调
该接口为为微信异步回调提供的接口
/** * @Description: 订单微信支付回调 * * @Param: [request] * @return: java.util.Map<java.lang.String,java.lang.String> * @Author: zengXianKang * @Date: 2019/7/28 */ @RequestMapping(value = "/tenPayOrderCallBack", method = RequestMethod.POST) @ResponseBody public Map<String, String> tenPayOrderCallBack(HttpServletRequest request){ Map<String, String> map = new HashMap<String, String>(); try { TenPayVO tenPayVO = payService.tenPayCallBack(request); payService.tenPayOrderCallBack(tenPayVO); map.put("return_code", "SUCCESS"); map.put("return_msg", "OK"); } catch (Exception e) { e.printStackTrace(); } return map; }
4.2.1payService的tenPayCallBack,用于解析回调的信息,拼接TenPayVO
/** * @Description: 财付通回调 * @Param: [request] * @return: TenPayVO * @Author: zengXianKang * @Date: 2019/7/28 */ @Override public TenPayVO tenPayCallBack(HttpServletRequest request) throws Exception { InputStream inputStream = request.getInputStream(); StringBuffer resXml = new StringBuffer(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); String str; while ((str = bufferedReader.readLine()) != null) { resXml.append(str); } bufferedReader.close(); inputStream.close(); //logger.info("微信回调报文: " + resXml); TenPayVO tenPayVO = this.tenPayCallBackInfo(resXml.toString(), "xml", ""); return tenPayVO; }
4.2.2tenPayCallBackInfo,用于解析微信支付回调返回结果,拼接TenPayVO
/** * @Description: 微信支付回调返回结果 * @Param: [xml, rootName, rowName] * @return: com.huaku.ecom.system.model.vo.TenPayVO * @Author: zengXianKang * @Date: 2019/7/28 */ private TenPayVO tenPayCallBackInfo(String xml, String rootName, String rowName) throws Exception { Map<String, Object> resHashMap = (Map<String, Object>) TenPayUtils.readXml(xml, "xml", ""); SortedMap<String, Object> resMap = new TreeMap<String, Object>(resHashMap); //微信返回状态码 if (!resMap.get("return_code").equals("SUCCESS")) { logger.error("微信支付回调连接失败: " + resMap.get("return_msg")); throw new RRException(AppWSConstant.RS_MSG_TENPAY_FALL); } //业务结果 if (!resMap.get("result_code").equals("SUCCESS")) { logger.error("err_code: " + resMap.get("err_code"), "err_code_des: " + resMap.get("err_code_des")); throw new RRException(AppWSConstant.RS_MSG_TENPAY_FALL); } TenPayUtils tenPayUtils = new TenPayUtils(); //校验签名 String sign = tenPayUtils.createSign(resMap, "UTF-8"); if (!sign.equals(resMap.get("sign"))) { logger.error("微信支付回调签名不正确"); throw new RRException(AppWSConstant.RS_MSG_TENPAY_FALL); } TenPayVO tenPayVO = new TenPayVO(); //商户订单号 tenPayVO.setOutTradeNo((String) resMap.get("out_trade_no")); //业务结果 tenPayVO.setResultCode((String) resMap.get("result_code")); //签名方式 tenPayVO.setSignType("ASCII"); //签名 tenPayVO.setSign((String) resMap.get("sign")); //交易类型 tenPayVO.setTradeType("APP"); //交易状态 tenPayVO.setTradeState((String) resMap.get("trade_state")); //商户号 tenPayVO.setMchId((String) resMap.get("mch_id")); //付款银行 tenPayVO.setBankType((String) resMap.get("bank_type")); //交易金额 BigDecimal totalFee = new BigDecimal((String) resMap.get("total_fee")); totalFee = totalFee.divide(new BigDecimal(100)); tenPayVO.setTotalFee(totalFee); //币种 if (resMap.containsKey("fee_type")) { tenPayVO.setFeeType((String) resMap.get("fee_type")); } //微信支付订单号 tenPayVO.setTransactionId((String) resMap.get("transaction_id")); //支付完成时间 tenPayVO.setTimeEnd((String) resMap.get("time_end")); return tenPayVO; }
4.2.3payService的tenPayOrderCallBack,用于自己的回调完成的业务逻辑,如修改订单状态,存微信支付交易表等操作
/** * @Description: 微信支付订单回调 * @Param: [tenPayVO] * @return: void * @Author: zengXianKang * @Date: 2019/7/28 */ @Override public void tenPayOrderCallBack(TenPayVO tenPayVO) throws Exception { if (tenPayVO != null && tenPayVO.getResultCode().equals("SUCCESS") && tenPayVO.getTradeState().equals("SUCCESS")) { //根据交易编号加锁,处理高并发 synchronized (tenPayVO.getOutTradeNo()) { TOrder order = orderMapper.getOneOrderByPayNo(tenPayVO.getOutTradeNo()); if (order.getOrderStatus().equals(OrderStatusEnum.PENDING_PAYMENT.toString())) { //订单需支付金额总和 BigDecimal payNumSum = this.getPayNumSumByPayNo(tenPayVO.getOutTradeNo()); String orderStatus = ""; //以防万一,再次校验金额 if (payNumSum.compareTo(tenPayVO.getTotalFee()) != 0) { logger.error("***订单号: " + tenPayVO.getOutTradeNo() + "***微信支付支付金额与订单需支付金额总和不一致***微信支付金额为:" + tenPayVO.getTotalFee() + " ***订单需支付金额总为:" + payNumSum + "***日期:" + new Date()); //金额异常,订单状态为支付金额异常 orderStatus = OrderStatusEnum.ABNORMAL_PAYMENT_AMOUNT.toString(); } else { //金额正常,订单状态为已付款(待发货) orderStatus = OrderStatusEnum.WAIT_FOR_DELIVERY.toString(); } //修改订单状态 int orderFlag = orderMapper.updatePayOrderStatusByPayNo(orderStatus, tenPayVO.getOutTradeNo()); //微信支付交易记录表 TTenpayTradeLog tenpayTradeLog = new TTenpayTradeLog(); tenpayTradeLog.setTradeLogId(Convert.createUniqueId(idWorker)); //签名方式 tenpayTradeLog.setSignType(tenPayVO.getSignType()); //交易方式 tenpayTradeLog.setTradeMode(tenPayVO.getTradeType()); //交易状态 tenpayTradeLog.setTradeStatus(tenPayVO.getResultCode()); //商户号 tenpayTradeLog.setPartner(tenPayVO.getMchId()); //银行类型 tenpayTradeLog.setBankType(tenPayVO.getBankType()); //交易金额 tenpayTradeLog.setTotalFee(tenPayVO.getTotalFee()); //币种 tenpayTradeLog.setFeeType(tenPayVO.getFeeType()); //微信支付订单号 tenpayTradeLog.setTransactionId(tenPayVO.getTransactionId()); //商户订单号 tenpayTradeLog.setOutTradeNo(tenPayVO.getOutTradeNo()); //支付完成时间 tenpayTradeLog.setTimeEnd(tenPayVO.getTimeEnd()); int payFlag = tenpayTradeLogMapper.insertSelective(tenpayTradeLog); //若有一个操作出错,抛错回滚 if (!(orderFlag > 0 && payFlag == 1)) { logger.error("微信支付订单回调失败"); throw new RRException(AppWSConstant.RS_MSG_TENPAY_FALL); } } else { logger.info("该订单已支付处理,交易编号为: " + tenPayVO.getOutTradeNo()); throw new RRException(AppWSConstant.RS_MSG_ORDER_PAY_ERROR); } } } }
4.3定时任务主动查询微信支付回调,一般微信发起的异步回调都是无序不定时的,所以一般保险起见都会写一个自己的定时任务主动查询微信支付回调
/** * 定时任务:每十五分钟触发一次主动调用订单支付回调 */ @Scheduled(cron = "0 */15 * * * ?") public void initiativeOrderPayCallBack(){ //主动调用订单支付回调 try { payService.initiativeOrderPayCallBack(); } catch (Exception e) { logger.error("timer initiativeOrderPayCallBack Error.", e); e.printStackTrace(); } }
4.3.1payService的initiativeOrderPayCallBack,用于主动查询微信支付回调与回调业务逻辑处理
/** * 主动调用订单支付回调 * * @throws Exception */ @Override public void initiativeOrderPayCallBack() throws Exception { //查询订单状态为orderStatus的支付编号 List<Map<String, String>> payNoList = orderMapper.getPayNoByStatus(OrderStatusEnum.PENDING_PAYMENT.toString()); for (Map<String, String> map : payNoList) { try { switch (PayTypeEnum.valueOf(map.get("payType"))) { case TENPAY://财付通 TenPayVO tenPayVO = this.tenPayQueryCallBack(map.get("payNo")); //订单回调处理 this.tenPayOrderCallBack(tenPayVO); break; default: break; } } catch (Exception e) { logger.error(e.getMessage()); e.printStackTrace(); } } }
4.3.2payService的tenPayQueryCallBack,用于主动查询微信支付回调,拼接TenPayVO
/** * @Description: 微信支付主动查询回调 * @Param: [payNo] * @return: com.huaku.ecom.system.model.vo.TenPayVO * @Author: zengXianKang * @Date: 2019/5/30 */ @Override public TenPayVO tenPayQueryCallBack(String payNo) throws Exception { SortedMap<String, Object> paramsMap = new TreeMap<String, Object>(); //应用APPID paramsMap.put("appid", tenpayConfig.getAppId()); //商户号 paramsMap.put("mch_id", tenpayConfig.getMchId()); //商户订单号 paramsMap.put("out_trade_no", payNo); //随机字符串 paramsMap.put("nonce_str", Convert.getUUID()); TenPayUtils tenPayUtils = new TenPayUtils(); //签名 String sign = tenPayUtils.createSign(paramsMap, "UTF-8"); paramsMap.put("sign", sign); //请求报文 String requestXml = TenPayUtils.tenPayXmlInfo(paramsMap); //发送微信查询post请求 String tenQueryPost = TenPayUtils.httpsRequest(tenpayConfig.getQueryUrl(), "POST", requestXml); TenPayVO tenPayVO = this.tenPayCallBackInfo(tenQueryPost, "xml", ""); return tenPayVO; }