用户下单
需求分析和设计
用户下单业务说明:
在电商系统中,用户是通过下单的方式通知商家,用户已经购买了商品,需要商家进行备货和发货。
用户下单后会产生订单相关数据,订单数据需要能够体现如下信息:
用户点餐业务流程:
接口设计(分析):
接口设计:
数据库设计:
用户下单业务对应的数据表为 orders 表和 order_detail 表(一对多关系,一个订单关联多个订单明细):
数据库设计:订单表 orders
数据库设计:订单明细表 order_detail
代码开发
根据用户下单接口的参数设计DTO:
@Data
public class OrdersSubmitDTO implements Serializable {
//地址簿id
private Long addressBookId;
//付款方式
private int payMethod;
//备注
private String remark;
//预计送达时间
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime estimatedDeliveryTime;
//配送状态 1立即送出 0选择具体时间
private Integer deliveryStatus;
//餐具数量
private Integer tablewareNumber;
//餐具数量状态 1按餐量提供 0选择具体数量
private Integer tablewareStatus;
//打包费
private Integer packAmount;
//总金额
private BigDecimal amount;
}
根据用户下单接口的返回结果设计VO:
创建OrderController并提供用户下单方法:
创建OrderService接口,并声明用户下单方法:
创建OrderServiceImpl实现OrderService接口(1):
package com.sky.service.impl;
import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.dto.OrdersSubmitDTO;
import com.sky.entity.AddressBook;
import com.sky.entity.OrderDetail;
import com.sky.entity.Orders;
import com.sky.entity.ShoppingCart;
import com.sky.exception.AddressBookBusinessException;
import com.sky.exception.ShoppingCartBusinessException;
import com.sky.mapper.AddressBookMapper;
import com.sky.mapper.OrderDetailMapper;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.service.OrderService;
import com.sky.vo.OrderSubmitVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* @author 石头
* @version 1.0
*/
@Service
public class OrderServiceImp implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private AddressBookMapper addressBookMapper;
/**
* 用户下单
* @param ordersSubmitDTO
* @return
*/
@Transactional
@Override
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);
//向订单明细插入n条数据
List<OrderDetail> orderDetailList = new ArrayList<>();
for (ShoppingCart cart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail();//订单明细数据
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(order.getId());//设置当前订单明细关联的订单id
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;
}
}
创建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映射文件:
<?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>
功能测试
订单支付
微信支付介绍
要实现微信支付就需要注册微信支付的一个商户号,这个商户号是必须要有一家企业并且有正规的营业执照。只有具备了这些资质之后,才可以去注册商户号,才能开通支付权限。
微信支付产品:
微信支付接入流程:
微信小程序支付时序图:
JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单
JSAPI下单(对应时序图的第5步):商户系统调用该接口在微信支付服务后台生成 预支付交易单
接口地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml微信支付-开发者文档
微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付
微信支付-开发者文档
微信小程序调起支付(对应时序图的第10步)
调用wx.requestPayment(OBJECT)发起微信支付
微信支付准备工作
微信小程序支付时序图:
如何保证数据安全?
完成微信支付有两个关键的步骤:
- 第一个就是需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。
- 第二个就是支付成功之后微信后台会给推送消息。
这两个接口数据的安全性,要求其实是非常高的。
解决: 微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密,需要提前准备相应的一些文件,其实就是一些证书。
获取微信支付平台证书、商户私钥文件:
如何调用到商户系统?
微信后台会调用到商户系统给推送支付的结果,在这里我们就会遇到一个问题,就是微信后台怎么就能调用到我们这个商户系统呢?因为这个调用过程,其实本质上也是一个HTTP请求。
目前,商户系统它的ip地址就是当前自己电脑的ip地址,只是一个局域网内的ip地址,微信后台无法调用到。
解决:内网穿透。通过cpolar软件 可以获得一个临时域名,而这个临时域名是一个公网ip,这样,微信后台就可以请求到商户系统了。
cpolar软件的使用:
获取临时域名:支付成功后微信服务通过该域名回调我们的程序
1). 下载与安装
下载地址:https://dashboard.cpolar.com/get-started
2). cpolar指定authtoken
复制authtoken:
3). 获取临时域名
4). 验证临时域名有效性
访问接口文档
https://544d85d1.r17.cpolar.top/doc.html#/home
代码导入
微信支付相关配置: notify/psySuccess对应的某个controller的路径设置
导入微信支付功能代码: