下单支付
商城的主线剧情到这就要结束了。
实现步骤:
我先从前台数据接收和下单讲。
1 接收前台商品和用户数据(用户点确认支付,到支付详情页时)
前台传来的数据需要进行校验,从数据库里查一查传来的价格是否和数据库一样,不然可能被某些人篡改,改成一分钱付款,那就赔的裤头都没了。
1.1 DTO类用来接收前台传来的信息,然后转为对象:
CartDTO :
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CartDTO {
private Long skuId;//商品skuid
private Integer num; //购买数量
}
OrderDTO:
前台数据传到这里来
/**
* 用来接收前台的数据
*DTO: datatransferobject,数据转成对象
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDTO {
@NotNull
private Long addressId; //收货人地址id
@NotNull
private Integer paymentType; //付款类型
@NotNull
private List<CartDTO> carts; //订单详情
}
1.2 在这里进行订单校验和创建订单
service:
@Transactional
public Long createOrder(OrderDTO orderDTO) {
// 1.新增订单
Order order = new Order();
// 1.1 订单编号,基本信息
long orderId = idWorker.nextId();
order.setOrderId(orderId); //order的id,上面生成了
order.setCreateTime(new Date()); //order的创建时间
order.setPaymentType(orderDTO.getPaymentType()); //order的付款方式,从前台页面获取
// 1.2 用户信息
UserInfo userInfo = CartFilter.getUserInfo();
order.setUserId(userInfo.getId()); //购买人id,从cookie中获取信息,解析后放到线程变量中获取
order.setBuyerNick(userInfo.getUsername()); //购买人名字
order.setBuyerRate(false); //评价
// 1.3 收货人地址(假的)
AddressDTO addr = AddressClient.findById(orderDTO.getAddressId());
order.setReceiver(addr.getName());
order.setReceiverAddress(addr.getAddress());
order.setReceiverCity(addr.getCity());
order.setReceiverDistrict(addr.getDistrict());
order.setReceiverMobile(addr.getPhone());
order.setReceiverState(addr.getState());
order.setReceiverZip(addr.getZipCode());
// 1.4 金额
// 把cartDTO转为一个map,key是sku的id,值是num
Map<Long, Integer> collect = orderDTO.getCarts().stream().collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));
// 取出sku id
Set<Long> ids = collect.keySet();
// 从数据库中查询skus
List<Sku> skus = goodsClient.querySkuByIds(new ArrayList<>(ids));
// 准备orderdetail集合
ArrayList<OrderDetail> details = new ArrayList<>();
Long totalPay = 0L;
for (Sku sku : skus) {
totalPay += sku.getPrice() * collect.get(sku.getId());
OrderDetail orderDetail = new OrderDetail();
orderDetail.setImage(StringUtils.substringBefore(sku.getImages(), ","));
orderDetail.setNum(collect.get(sku.getId()));
orderDetail.setOwnSpec(sku.getOwnSpec());
orderDetail.setPrice(sku.getPrice());
orderDetail.setSkuId(sku.getSpuId());
orderDetail.setOrderId(orderId);
orderDetail.setTitle(sku.getTitle());
details.add(orderDetail);
}
order.setTotalPay(totalPay);
// 实付金额 商品金额+邮费-优惠
// System.out.println("总金额:" + totalPay + "邮费" + order.getPostFee());
order.setActualPay(totalPay/* + order.getPostFee()*/);
// 1.5 order写入数据库,有选择性的新增,没填的让他走数据库中的默认值
int count = orderMapper.insertSelective(order);
if (count != 1) {
log.error("创建订单失败,orderId:{}", orderId);
throw new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
// 2 新增订单详情
int count2 = detailMapper.insertList(details);
if (count != details.size()) {
log.error("创建订单详情失败,orderId:{}", orderId);
throw new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
// 3 新增订单状态
OrderStatus orderStatus = new OrderStatus();
orderStatus.setCreateTime(order.getCreateTime());
orderStatus.setOrderId(orderId);
orderStatus.setStatus(OrderStatusEnum.UNPAY.value());
int count3 = statusMapper.insertSelective(orderStatus);
if (count3 != 1) {
log.error("创建订单状态失败,orderId:{}", orderId);
throw new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
// 4 减库存
List<CartDTO> cartDTOS = orderDTO.getCarts();
goodsClient.decreaseStock(cartDTOS);
return orderId;
}
(创建订单时有idWorker工具类,用来生成商品id编号,编号不会重复,可以自己上网搜一个编号生成器。)
到这里订单信息就已经存到数据库中了,该处理交钱的事了。
2 微信支付的相关配置
流程是:
- 用户点确认支付后根据商品id创建一个支付订单。
- 用户支付订单(这一步微信处理,我们不用管)。
- 当用户处理完后会给用户反馈付款成功,也会给我们(商家)发送一条信息(通过通知回调地址),提示用户已付款让我们进行下一步操作。(必须做出处理回应接收成功了,不然微信会一直发这条消息给我们)
- 我们给用户发货。
先配置一下微信支付:
2.1 把微信需要的配置放到yml:
6个配置分别是:
公众账号ID(我胡乱改了,不能拿来直接用的,需要的话自己申请一个吧)
商户号(也改了)
生成签名的密钥
连接超时时间
读取超时时间
下单通知回调地址 (自己申请的,下篇会讲到)
pay:
appId: wx8397f8696b531232
mchId: 1473426331
key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
connectTimeoutMs: 5000
readTimeoutMs: 10000
notifyUrl: http://h7j4zz.natappfree.cc/notify/pay
2.2 用config类,指明这些都是什么:
@Data
public class PayConfig implements WXPayConfig {
private String appID; //公众账号ID
private String mchID; //商户号
private String key; //生成签名的密钥
private int httpConnectTimeoutMs; //连接超时时间
private int httpReadTimeoutMs; //读取超时时间
private String notifyUrl;// 下单通知回调地址
/**
* 获取商户证书内容
*
* @return 商户证书内容
*/
@Override
public InputStream getCertStream() {
return null;
}
}
2.3 将这些传入到微信的配置中:
@Configuration
public class PayConfiguration {
@Bean
@ConfigurationProperties(prefix = "ly.pay")
public PayConfig payConfig() {
return new PayConfig();
}
@Bean
public WXPay wxPay(PayConfig payConfig) {
return new WXPay(payConfig, WXPayConstants.SignType.HMACSHA256);
}
}
2.4 创建微信支付订单:
这里面的需要三个参数:
1,商品描述信息(用户支付时能看到买的什么)
2,商品订单编号
3,总金额
有一些其它的配置前面 PayConfig已经传入,这里调用就好,有一些其他的配置根据自己需要改动。
@Slf4j
@Component
public class PayHelper {
@Autowired
private WXPay wxPay;
@Autowired
private PayConfig payConfig;
public String getPayUrl(String desc, long orderId, long totalPay) {
// 准备请求参数
Map<String, String> data = new HashMap<>();
// 商品描述
data.put("body", desc);
// 订单号
data.put("out_trade_no", String.valueOf(orderId));
// 金额,单位是分
data.put("total_fee", String.valueOf(totalPay));
// 调用微信支付的终端ip
data.put("spbill_create_ip", "127.0.0.1");
// 回调地址
data.put("notify_url", payConfig.getNotifyUrl());
// 交易类型为扫码支付
data.put("trade_type", "NATIVE"); // 此处指定为扫码支付
try {
// 发起请求
Map<String, String> result = wxPay.unifiedOrder(data);
// 校验业务标示
checkResultCode(result);
// 取出支付链接地址,并返回
String url = result.get("code_url");
if (StringUtils.isBlank(url)) {
throw new RuntimeException("【微信支付】下单支付链接为空");
}
return url;
} catch (Exception e) {
// 统一下单失败
log.error("【支付助手】统一下单失败,失败原因:{}", e.getMessage(), e);
throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
}
}
/**
* 判断业务标识,记录错误原因
*
* @param resp
*/
public void checkResultCode(Map<String, String> resp) {
// 判断业务执行结果
if (WXPayConstants.FAIL.equals(resp.get("result_code"))) {
// 下单业务失败,
log.error("【支付助手】统一下单失败,错误码:{},错误原因:{}",
resp.get("err_code"), resp.get("err_code_des"));
throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
}
}
/**
* 校验签名
*
* @param resp
*/
public void checkSignature(Map<String, String> data) {
try {
boolean boo1 = WXPayUtil.isSignatureValid(data, payConfig.getKey(), WXPayConstants.SignType.MD5);
boolean boo2 = WXPayUtil.isSignatureValid(data, payConfig.getKey(), WXPayConstants.SignType.HMACSHA256);
if (!boo1 && !boo2) {
throw new RuntimeException();
}
} catch (Exception e) {
// 校验失败
log.error("【支付助手】签名校验失败!");
throw new RuntimeException("非法签名!", e);
}
}
}
到这里,让用户交钱的配置已经完成,下一篇让他们交钱。