微信开发之扫码支付

此项目已开源欢迎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设置指引:进入公众平台–>微信支付–>开发配置–>扫码支付–>修改 如下图(来自官方文档)

开源项目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、企业转账

时间: 2024-09-17 17:49:29

微信开发之扫码支付的相关文章

微信支付开发(2) 扫码支付模式一

关键字:微信支付 微信支付v3 native支付 扫码支付模式一 统一支付 Native支付 prepay_id 作者:方倍工作室原文: http://www.cnblogs.com/txw1958/p/wxpayv3-native-static.html   本文介绍微信支付下的基于静态链接二维码的Native支付实现流程.目前该方法被改称为扫码支付模式一. 注意 微信支付现在分为v2版和v3版,2014年9月10号之前申请的为v2版,之后申请的为v3版.V3版的微信支付没有paySignKe

微信支付开发(4) 扫码支付模式二

关键字:微信支付 微信支付v3 动态native支付 扫码支付模式二 统一支付 Native支付 prepay_id 作者:方倍工作室原文: http://www.cnblogs.com/txw1958/p/wxpayv3_native_dynamic_qrcode.html    本文介绍微信支付下的基于动态链接二维码的Native支付实现流程.目前该方法被改为扫码支付模式二. 注意 微信支付现在分为v2版和v3版,2014年9月10号之前申请的为v2版,之后申请的为v3版.V3版的微信支付没

php,微信扫码支付开发,在部分电脑上测试,出现invalid spbill_create_ip错误

问题描述 php,微信扫码支付开发,在部分电脑上测试,出现invalid spbill_create_ip错误 最近在做支付的开发,微信网页扫码支付,语言是php. 在本地开发和测试很顺利,在大部分电脑上测试也正常.但是在某几台电脑上测试时,执行到微信统一下单一步时,会返回错误信息"invalid spbill_create_ip". 试了两种方案: 1.用$ip = Request::getClientIp(true);获取IP. 输出的$ip变量是string '::1' (len

求微信扫码支付开发的流程或细节说明

问题描述 各位好!目前正打算用winform帮客户做微信扫码支付.公众号已开通且已认证,想实现:出了一个微信二位码,用户照一下后用户手机上显示商品名称和价格,客户完成支付,界面收到回馈跳转显示付款成功!不知流程和具体的开发步骤,请教各位! 解决方案 解决方案二:微信支付平台SDK上面有全套的.net源代码,扫码支付很简单,而且几乎你只要改几个参数就能跑起来.有关签名.与腾讯服务平台的交互等等后台操作,你都可以照搬.解决方案三:虽然文档上说是"微信内网页支付",但是其实扫码支付是一个简单

asp.net 微信扫码支付 回调处理的问题

问题描述 asp.net 微信扫码支付 回调处理的问题 我使用开发模式二,先调用统一下单接口生成订单,然后在回调地址里面处理更新订单状态以及商品库存的逻辑,最后通知微信支付完成. 下载了官方的demo,发现回调方法里面的代码是这样的. public override void ProcessNotify() { WxPayData notifyData = GetNotifyData(); //检查openid和product_id是否返回 if (!notifyData.IsSet("open

微信原生支付 Native扫码支付( V3.3.7 版本)

原文:微信原生支付 Native扫码支付( V3.3.7 版本) [尊重别人的劳动成果,转载请注明出处:一缕晨光工作室,www.wispdawn.com] 前言 辛苦研究三天,遇到各种困难,最终还是克服了,把我的理解和思路分享给需要帮助的人,如果你觉的好,请帮我分享一下,谢谢. 在没有做之前,我以为和支付宝,以及银联一样,会在官网找到相应的demo,照葫芦画瓢即可,没有什么复杂的,真正去做的时候,发现各种错误,很多时候都莫名其妙找不到北, 在网上搜了不知道多少遍,有V3版的,不过都是js api

JAVA微信扫码支付模式二线上支付功能实现以及回调_java

 一.准备工作 首先吐槽一下微信关于支付这块,本身支持的支付模式就好几种,但是官方文档特别零散,连像样的Java相关的demo也没几个.本人之前没有搞过微信支付,一开始真是被它搞晕,折腾两天终于调通了,特此写下来,以享后人吧! 关于准备工作,就"微信扫码支付模式二"官方文档地址在这 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 可以先看看,实际上需要准备的东西有以下几个: 其中APP_ID和APP_SECRE

微信扫码支付原生Native支付 java版 php版

java版微信扫码支付 账号配置参数生成等请参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 微信扫码支付.简单来说,就是你把微信支付需要的信息,生成到二维码图片中.通过微信扫一扫,发起支付.我们需要做的就是二件事:一是:按照微信扫码支付规则生成二维码信息.二是:微信没有提供生成二维码图片的接口.需要我们自己把二维码信息生成到二维码图片中. 1.模式选择: 微信扫码支付,有两种模式,文档中有介绍.第二种模式,

Java SpringMVC实现PC端网页微信扫码支付(完整版)_java

一:前期微信支付扫盲知识 前提条件是已经有申请了微信支付功能的公众号,然后我们需要得到公众号APPID和微信商户号,这个分别在微信公众号和微信支付商家平台上面可以发现.其实在你申请成功支付功能之后,微信会通过邮件把Mail转给你的,有了这些信息之后,我们就可以去微信支付服务支持页面:https://pay.weixin.qq.com/service_provider/index.shtml 打开这个页面,点击右上方的链接[开发文档]会进入到API文档说明页面,看起来如下 选择红色圆圈的扫码支付就