微信公众号开发之微信支付开发

微信公众号开发对接,开发文档也有蛮多坑,所以一路的血泪教训,这次先针对微信支付整理一下支付的踩坑全过程,开发时间紧现在整理出来,既是对此段时间的学习总结,也希望对遇到同样问题的童鞋可以有参考价值。

 

1. 微信扫码支付

认证微信服务号,申请开通微信支付功能,获取到微信支付商户号,然后可以进行开发

 

扫码支付可分为两种模式,商户根据支付场景选择相应模式。(具体可以参考 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_3 流程比较详细

模式一开发前,商户必须在公众平台后台设置支付回调URL。URL实现的功能:接收用户扫码后微信支付系统回调的productid和openid。先根据规则生成支付二维码,用户扫码获取openid调用微信支付的统一下单接口生成预支付id,用户支付。

模式二流程相比模式一更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。

 

所以这次的扫码支付采用了简单点的模式二

先调用统一下单接口https://api.mch.weixin.qq.com/pay/unifiedorder

1) 参数

private String appid = "";//微信支付分配的公众账号ID(企业号corpid即为此appId)

    private String mch_id = "";//微信支付分配的商户号(申请通过之后邮件里收到的商户号

    private String nonce_str = "";//随机字符串,不长于32 位

    private String sign = "";//根据API给的签名规则进行签名

    private String body = "";//要支付的商品的描述信息

    private String attach = "";//支付订单里面可以填的附加数据

    private String out_trade_no = "";//商户系统内部的订单号

    private int total_fee = 0;//订单总金额,单位为“分”,只能整数

    private String spbill_create_ip = "";//订单生成的机器IP

    private String time_start = "";//订单生成时间, 格式为yyyyMMddHHmmss

    private String goods_tag = "";//商品标记,微信平台配置的商品标记,用于优惠券或者满减使用

    private String trade_type = "NATIVE";//JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里

    private String notify_url = “”;//回调url 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数

    private String detail = "name";

    private String openid = "";//JSAPI 一定要传 (下面的微信公众号支付一定要传的

2) 签名生成算法

public static String getSign(Map map){
    ArrayList list = new ArrayList();
    for(Map.Entry entry:map.entrySet()){
        if(entry.getKey().equals("sign")) {
            continue;
        }
        if (entry.getValue() != "") {
            list.add(entry.getKey() + "=" + entry.getValue() + "&");
        }
    }
    int size = list.size();
    String [] arrayToSort = list.toArray(new String[size]);
    Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < size; i ++) {
        sb.append(arrayToSort[i]);
    }
    String result = sb.toString();
    result += "key=" + WxpayConfig.getKey();
    result = MD5.MD5Encode(result).toUpperCase();
    return result;
}

 

随机字符串生成

public static String getRandomStringByLength(int length) {// length<=32
    String base = "abcdefghijklmnopqrstuvwxyz0123456789";
    Random random = new Random();
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < length; i++) {
        int number = random.nextInt(base.length());
        sb.append(base.charAt(number));
    }
    return sb.toString();
}

 

3) 回调处理

public String wxpaymentCallBack(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws Exception {
        String returnXML = "";
        try {
            //获取HTTP请求的输入流
            
InputStream is = httpRequest.getInputStream();
            //HTTP请求输入流建立一个BufferedReader对象
            
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            //读取HTTP请求内容
            
String buffer = null;
            StringBuffer sb = new StringBuffer();
            while ((buffer = br.readLine()) != null) {
                sb.append(buffer);
            }
            br.close();
            ScanPayResDTO scanPayResDto = (ScanPayResDTO) Util.getObjectFromXML(sb.toString(), ScanPayResDTO.class);
            System.out.println("微信回调:"+scanPayResDto.getReturn_code()+",订单号:"+scanPayResDto.getOut_trade_no());
            //商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现假通知,造成资金损失。
            
if (scanPayResDto.getReturn_code().equals("SUCCESS")) {
                if (scanPayResDto.getSign() != null && scanPayResDto.getOut_trade_no() != null) {
                    
Map reqData= xmlToMap(sb.toString());
                    System.out.println("微信返回的签名:"+scanPayResDto.getSign());
                    String sign = Signature.getSign(reqData);
                    System.out.println("系统生成的签名:"+sign);
                    if (scanPayResDto.getSign().equals(sign)) {//签名一样处理订单信息
                        
logger.info("【支付成功】");                       if(memberService.existOrderSnInRedis(scanPayResDto.getOut_trade_no())){
                           return returnXML("SUCCESS");
                        }
                        Charge charge = new Charge();
                        charge.setOrderNo(scanPayResDto.getOut_trade_no());//订单号
                    
charge.setTransactionNo(scanPayResDto.getTransaction_id());//交易号
                        
orderPaymentService.paymentCallBackAsyn(charge);
                        
returnXML = returnXML("SUCCESS");
                    } else {
                        logger.info("【签名失败】");
                        returnXML = returnXML("FAIL");
                    }
                }
            } else {
                returnXML = returnXML("FAIL");
                logger.info("【支付失败】");
            }
        } catch (Exception e) {
            returnXML = returnXML("FAIL");
        }
        return returnXML;
    }

    private String returnXML(String return_code) {
        return "+ return_code
          + "]]>";
    }

 

 

微信支付结果的通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒

注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。

推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。

2. 微信公众号支付

公众号支付需要先在公众号设置支付目录(请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付)和授权域名(开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名)。设置参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3

服务器端先调用统一下单接口,获取到prepay_id,再根据签名算法(直接调用上方扫码支付的签名算法)生成paySign(参与签名的参数有appId, timeStamp, nonceStr, package, signType,注意 大小写,时间戳要转成秒

   public Map createWxSDPaySign(String prepay_id) {
    Map map = new HashMap();
    map.put("appId", WxpayConfig.getAppid());
    map.put("timeStamp", System.currentTimeMillis()/1000);
    map.put("nonceStr", RandomStringGenerator.getRandomStringByLength(32));
    map.put("package", "prepay_id=" + prepay_id);
    map.put("signType", "MD5");
    String sign = Signature.getSign(map);
    map.put("paySign", sign);
    return map;
}

 

注意微信内置对象在其他浏览器中无效,即微信公众号支付只能在微信内部打开付款

列表中参数名区分大小,大小写错误签名验证会失败。其中:低版本的timestamp字段名s小写,新版的timeStamp字段名S大写

低版本(见https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115:

wx.chooseWXPay({
    timestamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
    nonceStr: '', // 支付签名随机串,不长于 32
    package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***
    signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
    paySign: '', // 支付签名
    success: function (res) { // 支付成功后的回调函数
    }
    });

 

 

新版本(见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6):

WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
           "appId":"wx2421b1c4370ec43b",     //公众号名称,由商户传入     
           "timeStamp":"1395712654",         //时间戳,自1970年以来的秒数     
           "nonceStr":"e61463f8efa94090b1f366cccfbbb444"//随机串     
           "package":"prepay_id=u802345jgfjsdfgsdg888",     
           "signType":"MD5",         //微信签名方式:     
           "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
       },
       function(res){     
           if(res.err_msg == "get_brand_wcpay_request:ok" ) {}      
       }
   ); 

3. 微信支付--优惠券 红包支付--回调签名验证问题

还要注意一个问题 支付回调通知返回的数据最好用Map接收,再进行签名校验,因为如果有奖励金或者优惠券等参数,可能接收的对象没有写全而导致验签失败,踩坑教训,所以后面签名用Map了,微信官方jdk一开始也是对象,后面也改了,都是用的map接收,还有一个原因就是参数命名,如下图的参数名是不确定几个的,所以还是map最方便

 

你可能感兴趣的:(微信公众号开发)