微信支付-----统一下单接口对接

    本以为没有机会接触鼎鼎大名的支付宝和微信接口(公司本身是做第三方支付的),最近由于一个售货机项目需要对接银联,支付宝和微信接口,因为我自身已经对接了银联,之后根据安排,由我对接微信的相关接口。话不多说,让我们开启踩坑之旅。

     对接微信支付接口准备工作:

     1.微信支付文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1

     2.微信支付签名验证地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=20_1

     3.下载微信支付官方demo(根据自身需求):https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1

     4.申请相应的账号,包括:微信支付商户号,公众号ID:appid,appsecret,key,以及业务需要有可能用到的证书文件

    接下来所有的示例都用统一下单接口来展现

  • 第一步:阅读文档  

        

进入微信支付文档首页,根据我们使用微信支付的不同场景选择不同的文档来进行阅读,否则,你就有可能因为文档阅读错误导致demo跑不通而到处找错。

仔细阅读方框中的文档说明后,我们就可以进入到API列表中的统一下单接口中,准备对接此接口

 请务必注意传递字段的描述,微信支付文档中有很多地方在传递参数中,如果某个必传参数触发某个条件,那就需要多传递另外的参数,务必看仔细后再用demo去进行调用。

  • 第二步:修改官方demo进行测试

   此处为什么说要修改官方demo,因为官方demo只提供了必要的结构和代码,相关我们自己的参数还需要自己去实现,打比方说:微信demo中有一个类:WXPayConfig,是一个抽象类,你就必须先实现里面的方法,然后自己写一个测试用例进行调用和调试。

和官方demo相比,我只增加了这两个类来分别实现相应的接口,统一下单接口中的测试和修改基本都在WXPay中完成,下面我贴上这两个类的代码,仅供参考。

WxPayConfigImpl类代码:

package com.github.wxpay.sdk;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class WxPayConfigImpl extends WXPayConfig{

    private byte[] certData;
    private static WxPayConfigImpl INSTANCE;

    //公众号AppID
    private String appId = "";
    //商户号
    private String mchId = "";
    //key
    private String key = "";

    private WxPayConfigImpl() throws Exception{
//        String certPath = "D://CERT/common/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();
    }

    public static WxPayConfigImpl getInstance() throws Exception{
        if (INSTANCE == null) {
            synchronized (WxPayConfigImpl.class) {
                if (INSTANCE == null) {
                    INSTANCE = new WxPayConfigImpl();
                }
            }
        }
        return INSTANCE;
    }

    public String getAppID() {
        return this.appId;
    }

    public String getMchID() {
        return this.mchId;
    }

    public String getKey() {
        return this.key;
    }

    public InputStream getCertStream() {
        ByteArrayInputStream certBis;
        certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }


    public int getHttpConnectTimeoutMs() {
        return 2000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }

    IWXPayDomain getWXPayDomain() {
        return WXPayDomainSimpleImpl.instance();
    }

    public String getPrimaryDomain() {
        return "api.mch.weixin.qq.com";
    }

    public String getAlternateDomain() {
        return "api2.mch.weixin.qq.com";
    }

    @Override
    public int getReportWorkerNum() {
        return 1;
    }

    @Override
    public int getReportBatchSize() {
        return 2;
    }
}

WXPayDomainSimpleImp类代码:

package com.github.wxpay.sdk;
import org.apache.http.conn.ConnectTimeoutException;

import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by blaketang on 2017/6/16.
 */
public class WXPayDomainSimpleImpl implements IWXPayDomain {
    private WXPayDomainSimpleImpl(){}
    private static class WxpayDomainHolder{
        private static IWXPayDomain holder = new WXPayDomainSimpleImpl();
    }
    public static IWXPayDomain instance(){
        return WxpayDomainHolder.holder;
    }

    public synchronized void report(final String domain, long elapsedTimeMillis, final Exception ex) {
        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;
        }
    }

    public synchronized DomainInfo getDomain(final WXPayConfig config) {
        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;
        }
    }
    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>();
}

记下来就是展现你有没有仔细看文档的时候了,我们可以写个测试类,也可以直接写个main方法,我这里因为后续要集成到代码中,所以我直接用main方法直接运行

<xml>
<nonce_str>8SwC0RG5WpHXPZrykLxEq7chaYFrKCbd</nonce_str>
<out_trade_no>L0000000000000000026</out_trade_no>
<total_fee>10</total_fee>
<product_id>359</product_id>
<appid></appid>
<sign></sign>
<trade_type>NATIVE</trade_type>
<body>福优售货机</body>
<notify_url>http://222.186.36.87:8086/paytest/weixin/payNotify</notify_url>
<mch_id></mch_id>
<spbill_create_ip>222.186.36.87</spbill_create_ip>
<sign_type>MD5</sign_type>
</xml>

   此处为使用NATIVE支付时传递的参数(此处传递的是必传参数,根据业务需要大家可以根据文档来灵活使用),这里需要给大家说一下微信的签名验证工具

   一般情况下,只要正确调用了相关的接口,是不会出现签名不过的情况,但就是有一些拿到官方文档后大改特改的人,所以这个时候需要用到微信的签名验证工具,地址在博客开头。

  具体用法:拿到上面我展现的xml文件(debug找到输出地方打印即可),放到下图的文本框中进行验证

最终进行签名的校验即可。

  • 第三步:解决过程中遇到的bug

     1.切记,必须拿到最新的商户参数和密钥,并且保证正确可用

     2.拿到demo后全局浏览后,补充必要文件和参数

     3.根据文档传递参数,注意文档的备注

     4.参数签名为MD5,注意追踪demo默认签名类型

  • 第四步:生成二维码进行支付

    统一下单接口支付成功后,会返回你一串以weixin.开头的url地址,用二维码生成工具生成后,即可用微信扫一扫功能扫码付款了,注意,传递金额是以分为单位,测试时不要金额传递过多哟。

  •  第五步:接收支付成功回调

    

public void payNotify() {
        try {
            InputStream inputStream ;
            StringBuffer sb = new StringBuffer();
            inputStream = request.getInputStream();
            String s ;
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            while ((s = in.readLine()) != null){
                sb.append(s);
            }
            in.close();
            inputStream.close();

            //解析xml成map
            Map<String, String> m = new HashMap<String, String>();
            m = XMLUtil.doXMLParse(sb.toString());
            System.out.println("微信支付,支付通知数据:"+m.toString());
            //过滤空 设置 TreeMap
            SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
            Iterator it = m.keySet().iterator();
            while (it.hasNext()) {
                String parameter = (String) it.next();
                String parameterValue = m.get(parameter);

                String v = "";
                if(null != parameterValue) {
                    v = parameterValue.trim();
                }
                packageParams.put(parameter, v);
            }//判断签名是否正确
            if(PayCommonUtil.isTenpaySign("UTF-8", packageParams,key)) {
                //------------------------------
                //处理业务开始
                //------------------------------
                String resXml = "";
                if(VendorConstant.Weixin.SUCCESS.equals((String)packageParams.get("result_code"))){
                    // 这里是支付成功
                    //执行自己的业务逻辑
                    String mch_id = (String)packageParams.get("mch_id");
                    String openid = (String)packageParams.get("openid");
                    String is_subscribe = (String)packageParams.get("is_subscribe");
                    String out_trade_no = (String)packageParams.get("out_trade_no");
                    String result_code = (String)packageParams.get("result_code");
                    String total_fee = (String)packageParams.get("total_fee");
                    String transaction_id = (String)packageParams.get("transaction_id");

                    System.out.println("mch_id:"+mch_id);
                    System.out.println("openid:"+openid);
                    log.info("is_subscribe:"+is_subscribe);
                    log.info("out_trade_no:"+out_trade_no);
                    log.info("result_code:"+result_code);
                    log.info("total_fee:"+total_fee);
                    log.info("transaction_id:",transaction_id);
                    payCombineService.notifyPayResult("4",divideAmount(total_fee),out_trade_no,result_code,transaction_id);

                    //执行自己的业务逻辑

                    log.info("支付成功");
                    //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";

                } else {
                    log.info("支付失败,错误信息:" + packageParams.get("err_code"));
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                            + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                }
                //------------------------------
                //处理业务完毕
                //------------------------------
                BufferedOutputStream out = new BufferedOutputStream(
                        response.getOutputStream());
                out.write(resXml.getBytes());
                out.flush();
                out.close();
            } else{
                log.info("通知签名验证失败");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

因为此处的回调代码已经集成到公司项目里面了,这里就只贴出相应的代码点到为止,相应的工具类大家可以到支付回调工具类下载,配套使用更佳哦。

     总结:实际上,微信支付能对接全国的开发者是有其道理的,我遇到问题的时候在百度的时候碰到很多这样的语句,都说微信支付文档是大坑,确实,很多时候有很多问题都是自己不仔细,然后微信接口提示你签名错误,其实,只要认真看了微信官方文档,绝大多数问题都可以迎刃而解,很多时候都是自己不细心导致的。所以,有些时候,细心最重要。

    下一波我将介绍微信退款和退款结果通知的相关内容,剧透一下下:退款结果通知又是一个小坑。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值