SpringBoot+SSM项目实战 苍穹外卖(08) 用户下单支付 内网穿透cpolar软件 绕开微信支付实现

继续上一节的内容,本节导入地址簿功能代码,并实现用户下单和订单支付功能。

这里写目录标题

  • 导入地址簿功能代码
    • 接口分析
    • 代码实现
  • 用户下单
    • 接口分析
    • 代码实现
  • 订单支付
    • 内网穿透——cpolar软件
    • 代码导入
    • 绕开微信支付实现

导入地址簿功能代码

地址簿,指的是消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址。

请添加图片描述

接口分析

对于地址簿管理,我们需要实现以下几个功能:查询地址列表、新增地址、修改地址、删除地址、设置默认地址、查询默认地址。共包含7个接口:

1). 新增地址

请添加图片描述

2). 查询登录用户所有地址

请添加图片描述

3). 查询默认地址

请添加图片描述

4). 修改地址

请添加图片描述

5). 根据id删除地址

请添加图片描述

6). 根据id查询地址

请添加图片描述

7). 设置默认地址

请添加图片描述

用户的地址信息会存储在address_book表,即地址簿表中。这里面有一个字段is_default,实际上我们在设置默认地址时,只需要更新这个字段就可以了。

字段名数据类型说明备注
idbigint主键自增
user_idbigint用户id逻辑外键
consigneevarchar(50)收货人
sexvarchar(2)性别
phonevarchar(11)手机号
province_codevarchar(12)省份编码
province_namevarchar(32)省份名称
city_codevarchar(12)城市编码
city_namevarchar(32)城市名称
district_codevarchar(12)区县编码
district_namevarchar(32)区县名称
detailvarchar(200)详细地址信息具体到门牌号
labelvarchar(100)标签公司、家、学校
is_defaulttinyint(1)是否默认地址1是 0否



代码实现

对于这一类的单表的增删改查,我们已经写过很多了,直接导入进来,做一个测试即可。进入到sky-server模块中导入课程资料中的地址簿模块功能代码:

Controller层

com.sky.controller.user.AddressBookController

@RestController
@RequestMapping("/user/addressBook")
@Api(tags = "C端地址簿接口")
public class AddressBookController {@Autowiredprivate AddressBookService addressBookService;/*** 查询当前登录用户的所有地址信息** @return*/@GetMapping("/list")@ApiOperation("查询当前登录用户的所有地址信息")public Result<List<AddressBook>> list() {AddressBook addressBook = new AddressBook();addressBook.setUserId(BaseContext.getCurrentId());List<AddressBook> list = addressBookService.list(addressBook);return Result.success(list);}/*** 新增地址** @param addressBook* @return*/@PostMapping@ApiOperation("新增地址")public Result save(@RequestBody AddressBook addressBook) {addressBookService.save(addressBook);return Result.success();}@GetMapping("/{id}")@ApiOperation("根据id查询地址")public Result<AddressBook> getById(@PathVariable Long id) {AddressBook addressBook = addressBookService.getById(id);return Result.success(addressBook);}/*** 根据id修改地址** @param addressBook* @return*/@PutMapping@ApiOperation("根据id修改地址")public Result update(@RequestBody AddressBook addressBook) {addressBookService.update(addressBook);return Result.success();}/*** 设置默认地址** @param addressBook* @return*/@PutMapping("/default")@ApiOperation("设置默认地址")public Result setDefault(@RequestBody AddressBook addressBook) {addressBookService.setDefault(addressBook);return Result.success();}/*** 根据id删除地址** @param id* @return*/@DeleteMapping@ApiOperation("根据id删除地址")public Result deleteById(Long id) {addressBookService.deleteById(id);return Result.success();}/*** 查询默认地址*/@GetMapping("default")@ApiOperation("查询默认地址")public Result<AddressBook> getDefault() {//SQL:select * from address_book where user_id = ? and is_default = 1AddressBook addressBook = new AddressBook();addressBook.setIsDefault(1);addressBook.setUserId(BaseContext.getCurrentId());List<AddressBook> list = addressBookService.list(addressBook);if (list != null && list.size() == 1) {return Result.success(list.get(0));}return Result.error("没有查询到默认地址");}}

Service层实现类

com.sky.service.impl.AddressBookServiceImpl

@Service
@Slf4j
public class AddressBookServiceImpl implements AddressBookService {@Autowiredprivate AddressBookMapper addressBookMapper;/*** 条件查询** @param addressBook* @return*/public List<AddressBook> list(AddressBook addressBook) {return addressBookMapper.list(addressBook);}/*** 新增地址** @param addressBook*/public void save(AddressBook addressBook) {addressBook.setUserId(BaseContext.getCurrentId());addressBook.setIsDefault(0);addressBookMapper.insert(addressBook);}/*** 根据id查询** @param id* @return*/public AddressBook getById(Long id) {AddressBook addressBook = addressBookMapper.getById(id);return addressBook;}/*** 根据id修改地址** @param addressBook*/public void update(AddressBook addressBook) {addressBookMapper.update(addressBook);}/*** 设置默认地址** @param addressBook*/@Transactionalpublic void setDefault(AddressBook addressBook) {//1、将当前用户的所有地址修改为非默认地址 update address_book set is_default = ? where user_id = ?addressBook.setIsDefault(0);addressBook.setUserId(BaseContext.getCurrentId());addressBookMapper.updateIsDefaultByUserId(addressBook);//2、将当前地址改为默认地址 update address_book set is_default = ? where id = ?addressBook.setIsDefault(1);addressBookMapper.update(addressBook);}/*** 根据id删除地址** @param id*/public void deleteById(Long id) {addressBookMapper.deleteById(id);}}

Mapper层

com.sky.mapper.AddressBookMapper

@Mapper
public interface AddressBookMapper {/*** 条件查询* @param addressBook* @return*/List<AddressBook> list(AddressBook addressBook);/*** 新增* @param addressBook*/@Insert("insert into address_book" +"        (user_id, consignee, phone, sex, province_code, province_name, city_code, city_name, district_code," +"         district_name, detail, label, is_default)" +"        values (#{userId}, #{consignee}, #{phone}, #{sex}, #{provinceCode}, #{provinceName}, #{cityCode}, #{cityName}," +"                #{districtCode}, #{districtName}, #{detail}, #{label}, #{isDefault})")void insert(AddressBook addressBook);/*** 根据id查询* @param id* @return*/@Select("select * from address_book where id = #{id}")AddressBook getById(Long id);/*** 根据id修改* @param addressBook*/void update(AddressBook addressBook);/*** 根据 用户id修改 是否默认地址* @param addressBook*/@Update("update address_book set is_default = #{isDefault} where user_id = #{userId}")void updateIsDefaultByUserId(AddressBook addressBook);/*** 根据id删除地址* @param id*/@Delete("delete from address_book where id = #{id}")void deleteById(Long id);}

mapper/AddressBookMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.AddressBookMapper"><select id="list" parameterType="AddressBook" resultType="AddressBook">select * from address_book<where><if test="userId != null">and user_id = #{userId}</if><if test="phone != null">and phone = #{phone}</if><if test="isDefault != null">and is_default = #{isDefault}</if></where></select><update id="update" parameterType="addressBook">update address_book<set><if test="consignee != null">consignee = #{consignee},</if><if test="sex != null">sex = #{sex},</if><if test="phone != null">phone = #{phone},</if><if test="detail != null">detail = #{detail},</if><if test="label != null">label = #{label},</if><if test="isDefault != null">is_default = #{isDefault},</if></set>where id = #{id}</update></mapper>

使用前后端联调测试:

登录进入首页–>进入个人中心–>进入地址管理

请添加图片描述

在这里进行新增收货地址、查看收货地址、设置默认收货地址、删除收货地址等接口的测试,同时观察数据库数据确保无误。测试通过提交代码。





用户下单

用户是通过下单的方式通知商家,用户已经购买了商品,需要商家进行备货和发货。用户下单后会产生订单相关数据,订单数据需要能够体现如下信息:

在这里插入图片描述

用户将菜品或者套餐加入购物车后,可以点击购物车中的 “去结算” 按钮,页面跳转到订单确认页面,点击 “去支付” 按钮则完成下单操作。效果图如下所示:

请添加图片描述




接口分析

接口如下:

请添加图片描述

用户下单业务对应的数据表为orders表和order_detail表(一对多关系,一个订单关联多个订单明细),用户提交订单时,需要往订单表orders中插入一条记录,并且需要往order_detail中插入一条或多条记录(来自购物车)。

表名含义说明
orders订单表主要存储订单的基本信息(如: 订单号、状态、金额、支付方式、下单用户、收件地址等)
order_detail订单明细表主要存储订单详情信息(如: 该订单关联的套餐及菜品的信息)

1). orders订单表

字段名数据类型说明备注
idbigint主键自增
numbervarchar(50)订单号
statusint订单状态1待付款 2待接单 3已接单 4派送中 5已完成 6已取消
user_idbigint用户id逻辑外键
address_book_idbigint地址id逻辑外键
order_timedatetime下单时间
checkout_timedatetime付款时间
pay_methodint支付方式1微信支付 2支付宝支付
pay_statustinyint支付状态0未支付 1已支付 2退款
amountdecimal(10,2)订单金额
remarkvarchar(100)备注信息
phonevarchar(11)手机号冗余字段
addressvarchar(255)详细地址信息冗余字段
consigneevarchar(32)收货人冗余字段
cancel_reasonvarchar(255)订单取消原因
rejection_reasonvarchar(255)拒单原因
cancel_timedatetime订单取消时间
estimated_delivery_timedatetime预计送达时间
delivery_statustinyint配送状态1立即送出 0选择具体时间
delivery_timedatetime送达时间
pack_amountint打包费
tableware_numberint餐具数量
tableware_statustinyint餐具数量状态1按餐量提供 0选择具体数量

2). order_detail订单明细表

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)商品名称冗余字段
imagevarchar(255)商品图片路径冗余字段
order_idbigint订单id逻辑外键
dish_idbigint菜品id逻辑外键
setmeal_idbigint套餐id逻辑外键
dish_flavorvarchar(50)菜品口味
numberint商品数量
amountdecimal(10,2)商品单价

根据用户下单接口的参数设计DTO,在sky-pojo模块,OrdersSubmitDTO.java已定义。

在这里插入图片描述

根据用户下单接口的返回结果设计VO,在sky-pojo模块,OrderSubmitVO.java已定义。

在这里插入图片描述




代码实现

Controller层

创建com.sky.controller.user.OrderController并提供用户下单方法

/*** 订单*/
@RestController("userOrderController")
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "C端-订单接口")
public class OrderController {@Autowiredprivate OrderService orderService;/*** 用户下单** @param ordersSubmitDTO* @return*/@PostMapping("/submit")@ApiOperation("用户下单")public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {log.info("用户下单:{}", ordersSubmitDTO);OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);return Result.success(orderSubmitVO);}}

Service层实现类

创建com.sky.service.impl.OrderServiceImpl实现OrderService接口

/*** 订单*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper; // 订单表@Autowiredprivate OrderDetailMapper orderDetailMapper; // 订单明细表@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate AddressBookMapper addressBookMapper;/*** 用户下单** @param ordersSubmitDTO* @return*/@Transactionalpublic OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {//异常情况的处理(收货地址为空、超出配送范围、购物车为空)AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId()); //根据地址簿id查询收货地址信息if (addressBook == null) {throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);//收货地址为空异常}Long userId = BaseContext.getCurrentId();ShoppingCart shoppingCart = new ShoppingCart();shoppingCart.setUserId(userId); //构造一个购物车对象用于根据用户id查询购物车数据//查询当前用户的购物车数据List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);if (shoppingCartList == null || shoppingCartList.size() == 0) {throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);//购物车为空异常}//构造订单数据Orders order = new Orders();BeanUtils.copyProperties(ordersSubmitDTO,order);order.setPhone(addressBook.getPhone());order.setAddress(addressBook.getDetail());order.setConsignee(addressBook.getConsignee());order.setNumber(String.valueOf(System.currentTimeMillis())); //订单号就用时间戳order.setUserId(userId);order.setStatus(Orders.PENDING_PAYMENT); //设置订单状态为代付款order.setPayStatus(Orders.UN_PAID); //设置支付状态为未支付order.setOrderTime(LocalDateTime.now());//向订单表插入1条数据orderMapper.insert(order);//订单明细数据List<OrderDetail> orderDetailList = new ArrayList<>();for (ShoppingCart cart : shoppingCartList) {OrderDetail orderDetail = new OrderDetail();BeanUtils.copyProperties(cart, orderDetail);orderDetail.setOrderId(order.getId());orderDetailList.add(orderDetail);}//向明细表插入n条数据orderDetailMapper.insertBatch(orderDetailList);//清理购物车中的数据shoppingCartMapper.deleteByUserId(userId);//封装返回结果OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder().id(order.getId()).orderNumber(order.getNumber()).orderAmount(order.getAmount()).orderTime(order.getOrderTime()).build();return orderSubmitVO;}}

Mapper层

创建OrderMapper接口和对应的xml映射文件

OrderMapper.java

@Mapper
public interface OrderMapper {/*** 插入订单数据* @param order*/void insert(Orders order);
}

OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.OrderMapper"><insert id="insert" parameterType="Orders" useGeneratedKeys="true" keyProperty="id">insert into orders(number, status, user_id, address_book_id, order_time, checkout_time, pay_method, pay_status, amount, remark,phone, address, consignee, estimated_delivery_time, delivery_status, pack_amount, tableware_number,tableware_status)values (#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod},#{payStatus}, #{amount}, #{remark}, #{phone}, #{address}, #{consignee},#{estimatedDeliveryTime}, #{deliveryStatus}, #{packAmount}, #{tablewareNumber}, #{tablewareStatus})</insert>
</mapper>

创建OrderDetailMapper接口和对应的xml映射文件:

OrderDetailMapper.java

@Mapper
public interface OrderDetailMapper {/*** 批量插入订单明细数据* @param orderDetails*/void insertBatch(List<OrderDetail> orderDetails);}

OrderDetailMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.OrderDetailMapper"><insert id="insertBatch" parameterType="list">insert into order_detail(name, order_id, dish_id, setmeal_id, dish_flavor, number, amount, image)values<foreach collection="orderDetails" item="od" separator=",">(#{od.name},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount},#{od.image})</foreach></insert></mapper>

功能测试,登录小程序,完成下单操作,下单操作时,同时会删除购物车中的数据

在这里插入图片描述

查看shopping_cart表:

在这里插入图片描述

去结算–>去支付

请添加图片描述

查看orders表:

在这里插入图片描述

查看order_detail表:

在这里插入图片描述

同时,购物车表中数据被删除:

在这里插入图片描述

测试通过,提交代码到github。





订单支付

已经实现了用户下单,那接下来就是订单支付,就是完成付款功能。要实现微信支付就需要注册微信支付的一个商户号,这个商户号是必须要有一家企业并且有正规的营业执照。只有具备了这些资质之后,才可以去注册商户号,才能开通支付权限。

个人不具备这种资质,所以我们在学习微信支付时,主要是是了解微信支付的流程,并且能够阅读微信官方提供的接口文档,能够和第三方支付平台对接起来就可以了。微信支付产品:

在这里插入图片描述

本项目选择小程序支付,可以参考官网,微信小程序支付时序图:

在这里插入图片描述

微信支付相关接口:

JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步)

在这里插入图片描述

微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)

在这里插入图片描述

如何保证数据安全?完成微信支付有两个关键的步骤:
第一个就是需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。第二个就是支付成功之后微信后台会给推送消息。这两个接口数据的安全性,要求其实是非常高的。
微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密,需要提前准备相应的一些文件,其实就是一些证书。微信支付平台证书、商户私钥文件:

在这里插入图片描述

内网穿透——cpolar软件

微信后台会调用到商户系统给推送支付的结果,在这里我们就会遇到一个问题,就是微信后台怎么就能调用到我们这个商户系统呢?因为这个调用过程,其实本质上也是一个HTTP请求。
目前,商户系统它的ip地址就是当前自己电脑的ip地址,只是一个局域网内的ip地址,微信后台无法调用到。

解决:内网穿透。通过cpolar软件可以获得一个临时域名,而这个临时域名是一个公网ip,这样,微信后台就可以请求到商户系统了。下载地址

复制authtoken:

在这里插入图片描述

在安装目录下执行下面的命令在我们的电脑上设置配置文件:

在这里插入图片描述

windows系统是执行命令:cpolar.exe authoken ***



再执行命令获取临时域名:

在这里插入图片描述

在这里插入图片描述

访问接口文档,原本是localhost:8080/doc.html,现在使用临时域名访问验证临时域名有效性:

在这里插入图片描述




代码导入

老师让导入资料中的代码,导个钩子,导了也不能用,秘钥都是过期的,微信支付平台证书、商户私钥文件老师也不能提供,涉及商户隐私。代码放这,以后开发的时候需要用到的时候再看看吧。

微信支付相关配置

application-dev.yml

sky:wechat:appid: # 小程序的appidsecret: # 小程序的秘钥mchid : # 商户号mchSerialNo: # 商户API证书的证书序列号privateKeyFilePath: # 商户私钥文件路径apiV3Key: # 证书解密的密钥weChatPayCertFilePath: # 平台证书路径notifyUrl: # 支付成功的回调地址 我们这里要填写内网穿透后的地址# 比如老师的 notifyUrl: https://6619cf50.r6.cpolar.top/notify/paySuccessrefundNotifyUrl: # 退款成功的回调地址 我们这里要填写内网穿透后的地址

application.yml

sky:wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}mchid : ${sky.wechat.mchid}mchSerialNo: ${sky.wechat.mchSerialNo}privateKeyFilePath: ${sky.wechat.privateKeyFilePath}apiV3Key: ${sky.wechat.apiV3Key}weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}notifyUrl: ${sky.wechat.notifyUrl}refundNotifyUrl: ${sky.wechat.refundNotifyUrl}

WeChatProperties.java:读取配置(已定义)

@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {private String appid; //小程序的appidprivate String secret; //小程序的秘钥private String mchid; //商户号private String mchSerialNo; //商户API证书的证书序列号private String privateKeyFilePath; //商户私钥文件private String apiV3Key; //证书解密的密钥private String weChatPayCertFilePath; //平台证书private String notifyUrl; //支付成功的回调地址private String refundNotifyUrl; //退款成功的回调地址
}

Controller层

在OrderController.java中添加payment方法

/*** 订单支付** @param ordersPaymentDTO* @return*/
@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {log.info("订单支付:{}", ordersPaymentDTO);OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);log.info("生成预支付交易单:{}", orderPaymentVO);return Result.success(orderPaymentVO);
}

com.sky.controller.notify.PayNotifyController.java

/*** 支付回调相关接口*/
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {@Autowiredprivate OrderService orderService;@Autowiredprivate WeChatProperties weChatProperties;/*** 支付成功回调** @param request*/@RequestMapping("/paySuccess")public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {//读取数据String body = readData(request);log.info("支付成功回调:{}", body);//数据解密String plainText = decryptData(body);log.info("解密后的文本:{}", plainText);JSONObject jsonObject = JSON.parseObject(plainText);String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号String transactionId = jsonObject.getString("transaction_id");//微信支付交易号log.info("商户平台订单号:{}", outTradeNo);log.info("微信支付交易号:{}", transactionId);//业务处理,修改订单状态、来单提醒orderService.paySuccess(outTradeNo);//给微信响应responseToWeixin(response);}/*** 读取数据** @param request* @return* @throws Exception*/private String readData(HttpServletRequest request) throws Exception {BufferedReader reader = request.getReader();StringBuilder result = new StringBuilder();String line = null;while ((line = reader.readLine()) != null) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();}/*** 数据解密** @param body* @return* @throws Exception*/private String decryptData(String body) throws Exception {JSONObject resultObject = JSON.parseObject(body);JSONObject resource = resultObject.getJSONObject("resource");String ciphertext = resource.getString("ciphertext");String nonce = resource.getString("nonce");String associatedData = resource.getString("associated_data");AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));//密文解密String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);return plainText;}/*** 给微信响应* @param response*/private void responseToWeixin(HttpServletResponse response) throws Exception{response.setStatus(200);HashMap<Object, Object> map = new HashMap<>();map.put("code", "SUCCESS");map.put("message", "SUCCESS");response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));response.flushBuffer();}
}


Service层实现类

在OrderServiceImpl.java中实现payment和paySuccess两个方法

@Autowired
private UserMapper userMapper;
@Autowired
private WeChatPayUtil weChatPayUtil;
/*** 订单支付** @param ordersPaymentDTO* @return*/
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {// 当前登录用户idLong userId = BaseContext.getCurrentId();User user = userMapper.getById(userId);//调用微信支付接口,生成预支付交易单JSONObject jsonObject = weChatPayUtil.pay(ordersPaymentDTO.getOrderNumber(), //商户订单号new BigDecimal(0.01), //支付金额,单位 元"苍穹外卖订单", //商品描述user.getOpenid() //微信用户的openid);if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {throw new OrderBusinessException("该订单已支付");}OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);vo.setPackageStr(jsonObject.getString("package"));return vo;
}/*** 支付成功,修改订单状态** @param outTradeNo*/
public void paySuccess(String outTradeNo) {// 当前登录用户idLong userId = BaseContext.getCurrentId();// 根据订单号查询当前用户的订单Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);// 根据订单id更新订单的状态、支付方式、支付状态、结账时间Orders orders = Orders.builder().id(ordersDB.getId()).status(Orders.TO_BE_CONFIRMED).payStatus(Orders.PAID).checkoutTime(LocalDateTime.now()).build();orderMapper.update(orders);
}

其中WeChatPayUtil已经在common模块下的com.sky.utils中已定义好:

/*** 微信支付工具类*/
@Component
public class WeChatPayUtil {//微信支付下单接口地址public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";//申请退款接口地址public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";@Autowiredprivate WeChatProperties weChatProperties;/*** 获取调用微信接口的客户端工具对象** @return*/private CloseableHttpClient getClient() {PrivateKey merchantPrivateKey = null;try {//merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));//加载平台证书文件X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));//wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey).withWechatPay(wechatPayCertificates);// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签CloseableHttpClient httpClient = builder.build();return httpClient;} catch (FileNotFoundException e) {e.printStackTrace();return null;}}/*** 发送post方式请求** @param url* @param body* @return*/private String post(String url, String body) throws Exception {CloseableHttpClient httpClient = getClient();HttpPost httpPost = new HttpPost(url);// 设置一些微信支付要求的请求头httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());httpPost.setEntity(new StringEntity(body, "UTF-8"));CloseableHttpResponse response = httpClient.execute(httpPost);try {String bodyAsString = EntityUtils.toString(response.getEntity());return bodyAsString;} finally {httpClient.close();response.close();}}/*** 发送get方式请求** @param url* @return*/private String get(String url) throws Exception {CloseableHttpClient httpClient = getClient();HttpGet httpGet = new HttpGet(url);httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());return bodyAsString;} finally {httpClient.close();response.close();}}/*** jsapi下单** @param orderNum    商户订单号* @param total       总金额* @param description 商品描述* @param openid      微信用户的openid* @return*/private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {JSONObject jsonObject = new JSONObject();jsonObject.put("appid", weChatProperties.getAppid());jsonObject.put("mchid", weChatProperties.getMchid());jsonObject.put("description", description);jsonObject.put("out_trade_no", orderNum);jsonObject.put("notify_url", weChatProperties.getNotifyUrl());JSONObject amount = new JSONObject();amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());amount.put("currency", "CNY");jsonObject.put("amount", amount);JSONObject payer = new JSONObject();payer.put("openid", openid);jsonObject.put("payer", payer);String body = jsonObject.toJSONString();return post(JSAPI, body);}/*** 小程序支付** @param orderNum    商户订单号* @param total       金额,单位 元* @param description 商品描述* @param openid      微信用户的openid* @return*/public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {//统一下单,生成预支付交易单String bodyAsString = jsapi(orderNum, total, description, openid);//解析返回结果JSONObject jsonObject = JSON.parseObject(bodyAsString);System.out.println(jsonObject);String prepayId = jsonObject.getString("prepay_id");if (prepayId != null) {String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);String nonceStr = RandomStringUtils.randomNumeric(32);ArrayList<Object> list = new ArrayList<>();list.add(weChatProperties.getAppid());list.add(timeStamp);list.add(nonceStr);list.add("prepay_id=" + prepayId);//二次签名,调起支付需要重新签名StringBuilder stringBuilder = new StringBuilder();for (Object o : list) {stringBuilder.append(o).append("\n");}String signMessage = stringBuilder.toString();byte[] message = signMessage.getBytes();Signature signature = Signature.getInstance("SHA256withRSA");signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));signature.update(message);String packageSign = Base64.getEncoder().encodeToString(signature.sign());//构造数据给微信小程序,用于调起微信支付JSONObject jo = new JSONObject();jo.put("timeStamp", timeStamp);jo.put("nonceStr", nonceStr);jo.put("package", "prepay_id=" + prepayId);jo.put("signType", "RSA");jo.put("paySign", packageSign);return jo;}return jsonObject;}/*** 申请退款** @param outTradeNo    商户订单号* @param outRefundNo   商户退款单号* @param refund        退款金额* @param total         原订单金额* @return*/public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {JSONObject jsonObject = new JSONObject();jsonObject.put("out_trade_no", outTradeNo);jsonObject.put("out_refund_no", outRefundNo);JSONObject amount = new JSONObject();amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());amount.put("currency", "CNY");jsonObject.put("amount", amount);jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());String body = jsonObject.toJSONString();//调用申请退款接口return post(REFUNDS, body);}
}

Mapper层

在OrderMapper.java中添加getByNumberAndUserId和update两个方法

/*** 根据订单号和用户id查询订单* @param orderNumber* @param userId*/
@Select("select * from orders where number = #{orderNumber} and user_id= #{userId}")
Orders getByNumberAndUserId(String orderNumber, Long userId);/*** 修改订单信息* @param orders*/
void update(Orders orders);

在OrderMapper.xml中添加

<update id="update" parameterType="com.sky.entity.Orders">update orders<set><if test="cancelReason != null and cancelReason!='' ">cancel_reason=#{cancelReason},</if><if test="rejectionReason != null and rejectionReason!='' ">rejection_reason=#{rejectionReason},</if><if test="cancelTime != null">cancel_time=#{cancelTime},</if><if test="payStatus != null">pay_status=#{payStatus},</if><if test="payMethod != null">pay_method=#{payMethod},</if><if test="checkoutTime != null">checkout_time=#{checkoutTime},</if><if test="status != null">status = #{status},</if><if test="deliveryTime != null">delivery_time = #{deliveryTime}</if></set>where id = #{id}
</update>



绕开微信支付实现

由于个人无法申请微信支付资质,固有必要绕开这部分代码以继续这个项目。可以直接修改微信小程序代码中的这一部分内容,将requestPayment方法整体注释掉,然后将下方的重定向注释取消即可:

在这里插入图片描述

在这里插入图片描述

后端代码,路径:OrderServiceImpl中的payment方法中,将如下部分注释掉:

在这里插入图片描述

相当于没真正执行paysuccess方法,所以将修改订单状态的逻辑也加入到payment函数中,最后payment函数如下:

/*** 订单支付** @param ordersPaymentDTO* @return*/
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {// 当前登录用户idLong userId = BaseContext.getCurrentId();User user = userMapper.getById(userId);//        //调用微信支付接口,生成预支付交易单
//        JSONObject jsonObject = weChatPayUtil.pay(
//                ordersPaymentDTO.getOrderNumber(), //商户订单号
//                new BigDecimal(0.01), //支付金额,单位 元
//                "苍穹外卖订单", //商品描述
//                user.getOpenid() //微信用户的openid
//        );
//
//        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
//            throw new OrderBusinessException("该订单已支付");
//        }JSONObject jsonObject = new JSONObject();jsonObject.put("code", "ORDERPAID");OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);vo.setPackageStr(jsonObject.getString("package"));// 替代微信支付成功后的数据库订单状态更新,直接在这里更新了// 根据订单号查询当前用户的该订单Orders ordersDB = orderMapper.getByNumberAndUserId(ordersPaymentDTO.getOrderNumber(), userId);// 根据订单id更新订单的状态、支付方式、支付状态、结账时间Orders orders = Orders.builder().id(ordersDB.getId()).status(Orders.TO_BE_CONFIRMED) // 订单状态,待接单.payStatus(Orders.PAID) // 支付状态,已支付.checkoutTime(LocalDateTime.now()) // 更新支付时间.build();orderMapper.update(orders);return vo;
}

后端和小程序端都重新编译,重新请求即可,最后可以看到已经支付成功:

在这里插入图片描述

测试成功,提交代码到github。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/315754.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12.31信号位宽转换(整数,非整数),时钟分频(奇数,偶数,任意小数,占空比),自动售货机(1,2),游戏机

非整数倍数据位宽转换8to12 所谓非整数倍&#xff0c;就是利用一个cnt去周期性决定寄存器里怎么输出&#xff0c;这个cnt的值&#xff0c;是最小公倍数 寄存器就正常的寄存&#xff0c;怎么输入怎么寄存 timescale 1ns/1nsmodule width_8to12(input clk , input…

STM32L4

STM32L4系列超低功耗微控制器 意法半导体通过构建新型芯片架构实现了同类产品中最佳的超低功耗及性能&#xff0c;这得益于应用设计上的高度灵活性。 STM32L4系列可以根据微处理器运行时不同的应用需求来适时调整电压从而实现功耗的动态平衡。 该系列包含不同的产品线&#…

基于Java (spring-boot)的在线培训考试系统

一、项目介绍 在线培训系统是一款基于SpringBootVue开发的考试系统。一款多角色在线培训考试系统&#xff0c;系统集成了用户管理、角色管理、部门管理、题库管理、试题管理、试题导入导出、考试管理、在线考试、错题训练等功能&#xff0c;考试流程完善。 多角色&#xff1a;多…

docker 部署教学版本

文章目录 一、docker使用场景及常用命令1&#xff09;docker使用场景2&#xff09;rocky8(centos8)安装 docker3&#xff09;docker 常用命令补充常用命令 二、 单独部署每个镜像&#xff0c;部署spring 应用镜像推荐&#xff08;2023-12-18&#xff09;1、 安装使用 mysql1.1 …

虚幻UE 材质-边界混合之PDO像素深度偏移量

2024年的第一天&#xff01;&#xff01;&#xff01;大家新年快乐&#xff01;&#xff01;&#xff01; 可能是长大了才知道 当你过得一般 你的亲朋好友对你真正态度只可能是没有表露出来的冷嘲热讽了 希望大家新的一年平安、幸福、 永远活力满满地追求自己所想做的、爱做的&…

hubSpot有哪些功能?

HubSpot是一款综合性的市场营销、销售和服务软件平台&#xff0c;旨在帮助企业实现更有效的客户关系管理和增长。以下是HubSpot的一些主要功能&#xff1a; 市场营销自动化&#xff1a; 制定和执行多渠道的市场营销活动。 创建和管理电子邮件营销、社交媒体发布和广告活动。 …

ubuntu terminator 非常好用的护眼配置

安装 sudo apt install terminator 配置文件&#xff1a;sudo gedit ~/.config/terminator/config &#xff08;如果没有就创建&#xff09; 配置如下&#xff1a; [global_config] handle_size -3 title_transmit_fg_color "#000000" title_trans…

IIC协议之TH09传感器采样

IIC协议之TH09传感器采样 0. 写在前面1.IIC协议1.1 BSP_I2C_Start1.2 BSP_I2C_Stop1.3 BSP_I2C_waitAck1.4 BSP_I2C_Send_Byte1.5 BSP_IIC_Read_Byte 2. TH092.1 Init2.2 时序图 3.TH09 CRC4.采集结果 0. 写在前面 AT32F415TH09BSP库 实现需了解&#xff1a; 1.IIC协议的软件模…

C++面试宝典第12题:数组元素相除

题目 从控制台输入若干个整数作为数组,将数组中每一个元素除以第一个元素的结果,作为新的数组元素值。比如:可以先输入3,作为数组元素的个数;然后输入3个整数,作为数组元素的值。 解析 这道题本身并不复杂,但里面隐藏了不少“坑点”和“雷区”,主要考察应聘者全面思考问…

HTML语义化的理解

HTML语义化是指在编写HTML代码时&#xff0c;合理地选择适当的标签和属性来描述页面的结构和内容&#xff0c;使得代码更具有可读性、可维护性和可访问性。 可读性&#xff1a;通过使用语义化的标签&#xff0c;可以清晰地表达页面的结构和内容&#xff0c;使得代码更易于阅读和…

基于多元宇宙算法优化的Elman神经网络数据预测 - 附代码

基于多元宇宙算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于多元宇宙算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于多元宇宙优化的Elman网络5.测试结果6.参考文献7.Matlab代码 摘要&…

【工具】vscode搜索结果及工程目录的文件夹、文件的排除

&#x1f41a;作者简介&#xff1a;花神庙码农&#xff08;专注于Linux、WLAN、TCP/IP、Python等技术方向&#xff09;&#x1f433;博客主页&#xff1a;花神庙码农 &#xff0c;地址&#xff1a;https://blog.csdn.net/qxhgd&#x1f310;系列专栏&#xff1a;善假于物&#…