1.前言
接上一篇《vue+element+SpringBoot+OAuth2+Spring Security+Redis+mybatis-plus+mysql+swagger模仿商城,前后端分离实现》。
上篇文章介绍了:
- 用户注册
- 用户登录
- 首页商品推荐展览
- 商品搜索
- 商品分类
- 按商品分类预览商品
- 商品详情预览
- 加入购物车
上一篇文章有说过已实现单一订单下单,即只能一个一个商品去下单购买,所以这次进行了优化完善,把单一订单改成了合并订单,即一个订单下面可包含多个订单行项目,也就是一次可购买多个商品,统一生成一个订单,然后进行扫码支付;
2.合并订单需要设计两个数据表:
大订单表:
CREATE TABLE `product_order`
(
`id` bigint NOT NULL COMMENT '主键',
`order_num` varchar(100) DEFAULT NULL COMMENT '订单号',
`user_id` bigint NOT NULL COMMENT '所属用户',
`pay_amount` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '支付金额',
`receiver_name` varchar(225) NOT NULL COMMENT '收货人姓名',
`receiver_phone` varchar(225) NOT NULL COMMENT '收货人联系电话',
`receiver_addr` varchar(225) NOT NULL COMMENT '收货人地址',
`receiver_remark` varchar(225) DEFAULT NULL COMMENT '收货人备注',
`pay_status` TINYINT(1) DEFAULT 1 COMMENT '支付状态 1待支付 2待发货 3已发货 4已完成 5已取消',
`deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标记 是否已删除: 0否 1是',
`create_time` datetime(0) COMMENT '创建时间',
`update_time` datetime(0) COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `order_num` (`order_num`) USING BTREE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='订单';
订单行项目表:
-- 订单行项目
CREATE TABLE `product_order_item`
(
`id` bigint NOT NULL COMMENT '主键',
`parent_id` bigint NOT NULL COMMENT '订单主键',
`order_num` varchar(100) DEFAULT NULL COMMENT '订单号',
`product_id` bigint NOT NULL COMMENT '商品主键',
`product_name` varchar(225) NOT NULL COMMENT '商品名称',
`product_des` varchar(225) DEFAULT NULL COMMENT '商品描述',
`price` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '商品单价',
`original_price` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '商品原价',
`cover_path` varchar(500) DEFAULT NULL COMMENT '商品封面图片',
`product_num` int(11) NULL DEFAULT 0 COMMENT '商品购买数量',
`deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标记 是否已删除: 0否 1是',
`create_time` datetime(0) COMMENT '创建时间',
`update_time` datetime(0) COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='订单行项目';
3.生成支付二维码
controller:
/**
* 微信支付->扫码支付(模式二)->统一下单->微信二维码
*/
@GetMapping("/getQrcode")
public void wxPayPay(@Param("订单号") @RequestParam("orderNum") String orderNum, HttpServletResponse response) {
wxPayService.getPayQrcode(orderNum,response);
}
service:
/**
* 微信支付->扫码支付(模式二)->统一下单->微信二维码
*
* @param orderNum 订单号
* @param response 返回
*/
@Override
public void getPayQrcode(String orderNum, HttpServletResponse response) {
String urlCode;
// 获取订单信息
WxpayVO vo = new WxpayVO();
String outTradeNo = UUID.randomUUID().toString().replace("-", "");
vo.setOutTradeNo(outTradeNo);
// 账号信息
vo.setAppId(wxPayConfig.getAppId());
vo.setMchId(wxPayConfig.getMchId());
vo.setKey(wxPayConfig.getKey());
String currTime = PayToolUtil.getCurrTime();
vo.setCurrTime(currTime);
String strTime = currTime.substring(8, currTime.length());
vo.setStrTime(strTime);
String strRandom = String.valueOf(PayToolUtil.buildRandom(4));
vo.setStrRandom(strRandom);
String nonceStr = strTime + strRandom;
vo.setNonceStr(nonceStr);
vo.setSpbillCreateIp(wxPayConfig.getSpbillCreateIp());
vo.setNotifyUrl(wxPayConfig.getNotifyUrl());
vo.setTradeType("NATIVE");
String totalFee = "1";
vo.setTotalFee(totalFee);//价格的单位为分
SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
packageParams.put("appid", wxPayConfig.getAppId());//公众账号ID
packageParams.put("mch_id", wxPayConfig.getMchId());//商户号
packageParams.put("nonce_str", wxPayConfig.getNonceStr());//随机字符串
packageParams.put("body", "资源"); //商品描述
packageParams.put("out_trade_no", wxPayConfig.getNonceStr());//商户订单号
packageParams.put("total_fee", totalFee); //标价金额 订单总金额,单位为分
packageParams.put("spbill_create_ip", wxPayConfig.getSpbillCreateIp());//终端IP APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP
packageParams.put("notify_url", wxPayConfig.getNotifyUrl());//通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
packageParams.put("trade_type", "NATIVE");//交易类型 NATIVE 扫码支付
// 签名
String sign = PayToolUtil.createSign("UTF-8", packageParams, wxPayConfig.getKey());
packageParams.put("sign", sign);
// 将请求参数转换为xml格式的string
String requestXML = PayToolUtil.getRequestXml(packageParams);
log.info("requestXML:{}", requestXML);
// 调用微信支付统一下单接口
String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
log.info("resXml: {}", resXml);
// 解析微信支付结果
Map map = null;
try {
map = XMLUtil4jdom.doXMLParse(resXml);
log.info("map: {}", map);
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 返回微信支付的二维码连接
urlCode = (String) map.get("code_url");
log.info("urlCode:{}", urlCode);
if(urlCode == null){//测试
urlCode = "http://192.168.50.231:8089/order/updatePayStatus?orderNum=" + orderNum + "&payStatus=" + PayStatus.WAIT_SEND.getValue();
}
try {
int width = 300;
int height = 300;
//二维码的图片格式
String format = "gif";
Hashtable hints = new Hashtable();
//内容所使用编码
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
BitMatrix bitMatrix;
bitMatrix = new MultiFormatWriter().encode(urlCode, BarcodeFormat.QR_CODE, width, height, hints);
QRUtil.writeToStream(bitMatrix, format, response.getOutputStream());
} catch (WriterException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
注意:
urlCode = "http://192.168.50.231:8089/order/updatePayStatus?orderNum=" + orderNum + "&payStatus=" + PayStatus.WAIT_SEND.getValue();
这一句是为了本地测试加的,因为微信支付首先要去微信申请开通微信支付:
微信支付文档
二维码代表的内容就是urlCode的具体值,扫码模拟微信支付就会直接请求路径:http://192.168.50.231:8089/order/updatePayStatus?orderNum=" + orderNum + "&payStatus=" + PayStatus.WAIT_SEND.getValue(),进行订单状态更新,所以接口要开放白名单,即是无需登录即可请求。
当然如果是真实的微信支付,则需要配置支付回调地址,由微信官方通知服务器订单支付的状态,同样也要配置开放白名单,否则微信无法请求到我们的服务器。
微信回调:
controller:
/**
* 微信支付-回调
* @param request request请求
* @param response response返回
*/
@PostMapping("/notify")
public String wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
return wxPayService.wxPayNotify(request,response);
}
service:
/**
* 微信支付回调
*
* @param request 请求
* @param response 返回
* @return 结果
*/
@Override
public String wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
//读取参数
InputStream inputStream ;
StringBuffer sb = null;
try {
sb = new StringBuffer();
inputStream = request.getInputStream();
String s ;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
//解析xml成map
Map<String, String> map = new HashMap<String, String>();
try {
map = XMLUtil4jdom.doXMLParse(sb.toString());
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//过滤空 设置 TreeMap
SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = map.get(parameter);
String v = "";
if(null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
//判断签名是否正确
if(PayToolUtil.isTenpaySign("UTF-8", packageParams, wxPayConfig.getKey())) {
//------------------------------
//处理业务开始
//------------------------------
String resXml = "";
if("SUCCESS".equals((String)packageParams.get("result_code"))){
// 这里是支付成功
//执行自己的业务逻辑
String mch_id = (String)packageParams.get("mch_id");
String openid = (String)packageParams.get("openid");
String is_subscribe = (String)packageParams.get("is_subscribe");
String out_trade_no = (String)packageParams.get("out_trade_no");
String total_fee = (String)packageParams.get("total_fee");
//执行自己的业务逻辑
productOrderService.updatePayStatus(out_trade_no,PayStatus.WAIT_SEND.getValue());
log.info("支付成功");
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return ("fail");
}
//------------------------------
//处理业务完毕
//------------------------------
try {
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} else{
log.info("通知签名验证失败");
}
return ("success");
}
4.模拟微信扫码支付,逻辑其实是一样的
1.单一商品下单,微信扫一扫:
轮询查询订单状态:
当订单状态为完成支付时,倒计时5秒跳转:
getByOrderNumFun(){
getByOrderNum(this.orderNum).then(res => {
if(res.code === 200){
if(res.data.payStatus === 1){//待支付
if(this.url === ''){
this.url = '/api/wxPay/getQrcode?orderNum=' + this.orderNum;
}
}else if(res.data.payStatus === 2){//已支付
clearInterval(this.inter);
this.tipColor = '#67C23A';
this.tip = '支付成功,正在为您跳转订单页面~';
this.countDownFun(5,'/order/index');
}else if(res.data.payStatus === 4){//已取消订单
clearInterval(this.inter);
this.tip = '订单已取消,正在为您跳转首页~';
this.countDownFun(5,'/home/index');
}else{
clearInterval(this.inter);
}
}else{
clearInterval(this.inter);
}
},error => {
clearInterval(this.inter);
})
},
多个商品下单:
看到订单列表超过两个商品时显示样式有问题,修正优化~:
待支付,可以点击进行支付:
已付款,待发货:
可取消订单:
微信模拟扫码截图(忽略提示乱码):
5.至此订单告一段落
感谢看到这里,需要源码或有什么问题和想说的请私聊!