今日目标:
二维码的简介
二维码的入门demo
微信平台支付接口调用
检测支付状态
支付日志
一.二维码简介以及入门demo
1.简介:
二维码又称 QR Code,QR 全称 Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的 Bar Code 条形码能存更多的信息,也能表示更多的数据类
2.优势
信息容量大, 可以容纳多达 1850 个大写字母或 2710 个数字或 500 多个汉字
应用范围广, 支持文字,声音,图片,指纹等等...
容错能力强, 即使图片出现部分破损也能使用
成本低, 容易制作
3.二维码的容错级别
L 级(低) 7%的码字可以被恢复。
M 级(中) 的码字的 15%可以被恢复。
Q 级(四分)的码字的 25%可以被恢复。
H 级(高) 的码字的 30%可以被恢复。
4.二维码的生成插件qrious
qrious 是一款基于 HTML5 Canvas 的纯 JS 二维码生成插件。通过 qrious.js 可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行 Base64 编码。
5.入门demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script type="text/javascript" src="js/qrious.min.js"></script>
<body>
<img id="qrious">
</body>
<script type="text/javascript">
var qr = window.qr = new QRious({
element: document.getElementById('qrious'),
size: 250,
value: 'http://www.baidu.com'
})
</script>
</html>
导入qrious的js插件
二.微信支付简介
微信扫码支付的申请步骤:
1.注册公众号(类型:服务号)
2.认证公众号
认证后才能申请 一次300
3.提交申请微信支付材料
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为 1-5个工作日内。
4.开户成功,登陆商户平台进行验证
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。
5.在线签署协议
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。
开发文档简介:
网址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
我们介绍扫码支付:native
两种模式介绍:
第一种模式:微信平台返回支付的二维码
详细的业务流程,查看维信开发文档
第二种模式:微信平台返回一个路径,我们在客户端,自己生成,二维码
同一下单api:
接口链接:
URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
因为需要的参数很多,我们必须要封装的参数都要封装
注:参数值用XML转义即可,CDATA标签用于说明数据不被XML解析器解析。
查询订单:
应用场景:
该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
需要调用查询接口的情况:
- ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
- ◆ 调用支付接口后,返回系统错误或未知交易状态情况;
- ◆ 调用付款码支付API,返回USERPAYING的状态;
- ◆ 调用关单或撤销接口API之前,需确认支付状态;
https://api.mch.weixin.qq.com/pay/orderquery
SDK安装:
维信需要xml格式的数据,我们可以通过维信提供的sdk 来转
所以我们可以组装成map格式的数据,然后通过通过SDK转化为xml格式的字符串
我们通过原生的HttpClient来发送请求
在这我们通过工具类来组装,底层我们封装
三.电商二维码生成
思路分析:我们首先封装,微信接口需要的数据,注意微信要的是xml格式的数据,我们必须通过微信的sdk将map格式转化为xml格式,然后通过httpClient发送请求,获得数据同样转化为map格式
后台代码:
@Service
@Transactional
public class PayServiceImpl implements PayService {
//将需要的参数通过value值注入
@Value("${appid}")
private String appid;
@Value("${partner}")
private String partner;
@Value("${partnerkey}")
private String partnerkey;
@Value("${notifyurl}")
private String notifyurl;//回调地址
@Override
public Map<String, Object> createNative(String out_trade_no, String total_fee) throws Exception {
//1.组装请求参数
Map<String,String> paramMap = new HashMap<>();
paramMap.put("appid",appid);
paramMap.put("mch_id",partner);
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());//获得随机字符串
paramMap.put("tbody","品优购");
paramMap.put("tout_trade_no",out_trade_no);//订单编号
paramMap.put("total_fee",total_fee);//总费用
paramMap.put("spbill_create_ip","127.0.0.1");//本地ip
paramMap.put("tnotify_url",notifyurl);//通知地址
paramMap.put("trade_type","Native");//支付类型
paramMap.put("product_id","1");//这个显示不是必须传的,但是native支付至必须传
//将map格式的数据转换为xml数据格式
String xmlParam = WXPayUtil.generateSignedXml(paramMap, partnerkey);
//2.基于httpClient发送请求
HttpClient httpClient = new HttpClient("URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder");
httpClient.setHttps(true);
httpClient.setXmlParam(xmlParam);//设置请求参数
httpClient.post();
//3.处理相应结果
String content = httpClient.getContent();
System.out.println(content);
Map<String, String> resultMap = WXPayUtil.xmlToMap(content);//转为map数据
//我们必须自己封装返回给前台
Map<String,Object> map = new HashMap<>();
map.put("code_url",resultMap.get("code_url"));
map.put("out_trade_no",out_trade_no);
map.put("total_fee",total_fee);
return map;
}
controller层:
public class PayController {
/**
* 生成二维码
*/
@Reference
private PayService payService;
@RequestMapping("/createNative")
public Map<String,Object> createNative(){
IdWorker idWorker = new IdWorker();
try {
//注意我们在这先是写死的id号
return payService.createNative(idWorker.nextId()+"","1");
} catch (Exception e) {
e.printStackTrace();
return new HashMap<>();
}
}
}
前台代码:
//控制层
app.controller('payController' ,function($scope,$controller ,payService){
$controller('baseController',{$scope:$scope});//继承
//生成二维码
$scope.createNative=function () {
payService.createNative().success(function (response) {
//接受后端传过来的支付订单号和支付金额
$scope.out_trade_no=response.out_trade_no;
$scope.total_fee=response.total_fee;
//基于qrious生成二维码
new QRious({
element: document.getElementById('qrious'),
size: 300,
value: response.code_url,
level:'H'
})
})
}
});
service层:
//服务层
app.service('payService',function($http){
//读取列表数据绑定到表单中
this.createNative=function(){
return $http.get('pay/createNative.do');
}
});
四.检测支付状态
注意:我们在实现过程中,在二维码生成的时候就继续调用 查询状态
/**
* 调用查询状态接口
* @param out_trade_no
* @return
*/
@Override
public Map queryPayStatus(String out_trade_no) throws Exception {
//1.组装请求数据
//1、组装请求参数
Map<String,String> paramMap = new HashMap<>();
paramMap.put("appid",appid);
paramMap.put("mch_id",partner);
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
paramMap.put("out_trade_no",out_trade_no);
String xmlParam = WXPayUtil.generateSignedXml(paramMap, partnerkey);
//2.发送HttpClient请求
HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
httpClient.setHttps(true);
httpClient.setXmlParam(xmlParam);//设置请求参数
httpClient.post();
//3.返回相应数据
String content = httpClient.getContent();
System.out.println(content);
Map<String, String> xmlMap = WXPayUtil.xmlToMap(content);
return xmlMap;
}
/**
* 查询支付状态
*/
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
try {
int count=0;
while (true){
//每隔3秒调用一次
Thread.sleep(3000);
//超过5分钟,跳转循环(支付超时)
count++;
if(count>100){
return new Result(false,"timeout");
}
Map resultMap = payService.queryPayStatus(out_trade_no);
//判断交易状态
if(resultMap.get("trade_state").equals("SUCCESS")){
//支付成功后,更新订单和支付日志状态
//payService.updateStatus(out_trade_no, (String) resultMap.get("transaction_id"));
//支付成功
return new Result(true,"支付成功");
}
}
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"支付失败");
}
}
}
前台代码:
//查询支付状态
$scope.queryPayStatus=function () {
payService.queryPayStatus($scope.out_trade_no).success(function (response) {
if(response.success){
//支付成功
location.href="paysuccess.html#?money="+$scope.total_fee;
}else {
if(response.message=="timeout"){
//支付超时
$scope.createNative();
}
//支付失败
location.href="payfail.html";
}
})
}
//获取支付金额
$scope.getMoney=function () {
$scope.money=$location.search()["money"];
}
//查询支付状态
this.queryPayStatus=function (out_trade_no) {
return $http.get('pay/queryPayStatus.do?out_trade_no='+out_trade_no);
}
注意:路由传参前面添加#号
五.支付日志
需求分析以及思路介绍
1、保存订单时,插入一条支付操作。(前提:在线支付)tb_pay_log 此时:订单和支付日志中的中的支付状态都是"未支付"
2、当用户微信扫码支付成功后,修改订单和支付日志中的中的支付状态,为"已支付",修改支付时间为当前时间。
tb_order tb_order_item
tb_pay_log 支付日志表,记录支付行为
以下字段,是保存订单时,记录一笔支付信息,需要操作的字段
`out_trade_no` varchar(30) NOT NULL COMMENT '支付订单号', //分布式存储 idWorker
`create_time` datetime DEFAULT NULL COMMENT '创建日期',
`total_fee` bigint(20) DEFAULT NULL COMMENT '支付金额(分)',
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易状态', //未支付状态
`user_id` varchar(50) DEFAULT NULL COMMENT '用户ID',
`order_list` varchar(200) DEFAULT NULL COMMENT '订单编号列表', //一笔支付可能对应多笔订单 1,2,3
`pay_type` varchar(1) DEFAULT NULL COMMENT '支付类型', //微信支付
以下字段是微信支付成功后,需要更新的字段:
`pay_time` datetime DEFAULT NULL COMMENT '支付完成时间',
`transaction_id` varchar(30) DEFAULT NULL COMMENT '交易号码', //微信返回,支付成功后需要更新的数据
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易状态',
保存订单的同时,生成一笔支付。
保存多个订单,也只生成一笔支付。
在线支付时,保存支付日志
读取支付日志,显示支付订单号和支付金额
支付成功后,修改订单和支付日志状态
`pay_time` datetime DEFAULT NULL COMMENT '支付完成时间',
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易状态', 已支付:2
`transaction_id` varchar(30) DEFAULT NULL COMMENT '交易号码',
首先我们在保存订单是,插入一条支付操作,用户保存支付的所有信息
//如果是在线支付则,保存一笔订单
if (order.getPaymentType().equals("1")){
//创建payLog对象
TbPayLog payLog = new TbPayLog();
/* `out_trade_no` varchar(30) NOT NULL COMMENT '支付订单号', //分布式存储 idWorker
`create_time` datetime DEFAULT NULL COMMENT '创建日期',
`total_fee` bigint(20) DEFAULT NULL COMMENT '支付金额(分)',
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易状态', //未支付状态
`user_id` varchar(50) DEFAULT NULL COMMENT '用户ID',
`order_list` varchar(200) DEFAULT NULL COMMENT '订单编号列表', //一笔支付可能对应多笔订单 1,2,3
`pay_type` varchar(1) DEFAULT NULL COMMENT '支付类型', //微信支付*/
payLog.setOutTradeNo(idWorker.nextId()+"");
payLog.setCreateTime(new Date());
payLog.setTotalFee((long)(totalMoney*100));//转化为分
payLog.setTradeState("1");
payLog.setUserId(order.getUserId());
//[1 , 2 , 3]我们通过切割的方式
payLog.setOrderList(ids.toString().replace("[","").replace("]","").replace(" ",""));
payLog.setPayType("1");
//保存
payLogMapper.insert(payLog);
//将日志保存redis中
redisTemplate.boundHashOps("payLog").put(order.getUserId(),payLog);
交易成功后跟新支付日志
@Autowired
private TbPayLogMapper payLogMapper;
@Autowired
private TbOrderMapper orderMapper;
/**
* 跟新支付日志状态
* @param out_trade_no
* @param transaction_id
*/
@Override
public void updateStatus(String out_trade_no, String transaction_id) {
//跟新日志状态
TbPayLog payLog = payLogMapper.selectByPrimaryKey(out_trade_no);
payLog.setPayTime(new Date());
payLog.setTradeState("2");
payLog.setTransactionId(transaction_id);
payLogMapper.updateByPrimaryKey(payLog);
//跟新订单状态
String orderList = payLog.getOrderList();
String[] split = orderList.split(",");
for (String orderId : split) {
TbOrder tbOrder = orderMapper.selectByPrimaryKey(Long.parseLong(orderId));
tbOrder.setStatus("2");//已支付
tbOrder.setPaymentTime(new Date());
}
//清除当前redis中关联支付日志的信息
redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
}
注意:最后一定把存在redis中的关联支付日志信息删除
从新打包,测试