我这边是针对微信商户支付功能开发。其他的未涉及到。

当你所有的准备工作准备好后:微信支付申请成功,api_key 配置好,等等一系列。

那么让我们进入java开发吧。

微信支付demo下载:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

下载JAVA版。这里面全是微信支付的工具类。

maven配置:

<!-- https://mvnrepository.com/artifact/com.github.wxpay/wxpay-sdk -->
         <dependency>
             <groupId>com.github.wxpay</groupId>
             <artifactId>wxpay-sdk</artifactId>
             <version>0.0.3</version>
         </dependency>

别去maven工厂搜索了。就3个版本0.01,0.02,0.03

微信支付和支付宝的支付区别在于。支付宝已经全部封装好了。而微信支付这B坑的一比吊扫。只有工具类,还要自己去写继承方法。

上面的java demo 下载之后。当如到自己当前的项目中。新建包名存放。

需要修改的地方:

新建WXPayConfig继承实现类:

public class WxPayUtilConfig extends WXPayConfig

如果该WxPayUtilConfig 和微信工具类的WXPayConfig 不在同一个包下,则WXPayConfig里面的方法需要

public
/**
  * 微信支付配置文件
  * 继承微信工具配置文件抽象接口
  * @author think
  */
 public class WxPayUtilConfig extends WXPayConfig{
     
     private byte[] certData;//将证书地址解析成byte
     //证书需要自己到微信商户端自己下来。一些了操作很简答,需要找找而已。如果支付放到服务器,则cert这个需要放到服务器,并且路径改成服务器路径,否则,app唤醒支付报500异常
     public WxPayUtilConfig() throws Exception {
         //从微信商户平台下载的安全证书存放的路径
         String certPath = "C:\\services\\wxpay\\cert\\apiclient_cert.p12";
         File file = new File(certPath);
         InputStream certStream = new FileInputStream(file);
         this.certData = new byte[(int) file.length()];
         certStream.read(this.certData);
         certStream.close();
     }    @Override
     public IWXPayDomain getWXPayDomain() {
         // TODO Auto-generated method stub
         return WXPayDomainSimpleImpl.instance();
     }
     
     @Override
     public String getAppID() {
         // TODO Auto-generated method stub
         return WXPayCommonPath.APP_ID;
     }    @Override
     public String getMchID() {
         // TODO Auto-generated method stub
         return WXPayCommonPath.MCH_ID;
     }    @Override
     public String getKey() {
         // TODO Auto-generated method stub
         return WXPayCommonPath.API_KEY;
     }    @Override
     public InputStream getCertStream() {
         // TODO Auto-generated method stub
         ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
         return certBis;
     }    @Override
     public int getHttpConnectTimeoutMs() {
         // TODO Auto-generated method stub
         return 8000;
     }    @Override
     public int getHttpReadTimeoutMs() {
         // TODO Auto-generated method stub
         return 10000;
     }
 }2:实现IWXPayDomain域名主备方法,该方法直接复制即可。无需修改。该方法与IWXPayDomain微信工具类一个包名下。
/**
  * 域名管理,实现主备域名自动切换
  * 该实现方法需要自己写一个。
  * @author think
  */
 public class WXPayDomainSimpleImpl implements IWXPayDomain {
     
     private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000;  //3 minutes
     private long switchToAlternateDomainTime = 0;
     private Map<String, DomainStatics> domainData = new HashMap<String, DomainStatics>();
     
     private WXPayDomainSimpleImpl(){}
     private static class WxpayDomainHolder{
         private static IWXPayDomain holder = new WXPayDomainSimpleImpl();
     }
     public static IWXPayDomain instance(){
         return WxpayDomainHolder.holder;
     }    @Override
     public void report(String domain, long elapsedTimeMillis, Exception ex) {
         // TODO Auto-generated method stub
         DomainStatics info = domainData.get(domain);
         if(info == null){
             info = new DomainStatics(domain);
             domainData.put(domain, info);
         }        if(ex == null){ //success
             if(info.succCount >= 2){    //continue succ, clear error count
                 info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0;
             }else{
                 ++info.succCount;
             }
         }else if(ex instanceof ConnectTimeoutException){
             info.succCount = info.dnsErrorCount = 0;
             ++info.connectTimeoutCount;
         }else if(ex instanceof UnknownHostException){
             info.succCount = 0;
             ++info.dnsErrorCount;
         }else{
             info.succCount = 0;
             ++info.otherErrorCount;
         }
     }    @Override
     public DomainInfo getDomain(WXPayConfig config) {
         // TODO Auto-generated method stub
         DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API);
         if(primaryDomain == null ||
                 primaryDomain.isGood()) {
             return new DomainInfo(WXPayConstants.DOMAIN_API, true);
         }        long now = System.currentTimeMillis();
         if(switchToAlternateDomainTime == 0){   //first switch
             switchToAlternateDomainTime = now;
             return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
         }else if(now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC){
             DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
             if(alternateDomain == null ||
                 alternateDomain.isGood() ||
                 alternateDomain.badCount() < primaryDomain.badCount()){
                 return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
             }else{
                 return new DomainInfo(WXPayConstants.DOMAIN_API, true);
             }
         }else{  //force switch back
             switchToAlternateDomainTime = 0;
             primaryDomain.resetCount();
             DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
             if(alternateDomain != null)
                 alternateDomain.resetCount();
             return new DomainInfo(WXPayConstants.DOMAIN_API, true);
         }
     }    static class DomainStatics {
         final String domain;
         int succCount = 0;
         int connectTimeoutCount = 0;
         int dnsErrorCount =0;
         int otherErrorCount = 0;        DomainStatics(String domain) {
             this.domain = domain;
         }
         void resetCount(){
             succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0;
         }
         boolean isGood(){ return connectTimeoutCount <= 2 && dnsErrorCount <= 2; }
         int badCount(){
             return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4;
         }
     }
 }3:新建WXPayCommonPath微信支付配置的常量类。自己创建
/**
  * 微信支付常量
  * @author think
  */
 public class WXPayCommonPath {    public static final String WXUNIFIEDORDER_HTTPURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";// 微信统一下单接口  
     public static final String APP_ID = "";// 微信appid  
     public static final String MCH_ID = "";// 商户号  
     public static final String API_KEY = "";// apikey应用私钥 api密钥  这个需要自己到商户平台去配置32位长度。
     public static final String NOTIFYURL = "";//回调方法
     public static final String TRADE_TYPE = "APP";//app支付
     public static final String SPBILL_CREATE_IP = "";//服务器ip地址
 }下面写微信支付调用方法:
1:新建WxPayController
:OperLog 该方法是aop日志,可以去掉。
/**
      * (微信支付) app请求预付支付订单
      * @param req
      * @param res
      * @return
      * @throws Exception
      */
     @OperLog(logDescription = "app请求预付支付订单(微信支付)")
     @RequestMapping(value = "getOrderInfo", method = { RequestMethod.POST, RequestMethod.GET })
     @ResponseBody
     public Map<String, Object> getOrderInfo(HttpServletRequest req, HttpServletResponse res) throws Exception {
         res.setCharacterEncoding("UTF-8");        //该字段属于自定义字段,比如传入id用来service逻辑处理等,不要放入中文,中文会乱码.
         String attach = "";
         //创建订单编号  commonUtils是我自己的工具类,你可以自己写一个生成随机的订单编号
         String out_trade_no = CommonUtils.createNum();        //创建预支付信息
         Map<String, String> resultMap = wxPayService.dounifiedOrder(attach,out_trade_no);
         
         Map<String, String> map = new HashMap<String, String>();
         map.put("appid", resultMap.get("appid"));
         map.put("partnerid", resultMap.get("mch_id"));
         map.put("prepayid", resultMap.get("prepay_id"));
         map.put("packagevalue", "Sign=WXPay");
         map.put("noncestr", resultMap.get("nonce_str"));
         map.put("timestamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));// 单位为秒
         //这里使用生成带有sign的xml方法,如果使用签名,则前端app无法唤起微信支付界面
         String sign = WXPayUtil.generateSignedXml(map, WXPayCommonPath.API_KEY);
         map.put("sign",sign);
         map.put("extdata", "预留字段,可传入自定字段。");
         map.put("tradetype", resultMap.get("trade_type"));        return JsonMethod.setJsonMethod(PathKeyEnum.SUCCESS.getKey(), PathKeyEnum.SUCCESS.getValue(), map);
     } 
/**
      * 微信支付回调接口
      * 
      * @param request
      * @param response
      * @return
      */
     @OperLog(logDescription = "微信支付回调接口(微信支付)")
     @RequestMapping(value = "returnNotify", method = { RequestMethod.POST, RequestMethod.GET })
     @ResponseBody
     public String returnNotify(HttpServletRequest request, HttpServletResponse response) {
         response.setCharacterEncoding("UTF-8");
         
         String resXml = "";
         try {
             InputStream inputStream = request.getInputStream();
             //将InputStream转换成xmlString
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
             StringBuilder sb = new StringBuilder();
             String line = null;
             try {
                 while ((line = reader.readLine()) != null) {
                     sb.append(line + "\n");
                 }
             } catch (IOException e) {
                 System.out.println(e.getMessage());
             } finally {
                 try {
                     inputStream.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
             resXml = sb.toString();
             String result = wxPayService.returnXml(resXml);
             return result;
         } catch (Exception e) {
             System.out.println("微信手机支付失败:" + e.getMessage());
             String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
             return result;
         }
     } 
2:创建 service  serviceImpl 方法  (spring mvc 框架)
WxPayService:
/**
  * 微信支付逻辑处理
  * @author think
  *
  */
 public interface WxPayService {    /**
      * 微信支付请求预付单
      * @return
      * @throws Exception
      */
     public Map<String, String> dounifiedOrder(String attach,String out_trade_no)throws Exception;
     
     /**
      * 接收微信回调的方法并解析xml数据
      * @param notifyData
      * @return
      */
     public String returnXml(String notifyData)throws Exception;
 } 
创建impl方法
/**
  * 微信支付逻辑实现
  * @author think
  */
 @Service("WxPayService")
 public class WxPayServiceImpl implements WxPayService {    /**
      * 微信支付预付单生成
      */
     @Override
     public Map<String, String> dounifiedOrder(String attach,String out_trade_no) throws Exception {
         // TODO Auto-generated method stub
         Map<String, String> returnMap = new HashMap<String, String>();
         Map<String, String> param = new HashMap<String, String>();
         WxPayUtilConfig config = new WxPayUtilConfig();
         
         WXPay wxPay = new WXPay(config);
         param.put("appid", config.getAppID());
         param.put("attach", attach);
         //内容描述可通过前端app传递过来.
         param.put("body", "订单支付");
         param.put("mch_id", config.getMchID());
         param.put("nonce_str", WXPayUtil.generateNonceStr());
         param.put("notify_url", WXPayCommonPath.NOTIFYURL);
         param.put("out_trade_no", out_trade_no);
         //自己的服务器IP地址
         param.put("spbill_create_ip", WXPayCommonPath.SPBILL_CREATE_IP);
         param.put("total_fee", "1");//金额 以分为单位.我这边是测试数据.
         //交易类型
         param.put("trade_type", WXPayCommonPath.TRADE_TYPE);
         //该类型定义和不定义都无法更改微信回调的签名方式.所以我注释掉.写只是为了测试是否有用
         //param.put("sign_type","MD5");
         String sign = WXPayUtil.generateSignature(param,WXPayCommonPath.API_KEY);
         param.put("sign", sign);
         Map<String, String> response = wxPay.unifiedOrder(param);
         
         String returnCode = response.get("return_code");//获取返回码
         //若返回码为SUCCESS,则会返回一个result_code,再对该result_code进行判断
         if (returnCode.equals("SUCCESS")) {//主要返回以下5个参数
             String resultCode = response.get("result_code");
             returnMap.put("appid", response.get("appid"));
             returnMap.put("mch_id", response.get("mch_id"));
             returnMap.put("nonce_str", response.get("nonce_str"));
             returnMap.put("sign", response.get("sign"));
             if ("SUCCESS".equals(resultCode)) {
                 //resultCode 为SUCCESS,才会返回prepay_id和trade_type
                 //获取预支付交易回话标志
                 returnMap.put("trade_type", response.get("trade_type"));
                 returnMap.put("prepay_id", response.get("prepay_id"));
                 return returnMap;
             } else {
                 //此时返回没有预付订单的数据
                 return returnMap;
             }
         }
         return returnMap;
     }    /**
      * 解析回调后的xml数据
      */
     @Override
     public String returnXml(String notifyData) throws Exception{
         // TODO Auto-generated method stub
         String xmlBack = "";
         Map<String, String> notifyMap = null;
         try {
             notifyMap = WXPayUtil.xmlToMap(notifyData);//调用官方SDK转换成map类型数据
             /**
              * 微信回调返回的xml签名使用的是HMACSHA256
              * if判断的方法里需要加入HMACSHA256签名
              * 并且HMACSHA256该签名方法要导入sdk的包,别用工具类的包
              * 因为它底层判断的是用sdk的包里的枚举来判断的.
              */
             if (WXPayUtil.isSignatureValid(notifyMap, WXPayCommonPath.API_KEY,SignType.HMACSHA256)) {//验证签名是否有效,有效则进一步处理
                 String return_code = notifyMap.get("return_code");//状态
                 String out_trade_no = notifyMap.get("out_trade_no");//商户订单号
                 if (return_code.equals("SUCCESS")) {
                     if (out_trade_no != null) {
                         //这里就需要去做你的逻辑判断,比如说订单的状态                        System.err.println("支付成功");
                         xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                     } else {
                         LogComm.setLog("微信手机支付回调失败订单号:{}" + out_trade_no);
                         xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                     }
                 }
                 return xmlBack;
             } else {
                 // 签名错误,如果数据里没有sign字段,也认为是签名错误
                 //失败的数据要不要存储?
                 LogComm.setLog("手机支付回调通知签名错误:");
                 xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                 return xmlBack;
             }
         } catch (Exception e) {
             LogComm.setLog("手机支付回调通知失败:" + e.getMessage());
             xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
         }
         return xmlBack;
     }
 }

以上所有功能就是java微信支付服务端开发的功能

备注:

1:微信预付单需要签名,而返回给app的json里面的sign 调用工具类里面的 xml方法即可,不需要继续签名,否则app端无法唤起支付,上面有注释到。

2:微信回调已经给你处理过了签名使用的是HMACSHA256 签名方式。

在回调方法里如果用MD5签名去和微信回调的xml签名对比,永远都不会对,

所以回调之后需要用HMACSHA256 签名 并且与微信回调xml签名对比即可。

HMACSHA256:导入的是sdk包,别到工具类包,看工具类源码就可以读到。它底层判断sign就是用sdk的包来判断的。

好了。最后说一句。微信支付是真坑。开发花半天,回调 调试就浪费了半天,就因为签名对比不成功。坑。

注各位脱坑愉快。