微信开发接收固定扫码钱_微信开发平台,微信扫码登录 - CSDN
  • 微信开发扫码支付

    万次阅读 2019-06-22 11:52:00
    微信极速开发系列文章:http://www.jianshu.com/p/a172a1b69fdd上一篇文章介绍了微信提供的那些支付方式以及公众号支付http://www.jianshu.com/p/cb2456a2d7a7这篇文章我们来聊聊微信扫码支付(模式一以及模式二)先...

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

    文章首发地址:http://www.jianshu.com/p/474af73eb176
    微信极速开发系列文章:http://www.jianshu.com/p/a172a1b69fdd

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

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


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


    微信扫码支付文档


    扫码支付官方文档


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

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

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

    扫码支付模式一

    1、设置支付回调URL

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

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

    开源项目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://blog.csdn.net/column/details/14826.html

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

    展开全文
  • 微信支付 前言:最近的需求中,频繁出现微信支付功能的开发,于是研读了微信官方开发文档以及相关代码做了以下总结,并记录在此...用户使用微信客户端扫码后发起支付。 注意:code_url有效期为2小时,过期后扫码不能再

    微信支付之扫码支付与小程序支付

    前言:最近的需求中,频繁出现微信支付功能的开发,于是研读了微信官方开发文档以及相关代码做了以下总结,并记录在此,以备不时之需。如有不足之处,欢迎批评指正。
    微信官方开发文档

    扫码支付

    模式二:本文着重介绍扫码支付的模式二,其他情况以此类推,主要区别在统一下单前的步骤。

    1. 商户后台系统先调用微信支付的统一下单接口。
    2. 微信后台系统返回链接参数code_url。
    3. 商户后台系统将code_url值生成二维码图片。
    4. 用户使用微信客户端扫码后发起支付。

    注意:code_url有效期为2小时,过期后扫码不能再发起支付

    附上微信官方序列图,个人觉得还是挺通俗易懂的~
    图一  扫码支付序列图

    相关实体

    WeChatPayInfo (微信支付实体)

    代码示例
    /**
    * 微信支付信息实体类
    */
    @Data
    public class WeChatPayInfo {
    
    // 应用ID
    private String appid;
    // 商户号
    private String mch_id;
    // 终端设备号(门店号或收银设备ID),默认请传"WEB"
    private String device_info = "WEB";
    // 随机字符串
    private String nonce_str;
    // 签名,信息填充完整后使用工具类设置签名
    private String sign;
    // 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
    private String sign_type = "MD5";
    /**
     * 商品描述交易字段格式根据不同的应用场景按照以下格式: APP——需传入应用市场上的APP名字-实际商品名称
     */
    private String body;
    // 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
    private String attach;
    // 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
    private String out_trade_no;
    // 符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见
    private String fee_type = "CNY";
    // 订单总金额,单位为分
    private int total_fee;
    //订单详情,用于单个商品的优惠,设置成对象的json
    private String detail;
    // 用户端实际ip
    private String spbill_create_ip;
    // 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
    private String notify_url;
    // 交易类型
    private String trade_type;
    // 该字段用于统一下单时上报场景信息,目前支持上报实际门店信息,设置成对象的json
    private String scene_info;
    //微信公众号支付必填
    private String openid;
    //限制使用信用卡支付
    private String limit_pay;
    //二维码有效时间
    private String time_expire;
    
    
    /**
     * 设置限制使用信用卡
     */
    public void configureLimitPay() {
      this.limit_pay = "no_credit";
    }
    
    /**
     * 设置必填的自定义参数
     */
    public WeChatPayInfo(String body, String out_trade_no, String suffix,
        int total_fee, String trade_type, String spbill_create_ip) throws IOException {
      this.body = WeChatPayConfigurations.getAppName() + "-" + body;
      this.out_trade_no = out_trade_no;
      this.notify_url = WeChatPayConfigurations.getNotifyUrl(suffix);
      this.trade_type = trade_type;
      this.spbill_create_ip = spbill_create_ip;
      if (!WeChatPayConfigurations.getPayEnvironment()) {
        this.total_fee = 1;
      } else {
        this.total_fee = total_fee;
      }
    }
    
    /**
     *构造函数1- 设置必填的自定义参数
     */
    public WeChatPayInfo(String body, String out_trade_no,
        int total_fee, String notify_url,
        String trade_type, String spbill_create_ip) throws IOException {
      this.body = WeChatPayConfigurations.getAppName() + "-" + body;
      this.out_trade_no = out_trade_no;
      this.notify_url = notify_url;
      this.trade_type = trade_type;
      this.spbill_create_ip = spbill_create_ip;
      if (!WeChatPayConfigurations.getPayEnvironment()) {
        this.total_fee = 1;
      } else {
        this.total_fee = total_fee;
      }
    }
    
    
    /**
     *构造函数2- 设置必填的自定义参数
     */
    public WeChatPayInfo(String body, String out_trade_no, int total_fee, String notify_url,
        String trade_type, String spbill_create_ip, String openid) {
      this.body = body;
      this.out_trade_no = out_trade_no;
      if (!WeChatPayConfigurations.getPayEnvironment()) {
        this.total_fee = 1;
      } else {
        this.total_fee = total_fee;
      }
      this.spbill_create_ip = spbill_create_ip;
      this.notify_url = notify_url;
      this.trade_type = trade_type;
      this.openid = openid;
    }
    
    
    /**
     *构造函数3- 设置必填的自定义参数
     */
    public WeChatPayInfo(String body, String out_trade_no, int total_fee, String notify_url,
                         String trade_type, String spbill_create_ip, String openid,String appid,String mch_id) {
      this.body = body;
      this.out_trade_no = out_trade_no;
      if (!WeChatPayConfigurations.getPayEnvironment()) {
        this.total_fee = 1;
      } else {
        this.total_fee = total_fee;
      }
      this.spbill_create_ip = spbill_create_ip;
      this.notify_url = notify_url;
      this.trade_type = trade_type;
      this.openid = openid;
      this.appid = appid;
      this.mch_id = mch_id;
    }
    
    
    /**
     * 设置单品优惠信息
     */
    public void configDetail(OrderInfos orderInfos) {
      this.detail = JSON.toJSONString(orderInfos);
    }
    
    /**
     * 设置实际门店信息
     */
    public void configScene_info(SceneInfo sceneInfo) {
      this.scene_info = JSON.toJSONString(sceneInfo);
    }
    
    
    }
    
    

    WeChatPreOrderInfo (微信预支付实体)

    代码示例
    
    /**
    * 微信预支付订单返回信息
    */
    @Data
    public class WeChatPreOrderInfo {
    
    //返回状态码
    private String return_code;
    //返回信息
    private String return_msg;
    //应用APPID
    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;
    //交易类型
    private String trade_type;
    //预支付交易会话标识
    private String prepay_id;
    //扫码支付返回字段,用于生成二维码
    private String code_url;
    //二维码有效时间
    private String time_expire;
    
    
    /**
     * 连接是否成功
     */
    public boolean isContact() {
      return "SUCCESS".equals(this.return_code);
    }
    
    /**
     * 业务是否成功
     */
    public boolean isSuccess() {
      if (isContact()) {
        return "SUCCESS".equals(this.result_code);
      }
      return false;
    }
    
    /**
     * 固定字段
     */
    public String getPackage() {
      return "Sign=WXPay";
    }
    
    /**
     * 时间戳
     */
    public Long getTimestamp() {
      return System.currentTimeMillis() / 1000;
    }
    
    }
    
    

    WeChatPayRet (微信支付返回结果实体)

    代码示例
    
    
    /**
    * 微信支付返回信息类
    */
    @Data
    public class WeChatPayRet {
    
    //返回状态码
    private String return_code;
    //返回信息
    private String return_msg;
    //应用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_des;
    //用户标识
    private String openid;
    //是否关注公众账号
    private String is_subscribe;
    //交易类型
    private String trade_type;
    //付款银行
    private String bank_type;
    //总金额
    private Integer total_fee;
    //货币种类
    private String fee_type;
    //现金支付金额
    private Integer cash_fee;
    //现金支付货币类型
    private String cash_fee_type;
    //代金券金额
    private Integer coupon_fee;
    //代金券使用数量
    private Integer coupon_count;
    //微信支付订单号
    private String transaction_id;
    //商户订单号
    private String out_trade_no;
    //商家数据包
    private String attach;
    //加密方式
    private String sign_type;
    //支付完成时间
    
    //支付完成时间
    private String time_end;
    
    /**
     * 连接是否成功
     */
    public boolean isContact() {
      return "SUCCESS".equals(this.return_code);
    }
    
    /**
     * 业务是否成功
     */
    public boolean isSuccess() {
      if (isContact()) {
        return "SUCCESS".equals(this.result_code);
      }
      return false;
    }
    
    @Override
    public String toString() {
      return "WeChatPayRet{" +
              "return_code='" + return_code + '\'' +
              ", return_msg='" + return_msg + '\'' +
              ", appid='" + appid + '\'' +
              ", mch_id='" + mch_id + '\'' +
              ", device_info='" + device_info + '\'' +
              ", nonce_str='" + nonce_str + '\'' +
              ", sign='" + sign + '\'' +
              ", result_code='" + result_code + '\'' +
              ", err_code='" + err_code + '\'' +
              ", err_des='" + err_des + '\'' +
              ", openid='" + openid + '\'' +
              ", is_subscribe='" + is_subscribe + '\'' +
              ", trade_type='" + trade_type + '\'' +
              ", bank_type='" + bank_type + '\'' +
              ", total_fee=" + total_fee +
              ", fee_type='" + fee_type + '\'' +
              ", cash_fee=" + cash_fee +
              ", cash_fee_type='" + cash_fee_type + '\'' +
              ", coupon_fee=" + coupon_fee +
              ", coupon_count=" + coupon_count +
              ", transaction_id='" + transaction_id + '\'' +
              ", out_trade_no='" + out_trade_no + '\'' +
              ", attach='" + attach + '\'' +
              ", sign_type='" + sign_type + '\'' +
              ", time_end='" + time_end + '\'' +
              '}';
    }
    }
    
    

    注意:随着微信版本的更新微信官方会扩展出新的字段,届时注意接收实体的局部变动

    相关工具类

    IpUtil(ip获取工具类)

    代码示例
    
    import org.apache.commons.lang.StringUtils;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
    * 获取用户侧ip地址
    */
    public class IpUtil {
    
    public static String getIp(HttpServletRequest request) {
      String ip = request.getHeader("X-Real-IP");
      if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip.trim())) {
        ip = request.getHeader("remote-host");
      }
      if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip.trim())) {
        ip = request.getRemoteAddr();
      }
      if (StringUtils.isNotBlank(ip)) {
        if (ip.startsWith("10.")) {
          String tip = request.getParameter("ip");
          if (StringUtils.isNotBlank(tip)) {
            ip = tip;
          }
        }
      }
      return ip;
    }
    }
    
    

    SnGenerator(随机字符串生成器)

    代码示例
    
    /**
     * 随机字符串生成工具
     */
    public class SnGenerator {
    
    private final static char[] NUMS = "123456789".toCharArray();
    private final static char[] LETTERS = "QWERTYUIPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"
        .toCharArray();
    private final static char[] MIX_LETTERS_AND_NUM = "QWERTYUIPASDFGHJKLZXCVBNMqwertyuipasdfghjklzxcvbnm01234567890"
        .toCharArray();
    
    public final static int MODE_NUM = 0;
    public final static int MODE_LOWER_STR = 1;
    public final static int MODE_UPPER_STR = 2;
    public final static int MODE_STR = 3;
    public final static int MODE_MIX = 4;
    
    
    /**
     * 生成带前缀的字符串,如果前缀+日期字符串+随机字符串的长度超过count,将会在保留前缀的情况下压缩其余部分
     */
    public static String generateFormatWithPrefix(String prefix, int count, int mode) {
      return generateFormat(prefix, null, count, mode);
    }
    
    /**
     * 生成带前缀的字符串,如果日期字符串+随机字符串+后缀的长度超过count,将会在保留后缀的情况下压缩其余部分
     */
    public static String generateFormatWithSuffix(String suffix, int count, int mode) {
      return generateFormat(null, suffix, count, mode);
    }
    
    /**
     * 生成不带前后缀的字符串,格式为yyyyMMddHHmmssSSS+随机字符串,长度为count
     */
    public static String generateFormat(int count, int mode) {
      return generateFormat(null, null, count, mode);
    }
    
    /**
     * 生成格式化的字符串,格式为前缀+日期字符串(17位)+中间随机字符串+后缀。如果前缀+后缀+中间字符串的长度超过count, 将会压缩中间字符串的长度来满足count
     *
     * @param prefix 字符串前缀
     * @param suffix 字符串后缀
     * @param count 生成字符串的长度
     * @param mode 模式
     */
    public static String generateFormat(String prefix, String suffix, int count, int mode) {
      if (count <= 17) {
        count = 18;
      }
      int prefixLen = 0;
      int suffixLen = 0;
      StringBuilder sb = new StringBuilder();
      if (prefix != null && (!"".equals(prefix))) {
        prefixLen = prefix.length();
        sb.append(prefix);
      }
      if (suffix != null && (!"".equals(suffix))) {
        suffixLen = suffix.length();
      }
      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
      String date = sdf.format(new Date());
    
      int len = count - prefixLen - suffixLen - date.length();
      if (len > 0) {
        switch (mode) {
          case MODE_NUM:
            date = date + randomNums(len);
            break;
          case MODE_LOWER_STR:
            date = date + randomLowerStr(len);
            break;
          case MODE_UPPER_STR:
            date = date + randomUpperStr(len);
            break;
          case MODE_STR:
            date = date + randomStr(len);
            break;
          case MODE_MIX:
            date = date + randomMix(len);
            break;
          default:
            date = date + randomNums(len);
            break;
        }
      }
      sb.append(date.substring(0, count - prefixLen - suffixLen));
      if (suffixLen > 0) {
        sb.append(suffix);
      }
    
      return sb.toString();
    }
    
    /**
     * 生成写字母和数字随机字符串
     */
    public static String randomMix(int count) {
      return generator(count, MIX_LETTERS_AND_NUM);
    }
    
    /**
     * 生成大小写混合字母随机字符串
     */
    public static String randomStr(int count) {
      return generator(count, LETTERS);
    }
    
    /**
     * 生成纯大写字母随机字符串
     */
    public static String randomUpperStr(int count) {
      return generator(count, LETTERS).toUpperCase();
    }
    
    /**
     * 生成纯数字随机字符串
     */
    public static String randomNums(int count) {
      return generator(count, NUMS);
    }
    
    /**
     * 生成纯小写字母随机字符串
     */
    public static String randomLowerStr(int count) {
      return generator(count, LETTERS).toLowerCase();
    }
    
    /**
     * 生成器
     */
    private static String generator(int count, char[] arr) {
      if (count <= 0) {
        count = 6;
      }
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < count; i++) {
        double d = Math.random();
        int index = (int) Math.floor(d * arr.length);
        sb.append(arr[index]);
      }
    
      return sb.toString();
    }
    
    }
    
    
    

    XMLUtils(xml转化工具类)

    代码示例
    
    /**
    * xml转化工具类
    */
    public class XMLUtils {
    
    /**
     * 从request读取xml
     */
    public static String readXmlFromRequest(HttpServletRequest request) {
      StringBuilder xmlSb = new StringBuilder();
      try (
              ServletInputStream in = request.getInputStream();
              InputStreamReader inputStream = new InputStreamReader(in);
              BufferedReader buffer = new BufferedReader(inputStream);
      ) {
        String line;
        while ((line = buffer.readLine()) != null) {
          xmlSb.append(line);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      return xmlSb.toString();
    }
    
    /**
     * 将获取的Map转换成xml
     */
    public static Document convertMap2Xml(Map<String, Object> map) {
      Document doc = DocumentHelper.createDocument();
      try {
        Element root = doc.addElement("xml");
        Set<String> keys = map.keySet();
        for (String key : keys) {
          Element ele = root.addElement(key);
          ele.addCDATA(map.get(key).toString());
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      return doc;
    }
    
    /**
     * xml文档Document转对象
     */
    @SuppressWarnings("unchecked")
    public static Object convertXml2Bean(Document document, Class<?> clazz) {
      Map<String, String> map = new HashMap<>();
      // 获取根节点
      Element root = document.getRootElement();
      try {
        List<Element> properties = root.elements();
        for (Element pro : properties) {
          String propName = pro.getName();
          String propValue = pro.getText();
          map.put(propName, propValue);
        }
    
      } catch (Exception e) {
        e.printStackTrace();
      }
    
      //处理map里的JSON字符串字段,防止解析错误
      Map<String, Object> objMap = new TreeMap<>();
      Set<String> keys = map.keySet();
      for (String key : keys) {
        String str = map.get(key);
        try {
          //如果是JSON字符串,则转换成对象,再添加到objMap中
          objMap.put(key, JSON.parse(str));
        } catch (JSONException e) {
          //如果不是JSON字符串,则直接添加到objMap中
          objMap.put(key, str);
        } catch (Exception e) {
          //其余错误抛出
          e.printStackTrace();
        }
    
      }
    
      return JSON.parseObject(JSON.toJSONString(map), clazz);
    }
    
    /**
     * xml字符串转对象
     */
    public static Object convertXml2Bean(String xmlString, Class<?> clazz) {
      Document document;
      try {
        document = DocumentHelper.parseText(xmlString);
      } catch (DocumentException e) {
        throw new RuntimeException("获取Document异常" + xmlString);
      } // 获取根节点
      return convertXml2Bean(document, clazz);
    }
    
    /**
     * 对象转xml文件
     */
    public static Document convertBean2Xml(Object b) {
      Document document = DocumentHelper.createDocument();
      try {
        // 创建根节点元素
        Element root = document.addElement(b.getClass().getSimpleName());
        // 获取实体类b的所有属性,返回Field数组
        Field[] field = b.getClass().getDeclaredFields();
        // 遍历所有有属性
        for (Field aField : field) {
          String name = aField.getName(); // 获取属属性的名字
          if (!name.equals("serialVersionUID")) {// 去除串行化序列属性
            name = name.substring(0, 1).toUpperCase() + name.substring(1); // 将属性的首字符大写,方便构造get,set方法
            Method m = b.getClass().getMethod("get" + name);
            String propValue = (String) m.invoke(b);// 获取属性值
            Element propertie = root.addElement(name);
            propertie.setText(propValue);
          }
        }
    
      } catch (Exception e) {
        e.printStackTrace();
      }
    
      return document;
    }
    
    /**
     * 对象转xml格式的字符串
     */
    public static String getXmlString(Object b) {
      return convertBean2Xml(b).asXML();
    }
    }
    
    

    WeChatPayAssistant (微信支付助手)

    代码示例
    
    public class WeChatPayAssistant {
    
    private static final Logger logger = LoggerFactory.getLogger(WeChatPayAssistant.class);
    
    /**
     * 解析付款回调请求
     */
    public static WeChatPayRet parsePayNotifyRequest(HttpServletRequest request) {
      String xml = XMLUtils.readXmlFromRequest(request);
      logger.info("wechatXml is:           "+xml);
      return (WeChatPayRet) XMLUtils.convertXml2Bean(xml, WeChatPayRet.class);
    }
    
    /**
     * 解析退款回调请求
     */
    public static WeChatRefundNotifyRet parseRefundNotifyRequest(HttpServletRequest request) {
      String xml = XMLUtils.readXmlFromRequest(request);
      return (WeChatRefundNotifyRet) XMLUtils.convertXml2Bean(xml, WeChatRefundNotifyRet.class);
    }
    
    /**
     * 应答微信回调
     */
    public static void echo(HttpServletResponse response) throws Exception {
      response.setContentType("application/xml");
      ServletOutputStream os = response.getOutputStream();
      os.print(echo());
    }
    
    /**
     * 异步回调应答
     */
    public static String echo() {
      return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
    }
    
    
    /**
     * 微信公众号或移动支付退款
     */
    public static WeChatRefundRet refund(WeChatRefundInfo refundInfo, String tradeType)
        throws Exception {
    
      refundInfo.setNotify_url(WeChatPayConfigurations.getNotifyUrl("refund"));
    
      refundInfo.setNonce_str(SnGenerator.randomMix(32));
      String xml = "";
      refundInfo.setAppid(WeChatPayConfigurations.getAppId());
      refundInfo.setMch_id(WeChatPayConfigurations.getMchId());
      refundInfo.setSign(generateSign(refundInfo));
      TreeMap<String, Object> map = getSignMap(refundInfo, WeChatRefundInfo.class);
      Document doc = XMLUtils.convertMap2Xml(map);
      URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com")
          .setPath("/secapi/pay/refund")
          .build();
      xml = HttpClientUtils.connectWithXMLAndSSLByPost(uri, doc,
          WeChatPayConfigurations.getRefundCertificatePath(),
          WeChatPayConfigurations.getRefundCertificatePassword());
    
      WeChatRefundRet refundRet = (WeChatRefundRet) XMLUtils
          .convertXml2Bean(xml, WeChatRefundRet.class);
      if (!refundRet.isContact()) {
        String msg = refundRet.getReturn_code() + ":" + refundRet.getReturn_msg();
        msg = new String(msg.getBytes("iso-8859-1"), "utf-8");
        throw new RuntimeException(msg);
      }
      if (!refundRet.isSuccess()) {
        String msg = refundRet.getResult_code() + ":" + refundRet.getErr_code() + " - " + refundRet
            .getErr_code_des();
        throw new RuntimeException(msg);
      }
      return refundRet;
    }
    
    /**
     * 微信预支付订单
     */
    public static WeChatPreOrderInfo preOrder(WeChatPayInfo payInfo) throws Exception {
    
      if (payInfo.getTrade_type().equals(WeChatPayConst.TRADE_TYPE_OFFICIAL_ACCOUNT) && StringUtils
          .isEmpty(payInfo.getOpenid())) {
        throw new RuntimeException("公众号支付openid不能为空,请填入正确的openid");
      }
    
      payInfo.setAppid(WeChatPayConfigurations.getAppId());
      payInfo.setMch_id(WeChatPayConfigurations.getMchId());
    
      payInfo.setNonce_str(SnGenerator.randomMix(32).toUpperCase());
      payInfo.setTime_expire(getOrderExpireTime(2*60*60*1000L));
      payInfo.setSign(generateSign(payInfo));
    
      Document doc = XMLUtils.convertMap2Xml(getSignMap(payInfo, WeChatPayInfo.class));
      URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com")
          .setPath("/pay/unifiedorder")
          .build();
      String xml = HttpClientUtils.connectWithXMLByPost(uri, doc);
      WeChatPreOrderInfo info = (WeChatPreOrderInfo) XMLUtils
          .convertXml2Bean(xml, WeChatPreOrderInfo.class);
      if (!info.isContact()) {
        String msg = info.getReturn_code() + ":" + info.getReturn_msg();
        msg = new String(msg.getBytes("iso-8859-1"), "utf-8");
        throw new RuntimeException(msg);
      }
      if (!info.isSuccess()) {
        String msg = info.getResult_code() + ":" + WeChatPayErrorUtil.getErrorMsg(info.getErr_code());
        throw new RuntimeException(msg);
      }
      return info;
    }
    
    /**
     *  设置微信二维码失效时间,并返回具体失效的时间点
     *  expire 二维码的有效时间,单位是毫秒
     */
    public static String getOrderExpireTime(Long expire){
      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
      Date now = new Date();
      Date afterDate = new Date(now.getTime() + expire);
      return sdf.format(afterDate );
    }
    
    /**
     * 设置签名
     */
    public static String generateSign(Object obj) throws Exception {
      TreeMap<String, Object> map = getSignMap(obj, obj.getClass());
      return signMap(map);
    }
    
    /**
     * 验证签名
     */
    public static boolean isSignValid(Object obj) throws Exception {
      TreeMap<String, Object> map = getSignMap(obj, obj.getClass());
      String signFromAPIResponse = map.get("sign").toString();
      if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
        logger.warn("The data signature data returned by the API does not exist and may be tampered with by a third party!!!");
        return false;
      }
    
      logger.info("服务器回包里面的签名是: {}", signFromAPIResponse);
    
      //清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
      map.remove("sign");
      logger.info("sign map: {}", new JSONObject(map));
      String signForAPIResponse = signMap(map);
    
      if (!signForAPIResponse.equals(signFromAPIResponse)) {
        //签名验不过,表示这个API返回的数据有可能已经被篡改了
        logger.warn("The data signature verification returned by the API fails, and may be tampered with by a third party!!! signForAPIResponse The resulting signature is" + signForAPIResponse);
        return false;
      }
      return true;
    }
    /**
     * TreeMap加签
     */
    private static String signMap(TreeMap<String, Object> map) {
      Set<String> keys = map.keySet();
      StringBuilder sb = new StringBuilder();
      for (String key : keys) {
        sb.append(key).append("=").append(map.get(key)).append("&");
      }
      sb.append("key").append("=").append(WeChatPayConfigurations.getPayKey());
    
      return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }
    
    
    /**
     * 获取按顺序整理好的非空值字段
     */
    @SuppressWarnings("unchecked")
    private static TreeMap<String, Object> getSignMap(Object obj, Class<?> clz) throws Exception {
      if (obj == null) {
        throw new RuntimeException("支付对象不能为空");
      }
      // 使用treeMap将字段按要求排序
      TreeMap<String, Object> map = new TreeMap<>();
      if (obj instanceof Map){
        Map mapObj = (Map)obj;
        Set<Map.Entry<Object,Object>> set = mapObj.entrySet();
        for (Map.Entry<Object,Object> entry:set){
          map.put(entry.getKey().toString(),entry.getValue());
        }
      }else {
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
          map.put(field.getName(), clz.getMethod("get" + upperFirst(field.getName())).invoke(obj));
        }
      }
      // 使用fastjson过滤掉null的字段
      String json = JSON.toJSONString(map);
      map = JSON.parseObject(json, TreeMap.class);
      return map;
    }
    
    /**
     * 首字母大写
     */
    private static String upperFirst(String name) {
      return name.substring(0, 1).toUpperCase() + name.substring(1);
    }
    
    /**
     * APP支付二次加签
     */
    public static Map<String, Object> sign4App(WeChatPreOrderInfo preOrderInfo) {
      TreeMap<String, Object> map = new TreeMap<>();
      map.put("appid", preOrderInfo.getAppid());
      map.put("partnerid", preOrderInfo.getMch_id());
      map.put("prepayid", preOrderInfo.getPrepay_id());
      map.put("package", preOrderInfo.getPackage());
      map.put("noncestr", SnGenerator.randomMix(32));
      map.put("timestamp", preOrderInfo.getTimestamp());
      map.put("sign", signMap(map));
      return map;
    }
    
    /**
     * 微信内H5支付二次加签(注意:APP加签字段全部小写,这里加签用驼峰)
     */
    public static Map<String, Object> sign4WxH5(WeChatPreOrderInfo preOrderInfo) {
      TreeMap<String, Object> map = new TreeMap<>();
      map.put("appId", preOrderInfo.getAppid());
      map.put("timeStamp", preOrderInfo.getTimestamp().toString());
      map.put("package", "prepay_id=" + preOrderInfo.getPrepay_id());
      map.put("nonceStr", SnGenerator.randomMix(32));
      map.put("signType", "MD5");
      map.put("paySign", signMap(map));
      return map;
    }
    
    public static WeChatRefundReqInfo decodeReqInfo(String reqInfo) throws Exception {
      String md5Key = DigestUtils.md5Hex(WeChatPayConfigurations.getPayKey());
      Security.addProvider(new BouncyCastleProvider());
      Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
      SecretKey keySpec = new SecretKeySpec(md5Key.getBytes(), "AES"); //生成加密解密需要的Key
      cipher.init(Cipher.DECRYPT_MODE, keySpec);
      String result = new String(cipher.doFinal(Base64.decodeBase64(reqInfo)),
          StandardCharsets.UTF_8);
      return (WeChatRefundReqInfo) XMLUtils.convertXml2Bean(result, WeChatRefundReqInfo.class);
    }
    
    
    
    }
    
    

    微信支付常量

    代码示例
    
    /**
    * 微信支付常量词典
    */
    public class WeChatPayConst {
    
    // 签名类型md5
    public static final String SIGN_TYPE_MD5 = "MD5";
    // 签名类型sha256
    public static final String SIGN_TYPE_SHA256 = "HMAC-SHA256";
    
    // 支付类型:公众号支付
    public static final String TRADE_TYPE_OFFICIAL_ACCOUNT = "JSAPI";
    // 支付类型:扫码支付
    public static final String TRADE_TYPE_SWEEP_CODE = "NATIVE";
    // 支付类型:APP
    public static final String TRADE_TYPE_APP = "APP";
    }
    
    

    关键步骤

    1. 支付账号的开通
      参考微信官方文档

    2. 微信预支付

      1. 确定金额,生成商户系统相关订单信息,初始化状态为待支付

      2. 构造微信支付实体(WeChatPayInfo)

      WeChatPayInfo weChatPayInfo = new WeChatPayInfo(BODY, orderId, amount, notifyUrl,
                       WeChatPayConst.TRADE_TYPE_SWEEP_CODE, ip);
      
      1. 获取用户侧ip

      2. 设置签名

      3. 生成随机字符串

      4. 定义回调接口url(notifyUrl)

      5. 按要求将微信支付实体转化为xml

      6. 调用微信统一下单接口

      Document doc = XMLUtils.convertMap2Xml(getSignMap(payInfo, WeChatPayInfo.class));
      URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/pay/unifiedorder").build();
      
      1. 将返回结果转化为微信预支付实体类(WeChatPreOrderInfo),并获取二维码url

        备注:预支付详细代码见WeChatPayAssistant工具类的
        preOrder(WeChatPayInfo payInfo)方法

    1. 微信回调(微信支付结果通知)
      • 判断连接和业务是否都成功
      • 验签
      • 修改订单状态(支付成功或失败)
      • 返回回调应答
    public String weChatPayNotify(HttpServletRequest request) {
    WeChatPayRet weChatPayRet = WeChatPayAssistant.parsePayNotifyRequest(request);
    //判断连接和业务是否都成功且通过验签
    if (weChatPayRet.isContact() && weChatPayRet.isSuccess() && WeChatPayAssistant.isSignValid(weChatPayRet)) { 
    //修改订单状态及相关业务处理
    }
    
    //返回应答
    return WeChatPayAssistant.echo();
    }
    

    4.如果未收到微信支付回调通知,可主动调用微信查询订单状态接口,
    此处逻辑省略 微信官方查询订单

    小程序支付

    小程序与扫码支付的主要不同之处即小程序需要先调起微信登录接口( wx.login(Object object) )获取登录凭证(code),然后通过code进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话密钥(session_key)等。

    小程序支付序列图:
    小程序支付序列图

    关键步骤

    1. 根据code获取openId并将session_key存入redis

       public static String getOpenId(String appId,String appSecret,String code){
           String s = HttpClientUtil.sendHttpGet(getOpenIdUrl + "&appid=" + appId + "&secret=" + appSecret+"&js_code="+code);
           if (null!=s&&!"".equals(s)){
               JSONObject jsonObject = JSON.parseObject(s);
               String openId = jsonObject.getString("openid");
               //把session_key存进redis
               String session_key = jsonObject.getString("session_key");
               JedisCluster jedisCluster = RedisUtil.getJedisCluster();
               String s1 = jedisCluster.get(RedisUtil.getKey(sessionKeyPre + openId));
               if (null!=openId&&!"".equals(openId)){
                   if (null==s1||"".equals(s1)||!s1.equals(session_key)){
                       jedisCluster.set(RedisUtil.getKey(sessionKeyPre+openId),session_key);
                       jedisCluster.expire(RedisUtil.getKey(sessionKeyPre+openId),86400*2);
                   }
                   return openId;
               } else {
                   Integer errcode = jsonObject.getInteger("errcode");
                   String errmsg = jsonObject.getString("errmsg");
                   log.error("getOpenId is failed errcode:"+errcode+"-----errmsg:"+errmsg);
                   throw new RuntimeException("获取openId失败");
               }
           }else {
               log.error("getOpenId is failed ");
               throw new RuntimeException("获取openId失败");
           }
       }
      
    2. 微信预支付(同扫码支付,但不返回用于生成二维码的code_url)需将预支付交易会话标识prepay_id以及拼接好的数据包package返给前端

    3. 微信回调(同扫码支付)

    展开全文
  • Java微信支付开发扫码支付模式一

    千次阅读 热门讨论 2019-12-20 23:08:55
    大体过程:先扫码(还没有确定实际要支付的金额),这个码是商品的二维码,再生成订单,适用于自动贩卖机之类固定金额的。 模式一支付的流程如下图,稍微有点复杂 业务流程说明: (1)商户后台系统根据微信...

    官方文档
    准备工作:已通过微信认证的公众号,必须通过ICP备案域名(否则会报支付失败)

    借鉴了很多大神的文章,在此先谢过了

    大体过程:先扫码(还没有确定实际要支付的金额),这个码是商品的二维码,再生成订单,适用于自动贩卖机之类固定金额的。

     

    模式一支付的流程如下图,稍微有点复杂

    原生支付接口模式一时序图

     

    业务流程说明:

    (1)商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
    (2)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
    (3)微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包

    (4)商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。

    (5)商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)。
    (6)微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
    (7)商户后台系统得到交易会话标识prepay_id(2小时内有效)。
    (8)商户后台系统将prepay_id返回给微信支付系统。
    (9)微信支付系统根据交易会话标识,发起用户端授权支付流程。
    (10)用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
    (11)微信支付系统验证后扣款,完成支付交易。
    (12)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
    (13)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
    (14)未收到支付通知的情况,商户后台系统调用【查询订单API】。
    (15)商户确认订单已支付后给用户发货。

    一、设置回调地址

    商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程
    商户支付回调URL设置指引:进入公众平台-->微信支付-->开发配置-->扫码支付-->修改,如下图所示。

    这个支付回调的URL设置的作用是接收用户扫码后微信支付系统发送的数据,根据接收的数据生成商户系统的支付订单返回给微信支付系统,调用【统一下单API】提交支付交易。

    扫码支付参数设置栏目入口

     

    二、生成微信支付二维码

    参考文档https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_6

    二维码长链接示例:

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

    这样太冗长了,转换成短链接 weixin://wxpay/bizpayurl?pr=XXXX

     /**
         * 扫码支付模式一生成二维码
         *
         * @param productId 商品ID
         * @throws IOException
         */
        @GetMapping("M1")
        public Map<String, Object> payone(HttpServletRequest request, String productId) {
            Map<String, Object> data = new HashMap<>();
            String nonce_str = PayHelper.createNonceStr();
            // String product_id = "product_001"; // 推荐根据商品ID生成
            TreeMap<String, Object> packageParams = new TreeMap<>();
            packageParams.put("appid", wechatAccountConfig.getAppid());
            packageParams.put("mch_id", PayConstant.MCH_ID);
            packageParams.put("product_id", productId);
            packageParams.put("time_stamp", PayHelper.createTimeStamp());
            packageParams.put("nonce_str", nonce_str);
            String str_url = PayHelper.createPayImageUrl(packageParams);
            String sign = SignatureUtil.md5Hex(packageParams, PayConstant.API_KEY, null);
            packageParams.put("sign", sign);
            String payurl = "weixin://wxpay/bizpayurl?sign=" + sign + str_url;
            log.debug("payurl is {}", payurl);
            /**** 转成短链接 ****/
            PayShortUrlParams payShortUrlParams = new PayShortUrlParams();
            payShortUrlParams.setAppid(wechatAccountConfig.getAppid());
            payShortUrlParams.setMch_id(PayConstant.MCH_ID);
            payShortUrlParams.setLong_url(payurl);
            payShortUrlParams.setNonce_str(nonce_str);
            String urlSign = SignatureUtil.md5Hex(payShortUrlParams, PayConstant.API_KEY,
                    null);
            payShortUrlParams.setSign(urlSign);
            String longXml = XmlUtil.toXml(payShortUrlParams);
            String shortResult = HttpUtil.doPost(wechatPayConfig.getPayShortUrl(), null,
                    longXml);
            PayShortUrlResult payShortUrlResult = XmlUtil.fromXml(shortResult, PayShortUrlResult.class);
            if (Objects.equals("SUCCESS", payShortUrlResult.getReturn_code())) {
                payurl = payShortUrlResult.getShort_url();
            } else {
                log.debug("错误信息" + payShortUrlResult.getReturn_msg());
            }
            /**** 生成 二维码图片自行实现 ****/
            //定义savePath
            ZXingCodeUtil.toQRCode(payurl, null, "savePath", null);
            return data;
        }
    /**
         * 生成支付二维码URL
         *
         * @param params
         * @return
         */
        public static String createPayImageUrl(TreeMap<String, Object> params) {
            StringBuilder buffer = new StringBuilder();
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (entry.getValue() != null) {
                    buffer.append("&").append(entry.getKey()).append("=").append(entry.getValue());
                }
            }
            return buffer.toString();
        }

     

    三、回调商户支付URL

    接收用户扫码后微信支付系统发送的数据,根据接收的数据生成商户系统的支付订单返回给微信支付系统,调用统一下单API提交支付交易

    package com.phil.wechat.pay.controller;
    
    import com.phil.modules.config.WechatAccountConfig;
    import com.phil.modules.util.HttpUtil;
    import com.phil.modules.util.SignatureUtil;
    import com.phil.modules.util.XmlUtil;
    import com.phil.wechat.pay.config.WechatPayConfig;
    import com.phil.wechat.pay.constant.PayConstant;
    import com.phil.wechat.pay.model.request.PayCallBackParams;
    import com.phil.wechat.pay.model.request.UnifiedOrderParams;
    import com.phil.wechat.pay.model.response.PayCallBackResult;
    import com.phil.wechat.pay.model.response.UnifiedOrderResult;
    import com.phil.wechat.pay.util.PayHelper;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.IOUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedOutputStream;
    import java.util.Objects;
    
    /**
     * 扫码模式一回调
     *
     * @author phil
     * @date 2017年6月27日
     */
    @Controller
    @RequestMapping("api/wxpay")
    @Slf4j
    public class WechatPayCallBackController {
    
        private final WechatPayConfig wechatPayConfig;
    
        private final WechatAccountConfig wechatAccountConfig;
    
        @Autowired
        public WechatPayCallBackController(WechatPayConfig wechatPayConfig, WechatAccountConfig wechatAccountConfig) {
            this.wechatPayConfig = wechatPayConfig;
            this.wechatAccountConfig = wechatAccountConfig;
        }
    
        @RequestMapping("callback") // 仅仅是扫码模式一的
        public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
            String resXml = "";// 反馈给微信服务器
            // 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
            String xml = IOUtils.toString(request.getInputStream());
            // logger.info("微信支付系统发送的数据"+xml);
            /**** 微信支付系统发送的数据其实就是回调地址输入的参数Xml ****/
            // 验证签名
            if (SignatureUtil.checkValidPaySign(xml, PayConstant.API_KEY)) {
                // 转换成输入参数,
                PayCallBackParams payCallBackParams = XmlUtil.fromXml(xml, PayCallBackParams.class);
                // appid openid mch_id is_subscribe nonce_str product_id sign
                // 统一下单
                String openid = payCallBackParams.getOpenid();
                String product_id = payCallBackParams.getProduct_id();
                /**** product_id 等 生成自己系统的订单 ****/
                int total_fee = 1; // 根据product_id算出价格
                String out_trade_no = PayHelper.createOutTradeNo(); // 生成订单号
                String body = product_id; // 商品名称设置为product_id
                String attach = "XXX店"; // 附加数据
                String nonce_str = PayHelper.createNonceStr();
                String spbill_create_ip = HttpUtil.getRemortIP(request);
                // 组装统一下单的请求参数
                UnifiedOrderParams unifiedOrderParams = new UnifiedOrderParams();
    //			unifiedOrderParams.setAppid(WechatConfig.APP_ID);// 必须
    //			unifiedOrderParams.setMch_id(PayConstant.MCH_ID);// 必须
                unifiedOrderParams.setOut_trade_no(out_trade_no);
                unifiedOrderParams.setBody(body);
                unifiedOrderParams.setAttach(attach);
                unifiedOrderParams.setTotal_fee(total_fee);// 必须
                unifiedOrderParams.setNonce_str(nonce_str); // 必须
                unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必须
                unifiedOrderParams.setTrade_type("NATIVE");// 必须
                unifiedOrderParams.setOpenid(openid);
                unifiedOrderParams.setNotify_url(wechatPayConfig.getNotifyUrl()); // 异步通知URL
                // 签名
                String sign = SignatureUtil.md5Hex(unifiedOrderParams, PayConstant.API_KEY, new String[] {"serialVersionUID"});
                unifiedOrderParams.setSign(sign);
                // 统一下单 请求的Xml
                String unifiedXmL = XmlUtil.toXml(unifiedOrderParams);
                // 统一下单 返回的xml
                String unifiedOrderResultXmL = HttpUtil.doPost(
                        wechatPayConfig.getUnifiedOrderUrl(), null, unifiedXmL);
                // 统一下单返回 验证签名
                if (SignatureUtil.checkValidPaySign(unifiedOrderResultXmL, PayConstant.API_KEY)) {
                    UnifiedOrderResult unifiedOrderResult = XmlUtil
                            .fromXml(unifiedOrderResultXmL, UnifiedOrderResult.class);
                    if (Objects.equals("SUCCESS", unifiedOrderResult.getReturn_code())
                            && Objects.equals("SUCCESS", unifiedOrderResult.getResult_code())) {
                        PayCallBackResult payCallBackResult = new PayCallBackResult();
                        payCallBackResult.setReturn_code(unifiedOrderResult.getReturn_code());
                        payCallBackResult.setAppid(wechatAccountConfig.getAppid());
                        payCallBackResult.setMch_id(PayConstant.MCH_ID);
                        payCallBackResult.setNonce_str(unifiedOrderResult.getNonce_str());// 直接用微信返回的
                        /**** prepay_id 2小时内都有效,根据product_id再次支付方法自己写 ****/
                        payCallBackResult.setPrepay_id(unifiedOrderResult.getPrepay_id());
                        payCallBackResult.setResult_code(unifiedOrderResult.getResult_code());
                        String callsign = SignatureUtil.md5Hex(payCallBackResult, PayConstant.API_KEY, null);
                        payCallBackResult.setSign(callsign);
                        resXml = XmlUtil.toXml(payCallBackResult).replace("__", "_");
                        // 将数据包返回给微信支付系统处理
                    }
                } else {
                    log.error("签名验证错误");
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                            + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
                }
            } else {
                log.error("签名验证错误");
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
            }
            try (BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream())) {
                out.write(resXml.getBytes());
                out.flush();
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
    }

    四、完成支付并通知支付结果

    package com.phil.wechat.pay.controller;
    
    import com.phil.modules.result.ResultState;
    import com.phil.modules.util.SignatureUtil;
    import com.phil.modules.util.XmlUtil;
    import com.phil.wechat.pay.model.response.PayNotifyResult;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.IOUtils;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedOutputStream;
    import java.util.Objects;
    
    /**
     * 微信支付结果通知(统一下单参数的notify_url)
     *
     * @author phil
     * @date 2017年6月27日
     */
    @RestController
    @RequestMapping("api/wxpay")
    @Slf4j
    public class WechatPayNotifyController {
    
        @RequestMapping("notify")
        public ResultState notice(HttpServletRequest request, HttpServletResponse response) throws Exception {
            ResultState resultState = new ResultState();
            log.debug("开始处理支付返回的请求");
            String resXml = ""; // 反馈给微信服务器
            String notifyXml = IOUtils.toString(request.getInputStream());// 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
            log.debug("微信支付系统发送的数据" + notifyXml);
            // 验证签名
            if (SignatureUtil.checkValidPaySign(notifyXml, null)) {
                PayNotifyResult notify = XmlUtil.fromXml(notifyXml, PayNotifyResult.class);
                log.debug("支付结果 {}", notify.toString());
                if (Objects.equals("SUCCESS", notify.getResult_code())) {
                    resultState.setErrcode(0);// 表示成功
                    resultState.setErrmsg(notify.getResult_code());
                    /**** 业务逻辑 保存openid之类的 ****/
                    // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                } else {
                    resultState.setErrcode(-1);// 支付失败
                    resultState.setErrmsg(notify.getErr_code_des());
                    log.debug("支付失败,错误信息:" + notify.getErr_code_des());
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA["
                            + notify.getErr_code_des() + "]]></return_msg>" + "</xml> ";
                }
            } else {
                resultState.setErrcode(-1);// 支付失败
                resultState.setErrmsg("签名验证错误");
                log.debug("签名验证错误");
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
            }
            try (BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream())) {
                out.write(resXml.getBytes());out.flush();
            } catch (Exception e) {
    
                log.error(e.getMessage());
            }
            return resultState;
        }
    }

     

    扫一扫加群

     

    展开全文
  • 微信支付是集成在微信客户端的支付功能,用户可以通过手机完成快速的支付流程。微信支付以绑定银行卡的快捷支付为基础,向用户提供安全、快捷、高效的支付服务。 公众号支付 APP支付 扫码支付 刷卡支付 H5支付 ...

    微信支付是集成在微信客户端的支付功能,用户可以通过手机完成快速的支付流程。微信支付以绑定银行卡的快捷支付为基础,向用户提供安全、快捷、高效的支付服务。

    • 公众号支付
    • APP支付
    • 扫码支付
    • 刷卡支付
    • H5支付
    • 小程序支付

    (本文档主要介绍公众号支付和扫码支付)

    注:微信公众号目前必须是服务认证号,且开通微信支付功能。公众号对应的商户号也必须开通支付功能。

    一、公众号支付

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

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

    1.  设置JSSDK接口调用域名

    先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。  

    2.  设置授权域名

    开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。如下图:

     

    3.  设置支付目录

    请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付。

      在微信商户平台(pay.weixin.qq.com)设置您的公众号支付支付目录,设置路径:商户平台-->产品中心-->开发配置,如下图所示。公众号支付在请求支付的时候会校验请求来源是否有在商户平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。

    注意:这三个地方的域名需使用字母、数字及“-”的组合,不支持IP地址、端口号及短链域名,且填写的域名须通过ICP备案的验证。

    4.   设置支付密钥app_key

    点击帐户中心——API安全——设置API密钥(用于支付是的验签)

    5.   获取openid

      由于openid必须为当前用户在当前支付公众号内的身份标识,所以在智慧校园项目中采取实时获取的方式获得。

      步骤一:用户同意授权,获取code

      访问以下链接获得带code参数的Url:

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

      参数说明

    参数

    是否必须

    说明

    appid

    公众号的唯一标识

    redirect_uri

    授权后重定向的回调链接地址,请使用urlencode对链接进行处理

    response_type

    返回类型,请填写code

    scope

    应用授权作用域,snsapi_base(不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)

    state

    重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节

    #wechat_redirect

    无论直接打开还是做页面302重定向时候,必须带此参数

    注:回调链接一定要urlencode,不然识别不出

      步骤二:通过code换取网页授权access_token

      首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。

      获取code后,请求以下链接获取access_token:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

      参数说明

    参数

    是否必须

    说明

    appid

    公众号的唯一标识

    secret

    公众号的appsecret

    code

    填写第一步获取的code参数

    grant_type

    填写为authorization_code

      返回JSON数据包如下:

    复制代码

    {
       "access_token":"ACCESS_TOKEN",
       "expires_in":7200,
       "refresh_token":"REFRESH_TOKEN",
       "openid":"OPENID",
       "scope":"SCOPE",
       "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL" 
    }

    复制代码

      附:关于网页授权access_token和普通access_token的区别

      1、微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息;

      2、其他微信接口,如发微信消息接口,需要通过基础支持中的“获取access_token”接口(https://api.weixin.qq.com/cgi-bin/token)来获取到的普通access_token调用。

    -----------------------------上述5个步骤都为支付的准备步骤。-----------------------------

      支付业务流程及步骤如下图:

     

    6.   微信内H5调起支付

      步骤一:后台统一下单获取jsapi支付参数

      请求地址:https://api.mch.weixin.qq.com/pay/unifiedorder

      请求参数:   appid(公众账号ID),

    mch_id(商户号),

    device_info(设备号),

    nonce_str(随机字符串),

    sign(签名),

    sign_type(签名类型),

    body(商品描述) ,

    detail(商品详情),

    attach(附加数据),

    out_trade_no(商户订单号),

    fee_type(标价币种),

    total_fee(标价金额),

    spbill_create_ip(终端IP),

    time_start(交易起始时间),

    time_expire(交易结束时间),

    notify_url(支付回调地址),

    trade_type(交易类型),

    product_id(商品ID),

    opened(用户标识)

       注:参数值用XML转义即可,CDATA标签用于说明数据不被XML解析器解析。

       具体下单过程可参照微信支付Demo:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

      当请求成功,返回的XML中return_code 和result_code都为SUCCESS时会返回如下:

    复制代码

    <xml>
       <return_code><![CDATA[SUCCESS]]></return_code>
       <return_msg><![CDATA[OK]]></return_msg>
       <appid><![CDATA[wx3556f3b4523gf12r]]></appid>
       <mch_id><![CDATA[10000100]]></mch_id>
       <nonce_str><![CDATA[jfjsdh47fin0jdnv]]></nonce_str>
       <openid><![CDATA[oECxywCpJW8G-ktn3lwZ7MR4Cx2Q]]></openid>
       <sign><![CDATA[8SDVCNKDF89W32SDKCSAA0S32JKSDJJL]]></sign>
       <result_code><![CDATA[SUCCESS]]></result_code>
       <prepay_id><![CDATA[wx201801041172356eidnvnd9qk398324745]]></prepay_id>
       <trade_type><![CDATA[JSAPI]]></trade_type>
    </xml>

    复制代码

      步骤二:H5发起微信支付

      在微信浏览器里面打开H5网页中执行JS调起支付。接口输入输出数据格式为JSON。

      注意:WeixinJSBridge内置对象在其他浏览器中无效,参数名区分大小,大小写错误签名验证会失败。

    引用js:http://res.wx.qq.com/open/js/jweixin-1.0.0.js

      调用支付js如下:

    复制代码

    function onBridgeReady(){
       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" ) {}     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 
           }
       ); 
    }
    if (typeof WeixinJSBridge == "undefined"){
       if( document.addEventListener ){
           document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
       }else if (document.attachEvent){
           document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
           document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
       }
    }else{
       onBridgeReady();
    }

    复制代码

      支付完成后,微信会将订单参数会以XML方式发送给商户系统回调地址,商户在回调地址接收参数并做相应处理。

      回调的XML格式为:

    复制代码

    <xml>
      <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
      <attach><![CDATA[支付测试]]></attach>
      <bank_type><![CDATA[CFT]]></bank_type>
      <fee_type><![CDATA[CNY]]></fee_type>
      <is_subscribe><![CDATA[Y]]></is_subscribe>
      <mch_id><![CDATA[10000100]]></mch_id>
      <nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
      <openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
      <out_trade_no><![CDATA[1409811653]]></out_trade_no>
      <result_code><![CDATA[SUCCESS]]></result_code>
      <return_code><![CDATA[SUCCESS]]></return_code>
      <sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
      <sub_mch_id><![CDATA[10000100]]></sub_mch_id>
      <time_end><![CDATA[20140903131540]]></time_end>
      <total_fee>1</total_fee>
      <trade_type><![CDATA[JSAPI]]></trade_type>
      <transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
    </xml>

    复制代码

    推荐技术文档:

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

    2.https://wenku.baidu.com/view/cebde8a764ce0508763231126edb6f1aff007185.html?re=view

     

    二、扫码支付

      扫码支付可分为两种模式,商户根据支付场景选择相应模式。

      【模式一】:

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

      商户支付回调URL设置指引:进入商户平台-->产品中心-->开发配置,进行配置和修改,如下图所示。

      【模式二】:

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

      流程图:

      

      业务流程说明:

    (1)商户后台系统根据用户选购的商品生成订单。

    (2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;

    (3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。

    (4)商户后台系统根据返回的code_url生成二维码。

    (5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。

    (6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。

    (7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。

    (8)微信支付系统根据用户授权完成支付交易。

    (9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。

    (10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。

    (11)未收到支付通知的情况,商户后台系统调用【查询订单API】。

    (12)商户确认订单已支付后给用户发货。

      附:

      前端页面代码:

    //前端页面
    <img src=" MakeQRCode?code_url=@code_url" alt="二维码"/>

       C#生成二维码后台代码:

    复制代码

        //生成二维码方法  
         public FileResult MakeQRCode(string code_url)
            {
                if (string.IsNullOrEmpty(code_url))
                    throw new ArgumentException("code_url");
                //初始化二维码生成工具
                QRCodeEncoder qrCodeEncoder = new QRCodeEncoder();
                qrCodeEncoder.QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE;
                qrCodeEncoder.QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.M;
                qrCodeEncoder.QRCodeVersion = 0;
                qrCodeEncoder.QRCodeScale = 4;
                //将字符串生成二维码图片
                Bitmap image = qrCodeEncoder.Encode(code_url, Encoding.Default);
                //保存为PNG到内存流  
                MemoryStream ms = new MemoryStream();
                image.Save(ms, ImageFormat.Jpeg);
                return File(ms.ToArray(), "image/jpeg");
            }

    复制代码

      推荐技术文档:

    1. https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1
    2. http://www.cnblogs.com/vinsonLu/p/5166214.html
    展开全文
  • 微信支付之Native扫码支付功能

    千次阅读 2018-12-11 09:58:51
    作者:陈惠,叩丁狼教育高级讲师。原创文章,转载请注明出处。 上一篇微信支付文章:...那么本篇文章,会以另外一种方式实现,使用扫码的方式来进行支付。 需要注意的是,扫码支付分两种形式: 线下的扫码...
  • JAVA微信扫码支付模式一功能实现

    万次阅读 多人点赞 2016-09-27 12:58:33
    一、准备工作4月份那会发了篇关于 JAVA微信扫码支付模式二功能实现的博客,无数人来追问模式一的开发,所以在这就贴出来,仅供参考。关于模式一和模式二的区别,我有解释过很多次,无非就是模式一的二维码是针对...
  • 微信支付教程系列之扫码支付

    千次阅读 2018-07-19 10:38:08
    今天,我们来一起探讨一下这个微信扫码支付。何为扫码支付呢?这里面,扫的码就是二维码了,就是我们经常扫一扫的那种二维码图片,例如,我们自己添加好友的时候,可以通过输入对方的微信号,也可以扫一扫对方的...
  • 此项目已开源欢迎Start、PR、发起Issues一起讨论交流共同进步 ...微信极速开发系列文章:http://www.jianshu.com/p/a172a1b69fdd 上一篇文章介绍了微信提供的那些支付方式以及公众号支付http://www.jiansh...
  • 前提:自己测试的话,需给项目映射一个80端口的域名服务,微信识别80端口。需要在公众平台后台认证网页授权域名。 后面介绍如何配置的网页授权域名 1:生成一个二维码,以供扫描使用,我这里用的是Qrcode生成的...
  • 【标题】微信-推送公众号扫码关注信息(附源码) 【前言】 最近在做一个需求,也是公众号常用的功能-《扫码点餐》。 在店铺桌子贴一张固定二维码,用户扫码后进入公众号窗口①,推送模板消息(点餐消息)给用户...
  • 一、扫码支付介绍 扫码支付可分为两种模式,商户根据支付场景选择相应模式。 【模式一】: 商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信...
  • Java调用微信扫码支付接口(模式二)

    千次阅读 热门讨论 2019-04-12 17:01:29
    前些天帮朋友实现了一个微信扫码的接口,在之前也是不会搞这个东西,抱着试试的心态就开始看各种文档和blogs,大多数人都在吐槽微信给的java调用微信接口文档太含糊,而且网上的查到的资料也不详细,只有大概的开发...
  • Java微信支付开发扫码支付模式二

    千次阅读 2019-12-20 23:08:20
    官方文档 ...借鉴了很多大神的文章,在此先谢过了 大体过程:根据固定金额和商品的ID先生成订单,再生成二维码,客户付款 模式二支付的流程如下图,可以说是最简单的支付方式了 ...(3)微信支付系统收到请...
  • C# MVC 微信支付教程系列之扫码支付

    千次阅读 2017-08-02 10:26:29
    微信官方的文档,这个扫码支付(NativePay)分为两种,一种是“生成扫描支付模式”,另外一种是“生成直接支付url,支付url有效期为2小时”,至于这里面,两种扫码模式,怎么灵活利用呢,官方也没有一个明确的说明。...
  • JAVA PC端扫码支付(一)微信支付

    千次阅读 2019-08-28 17:41:34
    微信支付从配置到开发 一、配置 1、开通公众平台支付功能 商户号 微信支付功能先要申请微信(企业)公众平台,然后开通企业...因为微信公众平台调整,公众平台微信支付公众号支付授权目录、扫码支付回调URL配置...
  • 微信扫码分析

    2019-07-25 14:41:11
    业务流程说明:(1)商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。(2)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。(3)微信支付系统收到客户端...
  • 我有一个网站,网站上有很多作者分享自己的作品,如果其他用户浏览作品后想联系这位作者,就需要扫码关注我们的平台,我们会推送对方的联系方式。 第一步,生成带参数的二维码 步骤: 1.用微信appid和appsecret...
  • 2. 能够说出微信支付开发的整体思路 3. 能够调用微信支付接口(统一下单)生成支付二维码 4. 能够调用微信支付接口(查询订单)查询支付状态 5. 实现支付日志的生成与订单状态的修改 二维码 什么是二维码...
  • 微信现在接口现在越来越严格了,每出点新功能都要各种验证,而且接口调用还不固定,现在就一家独大程序员只能各种忍了。 这次开发没有采用微信推荐的OAuth2.0协议方式实现微信扫码登陆,OAuth2.0协议要求比较多,...
1 2 3 4 5 ... 20
收藏数 1,047
精华内容 418
关键字:

微信开发接收固定扫码钱