目录
- Native 下单
- 1、创建课程订单保存到数据库
- 1-1:需求:
- 1-2:代码:
- 1-3:测试结果:
- 2、保存支付二维码的url
- 2-1:需求:
- 2-2:代码:
- 2-3:测试:
- 2-4:完整代码:
- 后端:
- WxPayController
- WxPayService
- WxPayServiceImpl
- OrderInfoService
- OrderInfoServiceImpl
- 3、显示订单列表
- 3-1:需求:
- 3-2:代码:
- 前端:
- 后端:
- 3-3:测试:
- 查看swagger
- 查看订单列表
- 3-4:完整代码
- 后端:
- OrderInfoController
- OrderInfoService
- OrderInfoServiceImpl
Native 下单
1、创建课程订单保存到数据库
1-1:需求:
之前的下单,只是获取支付二维码,但是并没有将订单存到数据库
需求1:点击确认支付后,创建商品的订单存到数据库
需求2:每次确认支付之前,要判断这个人是否存在已下单未支付的订单,有的话就不用再创建订单了,把他的订单查询出来给他就行。
1-2:代码:
需求1:点击确认支付后,创建商品的订单存到数据库
需求2:每次确认支付之前,要判断这个人是否存在已下单未支付的订单,有的话就不用再创建订单了,把他的订单查询出来给他就行。
1-3:测试结果:
成功在数据库添加订单,并且多次点击确认下单,并不会重复添加订单到数据库
2、保存支付二维码的url
2-1:需求:
上面创建订单的时候,是没有存二维码的url到数据库的,这里需要在创建订单的时候,把url存进去。
Native调起支付
2-2:代码:
解释:
因为获取支付二维码url的代码在创建订单之后,所以第一次创建订单是没有支付二维码的url的。
所以在往下的代码中,添加了保存二维码的代码。
2-3:测试:
保存二维码成功,并且在重复访问的时候,因为存在二维码,所以不会再去调用微信的下单接口。
因为二维码有效期为2小时,所以后面还需要优化,如果二维码过期,需要再次更新数据库中的二维码。
2-4:完整代码:
包含创建订单和保存支付二维码的代码
后端:
WxPayController
@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API") //swagger 注解
@Slf4j
public class WxPayController
{
@Resource
private WxPayService wxPayService;
//调用统一下单API,生成支付二维码的链接和订单号
//swagger注解
@ApiOperation("调用统一下单API,生成支付二维码")
@PostMapping("/native/{productId}")
public R nativePay(@PathVariable Long productId) throws Exception
{
log.info("发起支付请求");
//返回支付二维码的链接和订单号
Map<String,Object> map = wxPayService.nativePay(productId);
return R.ok().setData(map);
}
}
WxPayService
public interface WxPayService
{
//调用统一下单API,生成支付二维码的链接和订单号
Map<String, Object> nativePay(Long productId) throws Exception;
}
WxPayServiceImpl
//创建订单,调用 Native 支付接口
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService
{
@Resource
private WxPayConfig wxPayConfig;
/* 原本应该注 入WxPayConfig 这个类,然后调用 getWxPayClient() 方法获取 HttpClient请求对象
* 但是因为 getWxPayClient() 方法加了@Bean注解,交给了spring容器管理,所以项目启动的时候就会执行这个方法,
* 就会存在返回值为 CloseableHttpClient 类型的 HttpClient请求对象
* 所以这里可以直接注入这个 CloseableHttpClient 对象
*/
@Resource
private CloseableHttpClient wxPayClient;
@Resource
private OrderInfoService orderInfoService;
/**
* 创建订单,调用 Native 支付接口
*
* @param productId 商品id
* @return code_url 和 订单号
* @throws Exception
*/
//调用统一下单API,生成支付二维码的链接和订单号
@Override
public Map<String, Object> nativePay(Long productId) throws Exception
{
//生成订单
OrderInfo orderInfo = orderInfoService.createOrderInfoByProduct(productId);
//获取二维码url-----如果是第一次生成订单,那么这个订单是没有二维码url的
String codeUrl = orderInfo.getCodeUrl();
//判断--如果订单存在,并且二维码的url也存在,那么就不需要再去调用微信的下单接口来获取支付二维码了
if (orderInfo != null && !StringUtils.isEmpty(codeUrl))
{
//创建一个包含url和订单号的返回值
Map<String, Object> map = new HashMap<>();
map.put("codeUrl", codeUrl);
map.put("orderNo", orderInfo.getOrderNo());
return map;
}
/*
* 官方提供的 Native下单 接口
* 支持商户:【普通商户】
* 请求方式:【POST】/v3/pay/transactions/native
* 请求域名:【主域名】https://api.mch.weixin.qq.com
* "https://api.mch.weixin.qq.com/v3/pay/transactions/native" 改成
* wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType())
*/
log.info("调用统一下单API.....");
//调用统一下单API---拷贝官网的实例代码进行修改---统一下单的接口地址
//封装统一下单API 的url
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
// 请求body参数---------调用接口需要的参数
Gson gson = new Gson();
//数据类型不固定,所以就不写泛型了
Map paramsMap = new HashMap();
//设置参数 --- 根据官网要求设置对应的参数
paramsMap.put("appid", wxPayConfig.getAppid()); //公众号ID
paramsMap.put("mchid", wxPayConfig.getMchId()); //直连商户号
paramsMap.put("description", orderInfo.getTitle()); // 商品描述
paramsMap.put("out_trade_no", orderInfo.getOrderNo()); //商户订单号
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType())); //通知地址
//订单金额有两个参数--嵌套数据
Map amountMap = new HashMap();
amountMap.put("total", orderInfo.getTotalFee()); //总金额
amountMap.put("currency", "CNY"); //货币类型
paramsMap.put("amount", amountMap); // 订单金额
//将参数转成字符串
String jsonParams = gson.toJson(paramsMap);
log.info("支付的请求参数:" + jsonParams);
//把参数设置到请求体当中
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
//希望得到的响应类型
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpPost);
//这些就是对调用下单方法的响应结果的处理了
try
{
//字符串形式的响应体
String bodyAsString = EntityUtils.toString(response.getEntity());
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200)
{ //处理成功
System.out.println("成功, 返回结果 = " + bodyAsString);
} else if (statusCode == 204)
{ //处理成功,无返回Body
System.out.println("成功");
} else
{
System.out.println("下单失败, 响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
throw new IOException("请求失败 request failed");
}
//响应结果---如果下单成功,获取响应结果, gson.fromJson()用于将 JSON 字符串转换为 Java 对象
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二维码---从返回的结果中获取二维码的url, 从官网看出 code_url 是 二维码的key
codeUrl = resultMap.get("code_url");
//保存二维码
String orderNo = orderInfo.getOrderNo();
orderInfoService.saveCodeUrl(orderNo,codeUrl);
//创建一个包含url和订单号的返回值
Map<String, Object> map = new HashMap<>();
map.put("codeUrl", codeUrl);
map.put("orderNo", orderInfo.getOrderNo());
return map;
} finally
{
response.close();
}
}
}
OrderInfoService
public interface OrderInfoService extends IService<OrderInfo> {
/**
* 根据商品id创建商品订单
* @param productId 商品id
* @return 订单对象
*/
OrderInfo createOrderInfoByProduct(Long productId);
/**
* 将支付二维码的url存到订单中
* @param orderNo 订单编号
* @param codeUrl 支付二维码的地址url
*/
void saveCodeUrl(String orderNo, String codeUrl);
}
OrderInfoServiceImpl
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService
{
@Resource
private ProductMapper productMapper;
@Resource
private OrderInfoMapper orderInfoMapper;
//创建商品订单
@Override
public OrderInfo createOrderInfoByProduct(Long productId)
{
//用户点击确认支付,要先查找该用户是否存在已下单未支付的订单
OrderInfo orderInfo = this.getNoPayOrderByProductId(productId);
if (orderInfo != null)
{
//表示该用户已经下单了,还没有支付,那就直接把他的订单返回回去就行了,否则就创建新订单
return orderInfo;
}
//根据商品的id获取到该商品对象数据
Product product = productMapper.selectById(productId);
orderInfo = new OrderInfo();
orderInfo.setTitle(product.getTitle()); //订单标题
orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //生成订单号
orderInfo.setProductId(productId); //商品id
orderInfo.setTotalFee(1); //单位是:分
orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType()); //支付状态
//把商品订单存到数据库
orderInfoMapper.insert(orderInfo);
return orderInfo;
}
/**
* 根据商品id查询已下单未支付的订单,防止重复创建订单
* @param productId 商品id
* @return 订单对象
*/
private OrderInfo getNoPayOrderByProductId(Long productId)
{
//QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
//相当于封装查询对象,这是查询条件
//就是查询该表中,是否有 列名 product_id 对应的值等于这个 productId ,有就查询出来
queryWrapper.eq("product_id", productId);
queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType());
//把 queryWrapper 作为查询条件对象
//selectOne 就是查询出一条,如果查询出多条,则会报错
OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);
return orderInfo;
}
/**
* 将支付二维码的url存到订单中
* @param orderNo 订单编号
* @param codeUrl 支付二维码的地址url
*/
@Override
public void saveCodeUrl(String orderNo, String codeUrl)
{
//QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
//组装查询条件
queryWrapper.eq("order_no",orderNo);
//要修改的字段,存到这个 orderInfo 对象里面
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCodeUrl(codeUrl);
//执行sql
orderInfoMapper.update(orderInfo,queryWrapper);
}
}
3、显示订单列表
3-1:需求:
在我的订单页面按时间倒序显示订单列表
目前是有订单,但是没展示出来。
3-2:代码:
前端:
调用后端接口的是api模块
<script> 脚本模块
<template> 模板,是用来定义组件的模板部分,用于描述组件的结构和布局
将后端返回的list商品订单列表赋值给 orders.vue 这个类后,就需要对这个数据进行渲染。
后端:
创建一个订单的controller
3-3:测试:
查看swagger
查看订单列表
成功显示
3-4:完整代码
后端:
OrderInfoController
@CrossOrigin //开放前端的跨域访问
@RestController
@RequestMapping(value = "/api/order-info")
@Api(tags = "商品订单管理")
public class OrderInfoController
{
//依赖注入
@Resource
private OrderInfoService orderInfoService;
@ApiOperation("显示商品订单列表")
@GetMapping("/list")
public R getOrderInfoList()
{
List<OrderInfo> list =
orderInfoService.getOrderInfoListByCreateTimeDesc();
return R.ok().data("list",list);
}
}
OrderInfoService
/**
* 获取商品订单列表,并按时间倒序显示
* @return 商品订单列表,倒序显示
*/
List<OrderInfo> getOrderInfoListByCreateTimeDesc();
OrderInfoServiceImpl
/**
* 获取商品订单列表,并按时间倒序显示
* @return 商品订单列表,倒序显示
*/
@Override
public List<OrderInfo> getOrderInfoListByCreateTimeDesc()
{
//QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
//组装查询条件
queryWrapper.orderByDesc("create_time");
//查询
List<OrderInfo> list = orderInfoMapper.selectList(queryWrapper);
return list;
}