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.至此订单告一段落
感谢看到这里,需要源码或有什么问题和想说的请私聊!


![[python][学习]循环与嵌套---打印乘法口诀](https://img-blog.csdnimg.cn/05e15267dbd841d392683f07ecc61909.png)
















