支付功能
后端
支付流程图
- 课程分为免费课程和付费课程,如果是免费课程可以直接观看,如果是付费观看的课程,用户需下单支付后才可以观看

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

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

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

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

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

搭建环境
创建service-order工程,导入依赖
<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>
配置文件
server:port: 8007 # 服务端口spring:application:name: service-order # 微服务名称profiles:active: dev # 设置为开发环境datasource: # 配置数据源driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/guli_edu?characterEncoding=utf-8&serverTimezone=GMT%2B8username: rootpassword: rootjackson: # 配置json全局时间date-format: yyyy-MM-dd HH:mm:ss # 配置返回json的时间格式time-zone: GMT+8 # json是格林尼治时间,和我们相差8小时,需要加上8cloud:nacos:discovery:server-addr: 127.0.0.1:8848 # nacos服务地址redis:host: 192.168.241.130 # ip地址port: 6379 # 端口号database: 0timeout: 1800000 # 超时时间lettuce:pool:max-active: 20max-wait: -1 # 最大阻塞等待时间(负数表示没限制)max-idle: 5min-idle: 0mybatis-plus: # mybatis-plus日志configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:com/atguigu/orderservice/mapper/xml/*.xmlfeign:hystrix:enabled: true # 开启熔断机制wx:pay:appId: wx74862e0dfcf69954 #关联的公众号appidpartner: 1558950191 #商户号partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb # 商户keynotifyurl: http://guli.shop/api/order/weixinPay/weixinNotifyspbillCreateIp: 127.0.0.1sendAddress: https://api.mch.weixin.qq.com/pay/unifiedorder # wx提供获取二维码地址的请求地址sendAddressPay: https://api.mch.weixin.qq.com/pay/orderquery # wx提供支付的地址
主启动类
@SpringBootApplication@MapperScan("com.atguigu.orderservice.mapper")@ComponentScan("com.atguigu")@EnableDiscoveryClient // 开启nacos注册@EnableFeignClientspublic class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}}
代码生成器生成项目结构,表名为t_order,注意添加自动填充字段
生成订单
controller创建订单接口
@PostMapping("saveOrder/{courseId}")public ResultEntity saveOrder(@PathVariable String courseId, HttpServletRequest request) {// 获取用户idString memberId = JwtUtils.getMemberIdByJwtToken(request);String orderId = orderService.saveOrder(courseId, memberId);return ResultEntity.ok().data("orderId", orderId);}
common工程中,创建DO类传输数据
@Data@ApiModel(value = "CourseInfo信息对象用于订单", description = "传输CourseInfo信息")public class CourseInfoDO {@ApiModelProperty(value = "课程讲师ID")private String teacherId;@ApiModelProperty(value = "课程标题")private String title;@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")private BigDecimal price;@ApiModelProperty(value = "总课时")private Integer lessonNum;@ApiModelProperty(value = "课程封面图片路径")private String cover;@ApiModelProperty(value = "课程简介")private String description;}
```java @Data @ApiModel(value = “TeacherInfo信息对象用于订单和评论”, description = “传输TeacherInfo信息”) public class TeacherInfoDO {
@ApiModelProperty(value = “讲师姓名”) private String name;
}
- service_edu工程通过课程id查询课程信息接口```java@GetMapping("getCourseInfoByCourseId/{courseId}")public CourseInfoDO getCourseInfoByCourseId(@PathVariable String courseId) {CourseInfoVO courseInfoVO = eduCourseService.getCourseInfoVO(courseId);CourseInfoDO courseInfoDO = new CourseInfoDO();BeanUtils.copyProperties(courseInfoVO, courseInfoDO);return courseInfoDO;}
service_ucenter查询信息
// 用户id获取用户信息@GetMapping("getLoginInfoById/{memberId}")public LoginInfoDO getLoginInfoById(@PathVariable String memberId) {UcenterMember ucenterMember = ucenterMemberService.getById(memberId);LoginInfoDO loginInfoDO = new LoginInfoDO();BeanUtils.copyProperties(ucenterMember, loginInfoDO);return loginInfoDO;}
代理接口
@Component@FeignClient(name = "service-edu", fallback = com.atguigu.orderservice.client.EduDegradeFeignClient.class)public interface EduClient {@GetMapping("/eduservice/front-course/getCourseInfoByCourseId/{courseId}")public CourseInfoDO getCourseInfoByCourseId(@PathVariable("courseId") String courseId);@GetMapping("/eduservice/front-teacher/getTeacherName/{teacherId}")public TeacherInfoDO getTeacherName(@PathVariable("teacherId") String teacherId);}
@Component@FeignClient(name = "service-ucenter", fallback = com.atguigu.orderservice.client.MemberDegradeFeignClient.class)public interface MemberClient {// 用户id获取用户信息@GetMapping("/ucenterservice/ucenter-member/getLoginInfoById/{memberId}")public LoginInfoDO getLoginInfoById(@PathVariable("memberId") String memberId);}
impl实现订单生成
@Service@Transactional(readOnly = true)public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Autowiredprivate EduClient eduClient;@Autowiredprivate MemberClient memberClient;@Override@Transactional(readOnly = false, rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public String saveOrder(String courseId, String memberId) {// 调用member模块获取用户信息LoginInfoDO loginInfoById = memberClient.getLoginInfoById(memberId);// 调用edu模块获取课程信息CourseInfoDO courseInfoByCourseId = eduClient.getCourseInfoByCourseId(courseId);// 调用edu模块获取讲师NameTeacherInfoDO teacherName = eduClient.getTeacherName(courseInfoByCourseId.getTeacherId());//创建订单Order order = new Order();order.setOrderNo(OrderNoUtil.getOrderNo()); // 通过工具类生成订单idorder.setCourseId(courseId);order.setCourseTitle(courseInfoByCourseId.getTitle());order.setCourseCover(courseInfoByCourseId.getCover());order.setTeacherName(teacherName.getName());order.setTotalFee(courseInfoByCourseId.getPrice());order.setMemberId(memberId);order.setMobile(loginInfoById.getMobile());order.setNickname(loginInfoById.getNickname());order.setStatus(0);order.setPayType(1); // 1代表微信支付,默认为1baseMapper.insert(order);return order.getOrderNo(); // 返回order_no,注意不是id}}
通过订单id获取订单
controller
@GetMapping("getOrderInfoByOrederNo/{orderNo}")public ResultEntity getOrderInfoByOrderNo(@PathVariable String orderNo) {Order order = orderService.getOrderByOrderNo(orderNo);return ResultEntity.ok().data("order", order);}
impl实现
@Overridepublic Order getOrderByOrderNo(String orderNo) {QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();orderQueryWrapper.eq("order_no", orderNo);Order order = baseMapper.selectOne(orderQueryWrapper);return order;}
生成微信支付二维码
controller接口
@RestController@RequestMapping("/orderservice/pay-log")//@CrossOriginpublic class PayLogController {@Autowiredprivate PayLogService payLogService;// 生成二维码@GetMapping("createNative/{orderId}")public ResultEntity createNative(@PathVariable String orderId) {Map map = payLogService.createNative(orderId);return ResultEntity.ok().data(map);}// 根据订单号查询支付状态@GetMapping("getOrderStatus/{orderId}")public ResultEntity getOrderStatus(@PathVariable String orderId) {// 获取返回的结果集Map map = payLogService.queryPayStatus(orderId);if (map == null) {return ResultEntity.error().message("支付失败!");}// 支付成功if (map.get("trade_state").equals("SUCCESS")) {// 修改订单的状态payLogService.updateOrderStatus(map);return ResultEntity.ok().message("支付成功!");}return ResultEntity.ok().code(25000).message("正在支付!");}}
service
@Servicepublic class PayLogServiceImpl extends ServiceImpl<PayLogMapper, PayLog> implements PayLogService {@Autowiredprivate OrderService orderService;@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic Map createNative(String orderId) {try {// 根据订单id获取订单信息QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();orderQueryWrapper.eq("order_no", orderId);Order order = orderService.getOne(orderQueryWrapper);HashMap<String, String> map = new HashMap<>();//1、设置支付参数map.put("appid", ConstantPayUtil.APP_ID);map.put("mch_id", ConstantPayUtil.PARTNER);map.put("nonce_str", WXPayUtil.generateNonceStr());map.put("body", order.getCourseTitle());map.put("out_trade_no", orderId);map.put("total_fee", order.getTotalFee().multiply(new BigDecimal("100")).longValue() + "");map.put("spbill_create_ip", ConstantPayUtil.SPBILL_CREATE_IP);map.put("notify_url", ConstantPayUtil.NOTIFY_URL);map.put("trade_type", "NATIVE");// httpclient根据url访问第三方接口,并返回数据HttpClient httpClient = new HttpClient(ConstantPayUtil.SEND_ADDRESS);// client设置参数httpClient.setXmlParam(WXPayUtil.generateSignedXml(map, ConstantPayUtil.PARTNER_KEY));httpClient.setHttps(true);httpClient.post();// 返回第三方数据String xml = httpClient.getContent();Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);HashMap m = new HashMap<>();m.put("out_trade_no", orderId);m.put("course_id", order.getCourseId());m.put("total_fee", order.getTotalFee());m.put("result_code", resultMap.get("result_code"));m.put("code_url", resultMap.get("code_url"));// 返回结果return m;} catch (Exception exception) {exception.printStackTrace();return new HashMap();}}@Overridepublic Map queryPayStatus(String orderId) {try {//1、封装参数Map map = new HashMap<>();map.put("appid", ConstantPayUtil.APP_ID);map.put("mch_id", ConstantPayUtil.PARTNER);map.put("out_trade_no", orderId);map.put("nonce_str", WXPayUtil.generateNonceStr());// 发送请求HttpClient httpClient = new HttpClient(ConstantPayUtil.SEND_ADDRESS_PAY);// 设置参数// 根据商户key生成xml密钥httpClient.setXmlParam(WXPayUtil.generateSignedXml(map, ConstantPayUtil.PARTNER_KEY));httpClient.setHttps(true);httpClient.post();// 返回第三方数据String xml = httpClient.getContent();Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);return resultMap;} catch (Exception exception) {exception.printStackTrace();return new HashMap();}}@Overridepublic void updateOrderStatus(Map<String, String> map) {String orderId = map.get("out_trade_no");if (StringUtils.isEmpty(orderId)) {throw new GuliException(20001, "订单不存在!");}Order order = orderService.getOrderByOrderNo(orderId);// 判断订单的状态if (order.getStatus().intValue() != 1) {order.setStatus(1);orderService.updateById(order);}// 保存支付日志PayLog payLog = new PayLog();payLog.setOrderNo(order.getOrderNo());//支付订单号payLog.setPayTime(new Date());payLog.setPayType(1); //支付类型payLog.setTotalFee(order.getTotalFee());//总金额(分)payLog.setTradeState(map.get("trade_state"));//支付状态payLog.setTransactionId(map.get("transaction_id"));payLog.setAttr(JSONObject.toJSONString(map));// 其他信息,使用json字符串的形式存储在数据库baseMapper.insert(payLog);//插入到支付日志表}}
