• 微信公众号开发之扫码支付


    此项目已开源欢迎Start、PR、发起Issues一起讨论交流共同进步
    https://github.com/Javen205/IJPay
    http://git.oschina.net/javen205/IJPay

    微信极速开发系列文章:http://www.jianshu.com/p/a172a1b69fdd

    上一篇文章介绍了微信提供的那些支付方式以及公众号支付http://www.jianshu.com/p/cb2456a2d7a7

    这篇文章我们来聊聊微信扫码支付(模式一以及模式二)


    先奉上研究微信扫码支付踩过的坑


    微信扫码支付文档


    扫码支付官方文档


    扫码支付分为以下两种方式:

    【模式一】:商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程。

    【模式二】:商户后台系统调用微信支付统一下单API生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付。详细接入步

    扫码支付模式一

    1、设置支付回调URL

    商户支付回调URL设置指引:进入公众平台-->微信支付-->开发配置-->扫码支付-->修改 如下图(来自官方文档)

    扫码支付模式一  设置回调URL.png

    开源项目weixin-guide扫码支付模式一的回调URL为http://域名[/项目名称]/pay/wxpay

    2、根据微信支付规则链接生成二维码

    2.1 生成二维码规则

    二维码中的内容为链接,形式为:

    weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX

    详细的参数说明参考文档 点击这里

    商户ID(mch_id)如何获取点击这里

    签名安全规则文档 点击这里

    开源项目weixin-guide扫码支付模式一 生成二维码规则封装如下:

    public String getCodeUrl(){
      String url="weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXX&time_stamp=XXXXX&nonce_str=XXXXX";
      String product_id="001";
      String timeStamp=Long.toString(System.currentTimeMillis() / 1000);
      String nonceStr=Long.toString(System.currentTimeMillis());
      Map<String, String> packageParams = new HashMap<String, String>();
      packageParams.put("appid", appid);
      packageParams.put("mch_id", partner);
      packageParams.put("product_id",product_id);
      packageParams.put("time_stamp", timeStamp);
      packageParams.put("nonce_str", nonceStr);
      String packageSign = PaymentKit.createSign(packageParams, paternerKey);
      
      return StringUtils.replace(url, "XXXXX", packageSign,appid,partner,product_id,timeStamp,nonceStr);
     }
    

    以上action开源项目weixin-guide中 访问地址为http://域名[/项目名称]/pay/getCodeUrl 其中 product_id 根据实际的业务逻辑可以当做参数传入

    2.2 生成二维码并在页面上显示

    根据2.1生成二维码规则生成了二维码中的内容(链接)来生成二维码。

    商户可调用第三方库生成二维码图片

    这里使用google 开源图形码工具Zxing

    项目中引入相关的jar包 具体配置参考项目中的pom.xml

    <!-- 版本号-->
    <zxing.version>3.2.1</zxing.version>
    
    <!-- 开源多维码生成工具 -->
      <dependency>
       <groupId>com.google.zxing</groupId>
       <artifactId>core</artifactId>
       <version>${zxing.version}</version>
      </dependency>
      <dependency>
       <groupId>com.google.zxing</groupId>
       <artifactId>javase</artifactId>
       <version>${zxing.version}</version>
      </dependency>
    

    封装的工具类为com.javen.kit.ZxingKit

    /**
     * google 开源图形码工具Zxing使用
     */
    public class ZxingKit {
     private static Log log = Log.getLog(ZxingKit.class.getSimpleName());
    
     /**
      * Zxing图形码生成工具
      *
      * @param contents
      *            内容
      * @param barcodeFormat
      *            BarcodeFormat对象
      * @param format
      *            图片格式,可选[png,jpg,bmp]
      * @param width
      *            宽
      * @param height
      *            高
      * @param margin
      *            边框间距px
      * @param saveImgFilePath
      *            存储图片的完整位置,包含文件名
      * @return
      */
     public static Boolean encode(String contents, BarcodeFormat barcodeFormat, Integer margin,
       ErrorCorrectionLevel errorLevel, String format, int width, int height, String saveImgFilePath) {
      Boolean bool = false;
      BufferedImage bufImg;
      Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
      // 指定纠错等级
      hints.put(EncodeHintType.ERROR_CORRECTION, errorLevel);
      hints.put(EncodeHintType.MARGIN, margin);
      hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
      try {
       // contents = new String(contents.getBytes("UTF-8"), "ISO-8859-1");
       BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, barcodeFormat, width, height, hints);
       MatrixToImageConfig config = new MatrixToImageConfig(0xFF000001, 0xFFFFFFFF);
       bufImg = MatrixToImageWriter.toBufferedImage(bitMatrix, config);
       bool = writeToFile(bufImg, format, saveImgFilePath);
      } catch (Exception e) {
       e.printStackTrace();
      }
      return bool;
     }
    
     /**
      * @param srcImgFilePath
      *            要解码的图片地址
      * @return
      */
     @SuppressWarnings("finally")
     public static Result decode(String srcImgFilePath) {
      Result result = null;
      BufferedImage image;
      try {
       File srcFile = new File(srcImgFilePath);
       image = ImageIO.read(srcFile);
       if (null != image) {
        LuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    
        Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();
        hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
        result = new MultiFormatReader().decode(bitmap, hints);
       } else {
        log.debug("Could not decode image.");
       }
      } catch (Exception e) {
       e.printStackTrace();
      } finally {
       return result;
      }
     }
    
     /**
      * 将BufferedImage对象写入文件
      *
      * @param bufImg
      *            BufferedImage对象
      * @param format
      *            图片格式,可选[png,jpg,bmp]
      * @param saveImgFilePath
      *            存储图片的完整位置,包含文件名
      * @return
      */
     @SuppressWarnings("finally")
     public static Boolean writeToFile(BufferedImage bufImg, String format, String saveImgFilePath) {
      Boolean bool = false;
      try {
       bool = ImageIO.write(bufImg, format, new File(saveImgFilePath));
      } catch (Exception e) {
       e.printStackTrace();
      } finally {
       return bool;
      }
     }
    
     public static void main(String[] args) {
      String saveImgFilePath = "D://zxing.png";
      Boolean encode = encode("我是Javen205", BarcodeFormat.QR_CODE, 3, ErrorCorrectionLevel.H, "png", 200, 200,
        saveImgFilePath);
      if (encode) {
       Result result = decode(saveImgFilePath);
       String text = result.getText();
       System.out.println(text);
      }
     }
    }
    

    OK 上面就是生成支付二维码的部分,接下来就是要将二维码显示在页面上,于是就有了下面的代码:
    com.javen.weixin.controller.WeixinPayController.getPayQRCode()

    src\main\webapp\view\payQRCode.jsp

    /**
      * 生成支付二维码(模式一)并在页面上显示
      */
    public void scanCode1(){
      //获取扫码支付(模式一)url
      String qrCodeUrl=getCodeUrl();
      System.out.println(qrCodeUrl);
      //生成二维码保存的路径
      String name = "payQRCode.png";
      Boolean encode = ZxingKit.encode(qrCodeUrl, BarcodeFormat.QR_CODE, 3, ErrorCorrectionLevel.H, "png", 200, 200,
        PathKit.getWebRootPath()+File.separator+"view"+File.separator+name );
      if (encode) {
       //在页面上显示
       setAttr("payQRCode", name);
       render("payQRCode.jsp");
      }
     }
    

    JSP 部分代码如下

    <body>
    <img alt="" src="<%=path %>/view/${payQRCode}">
    </body>
    

    最终生成二维码访问地址为http://域名[/项目名称]/pay/scanCode1

    以上就是微信扫码支付(模式一)生成支付二维码的全过程

    3、扫码回调商户支付URL

    用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统。

    此回调的URL为上文设置支付回调的URL。特别要注意的是返回参数是xml输入流

    HttpServletRequest request = getRequest();
        /**
        * 获取用户扫描二维码后,微信返回的信息
        */
       InputStream inStream = request.getInputStream();
       ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
       byte[] buffer = new byte[1024];
       int len = 0;
       while ((len = inStream.read(buffer)) != -1) {
           outSteam.write(buffer, 0, len);
       }
       outSteam.close();
       inStream.close();
       String result  = new String(outSteam.toByteArray(),"utf-8");
       System.out.println("callBack_xml>>>"+result);
    
    <xml>
    <appid><![CDATA[wx5e9360a3f46f64cd]]></appid>
    <openid><![CDATA[o_pncsidC-pRRfCP4zj98h6slREw]]></openid>
    <mch_id><![CDATA[商户ID]]></mch_id>
    <is_subscribe><![CDATA[Y]]></is_subscribe>
    <nonce_str><![CDATA[gT5NJAlv9eXawn1j]]></nonce_str>
    <product_id><![CDATA[001]]></product_id>
    <sign><![CDATA[D2BD7949269271B3112B442421B43D66]]></sign>
    </xml>
    

    4、根据回调参数生成预付订单进行支付

    根据回调参数调用统一下单API生成预支付交易的prepay_id

    prepay_xml>>>
    <xml><return_code><![CDATA[SUCCESS]]></return_code>
    <return_msg><![CDATA[OK]]></return_msg>
    <appid><![CDATA[微信的appid]]></appid>
    <mch_id><![CDATA[商户ID]]></mch_id>
    <nonce_str><![CDATA[p46NAoD82eAH2d9j]]></nonce_str>
    <sign><![CDATA[4117C601F41533DC84159AF6B892F72D]]></sign>
    <result_code><![CDATA[SUCCESS]]></result_code>
    <prepay_id><![CDATA[wx201610151315007cb1cbe40b0064755332]]></prepay_id>
    <trade_type><![CDATA[NATIVE]]></trade_type>
    <code_url><![CDATA[weixin://wxpay/bizpayurl?pr=QCLqJIG]]></code_url>
    </xml>
    

    商户后台系统将prepay_id返回给微信支付系统,微信支付系统根据交易会话标识,发起用户端授权支付流程。

    /**
              * 发送信息给微信服务器
              */
       Map<String, String> payResult = PaymentKit.xmlToMap(xmlResult);
       
       String return_code = payResult.get("return_code");
       String result_code = payResult.get("result_code");
       
       if (StrKit.notBlank(return_code) && StrKit.notBlank(result_code) && return_code.equalsIgnoreCase("SUCCESS")&&result_code.equalsIgnoreCase("SUCCESS")) {
        // 以下字段在return_code 和result_code都为SUCCESS的时候有返回
        String prepay_id = payResult.get("prepay_id");
        
        Map<String, String> prepayParams = new HashMap<String, String>();
        prepayParams.put("return_code", "SUCCESS");
        prepayParams.put("appId", appid);
        prepayParams.put("mch_id", mch_id);
        prepayParams.put("nonceStr", System.currentTimeMillis() + "");
        prepayParams.put("prepay_id", prepay_id);
        String prepaySign = null;
        if (sign.equals(packageSign)) {
         prepayParams.put("result_code", "SUCCESS");
        }else {
         prepayParams.put("result_code", "FAIL");
         prepayParams.put("err_code_des", "订单失效");   //result_code为FAIL时,添加该键值对,value值是微信告诉客户的信息
        }
        prepaySign = PaymentKit.createSign(prepayParams, paternerKey);
        prepayParams.put("sign", prepaySign);
        String xml = PaymentKit.toXml(prepayParams);
        log.error(xml);
        renderText(xml);
        
       }
    

    5、支付结果通用通知


    官方文档 点击这里

    对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
    注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
    推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
    特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。
    技术人员可登进微信商户后台扫描加入接口报警群。

    此通知接收地址为生成预付订单时设置的notify_url 。在开源项目weixin-guide中通知默认的地址为http://域名[/项目名称]/pay/pay_notify

    以上是微信扫码支付模式一的全过程。

    扫码支付模式二

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

    微信支付的统一下单接口具体实现上文也有提及到,如果还不是很清楚可以看 com.javen.weixin.controller.WeixinPayController中的scanCode2 以及官方文档介绍

    以下是调用预付订单返回的xml

    <xml><return_code><![CDATA[SUCCESS]]></return_code>
    <return_msg><![CDATA[OK]]></return_msg>
    <appid><![CDATA[wx5e9360a3f46f64cd]]></appid>
    <mch_id><![CDATA[1322117501]]></mch_id>
    <nonce_str><![CDATA[XdVf2zXLErIHRfJn]]></nonce_str>
    <sign><![CDATA[916703CD13C3615B9B629C4A9E4C3337]]></sign>
    <result_code><![CDATA[SUCCESS]]></result_code>
    <prepay_id><![CDATA[wx2016101514433661797ee3010493199442]]></prepay_id>
    <trade_type><![CDATA[NATIVE]]></trade_type>
    <code_url><![CDATA[weixin://wxpay/bizpayurl?pr=WWOXnrb]]></code_url>
    </xml>
    

    其中code_url 就是生成二维码的链接

    String qrCodeUrl = result.get("code_url");
      String name = "payQRCode1.png";
      Boolean encode = ZxingKit.encode(qrCodeUrl, BarcodeFormat.QR_CODE, 3, ErrorCorrectionLevel.H, "png", 200, 200,
        PathKit.getWebRootPath()+File.separator+"view"+File.separator+name );
      if (encode) {
       //在页面上显示
       setAttr("payQRCode", name);
       render("payQRCode.jsp");
      }
    

    扫码即可进行支付,code_url有效期为2小时,过期后扫码不能再发起支付

    最终生成二维码访问地址为http://域名[/项目名称]/pay/scanCode2

    码字完毕,以上就是微信扫码支付(模式一、模式二)的详细介绍。

    欢迎留言、转发
    微信极速开发系列文章:http://www.jianshu.com/p/a172a1b69fdd

    后续更新预告
    1、刷卡支付
    2、微信红包
    3、企业转账

  • 相关阅读:
    Solr学习笔记(2) —— Solr管理索引库
    Solr学习笔记(1) —— Solr概述&Solr的安装
    Linux软件安装
    Redis学习笔记(5)—— Redis的持久化方案&Redis的集群搭建
    Redis学习笔记(3)—— 五种数据类型&keys的通用操作
    Redis学习笔记(4)—— Jedis入门
    Redis学习笔记(2)—— Redis的安装和使用
    Redis学习笔记(1)—— NoSQL&Redis简介
    FastDFS
    Nginx
  • 原文地址:https://www.cnblogs.com/zyw-205520/p/5967436.html
Copyright © 2020-2023  润新知