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

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

上一篇文章介绍了微信提供的那些支付方式以及公众号支付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中 扫码支付模式一 生成二维码规则封装如下:

代码语言:javascript复制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 packageParams = new HashMap();

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

代码语言:javascript复制

3.2.1

com.google.zxing

core

${zxing.version}

com.google.zxing

javase

${zxing.version}

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

代码语言:javascript复制/**

* 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 hints = new HashMap();

// 指定纠错等级

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 hints = new Hashtable();

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

代码语言:javascript复制/**

* 生成支付二维码(模式一)并在页面上显示

*/

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 部分代码如下

代码语言:javascript复制

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

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

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

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

代码语言:javascript复制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);代码语言:javascript复制

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

代码语言:javascript复制prepay_xml>>>

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

代码语言:javascript复制/**

* 发送信息给微信服务器

*/

Map 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 prepayParams = new HashMap();

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

代码语言:javascript复制

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

代码语言:javascript复制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、企业转账

✨ 相关推荐

衣服上油渍去不掉了怎么办?可以用洗衣液去掉吗?
弭怎么读
h365官方登录平台

弭怎么读

📅 01-17 👀 6448
李严,魏延,关平三人武艺如何排名,谁才是蜀汉的第六虎将?