feat(printer): 扩展虚拟订单功能支持实际支付模式

- 修改CreateVirtualOrderRequest添加needActualPayment字段
- 更新SourceController接口方法签名以传递实际支付参数
- 在PrinterServiceImpl中实现两种订单模式:0元立即购买和待支付订单
- 添加价格计算逻辑,支持通过价格计算服务获取真实价格
- 实现微信Native支付集成,为待支付订单生成支付二维码
- 添加Redis临时存储机制,用于支付完成后恢复needEnhance配置
- 更新createVirtualOrder方法重载,支持完整的参数组合
- 添加详细的日志记录以便跟踪订单创建和支付状态变化
This commit is contained in:
2026-02-11 20:12:42 +08:00
parent 350df0fc28
commit a4496db344
4 changed files with 135 additions and 20 deletions

View File

@@ -53,8 +53,9 @@ public class SourceController {
}
/**
* 创建虚拟用户0元订单
* 创建虚拟用户订单
* 用于后台直接从source创建订单,不需要真实用户
* 支持立即0元购买或创建待支付订单(由needActualPayment控制)
*
* @param request 请求参数
* @return 订单信息
@@ -67,7 +68,8 @@ public class SourceController {
request.getScenicId(),
request.getPrinterId(),
request.getNeedEnhance(),
request.getPrintImgUrl()
request.getPrintImgUrl(),
request.getNeedActualPayment()
);
return ApiResponse.success(result);
} catch (Exception e) {

View File

@@ -31,4 +31,11 @@ public class CreateVirtualOrderRequest {
* 打印图片URL(可选,如果提供则使用此URL进行打印)
*/
private String printImgUrl;
/**
* 是否需要实际支付(可选,默认false)
* false/null: 创建0元虚拟订单,立即完成购买
* true: 创建待支付订单(计算实际价格),由前端处理支付流程
*/
private Boolean needActualPayment;
}

View File

@@ -144,6 +144,19 @@ public interface PrinterService {
*/
Map<String, Object> createVirtualOrder(Long sourceId, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl);
/**
* 创建虚拟用户订单(支持实际支付模式)
*
* @param sourceId source记录ID
* @param scenicId 景区ID
* @param printerId 打印机ID(可选)
* @param needEnhance 是否需要图像增强(可选)
* @param printImgUrl 打印图片URL(可选)
* @param needActualPayment 是否需要实际支付(true: 创建待支付订单, false/null: 0元立即购买)
* @return 订单信息
*/
Map<String, Object> createVirtualOrder(Long sourceId, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl, Boolean needActualPayment);
/**
* 根据accessKey获取打印机详情
* @param accessKey 打印机accessKey

View File

@@ -1,5 +1,12 @@
package com.ycwl.basic.service.printer.impl;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import com.ycwl.basic.pay.adapter.IPayAdapter;
import com.ycwl.basic.pay.adapter.WxMpPayAdapter;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.enums.OrderStateEnum;
@@ -162,6 +169,9 @@ public class PrinterServiceImpl implements PrinterService {
@Autowired
@Lazy
private WatermarkEdgeService watermarkEdgeService;
@Autowired
@Lazy
private ScenicService scenicService;
@Override
public List<PrinterResp> listByScenicId(Long scenicId) {
@@ -1126,7 +1136,14 @@ public class PrinterServiceImpl implements PrinterService {
@Override
public void setUserIsBuyItem(Long memberId, Long id, Long orderId) {
setUserIsBuyItem(memberId, id, orderId, null);
// 尝试从 Redis 读取虚拟订单存储的 needEnhance 配置
Boolean needEnhance = null;
String enhanceFlag = redisTemplate.opsForValue().get("virtual_order_enhance:" + orderId);
if (enhanceFlag != null) {
needEnhance = Boolean.parseBoolean(enhanceFlag);
redisTemplate.delete("virtual_order_enhance:" + orderId);
}
setUserIsBuyItem(memberId, id, orderId, needEnhance);
}
@Override
@@ -1680,16 +1697,21 @@ public class PrinterServiceImpl implements PrinterService {
@Override
public Map<String, Object> createVirtualOrder(Long sourceId, Long scenicId, Integer printerId) {
return createVirtualOrder(sourceId, scenicId, printerId, null, null);
return createVirtualOrder(sourceId, scenicId, printerId, null, null, null);
}
@Override
public Map<String, Object> createVirtualOrder(Long sourceId, Long scenicId, Integer printerId, Boolean needEnhance) {
return createVirtualOrder(sourceId, scenicId, printerId, needEnhance, null);
return createVirtualOrder(sourceId, scenicId, printerId, needEnhance, null, null);
}
@Override
public Map<String, Object> createVirtualOrder(Long sourceId, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl) {
return createVirtualOrder(sourceId, scenicId, printerId, needEnhance, printImgUrl, null);
}
@Override
public Map<String, Object> createVirtualOrder(Long sourceId, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl, Boolean needActualPayment) {
// 1. 查询source记录
SourceEntity source = sourceMapper.getEntity(sourceId);
FaceSampleEntity faceSample = faceSampleMapper.getEntity(source.getFaceSampleId());
@@ -1752,7 +1774,7 @@ public class PrinterServiceImpl implements PrinterService {
throw new BaseException("打印机不属于该景区");
}
// 6. 创建0元订单
// 6. 创建订单
OrderEntity order = new OrderEntity();
Long orderId = SnowFlakeUtil.getLongId();
redisTemplate.opsForValue().set("printer_size:" + orderId, printer.getPreferPaper(), 60, TimeUnit.SECONDS);
@@ -1776,13 +1798,52 @@ public class PrinterServiceImpl implements PrinterService {
return orderItem;
}).collect(Collectors.toList());
// 设置价格为0
order.setPrice(BigDecimal.ZERO);
order.setSlashPrice(BigDecimal.ZERO);
order.setPayPrice(BigDecimal.ZERO);
order.setFaceId(faceId);
order.setStatus(OrderStateEnum.PAID.getState());
order.setPayAt(new Date());
boolean actualPayment = Boolean.TRUE.equals(needActualPayment);
if (actualPayment) {
// 需要实际支付:通过价格计算服务获取真实价格
PriceCalculationRequest priceRequest = new PriceCalculationRequest();
priceRequest.setUserId(virtualMemberId);
priceRequest.setScenicId(scenicId);
List<ProductItem> productItems = new ArrayList<>();
ProductItem photoItem = new ProductItem();
photoItem.setProductType(ProductType.PHOTO_PRINT);
photoItem.setProductId(scenicId.toString());
photoItem.setQuantity(1);
photoItem.setPurchaseCount(1);
photoItem.setScenicId(scenicId.toString());
// 通过 source 的 deviceId 设置 attributeKeys
SourceEntity priceSource = sourceRepository.getSource(sourceId);
if (priceSource != null && priceSource.getDeviceId() != null) {
photoItem.setAttributeKeys(List.of(String.valueOf(priceSource.getDeviceId())));
}
productItems.add(photoItem);
priceRequest.setProducts(productItems);
priceRequest.setAutoUseCoupon(false);
priceRequest.setPreviewOnly(false);
PriceCalculationResult priceResult = priceCalculationService.calculatePrice(priceRequest);
order.setPrice(priceResult.getFinalAmount());
order.setSlashPrice(priceResult.getOriginalAmount());
order.setPayPrice(priceResult.getFinalAmount());
order.setStatus(OrderStateEnum.UNPAID.getState());
log.info("创建待支付虚拟订单: orderId={}, price={}", orderId, priceResult.getFinalAmount());
// 将 needEnhance 存入 Redis,支付完成后 setUserIsBuyItem 可读取
if (needEnhance != null) {
redisTemplate.opsForValue().set("virtual_order_enhance:" + orderId, needEnhance.toString(), 24, TimeUnit.HOURS);
}
} else {
// 虚拟0元购买:价格为0,直接标记已支付
order.setPrice(BigDecimal.ZERO);
order.setSlashPrice(BigDecimal.ZERO);
order.setPayPrice(BigDecimal.ZERO);
order.setStatus(OrderStateEnum.PAID.getState());
order.setPayAt(new Date());
}
// 保存订单
orderMapper.add(order);
@@ -1792,19 +1853,51 @@ public class PrinterServiceImpl implements PrinterService {
throw new BaseException("订单添加失败");
}
log.info("创建0元订单成功: orderId={}, virtualMemberId={}, faceId={}", orderId, virtualMemberId, faceId);
log.info("创建虚拟订单成功: orderId={}, virtualMemberId={}, faceId={}, actualPayment={}", orderId, virtualMemberId, faceId, actualPayment);
// 7. 触发购买后逻辑(调用setUserIsBuyItem)
setUserIsBuyItem(virtualMemberId, memberPrintId.longValue(), orderId, needEnhance);
log.info("触发购买后逻辑完成: orderId={}", orderId);
// 8. 返回结果
Map<String, Object> result = new HashMap<>();
result.put("orderId", orderId);
result.put("faceId", faceId);
result.put("virtualMemberId", virtualMemberId);
result.put("memberPrintId", memberPrintId);
result.put("needPay", false);
if (actualPayment) {
if (order.getPayPrice().compareTo(BigDecimal.ZERO) <= 0) {
// 计算后价格为0,直接走免费逻辑
order.setStatus(OrderStateEnum.PAID.getState());
order.setPayAt(new Date());
orderMapper.updateOrder(order);
setUserIsBuyItem(virtualMemberId, memberPrintId.longValue(), orderId, needEnhance);
log.info("待支付订单计算后价格为0,直接完成购买: orderId={}", orderId);
result.put("needPay", false);
} else {
// 通过 Native 支付生成二维码
IPayAdapter payAdapter = scenicService.getScenicPayAdapter(scenicId);
if (payAdapter instanceof WxMpPayAdapter adapter) {
NativePayService nativePayService = new NativePayService.Builder().config(adapter.getConfig()).build();
PrepayRequest prepayRequest = new PrepayRequest();
prepayRequest.setAppid(adapter._config().getAppId());
prepayRequest.setMchid(adapter._config().getMerchantId());
prepayRequest.setDescription("照片打印");
prepayRequest.setOutTradeNo(String.valueOf(orderId));
prepayRequest.setNotifyUrl("https://zhentuai.com/api/mobile/wx/pay/v1/" + scenicId + "/payNotify");
Amount amount = new Amount();
amount.setTotal(order.getPayPrice().multiply(new BigDecimal(100)).intValue());
prepayRequest.setAmount(amount);
PrepayResponse prepayResponse = nativePayService.prepay(prepayRequest);
result.put("payCode", prepayResponse.getCodeUrl());
} else {
throw new BaseException("该景区不支持 Native 支付");
}
result.put("needPay", true);
result.put("price", order.getPayPrice());
}
} else {
// 立即购买模式:触发购买后逻辑
setUserIsBuyItem(virtualMemberId, memberPrintId.longValue(), orderId, needEnhance);
log.info("触发购买后逻辑完成: orderId={}", orderId);
result.put("needPay", false);
}
return result;
}