今日目标:
(1)掌握二维码生成插件 qrious 的使用
(2)理解微信支付开发的整体思路
(3)调用微信支付接口(统一下单)生成支付二维码
(4)调用微信接口(查询订单)查询支付状态
(5)实现支付日志的生成与订单状态的修改
目录
1、工程搭建
1.1 建立支付服务接口工程(pay-interface)
参考其他服务层接口
1.2 建立支付服务实现工程(pay-service)
(1)依赖pay-interface和common,其他依赖参考其他服务层工程
(2)添加微信支付 SDK 依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
(3)在common工程中放入HttpClient的工具类,并在pom.xml文件中引入依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
(4)添加微信支付配置文件
(5)引入 qrious 的js文件到cart-web的plugins目录中
2、微信支付二维码生成
2.1 需求分析
在支付页面上生成支付二维码,并显示订单号和金额
用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付
2.2 后端
(1)服务层接口(pay-interface),新建WinxinPayService接口
package com.pinyougou.pay.service;
import java.util.Map;
/**
* 微信支付接口
* Author xushuai
* Description
*/
public interface WeixinPayService {
/**
* 生成本地支付的数据信息
*
* @param out_trade_no 外部订单号
* @param total_fee 金额,单位为:分
* @return java.util.Map
*/
Map createNative(String out_trade_no, String total_fee);
}
(2)服务层实现(pay-service),新建WinxinPayServiceImpl实现WinxinPayService
package com.pinyougou.pay.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.github.wxpay.sdk.WXPayUtil;
import com.pinyougou.pay.service.WeixinPayService;
import org.springframework.beans.factory.annotation.Value;
import util.HttpClient;
import java.util.HashMap;
import java.util.Map;
/**
* 微信支付接口实现
* Author xushuai
* Description
*/
@Service
public class WeixinPayServieImpl implements WeixinPayService {
@Value("${appid}")
private String WEIXIN_APPID;
@Value("${partner}")
private String WEIXIN_PARTNER;
@Value("${partnerkey}")
private String WEIXIN_PARTNERKEY;
@Value("${notifyurl}")
private String WEIXIN_NOTIFYURL;
@Override
public Map createNative(String out_trade_no, String total_fee) {
try {
// 1.封装微信支付请求参数
Map<String, String> param = buildParam(out_trade_no, total_fee);
// 2.使用请求参数生成请求xml数据
String signXml = WXPayUtil.generateSignedXml(param, WEIXIN_PARTNERKEY);
System.out.println("xml数据:" + signXml);
// 3.发送请求
HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
httpClient.setHttps(true);
httpClient.setXmlParam(signXml);
httpClient.post();
// 4.获取请求响应结果
String response = httpClient.getContent();
Map<String, String> responseMap = WXPayUtil.xmlToMap(response);
System.out.println("响应结果:" + responseMap);
// 5.生成返回结果
if (responseMap.get("return_code").equals("SUCCESS")) {
Map resultMap = new HashMap();
resultMap.put("code_url", responseMap.get("code_url"));// 二维码链接地址
resultMap.put("out_trade_no", out_trade_no);// 订单号
resultMap.put("total_fee", total_fee);// 金额
return resultMap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 构造微信支付请求参数
*
* @param out_trade_no 外部订单号
* @param total_fee 金额
*/
private Map<String, String> buildParam(String out_trade_no, String total_fee) {
Map<String, String> param = new HashMap<>();
// 公众账号ID
param.put("appid", WEIXIN_APPID);
// 商户号
param.put("mch_id", WEIXIN_PARTNER);
// 设备号(自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB")
//param.put("device_info", "WEB");
// 随机字符串(随机字符串,长度要求在32位以内。推荐随机数生成算法)
param.put("nonce_str", WXPayUtil.generateNonceStr());
// 商品描述
param.put("body", "品优购");
// 商品详情
//param.put("detail", WEIXIN_APPID);
// 商户订单号
param.put("out_trade_no", out_trade_no);
// 标价金额
param.put("total_fee", total_fee);
// 终端IP
param.put("spbill_create_ip", "127.0.0.1");
// 通知地址
param.put("notify_url", "http://test.itcast.cn");
// 交易类型
param.put("trade_type", "NATIVE");
return param;
}
}
(3)控制层(cart-web),新建PayController
package com.pinyougou.cart.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.pinyougou.pay.service.WeixinPayService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import util.IdWorker;
import java.util.Map;
/**
* 支付控制层
* Author xushuai
* Description
*/
@RestController
@RequestMapping("/pay")
public class PayController {
@Reference
private WeixinPayService weixinPayService;
/**
* 生成二维码数据
*
* @return java.util.Map
*/
@RequestMapping("/createNative")
public Map createNative(){
IdWorker idWorker = new IdWorker();
return weixinPayService.createNative(String.valueOf(idWorker.nextId()), "1");
}
}
2.3 前端
(1)新建payService.js
app.service('payService',function($http){
//本地支付
this.createNative=function(){
return $http.get('pay/createNative.do');
}
});
(2)新建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)在pay.html页面中引入JS文件和基础指令
(4)设置展示二维码的img标签的id为:qrious
(5)绑定变量,展示订单ID和订单总金额
3、检测支付状态
3.1 需求分析
当用户支付成功后跳转到成功页面
当返回异常时跳转到错误页面
3.2 后端
(1)服务层接口(pay-interface),在WinxinPayService新增方法
/**
* 查询订单支付状态
*
* @param out_trade_no 外部订单号
* @return java.util.Map
*/
Map queryPayStatus(String out_trade_no);
(2)服务层实现(pay-service),在WinxinPayServiceImpl新增实现
@Override
public Map queryPayStatus(String out_trade_no) {
try {
// 1.封装请求参数
Map<String, String> param = buildQueryPayStatusParam(out_trade_no);
// 2.使用请求参数生成请求xml数据
String signXml = WXPayUtil.generateSignedXml(param, WEIXIN_PARTNERKEY);
System.out.println("xml数据:" + signXml);
// 3.发送请求
HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
httpClient.setHttps(true);
httpClient.setXmlParam(signXml);
httpClient.post();
// 4.获取请求结果
String response = httpClient.getContent();
Map<String, String> responseMap = WXPayUtil.xmlToMap(response);
System.out.println("响应结果:" + response);
return responseMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 构造查询支付状态的请求参数
*
* @param out_trade_no 商户订单号
* @return java.util.Map<java.lang.String,java.lang.String>
*/
private Map<String, String> buildQueryPayStatusParam(String out_trade_no) {
Map<String, String> param = new HashMap<>();
// 公众账号ID
param.put("appid", WEIXIN_APPID);
// 商户号
param.put("mch_id", WEIXIN_PARTNER);
// 商户订单号
param.put("out_trade_no", out_trade_no);
// 随机字符串
param.put("nonce_str", WXPayUtil.generateNonceStr());
return param;
}
(3)控制层(cart-web),在PayController中新增方法
/**
* 查询订单状态(三秒查询一次,直到支付成功)
*
* @param out_trade_no 商家订单号
* @return entity.Result
*/
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
Result result = null;
while (true) {
// 查询订单状态
Map map = weixinPayService.queryPayStatus(out_trade_no);
if (map == null) {
result = Result.error("支付出错");
break;
}
if (map.get("trade_state").equals("SUCCESS")) {
result = Result.success("支付成功");
break;
}
try {
// 三秒查询一次订单状态
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
3.3 前端
(1)在payService.js中新增方法
//查询支付状态
this.queryPayStatus = function (out_trade_no) {
return $http.get('pay/queryPayStatus.do?out_trade_no=' + out_trade_no);
}
(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";
}
}
);
}
(3)在生成二维码成功响应后,执行查询订单状态的方法
3.4 二维码超时处理
(1)后端:在控制层中的PayController中的查询订单状态方法加入逻辑
(2)前端:当二维码已过期时,重新生成二维码
3.5 支付成功显示支付金额
(1)前端:修改payController中支付成功后的逻辑
(2)前端:在payController中引入$location 服务
(3)前端:在payController中新增方法
//获取金额
$scope.getMoney=function(){
return $location.search()['money'];
}
(4)前端:在paysuccess.html中引入相关js文件和基础指令
(5)前端:页面绑定变量展示支付金额
4、支付日志
4.1 需求分析
我们现在系统还有两个问题需要解决:
- 系统中无法查询到支付记录
- 支付后订单状态没有改变
我们现在就来解决这两个问题。
实现思路:
(1)在用户下订单时,判断如果为微信支付,就想支付日志表添加一条记录,信息包括支付总金额、订单ID(多个)、用户ID 、下单时间等信息,支付状态为0(未支付)
(2)生成的支付日志对象放入redis中,以用户ID作为key,这样在生成支付二维码时就可以从redis中提取支付日志对象中的金额和订单号。
(3)当用户支付成功后,修改支付日志的支付状态为1(已支付),并记录微信传递给我们的交易流水号。根据订单ID(多个)修改订单的状态为2(已付款)。
4.2 插入支付日志
(1)生成订单时,生成支付日志,修改OrderServiceImpl中的add方法(order-service)
/**
* 增加
*/
@Override
public void add(TbOrder order) {
// 1.从redis中提取购物车列表
List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(order.getUserId());
List<String> orderIdList = new ArrayList();//订单ID列表
double total_money = 0;//总金额 (元)
// 2.循环购物车保存订单
for (Cart cart : cartList) {
// 生成订单对象
TbOrder tbOrder = buildOrder(order);
// 设置商家ID
tbOrder.setSellerId(cart.getSellerId());
// 合计金额
double money = 0;
// 循环购物车明细
for (TbOrderItem orderItem : cart.getOrderItemList()) {
// 补全订单明细数据
orderItem.setOrderId(tbOrder.getOrderId());
orderItem.setSellerId(cart.getSellerId());
orderItem.setId(idWorker.nextId());
// 保存
orderItemMapper.insert(orderItem);
// 累加合计金额
money += orderItem.getTotalFee().doubleValue();
}
// 设置合计金额到订单
tbOrder.setPayment(BigDecimal.valueOf(money));
// 保存
orderMapper.insert(tbOrder);
// 添加到订单列表
orderIdList.add(tbOrder.getOrderId() + "");
// 累加到总金额
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);//放入缓存
}
// 3.清除购物车中的数据
redisTemplate.boundHashOps("cartList").delete(order.getUserId());
}
4.3 读取支付日志
(1)后端:服务层接口(order-interface),在OrderService中新增方法
/**
* 获取指定用户的支付日志
*
* @param username 用户登录名
* @return com.pinyougou.pojo.TbPayLog
*/
TbPayLog searchPayLogFromRedis(String username);
(2)后端:服务层实现(order-service),在OrderServiceImpl中实现该方法
@Override
public TbPayLog searchPayLogFromRedis(String username) {
return (TbPayLog) redisTemplate.boundHashOps("payLog").get(username);
}
(3)后端:控制层(cart-web),修改PayController中的createNative方法的逻辑
/**
* 生成二维码数据
*
* @return java.util.Map
*/
@RequestMapping("/createNative")
public Map createNative(){
// 获取当前登录用户名
String username = SecurityContextHolder.getContext().getAuthentication().getName();
// 从缓存中取出支付日志
TbPayLog tbPayLog = orderService.searchPayLogFromRedis(username);
if (tbPayLog != null) {
return weixinPayService.createNative(tbPayLog.getOutTradeNo(), String.valueOf(tbPayLog.getTotalFee()));
}
return new HashMap();
}
5、支付成功,修改订单状态
(1)后端:服务层接口(order-interface),在OrderService中新增方法
/**
* 修改订单状态
*
* @param out_trade_no 支付订单号
* @param transaction_id 微信返回的交易流水号
*/
void updateOrderStatus(String out_trade_no, String transaction_id);
(2)后端:服务层实现(order-service),在OrderServiceImpl中实现该方法
@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(TbOrder.STATUS_PAY);//已付款
orderMapper.updateByPrimaryKey(order);
}
}
//清除redis缓存数据
redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
}
(3)后端:控制层(cart-web),在支付成功调用该方法