生成付款二维码
一、微信网站扫码支付介绍
1、扫码支付文档
微信开发官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
有关微信支付的流程图微信官方已经说的很清楚了,这里也无需其它解释。这边采用微信支付扫码模式二(不依赖商户平台设置回调url),所以在生成二维码之前
要先调用微信统一下单支付接口,获得code_url,再通过谷歌二维码工具将code_url生成二维码图片。
2、名称理解
在微信扫码支付功能开发之前,首先要获得微信认证而得到的一些信息如下:
开发者账户信息
公众号 appid: wx0pi2m4x6we76140w
公众号 appsecret: x82552d8w0y1i161lp9o7821s5d7osry
商户号 mer_id: 5731202714
支付 key: PL9wT9n9Ljav4zSN66J0bmzT1Yl54429
3、和微信支付交互方式
(1)、post方式提交
(2)、xml格式的协议
(3)、签名算法MD5
(4)、接口交易单位为 分
(5)、交易类型:JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
(6)、商户订单号规则:
商户支付的订单号由商户自定义生成,仅支持使用字母、数字、中划线-、下划线_、竖线|、星号*这些英文半角字符的组合,请勿使用汉字或全角等特殊字符,
微信支付要求商户订单号保持唯一性
(7)、安全规范:
签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
校验工具:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1
(8)、采用微信支付扫码模式二(不依赖商户平台设置回调url)
二、实战开发
1、点击购买商品调用接口
接口需要接收购买商品ID,用户ID,返回的就是二维码图片供用户扫码支付。
/**
* 订单接口
*/
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
@Autowired
private VideoOrderService videoOrderService;
/**
* 用户点击购买下单接口
*/
@GetMapping("buy")
public void saveOrder(@RequestParam(value = "video_id",required = true)int videoId,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
/**
* 实际开发需要获取用户id和用户当前ip,这里临时写死的配置
* String ip = IpUtils.getIpAddr(request);
* int userId = request.getAttribute("user_id");
*/
int userId = 1;
String ip = "120.25.1.43";
//1、根据用户id和商品id生成订单
VideoOrderDto videoOrderDto = new VideoOrderDto();
videoOrderDto.setUserId(userId);
videoOrderDto.setVideoId(videoId);
videoOrderDto.setIp(ip);
//2、保存订单同时返回codeUrl
String codeUrl = videoOrderService.save(videoOrderDto);
if(codeUrl == null) {
throw new NullPointerException();
}
//3、通过google工具生成二维码供用户扫码支付
try{
//3、1生成二维码配置
Map hints = new HashMap();
//3、2设置纠错等级
hints.put(EncodeHintType.ERROR_CORRECTION,ErrorCorrectionLevel.L);
//3、3编码类型
hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
BitMatrix bitMatrix = new MultiFormatWriter().encode(codeUrl,BarcodeFormat.QR_CODE,400,400,hints);
OutputStream out = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix,"png",out);
}catch (Exception e){
e.printStackTrace();
}
}
}
2、进入VideoOrderService具体业务实现类
该类的主要业务逻辑是:
(1)通过商品ID查询是否有该商品信息
(2)通过用户ID查询是否存在该用户
(3)如果上面两步没有问题,则生成用户订单信息保存到数据库中
@Override
@Transactional(propagation = Propagation.REQUIRED)
public String save(VideoOrderDto videoOrderDto) throws Exception {
//1、查找商品信息(这里商品指的是视频课程)
Video video = videoMapper.findById(videoOrderDto.getVideoId());
//2、查找用户信息
User user = userMapper.findByid(videoOrderDto.getUserId());
//3、生成订单,插入数据库
VideoOrder videoOrder = new VideoOrder();
videoOrder.setTotalFee(video.getPrice());
videoOrder.setVideoImg(video.getCoverImg());
videoOrder.setVideoTitle(video.getTitle());
videoOrder.setCreateTime(new Date());
videoOrder.setVideoId(video.getId());
videoOrder.setState(0);
videoOrder.setUserId(user.getId());
videoOrder.setHeadImg(user.getHeadImg());
videoOrder.setNickname(user.getName());
videoOrder.setDel(0);
videoOrder.setIp(videoOrderDto.getIp());
videoOrder.setOutTradeNo(CommonUtils.generateUUID());
videoOrderMapper.insert(videoOrder);
//4、获取codeurl
String codeUrl = unifiedOrder(videoOrder);
return codeUrl;
}
3、回调微信统一下单接口,获取codeurl方法主要逻辑
微信官方统一下单接口文档说明: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
(1) 根据接口需求添加所需参数:比如appid,mch_id,body等等......
(2)sign签名获取:具体获取规则官方已经说明: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
(3) 通过工具类将map集合转为xml格式字符串
(4)回调微信统一下单接口,接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
(5)如果上一步成功(成功标志返回SUSSCUSS),则将返回成功的xml格式再通过工具类转为map
(6)通过key=code_url,获取value字符串,这也是最终生成二维码的字符串。code_url格式大致为:weixin://wxpay/s/An4baqw
接下来只要将code_url值变成二维码就可以供用户扫码付款了。
4、测试
调用http://localhost:8081/api/v1/order/buy?video_id=1接口
成功返回二维码:code_url有效期是两个小时
![](https://img2018.cnblogs.com/blog/1090617/201809/1090617-20180926230511616-1787872663.png)
微信扫码之后:看到具体信息
![](https://img2018.cnblogs.com/blog/1090617/201910/1090617-20191030112705038-578980582.png)
再看数据库该商品信息:
![](https://img2018.cnblogs.com/blog/1090617/201809/1090617-20180926230947569-189443587.png)
用户扫码支付成功,微信异步回调商户
上一篇博客完成用户扫码支付功能: https://www.cnblogs.com/qdhxhz/p/9708534.html
当用户扫码支付成功之后,微信会异步回调商户接口,告知用户支付成功。好让商户进行下一步操作。
一、接口说明
1、流程图
这里要做的就是用户支付成功后,微信异步通知商户支付结果,商户收到通知后告知支付通知接收情况。
![](https://img2018.cnblogs.com/blog/1090617/201809/1090617-20180927225845637-241415423.png)
2、接口说明
有关商户接口应注意以下几点:
(1)该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
(2)notify_url不能有参数,外网可以直接访问,不能有访问控制(比如必须要登录才能操作)。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
(3)支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
(4)对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不
保证通知最终能成功。(通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理
重复的通知。推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
(5)特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。
二、接口开发
1、回调接口
有关ngrok工具如果不了解的话,可以参考博客: https://www.cnblogs.com/qdhxhz/p/9678137.html
/**
* 微信支付回调
* 这里在统一下单中提供的notify_url地址是:http://jincou.vipgz1.idcfengye.com/api/v1/order/callback
* 该域名是sunny-ngrok中的二级域名,通过它映射到微信本地
*/
@RequestMapping("callback")
public void orderCallback(HttpServletRequest request,HttpServletResponse response) throws Exception {
InputStream inputStream = request.getInputStream();
//BufferedReader是包装设计模式,性能更搞
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
StringBuffer sb = new StringBuffer();
//1、将微信回调信息转为字符串
String line ;
while ((line = in.readLine()) != null){
sb.append(line);
}
in.close();
inputStream.close();
//2、将xml格式字符串格式转为map集合
Map callbackMap = WXPayUtil.xmlToMap(sb.toString());
System.out.println(callbackMap.toString());
//3、转为有序的map
SortedMap sortedMap = WXPayUtil.getSortedMap(callbackMap);
//4、判断签名是否正确
if(WXPayUtil.isCorrectSign(sortedMap,weChatConfig.getKey())){
//5、判断回调信息是否成功
if("SUCCESS".equals(sortedMap.get("result_code"))){
//获取商户订单号
//商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一
String outTradeNo = sortedMap.get("out_trade_no");
System.out.println(outTradeNo);
//6、数据库查找订单,如果存在则根据订单号更新该订单
VideoOrder dbVideoOrder = videoOrderService.findByOutTradeNo(outTradeNo);
System.out.println(dbVideoOrder);
if(dbVideoOrder != null && dbVideoOrder.getState()==0){ //判断逻辑看业务场景
VideoOrder videoOrder = new VideoOrder();
videoOrder.setOpenid(sortedMap.get("openid"));
videoOrder.setOutTradeNo(outTradeNo);
videoOrder.setNotifyTime(new Date());
//修改支付状态,之前生成的订单支付状态是未支付,这里表面已经支付成功的订单
videoOrder.setState(1);
//根据商户订单号更新订单
int rows = videoOrderService.updateVideoOderByOutTradeNo(videoOrder);
System.out.println(rows);
//7、通知微信订单处理成功
if(rows == 0){
response.setContentType("text/xml");
response.getWriter().println("success");
return;
}
}}
}
//7、通知微信订单处理失败
response.setContentType("text/xml");
response.getWriter().println("fail");
}
2、校验签名方法
/**
* 校验签名
*/
public static boolean isCorrectSign(SortedMap params, String key){
//1、再进行一次生成sign
String sign = createSign(params,key);
String weixinPaySign = params.get("sign").toUpperCase();
//将两次生成的sign比较看是否一致
return weixinPaySign.equals(sign);
}
/**
* 生成微信支付sign
*/
public static String createSign(SortedMap params, String key){
StringBuilder sb = new StringBuilder();
Set es = params.entrySet();
Iterator it = es.iterator();
//生成 stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
while (it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key=").append(key);
String sign = CommonUtils.MD5(sb.toString()).toUpperCase();
return sign;
}
3、测试
(1)支付成功
(2)通过ngrok回调到本地,通过断点可以看出sb字符串格式
![](https://img2018.cnblogs.com/blog/1090617/201809/1090617-20180927230721977-407348669.png)
(3)将xml格式字符串转为map
![](https://img2018.cnblogs.com/blog/1090617/201809/1090617-20180927231019241-910799737.png)
成功!
|