feat(order): 添加订单货币转换功能

- 在CustomerOrder实体中添加原始货币、支付货币、汇率等相关字段
- 实现货币转换计算接口,支持自动转换不支持的货币到USD
- 添加货币转换信息更新服务方法
- 在订单创建时初始化货币转换相关字段
- 扩展订单响应DTO包含完整的货币转换信息
- 实现汇率锁定和转换记录功能
This commit is contained in:
2025-12-23 18:03:15 +08:00
parent ca5e88cdf1
commit 48eece45e5
7 changed files with 1873 additions and 2 deletions

View File

@@ -1,9 +1,12 @@
package com.mtkj.mtpay.controller;
import com.mtkj.mtpay.common.Result;
import com.mtkj.mtpay.dto.request.CalculateCurrencyConversionRequestDTO;
import com.mtkj.mtpay.dto.request.CreateCustomerOrderRequestDTO;
import com.mtkj.mtpay.dto.response.CurrencyConversionDTO;
import com.mtkj.mtpay.dto.response.CustomerOrderResponseDTO;
import com.mtkj.mtpay.service.CustomerOrderService;
import com.mtkj.mtpay.service.ExchangeRateService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -19,6 +22,7 @@ import org.springframework.web.bind.annotation.*;
public class CustomerOrderController {
private final CustomerOrderService customerOrderService;
private final ExchangeRateService exchangeRateService;
/**
* 创建客户订单
@@ -30,8 +34,84 @@ public class CustomerOrderController {
return Result.success("订单创建成功", order);
}
/**
* 计算并更新订单的货币转换信息
* 在订单确认页面加载时调用,提前计算货币转换信息
* 注意:这个路由必须放在 /{orderNo} 之前,避免路由冲突
*/
@PostMapping("/calculate-currency-conversion")
public Result<CurrencyConversionDTO> calculateCurrencyConversion(
@Valid @RequestBody CalculateCurrencyConversionRequestDTO request) {
log.info("计算订单货币转换信息,订单号:{},原始货币:{},原始金额:{}",
request.getOrderNo(), request.getOriginalCurrency(), request.getOriginalAmount());
// 检查货币是否需要转换
java.util.Set<String> supportedCurrencies = java.util.Set.of(
"USD", "EUR", "GBP", "AUD", "CAD", "JPY", "CNY", "HKD", "SGD", "NZD",
"CHF", "SEK", "NOK", "DKK", "PLN", "MXN", "BRL", "INR", "KRW", "THB"
);
String originalCurrency = request.getOriginalCurrency();
String paymentCurrency = originalCurrency;
java.math.BigDecimal paymentAmount = request.getOriginalAmount();
java.math.BigDecimal exchangeRate = java.math.BigDecimal.ONE;
boolean conversionRequired = false;
// 如果货币不支持转换为USD
if (!supportedCurrencies.contains(originalCurrency)) {
conversionRequired = true;
paymentCurrency = "USD";
// 计算汇率和转换后的金额
Double rate = exchangeRateService.getExchangeRate(originalCurrency, paymentCurrency);
Double convertedAmount = exchangeRateService.convertAmount(
request.getOriginalAmount().doubleValue(),
originalCurrency,
paymentCurrency
);
exchangeRate = java.math.BigDecimal.valueOf(rate);
paymentAmount = java.math.BigDecimal.valueOf(convertedAmount);
log.info("货币转换:{} {} -> {} {} (汇率: {})",
request.getOriginalAmount(), originalCurrency,
paymentAmount, paymentCurrency, exchangeRate);
}
// 构建货币转换DTO
CurrencyConversionDTO conversionDTO = CurrencyConversionDTO.builder()
.originalCurrency(originalCurrency)
.originalAmount(request.getOriginalAmount())
.paymentCurrency(paymentCurrency)
.paymentAmount(paymentAmount)
.exchangeRate(exchangeRate)
.rateLockedAt(java.time.LocalDateTime.now())
.conversionRequired(conversionRequired)
.rateDescription(buildRateDescription(originalCurrency, paymentCurrency, exchangeRate.doubleValue()))
.build();
// 更新订单的货币转换信息
try {
customerOrderService.updateCurrencyConversion(
request.getOrderNo(),
originalCurrency,
request.getOriginalAmount(),
paymentCurrency,
paymentAmount,
exchangeRate
);
log.info("订单货币转换信息已更新,订单号:{}", request.getOrderNo());
} catch (Exception e) {
log.warn("更新订单货币转换信息失败,订单号:{}", request.getOrderNo(), e);
// 不抛出异常,仍然返回计算结果
}
return Result.success("货币转换信息计算成功", conversionDTO);
}
/**
* 根据订单号获取订单详情
* 注意:这个路由必须放在 /calculate-currency-conversion 之后,避免路由冲突
*/
@GetMapping("/{orderNo}")
public Result<CustomerOrderResponseDTO> getOrderByOrderNo(@PathVariable String orderNo) {
@@ -49,5 +129,15 @@ public class CustomerOrderController {
CustomerOrderResponseDTO order = customerOrderService.getOrderById(id);
return Result.success(order);
}
/**
* 构建汇率说明文本
*/
private String buildRateDescription(String fromCurrency, String toCurrency, Double rate) {
if (fromCurrency.equals(toCurrency)) {
return "无需货币转换";
}
return String.format("1 %s = %.6f %s", fromCurrency, rate, toCurrency);
}
}

View File

@@ -22,6 +22,15 @@ public class CustomerOrderResponseDTO implements Serializable {
private BigDecimal unitPrice;
private BigDecimal totalAmount;
private String currency;
// 货币转换信息
private String originalCurrency; // 原始货币代码
private BigDecimal originalAmount; // 原始订单金额
private String paymentCurrency; // 实际支付货币代码
private BigDecimal paymentAmount; // 实际支付金额
private BigDecimal exchangeRate; // 使用的汇率
private LocalDateTime rateLockedAt; // 汇率锁定时间
private String status;
private String customerName;
private String customerPhone;

View File

@@ -68,11 +68,47 @@ public class CustomerOrder {
private BigDecimal totalAmount;
/**
* 货币代码
* 货币代码(保留字段,兼容旧数据)
*/
@TableField(value = "currency", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
private String currency;
/**
* 原始货币代码(客户选择的货币)
*/
@TableField(value = "original_currency", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
private String originalCurrency;
/**
* 原始订单金额(原始货币)
*/
@TableField(value = "original_amount", jdbcType = org.apache.ibatis.type.JdbcType.DECIMAL)
private BigDecimal originalAmount;
/**
* 实际支付货币代码PayPal支持的货币如USD
*/
@TableField(value = "payment_currency", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
private String paymentCurrency;
/**
* 实际支付金额(支付货币)
*/
@TableField(value = "payment_amount", jdbcType = org.apache.ibatis.type.JdbcType.DECIMAL)
private BigDecimal paymentAmount;
/**
* 使用的汇率(原始货币 -> 支付货币)
*/
@TableField(value = "exchange_rate", jdbcType = org.apache.ibatis.type.JdbcType.DECIMAL)
private BigDecimal exchangeRate;
/**
* 汇率锁定时间
*/
@TableField(value = "rate_locked_at", jdbcType = org.apache.ibatis.type.JdbcType.TIMESTAMP)
private LocalDateTime rateLockedAt;
/**
* 订单状态PENDING-待支付PAID-已支付SHIPPED-已发货COMPLETED-已完成CANCELLED-已取消
*/

View File

@@ -43,5 +43,20 @@ public interface CustomerOrderService {
* @param status 订单状态
*/
void updateOrderStatus(String orderNo, String status);
/**
* 更新订单货币转换信息
* @param orderNo 订单号
* @param originalCurrency 原始货币代码
* @param originalAmount 原始金额
* @param paymentCurrency 支付货币代码
* @param paymentAmount 支付金额
* @param exchangeRate 汇率
*/
void updateCurrencyConversion(String orderNo, String originalCurrency,
java.math.BigDecimal originalAmount,
String paymentCurrency,
java.math.BigDecimal paymentAmount,
java.math.BigDecimal exchangeRate);
}

View File

@@ -70,8 +70,18 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
order.setSkuName(sku.getSku());
order.setQuantity(request.getQuantity());
order.setUnitPrice(sku.getPrice());
order.setTotalAmount(sku.getPrice().multiply(new BigDecimal(request.getQuantity())));
BigDecimal totalAmount = sku.getPrice().multiply(new BigDecimal(request.getQuantity()));
order.setTotalAmount(totalAmount);
order.setCurrency(sku.getCurrency());
// 设置原始货币信息(创建订单时的货币)
order.setOriginalCurrency(sku.getCurrency());
order.setOriginalAmount(totalAmount);
// 支付货币和金额将在创建PayPal订单时设置
order.setPaymentCurrency(sku.getCurrency()); // 默认与原始货币相同
order.setPaymentAmount(totalAmount); // 默认与原始金额相同
order.setExchangeRate(BigDecimal.ONE); // 默认汇率为1
order.setStatus("PENDING");
order.setPaymentStatus("UNPAID");
@@ -165,6 +175,37 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
log.info("订单支付状态更新成功,订单号: {}", orderNo);
}
@Override
public void updateCurrencyConversion(String orderNo, String originalCurrency,
BigDecimal originalAmount,
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, "订单不存在");
}
// 更新货币转换信息
order.setOriginalCurrency(originalCurrency);
order.setOriginalAmount(originalAmount);
order.setPaymentCurrency(paymentCurrency);
order.setPaymentAmount(paymentAmount);
order.setExchangeRate(exchangeRate);
order.setRateLockedAt(java.time.LocalDateTime.now());
customerOrderMapper.updateById(order);
log.info("订单货币转换信息更新成功,订单号: {}", orderNo);
}
@Override
public void updateOrderStatus(String orderNo, String status) {
log.info("更新订单状态,订单号: {}, 状态: {}", orderNo, status);