【 苍穹外卖day8 | 微信支付 】

本节内容包含跳过微信支付功能的代码哟(一刷时未实现支付功能,项目差点烂尾😨)

主要内容

  • 导入地址簿功能代码

  • 用户下单

  • 订单支付


功能实现:用户下单订单支付

用户下单效果图:(虚拟用户)


1. 导入地址簿功能代码

此处略过,就不占用重点内容了


2. 用户下单

2.1 需求分析和设计

2.1.1 产品原型

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

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

用户点餐业务流程(效果图):


2.1.2 接口设计

接口分析:


接口设计:


2.1.3 表设计

用户下单业务对应的数据表为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)商品单价

说明:用户提交订单时,需要往订单表orders中插入一条记录,并且需要往order_detail中插入一条或多条记录。


2.2 代码开发

2.2.1 Controller层

创建OrderController并提供用户下单方法:

package com.sky.controller.user;

import com.sky.dto.OrdersPaymentDTO;
import com.sky.dto.OrdersSubmitDTO;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.vo.OrderPaymentVO;
import com.sky.vo.OrderSubmitVO;
import com.sky.vo.OrderVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 订单
 */
@RestController("userOrderController")
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "C端-订单接口")
public class OrderController {

    @Autowired
    private 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);
    } 

}

2.2.2 Service层接口

创建OrderService接口,并声明用户下单方法:

package com.sky.service;

import com.sky.dto.*;
import com.sky.vo.OrderSubmitVO;

public interface OrderService {

    /**
     * 用户下单
     * @param ordersSubmitDTO
     * @return
     */
    OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
}

2.2.3 Service层实现类

创建OrderServiceImpl实现OrderService接口:

package com.sky.service.impl;

/**
 * 订单
 */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private AddressBookMapper addressBookMapper;


    /**
     * 用户下单
     *
     * @param ordersSubmitDTO
     * @return
     */
    @Transactional
    public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
        //异常情况的处理(收货地址为空、超出配送范围、购物车为空)
        AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
        if (addressBook == null) {
            throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
        }

        Long userId = BaseContext.getCurrentId();
        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.setUserId(userId);

        //查询当前用户的购物车数据
        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;
    }
    
}

课程中遗漏的部分内容
order.setAddress(addressBook.getDetail());

重点代码解析一:

    //获取线程中的userId
 	Long userId = BaseContext.getCurrentId();
	//创建新的实体类
	ShoppingCart shoppingCart = new ShoppingCart();
	//给实体类赋值
	shoppingCart.setUserId(userId);

    //查询当前用户的购物车数据
    //根据userId进行查询,来进行其他数据库表相关数据的查询
    List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);

重点代码解析二:

//将DTO类重新属性拷贝到对应实体类
        //查询用户的购物车数据(如果主键自增,则前端无法传递自动生成的主键,需要额外赋值)
        //Long userId = BaseContext.getCurrentId();
        Orders order = new Orders();
        BeanUtils.copyProperties(ordersSubmitDTO, order);
        order.setAddress(addressBook.getDetail());
        order.setOrderTime(LocalDateTime.now());
        order.setPayStatus(Orders.UN_PAID);
        order.setStatus(Orders.PENDING_PAYMENT);
        order.setNumber(String.valueOf(System.currentTimeMillis()));
        order.setPhone(addressBook.getPhone());
        order.setConsignee(addressBook.getConsignee());
        order.setUserId(userId);
        orderMapper.insert(order);

2.2.4 Mapper层

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

OrderMapper.java

package com.sky.mapper;

@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

package com.sky.mapper;

import com.sky.entity.OrderDetail;
import java.util.List;

@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>

3. 订单支付

3.1 微信支付介绍

前面的课程已经实现了用户下单,那接下来就是订单支付,就是完成付款功能。支付大家应该都不陌生了,在现实生活中经常购买商品并且使用支付功能来付款,在付款的时候可能使用比较多的就是微信支付和支付宝支付了。在苍穹外卖项目中,选择的就是微信支付这种支付方式。

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

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

微信支付产品:

如何实现微信支付功能呢?

微信支付相关接口:

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

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

3.2 跳过微信支付

由于微信支付需要商家认证才能开通,个人开发者只能模拟支付的流程,完成后续项目的学习


第一步:打开小程序开发工具,按图中修改,改完最好重新编译下

修改前:

修改后:

保存并重新编译文件


第二步: 按照外卖学习视频导入书写资料Day08中以下红框文件中微信支付部分的代码(也就是视频Day08-17中提及的属于以下文件的代码)

注意:红框中代码按照老师所讲的步骤实现


第三步:在OrderServiceImpl中payment方法里按图操作


第四步:在OrderServiceImpl中paySuccess方法里检查是否实现这两个方法,有则完成;则无则回看视频Day08第17集中此部分方法实现。


第五步:调试,“支付成功”。(注:由于前端支付成功提示信息被注释,小程序则不显示支付成功的提示信息)


大功告成!

### 苍穹外卖系统与微信支付接口集成及常见问题解决方案 在开发苍穹外卖系统并集成微信支付功能时,需要关注以下几个关键点:支付接口的配置、参数传递、签名验证以及错误处理。以下是详细的说明: #### 1. 微信支付接口集成 微信支付接口的集成通常包括以下几个步骤: - **注册商户号**:确保已经在微信支付平台上注册了商户号,并获取到相关的 `AppID` 和 `MchID`[^2]。 - **配置密钥**:在商户平台中生成 API 密钥,并将其安全地存储在服务器端。 - **引入 SDK 或封装接口**:可以使用官方提供的微信支付 SDK,或者自行封装 HTTP 请求以调用微信支付接口。 以下是一个简单的 Java 示例代码,展示如何通过 HTTP 请求调用微信支付统一下单接口: ```java import java.io.*; import java.net.HttpURLConnection; import java.net.URL; public class WeChatPayExample { public static void main(String[] args) throws Exception { String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String xmlData = "<xml>" + "<appid>wx1234567890abcdef</appid>" + "<mch_id>1234567890</mch_id>" + "<nonce_str>1234567890</nonce_str>" + "<body>测试商品</body>" + "<out_trade_no>202304010001</out_trade_no>" + "<total_fee>1</total_fee>" + "<spbill_create_ip>127.0.0.1</spbill_create_ip>" + "<notify_url>http://yourdomain.com/notify</notify_url>" + "<trade_type>APP</trade_type>" + "<sign>Md5Signature</sign>" + "</xml>"; URL obj = new URL(url); HttpURLConnection con = (HttpURLConnection) obj.openConnection(); con.setRequestMethod("POST"); con.setDoOutput(true); try(OutputStream os = con.getOutputStream()) { os.write(xmlData.getBytes()); os.flush(); } int responseCode = con.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { try(BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } System.out.println(response.toString()); } } else { System.out.println("POST request failed."); } } } ``` #### 2. 常见问题及解决方案 - **签名错误**:如果在调用支付接口时返回签名错误,可能是因为参数顺序不对或未正确生成签名值。需按照文档中的签名规则重新生成签名[^1]。 - **接口文档不完整**:如果发现接口文档生成不完整(如看不到某些 Controller),可能是扫描包路径设置错误,导致相关类未被加载。检查 Spring Boot 的扫描路径配置是否正确[^1]。 - **回调通知失败**:确保支付成功后的回调地址能够正常访问,并且服务器端能正确解析微信支付发送的通知数据。 #### 3. Swagger 与 Yapi 的选择 在项目开发过程中,可能会面临是否使用 Swagger 或 Yapi 的问题。两者各有优劣: - **Swagger**:适合动态生成接口文档,尤其适用于基于注解的框架,例如 Spring Boot[^1]。 - **Yapi**:更适合团队协作,支持手动维护和多人编辑接口文档。 对于苍穹外卖系统,推荐根据团队需求选择合适的工具。如果团队更倾向于自动化生成文档,则可优先考虑 Swagger;如果需要更灵活的文档管理,则可以选择 Yapi。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值