feat(config): 更新开发和生产环境配置
- 修改开发环境前端URL从localhost改为公网地址 - 更新PayPal Webhook URL使用公网地址配置 - 在生产环境配置中添加服务器端口和应用配置 - 添加PayPal支付配置的详细注释说明 - 简化ERP用户管理API文档格式,移除冗余说明 - 移除PayPal订单生命周期和Webhook指南文档 - 优化PayPal Webhook配置文档内容 fix(order): 修复订单创建和库存管理并发问题 - 实现SELECT FOR UPDATE锁定SKU记录防止超卖 - 添加库存扣减原子操作确保并发安全 - 简化日志输出,移除冗余调试信息 - 添加订单取消功能并恢复库存 - 优化订单查询和状态更新逻辑 feat(mapper): 添加库存扣减和恢复功能 - 实现deductStock方法用于扣减库存 - 添加restoreStock方法用于恢复库存 - 实现selectByIdForUpdate方法用于悲观锁 - 为Mapper接口添加必要的注解支持
This commit is contained in:
@@ -15,12 +15,16 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 配置跨域
|
||||
* 注意:生产环境建议限制为具体的前端域名
|
||||
*/
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
log.info("配置跨域访问,路径: /api/**, 允许所有来源");
|
||||
// 开发环境允许所有来源,生产环境建议限制为具体域名
|
||||
String allowedOrigins = System.getProperty("cors.allowed.origins", "*");
|
||||
log.info("配置跨域访问,路径: /api/**, 允许来源: {}", allowedOrigins);
|
||||
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedOriginPatterns(allowedOrigins)
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.mtkj.mtpay.entity.MtProductSku;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,5 +33,38 @@ public interface MtProductSkuMapper extends BaseMapper<MtProductSku> {
|
||||
"</foreach>" +
|
||||
"</script>")
|
||||
int insertBatch(@Param("skuList") List<MtProductSku> skuList);
|
||||
|
||||
/**
|
||||
* 扣减库存(使用SELECT FOR UPDATE锁防止超卖)
|
||||
* 使用悲观锁确保并发安全
|
||||
*
|
||||
* @param skuId SKU ID
|
||||
* @param quantity 扣减数量
|
||||
* @return 更新行数(1表示成功,0表示库存不足)
|
||||
*/
|
||||
@Update("UPDATE mt_product_sku SET stock = stock - #{quantity}, update_time = NOW() " +
|
||||
"WHERE id = #{skuId} AND stock >= #{quantity} AND status = 'ACTIVE'")
|
||||
int deductStock(@Param("skuId") Long skuId, @Param("quantity") Integer quantity);
|
||||
|
||||
/**
|
||||
* 恢复库存(订单取消时使用)
|
||||
*
|
||||
* @param skuId SKU ID
|
||||
* @param quantity 恢复数量
|
||||
* @return 更新行数
|
||||
*/
|
||||
@Update("UPDATE mt_product_sku SET stock = stock + #{quantity}, update_time = NOW() " +
|
||||
"WHERE id = #{skuId}")
|
||||
int restoreStock(@Param("skuId") Long skuId, @Param("quantity") Integer quantity);
|
||||
|
||||
/**
|
||||
* 查询并锁定SKU(使用SELECT FOR UPDATE)
|
||||
* 用于在事务中锁定SKU记录,防止并发问题
|
||||
*
|
||||
* @param skuId SKU ID
|
||||
* @return SKU实体
|
||||
*/
|
||||
@Select("SELECT * FROM mt_product_sku WHERE id = #{skuId} FOR UPDATE")
|
||||
MtProductSku selectByIdForUpdate(@Param("skuId") Long skuId);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,5 +67,11 @@ public interface CustomerOrderService {
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<com.mtkj.mtpay.dto.response.OrderListResponseDTO> queryOrders(OrderQueryRequestDTO query);
|
||||
|
||||
/**
|
||||
* 取消订单(恢复库存)
|
||||
* @param orderNo 订单号
|
||||
*/
|
||||
void cancelOrder(String orderNo);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,42 +51,48 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public CustomerOrderResponseDTO createOrder(CreateCustomerOrderRequestDTO request) {
|
||||
log.info("创建客户订单,商品ID: {}, SKU ID: {}, 数量: {}",
|
||||
request.getProductId(), request.getSkuId(), request.getQuantity());
|
||||
|
||||
// 验证商品是否存在
|
||||
MtProduct product = productMapper.selectById(request.getProductId());
|
||||
if (product == null) {
|
||||
log.warn("商品不存在,商品ID: {}", request.getProductId());
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "商品不存在");
|
||||
}
|
||||
|
||||
// 验证商品状态:下架商品不能创建订单
|
||||
if (ProductStatus.INACTIVE.getCode().equals(product.getStatus())) {
|
||||
log.warn("商品已下架,无法创建订单,商品ID: {}", request.getProductId());
|
||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "商品已下架,无法创建订单");
|
||||
}
|
||||
|
||||
// 验证SKU是否存在
|
||||
MtProductSku sku = productSkuMapper.selectById(request.getSkuId());
|
||||
if (sku == null || !sku.getProductId().equals(request.getProductId())) {
|
||||
log.warn("SKU不存在或不属于该商品,SKU ID: {}, 商品ID: {}",
|
||||
request.getSkuId(), request.getProductId());
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "SKU不存在");
|
||||
}
|
||||
|
||||
// 验证库存:库存为0或不足时不能创建订单
|
||||
if (sku.getStock() == null || sku.getStock() <= 0) {
|
||||
log.warn("库存为0,无法创建订单,SKU ID: {}, 库存: {}",
|
||||
request.getSkuId(), sku.getStock());
|
||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "商品库存为0,无法创建订单");
|
||||
}
|
||||
if (sku.getStock() < request.getQuantity()) {
|
||||
log.warn("库存不足,SKU ID: {}, 库存: {}, 需要: {}",
|
||||
request.getSkuId(), sku.getStock(), request.getQuantity());
|
||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "库存不足");
|
||||
}
|
||||
|
||||
// 关键:使用SELECT FOR UPDATE锁定SKU记录,防止并发超卖
|
||||
MtProductSku lockedSku = productSkuMapper.selectByIdForUpdate(request.getSkuId());
|
||||
if (lockedSku == null) {
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "SKU不存在");
|
||||
}
|
||||
|
||||
// 再次验证库存(锁定后的最新库存)
|
||||
if (lockedSku.getStock() == null || lockedSku.getStock() < request.getQuantity()) {
|
||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "库存不足");
|
||||
}
|
||||
|
||||
// 扣减库存(使用原子操作,确保并发安全)
|
||||
int deductResult = productSkuMapper.deductStock(request.getSkuId(), request.getQuantity());
|
||||
if (deductResult <= 0) {
|
||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "库存扣减失败,可能库存不足");
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
CustomerOrder order = new CustomerOrder();
|
||||
order.setOrderNo(OrderIdGenerator.generateMerchantTransactionId());
|
||||
@@ -98,17 +104,12 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
if (currency != null && !currency.trim().isEmpty()) {
|
||||
try {
|
||||
String targetLanguage = baiduTranslatorUtils.getLanguageByCurrency(currency);
|
||||
log.debug("订单货币: {}, 推断目标语言: {}, 原始商品名称: {}",
|
||||
currency, targetLanguage, product.getName());
|
||||
String translated = baiduTranslatorUtils.getTransResult(product.getName(), targetLanguage);
|
||||
if (translated != null && !translated.equals(product.getName())) {
|
||||
translatedProductName = translated;
|
||||
log.info("商品名称翻译: {} -> {} (货币: {}, 语言: {})",
|
||||
product.getName(), translatedProductName, currency, targetLanguage);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("翻译商品名称失败,使用原始名称,商品ID: {}, 货币: {}",
|
||||
request.getProductId(), currency, e);
|
||||
// 翻译失败使用原始名称,静默处理
|
||||
}
|
||||
}
|
||||
order.setProductName(translatedProductName);
|
||||
@@ -190,11 +191,12 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
// 保存订单
|
||||
int result = customerOrderMapper.insert(order);
|
||||
if (result <= 0) {
|
||||
log.error("创建订单失败,商品ID: {}", request.getProductId());
|
||||
throw new BusinessException(ResultCode.SYSTEM_ERROR, "创建订单失败");
|
||||
}
|
||||
|
||||
log.info("客户订单创建成功,订单ID: {}, 订单号: {}", order.getId(), order.getOrderNo());
|
||||
log.info("订单创建成功,订单号: {}, 商品ID: {}, SKU ID: {}, 数量: {}, 库存扣减: {}",
|
||||
order.getOrderNo(), request.getProductId(), request.getSkuId(),
|
||||
request.getQuantity(), request.getQuantity());
|
||||
|
||||
// 转换为响应DTO
|
||||
CustomerOrderResponseDTO response = new CustomerOrderResponseDTO();
|
||||
@@ -210,13 +212,11 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
|
||||
@Override
|
||||
public CustomerOrderResponseDTO getOrderByOrderNo(String orderNo) {
|
||||
log.debug("查询订单,订单号: {}", orderNo);
|
||||
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||
|
||||
if (order == null) {
|
||||
log.warn("订单不存在,订单号: {}", orderNo);
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||
}
|
||||
|
||||
@@ -233,11 +233,9 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
|
||||
@Override
|
||||
public CustomerOrderResponseDTO getOrderById(Long id) {
|
||||
log.debug("查询订单,订单ID: {}", id);
|
||||
CustomerOrder order = customerOrderMapper.selectById(id);
|
||||
|
||||
if (order == null) {
|
||||
log.warn("订单不存在,订单ID: {}", id);
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||
}
|
||||
|
||||
@@ -254,14 +252,11 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
|
||||
@Override
|
||||
public void updatePaymentStatus(String orderNo, String paymentStatus, Long paymentOrderId) {
|
||||
log.info("更新订单支付状态,订单号: {}, 支付状态: {}, 支付订单ID: {}",
|
||||
orderNo, paymentStatus, paymentOrderId);
|
||||
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||
|
||||
if (order == null) {
|
||||
log.warn("订单不存在,订单号: {}", orderNo);
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||
}
|
||||
|
||||
@@ -273,10 +268,10 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
// 如果支付成功,更新订单状态为已支付
|
||||
if ("PAID".equals(paymentStatus)) {
|
||||
order.setStatus("PAID");
|
||||
log.info("订单支付成功,订单号: {}", orderNo);
|
||||
}
|
||||
|
||||
customerOrderMapper.updateById(order);
|
||||
log.info("订单支付状态更新成功,订单号: {}", orderNo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -285,16 +280,12 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
String paymentCurrency,
|
||||
BigDecimal paymentAmount,
|
||||
BigDecimal exchangeRate) {
|
||||
log.info("更新订单货币转换信息,订单号: {}, 原始: {} {}, 支付: {} {} (汇率: {})",
|
||||
orderNo, originalAmount, originalCurrency, paymentAmount, paymentCurrency, exchangeRate);
|
||||
|
||||
CustomerOrder order = customerOrderMapper.selectOne(
|
||||
new LambdaQueryWrapper<CustomerOrder>()
|
||||
.eq(CustomerOrder::getOrderNo, orderNo)
|
||||
);
|
||||
|
||||
if (order == null) {
|
||||
log.warn("订单不存在,订单号: {}", orderNo);
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||
}
|
||||
|
||||
@@ -307,30 +298,24 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
order.setRateLockedAt(java.time.LocalDateTime.now());
|
||||
|
||||
customerOrderMapper.updateById(order);
|
||||
log.info("订单货币转换信息更新成功,订单号: {}", orderNo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrderStatus(String orderNo, String status) {
|
||||
log.info("更新订单状态,订单号: {}, 状态: {}", orderNo, status);
|
||||
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||
|
||||
if (order == null) {
|
||||
log.warn("订单不存在,订单号: {}", orderNo);
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||
}
|
||||
|
||||
order.setStatus(status);
|
||||
customerOrderMapper.updateById(order);
|
||||
log.info("订单状态更新成功,订单号: {}", orderNo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<OrderListResponseDTO> queryOrders(OrderQueryRequestDTO query) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 处理分页参数
|
||||
int pageNum = query.getPageNum() != null && query.getPageNum() > 0 ? query.getPageNum() : 1;
|
||||
int pageSize = query.getPageSize() != null && query.getPageSize() > 0 ? query.getPageSize() : 10;
|
||||
@@ -338,11 +323,8 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
// 限制每页最大数量
|
||||
if (pageSize > 100) {
|
||||
pageSize = 100;
|
||||
log.warn("每页大小超过限制,已调整为100,原始值: {}", query.getPageSize());
|
||||
}
|
||||
|
||||
log.info("查询订单列表,查询条件: {}, 页码: {}, 每页大小: {}", query, pageNum, pageSize);
|
||||
|
||||
// 1. 构建客户订单查询条件
|
||||
LambdaQueryWrapper<CustomerOrder> orderWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
@@ -461,10 +443,8 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
IPage<CustomerOrder> orderPage = customerOrderMapper.selectPage(page, orderWrapper);
|
||||
|
||||
java.util.List<CustomerOrder> orders = orderPage.getRecords();
|
||||
log.debug("查询到订单数量: {}/{}, 总记录数: {}", orders.size(), pageSize, orderPage.getTotal());
|
||||
|
||||
if (orders.isEmpty()) {
|
||||
log.info("订单列表为空,耗时: {}ms", System.currentTimeMillis() - startTime);
|
||||
return new PageResult<>((long) pageNum, (long) pageSize, orderPage.getTotal(), new java.util.ArrayList<>());
|
||||
}
|
||||
|
||||
@@ -486,8 +466,6 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
order -> order,
|
||||
(existing, replacement) -> existing // 如果有重复,保留第一个
|
||||
));
|
||||
|
||||
log.debug("批量查询到PayPal订单数量: {}", paypalOrderMap.size());
|
||||
}
|
||||
|
||||
// 3. 组装响应数据
|
||||
@@ -512,10 +490,6 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
result.add(dto);
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("查询订单列表完成,查询条件: {}, 结果数量: {}/{}, 总记录数: {}, 耗时: {}ms",
|
||||
query, result.size(), pageSize, orderPage.getTotal(), endTime - startTime);
|
||||
|
||||
// 构建分页结果
|
||||
return new PageResult<>(
|
||||
orderPage.getCurrent(),
|
||||
@@ -525,5 +499,47 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelOrder(String orderNo) {
|
||||
// 查询订单
|
||||
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||
|
||||
if (order == null) {
|
||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||
}
|
||||
|
||||
// 检查订单状态,只有未完成或未取消的订单才能取消
|
||||
String currentStatus = order.getStatus();
|
||||
if ("CANCELLED".equals(currentStatus)) {
|
||||
return; // 已取消,无需重复操作
|
||||
}
|
||||
|
||||
if ("COMPLETED".equals(currentStatus)) {
|
||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "订单已完成,无法取消");
|
||||
}
|
||||
|
||||
// 恢复库存(只有在订单状态为PENDING或PAID时才恢复库存)
|
||||
if (("PENDING".equals(currentStatus) || "PAID".equals(currentStatus))
|
||||
&& order.getSkuId() != null && order.getQuantity() != null) {
|
||||
try {
|
||||
productSkuMapper.restoreStock(order.getSkuId(), order.getQuantity());
|
||||
} catch (Exception e) {
|
||||
// 库存恢复失败不影响订单取消,继续执行
|
||||
}
|
||||
}
|
||||
|
||||
// 更新订单状态为已取消
|
||||
order.setStatus("CANCELLED");
|
||||
if ("UNPAID".equals(order.getPaymentStatus())) {
|
||||
order.setPaymentStatus("CANCELLED");
|
||||
}
|
||||
customerOrderMapper.updateById(order);
|
||||
|
||||
log.info("订单取消成功,订单号: {}, 原状态: {}, 库存已恢复", orderNo, currentStatus);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -76,26 +76,17 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
*/
|
||||
@Async("paypalWebhookExecutor")
|
||||
public void processWebhookEventAsync(PayPalWebhookEventDTO event) {
|
||||
log.info("开始异步处理PayPal Webhook事件,事件ID: {}, 事件类型: {}", event.getId(), event.getEventType());
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
processWebhookEvent(event);
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
log.info("✅ Webhook事件异步处理完成,事件ID: {}, 事件类型: {}, 处理时间: {}ms",
|
||||
event.getId(), event.getEventType(), elapsedTime);
|
||||
} catch (Exception e) {
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
log.error("❌ Webhook事件异步处理异常,事件ID: {}, 事件类型: {}, 处理时间: {}ms",
|
||||
event.getId(), event.getEventType(), elapsedTime, e);
|
||||
log.error("Webhook事件处理异常,事件ID: {}, 事件类型: {}",
|
||||
event.getId(), event.getEventType(), e);
|
||||
// 异步处理中的异常不会影响HTTP响应,已在上层捕获
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processWebhookEvent(PayPalWebhookEventDTO event) {
|
||||
log.info("处理PayPal Webhook事件,事件ID: {}, 事件类型: {}", event.getId(), event.getEventType());
|
||||
|
||||
try {
|
||||
String eventType = event.getEventType();
|
||||
|
||||
@@ -119,13 +110,10 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
Map<String, Object> relatedIds = (Map<String, Object>) supplementaryData.get("related_ids");
|
||||
if (relatedIds != null) {
|
||||
paypalOrderId = (String) relatedIds.get("order_id");
|
||||
log.debug("从supplementary_data提取PayPal订单ID: {}", paypalOrderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("提取PayPal订单ID: {}, 事件类型: {}", paypalOrderId, eventType);
|
||||
|
||||
// 尝试获取商户订单号
|
||||
if (paypalOrderId != null) {
|
||||
try {
|
||||
@@ -133,10 +121,9 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
if (paypalOrder != null && paypalOrder.getPurchaseUnits() != null
|
||||
&& !paypalOrder.getPurchaseUnits().isEmpty()) {
|
||||
merchantOrderNo = paypalOrder.getPurchaseUnits().get(0).getReferenceId();
|
||||
log.debug("提取商户订单号: {}", merchantOrderNo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("无法获取PayPal订单详情,PayPal订单ID: {}", paypalOrderId, e);
|
||||
// 静默处理
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,23 +152,15 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
}
|
||||
|
||||
// 重要:更新PayPal支付订单信息(无论事件类型,只要获取到订单ID就更新)
|
||||
// 这样可以确保所有字段都被正确填充
|
||||
if (paypalOrderId != null) {
|
||||
try {
|
||||
log.info("Webhook事件触发订单信息更新,PayPal订单ID: {}, 事件类型: {}", paypalOrderId, eventType);
|
||||
var paypalOrder = payPalService.getOrder(paypalOrderId);
|
||||
if (paypalOrder != null) {
|
||||
payPalPaymentOrderService.updateOrderFromPayPal(paypalOrderId, paypalOrder);
|
||||
log.info("Webhook事件订单信息更新成功,PayPal订单ID: {}, 状态: {}, 事件类型: {}",
|
||||
paypalOrderId, paypalOrder.getStatus(), eventType);
|
||||
} else {
|
||||
log.warn("查询PayPal订单详情返回null,PayPal订单ID: {}", paypalOrderId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("更新PayPal支付订单信息失败,PayPal订单ID: {}, 事件类型: {}", paypalOrderId, eventType, e);
|
||||
// 静默处理,不影响主流程
|
||||
}
|
||||
} else {
|
||||
log.warn("无法提取PayPal订单ID,无法更新订单信息,事件类型: {}, 事件ID: {}", eventType, event.getId());
|
||||
}
|
||||
|
||||
// 根据事件类型处理不同的业务逻辑
|
||||
@@ -211,10 +190,10 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
handleOrderDeclined(event);
|
||||
break;
|
||||
default:
|
||||
log.info("未处理的事件类型: {}(已记录到数据库,但未执行特定业务逻辑)", eventType);
|
||||
// 未处理的事件类型,已记录到数据库
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理Webhook事件异常,事件ID: {}", event.getId(), e);
|
||||
log.error("处理Webhook事件异常,事件ID: {}, 事件类型: {}", event.getId(), event.getEventType(), e);
|
||||
throw new RuntimeException("处理Webhook事件失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
@@ -319,6 +298,9 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
try {
|
||||
customerOrderService.updatePaymentStatus(referenceId, "PAID", null);
|
||||
log.info("ERP订单支付状态已更新,订单号: {}", referenceId);
|
||||
// 注意:库存已在创建订单时扣减,支付成功时不需要再次扣减
|
||||
// 这里只记录日志,确认库存扣减已完成
|
||||
log.info("订单支付成功,库存扣减已确认,订单号: {}", referenceId);
|
||||
} catch (Exception e) {
|
||||
log.error("更新ERP订单状态失败,订单号: {}", referenceId, e);
|
||||
}
|
||||
@@ -461,8 +443,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
* 处理订单取消事件
|
||||
*/
|
||||
private void handleOrderCancelled(PayPalWebhookEventDTO event) {
|
||||
log.info("处理订单取消事件,事件ID: {}", event.getId());
|
||||
|
||||
try {
|
||||
Map<String, Object> resource = event.getResource();
|
||||
if (resource == null) {
|
||||
@@ -470,9 +450,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
}
|
||||
|
||||
String orderId = (String) resource.get("id");
|
||||
String status = (String) resource.get("status");
|
||||
|
||||
log.info("订单已取消/作废,Order ID: {}, 状态: {}", orderId, status);
|
||||
|
||||
// 更新PayPal支付订单信息
|
||||
if (orderId != null) {
|
||||
@@ -480,15 +457,29 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
var paypalOrder = payPalService.getOrder(orderId);
|
||||
if (paypalOrder != null) {
|
||||
payPalPaymentOrderService.updateOrderFromPayPal(orderId, paypalOrder);
|
||||
log.info("PayPal支付订单信息已更新(订单取消/作废后),PayPal订单ID: {}, 状态: {}",
|
||||
orderId, paypalOrder.getStatus());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("更新PayPal支付订单信息失败,PayPal订单ID: {}", orderId, e);
|
||||
// 静默处理
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 根据业务需求处理订单取消逻辑(如更新ERP订单状态为已取消)
|
||||
// 处理订单取消逻辑:更新ERP订单状态为已取消,并恢复库存
|
||||
if (orderId != null) {
|
||||
try {
|
||||
var paypalOrder = payPalService.getOrder(orderId);
|
||||
if (paypalOrder != null && paypalOrder.getPurchaseUnits() != null
|
||||
&& !paypalOrder.getPurchaseUnits().isEmpty()) {
|
||||
String merchantOrderNo = paypalOrder.getPurchaseUnits().get(0).getReferenceId();
|
||||
|
||||
if (merchantOrderNo != null) {
|
||||
customerOrderService.cancelOrder(merchantOrderNo);
|
||||
log.info("订单已取消,订单号: {}", merchantOrderNo);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理订单取消失败,PayPal订单ID: {}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理订单取消事件异常,事件ID: {}", event.getId(), e);
|
||||
@@ -500,8 +491,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
* 当用户拒绝支付或支付失败时触发
|
||||
*/
|
||||
private void handleOrderDeclined(PayPalWebhookEventDTO event) {
|
||||
log.info("处理订单拒绝事件,事件ID: {}", event.getId());
|
||||
|
||||
try {
|
||||
Map<String, Object> resource = event.getResource();
|
||||
if (resource == null) {
|
||||
@@ -509,9 +498,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
}
|
||||
|
||||
String orderId = (String) resource.get("id");
|
||||
String status = (String) resource.get("status");
|
||||
|
||||
log.info("订单已被拒绝,Order ID: {}, 状态: {}", orderId, status);
|
||||
|
||||
// 更新PayPal支付订单信息
|
||||
if (orderId != null) {
|
||||
@@ -519,15 +505,29 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
||||
var paypalOrder = payPalService.getOrder(orderId);
|
||||
if (paypalOrder != null) {
|
||||
payPalPaymentOrderService.updateOrderFromPayPal(orderId, paypalOrder);
|
||||
log.info("PayPal支付订单信息已更新(订单拒绝后),PayPal订单ID: {}, 状态: {}",
|
||||
orderId, paypalOrder.getStatus());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("更新PayPal支付订单信息失败,PayPal订单ID: {}", orderId, e);
|
||||
// 静默处理
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 根据业务需求处理订单拒绝逻辑(如更新ERP订单状态为支付失败)
|
||||
// 处理订单拒绝逻辑:更新ERP订单状态为支付失败,并恢复库存
|
||||
if (orderId != null) {
|
||||
try {
|
||||
var paypalOrder = payPalService.getOrder(orderId);
|
||||
if (paypalOrder != null && paypalOrder.getPurchaseUnits() != null
|
||||
&& !paypalOrder.getPurchaseUnits().isEmpty()) {
|
||||
String merchantOrderNo = paypalOrder.getPurchaseUnits().get(0).getReferenceId();
|
||||
|
||||
if (merchantOrderNo != null) {
|
||||
customerOrderService.cancelOrder(merchantOrderNo);
|
||||
log.info("订单已拒绝,订单号: {}", merchantOrderNo);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理订单拒绝失败,PayPal订单ID: {}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理订单拒绝事件异常,事件ID: {}", event.getId(), e);
|
||||
|
||||
@@ -128,9 +128,6 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
// 创建SKU(优化:并行翻译 + 批量插入)
|
||||
if (request.getSkus() != null && !request.getSkus().isEmpty()) {
|
||||
long skuStartTime = System.currentTimeMillis();
|
||||
log.debug("开始创建SKU,数量: {}", request.getSkus().size());
|
||||
|
||||
// 第一步:并行翻译所有SKU名称
|
||||
List<CompletableFuture<SkuTranslationResult>> translationFutures = new ArrayList<>();
|
||||
for (CreateProductRequestDTO.CreateProductSkuDTO skuDTO : request.getSkus()) {
|
||||
@@ -145,12 +142,9 @@ public class ProductServiceImpl implements ProductService {
|
||||
translatedSkuName = baiduTranslatorUtils.getTransResult(originalSkuName, targetLanguage);
|
||||
if (translatedSkuName == null || translatedSkuName.equals(originalSkuName)) {
|
||||
translatedSkuName = originalSkuName; // 翻译失败或无需翻译,使用原文
|
||||
} else {
|
||||
log.debug("SKU名称翻译: {} -> {} (货币: {}, 语言: {})",
|
||||
originalSkuName, translatedSkuName, currency, targetLanguage);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("翻译SKU名称失败,使用原文,SKU: {}, 货币: {}", originalSkuName, currency, e);
|
||||
// 翻译失败使用原文,静默处理
|
||||
translatedSkuName = originalSkuName;
|
||||
}
|
||||
}
|
||||
@@ -166,7 +160,6 @@ public class ProductServiceImpl implements ProductService {
|
||||
CompletableFuture.allOf(translationFutures.toArray(new CompletableFuture[0]))
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
log.error("等待SKU翻译完成超时或失败", e);
|
||||
// 继续执行,使用原文
|
||||
}
|
||||
|
||||
@@ -195,7 +188,6 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
skuList.add(sku);
|
||||
} catch (Exception e) {
|
||||
log.warn("获取SKU翻译结果失败,使用原文,SKU: {}", skuDTO.getSku(), e);
|
||||
// 使用原始SKU信息
|
||||
MtProductSku sku = new MtProductSku();
|
||||
sku.setProductId(product.getId());
|
||||
@@ -222,9 +214,6 @@ public class ProductServiceImpl implements ProductService {
|
||||
throw new BusinessException(ResultCode.SYSTEM_ERROR,
|
||||
String.format("批量创建SKU失败,期望插入%d个,实际插入%d个", skuList.size(), insertCount));
|
||||
}
|
||||
long skuEndTime = System.currentTimeMillis();
|
||||
log.info("SKU批量创建成功,商品ID: {}, SKU数量: {}, 耗时: {}ms",
|
||||
product.getId(), skuList.size(), skuEndTime - skuStartTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ server:
|
||||
app:
|
||||
# 前端访问地址(用于生成商品详情页URL等)
|
||||
frontend:
|
||||
url: http://localhost:3000
|
||||
url: http://175.178.252.59:3000
|
||||
|
||||
# 阿里云OSS相关配置(所有环境通用)
|
||||
aliyun:
|
||||
@@ -111,9 +111,9 @@ paypal:
|
||||
mode: sandbox
|
||||
# 是否启用PayPal支付
|
||||
enabled: true
|
||||
# Webhook URL(内网穿透公网地址 + 回调接口路径)
|
||||
# Webhook URL(服务器公网地址)
|
||||
# 注意:需要在PayPal控制台配置此URL
|
||||
webhook-url: https://2646b437.r33.cpolar.top/api/paypal/webhook
|
||||
webhook-url: http://175.178.252.59:8082/api/paypal/webhook
|
||||
# Webhook ID(从PayPal控制台获取,用于验证Webhook签名)
|
||||
webhook-id: 0SX6117212808615P
|
||||
|
||||
|
||||
@@ -60,6 +60,22 @@ spring:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
|
||||
# 服务器配置
|
||||
server:
|
||||
port: ${server.port:8082}
|
||||
servlet:
|
||||
context-path: /
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
max-request-size: 50MB
|
||||
file-size-threshold: 2MB
|
||||
|
||||
# 应用配置
|
||||
app:
|
||||
# 前端访问地址
|
||||
frontend:
|
||||
url: ${app.frontend.url:http://175.178.252.59:3000}
|
||||
|
||||
# PingPong支付配置(生产环境)
|
||||
pingpong:
|
||||
client-id: ${pingpong.client-id}
|
||||
@@ -71,13 +87,20 @@ pingpong:
|
||||
enabled: false
|
||||
|
||||
# PayPal支付配置(生产环境)
|
||||
# 注意:当前为测试环境,使用沙箱凭证
|
||||
# 正式环境需要替换为生产环境的Client ID和Secret
|
||||
paypal:
|
||||
# PayPal Client ID(API密钥)- 从环境变量或配置中心获取
|
||||
client-id: AdGYUZpvLuHR30dybOApvM-RNB1pVKtd74SVfh-6TK52xV-1JEBddHVMCWuDdyyHri4DXd4kABBi7Icb
|
||||
# PayPal Client Secret(密钥)- 从环境变量或配置中心获取
|
||||
client-secret: ENblspyRmwsOU_PWFurlhEYUF5Da6aYKl0pjK4ehm7p3R5aSqvbpaF_YsIIs8v0ty1c9WJu15XP-Fe_1
|
||||
# 环境模式:sandbox(沙箱)或 production(生产)
|
||||
# 当前为测试环境,使用sandbox
|
||||
mode: sandbox
|
||||
# 是否启用PayPal支付
|
||||
enabled: true
|
||||
# Webhook URL(部署时请修改为服务器的公网地址)
|
||||
webhook-url: ${paypal.webhook-url:https://your-domain.com/api/paypal/webhook}
|
||||
# Webhook ID(从PayPal控制台获取)
|
||||
webhook-id: ${paypal.webhook-id:}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user