• vue+element模仿电商商城,前后端分离实现,下单微信扫码支付


    1.前言

    接上一篇《vue+element+SpringBoot+OAuth2+Spring Security+Redis+mybatis-plus+mysql+swagger模仿商城,前后端分离实现》。

    上篇文章介绍了:

    1. 用户注册
    2. 用户登录
    3. 首页商品推荐展览
    4. 商品搜索
    5. 商品分类
    6. 按商品分类预览商品
    7. 商品详情预览
    8. 加入购物车

    上一篇文章有说过已实现单一订单下单,即只能一个一个商品去下单购买,所以这次进行了优化完善,把单一订单改成了合并订单,即一个订单下面可包含多个订单行项目,也就是一次可购买多个商品,统一生成一个订单,然后进行扫码支付;

    2.合并订单需要设计两个数据表:

    大订单表:

    1. CREATE TABLE `product_order`
    2. (
    3. `id` bigint NOT NULL COMMENT '主键',
    4. `order_num` varchar(100) DEFAULT NULL COMMENT '订单号',
    5. `user_id` bigint NOT NULL COMMENT '所属用户',
    6. `pay_amount` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '支付金额',
    7. `receiver_name` varchar(225) NOT NULL COMMENT '收货人姓名',
    8. `receiver_phone` varchar(225) NOT NULL COMMENT '收货人联系电话',
    9. `receiver_addr` varchar(225) NOT NULL COMMENT '收货人地址',
    10. `receiver_remark` varchar(225) DEFAULT NULL COMMENT '收货人备注',
    11. `pay_status` TINYINT(1) DEFAULT 1 COMMENT '支付状态 1待支付 2待发货 3已发货 4已完成 5已取消',
    12. `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标记 是否已删除: 0否 1是',
    13. `create_time` datetime(0) COMMENT '创建时间',
    14. `update_time` datetime(0) COMMENT '更新时间',
    15. PRIMARY KEY (`id`) USING BTREE,
    16. UNIQUE KEY `order_num` (`order_num`) USING BTREE
    17. ) ENGINE = InnoDB
    18. DEFAULT CHARSET = utf8mb4 COMMENT ='订单';

    订单行项目表:

    1. -- 订单行项目
    2. CREATE TABLE `product_order_item`
    3. (
    4. `id` bigint NOT NULL COMMENT '主键',
    5. `parent_id` bigint NOT NULL COMMENT '订单主键',
    6. `order_num` varchar(100) DEFAULT NULL COMMENT '订单号',
    7. `product_id` bigint NOT NULL COMMENT '商品主键',
    8. `product_name` varchar(225) NOT NULL COMMENT '商品名称',
    9. `product_des` varchar(225) DEFAULT NULL COMMENT '商品描述',
    10. `price` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '商品单价',
    11. `original_price` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '商品原价',
    12. `cover_path` varchar(500) DEFAULT NULL COMMENT '商品封面图片',
    13. `product_num` int(11) NULL DEFAULT 0 COMMENT '商品购买数量',
    14. `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标记 是否已删除: 0否 1是',
    15. `create_time` datetime(0) COMMENT '创建时间',
    16. `update_time` datetime(0) COMMENT '更新时间',
    17. PRIMARY KEY (`id`) USING BTREE,
    18. ) ENGINE = InnoDB
    19. DEFAULT CHARSET = utf8mb4 COMMENT ='订单行项目';

    3.生成支付二维码

    controller:

    1. /**
    2. * 微信支付->扫码支付(模式二)->统一下单->微信二维码
    3. */
    4. @GetMapping("/getQrcode")
    5. public void wxPayPay(@Param("订单号") @RequestParam("orderNum") String orderNum, HttpServletResponse response) {
    6. wxPayService.getPayQrcode(orderNum,response);
    7. }

    service: 

    1. /**
    2. * 微信支付->扫码支付(模式二)->统一下单->微信二维码
    3. *
    4. * @param orderNum 订单号
    5. * @param response 返回
    6. */
    7. @Override
    8. public void getPayQrcode(String orderNum, HttpServletResponse response) {
    9. String urlCode;
    10. // 获取订单信息
    11. WxpayVO vo = new WxpayVO();
    12. String outTradeNo = UUID.randomUUID().toString().replace("-", "");
    13. vo.setOutTradeNo(outTradeNo);
    14. // 账号信息
    15. vo.setAppId(wxPayConfig.getAppId());
    16. vo.setMchId(wxPayConfig.getMchId());
    17. vo.setKey(wxPayConfig.getKey());
    18. String currTime = PayToolUtil.getCurrTime();
    19. vo.setCurrTime(currTime);
    20. String strTime = currTime.substring(8, currTime.length());
    21. vo.setStrTime(strTime);
    22. String strRandom = String.valueOf(PayToolUtil.buildRandom(4));
    23. vo.setStrRandom(strRandom);
    24. String nonceStr = strTime + strRandom;
    25. vo.setNonceStr(nonceStr);
    26. vo.setSpbillCreateIp(wxPayConfig.getSpbillCreateIp());
    27. vo.setNotifyUrl(wxPayConfig.getNotifyUrl());
    28. vo.setTradeType("NATIVE");
    29. String totalFee = "1";
    30. vo.setTotalFee(totalFee);//价格的单位为分
    31. SortedMap packageParams = new TreeMap();
    32. packageParams.put("appid", wxPayConfig.getAppId());//公众账号ID
    33. packageParams.put("mch_id", wxPayConfig.getMchId());//商户号
    34. packageParams.put("nonce_str", wxPayConfig.getNonceStr());//随机字符串
    35. packageParams.put("body", "资源"); //商品描述
    36. packageParams.put("out_trade_no", wxPayConfig.getNonceStr());//商户订单号
    37. packageParams.put("total_fee", totalFee); //标价金额 订单总金额,单位为分
    38. packageParams.put("spbill_create_ip", wxPayConfig.getSpbillCreateIp());//终端IP APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP
    39. packageParams.put("notify_url", wxPayConfig.getNotifyUrl());//通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
    40. packageParams.put("trade_type", "NATIVE");//交易类型 NATIVE 扫码支付
    41. // 签名
    42. String sign = PayToolUtil.createSign("UTF-8", packageParams, wxPayConfig.getKey());
    43. packageParams.put("sign", sign);
    44. // 将请求参数转换为xml格式的string
    45. String requestXML = PayToolUtil.getRequestXml(packageParams);
    46. log.info("requestXML:{}", requestXML);
    47. // 调用微信支付统一下单接口
    48. String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
    49. log.info("resXml: {}", resXml);
    50. // 解析微信支付结果
    51. Map map = null;
    52. try {
    53. map = XMLUtil4jdom.doXMLParse(resXml);
    54. log.info("map: {}", map);
    55. } catch (JDOMException e) {
    56. e.printStackTrace();
    57. } catch (IOException e) {
    58. e.printStackTrace();
    59. }
    60. // 返回微信支付的二维码连接
    61. urlCode = (String) map.get("code_url");
    62. log.info("urlCode:{}", urlCode);
    63. if(urlCode == null){//测试
    64. urlCode = "http://192.168.50.231:8089/order/updatePayStatus?orderNum=" + orderNum + "&payStatus=" + PayStatus.WAIT_SEND.getValue();
    65. }
    66. try {
    67. int width = 300;
    68. int height = 300;
    69. //二维码的图片格式
    70. String format = "gif";
    71. Hashtable hints = new Hashtable();
    72. //内容所使用编码
    73. hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
    74. BitMatrix bitMatrix;
    75. bitMatrix = new MultiFormatWriter().encode(urlCode, BarcodeFormat.QR_CODE, width, height, hints);
    76. QRUtil.writeToStream(bitMatrix, format, response.getOutputStream());
    77. } catch (WriterException e) {
    78. e.printStackTrace();
    79. } catch (IOException e) {
    80. e.printStackTrace();
    81. }
    82. }

    注意:

    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:

    1. /**
    2. * 微信支付-回调
    3. * @param request request请求
    4. * @param response response返回
    5. */
    6. @PostMapping("/notify")
    7. public String wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
    8. return wxPayService.wxPayNotify(request,response);
    9. }

    service:

    1. /**
    2. * 微信支付回调
    3. *
    4. * @param request 请求
    5. * @param response 返回
    6. * @return 结果
    7. */
    8. @Override
    9. public String wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
    10. //读取参数
    11. InputStream inputStream ;
    12. StringBuffer sb = null;
    13. try {
    14. sb = new StringBuffer();
    15. inputStream = request.getInputStream();
    16. String s ;
    17. BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
    18. while ((s = in.readLine()) != null){
    19. sb.append(s);
    20. }
    21. in.close();
    22. inputStream.close();
    23. } catch (IOException e) {
    24. e.printStackTrace();
    25. }
    26. //解析xml成map
    27. Map map = new HashMap();
    28. try {
    29. map = XMLUtil4jdom.doXMLParse(sb.toString());
    30. } catch (JDOMException e) {
    31. e.printStackTrace();
    32. } catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. //过滤空 设置 TreeMap
    36. SortedMap packageParams = new TreeMap();
    37. Iterator it = map.keySet().iterator();
    38. while (it.hasNext()) {
    39. String parameter = (String) it.next();
    40. String parameterValue = map.get(parameter);
    41. String v = "";
    42. if(null != parameterValue) {
    43. v = parameterValue.trim();
    44. }
    45. packageParams.put(parameter, v);
    46. }
    47. //判断签名是否正确
    48. if(PayToolUtil.isTenpaySign("UTF-8", packageParams, wxPayConfig.getKey())) {
    49. //------------------------------
    50. //处理业务开始
    51. //------------------------------
    52. String resXml = "";
    53. if("SUCCESS".equals((String)packageParams.get("result_code"))){
    54. // 这里是支付成功
    55. //执行自己的业务逻辑
    56. String mch_id = (String)packageParams.get("mch_id");
    57. String openid = (String)packageParams.get("openid");
    58. String is_subscribe = (String)packageParams.get("is_subscribe");
    59. String out_trade_no = (String)packageParams.get("out_trade_no");
    60. String total_fee = (String)packageParams.get("total_fee");
    61. //执行自己的业务逻辑
    62. productOrderService.updatePayStatus(out_trade_no,PayStatus.WAIT_SEND.getValue());
    63. log.info("支付成功");
    64. //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
    65. resXml = "" + ""
    66. + "" + " ";
    67. } else {
    68. resXml = "" + ""
    69. + "" + " ";
    70. return ("fail");
    71. }
    72. //------------------------------
    73. //处理业务完毕
    74. //------------------------------
    75. try {
    76. BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
    77. out.write(resXml.getBytes());
    78. out.flush();
    79. out.close();
    80. } catch (IOException e) {
    81. e.printStackTrace();
    82. }
    83. } else{
    84. log.info("通知签名验证失败");
    85. }
    86. return ("success");
    87. }

    4.模拟微信扫码支付,逻辑其实是一样的

    1.单一商品下单,微信扫一扫:

     轮询查询订单状态:

     当订单状态为完成支付时,倒计时5秒跳转:

    1. getByOrderNumFun(){
    2. getByOrderNum(this.orderNum).then(res => {
    3. if(res.code === 200){
    4. if(res.data.payStatus === 1){//待支付
    5. if(this.url === ''){
    6. this.url = '/api/wxPay/getQrcode?orderNum=' + this.orderNum;
    7. }
    8. }else if(res.data.payStatus === 2){//已支付
    9. clearInterval(this.inter);
    10. this.tipColor = '#67C23A';
    11. this.tip = '支付成功,正在为您跳转订单页面~';
    12. this.countDownFun(5,'/order/index');
    13. }else if(res.data.payStatus === 4){//已取消订单
    14. clearInterval(this.inter);
    15. this.tip = '订单已取消,正在为您跳转首页~';
    16. this.countDownFun(5,'/home/index');
    17. }else{
    18. clearInterval(this.inter);
    19. }
    20. }else{
    21. clearInterval(this.inter);
    22. }
    23. },error => {
    24. clearInterval(this.inter);
    25. })
    26. },

    多个商品下单:

     看到订单列表超过两个商品时显示样式有问题,修正优化~:

     待支付,可以点击进行支付:

    已付款,待发货:

    可取消订单:

     微信模拟扫码截图(忽略提示乱码):

     

     5.至此订单告一段落

    感谢看到这里,需要源码或有什么问题和想说的请私聊!

  • 相关阅读:
    Con A-PEG-Indocyanine green 刀豆球蛋白A-聚乙二醇-吲哚菁绿,Concanavalin A-ICG
    【linux】VirtualBox启动虚拟机报错
    arx实现三维实体贴材质图
    基于consul的服务注册与消费案例
    ZED 2i 双目-IMU标定
    C语言实现杨辉三角
    一维数组和二维数组的使用(char类型)
    java计算机毕业设计家装行业门店订单管理系统源码+mysql数据库+系统+lw文档+部署
    【学习笔记】快速幂
    【洛谷】P1114 “非常男女”计划
  • 原文地址:https://blog.csdn.net/lucky_fang/article/details/128144683