赞
踩
支付功能的接入是一个网站很关键的功能:快速、便捷、安全。
用二维码用作支付识别:
L级(低) 7%的码字可以被恢复。
M级(中) 的码字的 15%可以被恢复。
Q 级(四分)的码字的 25%可以被恢复。
H 级(高) 的码字的 30%可以被恢复。 (一般用这个,使用这个识别率高)
1.4 二维码生成插件 qrious:
qrious 是一款基于 HTML5 Canvas 的纯 JS 二维码生成插件。通过 qrious.js 可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行 Base64 编码。
qrious.js 二维码插件的可用配置参数如下:
参数 | 类型 | 默认值 | 描述 |
background | String | "white" | 二维码的背景颜色。 |
foreground | String | "black" | 二维码的前景颜色。 |
level | String | "L" | 二维码的误差校正级别(L, M, Q, H)。 |
mime | String | "image/png" | 二维码输出为图片时的 MIME 类型。 |
size | Number | 100 | 二维码的尺寸,单位像素。 |
value | String | "" | 需要编码为二维码的值 |
下面的代码即可生成一张二维码
- <html>
-
- <head>
-
- <title>二维码入门小 demo</title>
-
- </head>
-
- <body>
-
- <img id="qrious">
-
- <script src="qrious.min.js"></script>
-
- <script> var qr = new QRious({ element:document.getElementById('qrious'), size:250, level:'H', value:'http://www.itcast.cn'
-
- });
-
- </script>
-
- </body>
-
- </html>

前端主要是通过插件对二维码的生成,微信支付接口主要是获得其中代表的数据,url是微信返回来的,二维码生成是插件生成的!
微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于 PC 网站支付、实体店单品或订单支付、媒体广告支付等场景。
https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml
主要是看api列表和支付模式的时序图。
看模式二的流程时序图,在调用统一下单api返回的是预支付订单的链接url.
微信支付接口调用的整体思路:
按 API 要求组装参数,以 XML 方式发送(POST)给微信支付接口(URL),微信支付接口也是以 XML 方式给予响应。程序根据返回的结果(其中包括支付 URL)生成二维码或判断订单状态。
在线微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/index.html
其实是用于简化开发的小工具包,并不是完全封装,比如对XML的转换以及签名的一个算法等。只是提供一个小工具,这是可选的。
微信支付提供了 SDK, 大家下载后打开源码,install 到本地仓库。
微信sdk安装到maven仓库:
https://zhuanlan.zhihu.com/p/33543731
使用微信支付 SDK,在 maven 工程中引入依赖
- <dependency>
- <groupId>com.github.wxpay</groupId>
- <artifactId>wxpay-sdk</artifactId>
- <version>0.0.3</version>
- </dependency>
我们主要会用到微信支付 SDK 的以下功能:
WXPayUtil.generateNonceStr()
WXPayUtil.generateSignedXml(param, partnerkey)
WXPayUtil.xmlToMap(result)
HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
HttpClient 通俗的讲就是模拟了浏览器的行为,在后端向某一地址提交数据获取结果。
这里为了简化 HttpClient 的使用,提供了工具类 HttpClient(对原生 HttpClient 进行了封装)
HttpClient 工具类使用的步骤
- HttpClient client=new HttpClient(请求的url地址);
- client.setHttps(true);//是否是https协议
- client.setXmlParam(xmlParam);//发送的xml数据
- client.post();//执行post请求
- String result = client.getContent(); //获取结果
2.5 工程搭建与准备工作
- <dependency>
- <groupId>com.github.wxpay</groupId>
- <artifactId>wxpay-sdk</artifactId>
- <version>0.0.3</version> </dependency>
添加 tomcat 插件,运行端口为 9000 添加 spring 配置文件 ,参见其它服务工程
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- </dependency>
添加配置文件 weixinpay.properties
- appid=wx8397f8696b538317
- partner=1473426802
- partnerkey=T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
- notifyurl=http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify
appid: 微信公众账号或开放平台 APP 的唯一标识
partner:财付通平台的商户账号
partnerkey:财付通平台的商户密钥
notifyurl: 回调地址 给一个符合规范,随便自己定义,在模式二的情况下并没有用到,但必须要给
3.1.1 需求分析
在支付页面上生成支付二维码,并显示订单号和金额用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付
3.1.2 实现思路
我们通过 HttpClient 工具类实现对远程支付接口的调用。
接口链接:
https://api.mch.weixin.qq.com/pay/unifiedorder
具体参数参见“统一下单”API, 构建参数发送给统一下单的 url ,返回的信息中有支付 url,根据 url 生成二维码,显示的订单号和金额也在返回的信息中。
3.2.1 服务接口层
(1)在 pinyougou-pay-interface 创建包 com.pinyougou.pay.service ,包下建立接口
- package com.pinyougou.pay.service;
- import java.util.Map;
- /**
- *微信支付接口
- *@author Administrator
- * */
- public interface WeixinPayService {
-
- /**
- *生成微信支付二维码
- *@param out_trade_no 订单号
- *@param total_fee 金额(分)
- *@return
- */
- public Map createNative(String out_trade_no,String total_fee);
- }

3.2.2 服务实现层
pinyougou-pay-service 创建 com.pinyougou.pay.service.impl 包,新建类
- @Service
- public class WeixinPayServiceImpl implements WeixinPayService {
- @Value("${appid}")
- private String appid;
-
- @Value("${partner}")
- private String partner;
-
- @Value("${partnerkey}")
- private String partnerkey;
-
- /**
- *生成二维码
- *@return
- */
- public Map createNative(String out_trade_no,String total_fee){
- //1.创建参数
- Map<String,String> param=new HashMap();//创建参数
- param.put("appid", appid);//公众号
- param.put("mch_id", partner);//商户号
-
- param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
- param.put("body", "品优购");//商品描述
- param.put("out_trade_no", out_trade_no);//商户订单号
- param.put("total_fee",total_fee);//总金额(分)
- param.put("spbill_create_ip", "127.0.0.1");//IP
- param.put("notify_url", "http://test.itcast.cn");//回调地址(随便写)
- param.put("trade_type", "NATIVE");//交易类型
- try {
- //2.生成要发送的xml
- String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
- System.out.println(xmlParam);
- HttpClient client=new
- HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
- client.setHttps(true);
- client.setXmlParam(xmlParam);
- client.post();
- //3.获得结果
- String result = client.getContent();
- System.out.println(result);
- Map<String, String> resultMap = WXPayUtil.xmlToMap(result); Map<String, String> map=new HashMap<>();
- map.put("code_url", resultMap.get("code_url"));//支付地址
- map.put("total_fee", total_fee);//总金额
- map.put("out_trade_no",out_trade_no);//订单号
-
-
-
-
-
-
-
-
- return map;
- } catch (Exception e) {
- e.printStackTrace();
- return new HashMap<>();
- }
-
- } }

3.2.3 控制层
pinyougou-cart-web 创建 PayController.java
- /**
- *支付控制层
- *@author Administrator
- *
- */
- @RestController @RequestMapping("/pay")
- public class PayController { @Reference
- private WeixinPayService weixinPayService;
-
- /**
- *生成二维码
-
-
-
-
-
-
-
- } * @return
- */
- @RequestMapping("/createNative")
- public Map createNative(){
- IdWorker idworker=new IdWorker();
- return weixinPayService.createNative(idworker.nextId()+"","1"); }

这里我们订单号通过分布式 ID 生成器生成,金额暂时写死,后续开发我们再对接业务系统得到订单号和金额浏
IdWorker
https://www.cnblogs.com/haoxinyue/p/5208136.html
https://www.cnblogs.com/cs99lzzs/p/9869414.html
览器测试
3.3.1 服务层
在 pinyougou-cart-web 创建 payService.js
- app.service('payService',function($http){
- //本地支付
- this.createNative=function(){
- return $http.get('pay/createNative.do');
- }
- });
3.3.2 控制层
在 pinyougou-cart-web 创建 payController.js
- app.controller('payController' ,function($scope ,payService){
- 本地生成二维码
- $scope.createNative=function(){
- payService.createNative().success(
- function(response){
- $scope.money= (response.total_fee/100).toFixed(2) ;
- $scope.out_trade_no= response.out_trade_no;//订单号
- //二维码
- var qr = new QRious({
- element:document.getElementById('qrious'),
- size:250,
- level:'H',
- value:response.code_url
- }); //金额
-
- ); }
-
- }
- });

3.3.3 页面
修改 pay.html ,引入 js
- <script type="text/javascript" src="plugins/angularjs/angular.min.js"> </script>
- <script type="text/javascript" src="js/base.js"> </script>
- <script type="text/javascript" src="js/service/payService.js"> </script>
- <script type="text/javascript" src="js/controller/payController.js"> </script>
- <script type="text/javascript" src="plugins/qrious.min.js"></script>
指令
<body ng-app="pinyougou" ng-controller="payController" ng-init="createNative()">
设置二维码图片的 ID
- <p class="red"></p>
- <div class="fl code">
- <img id="qrious">
- <div class="saosao">
- <p>请使用微信扫一扫</p>
- <p>扫描二维码支付</p>
- </div>
- </div>
显示订单号
订单号:{{out_trade_no}}
显示金额
<em class="orange money">¥{{money}}</em>元
用户是否支付成功,要查看商户是否收到钱的测试
4.1.1 需求分析
当用户支付成功后跳转到成功页面
当返回异常时跳转到错误页面
4.1.2 实现思路
我们通过 HttpClient 工具类实现对远程支付接口的调用。
接口链接:https://api.mch.weixin.qq.com/pay/orderquery
具体参数参见“查询订单”API, 我们在 controller 方法中轮询调用查询订单(间隔 3 秒),当返回状态为 success 时,我们会在 controller 方法返回结果。前端代码收到结果后跳转到成功页面。
4.2.1 服务接口层
在 pinyougou-pay-interface 的 WeixinPayService.java 中新增方法定义
- /**
- *查询支付状态
- *@param out_trade_no
- */
- public Map queryPayStatus(String out_trade_no);
4.2.2 服务实现层
在 pinyougou-pay-service 的 WeixinPayServiceImpl.java 中实现方法
- @Override
- public Map queryPayStatus(String out_trade_no) {
- Map param=new HashMap();
- param.put("appid", appid);//公众账号ID
- param.put("mch_id", partner);//商户号
- param.put("out_trade_no", out_trade_no);//订单号
- param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
- String url="https://api.mch.weixin.qq.com/pay/orderquery";
- try {
- String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
- HttpClient client=new HttpClient(url);
- client.setHttps(true);
-
- }
- client.setXmlParam(xmlParam);
- client.post();
- String result = client.getContent();
- Map<String, String> map = WXPayUtil.xmlToMap(result); System.out.println(map);
- return map;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }

4.2.3 控制层
在 pinyougou-cart-web 的 PayController.java 新增方法
- /**
- *查询支付状态
- *@param out_trade_no
- *@return
- */
- @RequestMapping("/queryPayStatus")
- public Result queryPayStatus(String out_trade_no){
- Result result=null;
- while(true){
- //调用查询接口
- Map<String,String> map = weixinPayService.queryPayStatus(out_trade_no);
- if(map==null){//出错
- result=new Result(false, "支付出错");
- break;
- }
- if(map.get("trade_state").equals("SUCCESS")){//如果成功
- result=new Result(true, "支付成功");
- break;
- }
- try {
- Thread.sleep(3000);//间隔三秒 } catch (InterruptedException e) {
- e.printStackTrace();
- } } }
- return result;

4.3.1 服务层
在 payService.js 新增方法
- //查询支付状态 this.queryPayStatus=function(out_trade_no){
- return $http.get('pay/queryPayStatus.do?out_trade_no='+out_trade_no);
- }
4.3.2 控制层
在 payController.js 中新增方法
- //查询支付状态
- queryPayStatus=function(out_trade_no){
- payService.queryPayStatus(out_trade_no).success(
- function(response){
- if(response.success){
- location.href="paysuccess.html";
- }else{
- location.href="payfail.html";
- }
- }
- );
- }
在 createNative 方法的回调方法中调用此查询方法
- //本地生成二维码
- createNative
- $scope. =function(){
- payService.createNative().success(
- function(response){
-
-
- ..........
- queryPayStatus(response.out_trade_no);//查询支付状态
-
-
- } }
- );
4.4.1 问题分析
如果用户到了二维码页面一直未支付,或是关掉了支付页面,我们的代码会一直循环调用微信接口,这样会对程序造成很大的压力。所以我们要加一个时间限制或是循环次数限制,当超过时间或次数时,跳出循环。
4.4.2 代码完善
(1)修改 pinyougou-cart-web 工程 PayController.java 的queryPayStatus方法
- @RequestMapping("/queryPayStatus")
- public Result queryPayStatus(String out_trade_no){
- Result result=null;
- int x=0;
- while(true){
- //调用查询接口 .......
- try {
- Thread.sleep(3000);//间隔三秒
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //为了不让循环无休止地运行,我们定义一个循环变量,如果这个变量超过了这个值则退出循环,设置时间为5分钟
- x++;
- if(x>=100){
- result=new Result(false, "二维码超时");
- break;
- }
- }
- return result;
- }

(2)修改 payController.js
- //查询支付状态
- queryPayStatus=function(out_trade_no){
- payService.queryPayStatus(out_trade_no).success(
- function(response){
- if(response.success){
- location.href="paysuccess.html";
- }else{
- if(response.message=='二维码超时'){
- $scope.createNative();//重新生成二维码
- }else{
- location.href="payfail.html";
-
- } }
-
- } }
- );

4.5.1 问题分析
现在我们支付成功页面显示的是固定的值,怎么显示真正的支付金额呢?我们这里可以使用 angularJS 的页面传参来解决。
4.5.2 代码完善
- //查询支付状态
- queryPayStatus=function(out_trade_no){
- payService.queryPayStatus(out_trade_no).success(
- function(response){
- if(response.success){
- location.href="paysuccess.html#?money="+$scope.money;
- }else{
- if(response.message=='二维码超时'){
- $scope.createNative();//重新生成二维码
- }else{
- location.href="payfail.html";
-
-
- } }
-
- } }
- );

- //获取金额
- $scope.getMoney=function(){
- return $location.search()['money'];
- }
ng-app="pinyougou" ng-controller="payController"
用表达式显示金额
<p >支付金额:¥{{getMoney()}}元</p
我们现在系统还有两个问题需要解决:(1)系统中无法查询到支付记录(2)支付后订单状态没有改变我们现在就来解决这两个问题。
实现思路:(1)在用户下订单时,判断如果为微信支付,就想支付日志表添加一条记录,信息包括支付总金额、订单 ID(多个)、用户 ID 、下单时间等信息,支付状态为 0(未支付)
5.2 表结构分析 tb_paylog 支付日志表
字段 | 类型 | 长度 | 含义 |
out_trade_no | varchar | 30 | 支付订单号 |
create_time | datatime |
| 创建时间 |
pay_time | datatime |
| 支付完成时间 |
total_fee | bigint |
| 支付金额(分) |
transaction_id | varchar | 30 | 交易流水号 |
trade_state | varchar | 1 | 交易状态 |
pay_type | varchar | 1 | 支付类型: 1:微信 2:支付宝 3:网银 |
order_list | varchar | 200 | 订单表 ID 串,用逗号分隔 |
修改 pinyougou-order-service 工程 OrderServiceImpl.java 的 add 方法。
内容:判断如果支付方式为微信支付,向数据库插入支付日志记录,并放入 redis 存储
- @Autowired
- private TbPayLogMapper payLogMapper;
- /**
- * 增加
- */
- public void add(TbOrder order) {
- List<Cart> cartList = (List<Cart>)
- redisTemplate.boundHashOps("cartList").get( order.getUserId() );
- List<String> orderIdList=new ArrayList();//订单ID列表
- double total_money=0;//总金额 (元)
- for(Cart cart:cartList){
- long orderId = idWorker.nextId(); ......
- orderIdList.add(orderId+"");//添加到订单列表
- total_money+=money;//累加到总金额
- }
- if("1".equals(order.getPaymentType())){//如果是微信支付
- TbPayLog payLog=new TbPayLog();
- String outTradeNo= idWorker.nextId()+"";//支付订单号
- payLog.setOutTradeNo(outTradeNo);//支付订单号
- payLog.setCreateTime(new Date());//创建时间
- //订单号列表,逗号分隔
- String ids=orderIdList.toString().replace("[", "").replace("]", "").replace(" ", "");
- payLog.setOrderList(ids);//订单号列表,逗号分隔
- payLog.setPayType("1");//支付类型
- payLog.setTotalFee( (long)(total_money*100 ) );//总金额(分)
- payLog.setTradeState("0");//支付状态
- payLog.setUserId(order.getUserId());//用户ID
- payLogMapper.insert(payLog);//插入到支付日志表
- redisTemplate.boundHashOps("payLog").put(order.getUserId(), payLog);//
- 放入缓存 }
- redisTemplate.boundHashOps("cartList").delete(order.getUserId());
- }

5.4.1 服务接口层
pinyougou-order-interface 工程的 OrderService.java 新增方法
- /**
- *根据用户查询payLog
- *@param userId
- *@return
- */
- public TbPayLog searchPayLogFromRedis(String userId);
5.4.2 服务实现层
pinyougou-order-service 的 OrderServiceImpl.java 实现方法
- @Override
- public TbPayLog searchPayLogFromRedis(String userId) {
- return (TbPayLog) redisTemplate.boundHashOps("payLog").get(userId);
- }
5.4.3 控制层
修改 pinyougou-cart-web 工程 PayController.java 的 createNative 方法
实现思路:调用获取支付日志对象的方法,得到订单号和金额
- @Reference
- private OrderService orderService;
- /**
- *生成二维码
- *@return
- */
- @RequestMapping("/createNative")
- public Map createNative(){
- //获取当前用户
- String
- userId=SecurityContextHolder.getContext().getAuthentication().getName();
- //到redis查询支付日志
- TbPayLog payLog = orderService.searchPayLogFromRedis(userId);
- //判断支付日志存在
- if(payLog!=null){
- return weixinPayService.createNative(payLog.getOutTradeNo(),payLog.getTotalFee()+"");
- }else{
- return new HashMap();
- }
- }

5.5.1 服务接口层
在 pinyougou-order-interface 的 OrderService.java 新增方法定义
- /**
- *修改订单状态
- *@param out_trade_no 支付订单号
- *@param transaction_id 微信返回的交易流水号
- */
- public void updateOrderStatus(String out_trade_no,String transaction_id);
5.5.2 服务实现层
在 pinyougou-order-service 工程 OrderServiceImpl.java 实现该方法.
这个方法主要做三件事:
1. 修改支付日志状态
2.修改关联的订单的状态
3.清除缓存中的支付日志对象
- @Override
-
- public void updateOrderStatus(String out_trade_no, String transaction_id) {
- //1.修改支付日志状态
- TbPayLog payLog = payLogMapper.selectByPrimaryKey(out_trade_no);
- payLog.setPayTime(new Date());
- payLog.setTradeState("1");//已支付
- payLog.setTransactionId(transaction_id);//交易号
- payLogMapper.updateByPrimaryKey(payLog);
- //2.修改订单状态
- String orderList = payLog.getOrderList();//获取订单号列表
- String[] orderIds = orderList.split(",");//获取订单号数组
-
- for(String orderId:orderIds){
- TbOrder order =
- orderMapper.selectByPrimaryKey( Long.parseLong(orderId) );
- if(order!=null){
- order.setStatus("2");//已付款
- orderMapper.updateByPrimaryKey(order);
- }
- }
- //清除redis缓存数据
- redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
- }

5.5.3 控制层
修改 pinyougou-cart-web 的 PayController.java。在微信支付接口有成功返回状态时,调用修改状态的方法
- /**
- *查询支付状态
- *@param out_trade_no
- *@return
- */
- @RequestMapping("/queryPayStatus")
- public Result queryPayStatus(String out_trade_no){
- Result result=null;
- int x=0;
- while(true){
- //调用查询接口
- Map<String,String> map = weixinPayService.queryPayStatus(out_trade_no);
- if(map==null){//出错
- result=new Result(false, "支付出错");
- break; }
- if(map.get("trade_state").equals("SUCCESS")){//如果成功
- result=new Result(true, "支付成功");
- //修改订单状态
- orderService.updateOrderStatus(out_trade_no,
- map.get("transaction_id"));
- break;
- }
- try {
- Thread.sleep(3000);//间隔三秒
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //为了不让循环无休止地运行,我们定义一个循环变量,如果这个变量超过了这个值则退出循环,设置时间为5分钟
- ......
- }
- return result;
- }

需求:在运营商后台中,显示支付日志列表,实现按日期、状态、用户进行查询。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。