JAVA项目实战 -微信支付开发

微信支付丰富着人们日常生活,下面我们来依据微信提供的API文档尝试着学习编写微信支付工具类,避免重复造轮子。

 1.JSAPI: 

         JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:

  1. ◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
  2. ◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
  3. ◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付

 2.Native支付

      Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

 3.H5支付

      H5支付主要是在手机、ipad等移动设备中通过浏览器来唤起微信支付的支付产品。

 4.小程序支付

     小程序支付是专门被定义使用在小程序中的支付产品。目前在小程序中能且只能使用小程序支付的方式来唤起微信支付。

 5.App支付

     APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。

注:微信支付功能开发 :1.必须拥有一个微信公众号;2.具备开通微信支付功能;3登录微信商户平台(https://pay.weixin.qq.com/index.php/core/home/login),设置商户号,支付秘钥和下载证书(退款接口需要携带证书请求)。

===========================================================================

                                       Utils工具类代码

================================================================

package com.sf.detectprocess.util.pay;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.sf.detectprocess.controller.param.*;
import com.sf.detectprocess.core.constant.SystemConstants; import com.sf.detectprocess.core.constant.WXPayConstants; import com.sf.detectprocess.core.exception.WeChatPayException; import com.sf.detectprocess.entity.Order; import com.sf.detectprocess.entity.OrderPay; import com.sf.detectprocess.mapper.OrderMapper; import com.sf.detectprocess.mapper.OrderPayMapper; import com.sf.detectprocess.service.LimsService; import com.sf.detectprocess.util.FuntionUtils; import com.sf.detectprocess.util.R; import com.sun.org.apache.regexp.internal.RE; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.*; /** * @description: 微信支付接口工具类 * @author: zhucj * @date: 2019-09-25 10:37 */ @Slf4j @Component public class WeChatPayUtils { /** * 公众号或小程序或app端 appid */ @Value("${wx.pay.appId}") private String appId; /** * 公众号或小程序 key */ @Value("${wx.pay.key}") private String key; /** * 商户号 (微信支付商户号) */ @Value("${wx.pay.mchNo}") private String mchNo; /** * 通知地址 */ @Value("${wx.pay.notifyUrl}") private String notifyUrl; /** * 安全证书密码(默认为商户号) */ @Value("${wx.pay.lcePassWord}") private String lcePassWord; @ApiOperation(value = "请求微信统一支付接口" ) @ApiImplicitParams({ @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String"), @ApiImplicitParam(name = "totalFee", required = true, value = "标价金额", dataType = "String"), @ApiImplicitParam(name = "body", required = true, value = "商品描述", dataType = "String"), @ApiImplicitParam(name = "tradeType", required = true, value = "交易类型 JSAPI:JSAPI支付(或小程序支付)," + "NATIVE:Native支付, APP:app支付, MWEB:H5支付", dataType = "String"), @ApiImplicitParam(name = "openId", required = false, value = "用户openId(JSAPI/小程序/必传)",dataType = "String"), @ApiImplicitParam(name = "spbillCreateIp", required = false, value = "APP和网页支付提交用户端ip,Native支付不需要传)", dataType = "String"), @ApiImplicitParam(name = "productId", required = false, value = "二维码中包含商品Id(Native支付必传,其他支付不传)", dataType = "String") }) public R unifiedOrder(UnfiedOrderParam map) throws WeChatPayException { /* 签名参数的map对象 */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); // 请求预下单对象 UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest(); //=>1.appId 商户Id  unifiedOrderRequest.setAppid(appId); //TODO:判断当前微信支付类型 APP端/JSAPI/H5/Native/MWEB if (Objects.equals(WXPayConstants.APP,map.getTradeType())){ //TODO:App端支付 必传用户终端IP 和单独的appId(应用Id) if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE, WXPayConstants.SPBILL_CREATE_APP_ERROR); } // => 2.终端Ip app支付由前端传  unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); } else if (Objects.equals(WXPayConstants.JSAPI,map.getTradeType())) { //TODO:JSAPI支付 必传openId和用户终端IP if (isNull(map.getOpenId())) { throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.OPENID_JSAPI_ERROR); } if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_JSAPI_ERROR); } unifiedOrderRequest.setOpenid(map.getOpenId()); // => 2.终端Ip 小程序或公众号支付由前端传  unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); //仅 JSAPI支付才传openId packageParams.put("openid", unifiedOrderRequest.getOpenid()); }else if (Objects.equals(WXPayConstants.NATIVE,map.getTradeType())){ //TODO:Native扫码支付 必传prductId(商品Id) if (isNull(map.getProductId())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.PRODUCT_NATIVE_ERROR); } unifiedOrderRequest.setProduct_id(map.getProductId()); packageParams.put("product_id", unifiedOrderRequest.getProduct_id()); //TODO:Native 本地获取终端Ip String localIp = WXPayUtil.getLocalIp(); //=> 2.终端Ip  unifiedOrderRequest.setSpbill_create_ip(localIp); }else if (Objects.equals(WXPayConstants.MWEB,map.getTradeType())){ //TODO:H5 前端必传Ip if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_HWEB_ERROR); } // => 2.终端Ip H5支付由前端传  unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); }else { throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"交易类型tradeType="+map.getTradeType()+"暂时不支持,请核对参数是否正确!"); } // => 3.商品描述  unifiedOrderRequest.setBody(map.getBody()); // => 4.商户号  unifiedOrderRequest.setMchId(mchNo); // 生成随机字符串 => 5.随机字符串  unifiedOrderRequest.setNonceStr(WXPayUtil.generateNonceStr()); // => 6.异步通知地址  unifiedOrderRequest.setNotify_url(notifyUrl); // => 7.商户订单号  unifiedOrderRequest.setOut_trade_no(map.getOutTradeNo()); // => 8.支付金额 需要扩大100倍(1代表支付时是0.01)  unifiedOrderRequest.setTotal_fee(WXPayUtil.changeY2F(map.getTotalFee())); // => 9.交易类型  unifiedOrderRequest.setTrade_type(map.getTradeType()); //TODO: 封装签名map packageParams.put("appid", unifiedOrderRequest.getAppid()); packageParams.put("body", unifiedOrderRequest.getBody()); packageParams.put("mch_id", unifiedOrderRequest.getMchId()); packageParams.put("nonce_str", unifiedOrderRequest.getNonceStr()); packageParams.put("notify_url", unifiedOrderRequest.getNotify_url()); packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no()); packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip()); packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee()); packageParams.put("trade_type", unifiedOrderRequest.getTrade_type()); try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); // =>10.签名  unifiedOrderRequest.setSign(signature); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //调统一下单接口 String s = WXPayUtil.doPost(1, WXPayUtil.mapToXml(packageParams), false, null); UnifiedOrderResponse resposeStr = JSON.parseObject(s, UnifiedOrderResponse.class); //预下单后 返回前端封装map TreeMap respMap = new TreeMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ //TODO: 依据交易类型返回前端参数 switch (resposeStr.getTrade_type()) { case WXPayConstants.NATIVE: { //Native扫码直接返回二维码链接 respMap.put("codeUrl", resposeStr.getCode_url()); break; } case WXPayConstants.APP : { //App端 需要商户号进行签名 返回 respMap.put("appid", resposeStr.getAppid()); respMap.put("partnerid", resposeStr.getMch_id()); respMap.put("prepayid", resposeStr.getPrepay_id()); respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp())); respMap.put("nonce_str", WXPayUtil.generateNonceStr()); respMap.put("package","Sign=WXPay"); respMap.put("sign", WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5)); break; } case WXPayConstants.JSAPI: { respMap.put("appId", resposeStr.getAppid()); respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp())); respMap.put("nonceStr",resposeStr.getNonce_str()); respMap.put("package","prepay_id="+resposeStr.getPrepay_id()); respMap.put("signType",WXPayConstants.MD5); respMap.put("paySign",WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5)); respMap.put("prepayid",resposeStr.getPrepay_id()); break; } case WXPayConstants.MWEB : { // h5支付链接地址 respMap.put("payUrl", resposeStr.getMweb_url()); break; } default: break; } return R.ok(respMap); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("预订单异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"预订单异常"); } } @ApiOperation(value = "请求微信订单查询接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String") public R orderQuery(String outTradeNo) throws WeChatPayException { /* 签名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //获得签名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //调微信查询订单服务接口 String s = WXPayUtil.doPost(2, WXPayUtil.mapToXml(packageParams), false, null); OrderQueryResponse resposeStr = JSON.parseObject(s, OrderQueryResponse.class); //查询返回结果封装map Map respMap = new HashMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ respMap.put("statusDesc",resposeStr.getTrade_state_desc()); respMap.put("data",resposeStr); /** * 自己设置的status => 1:已支付 ,2:转入退款 3:未支付 4:已关闭 5:已撤销 6:支付失败 * */ if (Objects.equals(WXPayConstants.TradeState.SUCCESS.getName(),resposeStr.getTrade_state())){ respMap.put("status",1); }else if (Objects.equals(WXPayConstants.TradeState.REFUND.getName(),resposeStr.getTrade_state())){ respMap.put("status",2); }else if (Objects.equals(WXPayConstants.TradeState.NOTPAY.getName(),resposeStr.getTrade_state())){ respMap.put("status",3); }else if (Objects.equals(WXPayConstants.TradeState.CLOSED.getName(),resposeStr.getTrade_state())){ respMap.put("status",4); }else if (Objects.equals(WXPayConstants.TradeState.REVOKED.getName(),resposeStr.getTrade_state())){ respMap.put("status",5); }else if (Objects.equals(WXPayConstants.TradeState.PAYERROR.getName(),resposeStr.getTrade_state())){ respMap.put("status",6); } return R.ok(respMap,"查询成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("查询订单异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查询订单异常"); } } @ApiOperation(value = "请求微信关闭订单接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType = "String") public R closeOrder(String outTradeNo) throws WeChatPayException { /* 签名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //获得签名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //请求微信关闭订单服务接口 String s = WXPayUtil.doPost(4, WXPayUtil.mapToXml(packageParams), false, null); UnifiedOrderResponse resposeStr = JSON.parseObject(s,UnifiedOrderResponse.class); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ return R.ok(null,"订单关闭成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("关闭订单异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"关闭订单异常"); } } @ApiOperation(value = "请求微信退款订单接口" ) @ApiImplicitParams({ @ApiImplicitParam(name = "transactionId", required = true, value = "微信订单号", dataType = "String"), @ApiImplicitParam(name = "outRefundNo", required = true, value = "商户退款单号(32个字符内 )", dataType = "String"), @ApiImplicitParam(name = "totalFee", required = true, value = "订单金额", dataType = "String"), @ApiImplicitParam(name = "refundFee", required = true, value = "退款金额", dataType = "String"), @ApiImplicitParam(name = "refundDesc", required = false, value = "退款原因", dataType = "String") }) public R refund(RefundParam map) throws WeChatPayException { /* 签名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); packageParams.put("out_refund_no",map.getOutRefundNo()); packageParams.put("total_fee",WXPayUtil.changeY2F(map.getTotalFee())); packageParams.put("refund_fee",WXPayUtil.changeY2F(map.getRefundFee())); packageParams.put("transaction_id",map.getTransactionId()); //获得签名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //调统一请求退款服务接口 String s = WXPayUtil.doPost(3,WXPayUtil.mapToXml(packageParams), true,lcePassWord); RefundResp resposeStr = JSON.parseObject(s, RefundResp.class); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ /** * ##订单已退款 此处更新数据库订单状态和退款信息 ### */ return R.ok(null,"订单退款成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("订单退款异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"订单退款异常"); } } @ApiOperation(value = "请求微信退款查询接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号", dataType= "String") public R refundQuery(String outTradeNo) throws WeChatPayException { /* 签名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //获得签名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //统一请求退款服务接口 String s = WXPayUtil.doPost(5, WXPayUtil.mapToXml(packageParams), false, null); RefundQueryResponse resposeStr = JSON.parseObject(s,RefundQueryResponse.class); //返回结果封装map Map respMap = new HashMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ /** * ##订单查询成功 此处更新数据库退款订单状态 * 退款状态: * SUCCESS—退款成功 status = 1 * REFUNDCLOSE—退款关闭 status = 2 * PROCESSING—退款处理中 status = 3 * CHANGE—退款异常 status = 4 */ respMap.put("statusDesc",resposeStr.getRefund_status_0()); respMap.put("data",resposeStr); if (Objects.equals(WXPayConstants.RefundState.SUCCESS.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",1); }else if (Objects.equals(WXPayConstants.RefundState.REFUNDCLOS.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",2); }else if (Objects.equals(WXPayConstants.RefundState.PROCESSING.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",3); }else if (Objects.equals(WXPayConstants.RefundState.CHANGE.getName(),resposeStr.getRefund_status_0())){ respMap.put("status",4); } return R.ok(respMap,"订单退款查询成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("查询退款订单异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查询退款订单异常"); } } /** * #### * 1、商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户的订单金额一致 * ,防止数据泄漏导致出现“假通知”,造成资金损失。 * 2、当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再 * 进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制 * ,以避免函数重入造成的数据混乱。 */ @ApiOperation(value = "微信支付异步通知") public void notify(HttpServletRequest req, HttpServletResponse resp){ //定义一个通知微信消息 String noticeStr = null; String notifyString = WXPayUtil.getNotifyUtils(req, resp); //解析xml成map Map<String, String> map = new HashMap<>(); try { map = WXPayUtil.xmlToMap(notifyString); } catch (Exception e) { e.printStackTrace(); } //获取异步通知请求 将map转成指定对象 OrderQueryResponse orderResp = JSON.parseObject(JSON.toJSONString(map), OrderQueryResponse.class); //判断return_code状态 if (Objects.equals(WXPayConstants.SUCCESS,orderResp.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,orderResp.getResult_code())){ //TODO 验签 try { if (WXPayUtil.isSignatureValid(notifyString,key)){ //TODO 新增验证预支付订单金额和异步回调支付金额是否匹配 /** * ## 此处步骤 * 1.异步回调的商户订单号或微信订单号 调用查询接口 * 2.判断此订单是否真实支付成功 * 3.验证返回的支付金额和数据库中预支付订单金额是否相等 * 4.鉴定完毕才更新数据库数据 * 5.跳转至成功页面 */ log.info("------------验签成功 开始下一步----------"); //调用微信支付查询订单接口 R r = orderQuery(orderResp.getOut_trade_no()); if (r.getSuccess()){ Map respMap =(Map) r.getData(); OrderQueryResponse data =(OrderQueryResponse) respMap.get("data"); if (Objects.equals(WXPayConstants.SUCCESS,data.getTrade_state())){ //## 此处调用此商户订单号查询数据库预订单的付款金额(注意单位:分)  noticeStr = setXML(WXPayConstants.SUCCESS, WXPayConstants.OK); }else { log.info("商户订单号:{},未支付成功状态",orderResp.getOut_trade_no()); noticeStr = setXML(WXPayConstants.FAIL, orderResp.getOut_trade_no()+"商户订单号状态是未支付成功状态"); } }else { log.error("订单查询失败:{}",r.getMsg()); } }else { log.error("验签错误 非法访问"); noticeStr = setXML(WXPayConstants.FAIL, "非法访问"); } } catch (Exception e) { e.printStackTrace(); log.error("验签异常:{}",e.getMessage()); noticeStr = setXML(WXPayConstants.FAIL, "验签异常"); } }else { log.error("错误信息:{}",orderResp.getErr_code_des()); noticeStr = setXML(WXPayConstants.FAIL, orderResp.getReturn_msg()); } }else { log.error("异常信息:{}",orderResp.getReturn_msg()); noticeStr = setXML(WXPayConstants.FAIL, orderResp.getReturn_msg()); } PrintWriter writer = null; try { writer = resp.getWriter(); } catch (IOException e) { e.printStackTrace(); } writer.write(noticeStr); writer.flush(); } /** * 判断对象是否非空 * @param object * @return */ public Boolean isNull(Object object){ if (Objects.isNull(object)||Objects.equals("",object)){ return true; } return false; } /** * 拼接Xml字符串 * @param return_code * @param return_msg * @return */ public static String setXML(String return_code, String return_msg) { return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; } public String notifyReqUrl(Integer code) throws WeChatPayException { String reqUrl = null; switch (code){ case 1: reqUrl = refusalRefundUrl; break; case 2: reqUrl = notPickRefundUrl; break; case 3: reqUrl = detectionlRefundUrl; break; default: throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"参数未被定义"); } return reqUrl; } }
==============================================================================================
微信支付工具包
===============================================================================================

package com.sf.vsolution.hb.sfce.util.wechat.pay;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
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.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; 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.*; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.KeyStore; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.*; /** * @Desc: 微信支付工具包 * @Author: zhucj * @Date: 2019/5/23 13:29 */ @Slf4j @Component public class WXPayUtil implements InitializingBean { /** * 安全证书位置 */ @Value("${wx.pay.certPath}") private String certPath; public static String CERT_PATH; private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final Random RANDOM = new SecureRandom(); /** * ##连接超时时间,默认10秒 */ private static int socketTimeout = 10000; /** * ## 传输超时时间,默认30秒 */ private static int connectTimeout = 30000; /** * ## 请求器的配置 */ private static RequestConfig requestConfig; /** * ## HTTP请求器 */ private static CloseableHttpClient httpClient; /** * 作用:统一请求地址<br> * 场景:公共号支付、扫码支付、APP支付 * @param reqUrlNub 请求地址编号 1:下单 2:查询订单 3:申请退款 4:关闭订单 5:查询退款 * @param xmlDate 发送内容 xml字符串 * @param isLoadCert 是否需要证书 * @param lcePassword 证书密码(默认商户Id) isLoadCert=true必传 如果isLoadCert=false 传null即可 * @throws Exception * @return JSON格式字符串对象 */ public static String doPost(Integer reqUrlNub, String xmlDate, boolean isLoadCert, String lcePassword) throws Exception{ if (Objects.isNull(reqUrlNub) || Objects.equals("",reqUrlNub)){return null;} //获取请求地址 String url = getReqUrl(reqUrlNub); log.info("POST请求参数,请求URL:{},请求XML信息:{}",url,xmlDate); HttpPost httpPost=new HttpPost(url); // 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别 httpPost.addHeader("Content-Type", "text/xml"); httpPost.setEntity(new StringEntity(xmlDate,"UTF-8")); // 根据默认超时限制初始化requestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build(); // 设置请求器的配置  httpPost.setConfig(requestConfig); CloseableHttpResponse execute = null; if(isLoadCert) { // 加载证书 try { initCert(lcePassword,2); } catch (Exception e) { e.printStackTrace(); } //发送含有证书的http请求 execute = httpClient.execute(httpPost); }else { execute = HttpClients.custom().build().execute(httpPost); } return getJsonString(execute); } /** * 返回结果解析成Json字符串 * @param response * @return */ public static String getJsonString(HttpResponse response) throws Exception{ HttpEntity entity = null; StringBuilder sb = new StringBuilder(); try { entity = response.getEntity(); String text; if (entity != null) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); while (true) { if (!((text = bufferedReader.readLine()) != null)) break;sb.append(text); } }else { return null; } }catch (Exception e){ e.printStackTrace(); log.error("--------读取异常---------"+e.getMessage()); } finally { try { EntityUtils.consume(entity); } catch (IOException ex) { ex.printStackTrace(); log.error("net io exception"); } } //将xml转成map对象 Map<String, String> stringMap = xmlToMap(sb.toString()); log.info("响应参数内容:{}", JSON.toJSONString(stringMap)); return JSON.toJSONString(stringMap); } /** * 加载证书 (apiclient_cert.p12)文件 * @param lcePsw 证书密码(默认是商户Id) * @param loadType 加载证书方法 1:存放在项目下 2:存放本地磁盘中 * @throws Exception */ private static void initCert(String lcePsw,Integer loadType) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); if (Objects.isNull(loadType) || Objects.equals("",loadType) || Objects.equals(1,loadType)){ //方式一:默认存放在项目下 ClassPathResource cl = new ClassPathResource(CERT_PATH); try { keyStore.load(cl.getInputStream(), lcePsw.toCharArray()); } finally { cl.getInputStream().close(); } }else if (Objects.equals(2,loadType)){ //方式二:存放在本地磁盘 读取本机存放的PKCS12证书文件 FileInputStream instream = new FileInputStream(new File(CERT_PATH)); try { keyStore.load(instream, lcePsw.toCharArray()); } finally { instream.close(); } }else {throw new WeChatPayException(400,"loadType参数格式错误,请核对");} // 信任自己的CA和所有自签名证书 SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore,lcePsw.toCharArray()) .build(); // 只允许TLSv1协议 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); } /** * 作用:获取请求地址<br> * 场景:1:下单 2:查询订单 3:申请退款 4:关闭订单 5:查询退款 * @param reqUrlNub * @return 请求地址字符串 * @throws Exception */ public static String getReqUrl(Integer reqUrlNub) throws WeChatPayException { StringBuilder reqUrl = new StringBuilder(); reqUrl.append(WXPayConstants.BASE_URL); switch (reqUrlNub){ case 1: reqUrl.append( WXPayConstants.UNIFIED_ORDER_URL); break; case 2: reqUrl.append(WXPayConstants.ORDER_QUERY_URL); break; case 3: reqUrl.append(WXPayConstants.REFUND_URL); break; case 4: reqUrl.append(WXPayConstants.CLOSEORDER_URL_SUFFIX); break; case 5: reqUrl.append(WXPayConstants.REFUNDQUERY_URL_SUFFIX); break; default: throw new WeChatPayException(400,"传入的请求地址编号暂不支持"); } return reqUrl.toString(); } /** * 获取随机字符串 Nonce Str * * @return String 随机字符串 */ public static String generateNonceStr() { char[] nonceChars = new char[32]; for (int index = 0; index < nonceChars.length; ++index) { nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); } return new String(nonceChars); } /** * 获取本机Ip地址 * @return */ public static String getLocalIp(){ try { InetAddress addr = InetAddress.getLocalHost(); //获取本机ip return addr.getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); return null; } } /** * 判断签名是否正确 * * @param xmlStr XML格式数据 * @param key API密钥 * @return 签名是否正确 * @throws Exception */ public static boolean isSignatureValid(String xmlStr, String key) throws Exception { Map<String, String> data = xmlToMap(xmlStr); if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { return false; } String sign = data.get(WXPayConstants.FIELD_SIGN); return generateSignature(data, key,WXPayConstants.SignType.MD5).equals(sign); } /** * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。 * * @param data 待签名数据 * @param key API密钥 * @param signType 签名方式 * @return 签名 */ public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { Set<String> keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (k.equals(WXPayConstants.FIELD_SIGN)) { continue; } if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 {sb.append(k).append("=").append(data.get(k).trim()).append("&");} } sb.append("key=").append(key); if (WXPayConstants.SignType.MD5.equals(signType)) { return MD5(sb.toString()).toUpperCase(); } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) { return HMACSHA256(sb.toString(), key); } else { throw new Exception(String.format("Invalid sign_type: %s", signType)); } } /** * 生成 MD5 * * @param data 待处理数据 * @return MD5结果 */ public static String MD5(String data) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] array = md.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(); } /** * 生成 HMACSHA256 * @param data 待处理数据 * @param key 密钥 * @return 加密结果 * @throws Exception */ public static String HMACSHA256(String data, String key) throws Exception { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] array = sha256_HMAC.doFinal(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(); } /** * 获取当前时间戳,单位秒 * @return */ public static long getCurrentTimestamp() { return System.currentTimeMillis()/1000; } /** * 支付金额单位:元转分 (12.23元 >>> 1223分) * @param amount * @return */ public static String changeY2F(String amount) { String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 或者$的金额 int index = currency.indexOf("."); int length = currency.length(); Long amLong = 0l; if (index == -1) { amLong = Long.valueOf(currency + "00"); } else if (length - index >= 3) { amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", "")); } else if (length - index == 2) { amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0); } else { amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00"); } return amLong.toString(); } /** * 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>(); DocumentBuilder documentBuilder = newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); 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) { // do nothing  } return data; } catch (Exception ex) { log.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML); throw ex; } } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 * @throws Exception */ public static String mapToXml(Map<String, String> data) throws Exception { Document document = 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(); try { writer.close(); } catch (Exception ex) { } return output; } public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); documentBuilderFactory.setXIncludeAware(false); documentBuilderFactory.setExpandEntityReferences(false); return documentBuilderFactory.newDocumentBuilder(); } public static Document newDocument() throws ParserConfigurationException { return newDocumentBuilder().newDocument(); } /** * 将微信异步通知消息解析成map对象 * @param req * @param response * @return */ public static String getNotifyUtils(HttpServletRequest req, HttpServletResponse response){ //返回字符串内容 String noticeStr = null; InputStream inputStream ; StringBuffer sb = new StringBuffer(); try{ inputStream = req.getInputStream(); String s ; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null){ sb.append(s); } in.close(); inputStream.close(); log.info("异步通知结果=>xml值:"+sb.toString()); //解析xml成map return sb.toString(); }catch (Exception e){ log.error("解析异步消息异常",e.getMessage()); return null; } } /** * 注入的属性转静态属性 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { CERT_PATH = this.certPath; } }
=======================================================================================================
微信支付相关参数(请求参数,响应参数,枚举,异常类)
=======================================================================================================
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 微信下单前端传递参数
 * @author: zhucj 
 * @date: 2019-09-26 11:08
 */

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UnfiedOrderParam {
    /**
     * 商户订单号(32个字符内)
     */
    private String outTradeNo;

    /**
     * 标价金额
     */
    private String totalFee; /** * 商品描述 */ private String body; /** * 交易类型 */ private String tradeType; /** * 用户openId */ private String openId; /** * 终端IP */ private String spbillCreateIp; /** * 产品Id */ private String productId; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 统一下单实体
 * @Author:zhucj
 * @Date: 2019/5/23 13:29
 */

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UnifiedOrderRequest {


   /**
    * 公众账号ID
    * 必填  String(32)
    * wxd678efh567hg6787微信支付分配的公众账号ID(企业号corpid即为此appId)
    */
   private String appid;
   /**
    * 商户号
    * 必填  String(32)
    * 1230000109  微信支付分配的商户号
    */
   private String mchId; /** * 设备号 * 否 * String(32) * 013467007045764 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" */ private String deviceInfo; /** * 随机字符串 * 必填 String(32) * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法 */ private String nonceStr; /** * 签名 * 必填 String(32) * C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法MD5加密 */ private String sign; /** * 签名类型 * 否 String(32) * HMAC-SHA256 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 */ private String sign_type; /** * 商品描述 * 必填 String(128) * 腾讯充值中心-QQ会员充值 商品简单描述,该字段请按照规范传递,具体请见参数规定 */ private String body; /** * 商品详情 * 否 String(6000) * 单品优惠字段(暂未上线) */ private String detail; /** * 商品详情 * 否 String(6000) * 单品优惠字段(暂未上线) */ private String attach; /** * 商户订单号 * 必传 String(32) * 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号 */ private String out_trade_no; /** * 标价币种 * 否 String(16) * CNY 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 */ private String fee_type; /** * 标价金额 * 必传 Int 88 订单总金额,单位为分,详见支付金额 */ private String total_fee; /** * 终端IP * 必传 String(16) * 123.12.12.123 APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 */ private String spbill_create_ip; /** * 交易起始时间 * 否 String(14) * 20091225091010 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 */ private String time_start; /** * 交易结束时间 * 否 String(14) * 20091227091010 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟 */ private String time_expire; /** * 订单优惠标记 * 否 String(32) * WXG 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 */ private String goods_tag; /** * 通知地址 * 必传 String(32) * 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 */ private String notify_url; /** * 交易类型 * 必传 String(16) * JSAPI 取值如下:JSAPI,NATIVE,APP等,说明详见参数规定 */ private String trade_type; /** * 商品ID * 否 String(32) * 12235413214070356458058 trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 */ private String product_id; /** * 指定支付方式 * 否 String(32)上传此参数no_credit--可限制用户不能使用信用卡支付 */ private String limit_pay; /** * 用户标识 *否 String(128) * oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取, * 可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换 */ private String openid; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * @description 统一下单返回参数
 * @author zhucj
 * @Date: 2019/5/23 13:29
 */ @Data @NoArgsConstructor @AllArgsConstructor @ToString public class UnifiedOrderResponse { /** * 返回状态码 * SUCCESS/FAIL * 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断 */ private String return_code; /** *返回信息 * 返回信息,如非空,为错误原因 签名失败 * 参数格式校验错误 */ private String return_msg; //TODO 以下字段在return_code为SUCCESS的时候有返回 /** * 公众账号ID */ private String appid; /** * 商户号 */ private String mch_id; /** * 设备号 */ private String device_info; /** * 随机字符串 */ private String nonce_str; /** * 签名 */ private String sign; /** * 业务结果 */ private String result_code; /** * 错误代码 */ private String err_code; /** * 错误代码描述 */ private String err_code_des; //TODO 以下字段在return_code 和result_code都为SUCCESS的时候有返回 /** * 交易类型 */ private String trade_type; /** * 预支付交易会话标识 */ private String prepay_id; /** * 二维码链接 */ private String code_url; /** *H5支付跳转页面 */ private String mweb_url; /** * 商户支付订单号 */ private String out_trade_no; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 微信退款参数
 * @author:zhucj
 * @date: 2019-09-26 11:21
 */

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundParam {


    /**
     * 微信订单号
     */
    private String transactionId;

    /**
     * 退款订单号
     */
    private String outRefundNo; /** * 订单总金额 */ private String totalFee; /** * 退款金额 */ private String refundFee; /** * 退款信息描述 */ private String refundDesc; /** * 退款异步地址编号 1:为拒签退款异步地址 2:不接样退款地址 3:检测完成退款地址 */ private Integer refundStatus; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 退款回调信息
 * @author: zhucj
 * @date: 2019-10-24 17:53
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundInfoParam {

    private String return_code;

    private String return_msg; private String appid; private String mch_id; private String nonce_str; private String req_info; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 退款查询返回视图
 * @author: zhucj
 * @date: 2019-07-24 18:30
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundQueryResponse extends UnifiedOrderResponse {


    //TODO 处理退款结果查询参数,如果通过微信单号或商户支付订单号查询,
    //     可能出现多次退款情况。

    /**
     * 微信退款单号.....
     */
    private String refund_id_0;
    private String refund_id_1; private String refund_id_2; private String refund_id_3; private String refund_id_4; /** * 微信单号 */ private String transaction_id; /** * 商户退款单号 ...... */ private String out_refund_no_0; private String out_refund_no_1; private String out_refund_no_2; private String out_refund_no_3; private String out_refund_no_4; /** * 商户订单号 */ private String out_trade_no; /** * 现金支付金额 */ private Integer cash_fee; /** * 订单总金额 */ private Integer total_fee; /** * 退款笔数 */ private String refund_count; /** * 退款金额...... */ private String refund_fee_0; private String refund_fee_1; private String refund_fee_2; private String refund_fee_3; private String refund_fee_4; /** * 退款状态 ...... */ private String refund_status_0; private String refund_status_1; private String refund_status_2; private String refund_status_3; private String refund_status_4; /** * 退入账号..... */ private String refund_recv_accout_0; private String refund_recv_accout_1; private String refund_recv_accout_2; private String refund_recv_accout_3; private String refund_recv_accout_4; /** * 退款成功时间 ..... */ private String refund_success_time_0; private String refund_success_time_1; private String refund_success_time_2; private String refund_success_time_3; private String refund_success_time_4; }
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 退款返回参数
 * @author: zhucj
 * @date: 2019-10-23 17:54
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundResp extends UnifiedOrderResponse {

    /**
     * 微信订单号
     */
    private String transaction_id;

    /**
     * 商户订单号
     */
    private String out_trade_no; /** * 商户退款单号 */ private String out_refund_no; /** * 微信退款单号 */ private String refund_id; /** * 退款金额 */ private String refund_fee; /** * 订单总金额 */ private String total_fee; /** * 现金支付金额 */ private String cash_fee; private String refund_status; private String success_time; }
package com.sf.vsolution.hb.sfce.util.wechat.pay;

/**
 * @author :zhucj
 * @date :Created in 2019/6/17 17:46
 * @description: 自定义微信支付异常类
 * @modified By:
 */
public class WeChatPayException extends Exception {
    private static final long serialVersionUID = -5317007026578376164L;

    /**
     * 错误码
     */
    private Integer errorCode;
    /**
     * 错误描述
     */
    private String errorMsg;

    /**
     * @param errorCode
     * @param errorMsg
     */
    public WeChatPayException(Integer errorCode, String errorMsg) { super(errorMsg); this.errorCode = errorCode; this.errorMsg = errorMsg; } public Integer getErrorCode() { return errorCode; } public String getErrorMsg() { return errorMsg; } }
package com.sf.vsolution.hb.sfce.util.wechat.pay;

/**
 * @Author: zhucj
 * @Date: 2019/5/23 13:29
 * @apiNote: 微信支付常量
 */
public class WXPayConstants {


    /**
     * 签名枚举
    */
   public static enum SignType {
      MD5,

      HMACSHA256;

      private SignType() {
      }
   }

   /**
    * 公众号、小程序支付
    */
   public static final String JSAPI = "JSAPI" ;

   /**
    * NATIVE 扫码支付
    */
   public static final String NATIVE = "NATIVE"; /** * app支付 */ public static final String APP = "APP"; /** * 小程序支付 */ public static final String MWEB = "MWEB"; public enum TradeState{ /** * 交易状态 */ SUCCESS("SUCCESS","支付成功",1), REFUND("REFUND","转入退款",2), NOTPAY("NOTPAY","未支付",3), CLOSED("CLOSED","已关闭",4), REVOKED("REVOKED","已撤销",5), PAYERROR("PAYERROR","支付失败",6); private String name; private String values; private Integer index; TradeState(String name, String values, int index) { this.name = name; this.values = values; this.index = index; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValues() { return values; } public void setValues(String values) { this.values = values; } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } } /** * 退款状态 */ public enum RefundState{ SUCCESS("SUCCESS","退款成功",1), REFUNDCLOS("REFUNDCLOS","退款关闭",2), PROCESSING("PROCESSING","退款处理中",3), CHANGE("CHANGE","退款异常",4); private String name; private String values; private Integer index; RefundState(String name, String values, int index) { this.name = name; this.values = values; this.index = index; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValues() { return values; } public void setValues(String values) { this.values = values; } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } } //TODO: 返回状态码 public static final String FAIL = "FAIL"; public static final String SUCCESS = "SUCCESS"; //TODO: 验签加密方法 public static final String HMACSHA256 = "HMAC-SHA256"; public static final String MD5 = "MD5"; //TODO: 签名 public static final String FIELD_SIGN = "sign"; public static final String FIELD_SIGN_TYPE = "sign_type"; public static final String OK = "OK"; //TODO:微信支付服务名 /* 系统地址 */ public static final String BASE_URL = "https://api.mch.weixin.qq.com"; /* 统一下单地址 */ public static final String UNIFIED_ORDER_URL = "/pay/unifiedorder"; /* 查询订单 */ public static final String ORDER_QUERY_URL = "/pay/orderquery"; /* 关闭订单 */ public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder"; /* 申请退款 */ public static final String REFUND_URL = "/secapi/pay/refund"; /* 查询退款 */ public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery"; //TODO:请求状态码 public static final Integer PARAM_ERROR_CODE = 400; public static final Integer EXCEPTION_ERROR_CODE = 500; //TODO: 异常信息状态 public static final String SPBILL_CREATE_APP_ERROR = "选择APP支付时,用户终端IP不为空"; public static final String SPBILL_CREATE_JSAPI_ERROR = "选择JSAPI支付时,用户终端IP不为空"; public static final String SPBILL_CREATE_HWEB_ERROR = "选择HWEB支付时,用户终端IP不为空"; public static final String OPENID_JSAPI_ERROR = "选择JSAPI支付时,用户openId不为空"; public static final String PRODUCT_NATIVE_ERROR = "选择NATIVE支付时,商品Id不为空"; public static final String SIGN_EXCEPTION_ERROR = "签名异常"; }
package com.sf.vsolution.hb.sfce.util.wechat.pay;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.ToString;

import java.io.Serializable;

/**
 * 返回前端格式类型
 * @author choleece
 * @date 2018/9/27
 */ @ApiModel @ToString public class R<T> implements Serializable { private static final long serialVersionUID = -6287952131441663819L; /** * 编码 */ @ApiModelProperty(value = "响应码", example = "200") private int code = 200; /** * 成功标志 */ @ApiModelProperty(value = "成功标志", example = "true") private Boolean success; /** * 返回消息 */ @ApiModelProperty(value = "返回消息说明", example = "操作成功") private String msg="操作成功"; /** * 返回数据 */ @ApiModelProperty(value = "返回数据") private T data; /** * 创建实例 * @return */ public static R instance() { return new R(); } public int getCode() { return code; } public R setCode(int code) { this.code = code; return this; } public Boolean getSuccess() { return success; } public R setSuccess(Boolean success) { this.success = success; return this; } public String getMsg() { return msg; } public R setMsg(String msg) { this.msg = msg; return this; } public T getData() { return data; } public R setData(T data) { this.data = data; return this; } public static R ok() { return R.instance().setSuccess(true); } public static R ok(Object data) { return ok().setData(data); } public static R ok(Object data, String msg) { return ok(data).setMsg(msg); } public static R error() { return R.instance().setSuccess(false); } public static R error(String msg) { return error().setMsg(msg); } /** * 无参 */ public R() { } public R(int code, String msg) { this.code = code; this.msg = msg; } public R(int code, T data){ this.code = code; this.data = data; } /** * 有全参 * @param code * @param msg * @param data * @param success */ public R(int code, String msg, T data, Boolean success) { this.code = code; this.msg = msg; this.data = data; this.success = success; } /** * 有参 * @param code * @param msg * @param data */ public R(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } private int code; private String msg; ResultEnum(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } }

====================================================================================
支付查询定时任务
===================================================================================
package com.sf.vsolution.hb.sfce.util.wechat.pay.task;

import com.sf.vsolution.hb.sfce.util.wechat.pay.WeChatPayUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; /** * @description: 定时任务-订单支付状态查询 * @author: zhucj * @date: 2019-08-28 10:18 */ @Slf4j @Component @Order(value = 1) public class OrderPayTask implements CommandLineRunner { @Autowired private WeChatPayUtils weChatPayUtils; @Scheduled(cron= "${task.cron.order}") public void orderTask(){ SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm"); log.info(s.format(new Date())+"=>开始执行支付订单查询定时任务"); //当前时间点 Date date = new Date(); //10分钟之前点 String fastFormat = s.format(date.getTime()-600000); String lastFormat = s.format(date); /** * #### 订单状态查询定时任务 ##### * 1.设置每隔10分钟查询一次条件为 * type:支付订单 * status:未支付状态的数据库预订单数据 * 2.通过商户支付订单号,调用微信查询接口 获取支付状态 * 3.判断是否状态一致,如不一致修改 */ } @Override public void run(String... args) throws Exception { this.orderTask(); } }
==================================================================================
yml配置信息
==================================================================================
#微信支付参数
wx:
  pay:
    #微信平台appId
    appId: *******
    #支付商户号
    mchNo: *******
    #支付成功,异步回调地址
    notifyUrl: **********
    #支付秘钥
    key: *****************
    #证书密码
    lcePassWord: ************* #证书存放路径 certPath: ****/apiclient_cert.p12 #定时任务配置 task: cron: #支付订单推送:每隔10分钟查询一次 order: 0 */10 * * * ?
===================================================================================
依赖maven jar包
===================================================================================
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
    <scope>provided</scope>
</dependency>

<!--htttpclient依赖-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.55</version>
</dependency>
<!-- swagger API -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.0</version>
</dependency>


猜你喜欢

转载自www.cnblogs.com/zhucj-java/p/11949888.html
今日推荐