今日目标:
(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}"</span>)</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="21"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-keyword">private</span> String WEIXIN_APPID;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="22"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-meta">@Value</span>(<span class="hljs-string">"${partner}")
-
private String WEIXIN_PARTNER;
-
@Value(
"${partnerkey}"</span>)</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="25"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-keyword">private</span> String WEIXIN_PARTNERKEY;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="26"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-meta">@Value</span>(<span class="hljs-string">"${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</span>)</span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-comment">//本地支付</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-keyword">this</span>.createNative=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-keyword">return</span> $http.get('pay/createNative.do');
-
}
-
});
(2)新建payController.js
-
app.controller(
'payController' ,
function($scope ,payService</span>)</span>{ </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-comment">//本地生成二维码</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> $scope.createNative=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> payService.createNative().success(</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">response</span>)</span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> $scope.money= (response.total_fee/<span class="hljs-number">100</span>).toFixed(<span class="hljs-number">2</span>) ; <span class="hljs-comment">//金额</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> $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=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-keyword">return</span> $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),在支付成功调用该方法