支付功能
后端
支付流程图
- 课程分为免费课程和付费课程,如果是免费课程可以直接观看,如果是付费观看的课程,用户需下单支付后才可以观看
- 如果是免费课程,在用户选择课程,进入到课程详情页面时候,直接显示 “立即观看”,用户点击立即观看,可以切换到播放列表进行视频播放
- 如果是付费课程,在用户选择课程,进入到课程详情页面时候,会显示 “立即购买”
- 点击“立即购买”,会生成课程的订单,跳转到订单页面
- 点击“去支付”,会跳转到支付页面,生成微信扫描的二维码
- 使用微信扫描支付后,会跳转回到课程详情页面,同时显示“立即观看
搭建环境
创建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.Driver
url: jdbc:mysql://localhost:3306/guli_edu?characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: root
jackson: # 配置json全局时间
date-format: yyyy-MM-dd HH:mm:ss # 配置返回json的时间格式
time-zone: GMT+8 # json是格林尼治时间,和我们相差8小时,需要加上8
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务地址
redis:
host: 192.168.241.130 # ip地址
port: 6379 # 端口号
database: 0
timeout: 1800000 # 超时时间
lettuce:
pool:
max-active: 20
max-wait: -1 # 最大阻塞等待时间(负数表示没限制)
max-idle: 5
min-idle: 0
mybatis-plus: # mybatis-plus日志
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:com/atguigu/orderservice/mapper/xml/*.xml
feign:
hystrix:
enabled: true # 开启熔断机制
wx:
pay:
appId: wx74862e0dfcf69954 #关联的公众号appid
partner: 1558950191 #商户号
partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb # 商户key
notifyurl: http://guli.shop/api/order/weixinPay/weixinNotify
spbillCreateIp: 127.0.0.1
sendAddress: 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注册
@EnableFeignClients
public 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) {
// 获取用户id
String 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 {
@Autowired
private EduClient eduClient;
@Autowired
private 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模块获取讲师Name
TeacherInfoDO teacherName = eduClient.getTeacherName(courseInfoByCourseId.getTeacherId());
//创建订单
Order order = new Order();
order.setOrderNo(OrderNoUtil.getOrderNo()); // 通过工具类生成订单id
order.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代表微信支付,默认为1
baseMapper.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实现
@Override
public 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")
//@CrossOrigin
public class PayLogController {
@Autowired
private 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
@Service
public class PayLogServiceImpl extends ServiceImpl<PayLogMapper, PayLog> implements PayLogService {
@Autowired
private OrderService orderService;
@Autowired
private RedisTemplate redisTemplate;
@Override
public 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();
}
}
@Override
public 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();
}
}
@Override
public 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);//插入到支付日志表
}
}