支付功能

后端

支付流程图

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

搭建环境

  • 创建service-order工程,导入依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>com.github.wxpay</groupId>
    4. <artifactId>wxpay-sdk</artifactId>
    5. <version>0.0.3</version>
    6. </dependency>
    7. <dependency>
    8. <groupId>com.alibaba</groupId>
    9. <artifactId>fastjson</artifactId>
    10. </dependency>
    11. </dependencies>
  • 配置文件

    1. server:
    2. port: 8007 # 服务端口
    3. spring:
    4. application:
    5. name: service-order # 微服务名称
    6. profiles:
    7. active: dev # 设置为开发环境
    8. datasource: # 配置数据源
    9. driver-class-name: com.mysql.cj.jdbc.Driver
    10. url: jdbc:mysql://localhost:3306/guli_edu?characterEncoding=utf-8&serverTimezone=GMT%2B8
    11. username: root
    12. password: root
    13. jackson: # 配置json全局时间
    14. date-format: yyyy-MM-dd HH:mm:ss # 配置返回json的时间格式
    15. time-zone: GMT+8 # json是格林尼治时间,和我们相差8小时,需要加上8
    16. cloud:
    17. nacos:
    18. discovery:
    19. server-addr: 127.0.0.1:8848 # nacos服务地址
    20. redis:
    21. host: 192.168.241.130 # ip地址
    22. port: 6379 # 端口号
    23. database: 0
    24. timeout: 1800000 # 超时时间
    25. lettuce:
    26. pool:
    27. max-active: 20
    28. max-wait: -1 # 最大阻塞等待时间(负数表示没限制)
    29. max-idle: 5
    30. min-idle: 0
    31. mybatis-plus: # mybatis-plus日志
    32. configuration:
    33. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    34. mapper-locations: classpath:com/atguigu/orderservice/mapper/xml/*.xml
    35. feign:
    36. hystrix:
    37. enabled: true # 开启熔断机制
    38. wx:
    39. pay:
    40. appId: wx74862e0dfcf69954 #关联的公众号appid
    41. partner: 1558950191 #商户号
    42. partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb # 商户key
    43. notifyurl: http://guli.shop/api/order/weixinPay/weixinNotify
    44. spbillCreateIp: 127.0.0.1
    45. sendAddress: https://api.mch.weixin.qq.com/pay/unifiedorder # wx提供获取二维码地址的请求地址
    46. sendAddressPay: https://api.mch.weixin.qq.com/pay/orderquery # wx提供支付的地址
  • 主启动类

    1. @SpringBootApplication
    2. @MapperScan("com.atguigu.orderservice.mapper")
    3. @ComponentScan("com.atguigu")
    4. @EnableDiscoveryClient // 开启nacos注册
    5. @EnableFeignClients
    6. public class OrderApplication {
    7. public static void main(String[] args) {
    8. SpringApplication.run(OrderApplication.class, args);
    9. }
    10. }
  • 代码生成器生成项目结构,表名为t_order,注意添加自动填充字段

生成订单

  • controller创建订单接口

    1. @PostMapping("saveOrder/{courseId}")
    2. public ResultEntity saveOrder(@PathVariable String courseId, HttpServletRequest request) {
    3. // 获取用户id
    4. String memberId = JwtUtils.getMemberIdByJwtToken(request);
    5. String orderId = orderService.saveOrder(courseId, memberId);
    6. return ResultEntity.ok().data("orderId", orderId);
    7. }
  • common工程中,创建DO类传输数据

    1. @Data
    2. @ApiModel(value = "CourseInfo信息对象用于订单", description = "传输CourseInfo信息")
    3. public class CourseInfoDO {
    4. @ApiModelProperty(value = "课程讲师ID")
    5. private String teacherId;
    6. @ApiModelProperty(value = "课程标题")
    7. private String title;
    8. @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
    9. private BigDecimal price;
    10. @ApiModelProperty(value = "总课时")
    11. private Integer lessonNum;
    12. @ApiModelProperty(value = "课程封面图片路径")
    13. private String cover;
    14. @ApiModelProperty(value = "课程简介")
    15. private String description;
    16. }

    ```java @Data @ApiModel(value = “TeacherInfo信息对象用于订单和评论”, description = “传输TeacherInfo信息”) public class TeacherInfoDO {

    @ApiModelProperty(value = “讲师姓名”) private String name;

}

  1. - service_edu工程通过课程id查询课程信息接口
  2. ```java
  3. @GetMapping("getCourseInfoByCourseId/{courseId}")
  4. public CourseInfoDO getCourseInfoByCourseId(@PathVariable String courseId) {
  5. CourseInfoVO courseInfoVO = eduCourseService.getCourseInfoVO(courseId);
  6. CourseInfoDO courseInfoDO = new CourseInfoDO();
  7. BeanUtils.copyProperties(courseInfoVO, courseInfoDO);
  8. return courseInfoDO;
  9. }
  • service_ucenter查询信息

    1. // 用户id获取用户信息
    2. @GetMapping("getLoginInfoById/{memberId}")
    3. public LoginInfoDO getLoginInfoById(@PathVariable String memberId) {
    4. UcenterMember ucenterMember = ucenterMemberService.getById(memberId);
    5. LoginInfoDO loginInfoDO = new LoginInfoDO();
    6. BeanUtils.copyProperties(ucenterMember, loginInfoDO);
    7. return loginInfoDO;
    8. }
  • 代理接口

    1. @Component
    2. @FeignClient(name = "service-edu", fallback = com.atguigu.orderservice.client.EduDegradeFeignClient.class)
    3. public interface EduClient {
    4. @GetMapping("/eduservice/front-course/getCourseInfoByCourseId/{courseId}")
    5. public CourseInfoDO getCourseInfoByCourseId(@PathVariable("courseId") String courseId);
    6. @GetMapping("/eduservice/front-teacher/getTeacherName/{teacherId}")
    7. public TeacherInfoDO getTeacherName(@PathVariable("teacherId") String teacherId);
    8. }
    1. @Component
    2. @FeignClient(name = "service-ucenter", fallback = com.atguigu.orderservice.client.MemberDegradeFeignClient.class)
    3. public interface MemberClient {
    4. // 用户id获取用户信息
    5. @GetMapping("/ucenterservice/ucenter-member/getLoginInfoById/{memberId}")
    6. public LoginInfoDO getLoginInfoById(@PathVariable("memberId") String memberId);
    7. }
  • impl实现订单生成

    1. @Service
    2. @Transactional(readOnly = true)
    3. public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
    4. @Autowired
    5. private EduClient eduClient;
    6. @Autowired
    7. private MemberClient memberClient;
    8. @Override
    9. @Transactional(readOnly = false, rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    10. public String saveOrder(String courseId, String memberId) {
    11. // 调用member模块获取用户信息
    12. LoginInfoDO loginInfoById = memberClient.getLoginInfoById(memberId);
    13. // 调用edu模块获取课程信息
    14. CourseInfoDO courseInfoByCourseId = eduClient.getCourseInfoByCourseId(courseId);
    15. // 调用edu模块获取讲师Name
    16. TeacherInfoDO teacherName = eduClient.getTeacherName(courseInfoByCourseId.getTeacherId());
    17. //创建订单
    18. Order order = new Order();
    19. order.setOrderNo(OrderNoUtil.getOrderNo()); // 通过工具类生成订单id
    20. order.setCourseId(courseId);
    21. order.setCourseTitle(courseInfoByCourseId.getTitle());
    22. order.setCourseCover(courseInfoByCourseId.getCover());
    23. order.setTeacherName(teacherName.getName());
    24. order.setTotalFee(courseInfoByCourseId.getPrice());
    25. order.setMemberId(memberId);
    26. order.setMobile(loginInfoById.getMobile());
    27. order.setNickname(loginInfoById.getNickname());
    28. order.setStatus(0);
    29. order.setPayType(1); // 1代表微信支付,默认为1
    30. baseMapper.insert(order);
    31. return order.getOrderNo(); // 返回order_no,注意不是id
    32. }
    33. }

通过订单id获取订单

  • controller

    1. @GetMapping("getOrderInfoByOrederNo/{orderNo}")
    2. public ResultEntity getOrderInfoByOrderNo(@PathVariable String orderNo) {
    3. Order order = orderService.getOrderByOrderNo(orderNo);
    4. return ResultEntity.ok().data("order", order);
    5. }
  • impl实现

    1. @Override
    2. public Order getOrderByOrderNo(String orderNo) {
    3. QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
    4. orderQueryWrapper.eq("order_no", orderNo);
    5. Order order = baseMapper.selectOne(orderQueryWrapper);
    6. return order;
    7. }

生成微信支付二维码

  • controller接口

    1. @RestController
    2. @RequestMapping("/orderservice/pay-log")
    3. //@CrossOrigin
    4. public class PayLogController {
    5. @Autowired
    6. private PayLogService payLogService;
    7. // 生成二维码
    8. @GetMapping("createNative/{orderId}")
    9. public ResultEntity createNative(@PathVariable String orderId) {
    10. Map map = payLogService.createNative(orderId);
    11. return ResultEntity.ok().data(map);
    12. }
    13. // 根据订单号查询支付状态
    14. @GetMapping("getOrderStatus/{orderId}")
    15. public ResultEntity getOrderStatus(@PathVariable String orderId) {
    16. // 获取返回的结果集
    17. Map map = payLogService.queryPayStatus(orderId);
    18. if (map == null) {
    19. return ResultEntity.error().message("支付失败!");
    20. }
    21. // 支付成功
    22. if (map.get("trade_state").equals("SUCCESS")) {
    23. // 修改订单的状态
    24. payLogService.updateOrderStatus(map);
    25. return ResultEntity.ok().message("支付成功!");
    26. }
    27. return ResultEntity.ok().code(25000).message("正在支付!");
    28. }
    29. }
  • service

    1. @Service
    2. public class PayLogServiceImpl extends ServiceImpl<PayLogMapper, PayLog> implements PayLogService {
    3. @Autowired
    4. private OrderService orderService;
    5. @Autowired
    6. private RedisTemplate redisTemplate;
    7. @Override
    8. public Map createNative(String orderId) {
    9. try {
    10. // 根据订单id获取订单信息
    11. QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
    12. orderQueryWrapper.eq("order_no", orderId);
    13. Order order = orderService.getOne(orderQueryWrapper);
    14. HashMap<String, String> map = new HashMap<>();
    15. //1、设置支付参数
    16. map.put("appid", ConstantPayUtil.APP_ID);
    17. map.put("mch_id", ConstantPayUtil.PARTNER);
    18. map.put("nonce_str", WXPayUtil.generateNonceStr());
    19. map.put("body", order.getCourseTitle());
    20. map.put("out_trade_no", orderId);
    21. map.put("total_fee", order.getTotalFee().multiply(new BigDecimal("100")).longValue() + "");
    22. map.put("spbill_create_ip", ConstantPayUtil.SPBILL_CREATE_IP);
    23. map.put("notify_url", ConstantPayUtil.NOTIFY_URL);
    24. map.put("trade_type", "NATIVE");
    25. // httpclient根据url访问第三方接口,并返回数据
    26. HttpClient httpClient = new HttpClient(ConstantPayUtil.SEND_ADDRESS);
    27. // client设置参数
    28. httpClient.setXmlParam(WXPayUtil.generateSignedXml(map, ConstantPayUtil.PARTNER_KEY));
    29. httpClient.setHttps(true);
    30. httpClient.post();
    31. // 返回第三方数据
    32. String xml = httpClient.getContent();
    33. Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
    34. HashMap m = new HashMap<>();
    35. m.put("out_trade_no", orderId);
    36. m.put("course_id", order.getCourseId());
    37. m.put("total_fee", order.getTotalFee());
    38. m.put("result_code", resultMap.get("result_code"));
    39. m.put("code_url", resultMap.get("code_url"));
    40. // 返回结果
    41. return m;
    42. } catch (Exception exception) {
    43. exception.printStackTrace();
    44. return new HashMap();
    45. }
    46. }
    47. @Override
    48. public Map queryPayStatus(String orderId) {
    49. try {
    50. //1、封装参数
    51. Map map = new HashMap<>();
    52. map.put("appid", ConstantPayUtil.APP_ID);
    53. map.put("mch_id", ConstantPayUtil.PARTNER);
    54. map.put("out_trade_no", orderId);
    55. map.put("nonce_str", WXPayUtil.generateNonceStr());
    56. // 发送请求
    57. HttpClient httpClient = new HttpClient(ConstantPayUtil.SEND_ADDRESS_PAY);
    58. // 设置参数
    59. // 根据商户key生成xml密钥
    60. httpClient.setXmlParam(WXPayUtil.generateSignedXml(map, ConstantPayUtil.PARTNER_KEY));
    61. httpClient.setHttps(true);
    62. httpClient.post();
    63. // 返回第三方数据
    64. String xml = httpClient.getContent();
    65. Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
    66. return resultMap;
    67. } catch (Exception exception) {
    68. exception.printStackTrace();
    69. return new HashMap();
    70. }
    71. }
    72. @Override
    73. public void updateOrderStatus(Map<String, String> map) {
    74. String orderId = map.get("out_trade_no");
    75. if (StringUtils.isEmpty(orderId)) {
    76. throw new GuliException(20001, "订单不存在!");
    77. }
    78. Order order = orderService.getOrderByOrderNo(orderId);
    79. // 判断订单的状态
    80. if (order.getStatus().intValue() != 1) {
    81. order.setStatus(1);
    82. orderService.updateById(order);
    83. }
    84. // 保存支付日志
    85. PayLog payLog = new PayLog();
    86. payLog.setOrderNo(order.getOrderNo());//支付订单号
    87. payLog.setPayTime(new Date());
    88. payLog.setPayType(1); //支付类型
    89. payLog.setTotalFee(order.getTotalFee());//总金额(分)
    90. payLog.setTradeState(map.get("trade_state"));//支付状态
    91. payLog.setTransactionId(map.get("transaction_id"));
    92. payLog.setAttr(JSONObject.toJSONString(map));// 其他信息,使用json字符串的形式存储在数据库
    93. baseMapper.insert(payLog);//插入到支付日志表
    94. }
    95. }