2022年最新《谷粒学院开发教程》:10 - 前台支付模块

资料
资料地址
后台管理系统目录 前台展示系统目录
1 - 构建工程篇 7 - 渲染前台篇
2 - 前后交互篇 8 - 前台登录篇
3 - 文件上传篇 9 - 前台课程篇
4 - 课程管理篇 10 - 前台支付篇
5 - 章节管理篇 11 - 统计分析篇
6 - 微服务治理 12 - 项目完结篇


一、需求分析

1、课程分为免费课程和付费课程,如果是免费课程可以直接观看,如果是付费观看的课程,用户需下单支付后才可以观看

在这里插入图片描述

2.1、如果是免费课程,在用户选择课程,进入到课程详情页面时候,直接显示 “立即观看”,用户点击立即观看,可以切换到播放列表进行视频播放

在这里插入图片描述

2.2、如果是付费课程,在用户选择课程,进入到课程详情页面时候,会显示 “立即购买”

在这里插入图片描述

3、点击“立即购买”,会生成课程的订单,跳转到订单页面

在这里插入图片描述

4、点击“去支付”,会跳转到支付页面,生成微信扫描的二维码

在这里插入图片描述

5、使用微信扫描支付后,会跳转回到课程详情页面,同时显示“立即观看”

在这里插入图片描述


二、支付模块搭建

1、在service模块下创建子模块service_order

2、pom

<dependencies>
    <dependency>
        <groupId>com.github.wxpay</groupId>
        <artifactId>wxpay-sdk</artifactId>
        <version>0.0.3</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

3、创建支付表

在这里插入图片描述

CREATE TABLE `t_order` (
  `id` char(19) NOT NULL DEFAULT '',
  `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号',
  `course_id` varchar(19) NOT NULL DEFAULT '' COMMENT '课程id',
  `course_title` varchar(100) DEFAULT NULL COMMENT '课程名称',
  `course_cover` varchar(255) DEFAULT NULL COMMENT '课程封面',
  `teacher_name` varchar(20) DEFAULT NULL COMMENT '讲师名称',
  `member_id` varchar(19) NOT NULL DEFAULT '' COMMENT '会员id',
  `nickname` varchar(50) DEFAULT NULL COMMENT '会员昵称',
  `mobile` varchar(11) DEFAULT NULL COMMENT '会员手机',
  `total_fee` decimal(10,2) DEFAULT '0.01' COMMENT '订单金额(分)',
  `pay_type` tinyint(3) DEFAULT NULL COMMENT '支付类型(1:微信 2:支付宝)',
  `status` tinyint(3) DEFAULT NULL COMMENT '订单状态(0:未支付 1:已支付)',
  `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_order_no` (`order_no`),
  KEY `idx_course_id` (`course_id`),
  KEY `idx_member_id` (`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单';

#
# Data for table "t_order"
#

INSERT INTO `t_order` VALUES ('1195693605513891841','1195693605555834880','18','Java精品课程','http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/06/866e9aca-b530-4f71-a690-72d4a4bfd1e7.jpg','晴天','1','小三1231','13700000001',1,NULL,0,0,'2019-11-16 21:22:25','2019-11-16 21:22:25'),('1195694200178118657','1195694200186507264','18','Java精品课程','http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/06/866e9aca-b530-4f71-a690-72d4a4bfd1e7.jpg','晴天','1','小三1231','13700000001',1,NULL,0,0,'2019-11-16 21:24:47','2019-11-16 21:24:47'),('1196264007411744769','1196264005255872512','1192252213659774977','java基础课程:test','https://guli-file-190513.oss-cn-beijing.aliyuncs.com/cover/default.gif','晴天','1','小三1231','13700000001',1,NULL,1,0,'2019-11-18 11:09:00','2019-11-18 11:10:14'),('1196265495278174209','1196265495273979904','18','Java精品课程','http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/06/866e9aca-b530-4f71-a690-72d4a4bfd1e7.jpg','晴天','1','小三1231','13700000001',1,NULL,0,0,'2019-11-18 11:14:54','2019-11-18 11:14:54');

#
# Structure for table "t_pay_log"
#

CREATE TABLE `t_pay_log` (
  `id` char(19) NOT NULL DEFAULT '',
  `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号',
  `pay_time` datetime DEFAULT NULL COMMENT '支付完成时间',
  `total_fee` decimal(10,2) DEFAULT '0.01' COMMENT '支付金额(分)',
  `transaction_id` varchar(30) DEFAULT NULL COMMENT '交易流水号',
  `trade_state` char(20) DEFAULT NULL COMMENT '交易状态',
  `pay_type` tinyint(3) NOT NULL DEFAULT '0' COMMENT '支付类型(1:微信 2:支付宝)',
  `attr` text COMMENT '其他属性',
  `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付日志表';

#
# Data for table "t_pay_log"
#

INSERT INTO `t_pay_log` VALUES ('1194498446013001730','1194498300579704832','2019-11-13 14:13:17',1,'4200000469201911130676624386','SUCCESS',1,'{\"transaction_id\":\"4200000469201911130676624386\",\"nonce_str\":\"2Lc23ILl231It53M\",\"trade_state\":\"SUCCESS\",\"bank_type\":\"CFT\",\"openid\":\"oNpSGwR-QGG5DaZtDkh2UZlsFDQE\",\"sign\":\"5404850AA3ED0E844DE104651477F07A\",\"return_msg\":\"OK\",\"fee_type\":\"CNY\",\"mch_id\":\"1473426802\",\"cash_fee\":\"1\",\"out_trade_no\":\"1194498300579704832\",\"cash_fee_type\":\"CNY\",\"appid\":\"wx8397f8696b538317\",\"total_fee\":\"1\",\"trade_state_desc\":\"支付成功\",\"trade_type\":\"NATIVE\",\"result_code\":\"SUCCESS\",\"attach\":\"\",\"time_end\":\"20191113141314\",\"is_subscribe\":\"N\",\"return_code\":\"SUCCESS\"}',0,'2019-11-13 14:13:17','2019-11-13 14:13:17'),('1195253787449430017','1195253049260314624','2019-11-15 16:14:44',1,'4200000454201911150981874895','SUCCESS',1,'{\"transaction_id\":\"4200000454201911150981874895\",\"nonce_str\":\"MAM5UM4Xhv1lItvO\",\"trade_state\":\"SUCCESS\",\"bank_type\":\"CFT\",\"openid\":\"oNpSGwR-QGG5DaZtDkh2UZlsFDQE\",\"sign\":\"7DBDCAF4A078B30BB3EF073E6A238C20\",\"return_msg\":\"OK\",\"fee_type\":\"CNY\",\"mch_id\":\"1473426802\",\"cash_fee\":\"1\",\"out_trade_no\":\"1195253049260314624\",\"cash_fee_type\":\"CNY\",\"appid\":\"wx8397f8696b538317\",\"total_fee\":\"1\",\"trade_state_desc\":\"支付成功\",\"trade_type\":\"NATIVE\",\"result_code\":\"SUCCESS\",\"attach\":\"\",\"time_end\":\"20191115161440\",\"is_subscribe\":\"N\",\"return_code\":\"SUCCESS\"}',0,'2019-11-15 16:14:44','2019-11-15 16:14:44'),('1196264321397342210','1196264005255872512','2019-11-18 11:10:14',1,'4200000453201911184025781554','SUCCESS',1,'{\"transaction_id\":\"4200000453201911184025781554\",\"nonce_str\":\"D1dHexCLIFIxAAg2\",\"trade_state\":\"SUCCESS\",\"bank_type\":\"CFT\",\"openid\":\"oNpSGwR-QGG5DaZtDkh2UZlsFDQE\",\"sign\":\"C9F5CA1EE49EA7891736D73BEB423962\",\"return_msg\":\"OK\",\"fee_type\":\"CNY\",\"mch_id\":\"1473426802\",\"cash_fee\":\"1\",\"out_trade_no\":\"1196264005255872512\",\"cash_fee_type\":\"CNY\",\"appid\":\"wx8397f8696b538317\",\"total_fee\":\"1\",\"trade_state_desc\":\"支付成功\",\"trade_type\":\"NATIVE\",\"result_code\":\"SUCCESS\",\"attach\":\"\",\"time_end\":\"20191118111011\",\"is_subscribe\":\"N\",\"return_code\":\"SUCCESS\"}',0,'2019-11-18 11:10:14','2019-11-18 11:10:14');

4、使用代码生成器生成相关代码

gc.setOutputDir("D:\\MyCode\\IdeaCode\\project\\gulicollege\\guli_parent\\service\\service_order" + "/src/main/java"); //输出目录

pc.setModuleName("order"); //模块名

strategy.setInclude("t_order","t_pay_log");//根据数据库哪张表生成,有多张表就加逗号继续填写

5、配置文件

# 服务端口
server.port=8007
# 服务名
spring.application.name=service-order
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://120.76.55.55:3306/guli?useSSL=false&useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=****
# 返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# 配置mapper xml文件路径
mybatis-plus.mapper-locations=/mapper/*.xml
# mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# nacos注册中心
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000

6、启动类

@SpringBootApplication
@EnableFeignClients("com.laptoy.order.client")
@MapperScan("com.laptoy.order.mapper")
@ComponentScan("com.laptoy")
@EnableDiscoveryClient //服务发现功能
public class ServiceOrderMain8007 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ServiceOrderMain8007.class, args);
    }
}

7、需要四个接口

在这里插入图片描述

三、接口 生成订单

1、图示

在这里插入图片描述

2、远程调用、订单公共返回类Vo

用于远程接口调用后返回给订单接口的统一返回类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class EduCourseVo implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "课程id")
    private String id;

    @ApiModelProperty(value = "课程标题")
    private String title;

    @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
    private BigDecimal price;

    @ApiModelProperty(value = "总课时")
    private Integer lessonNum;

    @ApiModelProperty(value = "课程封面图片路径")
    private String cover;

    @ApiModelProperty(value = "销售数量")
    private Long buyCount;

    @ApiModelProperty(value = "浏览数量")
    private Long viewCount;

    @ApiModelProperty(value = "课程简介")
    private String description;

    @ApiModelProperty(value = "讲师ID")
    private String teacherId;

    @ApiModelProperty(value = "讲师姓名")
    private String teacherName;

    @ApiModelProperty(value = "讲师资历,一句话说明讲师")
    private String intro;

    @ApiModelProperty(value = "讲师头像")
    private String avatar;

    @ApiModelProperty(value = "课程一级类别ID")
    private String subjectLevelOneId;

    @ApiModelProperty(value = "类别一级名称")
    private String subjectLevelOne;

    @ApiModelProperty(value = "课程二级类别ID")
    private String subjectLevelTwoId;

    @ApiModelProperty(value = "类别二级名称")
    private String subjectLevelTwo;
}
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="UcenterMember对象", description="会员表")
public class UcenterMemberVo implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "会员id")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "微信openid")
    private String openid;

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "昵称")
    private String nickname;

    @ApiModelProperty(value = "性别 1 女,2 男")
    private Integer sex;

    @ApiModelProperty(value = "年龄")
    private Integer age;

    @ApiModelProperty(value = "用户头像")
    private String avatar;

    @ApiModelProperty(value = "用户签名")
    private String sign;

    @ApiModelProperty(value = "是否禁用 1(true)已禁用,  0(false)未禁用")
    private Boolean isDisabled;

    @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
    private Boolean isDeleted;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
}

3、edu模块接口 - 根据课程id获取课程信息 - EduCourseController

//根据课程id,查询课程信息【订单】
@PostMapping("/getCourseInfoByIdOrder/{courseId}")
public EduCourseVo getCourseInfoByIdOrder(@PathVariable String courseId) {
    
    
    CourseWebVo courseInfo = eduCourseService.getBaseCourseInfo(courseId);

    EduCourseVo eduCourseVo = new EduCourseVo();
    BeanUtils.copyProperties(courseInfo, eduCourseVo);

    return eduCourseVo;
}

4、Ucenter模块接口 - 根据用户id获取用户信息 - UcenterMemberController

//根据用户id查询用户信息
@PostMapping("/getMemberInfoById/{memberId}")
public UcenterMemberVo getMemberInfoById(@PathVariable String memberId) {
    
    
    Member member = memberService.getById(memberId);
    UcenterMemberVo memberVo = new UcenterMemberVo();
    BeanUtils.copyProperties(member, memberVo);

    return memberVo;
}

5、Order模块接口 - 远程接口

@FeignClient(name = "service-edu")
public interface EduClient {
    
    

    //根据课程id,查询课程信息【订单】
    @PostMapping("/eduservice/course/getCourseInfoByIdOrder/{courseId}")
    EduCourseVo getCourseInfoByIdOrder(@PathVariable String courseId);
}
@FeignClient(name = "service-ucenter")
public interface UCenterClient {
    
    

    @PostMapping("/ucenter/member/getMemberInfoById/{memberId}")
    UcenterMemberVo getMemberInfoById(@PathVariable String memberId);
}

6、订单模块接口 - 生成订单

6.1、控制层

@CrossOrigin
@RestController
@RequestMapping("/order/t-order")
public class TOrderController {
    
    

    @Autowired
    TOrderService tOrderService;

    // 生成订单
    @PostMapping("/createOrder/{courseId}")
    public R createOrder(@PathVariable String courseId, HttpServletRequest request) {
    
    
        // 从请求头获取用户id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        // 判断是否登录
        if(StringUtils.isEmpty(memberId)){
    
    
            throw new LaptoyException(20001, "请登录");
        }
        // 生成订单并生成对应的订单号
        String orderNo = tOrderService.createOrder(courseId, memberId);

        return R.ok().data("orderNo", orderNo);
    }
}

6.2、业务层

@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {
    
    

    @Autowired
    EduClient eduClient;
    @Autowired
    UCenterClient uCenterClient;

    @Override
    public String createOrder(String courseId, String memberId) {
    
    
        // 根据课程id、用户id 获取课程、用户信息
        EduCourseVo courseInfo = eduClient.getCourseInfoByIdOrder(courseId);
        UcenterMemberVo memberInfo = uCenterClient.getMemberInfoById(memberId);

        TOrder tOrder = new TOrder();
        tOrder.setMobile(memberInfo.getMobile());
        tOrder.setNickname(memberInfo.getNickname());
        tOrder.setMemberId(memberId);
        tOrder.setCourseCover(courseInfo.getCover());
        tOrder.setCourseId(courseId);
        tOrder.setCourseTitle(courseInfo.getTitle());
        tOrder.setTeacherName(courseInfo.getTeacherName());
        tOrder.setTotalFee(courseInfo.getPrice());
        tOrder.setStatus(0);//支付状态:( 0:已支付,1:未支付 )
        tOrder.setPayType(1);//支付类型: 1:微信 , 2:支付宝
        tOrder.setOrderNo(OrderNoUtil.getOrderNo()); //订单号

        // 保存订单
        baseMapper.insert(tOrder);

        return tOrder.getOrderNo();
    }
}

7、调用服务启动类需要标注 @EnableFeignClient 开始远程调用功能

8、被调用服务启动类需要标注 @EnableDiscoveryClient 开启服务发现功能


四、接口 根据订单号查询订单信息

// 根据订单号查询订单信息
@GetMapping("/getOrderInfoById/{orderNo}")
public R getOrderInfoById(@PathVariable String orderNo) {
    
    
    QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
    wrapper.eq("order_no", orderNo);
    TOrder tOrder = tOrderService.getOne(wrapper);

    return R.ok().data("data", tOrder);
}

五、接口 生成微信支付二维码

1、控制层

@RestController
@RequestMapping("/order/t-pay-log")
@CrossOrigin
public class TPayLogController {
    
    

    @Autowired
    private TPayLogService tPayLogService;

    //根据订单号,生成微信支付二维码的接口
    @GetMapping("/createWxQRcode/{orderNo}")
    public R createWxQRcode(@PathVariable String orderNo){
    
    
        //返回信息,包含二维码地址、其他信息
        Map<String, Object> map = tPayLogService.createWxQrcode(orderNo);
        return R.ok().data(map);
    }
}

2、业务层(工具类HttpClient自行在资料中寻找)

@Service
public class TPayLogServiceImpl extends ServiceImpl<TPayLogMapper, TPayLog> implements TPayLogService {
    
    

    @Autowired
    private TOrderService tOrderService;

    //根据订单号,生成微信支付二维码的接口
    @Override
    public Map<String, Object> createWxQrcode(String orderNo) {
    
    
        try {
    
    
            //1、根据订单号查询订单信息
            QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
            wrapper.eq("order_no", orderNo);
            TOrder tOrder = tOrderService.getOne(wrapper);

            //2、使用map来设置生成二维码需要的参数
            HashMap<String, String> map = new HashMap<>();
            map.put("appid", "wx74862e0dfcf69954");//支付id
            map.put("mch_id", "1558950191");//商户号
            map.put("nonce_str", WXPayUtil.generateNonceStr());//生成随机的字符串,让每次生成的二维码不一样
            map.put("body", tOrder.getCourseTitle());//生成二维码的名字
            map.put("out_trade_no", orderNo);//二维码唯一的标识
            map.put("total_fee", tOrder.getTotalFee().multiply(new BigDecimal("100")).longValue() + "");//支付金额
            map.put("spbill_create_ip", "127.0.0.1");//现在进行支付的ip地址,实际项目使用项目的域名
            map.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify");//支付后回调地址
            map.put("trade_type", "NATIVE");//支付类型,NATIVE:根据价格生成二维码

            //3、发送httpClient请求,传递参数【xml格式】,微信支付提供的固定地址
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
            //参数1:要转换为xml格式的map
            //参数2:商户的key,用于加密二维码中的信息
            client.setXmlParam(WXPayUtil.generateSignedXml(map, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));
            client.setHttps(true);//上面发送请求的是https。默认支持,需要设置为true支持
            //执行post请求发送
            client.post();

            //4、得到发送请求返回的结果
            //返回的结果是xml格式的
            String content = client.getContent();

            //把xml格式转换为map集合,他里面的数据不全
            Map<String, String> resultMap = WXPayUtil.xmlToMap(content);

            //最终返回数据封装
            HashMap hashMap = new HashMap<>();
            hashMap.put("out_trade_no", orderNo);
            hashMap.put("course_id", tOrder.getCourseId());
            hashMap.put("total_fee", tOrder.getTotalFee());
            hashMap.put("result_code", resultMap.get("result_code")); //二维码操作状态码
            hashMap.put("code_url", resultMap.get("code_url")); //二维码地址

            //微信支付二维码2小时过期,可采取2小时未支付取消订单
            //redisTemplate.opsForValue().set(orderNo, hashMap, 120,TimeUnit.MINUTES);
            return hashMap;

        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new LaptoyException(20001, "生成二维码失败");
        }
    }
}

六、接口 获取订单状态

1、TPayLogController

//根据订单号查询订单支付状态
@GetMapping("/queryPayStatus/{orderNo}")
public R queryPayStatus(@PathVariable String orderNo){
    
    

    Map<String,String> map = tPayLogService.queryPayStatus(orderNo);
    if (map==null){
    
    
        return R.error().message("支付出错了");
    }
    //如果返回的map不为空,通过map获取订单的状态
    if (map.get("trade_state").equals("SUCCESS")){
    
     //支付成功
        //添加记录到支付表里,并更新订单表的状态
        tPayLogService.updateOrderStatus(map);
        return R.ok().message("支付成功");
    }

    return R.ok().message("支付中");
}

2、业务层

//根据订单号,查询支付的状态
@Override
public Map<String, String> queryPayStatus(String orderNo) {
    
    
    try {
    
    
        //1、封装参数
        Map m = new HashMap<>();
        m.put("appid", "wx74862e0dfcf69954");
        m.put("mch_id", "1558950191");
        m.put("out_trade_no", orderNo);
        m.put("nonce_str", WXPayUtil.generateNonceStr());

        //2、发送httpClient请求
        HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
        client.setHttps(true);
        client.setXmlParam(WXPayUtil.generateSignedXml(m, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));//通过商户key加密
        client.post();

        //3、返回第三方的数据
        String xml = client.getContent();
        //4、转成Map
        Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);

        //5、返回
        return resultMap;
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return null;
}

//向支付表中添加支付记录,并更新订单表的订单状态
@Override
public void updateOrderStatus(Map<String, String> map) {
    
    
    //获取前一步生成二维码中的订单号
    String orderNo = map.get("out_trade_no");
    //根据订单号,查询订单信息
    QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
    wrapper.eq("order_no", orderNo);
    TOrder tOrder = tOrderService.getOne(wrapper);
    
    //判断订单状态是否为1,为1就是支付过了
    if (tOrder.getStatus().intValue() == 1) {
    
    
        return;
    }

    //更新订单表中的叮当状态
    tOrder.setStatus(1);//1代表已支付
    tOrderService.updateById(tOrder);

    //向支付表里添加支付记录
    TPayLog tPayLog = new TPayLog();
    tPayLog.setOrderNo(orderNo);//支付订单号
    tPayLog.setPayTime(new Date());//支付时间
    tPayLog.setPayType(1);//支付类型
    tPayLog.setTotalFee(tOrder.getTotalFee());//总金额(分)
    tPayLog.setTradeState(map.get("trade_state"));//支付状态
    tPayLog.setTransactionId(map.get("transaction_id"));//订单流水号
    tPayLog.setAttr(JSONObject.toJSONString(map));
    baseMapper.insert(tPayLog);
}

七、整合生成订单页面

7.1、页面样式

1、将assets文件夹的内容复制到当前项目assets(在资料中找,部分重复跳过即可)
在这里插入图片描述

2、修改default.vue页面

import '~/assets/css/reset.css'
import '~/assets/css/theme.css'
import '~/assets/css/global.css'
import '~/assets/css/web.css'
import '~/assets/css/base.css'
import '~/assets/css/activity_tab.css'
import '~/assets/css/bottom_rec.css'
import '~/assets/css/nice_select.css'
import '~/assets/css/order.css'
import '~/assets/css/swiper-3.3.1.min.css'
import "~/assets/css/pages-weixinpay.css"

3、course/_id.vue页面样式冲突(删除以下单词)
在这里插入图片描述


7.2、课程支付前端

1、api/order.js

import request from '@/utils/request'

export default {
    
    
    //生成订单
    createOrder(courseId) {
    
    
        return request({
    
    
            url: '/order/t-order/createOrder/' + courseId,
            method: 'post'
        })
    },
    //根据订单号查询订单信息
    getOrderInfoByNo(orderNo) {
    
    
        return request({
    
    
            url: `/order/t-order/getOrderInfoById/${
      
      orderNo}`,
            method: 'get'
        })
    },
    //根据订单号,生产二维码
    createWxQRcode(orderNo) {
    
    
        return request({
    
    
            url: `/order/t-pay-log/createWxQRcode/${
      
      orderNo}`,
            method: 'get'
        })
    },
    //根据订单号,查询订单支付状态
    getPayStatus(orderNo) {
    
    
        return request({
    
    
            url: `/order/t-pay-log/queryPayStatus/${
      
      orderNo}`,
            method: 'get'
        })
    },
}

2、course/_id.vue 添加点击事件并添加方法

在这里插入图片描述

// 生成订单的方法
createOrder() {
    
    
  orderApi.createOrder(this.course.courseId).then(resp => {
    
    
    //获取返回订单号
    console.log(resp.data.data.orderNo)
    //跳转订单显示页面
    this.$router.push({
    
     path: '/order/' + resp.data.data.orderNo })
  })
},

3、创建订单页面,显示订单信息

在pages下面创建order文件夹,创建 _orderNo.vue 页面

<template>
  <div class="Page Confirm">
    <div class="Title">
      <h1 class="fl f18">订单确认</h1>
      <img src="~/assets/img/cart_setp2.png" class="fr" />
      <div class="clear"></div>
    </div>
    <form name="flowForm" id="flowForm" method="post" action="">
      <table class="GoodList">
        <tbody>
          <tr>
            <th class="name">商品</th>
            <th class="price">原价</th>
            <th class="priceNew">价格</th>
          </tr>
        </tbody>
        <tbody>
          <!-- <tr>
                <td colspan="3" class="Title red f18 fb"><p>限时折扣</p></td>
          </tr> -->
          <tr>
            <td colspan="3" class="teacher">讲师:{
   
   { order.teacherName }}</td>
          </tr>
          <tr class="good">
            <td class="name First">
              <a target="_blank" :href="'https://localhost:3000/course/' + order.courseId">
                <img :src="order.courseCover" /></a>
              <div class="goodInfo">
                <input type="hidden" class="ids ids_14502" value="14502" />
                <a target="_blank" :href="'https://localhost:3000/course/' + order.courseId">{
   
   { order.courseTitle }}</a>
              </div>
            </td>
            <td class="price">
              <p><strong>{
   
   { order.totalFee }}</strong>
              </p>
              <!-- <span class="discName red">限时8折</span> -->
            </td>
            <td class="red priceNew Last"><strong>{
   
   { order.totalFee }}</strong>
            </td>
          </tr>
          <tr>
            <td class="Billing tr" colspan="3">
              <div class="tr">
                <p><strong class="red">1</strong> 件商品,合计<span class="red f20"><strong>{
   
   { order.totalFee }}</strong></span>
                </p>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
      <div class="Finish">
        <div class="fr" id="AgreeDiv">
          <label for="Agree">
            <p class="on">
              <input type="checkbox" checked="checked" />我已阅读并同意<a href="javascript:" target="_blank">《谷粒学院购买 协议》</a>
            </p>
          </label>
        </div>
        <div class="clear"></div>
        <div class="Main fl">
          <div class="fl">
            <a :href="'/course/' + order.courseId">返回课程详情页</a>
          </div>
          <div class="fr">
            <p><strong class="red">1</strong> 件商品,合计<span class="red f20"><strong id="AllPrice">{
   
   { order.totalFee }}</strong></span>
            </p>
          </div>
        </div>
        <input name="score" value="0" type="hidden" id="usedScore" />
        <button class="fr redb" type="button" id="submitPay" @click="toPay()">
          去支 付
        </button>
        <div class="clear"></div>
      </div>
    </form>
  </div>
</template>
<script>
import orderApi from '@/api/order'
export default {
      
      
  created() {
      
      
    this.orderNo = this.$route.params.orderNo
    this.getOrderInfoByOrderNo()
  },
  methods: {
      
      
    getOrderInfoByOrderNo() {
      
      
      orderApi.getOrderInfoByNo(this.orderNo).then(resp => {
      
      
        this.order = resp.data.data.data
      })
    },
    toPay() {
      
      
      orderApi.createWxQRcode().then(resp => {
      
      
        this.$router.push({
      
       path: '/pay/' + this.orderNo })
      })
    }
  },
  data() {
      
      
    return {
      
      
      order: {
      
      },
      orderNo: '',
    }
  },
}
</script>

在这里插入图片描述


7.3、支付二维码前端

1、创建pages/pay/_pid.vue

<template>
  <div class="cart py-container">
    <!--主内容-->
    <div class="checkout py-container pay">
      <div class="checkout-tit">
        <h4 class="fl tit-txt">
          <span class="success-icon"></span><span class="success-info">订单提交成功,请您及时付款!订单 号:{
   
   {payObj.out_trade_no}}</span>
        </h4>
        <span class="fr"><em class="sui-lead">应付金额:</em><em class="orange money">¥{
   
   { payObj.total_fee }}</em></span>
        <div class="clearfix"></div>
      </div>
      <div class="checkout-steps">
        <div class="fl weixin">微信支付</div>
        <div class="fl sao">
          <p class="red">请使用微信扫一扫。</p>
          <div class="fl code">
            <!-- <img id="qrious" src="~/assets/img/erweima.png" alt=""> -->
            <!-- <qriously value="weixin://wxpay/bizpayurl?pr=R7tnDpZ":size="338"/> -->
            <!--code_url为二维码地址,这里需要通过一个vue的组件【qriously】显示-->
            <!--执行npm install vue-qriously下载组件-->
            <qriously :value="payObj.code_url" :size="338" />
            <div class="saosao">
              <p>请使用微信扫一扫</p>
              <p>扫描二维码支付</p>
            </div>
          </div>
        </div>
        <div class="clearfix"></div>
        <!-- <p><a href="pay.html" target="_blank">> 其他支付方式</a></p> -->
      </div>
    </div>
  </div>
</template>
<script>
import orderApi from "@/api/order";
export default {
      
      
  asyncData({
       
        params, error }) {
      
      
    return orderApi.createWxQRcode(params.pid)
      .then(response => {
      
      
        return {
      
      
          payObj: response.data.data
        }
      })
  },
  data() {
      
      
    return {
      
      
      timer1: ''
    }
  },
  //每隔三秒调用一次查询订单状态的方法
  mounted() {
      
      //页面渲染之后执行
    this.timer1 = setInterval(() => {
      
      
      this.queryOrderStatus(this.payObj.out_trade_no)
    }, 3000);
  },
  methods: {
      
      
    queryOrderStatus(orderNo) {
      
      
      orderApi.getPayStatus(orderNo)
        .then(response => {
      
      
          if (response.data.success) {
      
      
            //支付成功,清除定时器
            clearInterval(this.timer1)
            //提示
            this.$message({
      
      
              type: 'success',
              message: '支付成功!'
            })
            //跳转回到课程详情页面
            this.$router.push({
      
       path: '/course/' + this.payObj.course_id })
          }
        })
    }
  }
};
</script>

2、utils/request 新增响应拦截器

import axios from 'axios'
import {
    
     MessageBox, Message } from 'element-ui'
import cookie from 'js-cookie'

// 创建axios实例
const service = axios.create({
    
    
    baseURL: 'http://localhost:9001', // api的base_url
    timeout: 20000 // 请求超时时间
})

//第三步 创建拦截器  http request 拦截器
service.interceptors.request.use(
    config => {
    
    
        //debugger
        //判断cookie里面是否有名称是guli_token数据
        if (cookie.get('guli_token')) {
    
    
            //把获取cookie值放到header里面
            config.headers['token'] = cookie.get('guli_token');
        }
        return config
    },
    err => {
    
    
        return Promise.reject(err);
    })

// http response 拦截器
service.interceptors.response.use(
    response => {
    
    
        //debugger
        if (response.data.code == 28004) {
    
    
            console.log("response.data.resultCode是28004")
            // 返回 错误代码-1 清除ticket信息并跳转到登录页面
            //debugger
            window.location.href = "/login"
            return
        } else {
    
    
            if (response.data.code !== 20000) {
    
    
                //25000:订单支付中,不做任何提示
                if (response.data.code != 25000) {
    
    
                    Message({
    
    
                        message: response.data.message || 'error',
                        type: 'error',
                        duration: 5 * 1000
                    })
                }
            } else {
    
    
                return response;
            }
        }
    },
    error => {
    
    
        return Promise.reject(error.response)   // 返回接口返回的错误信息
    });

export default service

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


八、课程详情页面完善

8.1、修改课程详情接口

在这里插入图片描述

1、order模块添加接口

//根据【用户id、课程id】查询订单表中的状态
@GetMapping("/isBuyCourse/{memberId}/{courseId}")
public Boolean isBuyCourse(@PathVariable String memberId, @PathVariable String courseId) {
    
    
    QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
    wrapper.eq("course_id", courseId);
    wrapper.eq("member_id", memberId);
    wrapper.eq("status", 1);//支付状态 【1】代表已支付
    int result = tOrderService.count(wrapper);

    if (result > 0) {
    
    //已支付
        return true;
    } else {
    
    
        return false;
    }
}

2、edu模块添加远程接口

@Component
@FeignClient(value = "service-order",fallback = TOrderClientImpl.class)
public interface TOrderClient {
    
    

    //根据【用户id、课程id】查询订单表中的状态
    @GetMapping("/order/t-order/isBuyCourse/{memberId}/{courseId}")
    public Boolean isBuyCourse(@PathVariable("memberId") String memberId, @PathVariable("courseId") String courseId);
}
@Component
public class TOrderClientImpl implements TOrderClient {
    
    
    @Override
    public Boolean isBuyCourse(String memberId, String courseId) {
    
    
        return null;
    }
}

3、修改之前的查询课程详情的接口 CourseFrontController

在这里插入图片描述


8.2、修改课程详情页面

在这里插入图片描述


8.3、展示

免费的显示立即观看

在这里插入图片描述

支付过的显示立即观看

在这里插入图片描述

在这里插入图片描述

未支付的显示立即购买

在这里插入图片描述


猜你喜欢

转载自blog.csdn.net/apple_53947466/article/details/126210750
今日推荐