package com.talentcard.server.service;
|
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.util.LinkedHashMap;
|
import java.util.Locale;
|
import java.util.Map;
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.talentcard.server.business.BusinessIdGenerator;
|
import com.talentcard.server.request.BenefitVerificationRequest;
|
import com.talentcard.server.business.merchant.auth.MerchantPrincipal;
|
import com.talentcard.server.common.BusinessException;
|
import com.talentcard.server.common.ErrorCode;
|
import com.talentcard.server.entity.BenefitCinemaCoupon;
|
import com.talentcard.server.entity.BenefitCinemaCouponClaim;
|
import com.talentcard.server.entity.BenefitFreeStayVerificationCode;
|
import com.talentcard.server.entity.BenefitFreeStayVoucher;
|
import com.talentcard.server.entity.BenefitMerchant;
|
import com.talentcard.server.entity.BenefitMerchantProduct;
|
import com.talentcard.server.entity.BenefitOrder;
|
import com.talentcard.server.entity.BenefitOrderDynamicVerificationCode;
|
import com.talentcard.server.entity.BenefitVerifyRecord;
|
import com.talentcard.server.mapper.BenefitCinemaCouponClaimMapper;
|
import com.talentcard.server.mapper.BenefitCinemaCouponMapper;
|
import com.talentcard.server.mapper.BenefitFreeStayVerificationCodeMapper;
|
import com.talentcard.server.mapper.BenefitFreeStayVoucherMapper;
|
import com.talentcard.server.mapper.BenefitMerchantMapper;
|
import com.talentcard.server.mapper.BenefitMerchantProductMapper;
|
import com.talentcard.server.mapper.BenefitOrderDynamicVerificationCodeMapper;
|
import com.talentcard.server.mapper.BenefitOrderMapper;
|
import com.talentcard.server.mapper.BenefitVerifyRecordMapper;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@Service
|
public class BenefitVerificationService {
|
private final BenefitQuotaService quotaService;
|
private final BenefitVerifyRecordMapper verifyRecordMapper;
|
private final BenefitCinemaCouponClaimMapper claimMapper;
|
private final BenefitCinemaCouponMapper couponMapper;
|
private final BenefitOrderDynamicVerificationCodeMapper orderCodeMapper;
|
private final BenefitOrderMapper orderMapper;
|
private final BenefitFreeStayVerificationCodeMapper freeStayCodeMapper;
|
private final BenefitFreeStayVoucherMapper voucherMapper;
|
private final BenefitMerchantMapper merchantMapper;
|
private final BenefitMerchantProductMapper productMapper;
|
|
public BenefitVerificationService(
|
BenefitQuotaService quotaService,
|
BenefitVerifyRecordMapper verifyRecordMapper,
|
BenefitCinemaCouponClaimMapper claimMapper,
|
BenefitCinemaCouponMapper couponMapper,
|
BenefitOrderDynamicVerificationCodeMapper orderCodeMapper,
|
BenefitOrderMapper orderMapper,
|
BenefitFreeStayVerificationCodeMapper freeStayCodeMapper,
|
BenefitFreeStayVoucherMapper voucherMapper,
|
BenefitMerchantMapper merchantMapper,
|
BenefitMerchantProductMapper productMapper
|
) {
|
this.quotaService = quotaService;
|
this.verifyRecordMapper = verifyRecordMapper;
|
this.claimMapper = claimMapper;
|
this.couponMapper = couponMapper;
|
this.orderCodeMapper = orderCodeMapper;
|
this.orderMapper = orderMapper;
|
this.freeStayCodeMapper = freeStayCodeMapper;
|
this.voucherMapper = voucherMapper;
|
this.merchantMapper = merchantMapper;
|
this.productMapper = productMapper;
|
}
|
|
@Transactional
|
public Map<String, Object> verify(MerchantPrincipal principal, BenefitVerificationRequest request, String idempotencyKey) {
|
String key = normalizeIdempotencyKey(idempotencyKey);
|
BenefitVerifyRecord existing = verifiedByIdempotencyKey(principal, key);
|
if (existing != null) {
|
return result(existing, null, null, null, 0);
|
}
|
String benefitType = quotaService.normalizeBenefitType(request.benefitType());
|
String verifyCode = request.verifyCode() == null ? "" : request.verifyCode().trim();
|
if (verifyCode.isBlank()) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "核销码不能为空");
|
}
|
if (BenefitQuotaService.MOVIE_TICKET.equals(benefitType)) {
|
return verifyMovieTicket(principal, verifyCode, request.remark(), key);
|
}
|
return verifyHotel(principal, verifyCode, request.remark(), key);
|
}
|
|
private Map<String, Object> verifyMovieTicket(MerchantPrincipal principal, String verifyCode, String remark, String idempotencyKey) {
|
BenefitCinemaCouponClaim probe = movieClaimByCode(verifyCode, false);
|
if (probe == null) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "电影票核销码不存在");
|
}
|
if (!principal.merchantId().equals(probe.getMerchantId())) {
|
throw new BusinessException(ErrorCode.CONFLICT, "电影票不属于当前商户");
|
}
|
if (!verifyCode.startsWith(BenefitVerificationCodeService.MOVIE_TOKEN_PREFIX)) {
|
throw new BusinessException(ErrorCode.CONFLICT, "电影票核销码必须由二期生成核销码接口生成");
|
}
|
if (movieCodeExpired(probe.getUpdatedAt())) {
|
expireMovieClaim(probe.getId());
|
throw new BusinessException(ErrorCode.CONFLICT, "电影票核销码已过期");
|
}
|
|
quotaService.current(BenefitQuotaService.MOVIE_TICKET, probe.getCardId(), probe.getTalentId(), probe.getMerchantId(), null);
|
BenefitCinemaCouponClaim claim = movieClaimByCode(verifyCode, true);
|
validateMovieClaim(claim);
|
|
int updated = claimMapper.update(null, new UpdateWrapper<BenefitCinemaCouponClaim>()
|
.set("code_status", "USED")
|
.set("status", "USED")
|
.setSql("used_at = now()")
|
.setSql("updated_at = now()")
|
.eq("id", claim.getId())
|
.eq("deleted", 0)
|
.eq("exchange_code", verifyCode)
|
.apply("upper(coalesce(code_status, 'ACTIVE')) = 'ACTIVE'")
|
.apply("updated_at > date_sub(now(), interval 300 second)"));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "电影票核销码已使用或已过期");
|
}
|
BenefitVerifyRecord record = insertRecord(principal, null, claim.getTalentId(), claim.getCardId(), null,
|
BenefitQuotaService.MOVIE_TICKET, 0L, 0L,
|
sourceRemark("cinemaCouponClaimId=" + claim.getId(), remark), idempotencyKey);
|
BenefitQuotaService.QuotaSnapshot quotaAfter = quotaService.current(BenefitQuotaService.MOVIE_TICKET,
|
claim.getCardId(), claim.getTalentId(), claim.getMerchantId(), null);
|
return result(record, "CINEMA_COUPON_CLAIM", claim.getId(), quotaAfter, 1);
|
}
|
|
private Map<String, Object> verifyHotel(MerchantPrincipal principal, String verifyCode, String remark, String idempotencyKey) {
|
HotelOrderCodeContext orderCode = hotelOrderCode(verifyCode, principal, false);
|
if (orderCode != null) {
|
return verifyHotelOrderCode(principal, verifyCode, orderCode, remark, idempotencyKey);
|
}
|
FreeStayCodeContext freeStayCode = freeStayCode(verifyCode, false);
|
if (freeStayCode != null) {
|
return verifyFreeStayCode(principal, verifyCode, freeStayCode, remark, idempotencyKey);
|
}
|
throw new BusinessException(ErrorCode.NOT_FOUND, "酒店核销码不存在");
|
}
|
|
private Map<String, Object> verifyHotelOrderCode(MerchantPrincipal principal, String verifyCode, HotelOrderCodeContext probe,
|
String remark, String idempotencyKey) {
|
if (!belongsToPrincipal(probe.order(), principal)) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店订单不属于当前门店");
|
}
|
BenefitQuotaService.QuotaSnapshot quotaBefore = quotaService.requireAvailable(BenefitQuotaService.HOTEL,
|
probe.order().getCardId(), probe.order().getTalentId(), principal.merchantId(), probe.order().getProductId(), 1);
|
HotelOrderCodeContext context = hotelOrderCode(verifyCode, principal, true);
|
if (context == null || !belongsToPrincipal(context.order(), principal)) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店订单核销码不属于当前门店");
|
}
|
BenefitOrderDynamicVerificationCode code = context.code();
|
BenefitOrder order = context.order();
|
if (intValue(code.getStatus()) != 0 || expiredDateTime(code.getExpiresAt())
|
|| intValue(order.getOrderStatus()) != 1 || intValue(order.getVerifyStatus()) != 0) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店订单当前不可核销");
|
}
|
int codeUpdated = orderCodeMapper.update(null, new UpdateWrapper<BenefitOrderDynamicVerificationCode>()
|
.set("status", 1)
|
.eq("id", code.getId())
|
.eq("status", 0)
|
.gt("expires_at", LocalDateTime.now()));
|
int orderUpdated = orderMapper.update(null, new UpdateWrapper<BenefitOrder>()
|
.set("verify_status", 1)
|
.eq("id", order.getId())
|
.eq("order_status", 1)
|
.eq("verify_status", 0)
|
.eq("deleted", 0));
|
if (codeUpdated != 1 || orderUpdated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店订单已核销或核销码已过期");
|
}
|
BenefitVerifyRecord record = insertRecord(principal, order.getId(), order.getTalentId(), order.getCardId(),
|
order.getBenefitId(), "HOTEL_ORDER", order.getPayAmount(), order.getSubsidyAmount(),
|
sourceRemark("orderNo=" + order.getOrderNo(), remark), idempotencyKey);
|
return result(record, "BENEFIT_ORDER", order.getId(), afterDeduct(quotaBefore, 1), 1);
|
}
|
|
private Map<String, Object> verifyFreeStayCode(MerchantPrincipal principal, String verifyCode, FreeStayCodeContext probe,
|
String remark, String idempotencyKey) {
|
if (!principal.merchantId().equals(probe.voucher().getMerchantId())) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店核销码不属于当前商户");
|
}
|
HotelToken token = parseHotelToken(verifyCode);
|
Long sourceCardId = token.sourceCardId() == null ? probe.voucher().getCardId() : token.sourceCardId();
|
Long productId = token.productId();
|
BenefitQuotaService.QuotaSnapshot quotaBefore = quotaService.requireAvailable(BenefitQuotaService.HOTEL,
|
sourceCardId, null, principal.merchantId(), productId, 1);
|
|
FreeStayCodeContext context = freeStayCode(verifyCode, true);
|
if (context == null || !principal.merchantId().equals(context.voucher().getMerchantId())) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店核销码不属于当前商户");
|
}
|
BenefitFreeStayVerificationCode code = context.code();
|
BenefitFreeStayVoucher voucher = context.voucher();
|
if (intValue(code.getStatus()) != 0 || expiredDateTime(code.getExpiresAt())) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店核销码已过期");
|
}
|
if (intValue(voucher.getStatus()) != 1 || expiredDate(voucher.getValidEnd())
|
|| intValue(voucher.getUsedCount()) >= intValue(voucher.getTotalCount())) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店权益券次数已用完");
|
}
|
if (productId != null) {
|
decrementHotelProductStock(principal.merchantId(), productId);
|
}
|
int codeUpdated = freeStayCodeMapper.update(null, new UpdateWrapper<BenefitFreeStayVerificationCode>()
|
.set("status", 1)
|
.eq("id", code.getId())
|
.eq("status", 0)
|
.gt("expires_at", LocalDateTime.now()));
|
int voucherUpdated = voucherMapper.update(null, new UpdateWrapper<BenefitFreeStayVoucher>()
|
.setSql("used_count = used_count + 1")
|
.eq("id", voucher.getId())
|
.eq("status", 1)
|
.ge("valid_end", LocalDate.now())
|
.apply("used_count < total_count"));
|
if (codeUpdated != 1 || voucherUpdated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店核销码已使用或权益次数已用完");
|
}
|
BenefitVerifyRecord record = insertRecord(principal, null, quotaBefore.talentId(), quotaBefore.cardId(), null,
|
"HOTEL", 0L, 0L,
|
sourceRemark("freeStayVoucherId=" + voucher.getId() + productRemark(productId), remark), idempotencyKey);
|
return result(record, "FREE_STAY_VERIFICATION_CODE", code.getId(), afterDeduct(quotaBefore, 1), 1);
|
}
|
|
private void validateMovieClaim(BenefitCinemaCouponClaim claim) {
|
if (claim == null) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "电影票核销码不存在");
|
}
|
BenefitCinemaCoupon coupon = couponMapper.selectById(claim.getCouponId());
|
if (!"ACTIVE".equals(upper(claim.getCodeStatus())) || movieCodeExpired(claim.getUpdatedAt())) {
|
throw new BusinessException(ErrorCode.CONFLICT, "电影票核销码已过期");
|
}
|
if (coupon == null || expiredDate(claim.getValidEnd()) || intValue(coupon.getStatus()) != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "电影票权益券当前不可用");
|
}
|
}
|
|
private void expireMovieClaim(Long claimId) {
|
BenefitCinemaCouponClaim claim = claimMapper.selectOne(new QueryWrapper<BenefitCinemaCouponClaim>()
|
.eq("id", claimId)
|
.eq("deleted", 0)
|
.last("limit 1 for update"));
|
if (claim == null) {
|
return;
|
}
|
if ("ACTIVE".equals(upper(claim.getCodeStatus()))
|
&& startsWithMoviePrefix(claim.getExchangeCode())
|
&& movieCodeExpired(claim.getUpdatedAt())) {
|
int updated = claimMapper.update(null, new UpdateWrapper<BenefitCinemaCouponClaim>()
|
.set("code_status", "EXPIRED")
|
.set("status", "EXPIRED")
|
.eq("id", claimId)
|
.eq("deleted", 0)
|
.apply("upper(coalesce(code_status, 'ACTIVE')) = 'ACTIVE'"));
|
if (updated == 1) {
|
couponMapper.update(null, new UpdateWrapper<BenefitCinemaCoupon>()
|
.setSql("stock = stock + 1")
|
.eq("id", claim.getCouponId())
|
.eq("deleted", 0));
|
}
|
}
|
}
|
|
private BenefitCinemaCouponClaim movieClaimByCode(String code, boolean lock) {
|
return claimMapper.selectOne(new QueryWrapper<BenefitCinemaCouponClaim>()
|
.eq("deleted", 0)
|
.eq("exchange_code", code)
|
.last("limit 1" + (lock ? " for update" : "")));
|
}
|
|
private HotelOrderCodeContext hotelOrderCode(String token, MerchantPrincipal principal, boolean lock) {
|
BenefitOrderDynamicVerificationCode code = orderCodeMapper.selectOne(new QueryWrapper<BenefitOrderDynamicVerificationCode>()
|
.eq("code_token", token)
|
.last("limit 1" + (lock ? " for update" : "")));
|
if (code == null) {
|
return null;
|
}
|
BenefitOrder order = orderMapper.selectOne(new QueryWrapper<BenefitOrder>()
|
.eq("id", code.getOrderId())
|
.eq("deleted", 0)
|
.last("limit 1" + (lock ? " for update" : "")));
|
if (order == null || !principal.merchantId().equals(order.getMerchantId())) {
|
return null;
|
}
|
BenefitMerchant merchant = merchantMapper.selectById(order.getMerchantId());
|
if (merchant == null || intValue(merchant.getDeleted()) != 0 || !"hotel".equalsIgnoreCase(String.valueOf(merchant.getMerchantType()))) {
|
return null;
|
}
|
return new HotelOrderCodeContext(code, order);
|
}
|
|
private FreeStayCodeContext freeStayCode(String token, boolean lock) {
|
BenefitFreeStayVerificationCode code = freeStayCodeMapper.selectOne(new QueryWrapper<BenefitFreeStayVerificationCode>()
|
.eq("code_token", token)
|
.last("limit 1" + (lock ? " for update" : "")));
|
if (code == null) {
|
return null;
|
}
|
BenefitFreeStayVoucher voucher = voucherMapper.selectOne(new QueryWrapper<BenefitFreeStayVoucher>()
|
.eq("id", code.getVoucherId())
|
.last("limit 1" + (lock ? " for update" : "")));
|
if (voucher == null) {
|
return null;
|
}
|
return new FreeStayCodeContext(code, voucher);
|
}
|
|
private void decrementHotelProductStock(Long merchantId, Long productId) {
|
int updated = productMapper.update(null, new UpdateWrapper<BenefitMerchantProduct>()
|
.setSql("stock = case when stock = -1 then stock else stock - 1 end")
|
.setSql("sales_count = sales_count + 1")
|
.eq("id", productId)
|
.eq("merchant_id", merchantId)
|
.eq("deleted", 0)
|
.eq("status", 1)
|
.and(wrapper -> wrapper.isNull("valid_start_time").or().le("valid_start_time", LocalDateTime.now()))
|
.and(wrapper -> wrapper.isNull("valid_end_time").or().gt("valid_end_time", LocalDateTime.now()))
|
.apply("(stock = -1 or stock > 0)"));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "酒店房型或套餐库存不足");
|
}
|
}
|
|
private BenefitVerifyRecord insertRecord(MerchantPrincipal principal, Long orderId, Long talentId, Long cardId, Long benefitId,
|
String verifyType, Long verifyAmount, Long subsidyAmount, String remark, String idempotencyKey) {
|
BenefitVerifyRecord record = new BenefitVerifyRecord();
|
record.setVerifyNo(BusinessIdGenerator.next("V"));
|
record.setOrderId(orderId);
|
record.setTalentId(talentId);
|
record.setCardId(cardId);
|
record.setBenefitId(benefitId);
|
record.setMerchantId(principal.merchantId());
|
record.setStoreId(principal.storeId());
|
record.setStaffId(principal.operatorId());
|
record.setVerifyType(verifyType);
|
record.setVerifyAmount(zeroIfNull(verifyAmount));
|
record.setSubsidyAmount(zeroIfNull(subsidyAmount));
|
record.setRemark(trimToLength(remark, 512));
|
record.setStatus(1);
|
record.setIdempotencyKey(idempotencyKey);
|
verifyRecordMapper.insert(record);
|
return record(record.getId());
|
}
|
|
private BenefitVerifyRecord verifiedByIdempotencyKey(MerchantPrincipal principal, String key) {
|
return verifyRecordMapper.selectOne(new QueryWrapper<BenefitVerifyRecord>()
|
.eq("idempotency_key", key)
|
.eq("merchant_id", principal.merchantId())
|
.eq("status", 1)
|
.last("limit 1"));
|
}
|
|
private BenefitVerifyRecord record(Long recordId) {
|
BenefitVerifyRecord record = verifyRecordMapper.selectById(recordId);
|
if (record == null) {
|
throw new BusinessException(ErrorCode.INTERNAL_ERROR, "核销记录保存失败");
|
}
|
return record;
|
}
|
|
private Map<String, Object> result(BenefitVerifyRecord record, String sourceType, Object sourceId,
|
BenefitQuotaService.QuotaSnapshot quota, int quantity) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("verified", true);
|
result.put("verifyRecordId", record.getId());
|
result.put("verifyNo", record.getVerifyNo());
|
result.put("benefitType", record.getVerifyType());
|
result.put("sourceType", sourceType);
|
result.put("sourceId", sourceId);
|
result.put("quantity", quantity);
|
result.put("verifiedAt", record.getVerifyTime());
|
result.put("sharedQuotaMode", "AUTO_DEDUCT");
|
if (quota != null) {
|
result.put("sourceCardId", quota.cardId());
|
result.put("sourceTalentId", quota.talentId());
|
result.put("familyCard", quota.familyCard());
|
result.put("primaryCardId", quota.primaryCardId());
|
result.put("primaryTalentId", quota.primaryTalentId());
|
result.put("primaryCardNo", quota.primaryCardNo());
|
result.put("cardLevel", quota.cardLevel());
|
result.put("quotaTotal", quota.total());
|
result.put("quotaUsed", quota.used());
|
result.put("quotaRemaining", quota.remaining());
|
result.put("quotaResetCycle", quota.resetCycle());
|
result.put("quotaWindowStart", quota.windowStart());
|
}
|
return result;
|
}
|
|
private BenefitQuotaService.QuotaSnapshot afterDeduct(BenefitQuotaService.QuotaSnapshot quota, int quantity) {
|
return new BenefitQuotaService.QuotaSnapshot(
|
quota.benefitType(),
|
quota.talentId(),
|
quota.cardId(),
|
quota.cardNo(),
|
quota.cardType(),
|
quota.primaryTalentId(),
|
quota.primaryCardId(),
|
quota.primaryCardNo(),
|
quota.cardLevel(),
|
quota.total(),
|
quota.used() + quantity,
|
Math.max(quota.remaining() - quantity, 0),
|
quota.resetCycle(),
|
quota.windowStart(),
|
quota.familyCard(),
|
quota.sharedCardIds()
|
);
|
}
|
|
private boolean belongsToPrincipal(BenefitOrder order, MerchantPrincipal principal) {
|
Long storeId = order.getStoreId();
|
return principal.merchantId().equals(order.getMerchantId()) && (storeId == null || principal.storeId().equals(storeId));
|
}
|
|
private HotelToken parseHotelToken(String token) {
|
if (token == null || !token.startsWith(BenefitVerificationCodeService.HOTEL_TOKEN_PREFIX + "C")) {
|
return new HotelToken(null, null);
|
}
|
try {
|
int cIndex = token.indexOf('C', BenefitVerificationCodeService.HOTEL_TOKEN_PREFIX.length());
|
int pIndex = token.indexOf('P', cIndex + 1);
|
int splitIndex = token.indexOf('_', pIndex + 1);
|
if (cIndex < 0 || pIndex < 0 || splitIndex < 0) {
|
return new HotelToken(null, null);
|
}
|
Long cardId = parsePositiveLong(token.substring(cIndex + 1, pIndex));
|
Long productId = parsePositiveLong(token.substring(pIndex + 1, splitIndex));
|
return new HotelToken(cardId, productId);
|
} catch (RuntimeException ignored) {
|
return new HotelToken(null, null);
|
}
|
}
|
|
private Long parsePositiveLong(String value) {
|
long parsed = Long.parseLong(value);
|
return parsed > 0 ? parsed : null;
|
}
|
|
private boolean movieCodeExpired(LocalDateTime updatedAt) {
|
return updatedAt != null && !updatedAt.plusSeconds(BenefitVerificationCodeService.CODE_SECONDS).isAfter(LocalDateTime.now());
|
}
|
|
private boolean startsWithMoviePrefix(String code) {
|
return code != null && code.startsWith(BenefitVerificationCodeService.MOVIE_TOKEN_PREFIX);
|
}
|
|
private boolean expiredDate(LocalDate value) {
|
return value != null && value.isBefore(LocalDate.now());
|
}
|
|
private boolean expiredDateTime(LocalDateTime value) {
|
return value != null && !value.isAfter(LocalDateTime.now());
|
}
|
|
private String normalizeIdempotencyKey(String idempotencyKey) {
|
if (idempotencyKey == null || idempotencyKey.isBlank()) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "幂等键不能为空");
|
}
|
String key = idempotencyKey.trim();
|
if (key.length() > 128) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "幂等键长度不能超过限制");
|
}
|
return key;
|
}
|
|
private String sourceRemark(String source, String remark) {
|
String cleaned = remark == null ? "" : remark.trim();
|
return cleaned.isBlank() ? source : source + "; " + cleaned;
|
}
|
|
private String productRemark(Long productId) {
|
return productId == null ? "" : "; productId=" + productId;
|
}
|
|
private String trimToLength(String value, int maxLength) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
return trimmed.length() <= maxLength ? trimmed : trimmed.substring(0, maxLength);
|
}
|
|
private String upper(String value) {
|
return value == null ? "" : value.trim().toUpperCase(Locale.ROOT);
|
}
|
|
private int intValue(Object value) {
|
return value instanceof Number number ? number.intValue() : 0;
|
}
|
|
private long zeroIfNull(Long value) {
|
return value == null ? 0L : value;
|
}
|
|
private record HotelToken(Long sourceCardId, Long productId) {
|
}
|
|
private record HotelOrderCodeContext(BenefitOrderDynamicVerificationCode code, BenefitOrder order) {
|
}
|
|
private record FreeStayCodeContext(BenefitFreeStayVerificationCode code, BenefitFreeStayVoucher voucher) {
|
}
|
}
|