添加订单和支付网关逻辑
This commit is contained in:
parent
cc7280f375
commit
c566f987f5
|
@ -1,25 +1,33 @@
|
|||
package com.agileboot.api.controller;
|
||||
|
||||
import com.agileboot.common.core.base.BaseController;
|
||||
import com.agileboot.domain.shop.order.dto.CreateOrderResult;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import com.agileboot.domain.shop.order.OrderApplicationService;
|
||||
import com.agileboot.domain.shop.order.command.SubmitOrderCommand;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
/**
|
||||
* 调度日志操作处理
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/order")
|
||||
public class OrderController extends BaseController {
|
||||
|
||||
/**
|
||||
* 访问首页,提示语
|
||||
*/
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
return "暂无订单";
|
||||
private final OrderApplicationService orderApplicationService;
|
||||
|
||||
// 新增提交订单接口
|
||||
@PostMapping("/submit")
|
||||
public ResponseDTO<CreateOrderResult> submitOrder(@Validated @RequestBody SubmitOrderCommand command) {
|
||||
CreateOrderResult result = orderApplicationService.createOrder(command);
|
||||
return ResponseDTO.ok(result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package com.agileboot.api.controller;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.net.URLDecoder;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import com.agileboot.common.utils.OpenSignUtil;
|
||||
import com.agileboot.domain.shop.payment.dto.PaymentCallbackRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/payment")
|
||||
public class PaymentController {
|
||||
// 新增回调接口
|
||||
@PostMapping("/callback")
|
||||
public String paymentCallback(HttpServletRequest request, @RequestBody String requestBody) {
|
||||
try {
|
||||
// 1. 参数解析
|
||||
PaymentCallbackRequest callbackReq = parseCallbackRequest(requestBody);
|
||||
PaymentCallbackRequest.BizContent bizContent = parseBizContent(callbackReq.getBiz_content());
|
||||
|
||||
// 2. 签名验证(需要根据biz_id获取对应的appKey)
|
||||
String appKey = getAppKeyByBizId(callbackReq.getBiz_id()); // 需要实现根据biz_id获取appKey的逻辑
|
||||
boolean signValid = OpenSignUtil.checkOpenSign(appKey, callbackReq.getSign(), requestBody);
|
||||
|
||||
if (!signValid) {
|
||||
log.error("签名验证失败:{}", requestBody);
|
||||
return "fail";
|
||||
}
|
||||
|
||||
// 3. 业务处理(需要实现幂等性校验)
|
||||
handlePaymentSuccess(
|
||||
bizContent.getBiz_order_id(),
|
||||
bizContent.getTotal_amount(),
|
||||
bizContent.getTrade_id()
|
||||
);
|
||||
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
log.error("支付回调处理失败", e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
private PaymentCallbackRequest parseCallbackRequest(String requestBody) {
|
||||
// 实现将URL参数解析为PaymentCallbackRequest
|
||||
// 示例实现(需要根据实际参数格式调整):
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
for (String param : requestBody.split("&")) {
|
||||
String[] pair = param.split("=");
|
||||
if (pair.length == 2) {
|
||||
paramMap.put(pair[0], URLDecoder.decode(pair[1], StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
return BeanUtil.toBean(paramMap, PaymentCallbackRequest.class);
|
||||
}
|
||||
|
||||
private PaymentCallbackRequest.BizContent parseBizContent(String bizContent) {
|
||||
// 实现biz_content的JSON解析
|
||||
return JSONUtil.toBean(bizContent, PaymentCallbackRequest.BizContent.class);
|
||||
}
|
||||
|
||||
// 需要实现的方法(根据业务需求补充)
|
||||
private String getAppKeyByBizId(String bizId) {
|
||||
// 根据biz_id从数据库或配置获取对应的appKey
|
||||
return "wxshop202503081132";
|
||||
}
|
||||
|
||||
private void handlePaymentSuccess(String bizOrderId, Integer amount, String tradeId) {
|
||||
// 实现订单状态更新和幂等性校验
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.agileboot.common.utils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.hutool.crypto.SecureUtil.md5;
|
||||
|
||||
@Slf4j
|
||||
public class OpenSignUtil {
|
||||
/**
|
||||
* @param appKey 支付网关提供的业务秘钥,与biz_id是配对一起提供的
|
||||
* @param sign 支付网关回调参数里的sign字段值
|
||||
* @param reqBody 支付网关回调的原始请求body字符串
|
||||
*/
|
||||
public static boolean checkOpenSign(String appKey, String sign, String reqBody) {
|
||||
String[] fields = reqBody.split("&");
|
||||
Map<String, String> fieldMap = new HashMap<>();
|
||||
for (String field : fields) {
|
||||
String[] pair = field.split("=");
|
||||
if (pair.length != 2) {
|
||||
continue;
|
||||
}
|
||||
// sign字段不参与校验,空字段不校验
|
||||
if (!"sign".equals(pair[0]) && StringUtils.isNotBlank(pair[1])) {
|
||||
fieldMap.put(pair[0], pair[1]);
|
||||
}
|
||||
}
|
||||
String generateSign = openSign(appKey, fieldMap);
|
||||
return Objects.equals(generateSign, sign);
|
||||
}
|
||||
|
||||
private static List<String> getKVList(Map<String, String> sortedParams) {
|
||||
return sortedParams.entrySet().stream()
|
||||
.map(entry -> entry.getKey() + "=" + entry.getValue())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static String openSign(String appKey, Map<String, String> params) {
|
||||
if(MapUtils.isEmpty(params)) {
|
||||
params = new HashMap<String, String>(1);
|
||||
}
|
||||
Map<String, String> sortedParams = new TreeMap<>(params);
|
||||
List<String> kvPairList = getKVList(sortedParams);
|
||||
String sourceText = StringUtils.join(kvPairList, "&");
|
||||
sourceText = sourceText + "&app_key=" + appKey;
|
||||
log.info("sourceText:{}", sourceText);
|
||||
try {
|
||||
return md5(Arrays.toString(sourceText.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package com.agileboot.domain.shop.order;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.agileboot.common.core.page.PageDTO;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||||
import com.agileboot.domain.shop.goods.db.ShopGoodsEntity;
|
||||
import com.agileboot.domain.shop.goods.db.ShopGoodsService;
|
||||
import com.agileboot.domain.shop.order.command.SubmitOrderCommand;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderEntity;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderService;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsService;
|
||||
import com.agileboot.domain.shop.order.dto.CreateOrderResult;
|
||||
import com.agileboot.domain.shop.order.dto.ShopOrderDTO;
|
||||
import com.agileboot.domain.shop.order.model.OrderModel;
|
||||
import com.agileboot.domain.shop.order.model.OrderModelFactory;
|
||||
import com.agileboot.domain.shop.order.model.OrderGoodsModel;
|
||||
import com.agileboot.domain.shop.order.model.OrderGoodsModelFactory;
|
||||
import com.agileboot.domain.shop.order.query.SearchShopOrderQuery;
|
||||
import com.agileboot.domain.shop.payment.PaymentApplicationService;
|
||||
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateRequest;
|
||||
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OrderApplicationService {
|
||||
|
||||
private final ShopOrderService orderService;
|
||||
private final ShopOrderGoodsService orderGoodsService;
|
||||
private final ShopGoodsService goodsService;
|
||||
private final OrderModelFactory orderModelFactory;
|
||||
private final OrderGoodsModelFactory orderGoodsModelFactory;
|
||||
private final PaymentApplicationService paymentApplicationService;
|
||||
|
||||
/*public PageDTO<ShopOrderDTO> getOrderList(SearchShopOrderQuery<> query) {
|
||||
Page<ShopOrderEntity> page = orderService.page(query.toPage(), query.toQueryWrapper());
|
||||
List<ShopOrderDTO> dtoList = page.getRecords().stream().map(ShopOrderDTO::new).collect(Collectors.toList());
|
||||
return new PageDTO<>(dtoList, page.getTotal());
|
||||
}*/
|
||||
|
||||
@Transactional
|
||||
public CreateOrderResult createOrder(SubmitOrderCommand command) {
|
||||
ShopOrderEntity order = command.getOrder();
|
||||
List<ShopOrderGoodsEntity> goodsList = command.getGoodsList();
|
||||
|
||||
OrderModel orderModel = orderModelFactory.create();
|
||||
BeanUtil.copyProperties(order, orderModel);
|
||||
|
||||
orderModel.generateOrderNumber();
|
||||
orderModel.insert();
|
||||
|
||||
processOrderGoods(orderModel, goodsList);
|
||||
|
||||
// 新增支付接口调用
|
||||
WxJsApiPreCreateRequest paymentRequest = buildPaymentRequest(orderModel);
|
||||
WxJsApiPreCreateResponse paymentResponse = paymentApplicationService.callJsApiPreCreate(paymentRequest);
|
||||
|
||||
return new CreateOrderResult(orderModel.getOrderId(), paymentResponse);
|
||||
|
||||
}
|
||||
|
||||
private WxJsApiPreCreateRequest buildPaymentRequest(OrderModel orderModel) {
|
||||
WxJsApiPreCreateRequest request = new WxJsApiPreCreateRequest();
|
||||
request.setIp("222.218.10.217");
|
||||
request.setOpenid(orderModel.getOpenid()); //
|
||||
request.setBiz_order_id(String.valueOf(orderModel.getOrderId())); // 使用订单唯一编号
|
||||
// 金额转换(元转分)建议增加精度处理
|
||||
request.setPay_amount(orderModel.getTotalAmount().toPlainString());
|
||||
request.setTitle("商品订单支付");
|
||||
request.setNotify_url("http://wxshop.ab98.cn/shop-back-end/api/payment/callback");
|
||||
request.setBiz_id("wxshop");
|
||||
request.setUcid(orderModel.getUcid());
|
||||
request.setExtra("");
|
||||
return request;
|
||||
}
|
||||
|
||||
private void processOrderGoods(OrderModel orderModel, List<ShopOrderGoodsEntity> orderGoodsList) {
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
|
||||
for (ShopOrderGoodsEntity goods : orderGoodsList) {
|
||||
OrderGoodsModel goodsModel = orderGoodsModelFactory.create(goods);
|
||||
|
||||
// 设置订单ID关联
|
||||
goodsModel.setOrderId(orderModel.getOrderId());
|
||||
|
||||
// 计算商品金额并验证库存
|
||||
goodsModel.calculateTotal();
|
||||
goodsModel.validateQuantity();
|
||||
|
||||
// 保存订单商品
|
||||
goodsModel.insert();
|
||||
|
||||
// 扣减库存
|
||||
deductGoodsStock(goodsModel.getGoodsId(), goodsModel.getQuantity());
|
||||
|
||||
totalAmount = totalAmount.add(goodsModel.getTotalAmount());
|
||||
}
|
||||
|
||||
// 更新订单总金额
|
||||
orderModel.setTotalAmount(totalAmount);
|
||||
orderModel.updateById();
|
||||
}
|
||||
|
||||
private void deductGoodsStock(Long goodsId, Integer quantity) {
|
||||
ShopGoodsEntity goods = goodsService.getById(goodsId);
|
||||
if (goods == null || goods.getStock() < quantity) {
|
||||
throw new ApiException(ErrorCode.FAILED, "商品库存不足");
|
||||
}
|
||||
goods.setStock(goods.getStock() - quantity);
|
||||
goodsService.updateById(goods);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void cancelOrder(Long orderId) {
|
||||
OrderModel orderModel = orderModelFactory.loadById(orderId);
|
||||
|
||||
// 状态校验
|
||||
orderModel.validateStatusTransition(5); // 5代表已取消
|
||||
|
||||
// 恢复库存
|
||||
List<ShopOrderGoodsEntity> goodsList = orderGoodsService.lambdaQuery()
|
||||
.eq(ShopOrderGoodsEntity::getOrderId, orderId)
|
||||
.list();
|
||||
|
||||
goodsList.forEach(goods -> restoreGoodsStock(goods.getGoodsId(), goods.getQuantity()));
|
||||
|
||||
// 更新订单状态
|
||||
orderModel.setStatus(5);
|
||||
orderModel.updateById();
|
||||
}
|
||||
|
||||
private void restoreGoodsStock(Long goodsId, Integer quantity) {
|
||||
ShopGoodsEntity goods = goodsService.getById(goodsId);
|
||||
if (goods != null) {
|
||||
goods.setStock(goods.getStock() + quantity);
|
||||
goodsService.updateById(goods);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteOrders(BulkOperationCommand<Long> command) {
|
||||
for (Long orderId : command.getIds()) {
|
||||
OrderModel model = orderModelFactory.loadById(orderId);
|
||||
model.deleteById();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.agileboot.domain.shop.order.command;
|
||||
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderEntity;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SubmitOrderCommand {
|
||||
private String openid;
|
||||
private ShopOrderEntity order;
|
||||
private List<ShopOrderGoodsEntity> goodsList;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.agileboot.common.core.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 商品订单表
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("shop_order")
|
||||
@ApiModel(value = "ShopOrderEntity对象", description = "商品订单表")
|
||||
public class ShopOrderEntity extends BaseEntity<ShopOrderEntity> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("订单唯一ID")
|
||||
@TableId(value = "order_id", type = IdType.AUTO)
|
||||
private Long orderId;
|
||||
|
||||
@ApiModelProperty("ucid")
|
||||
@TableField("ucid")
|
||||
private String ucid;
|
||||
|
||||
@ApiModelProperty("openid")
|
||||
@TableField("openid")
|
||||
private String openid;
|
||||
|
||||
@ApiModelProperty("支付网关交易id")
|
||||
@TableField("trade_id")
|
||||
private String tradeId;
|
||||
|
||||
@ApiModelProperty("订单总金额")
|
||||
@TableField("total_amount")
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
@ApiModelProperty("订单状态(1待付款 2已付款 3已发货 4已完成 5已取消)")
|
||||
@TableField("`status`")
|
||||
private Integer status;
|
||||
|
||||
@ApiModelProperty("支付状态(1未支付 2已支付 3退款中 4已退款)")
|
||||
@TableField("pay_status")
|
||||
private Integer payStatus;
|
||||
|
||||
@ApiModelProperty("支付方式")
|
||||
@TableField("payment_method")
|
||||
private String paymentMethod;
|
||||
|
||||
@ApiModelProperty("支付时间")
|
||||
@TableField("pay_time")
|
||||
private Date payTime;
|
||||
|
||||
|
||||
@Override
|
||||
public Serializable pkVal() {
|
||||
return this.orderId;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.agileboot.common.core.base.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单商品明细表
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("shop_order_goods")
|
||||
@ApiModel(value = "ShopOrderGoodsEntity对象", description = "订单商品明细表")
|
||||
public class ShopOrderGoodsEntity extends BaseEntity<ShopOrderGoodsEntity> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("订单商品唯一ID")
|
||||
@TableId(value = "order_goods_id", type = IdType.AUTO)
|
||||
private Long orderGoodsId;
|
||||
|
||||
@ApiModelProperty("关联订单ID")
|
||||
@TableField("order_id")
|
||||
private Long orderId;
|
||||
|
||||
@ApiModelProperty("关联商品ID")
|
||||
@TableField("goods_id")
|
||||
private Long goodsId;
|
||||
|
||||
@ApiModelProperty("购买数量")
|
||||
@TableField("quantity")
|
||||
private Integer quantity;
|
||||
|
||||
@ApiModelProperty("购买时单价")
|
||||
@TableField("price")
|
||||
private BigDecimal price;
|
||||
|
||||
@ApiModelProperty("商品总金额")
|
||||
@TableField("total_amount")
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
@ApiModelProperty("商品状态(1正常 2已退货 3已换货)")
|
||||
@TableField("`status`")
|
||||
private Integer status;
|
||||
|
||||
|
||||
@Override
|
||||
public Serializable pkVal() {
|
||||
return this.orderGoodsId;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单商品明细表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
public interface ShopOrderGoodsMapper extends BaseMapper<ShopOrderGoodsEntity> {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单商品明细表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
public interface ShopOrderGoodsService extends IService<ShopOrderGoodsEntity> {
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 订单商品明细表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
@Service
|
||||
public class ShopOrderGoodsServiceImpl extends ServiceImpl<ShopOrderGoodsMapper, ShopOrderGoodsEntity> implements ShopOrderGoodsService {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 商品订单表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
public interface ShopOrderMapper extends BaseMapper<ShopOrderEntity> {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 商品订单表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
public interface ShopOrderService extends IService<ShopOrderEntity> {
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.agileboot.domain.shop.order.db;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 商品订单表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author valarchie
|
||||
* @since 2025-03-10
|
||||
*/
|
||||
@Service
|
||||
public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrderEntity> implements ShopOrderService {
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.agileboot.domain.shop.order.dto;
|
||||
|
||||
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class CreateOrderResult {
|
||||
private Long orderId;
|
||||
private WxJsApiPreCreateResponse paymentInfo;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.agileboot.domain.shop.order.dto;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderEntity;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ShopOrderDTO {
|
||||
|
||||
public ShopOrderDTO(ShopOrderEntity entity) {
|
||||
if (entity != null) {
|
||||
BeanUtil.copyProperties(entity, this);
|
||||
|
||||
// 如果需要关联查询(如商品明细),可参考以下方式
|
||||
/*
|
||||
List<ShopOrderGoodsEntity> goodsList = orderGoodsService.lambdaQuery()
|
||||
.eq(ShopOrderGoodsEntity::getOrderId, entity.getOrderId())
|
||||
.list();
|
||||
this.goodsList = goodsList.stream().map(ShopOrderGoodsDTO::new).collect(Collectors.toList());
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
private Long orderId;
|
||||
private String ucid;
|
||||
private String openid;
|
||||
private String tradeId;
|
||||
private BigDecimal totalAmount;
|
||||
private Integer status;
|
||||
private Integer payStatus;
|
||||
private String paymentMethod;
|
||||
private Date payTime;
|
||||
private Date createTime;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.agileboot.domain.shop.order.model;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.agileboot.domain.shop.goods.db.ShopGoodsEntity;
|
||||
import com.agileboot.domain.shop.goods.db.ShopGoodsService;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsService;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class OrderGoodsModel extends ShopOrderGoodsEntity {
|
||||
|
||||
private final ShopOrderGoodsService orderGoodsService;
|
||||
private final ShopGoodsService goodsService;
|
||||
|
||||
public OrderGoodsModel(ShopOrderGoodsEntity entity,
|
||||
ShopOrderGoodsService orderGoodsService,
|
||||
ShopGoodsService goodsService) {
|
||||
this(orderGoodsService, goodsService);
|
||||
if (entity != null) {
|
||||
BeanUtil.copyProperties(entity, this);
|
||||
}
|
||||
}
|
||||
|
||||
public OrderGoodsModel(ShopOrderGoodsService orderGoodsService,
|
||||
ShopGoodsService goodsService) {
|
||||
this.orderGoodsService = orderGoodsService;
|
||||
this.goodsService = goodsService;
|
||||
}
|
||||
|
||||
public void calculateTotal() {
|
||||
ShopGoodsEntity goods = goodsService.getById(getGoodsId());
|
||||
if (goods != null) {
|
||||
BigDecimal price = goods.getPrice();
|
||||
this.setPrice(price);
|
||||
this.setTotalAmount(price.multiply(BigDecimal.valueOf(getQuantity())));
|
||||
}
|
||||
}
|
||||
|
||||
public void validateQuantity() {
|
||||
ShopGoodsEntity goods = goodsService.getById(getGoodsId());
|
||||
if (goods != null && getQuantity() > goods.getStock()) {
|
||||
throw new RuntimeException("商品库存不足");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.agileboot.domain.shop.order.model;
|
||||
|
||||
import com.agileboot.domain.shop.goods.db.ShopGoodsService;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsService;
|
||||
|
||||
public class OrderGoodsModelFactory {
|
||||
|
||||
private final ShopOrderGoodsService orderGoodsService;
|
||||
private final ShopGoodsService goodsService;
|
||||
|
||||
public OrderGoodsModelFactory(ShopOrderGoodsService orderGoodsService,
|
||||
ShopGoodsService goodsService) {
|
||||
this.orderGoodsService = orderGoodsService;
|
||||
this.goodsService = goodsService;
|
||||
}
|
||||
|
||||
public OrderGoodsModel create(ShopOrderGoodsEntity entity) {
|
||||
return new OrderGoodsModel(entity, orderGoodsService, goodsService);
|
||||
}
|
||||
|
||||
public OrderGoodsModel create() {
|
||||
return new OrderGoodsModel(orderGoodsService, goodsService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package com.agileboot.domain.shop.order.model;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.agileboot.common.config.AgileBootConfig;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderEntity;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsService;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderService;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class OrderModel extends ShopOrderEntity {
|
||||
|
||||
private ShopOrderService orderService;
|
||||
private ShopOrderGoodsService orderGoodsService;
|
||||
|
||||
public OrderModel(ShopOrderEntity entity, ShopOrderService orderService,
|
||||
ShopOrderGoodsService orderGoodsService) {
|
||||
this(orderService, orderGoodsService);
|
||||
if (entity != null) {
|
||||
BeanUtil.copyProperties(entity, this);
|
||||
}
|
||||
}
|
||||
|
||||
public OrderModel(ShopOrderService orderService, ShopOrderGoodsService orderGoodsService) {
|
||||
this.orderService = orderService;
|
||||
this.orderGoodsService = orderGoodsService;
|
||||
}
|
||||
|
||||
public void calculateTotalAmount(List<Long> goodsIds) {
|
||||
BigDecimal total = goodsIds.stream()
|
||||
.map(goodsId -> {
|
||||
// 这里需要实现获取商品价格和计算逻辑
|
||||
return BigDecimal.ZERO; // 示例返回值
|
||||
})
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
this.setTotalAmount(total);
|
||||
}
|
||||
|
||||
public void validateStatusTransition(Integer newStatus) {
|
||||
Integer currentStatus = this.getStatus();
|
||||
// 实现状态机校验逻辑
|
||||
if (currentStatus == 5 && newStatus != 1) {
|
||||
throw new ApiException(ErrorCode.FAILED, "已取消订单不可修改状态");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateById() {
|
||||
if (AgileBootConfig.isDemoEnabled() && isSpecialOrder()) {
|
||||
throw new ApiException(ErrorCode.FAILED);
|
||||
}
|
||||
return super.updateById();
|
||||
}
|
||||
|
||||
private boolean isSpecialOrder() {
|
||||
// 示例逻辑:判断是否为特殊订单
|
||||
return this.getTotalAmount().compareTo(new BigDecimal("10000")) > 0;
|
||||
}
|
||||
public void generateOrderNumber() {
|
||||
// 实现订单号生成逻辑(示例:时间戳+随机数)
|
||||
String orderNo = "OD" + System.currentTimeMillis() + (int)(Math.random()*1000);
|
||||
this.setUcid(orderNo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.agileboot.domain.shop.order.model;
|
||||
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderEntity;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderGoodsService;
|
||||
import com.agileboot.domain.shop.order.db.ShopOrderService;
|
||||
|
||||
public class OrderModelFactory {
|
||||
|
||||
private final ShopOrderService orderService;
|
||||
private final ShopOrderGoodsService orderGoodsService;
|
||||
|
||||
public OrderModelFactory(ShopOrderService orderService,
|
||||
ShopOrderGoodsService orderGoodsService) {
|
||||
this.orderService = orderService;
|
||||
this.orderGoodsService = orderGoodsService;
|
||||
}
|
||||
|
||||
public OrderModel create(ShopOrderEntity entity) {
|
||||
return new OrderModel(entity, orderService, orderGoodsService);
|
||||
}
|
||||
|
||||
public OrderModel create() {
|
||||
return new OrderModel(orderService, orderGoodsService);
|
||||
}
|
||||
|
||||
public OrderModel loadById(Long orderId) {
|
||||
ShopOrderEntity entity = orderService.getById(orderId);
|
||||
if (entity == null) {
|
||||
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, orderId, "订单");
|
||||
}
|
||||
return new OrderModel(entity, orderService, orderGoodsService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.agileboot.domain.shop.order.query;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.agileboot.common.core.page.AbstractPageQuery;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class SearchShopOrderQuery<T> extends AbstractPageQuery<T> {
|
||||
|
||||
private String orderNumber;
|
||||
private Integer status;
|
||||
private Integer payStatus;
|
||||
private Date startTime;
|
||||
private Date endTime;
|
||||
|
||||
@Override
|
||||
public QueryWrapper<T> addQueryCondition() {
|
||||
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
queryWrapper
|
||||
.like(StrUtil.isNotEmpty(orderNumber), "order_number", orderNumber)
|
||||
.eq(status != null, "status", status)
|
||||
.eq(payStatus != null, "pay_status", payStatus)
|
||||
.between(startTime != null && endTime != null, "create_time", startTime, endTime)
|
||||
.eq("deleted", 0)
|
||||
.orderByDesc("create_time");
|
||||
|
||||
return queryWrapper;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.agileboot.domain.shop.payment;
|
||||
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateRequest;
|
||||
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentApplicationService {
|
||||
public WxJsApiPreCreateResponse callJsApiPreCreate(WxJsApiPreCreateRequest request) {
|
||||
String gatewayUrl = "http://111.59.237.29:7890/open/trade/wx/jsapi/precreate";
|
||||
|
||||
try {
|
||||
String jsonBody = JSONUtil.toJsonStr(request);
|
||||
|
||||
// 使用try-with-resources自动关闭连接
|
||||
try (HttpResponse httpResponse = HttpUtil.createPost(gatewayUrl)
|
||||
.contentType(ContentType.JSON.getValue())
|
||||
.body(jsonBody)
|
||||
.timeout(5000)
|
||||
.execute()) {
|
||||
|
||||
// 获取HTTP状态码和响应体
|
||||
int status = httpResponse.getStatus();
|
||||
String result = httpResponse.body();
|
||||
|
||||
// 先校验HTTP状态码
|
||||
if (status != HttpStatus.HTTP_OK) {
|
||||
throw new RuntimeException("支付网关返回异常状态码:" + status);
|
||||
}
|
||||
|
||||
// ... 保持原有的JSON解析逻辑 ...
|
||||
if (!JSONUtil.isTypeJSONObject(result)) {
|
||||
throw new RuntimeException("支付网关返回非JSON格式响应:" + result);
|
||||
}
|
||||
|
||||
WxJsApiPreCreateResponse response = JSONUtil.toBean(result, WxJsApiPreCreateResponse.class);
|
||||
if (response.getAppId() != null) {
|
||||
return response;
|
||||
}
|
||||
|
||||
PaymentGatewayError error = JSONUtil.toBean(result, PaymentGatewayError.class);
|
||||
throw new RuntimeException("支付网关业务错误:" + error.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("支付网关调用失败:" + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加错误响应处理类
|
||||
@Data
|
||||
private static class PaymentGatewayError {
|
||||
private String timestamp;
|
||||
private Integer status;
|
||||
private String error;
|
||||
private String message;
|
||||
private String path;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.agileboot.domain.shop.payment.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class PaymentCallbackRequest {
|
||||
private String client_id;
|
||||
private String biz_id;
|
||||
private String nonce_str;
|
||||
private String sign;
|
||||
private String sign_type;
|
||||
private String version;
|
||||
private String timestamp;
|
||||
private String biz_content; // 需要二次解析的字段
|
||||
|
||||
// biz_content解析后的实体
|
||||
@Data
|
||||
public static class BizContent {
|
||||
private String uid;
|
||||
private String trade_id;
|
||||
private Integer total_amount;
|
||||
private String trade_pay_time;
|
||||
private String trade_status;
|
||||
private Integer pay_type;
|
||||
private String callback_content;
|
||||
private String title;
|
||||
private String biz_order_id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.agileboot.domain.shop.payment.dto;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxJsApiPreCreateRequest {
|
||||
private String ip;
|
||||
|
||||
@NotBlank(message = "openid不能为空")
|
||||
private String openid;
|
||||
|
||||
private String biz_order_id;
|
||||
|
||||
private String pay_amount;
|
||||
|
||||
private String title;
|
||||
|
||||
private String notify_url;
|
||||
|
||||
private String biz_id;
|
||||
|
||||
private String ucid; // 选填
|
||||
private String extra; // 选填
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.agileboot.domain.shop.payment.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WxJsApiPreCreateResponse {
|
||||
private String appId;
|
||||
private String timeStamp;
|
||||
private String nonceStr;
|
||||
@JsonProperty("package")
|
||||
private String packageValue;
|
||||
private String signType;
|
||||
private String paySign;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?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.agileboot.domain.shop.order.db.ShopOrderGoodsMapper">
|
||||
|
||||
</mapper>
|
|
@ -0,0 +1,5 @@
|
|||
<?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.agileboot.domain.shop.order.db.ShopOrderMapper">
|
||||
|
||||
</mapper>
|
|
@ -61,7 +61,7 @@ public class CodeGenerator {
|
|||
//生成的类 放在orm子模块下的/target/generated-code目录底下
|
||||
.module("/agileboot-orm/target/generated-code")
|
||||
.parentPackage("com.agileboot")
|
||||
.tableName("shop_category")
|
||||
.tableName("shop_order_goods")
|
||||
// 决定是否继承基类
|
||||
.isExtendsFromBaseEntity(true)
|
||||
.build();
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
CREATE TABLE `shop_order` (
|
||||
`order_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单唯一ID',
|
||||
`ucid` VARCHAR(32) DEFAULT NULL COMMENT 'ucid',
|
||||
`openid` VARCHAR(32) NOT NULL COMMENT 'openid',
|
||||
`trade_id` VARCHAR(32) NOT NULL COMMENT '支付网关交易id',
|
||||
`total_amount` DECIMAL(15,2) NOT NULL COMMENT '订单总金额',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '订单状态(1待付款 2已付款 3已发货 4已完成 5已取消)',
|
||||
`pay_status` TINYINT NOT NULL DEFAULT 1 COMMENT '支付状态(1未支付 2已支付 3退款中 4已退款)',
|
||||
`payment_method` VARCHAR(32) DEFAULT NULL COMMENT '支付方式',
|
||||
`pay_time` DATETIME DEFAULT NULL COMMENT '支付时间',
|
||||
`creator_id` BIGINT NOT NULL DEFAULT 0 COMMENT '创建者ID',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` BIGINT NOT NULL DEFAULT 0 COMMENT '更新者ID',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志(0存在 1删除)',
|
||||
PRIMARY KEY (`order_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_pay_status` (`pay_status`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品订单表';
|
||||
|
||||
CREATE TABLE `shop_order_goods` (
|
||||
`order_goods_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单商品唯一ID',
|
||||
`order_id` BIGINT NOT NULL COMMENT '关联订单ID',
|
||||
`goods_id` BIGINT NOT NULL COMMENT '关联商品ID',
|
||||
`quantity` INT NOT NULL DEFAULT 1 COMMENT '购买数量',
|
||||
`price` DECIMAL(15,2) NOT NULL COMMENT '购买时单价',
|
||||
`total_amount` DECIMAL(15,2) GENERATED ALWAYS AS (quantity * price) STORED COMMENT '商品总金额',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '商品状态(1正常 2已退货 3已换货)',
|
||||
`creator_id` BIGINT NOT NULL DEFAULT 0 COMMENT '创建者ID',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` BIGINT NOT NULL DEFAULT 0 COMMENT '更新者ID',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志(0存在 1删除)',
|
||||
PRIMARY KEY (`order_goods_id`),
|
||||
KEY `idx_order` (`order_id`),
|
||||
KEY `idx_goods` (`goods_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
CONSTRAINT `fk_order_goods_order` FOREIGN KEY (`order_id`) REFERENCES `shop_order` (`order_id`),
|
||||
CONSTRAINT `fk_order_goods_goods` FOREIGN KEY (`goods_id`) REFERENCES `shop_goods` (`goods_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单商品明细表';
|
|
@ -0,0 +1,222 @@
|
|||
# 全局公共参数
|
||||
|
||||
**全局Header参数**
|
||||
|
||||
| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
|
||||
| --- | --- | ---- | ---- | ---- |
|
||||
| 暂无参数 |
|
||||
|
||||
**全局Query参数**
|
||||
|
||||
| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
|
||||
| --- | --- | ---- | ---- | ---- |
|
||||
| 暂无参数 |
|
||||
|
||||
**全局Body参数**
|
||||
|
||||
| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
|
||||
| --- | --- | ---- | ---- | ---- |
|
||||
| 暂无参数 |
|
||||
|
||||
**全局认证方式**
|
||||
|
||||
> 无需认证
|
||||
|
||||
# 状态码说明
|
||||
|
||||
| 状态码 | 中文描述 |
|
||||
| --- | ---- |
|
||||
| 暂无参数 |
|
||||
|
||||
# 微信支付
|
||||
|
||||
> 创建人: 达民
|
||||
|
||||
> 更新人: 达民
|
||||
|
||||
> 创建时间: 2025-03-08 08:44:12
|
||||
|
||||
> 更新时间: 2025-03-08 08:44:12
|
||||
|
||||
```text
|
||||
暂无描述
|
||||
```
|
||||
|
||||
**目录Header参数**
|
||||
|
||||
| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
|
||||
| --- | --- | ---- | ---- | ---- |
|
||||
| 暂无参数 |
|
||||
|
||||
**目录Query参数**
|
||||
|
||||
| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
|
||||
| --- | --- | ---- | ---- | ---- |
|
||||
| 暂无参数 |
|
||||
|
||||
**目录Body参数**
|
||||
|
||||
| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
|
||||
| --- | --- | ---- | ---- | ---- |
|
||||
| 暂无参数 |
|
||||
|
||||
**目录认证信息**
|
||||
|
||||
> 继承父级
|
||||
|
||||
**Query**
|
||||
|
||||
## JS API支付
|
||||
|
||||
> 创建人: 达民
|
||||
|
||||
> 更新人: 达民
|
||||
|
||||
> 创建时间: 2025-03-08 08:49:16
|
||||
|
||||
> 更新时间: 2025-03-08 15:08:38
|
||||
|
||||
**在微信内置浏览器里打开目标应用页面(如微信公众号、点击聊天窗口的链接打开页面),根据需要调起微信支付.
|
||||
1、openid的获取,参考:https://segmentfault.com/a/1190000013392838 。也可以自己找微信官方文档。
|
||||
2、如何调起jsapi支付,参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
|
||||
3、支付回调。支付网关根据请求支付传递的notify_url将支付结果通知到业务方
|
||||
回调参数**
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| client_id | 客户端ID |
|
||||
| biz_id | 支付网关为业务方分配的ID |
|
||||
| nonce_str | 参与签名的混淆字段 |
|
||||
| sign | 签名校验字段值 |
|
||||
| sign_type | 签名类型,一般是MD5 |
|
||||
| version | 支付网关版本号 |
|
||||
| timestamp | 回调时的时间戳,毫秒 |
|
||||
| biz_content | 订单的相关数据,以key=value组合并以&拼接在一起的字符串,其中value未做urlencode处理 |
|
||||
| 以上参数按照key=value的形式,并对参数值做urlencode,以 & 拼接多个key=value形成一个字符串,以POST的方式调用notify_url时将该字符串提交给业务方。 | |
|
||||
|
||||
**其中,biz_content包含了以下参数**
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| uid | 应用方为人员分配的记录ID,应用方在请求接口时以ucid传递的参数值 |
|
||||
| trade_id | 支付网关为本次支付分配的记录ID |
|
||||
| total_amount | 本次支付涉及的金额,单位:分 |
|
||||
| trade_pay_time | 支付成功的时间,格式:yyyy-MM-dd HH:mm:ss |
|
||||
| trade_status | 支付支付状态,成功时值为:SUCCESS |
|
||||
| pay_type | 支付方式,jsapi接口调起的支付,本字段值固定为:113 |
|
||||
| callback_content | 微信回调给支付网关的原始xml数据,参考https://pay.weixin.qq.com/doc/v2/partner/4011936644 |
|
||||
| title | 业务方调接口时传递的title字段值 |
|
||||
| biz_order_id | 业务方支付订单的订单号 |
|
||||
|
||||
**举个例子:
|
||||
biz_content=%7B%22uid%22%3A%22102579%22%2C%22trade_id%22%3A%221501822040%22%2C%22total_amount%22%3A83601%2C%22trade_pay_time%22%3A%222025-03-08%2008%3A48%3A35%22%2C%22trade_status%22%3A%22SUCCESS%22%2C%22pay_type%22%3A%22113%22%2C%22callback_content%22%3A%22%3Cxml%3E%E7%95%A5%3C%2Fxml%3E%22%2C%22title%22%3A%22%E4%BA%91%E9%98%9F%E9%95%BF%E5%8C%BB%E8%8D%AF%E8%BF%9E%E9%94%81%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F%22%2C%22biz_order_id%22%3A%2221-364535%22%7D&nonce_str=11961acffe074c7da556a18e8549027f&sign=5ec91e8d3f4feb4b3c273e5512a7d7c3&biz_id=ydrug&sign_type=MD5&version=1.0&client_id=yeshan×tamp=1741394916159,只有通过了签名校验,才应该能被业务方接受并进行业务的下一步处理。业务方应该根据回调数据里的相关参数(如:biz_order_id)找回相应的订单以进行下一步的业务处理。注意:同一个支付订单的支付结果,支付网关可能会多次回调通知业务方,所以业务方要做好幂等处理。签名校验算法如下:**
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param appKey 支付网关提供的业务秘钥,与biz_id是配对一起提供的
|
||||
* @param sign 支付网关回调参数里的sign字段值
|
||||
* @param reqBody 支付网关回调的原始请求body字符串
|
||||
*/
|
||||
public static boolean checkOpenSign(String appKey, String sign, String reqBody) {
|
||||
String[] fields = reqBody.split("&");
|
||||
Map<String, String> fieldMap = new HashMap<>();
|
||||
for (String field : fields) {
|
||||
String[] pair = field.split("=");
|
||||
if (pair.length != 2) {
|
||||
continue;
|
||||
}
|
||||
// sign字段不参与校验,空字段不校验
|
||||
if (!"sign".equals(pair[0]) && StringUtils.isNotBlank(pair[1])) {
|
||||
fieldMap.put(pair[0], pair[1]);
|
||||
}
|
||||
}
|
||||
String generateSign = openSign(appKey, fieldMap);
|
||||
return Objects.equals(generateSign, sign);
|
||||
}
|
||||
|
||||
public static String openSign(String appKey, Map<String, String> params) {
|
||||
if(MapUtils.isEmpty(params)) {
|
||||
params = new HashMap<String, String>(1);
|
||||
}
|
||||
Map<String, String> sortedParams = new TreeMap<>(params);
|
||||
List<String> kvPairList = getKVList(sortedParams);
|
||||
String sourceText = StringUtils.join(kvPairList, "&");
|
||||
sourceText = sourceText + "&app_key=" + appKey;
|
||||
LogUtils.NORMAL.info("sourceText:" + sourceText);
|
||||
try {
|
||||
return md5(sourceText.getBytes("utf-8"));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**接口状态**
|
||||
|
||||
> 开发中
|
||||
|
||||
**接口URL**
|
||||
|
||||
> http://localhost:7045/open/trade/wx/jsapi/precreate
|
||||
|
||||
| 环境 | URL |
|
||||
| --- | --- |
|
||||
|
||||
|
||||
**请求方式**
|
||||
|
||||
> POST
|
||||
|
||||
**Content-Type**
|
||||
|
||||
> json
|
||||
|
||||
**请求Body参数**
|
||||
|
||||
```javascript
|
||||
{
|
||||
"ucid": "123", //应用方为人员分配的记录ID,以方便后续排查问题。选填
|
||||
"ip": "127.0.0.1", //调用方应用所在服务器的ip。必填
|
||||
"openid": "o_xsewew131SGS", //微信用户的openid,参考【设计】模块的【详细说明】。必填
|
||||
"biz_order_id": "1234536", //应用方为本次支付订单定义的订单ID,务必确保在业务应用内唯一。必填
|
||||
"pay_amount": 1, //支付金额,单位:分。必填
|
||||
"title": "测试", //用户在微信支付界面看到订单标题,也就是商品描述。必填
|
||||
"notify_url": "http://...", //业务方接收支付结果的异步回调地址。必填
|
||||
"biz_id": "abc", //支付网关为业务方分配的业务应用ID。必填
|
||||
"extra": "342rewrs" //业务方期望支付网关回调时透传的数据。选填
|
||||
}
|
||||
```
|
||||
|
||||
**认证方式**
|
||||
|
||||
> 继承父级
|
||||
|
||||
**响应示例**
|
||||
|
||||
* 成功(200)
|
||||
|
||||
```javascript
|
||||
//成功,使用以下参数调起jsapi支付。
|
||||
{
|
||||
"appId": "",
|
||||
"timeStamp": "",
|
||||
"nonceStr": "",
|
||||
"package": "",
|
||||
"signType": "",
|
||||
"paySign": ""
|
||||
}
|
||||
```
|
||||
|
||||
* 失败(200)
|
||||
|
||||
```javascript
|
||||
{
|
||||
"timestamp": "2025-03-08T01:55:14.480+0000",
|
||||
"status": 500,
|
||||
"error": "Internal Server Error",
|
||||
"message": "第三方返回失败, errorMsg = 第三方返回失败, errorMsg = 无效的openid",
|
||||
"path": "/open/trade/wx/jsapi/precreate"
|
||||
}
|
||||
```
|
||||
|
||||
**Query**
|
Loading…
Reference in New Issue