feat: close overseas wallet and pallet cost flow

This commit is contained in:
2026-09-05 22:26:25 +08:00
parent f62283fb7f
commit 5fbd2bbd04
69 changed files with 1598 additions and 53 deletions
@@ -11,6 +11,8 @@ import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
import java.math.BigDecimal;
import jakarta.validation.constraints.DecimalMin;
public final class RelabelRequests {
private RelabelRequests() {}
@@ -21,6 +23,7 @@ public final class RelabelRequests {
@NotNull LocalDateTime labelDeadline,
@NotBlank @Size(max = 128) String contactName,
@NotBlank @Size(max = 64) String contactPhone,
@NotNull @DecimalMin(value = "0.01") BigDecimal chargeAmount,
@Size(max = 1000) String remark,
@NotBlank @Size(max = 255) String idempotencyKey,
@NotEmpty @Size(max = 500) List<@Valid Item> items
@@ -2,6 +2,7 @@ package com.baiyue.lastleg.relabel.dto;
import java.time.LocalDateTime;
import java.util.List;
import java.math.BigDecimal;
public final class RelabelResponses {
private RelabelResponses() {}
@@ -10,6 +11,8 @@ public final class RelabelResponses {
String customerOrderNo, String overseasWarehouseId, LocalDateTime labelDeadline,
String contactName, String contactPhone, String status, int itemCount,
int totalPackageCount, int notifiedPackageCount, String skuCodes,
BigDecimal chargeAmount, String chargeCurrency, String walletStatus,
String walletErrorMessage,
LocalDateTime createdAt) {}
public record Item(Long id, String skuCode, String productImageFileId, String productImageFileName,
String productImageUrl, String productNameZh, int totalPackageCount,
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
import java.math.BigDecimal;
@Data
@TableName("ll_relabel_application")
@@ -24,6 +25,12 @@ public class RelabelApplication {
private Integer itemCount;
private Integer totalPackageCount;
private Integer notifiedPackageCount;
private BigDecimal chargeAmount;
private String chargeCurrency;
private String walletStatus;
private String walletErrorCode;
private String walletErrorMessage;
private String walletCommandEventId;
private String idempotencyKey;
private String requestHash;
private String createdBy;
@@ -17,6 +17,9 @@ import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
import com.baiyue.lastleg.relabel.mapper.RelabelStatusHistoryMapper;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import com.baiyue.lastleg.wallet.WalletCommandOutboxService;
import com.baiyue.lastleg.wallet.WalletResultHandler;
import com.baiyue.common.event.WalletCommandCompletedV1;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
@@ -36,7 +39,7 @@ import java.util.Set;
import java.util.stream.Collectors;
@Service
public class RelabelApplicationService {
public class RelabelApplicationService implements WalletResultHandler {
public static final String PENDING_PROCESSING = "PENDING_PROCESSING";
private static final Set<String> STATUSES = Set.of(PENDING_PROCESSING, "PROCESSING", "COMPLETED", "REJECTED", "CANCELLED");
@@ -47,6 +50,7 @@ public class RelabelApplicationService {
@Resource private LastlegOrderStatusHistoryMapper orderHistories;
@Resource private LastlegOrderNoGenerator orderNoGenerator;
@Resource private ObjectMapper objectMapper;
@Resource private WalletCommandOutboxService walletCommands;
@Transactional
public RelabelResponses.Detail create(String customerId, String operatorId, RelabelRequests.Create request) {
@@ -109,6 +113,9 @@ public class RelabelApplicationService {
application.setItemCount(request.items().size());
application.setTotalPackageCount(total);
application.setNotifiedPackageCount(notified);
application.setChargeAmount(request.chargeAmount());
application.setChargeCurrency("CNY");
application.setWalletStatus("FREEZE_PENDING");
application.setIdempotencyKey(request.idempotencyKey().trim());
application.setRequestHash(requestHash);
application.setCreatedBy(trim(operatorId));
@@ -116,6 +123,11 @@ public class RelabelApplicationService {
application.setUpdatedAt(now);
applications.insert(application);
String walletEventId = walletCommands.publish("FREEZE", Long.parseLong(customerId), "RELABEL_ORDER",
relabelNo, request.chargeAmount(), "换标订单费用冻结", 1L);
application.setWalletCommandEventId(walletEventId);
applications.updateById(application);
for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now);
insertHistory(customerId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", now);
insertOrderHistory(customerId, order.getId(), null, order.getCurrentStatus(),
@@ -172,11 +184,23 @@ public class RelabelApplicationService {
if (!STATUSES.contains(next)) throw badRequest("Unsupported relabel status");
if (next.equals(application.getStatus())) return detail(customerId, relabelNo);
validateTransition(application.getStatus(), next);
if ("PROCESSING".equals(next) && !"FROZEN".equals(application.getWalletStatus())) {
throw conflict("Relabel funds must be frozen before processing");
}
String before = application.getStatus();
LocalDateTime now = LocalDateTime.now();
application.setStatus(next);
application.setUpdatedAt(now);
applications.updateById(application);
if ("COMPLETED".equals(next)) {
walletCommands.publish("SETTLE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
"换标完成,结算费用", 2L);
application.setWalletStatus("SETTLE_PENDING"); applications.updateById(application);
} else if (Set.of("REJECTED", "CANCELLED").contains(next) && "FROZEN".equals(application.getWalletStatus())) {
walletCommands.publish("RELEASE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
"换标取消,释放冻结", 2L);
application.setWalletStatus("RELEASE_PENDING"); applications.updateById(application);
}
LastlegOrder order = orders.selectById(application.getLastlegOrderId());
if (order == null || !customerId.equals(order.getCustomerId())) throw conflict("Linked tail-leg order is missing");
String orderBefore = order.getCurrentStatus();
@@ -221,7 +245,31 @@ public class RelabelApplicationService {
value.getLabelDeadline(), value.getContactName(), value.getContactPhone(), value.getStatus(),
Objects.requireNonNullElse(value.getItemCount(), detailItems.size()),
Objects.requireNonNullElse(value.getTotalPackageCount(), 0),
Objects.requireNonNullElse(value.getNotifiedPackageCount(), 0), skuCodes, value.getCreatedAt());
Objects.requireNonNullElse(value.getNotifiedPackageCount(), 0), skuCodes,
value.getChargeAmount(), value.getChargeCurrency(), value.getWalletStatus(), value.getWalletErrorMessage(),
value.getCreatedAt());
}
@Override public boolean supports(String businessType) { return "RELABEL_ORDER".equals(businessType); }
@Override
@Transactional
public void handle(WalletCommandCompletedV1 result) {
RelabelApplication value = applications.findNo(String.valueOf(result.customerId()), result.businessNo());
if (value == null) throw notFound("Relabel application not found for wallet result");
if ("SUCCEEDED".equals(result.result())) {
value.setWalletStatus(switch (result.operation()) { case "FREEZE" -> "FROZEN"; case "SETTLE" -> "SETTLED"; case "RELEASE" -> "RELEASED"; default -> result.holdStatus(); });
value.setWalletErrorCode(null); value.setWalletErrorMessage(null);
if ("FREEZE".equals(result.operation()) && Set.of("REJECTED", "CANCELLED").contains(value.getStatus())) {
walletCommands.publish("RELEASE", result.customerId(), "RELABEL_ORDER", value.getRelabelNo(), null,
"换标已取消,冻结成功后立即释放", 2L);
value.setWalletStatus("RELEASE_PENDING");
}
} else {
value.setWalletStatus(result.operation() + "_FAILED"); value.setWalletErrorCode(result.errorCode());
value.setWalletErrorMessage(result.errorMessage());
}
value.setUpdatedAt(LocalDateTime.now()); applications.updateById(value);
}
private RelabelResponses.Item item(RelabelItem value) {