| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- package com.ruoyi.app.omgpay;
- import com.ruoyi.app.omgpay.dto.OmgCreatePaymentResponse;
- import com.ruoyi.system.domain.PosStoreOmg;
- import com.ruoyi.system.omgpay.domain.OmgPaymentAttempt;
- import com.ruoyi.system.omgpay.domain.OmgPaymentOrderSnapshot;
- import com.ruoyi.system.omgpay.service.IOmgPaymentAttemptService;
- import com.ruoyi.system.service.IPosStoreOmgService;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.dao.DuplicateKeyException;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.*;
- @Service
- public class OmgPaymentCreateService {
- private static final Logger log = LoggerFactory.getLogger(OmgPaymentCreateService.class);
- private static final int MAX_TRADE_NUMBER_ATTEMPTS = 3;
- private final IOmgPaymentAttemptService attempts;
- private final IPosStoreOmgService credentials;
- private final OmgMerchantTradeNoGenerator generator;
- private final OmgPaymentFormFactory formFactory;
- public OmgPaymentCreateService(IOmgPaymentAttemptService attempts,
- IPosStoreOmgService credentials,
- OmgMerchantTradeNoGenerator generator,
- OmgPaymentFormFactory formFactory) {
- this.attempts = attempts;
- this.credentials = credentials;
- this.generator = generator;
- this.formFactory = formFactory;
- }
- @Transactional(rollbackFor = Exception.class)
- public OmgPaymentCreateOutcome create(Long userId, String orderId) {
- if (userId == null) {
- throw business(AUTH_REQUIRED);
- }
- String normalizedOrderId = normalizeOrderId(orderId);
- OmgPaymentOrderSnapshot order = attempts.selectOrderForUpdate(normalizedOrderId);
- validateOrder(userId, normalizedOrderId, order);
- if (attempts.selectActiveCreatedByDdIdForUpdate(normalizedOrderId) != null) {
- throw business(PAYMENT_ATTEMPT_EXISTS, order.getStoreId());
- }
- PosStoreOmg credential = credentials.getEnabledCredential(order.getStoreId());
- validateCredential(credential, order.getStoreId());
- log.info("OMG payment validation passed orderId={}, userId={}, storeId={}",
- safeLogOrderId(order.getDdId()), userId, order.getStoreId());
- return createWithBoundedTradeNumberRetries(order, userId, credential);
- }
- /**
- * Replaces the exact attempt previously verified as unpaid by the gateway.
- * The order lock and MerchantTradeNo comparison prevent concurrent retries from both creating a new attempt.
- */
- @Transactional(rollbackFor = Exception.class)
- public OmgPaymentCreateOutcome replaceActiveForRetry(Long userId, String orderId,
- String expectedMerchantTradeNo) {
- if (userId == null) {
- throw business(AUTH_REQUIRED);
- }
- String normalizedOrderId = normalizeOrderId(orderId);
- OmgPaymentOrderSnapshot order = attempts.selectOrderForUpdate(normalizedOrderId);
- validateOrder(userId, normalizedOrderId, order);
- OmgPaymentAttempt active = attempts.selectActiveCreatedByDdIdForUpdate(normalizedOrderId);
- if (active == null || active.getId() == null || expectedMerchantTradeNo == null
- || !expectedMerchantTradeNo.equals(active.getMerchantTradeNo())) {
- throw business(PAYMENT_RETRY_NOT_AVAILABLE, order.getStoreId());
- }
- PosStoreOmg credential = credentials.getEnabledCredential(order.getStoreId());
- validateCredential(credential, order.getStoreId());
- if (attempts.supersedeCreated(active.getId()) != 1) {
- throw business(PAYMENT_RETRY_NOT_AVAILABLE, order.getStoreId());
- }
- log.info("OMG payment retry replacing attempt orderId={}, userId={}, storeId={}, oldMerchantTradeNo={}",
- safeLogOrderId(order.getDdId()), userId, order.getStoreId(),
- maskMerchantTradeNo(active.getMerchantTradeNo()));
- return createWithBoundedTradeNumberRetries(order, userId, credential);
- }
- private OmgPaymentCreateOutcome createWithBoundedTradeNumberRetries(
- OmgPaymentOrderSnapshot order, Long userId, PosStoreOmg credential) {
- for (int number = 1; number <= MAX_TRADE_NUMBER_ATTEMPTS; number++) {
- String merchantTradeNo = generator.generate();
- OmgPaymentForm form;
- try {
- form = formFactory.create(order.getDdId(), order.getAmount(), credential.getMerchantId(),
- credential.getHashKey(), credential.getHashIv(), merchantTradeNo);
- } catch (IllegalArgumentException error) {
- throw business(PAYMENT_CONFIGURATION_INVALID, order.getStoreId());
- }
- try {
- OmgPaymentAttempt attempt = attempts.createCreated(order.getDdId(), merchantTradeNo,
- order.getStoreId(), credential.getMerchantId(), order.getAmount(),
- credential.getHashKey(), credential.getHashIv());
- OmgCreatePaymentResponse response = new OmgCreatePaymentResponse(form.gatewayUrl(), form.fields());
- return new OmgPaymentCreateOutcome(response, attempt.getId(), order.getDdId(), userId,
- order.getStoreId(), order.getAmount(), maskMerchantTradeNo(merchantTradeNo));
- } catch (DuplicateKeyException error) {
- if (attempts.selectActiveCreatedByDdIdForUpdate(order.getDdId()) != null) {
- throw business(PAYMENT_ATTEMPT_EXISTS, order.getStoreId());
- }
- boolean tradeNumberCollision = attempts.selectByMerchantTradeNoForUpdate(merchantTradeNo) != null;
- if (!tradeNumberCollision || number == MAX_TRADE_NUMBER_ATTEMPTS) {
- throw business(PAYMENT_CREATION_FAILED, order.getStoreId());
- }
- }
- }
- throw business(PAYMENT_CREATION_FAILED, order.getStoreId());
- }
- private static String normalizeOrderId(String orderId) {
- if (orderId == null || orderId.isBlank()) {
- throw business(ORDER_REQUIRED);
- }
- String normalized = orderId.trim();
- if (normalized.length() > 64) {
- throw business(ORDER_REQUIRED);
- }
- return normalized;
- }
- private static void validateOrder(Long userId, String orderId, OmgPaymentOrderSnapshot order) {
- if (order == null || !userId.equals(order.getUserId())) {
- throw business(ORDER_NOT_AVAILABLE);
- }
- Long storeId = order.getStoreId();
- if (order.getParentDdId() == null || !orderId.equals(order.getParentDdId())) {
- throw business(MULTI_STORE_ORDER_NOT_SUPPORTED, storeId);
- }
- if (storeId == null) {
- throw business(ORDER_NOT_AVAILABLE);
- }
- if (order.getState() == null || order.getState() < 0 || order.getState() > 2) {
- throw business(ORDER_STATE_NOT_PAYABLE, storeId);
- }
- if (order.getPayStatus() == null || order.getPayStatus() != 0) {
- throw business(ORDER_ALREADY_PAID, storeId);
- }
- if (!"2".equals(order.getPayType())) {
- throw business(PAYMENT_TYPE_INVALID, storeId);
- }
- if (order.getAmount() == null || order.getAmount() <= 0) {
- throw business(ORDER_AMOUNT_INVALID, storeId);
- }
- }
- private static void validateCredential(PosStoreOmg credential, Long storeId) {
- if (credential == null || credential.getMerchantId() == null
- || !credential.getMerchantId().matches("[A-Za-z0-9]{1,10}")
- || credential.getHashKey() == null || credential.getHashKey().isBlank()
- || credential.getHashIv() == null || credential.getHashIv().isBlank()) {
- throw business(STORE_CREDENTIAL_UNAVAILABLE, storeId);
- }
- }
- static String maskMerchantTradeNo(String merchantTradeNo) {
- if (merchantTradeNo == null || merchantTradeNo.length() < 10) {
- return "***";
- }
- return merchantTradeNo.substring(0, 5) + "***"
- + merchantTradeNo.substring(merchantTradeNo.length() - 4);
- }
- private static String safeLogOrderId(String orderId) {
- if (orderId == null) {
- return "<empty>";
- }
- StringBuilder safe = new StringBuilder();
- for (int index = 0; index < orderId.length() && safe.length() < 64; index++) {
- char value = orderId.charAt(index);
- if ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')
- || (value >= '0' && value <= '9') || value == '-' || value == '_') {
- safe.append(value);
- }
- }
- return safe.isEmpty() ? "<empty>" : safe.toString();
- }
- private static OmgPaymentBusinessException business(OmgPaymentErrorCode code) {
- return new OmgPaymentBusinessException(code);
- }
- private static OmgPaymentBusinessException business(OmgPaymentErrorCode code, Long storeId) {
- return new OmgPaymentBusinessException(code, storeId);
- }
- }
|