diff --git a/.env b/.env index b5dfcc3e..b5e4551c 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -PG_SECRET_KEY=test_sk_24xLea5zVA9wY1om2pjmVQAMYNwW \ No newline at end of file +PG_SECRET_KEY=test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6: \ No newline at end of file diff --git a/payment/build.gradle b/payment/build.gradle index 5f7bc9ea..b0e0ed37 100644 --- a/payment/build.gradle +++ b/payment/build.gradle @@ -29,7 +29,6 @@ ext { } dependencies { - implementation project(":common") implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' @@ -42,6 +41,8 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + implementation project(":common") + runtimeOnly 'org.postgresql:postgresql' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' implementation 'org.springframework.boot:spring-boot-starter-data-redis' @@ -52,6 +53,9 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14' implementation 'org.springframework.boot:spring-boot-starter-webflux' + + implementation 'org.flywaydb:flyway-core:11.20.0' + implementation 'org.flywaydb:flyway-database-postgresql:11.20.0' } dependencyManagement { diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/application/query/GetCancelPolicyQuery.java b/payment/src/main/java/com/smore/payment/cancelpolicy/application/query/GetCancelPolicyQuery.java deleted file mode 100644 index 7152a961..00000000 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/application/query/GetCancelPolicyQuery.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.smore.payment.cancelpolicy.application.query; - -import com.smore.payment.cancelpolicy.domain.model.CancelTargetType; -import com.smore.payment.cancelpolicy.domain.model.TargetKey; - -public record GetCancelPolicyQuery( - CancelTargetType cancelTargetType, - TargetKey targetKey -) { - -} diff --git a/payment/src/main/java/com/smore/payment/feepolicy/application/query/GetFeePolicyQuery.java b/payment/src/main/java/com/smore/payment/feepolicy/application/query/GetFeePolicyQuery.java deleted file mode 100644 index a5c6f1c9..00000000 --- a/payment/src/main/java/com/smore/payment/feepolicy/application/query/GetFeePolicyQuery.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.smore.payment.feepolicy.application.query; - -import com.smore.payment.feepolicy.domain.model.TargetKey; -import com.smore.payment.feepolicy.domain.model.TargetType; - -public record GetFeePolicyQuery( - TargetType targetType, - TargetKey targetKey -) { - -} diff --git a/payment/src/main/java/com/smore/payment/payment/application/ApprovePaymentService.java b/payment/src/main/java/com/smore/payment/payment/application/ApprovePaymentService.java new file mode 100644 index 00000000..a24f73b1 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/ApprovePaymentService.java @@ -0,0 +1,172 @@ +package com.smore.payment.payment.application; + +import com.smore.payment.payment.application.facade.FeePolicyFacade; +import com.smore.payment.payment.application.facade.dto.FeePolicyResult; +import com.smore.payment.payment.application.port.in.ApprovePaymentCommand; +import com.smore.payment.payment.application.port.in.ApprovePaymentResult; +import com.smore.payment.payment.application.port.in.ApprovePaymentUseCase; +import com.smore.payment.payment.application.port.out.*; +import com.smore.payment.payment.domain.event.SettlementCalculatedEvent; +import com.smore.payment.payment.domain.model.Payment; +import com.smore.payment.payment.domain.model.PgResponseResult; +import com.smore.payment.payment.domain.model.TemporaryPayment; +import com.smore.payment.payment.domain.service.SettlementAmountCalculator; +import com.smore.payment.shared.outbox.OutboxMessageCreator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ApprovePaymentService implements ApprovePaymentUseCase { + + private final ApiInboxPort apiInboxPort; + private final TemporaryPaymentPort temporaryPaymentPort; + private final PaymentRepository paymentRepository; + + private final PaymentAuditLogService paymentAuditLogService; + + private final PaymentFinalizeCreate paymentFinalizeCreate; + + private final PgClient pgClient; + + @Override + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public ApprovePaymentResult approve(ApprovePaymentCommand command) { + + // 단기 멱등(redis) + Optional cached = + apiInboxPort.find(command.idempotencyKey().toString()); + + if (cached.isPresent()) { + return cached.get(); + } + + // 장기 멱등(rdb) 후 redis 캐싱 + Optional existingPayment = + paymentRepository.findByIdempotencyKey(command.idempotencyKey()); + + if (existingPayment.isPresent()) { + Payment payment = existingPayment.get(); + + ApprovePaymentResult result = ApprovePaymentResult.success( + payment.getId(), + payment.getOrderId(), + payment.getAmount(), + payment.getApprovedAt(), + payment.getStatus().getDesc() + ); + + apiInboxPort.save(command.idempotencyKey().toString(), result); // 캐시 재적재 + return result; + } + + // 결제 승인 시작 - 임시 결제 가져와서 pg에 승인 요청 + TemporaryPayment temp = temporaryPaymentPort.findByOrderId(command.orderId()) + .orElseThrow(() -> new IllegalArgumentException("임시 결제 데이터가 존재하지 않습니다.")); + + // 금액 검증 + temp.validateApproval(command.amount()); + + // 승인 요청 감사 로그 작성(mongodb) + paymentAuditLogService.logApprovalRequested(command, temp); + + // pg 결제 승인 요청 + PgResponseResult pgResult = findOrRequestPgApproval(command, temp); + + // pg 결제 실패 시 실패 이유를 반환 + if (!pgResult.pgStatus().equals("DONE")) { + return ApprovePaymentResult.failed( + command.orderId(), + command.amount(), + pgResult.failureCode(), + pgResult.failureMessage() + ); + } + + ApprovePaymentResult result; + + try { + // 결제 내역 저장 및 수수료 정책 조회하여 정산금 계산 후 이벤트 발행 + Payment payment = paymentFinalizeCreate.finalizePayment(temp, pgResult); + + // 결제 승인 완료 감사 로그 작성 + paymentAuditLogService.logPaymentApprovalSucceeded(payment); + + result = ApprovePaymentResult.success( + payment.getId(), + payment.getOrderId(), + payment.getAmount(), + payment.getApprovedAt(), + payment.getStatus().getDesc() + ); + + } catch (DataIntegrityViolationException e) { + // UNIQUE 제약 충돌 → 이미 생성된 Payment + Payment existing = paymentRepository + .findByIdempotencyKey(command.idempotencyKey()) + .orElseThrow(); + + result = ApprovePaymentResult.success( + existing.getId(), + existing.getOrderId(), + existing.getAmount(), + existing.getApprovedAt(), + existing.getStatus().getDesc() + ); + } catch (RuntimeException e) { + paymentAuditLogService.logPaymentApprovalFailed(temp, e.getMessage()); + throw e; + } + + apiInboxPort.save(command.idempotencyKey().toString(), result); + return result; + } + + + private PgResponseResult findOrRequestPgApproval(ApprovePaymentCommand command, TemporaryPayment temp) { + + if (temp.hasPgApprovalResult()) { + return temp.getPgResponseResult(); + } + + try { + + paymentAuditLogService.logPgApprovalRequested(command, temp); + + PgResponseResult pgResult = pgClient.approve( + command.paymentKey(), + command.pgOrderId(), + command.amount() + ); + + if (!pgResult.pgStatus().equals("DONE")) { + paymentAuditLogService.logPgApprovalFailed( + temp, + pgResult.failureMessage() + ); + + return pgResult; + } + + temp.setPgResponseResult(pgResult); + temporaryPaymentPort.update(temp); + + paymentAuditLogService.logPgApprovalSucceeded(temp, pgResult); + + return pgResult; + + } catch (RuntimeException e) { + paymentAuditLogService.logPgApprovalFailed(temp, e.getMessage()); + throw e; + } + } + +} + diff --git a/payment/src/main/java/com/smore/payment/payment/application/CreatePaymentService.java b/payment/src/main/java/com/smore/payment/payment/application/CreatePaymentService.java deleted file mode 100644 index 1fa1a512..00000000 --- a/payment/src/main/java/com/smore/payment/payment/application/CreatePaymentService.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.smore.payment.payment.application; - -import com.smore.payment.global.outbox.OutboxMessage; -import com.smore.payment.global.outbox.OutboxMessageCreator; -import com.smore.payment.payment.application.command.ApprovePaymentCommand; -import com.smore.payment.payment.application.event.outbound.SettlementCalculatedEvent; -import com.smore.payment.payment.application.facade.FeePolicyFacade; -import com.smore.payment.payment.application.facade.dto.FeePolicyResult; -import com.smore.payment.payment.application.port.out.PgClient; -import com.smore.payment.payment.application.event.outbound.PaymentApprovedEvent; -import com.smore.payment.payment.domain.model.Payment; -//import com.smore.payment.payment.domain.document.PgApproveLog; -import com.smore.payment.payment.domain.model.PgResponseResult; -import com.smore.payment.payment.infrastructure.persistence.redis.model.TemporaryPayment; -import com.smore.payment.payment.domain.repository.MongoRepository; -import com.smore.payment.payment.domain.repository.OutboxRepository; -import com.smore.payment.payment.domain.repository.PaymentRepository; -import com.smore.payment.payment.domain.repository.RedisRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional -public class CreatePaymentService { - - private final RedisRepository redisRepository; - private final PaymentRepository paymentRepository; - private final OutboxRepository outboxRepository; - private final PgClient pgClient; - private final MongoRepository mongoRepository; - private final FeePolicyFacade feePolicyFacade; - private final OutboxMessageCreator outboxMessageCreator; - - public Payment approve(ApprovePaymentCommand command) { - - TemporaryPayment temp = redisRepository.findByOrderId(command.orderId()) - .orElseThrow(() -> new IllegalStateException("임시 결제를 찾을 수 없습니다.")); - - temp.validateApproval(command.amount()); - - paymentRepository.findByIdempotencyKey(temp.getIdempotencyKey()) - .ifPresent(existing -> { - throw new IllegalStateException("이미 결제가 승인되었습니다."); - }); - - PgResponseResult result; - - try { - result = pgClient.approve( - command.paymentKey(), - command.pgOrderId(), - command.amount() - ); - - // Todo: 로그 document 추가 -// PgApproveLog log = PgApproveLog.builder().build(); -// mongoRepository.savePgApproveLog(log); - - } catch (Exception e) { - -// PgApproveLog log = PgApproveLog.builder().build(); -// mongoRepository.savePgApproveLog(log); - - // Todo: 예외 반환 - - throw e; - } - - Payment payment = Payment.createFinalPayment( - temp.getIdempotencyKey(), - temp.getUserId(), - temp.getOrderId(), - temp.getAmount(), - temp.getSellerId(), - temp.getCategoryId(), - temp.getAuctionType(), - result - ); - - paymentRepository.save(payment); - - PaymentApprovedEvent event = new PaymentApprovedEvent( - payment.getOrderId(), - payment.getId(), - payment.getAmount(), - payment.getApprovedAt(), - payment.getIdempotencyKey() - ); - - FeePolicyResult policy = feePolicyFacade.findApplicablePolicy( - payment.getSellerId(), - payment.getCategoryId() - ); - - BigDecimal settlementAmount = calculateSettlementAmount(payment.getAmount(), policy); - - SettlementCalculatedEvent settlementEvent = new SettlementCalculatedEvent( - payment.getSellerId(), - settlementAmount, - payment.getIdempotencyKey() - ); - - OutboxMessage paymentApprovedMsg = outboxMessageCreator.paymentApproved(event); - OutboxMessage settlementCalculatedMsg = outboxMessageCreator.settlementCalculated(settlementEvent); - - outboxRepository.save(paymentApprovedMsg); - - outboxRepository.save(settlementCalculatedMsg); - - redisRepository.deleteByOrderId(temp.getOrderId()); - - return payment; - } - - private BigDecimal calculateSettlementAmount(BigDecimal approvedAmount, FeePolicyResult policy) { - - BigDecimal fee; - - switch (policy.feeType()) { - case "RATE" -> fee = approvedAmount.multiply(policy.rate()); - - case "FIXED" -> fee = policy.fixedAmount(); - - case "MIXED" -> fee = approvedAmount.multiply(policy.rate()) - .add(policy.fixedAmount()); - default -> throw new IllegalArgumentException("Unknown feeType: " + policy.feeType()); - } - - return approvedAmount.subtract(fee); - } - -} - diff --git a/payment/src/main/java/com/smore/payment/payment/application/CreateTemporaryPaymentService.java b/payment/src/main/java/com/smore/payment/payment/application/CreateTemporaryPaymentService.java index b5e83fe0..f5fde25e 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/CreateTemporaryPaymentService.java +++ b/payment/src/main/java/com/smore/payment/payment/application/CreateTemporaryPaymentService.java @@ -1,22 +1,21 @@ package com.smore.payment.payment.application; import com.smore.payment.payment.application.event.inbound.PaymentRequestedEvent; -import com.smore.payment.payment.infrastructure.persistence.redis.model.TemporaryPayment; -import com.smore.payment.payment.domain.repository.RedisRepository; +import com.smore.payment.payment.application.port.out.TemporaryPaymentPort; +import com.smore.payment.payment.domain.model.TemporaryPayment; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class CreateTemporaryPaymentService { - private final RedisRepository redisRepository; + private final TemporaryPaymentPort temporaryPaymentPort; + private final PaymentAuditLogService paymentAuditLogService; - @Transactional public void create(PaymentRequestedEvent paymentRequestedEvent) { - if (redisRepository.existsByOrderId(paymentRequestedEvent.orderId())) { + if (temporaryPaymentPort.existsByOrderId(paymentRequestedEvent.orderId())) { return; } @@ -31,6 +30,7 @@ public void create(PaymentRequestedEvent paymentRequestedEvent) { paymentRequestedEvent.expiredAt() ); - redisRepository.save(temp); + temporaryPaymentPort.save(temp); + paymentAuditLogService.logTemporaryPaymentCreated(paymentRequestedEvent); } } diff --git a/payment/src/main/java/com/smore/payment/payment/application/PaymentAuditLogService.java b/payment/src/main/java/com/smore/payment/payment/application/PaymentAuditLogService.java new file mode 100644 index 00000000..05a4fbbd --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/PaymentAuditLogService.java @@ -0,0 +1,268 @@ +package com.smore.payment.payment.application; + +import com.smore.payment.payment.application.event.inbound.PaymentRefundEvent; +import com.smore.payment.payment.application.event.inbound.PaymentRequestedEvent; +import com.smore.payment.payment.application.port.in.ApprovePaymentCommand; +import com.smore.payment.payment.application.port.out.PaymentAuditLogPort; +import com.smore.payment.payment.domain.model.Payment; +import com.smore.payment.payment.domain.model.PgResponseResult; +import com.smore.payment.payment.domain.model.TemporaryPayment; +import com.smore.payment.payment.infrastructure.persistence.mongo.model.PaymentAuditEventType; +import com.smore.payment.payment.infrastructure.persistence.mongo.model.PaymentAuditLog; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class PaymentAuditLogService { + + private final PaymentAuditLogPort paymentAuditLogPort; + + public void logTemporaryPaymentCreated(PaymentRequestedEvent event) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.TEMPORARY_PAYMENT_CREATED) + .orderId(event.orderId()) + .userId(event.userId()) + .sellerId(event.sellerId()) + .categoryId(event.categoryId()) + .auctionType(event.auctionType()) + .amount(event.amount()) + .idempotencyKey(event.idempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description("임시 결제 생성") + .build() + ); + } + + public void logApprovalRequested(ApprovePaymentCommand command, TemporaryPayment temp) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PAYMENT_APPROVAL_REQUESTED) + .orderId(command.orderId()) + .paymentKey(command.paymentKey()) + .pgOrderId(command.pgOrderId()) + .userId(temp.getUserId()) + .sellerId(temp.getSellerId()) + .categoryId(temp.getCategoryId()) + .auctionType(temp.getAuctionType()) + .amount(command.amount()) + .idempotencyKey(temp.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description("결제 승인 요청") + .build() + ); + } + + public void logPgApprovalRequested(ApprovePaymentCommand command, TemporaryPayment temp) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PG_APPROVAL_REQUESTED) + .orderId(command.orderId()) + .paymentKey(command.paymentKey()) + .pgOrderId(command.pgOrderId()) + .userId(temp.getUserId()) + .sellerId(temp.getSellerId()) + .categoryId(temp.getCategoryId()) + .auctionType(temp.getAuctionType()) + .amount(command.amount()) + .idempotencyKey(temp.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description("PG 승인 요청") + .build() + ); + } + + public void logPgApprovalSucceeded(TemporaryPayment temp, PgResponseResult result) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PG_APPROVAL_SUCCEEDED) + .orderId(temp.getOrderId()) + .paymentKey(result.paymentKey()) + .pgOrderId(result.pgOrderId()) + .userId(temp.getUserId()) + .sellerId(temp.getSellerId()) + .categoryId(temp.getCategoryId()) + .auctionType(temp.getAuctionType()) + .amount(result.totalAmount()) + .idempotencyKey(temp.getIdempotencyKey()) + .occurredAt(result.approvedAt()) + .description("PG 승인 완료") + .build() + ); + } + + public void logPgApprovalFailed(TemporaryPayment temp, String reason) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PG_APPROVAL_FAILED) + .orderId(temp.getOrderId()) + .userId(temp.getUserId()) + .sellerId(temp.getSellerId()) + .categoryId(temp.getCategoryId()) + .auctionType(temp.getAuctionType()) + .amount(temp.getAmount()) + .idempotencyKey(temp.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description(reason) + .build() + ); + } + + public void logPaymentApprovalSucceeded(Payment payment) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PAYMENT_APPROVAL_SUCCEEDED) + .orderId(payment.getOrderId()) + .paymentId(payment.getId()) + .paymentKey(payment.getPaymentKey()) + .pgOrderId(payment.getPgOrderId()) + .userId(payment.getUserId()) + .sellerId(payment.getSellerId()) + .categoryId(payment.getCategoryId()) + .auctionType(payment.getAuctionType()) + .amount(payment.getAmount()) + .idempotencyKey(payment.getIdempotencyKey()) + .occurredAt(payment.getApprovedAt()) + .description("결제 승인 완료") + .build() + ); + } + + public void logPaymentApprovalFailed(TemporaryPayment temp, String reason) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PAYMENT_APPROVAL_FAILED) + .orderId(temp.getOrderId()) + .userId(temp.getUserId()) + .sellerId(temp.getSellerId()) + .categoryId(temp.getCategoryId()) + .auctionType(temp.getAuctionType()) + .amount(temp.getAmount()) + .idempotencyKey(temp.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description(reason) + .build() + ); + } + + public void logRefundRequested(PaymentRefundEvent event) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PAYMENT_REFUND_REQUESTED) + .orderId(event.orderId()) + .paymentId(event.paymentId()) + .refundId(event.refundId()) + .userId(event.userId()) + .amount(event.refundAmount()) + .idempotencyKey(event.idempotencyKey()) + .occurredAt(event.publishedAt()) + .description(event.refundReason()) + .build() + ); + } + + public void logPgRefundRequested(Payment payment, PaymentRefundEvent event, BigDecimal refundAmount) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PG_REFUND_REQUESTED) + .orderId(payment.getOrderId()) + .paymentId(payment.getId()) + .refundId(event.refundId()) + .userId(payment.getUserId()) + .sellerId(payment.getSellerId()) + .categoryId(payment.getCategoryId()) + .auctionType(payment.getAuctionType()) + .amount(refundAmount) + .paymentKey(payment.getPaymentKey()) + .pgOrderId(payment.getPgOrderId()) + .idempotencyKey(payment.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description(event.refundReason()) + .build() + ); + } + + public void logPgRefundSucceeded(Payment payment, PaymentRefundEvent event, PgResponseResult result) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PG_REFUND_SUCCEEDED) + .orderId(payment.getOrderId()) + .paymentId(payment.getId()) + .refundId(event.refundId()) + .userId(payment.getUserId()) + .sellerId(payment.getSellerId()) + .categoryId(payment.getCategoryId()) + .auctionType(payment.getAuctionType()) + .amount(result.cancels().cancelAmount()) + .paymentKey(payment.getPaymentKey()) + .pgOrderId(payment.getPgOrderId()) + .idempotencyKey(payment.getIdempotencyKey()) + .occurredAt(result.cancels().canceledAt()) + .description(result.cancels().cancelReason()) + .build() + ); + } + + public void logPgRefundFailed(Payment payment, PaymentRefundEvent event, String reason) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PG_REFUND_FAILED) + .orderId(payment.getOrderId()) + .paymentId(payment.getId()) + .refundId(event.refundId()) + .userId(payment.getUserId()) + .sellerId(payment.getSellerId()) + .categoryId(payment.getCategoryId()) + .auctionType(payment.getAuctionType()) + .amount(event.refundAmount()) + .paymentKey(payment.getPaymentKey()) + .pgOrderId(payment.getPgOrderId()) + .idempotencyKey(payment.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description(reason) + .build() + ); + } + + public void logRefundSucceeded(Payment payment, PaymentRefundEvent event) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PAYMENT_REFUND_SUCCEEDED) + .orderId(payment.getOrderId()) + .paymentId(payment.getId()) + .refundId(event.refundId()) + .userId(payment.getUserId()) + .sellerId(payment.getSellerId()) + .categoryId(payment.getCategoryId()) + .auctionType(payment.getAuctionType()) + .amount(event.refundAmount()) + .idempotencyKey(payment.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description("결제 환불 완료") + .build() + ); + } + + public void logRefundFailed(Payment payment, PaymentRefundEvent event, String reason) { + paymentAuditLogPort.save( + PaymentAuditLog.builder() + .eventType(PaymentAuditEventType.PAYMENT_REFUND_FAILED) + .orderId(payment.getOrderId()) + .paymentId(payment.getId()) + .refundId(event.refundId()) + .userId(payment.getUserId()) + .sellerId(payment.getSellerId()) + .categoryId(payment.getCategoryId()) + .auctionType(payment.getAuctionType()) + .amount(event.refundAmount()) + .idempotencyKey(payment.getIdempotencyKey()) + .occurredAt(LocalDateTime.now()) + .description(reason) + .build() + ); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/application/PaymentFinalizeCreate.java b/payment/src/main/java/com/smore/payment/payment/application/PaymentFinalizeCreate.java new file mode 100644 index 00000000..e7e0fed2 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/PaymentFinalizeCreate.java @@ -0,0 +1,78 @@ +package com.smore.payment.payment.application; + +import com.smore.payment.payment.application.facade.FeePolicyFacade; +import com.smore.payment.payment.application.facade.dto.FeePolicyResult; +import com.smore.payment.payment.application.port.out.OutboxPort; +import com.smore.payment.payment.application.port.out.PaymentRepository; +import com.smore.payment.payment.application.port.out.SellerSettlementLedgerRepository; +import com.smore.payment.payment.application.port.out.TemporaryPaymentPort; +import com.smore.payment.payment.domain.event.SettlementCalculatedEvent; +import com.smore.payment.payment.domain.model.Payment; +import com.smore.payment.payment.domain.model.PgResponseResult; +import com.smore.payment.payment.domain.model.TemporaryPayment; +import com.smore.payment.payment.domain.service.SettlementAmountCalculator; +import com.smore.payment.shared.outbox.OutboxMessageCreator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PaymentFinalizeCreate { + + private final PaymentRepository paymentRepository; + private final FeePolicyFacade feePolicyFacade; + private final SellerSettlementLedgerRepository sellerSettlementLedgerRepository; + private final TemporaryPaymentPort temporaryPaymentPort; + + private final SettlementAmountCalculator settlementAmountCalculator; + + private final OutboxPort outboxPort; + private final OutboxMessageCreator outboxMessageCreator; + + @Transactional + public Payment finalizePayment( + TemporaryPayment temp, + PgResponseResult result + ) { + + Payment payment = Payment.createFinalPayment( + temp.getIdempotencyKey(), + temp.getUserId(), + temp.getOrderId(), + temp.getAmount(), + temp.getSellerId(), + temp.getCategoryId(), + temp.getAuctionType(), + result + ); + + paymentRepository.save(payment); + + FeePolicyResult policy = feePolicyFacade.findApplicablePolicy( + payment.getSellerId(), + payment.getCategoryId() + ); + + SettlementCalculatedEvent settlementEvent = + payment.createSettlementCalculatedEvent( + settlementAmountCalculator.calculate(payment.getAmount(), policy) + ); + + sellerSettlementLedgerRepository.saveLedger( + payment.getSellerId(), + "EARN", + settlementEvent.settlementAmount(), + payment.getId(), + payment.getIdempotencyKey() + ); + + outboxPort.save(outboxMessageCreator.paymentApproved(payment.createApprovedEvent())); + outboxPort.save(outboxMessageCreator.settlementCalculated(settlementEvent)); + + temporaryPaymentPort.deleteByOrderId(temp.getOrderId()); + + return payment; + } + +} diff --git a/payment/src/main/java/com/smore/payment/payment/application/PaymentFinalizeRefund.java b/payment/src/main/java/com/smore/payment/payment/application/PaymentFinalizeRefund.java new file mode 100644 index 00000000..1e8e245a --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/PaymentFinalizeRefund.java @@ -0,0 +1,191 @@ +package com.smore.payment.payment.application; + +import com.smore.payment.payment.application.event.inbound.PaymentRefundEvent; +import com.smore.payment.payment.application.event.outbound.PaymentRefundFailedEvent; +import com.smore.payment.payment.application.event.outbound.PaymentRefundSucceededEvent; +import com.smore.payment.payment.application.facade.CancelPolicyFacade; +import com.smore.payment.payment.application.facade.RefundPolicyFacade; +import com.smore.payment.payment.application.facade.dto.CancelPolicyResult; +import com.smore.payment.payment.application.facade.dto.RefundPolicyResult; +import com.smore.payment.payment.application.port.out.OutboxPort; +import com.smore.payment.payment.application.port.out.PaymentRepository; +import com.smore.payment.payment.application.port.out.PgClient; +import com.smore.payment.payment.domain.model.Payment; +import com.smore.payment.payment.domain.model.PaymentStatus; +import com.smore.payment.payment.domain.model.PgResponseResult; +import com.smore.payment.payment.domain.service.RefundCalculator; +import com.smore.payment.payment.domain.service.RefundDecision; +import com.smore.payment.payment.infrastructure.persistence.inbox.RefundInbox; +import com.smore.payment.payment.infrastructure.persistence.inbox.RefundInboxRepository; +import com.smore.payment.shared.outbox.OutboxMessage; +import com.smore.payment.shared.outbox.OutboxMessageCreator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; + +@Service +@RequiredArgsConstructor +public class PaymentFinalizeRefund { + + private final RefundInboxRepository refundInboxRepository; + + private final PaymentRepository paymentRepository; + private final CancelPolicyFacade cancelPolicyFacade; + private final RefundPolicyFacade refundPolicyFacade; + private final RefundCalculator refundCalculator; + + private final OutboxMessageCreator outboxCreator; + private final OutboxPort outboxPort; + private final PaymentAuditLogService paymentAuditLogService; + + private static final Long MAX_RETRY_COUNT = 3L; + + /** + * 1) 정책 판단 + Inbox 상태 갱신 (짧은 TX) + */ + @Transactional + public PolicyResult policyPhaseTx(PaymentRefundEvent event) { + RefundInbox inbox = refundInboxRepository.findByRefundId(event.refundId()) + .orElseThrow(() -> new IllegalStateException("inbox not found. refundId=" + event.refundId())); + + if (inbox.isFinalized()) { + return new PolicyResult(false, BigDecimal.ZERO, "이미 FINALIZED", null); + } + + Payment payment = paymentRepository.findById(event.paymentId()) + .orElseThrow(() -> new IllegalArgumentException("결제 정보를 찾을 수 없습니다. paymentId=" + event.paymentId())); + + CancelPolicyResult cancelResult = cancelPolicyFacade.findApplicablePolicy( + payment.getSellerId(), payment.getCategoryId(), payment.getAuctionType() + ); + + RefundPolicyResult refundResult = refundPolicyFacade.findApplicablePolicy( + payment.getSellerId(), payment.getCategoryId(), payment.getAuctionType() + ); + + RefundDecision decision = refundCalculator.decide(payment, event, cancelResult, refundResult); + + if (!decision.refundable()) { + paymentAuditLogService.logRefundFailed(payment, event, decision.failureReason()); + + inbox.markFailed(decision.failureReason()); + refundInboxRepository.save(inbox); + + // 정책 불가 실패는 “재시도 대상 아님”이 일반적 + OutboxMessage failedMsg = outboxCreator.paymentRefundFailed( + PaymentRefundFailedEvent.of(event.orderId(), event.refundId(), event.refundAmount(), decision.failureReason()) + ); + outboxPort.save(failedMsg); + + return new PolicyResult(false, BigDecimal.ZERO, decision.failureReason(), payment); + } + + inbox.markPolicyPassed(); + refundInboxRepository.save(inbox); + + return new PolicyResult(true, decision.refundAmount(), null, payment); + } + + /** + * 2) PG 요청 전 상태 기록 + */ + @Transactional + public void markPgRequestedTx(PaymentRefundEvent event) { + RefundInbox inbox = refundInboxRepository.findByRefundId(event.refundId()) + .orElseThrow(); + if (inbox.isFinalized()) return; + + inbox.markPgRequested(); + refundInboxRepository.save(inbox); + } + + /** + * PG 실패/시스템 실패 등 재시도 고려 케이스 + */ + @Transactional + public void failSystemTx(PaymentRefundEvent event, String reason, boolean toDlt) { + RefundInbox inbox = refundInboxRepository.findByRefundId(event.refundId()) + .orElseThrow(); + + inbox.increaseRetry(reason); + + boolean retryable = inbox.getRetryCount() <= MAX_RETRY_COUNT; + + if (!retryable) { + inbox.markFailed(reason); + } + refundInboxRepository.save(inbox); + + PaymentRefundFailedEvent payload = + PaymentRefundFailedEvent.of( + event.orderId(), + event.refundId(), + event.refundAmount(), + reason + ); + + OutboxMessage msg; + if (retryable) { + msg = outboxCreator.refundRetry(payload); + } else if (toDlt) { + msg = outboxCreator.refundDlt(payload); + } else { + msg = outboxCreator.paymentRefundFailed(payload); + } + + outboxPort.save(msg); + } + + /** + * 3) PG 성공 반영 + Payment 업데이트 + Outbox + Inbox FINALIZED (정합성 핵심) + */ + @Transactional + public void finalizeRefund(PgResponseResult pgResponseResult, PaymentRefundEvent paymentRefundEvent) { + RefundInbox inbox = refundInboxRepository.findByRefundId(paymentRefundEvent.refundId()) + .orElseThrow(); + + if (inbox.isFinalized()) { + return; // 멱등 + } + + inbox.markPgSucceeded(pgResponseResult.transactionKey()); + refundInboxRepository.save(inbox); + + Payment payment = paymentRepository + .findById(paymentRefundEvent.paymentId()) + .orElseThrow(); + + if (payment.getStatus() == PaymentStatus.REFUNDED) { + return; + } + + payment.updateRefund( + pgResponseResult.cancels().cancelReason(), + paymentRefundEvent.refundAmount(), + pgResponseResult.cancels().canceledAt(), + pgResponseResult.cancels().cancelTransactionKey(), + pgResponseResult.cancels().cancelAmount(), + "REFUNDED" + ); + paymentRepository.updateRefund(payment.getId(), payment.getRefund()); + + inbox.markFinalized(); + refundInboxRepository.save(inbox); + + outboxPort.save( + outboxCreator.paymentRefunded( + PaymentRefundSucceededEvent.of(paymentRefundEvent.orderId(), paymentRefundEvent.refundId(), paymentRefundEvent.refundAmount()) + ) + ); + } + + public record PolicyResult( + boolean refundable, + BigDecimal refundAmount, + String failureReason, + Payment payment + ) { + } +} diff --git a/payment/src/main/java/com/smore/payment/payment/application/PaymentRefundService.java b/payment/src/main/java/com/smore/payment/payment/application/PaymentRefundService.java index 998455ce..d224b431 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/PaymentRefundService.java +++ b/payment/src/main/java/com/smore/payment/payment/application/PaymentRefundService.java @@ -1,7 +1,5 @@ package com.smore.payment.payment.application; -import com.smore.payment.global.outbox.OutboxMessage; -import com.smore.payment.global.outbox.OutboxMessageCreator; import com.smore.payment.payment.application.event.inbound.PaymentRefundEvent; import com.smore.payment.payment.application.event.outbound.PaymentRefundFailedEvent; import com.smore.payment.payment.application.event.outbound.PaymentRefundSucceededEvent; @@ -9,118 +7,88 @@ import com.smore.payment.payment.application.facade.RefundPolicyFacade; import com.smore.payment.payment.application.facade.dto.CancelPolicyResult; import com.smore.payment.payment.application.facade.dto.RefundPolicyResult; +import com.smore.payment.payment.application.port.in.RefundPaymentUseCase; +import com.smore.payment.payment.application.port.out.OutboxPort; +import com.smore.payment.payment.application.port.out.PaymentRepository; import com.smore.payment.payment.application.port.out.PgClient; import com.smore.payment.payment.domain.model.Payment; +import com.smore.payment.payment.domain.model.PaymentStatus; import com.smore.payment.payment.domain.model.PgResponseResult; -import com.smore.payment.payment.domain.repository.OutboxRepository; -import com.smore.payment.payment.domain.repository.PaymentRepository; +import com.smore.payment.payment.domain.service.RefundCalculator; +import com.smore.payment.payment.domain.service.RefundDecision; +import com.smore.payment.payment.infrastructure.persistence.inbox.RefundInbox; +import com.smore.payment.payment.infrastructure.persistence.inbox.RefundInboxRepository; +import com.smore.payment.shared.outbox.OutboxMessage; +import com.smore.payment.shared.outbox.OutboxMessageCreator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; -import java.time.LocalDateTime; @Service @RequiredArgsConstructor -@Transactional -public class PaymentRefundService { +public class PaymentRefundService implements RefundPaymentUseCase { - private final PaymentRepository paymentRepository; - private final CancelPolicyFacade cancelPolicyFacade; - private final RefundPolicyFacade refundPolicyFacade; private final PgClient pgClient; - private final OutboxMessageCreator outboxCreator; - private final OutboxRepository outboxRepository; + private final PaymentAuditLogService paymentAuditLogService; + + private final PaymentFinalizeRefund paymentFinalizeRefund; + + @Override + @Transactional(propagation = Propagation.NOT_SUPPORTED) public void refund(PaymentRefundEvent event) { - // - // 1) 결제 조회 - // - Payment payment = paymentRepository.findById(event.paymentId()) - .orElseThrow(() -> new IllegalArgumentException("결제 정보를 찾을 수 없습니다. paymentId=" + event.paymentId())); - - // - // 2) 취소 정책 조회 - // - CancelPolicyResult cancelResult = cancelPolicyFacade.findApplicablePolicy( - payment.getSellerId(), - payment.getCategoryId(), - payment.getAuctionType() - ); - - // - // 3) 취소 정책 검증 - // - boolean cancelAvailable = cancelResult.cancellable(payment.getApprovedAt(), event.publishedAt()); - RefundPolicyResult refundResult = null; - if (!cancelAvailable) { - refundResult = refundPolicyFacade.findApplicablePolicy( - payment.getSellerId(), - payment.getCategoryId(), - payment.getAuctionType() - ); + paymentAuditLogService.logRefundRequested(event); - if (!refundResult.refundable(payment.getApprovedAt(), event.publishedAt())) { - OutboxMessage failedMsg = outboxCreator.paymentRefundFailed( - PaymentRefundFailedEvent.of(event.orderId(), event.refundId(), event.refundAmount(), "환불이 불가능 한 상품입니다.") - ); - outboxRepository.save(failedMsg); - return; - } + PaymentFinalizeRefund.PolicyResult policy = paymentFinalizeRefund.policyPhaseTx(event); + if (!policy.refundable()) { + return; } - // - // 5) 최종 환불 금액 계산 - // - final BigDecimal refundAmount = (cancelAvailable) - ? cancelResult.calculateCancelFee(event.refundAmount()) - : refundResult.calculateRefundFee(event.refundAmount()); + paymentFinalizeRefund.markPgRequestedTx(event); - // - // 6) PG 환불 호출 - // PgResponseResult pgRefundResult; try { + paymentAuditLogService.logPgRefundRequested(policy.payment(), event, policy.refundAmount()); + pgRefundResult = pgClient.refund( - payment.getPaymentKey(), - refundAmount, + policy.payment().getPaymentKey(), + policy.refundAmount(), event.refundReason() ); - } catch (Exception e) { + } catch (RuntimeException e) { + paymentAuditLogService.logPgRefundFailed(policy.payment(), event, e.getMessage()); - // PG 실패 → 환불 실패 이벤트 Outbox 발행 - OutboxMessage failedMsg = outboxCreator.paymentRefundFailed( - PaymentRefundFailedEvent.of(event.orderId(), event.refundId(), refundAmount, e.getMessage()) - ); - outboxRepository.save(failedMsg); + paymentFinalizeRefund.failSystemTx(event, "PG 환불 요청 실패(시스템 오류): " + e.getMessage(), true); throw e; } - // - // 7) Payment 엔티티 상태 업데이트 - // - payment.updateRefund( - pgRefundResult.cancels().cancelReason(), - event.refundAmount(), - pgRefundResult.cancels().canceledAt(), - pgRefundResult.cancels().cancelTransactionKey(), - pgRefundResult.cancels().cancelAmount() - ); - paymentRepository.updateRefund(payment.getId(), payment.getRefund()); - - // Todo: 환불 후 정산금 어떻게 하지.......... - // - // 8) 환불 성공 이벤트 Outbox 저장 - // - OutboxMessage successMsg = outboxCreator.paymentRefunded( - PaymentRefundSucceededEvent.of(event.orderId(), event.refundId(), refundAmount) - ); - - outboxRepository.save(successMsg); + if (!pgRefundResult.pgStatus().equals("CANCELED")) { + paymentAuditLogService.logPgRefundFailed(policy.payment(), event, pgRefundResult.failureMessage()); + + paymentFinalizeRefund.failSystemTx(event, "PG 환불 상태 오류: " + pgRefundResult.failureMessage(), false); + return; + } + + paymentAuditLogService.logPgRefundSucceeded(policy.payment(), event, pgRefundResult); + + try { + paymentFinalizeRefund.finalizeRefund(pgRefundResult, event); + + paymentAuditLogService.logRefundSucceeded(policy.payment(), event); + } catch (RuntimeException e) { + paymentAuditLogService.logRefundFailed(policy.payment(), event, e.getMessage()); + + paymentFinalizeRefund.failSystemTx(event, "finalize 실패: " + e.getMessage(), true); + throw e; + } } + + } diff --git a/payment/src/main/java/com/smore/payment/payment/application/PaymentSettlementService.java b/payment/src/main/java/com/smore/payment/payment/application/PaymentSettlementService.java index 4188543c..5c9404a5 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/PaymentSettlementService.java +++ b/payment/src/main/java/com/smore/payment/payment/application/PaymentSettlementService.java @@ -1,72 +1,53 @@ package com.smore.payment.payment.application; -import com.smore.payment.global.outbox.OutboxMessageCreator; import com.smore.payment.payment.application.event.inbound.PaymentSettlementRequestEvent; import com.smore.payment.payment.application.event.outbound.SettlementFailedEvent; import com.smore.payment.payment.application.event.outbound.SettlementSuccessEvent; -import com.smore.payment.payment.domain.repository.OutboxRepository; -import com.smore.payment.payment.domain.repository.SellerSettlementLedgerRepository; +import com.smore.payment.payment.application.port.in.SettlePaymentUseCase; +import com.smore.payment.payment.application.port.out.OutboxPort; +import com.smore.payment.payment.application.port.out.SellerSettlementLedgerRepository; +import com.smore.payment.payment.domain.service.SettlementValidationResult; +import com.smore.payment.payment.domain.service.SettlementValidationService; +import com.smore.payment.shared.outbox.OutboxMessageCreator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.math.BigDecimal; - @Slf4j @Service @RequiredArgsConstructor -public class PaymentSettlementService { +public class PaymentSettlementService implements SettlePaymentUseCase { private final SellerSettlementLedgerRepository sellerSettlementLedgerRepository; - private final OutboxRepository outboxRepository; + private final OutboxPort outboxPort; private final OutboxMessageCreator outboxCreator; + private final SettlementValidationService settlementValidationService; @Transactional - public void settlement(PaymentSettlementRequestEvent event) { + @Override + public void settle(PaymentSettlementRequestEvent event) { - // 1) 멱등성 체크 - if (sellerSettlementLedgerRepository.existsByIdempotencyKey(event.idempotencyKey())) { + SettlementValidationResult validation = settlementValidationService.validate(event); + if (validation.duplicated()) { return; } - // 2) 계좌번호 검증 – 비즈니스 실패 → Retry 불필요 - if (event.accountNumber() == null || event.accountNumber().isBlank()) { - outboxRepository.save( - outboxCreator.settlementFailed( - SettlementFailedEvent.of( - event.userId(), - event.amount(), - event.accountNumber(), - event.idempotencyKey(), - "계좌 번호가 잘못 입력되었습니다." - ) - ) - ); - return; - } - - // 3) 잔액 검증 – 비즈니스 실패 → Retry 불필요 - BigDecimal balance = sellerSettlementLedgerRepository.calculateBalance(event.userId()); - - if (balance.compareTo(event.amount()) < 0 - || balance.compareTo(BigDecimal.valueOf(1_000_000_000L)) > 0) { - - outboxRepository.save( + if (!validation.valid()) { + outboxPort.save( outboxCreator.settlementFailed( SettlementFailedEvent.of( event.userId(), event.amount(), event.accountNumber(), event.idempotencyKey(), - "출금 가능 잔액이 부족합니다." + validation.failureReason() ) ) ); return; } - // 4) 시스템 오류 가능 구간 – Retry + DLT 대상 try { sellerSettlementLedgerRepository.saveLedger( event.userId(), @@ -76,7 +57,7 @@ public void settlement(PaymentSettlementRequestEvent event) { event.idempotencyKey() ); - outboxRepository.save( + outboxPort.save( outboxCreator.settlementCompleted( SettlementSuccessEvent.of( event.userId(), @@ -89,8 +70,7 @@ public void settlement(PaymentSettlementRequestEvent event) { } catch (Exception e) { - // (1) 내부 DLT logging 저장 - outboxRepository.save( + outboxPort.save( outboxCreator.settlementDlt( SettlementFailedEvent.of( event.userId(), @@ -102,7 +82,6 @@ public void settlement(PaymentSettlementRequestEvent event) { ) ); - // (2) Kafka RetryTopic → DLT 로 넘어가도록 강제 예외 전파 throw new RuntimeException("정산 처리 중 시스템 오류 발생", e); } } diff --git a/payment/src/main/java/com/smore/payment/payment/application/event/outbound/PaymentApprovedEvent.java b/payment/src/main/java/com/smore/payment/payment/application/event/outbound/PaymentApprovedEvent.java deleted file mode 100644 index f91db2a7..00000000 --- a/payment/src/main/java/com/smore/payment/payment/application/event/outbound/PaymentApprovedEvent.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.smore.payment.payment.application.event.outbound; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.UUID; - -public record PaymentApprovedEvent(UUID orderId, UUID paymentId, BigDecimal amount, LocalDateTime approvedAt, - UUID idempotencyKey) { - -} diff --git a/payment/src/main/java/com/smore/payment/payment/application/facade/CancelPolicyFacade.java b/payment/src/main/java/com/smore/payment/payment/application/facade/CancelPolicyFacade.java index be530720..b725511b 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/facade/CancelPolicyFacade.java +++ b/payment/src/main/java/com/smore/payment/payment/application/facade/CancelPolicyFacade.java @@ -1,7 +1,7 @@ package com.smore.payment.payment.application.facade; -import com.smore.payment.cancelpolicy.domain.model.CancelPolicy; -import com.smore.payment.cancelpolicy.domain.repository.CancelPolicyRepository; +import com.smore.payment.policy.cancel.domain.model.CancelPolicy; +import com.smore.payment.policy.cancel.domain.repository.CancelPolicyRepository; import com.smore.payment.payment.application.facade.dto.CancelPolicyResult; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/payment/src/main/java/com/smore/payment/payment/application/facade/FeePolicyFacade.java b/payment/src/main/java/com/smore/payment/payment/application/facade/FeePolicyFacade.java index 81dc8831..df2327c7 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/facade/FeePolicyFacade.java +++ b/payment/src/main/java/com/smore/payment/payment/application/facade/FeePolicyFacade.java @@ -1,7 +1,7 @@ package com.smore.payment.payment.application.facade; -import com.smore.payment.feepolicy.domain.model.FeePolicy; -import com.smore.payment.feepolicy.domain.repository.FeePolicyRepository; +import com.smore.payment.policy.fee.domain.model.FeePolicy; +import com.smore.payment.policy.fee.domain.repository.FeePolicyRepository; import com.smore.payment.payment.application.facade.dto.FeePolicyResult; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/payment/src/main/java/com/smore/payment/payment/application/facade/RefundPolicyFacade.java b/payment/src/main/java/com/smore/payment/payment/application/facade/RefundPolicyFacade.java index 83ceb84d..660dd35f 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/facade/RefundPolicyFacade.java +++ b/payment/src/main/java/com/smore/payment/payment/application/facade/RefundPolicyFacade.java @@ -1,10 +1,8 @@ package com.smore.payment.payment.application.facade; -import com.smore.payment.cancelpolicy.domain.model.CancelPolicy; -import com.smore.payment.payment.application.facade.dto.CancelPolicyResult; import com.smore.payment.payment.application.facade.dto.RefundPolicyResult; -import com.smore.payment.refundpolicy.domain.model.RefundPolicy; -import com.smore.payment.refundpolicy.domain.repository.RefundPolicyRepository; +import com.smore.payment.policy.refund.domain.model.RefundPolicy; +import com.smore.payment.policy.refund.domain.repository.RefundPolicyRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/payment/src/main/java/com/smore/payment/payment/application/command/ApprovePaymentCommand.java b/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentCommand.java similarity index 68% rename from payment/src/main/java/com/smore/payment/payment/application/command/ApprovePaymentCommand.java rename to payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentCommand.java index d75ff647..72d627f3 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/command/ApprovePaymentCommand.java +++ b/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentCommand.java @@ -1,12 +1,13 @@ -package com.smore.payment.payment.application.command; +package com.smore.payment.payment.application.port.in; import java.math.BigDecimal; import java.util.UUID; public record ApprovePaymentCommand( UUID orderId, + UUID idempotencyKey, BigDecimal amount, String paymentKey, String pgOrderId - ) { +) { } diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentResult.java b/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentResult.java new file mode 100644 index 00000000..e9628267 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentResult.java @@ -0,0 +1,53 @@ +package com.smore.payment.payment.application.port.in; + +import org.springframework.cglib.core.Local; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +public record ApprovePaymentResult( + UUID paymentId, + UUID orderId, + BigDecimal approvedAmount, + LocalDateTime approvedAt, + String status, + String failureCode, + String failureMessage +) { + + public static ApprovePaymentResult success( + UUID paymentId, + UUID orderId, + BigDecimal amount, + LocalDateTime approvedAt, + String status + ) { + return new ApprovePaymentResult( + paymentId, + orderId, + amount, + approvedAt, + status, + null, + null + ); + } + + public static ApprovePaymentResult failed( + UUID orderId, + BigDecimal amount, + String failureCode, + String failureMessage + ) { + return new ApprovePaymentResult( + null, + orderId, + amount, + null, + "FAILED", + failureCode, + failureMessage + ); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentUseCase.java b/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentUseCase.java new file mode 100644 index 00000000..265c67bf --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/in/ApprovePaymentUseCase.java @@ -0,0 +1,5 @@ +package com.smore.payment.payment.application.port.in; + +public interface ApprovePaymentUseCase { + ApprovePaymentResult approve(ApprovePaymentCommand command); +} diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/in/RefundPaymentUseCase.java b/payment/src/main/java/com/smore/payment/payment/application/port/in/RefundPaymentUseCase.java new file mode 100644 index 00000000..f14cf75d --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/in/RefundPaymentUseCase.java @@ -0,0 +1,7 @@ +package com.smore.payment.payment.application.port.in; + +import com.smore.payment.payment.application.event.inbound.PaymentRefundEvent; + +public interface RefundPaymentUseCase { + void refund(PaymentRefundEvent event); +} diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/in/SettlePaymentUseCase.java b/payment/src/main/java/com/smore/payment/payment/application/port/in/SettlePaymentUseCase.java new file mode 100644 index 00000000..d0b24ade --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/in/SettlePaymentUseCase.java @@ -0,0 +1,7 @@ +package com.smore.payment.payment.application.port.in; + +import com.smore.payment.payment.application.event.inbound.PaymentSettlementRequestEvent; + +public interface SettlePaymentUseCase { + void settle(PaymentSettlementRequestEvent event); +} diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/out/ApiInboxPort.java b/payment/src/main/java/com/smore/payment/payment/application/port/out/ApiInboxPort.java new file mode 100644 index 00000000..ba3dd7d2 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/out/ApiInboxPort.java @@ -0,0 +1,12 @@ +package com.smore.payment.payment.application.port.out; + +import com.smore.payment.payment.application.port.in.ApprovePaymentResult; + +import java.util.Optional; + +public interface ApiInboxPort { + + Optional find(String key); + + void save(String key, ApprovePaymentResult result); +} diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/out/OutboxPort.java b/payment/src/main/java/com/smore/payment/payment/application/port/out/OutboxPort.java new file mode 100644 index 00000000..1a0b0132 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/out/OutboxPort.java @@ -0,0 +1,7 @@ +package com.smore.payment.payment.application.port.out; + +import com.smore.payment.shared.outbox.OutboxMessage; + +public interface OutboxPort { + void save(OutboxMessage outboxMessage); +} diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/out/PaymentAuditLogPort.java b/payment/src/main/java/com/smore/payment/payment/application/port/out/PaymentAuditLogPort.java new file mode 100644 index 00000000..527e65de --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/out/PaymentAuditLogPort.java @@ -0,0 +1,8 @@ +package com.smore.payment.payment.application.port.out; + +import com.smore.payment.payment.infrastructure.persistence.mongo.model.PaymentAuditLog; + +public interface PaymentAuditLogPort { + + void save(PaymentAuditLog auditLog); +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/repository/PaymentRepository.java b/payment/src/main/java/com/smore/payment/payment/application/port/out/PaymentRepository.java similarity index 78% rename from payment/src/main/java/com/smore/payment/payment/domain/repository/PaymentRepository.java rename to payment/src/main/java/com/smore/payment/payment/application/port/out/PaymentRepository.java index 869fc2e1..49dc6b00 100644 --- a/payment/src/main/java/com/smore/payment/payment/domain/repository/PaymentRepository.java +++ b/payment/src/main/java/com/smore/payment/payment/application/port/out/PaymentRepository.java @@ -1,13 +1,11 @@ -package com.smore.payment.payment.domain.repository; +package com.smore.payment.payment.application.port.out; import com.smore.payment.payment.domain.model.Payment; import com.smore.payment.payment.domain.model.PaymentRefund; -import org.springframework.stereotype.Repository; import java.util.Optional; import java.util.UUID; -@Repository public interface PaymentRepository { void save(Payment payment); diff --git a/payment/src/main/java/com/smore/payment/payment/domain/repository/SellerSettlementLedgerRepository.java b/payment/src/main/java/com/smore/payment/payment/application/port/out/SellerSettlementLedgerRepository.java similarity index 87% rename from payment/src/main/java/com/smore/payment/payment/domain/repository/SellerSettlementLedgerRepository.java rename to payment/src/main/java/com/smore/payment/payment/application/port/out/SellerSettlementLedgerRepository.java index d48ce415..abac3df2 100644 --- a/payment/src/main/java/com/smore/payment/payment/domain/repository/SellerSettlementLedgerRepository.java +++ b/payment/src/main/java/com/smore/payment/payment/application/port/out/SellerSettlementLedgerRepository.java @@ -1,4 +1,4 @@ -package com.smore.payment.payment.domain.repository; +package com.smore.payment.payment.application.port.out; import java.math.BigDecimal; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/payment/application/port/out/TemporaryPaymentPort.java b/payment/src/main/java/com/smore/payment/payment/application/port/out/TemporaryPaymentPort.java new file mode 100644 index 00000000..e9aeda20 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/application/port/out/TemporaryPaymentPort.java @@ -0,0 +1,17 @@ +package com.smore.payment.payment.application.port.out; + +import com.smore.payment.payment.domain.model.TemporaryPayment; +import java.util.Optional; +import java.util.UUID; + +public interface TemporaryPaymentPort { + Optional findByOrderId(UUID uuid); + + void deleteByOrderId(UUID orderId); + + void save(TemporaryPayment temp); + + void update(TemporaryPayment temp); + + boolean existsByOrderId(UUID orderId); +} diff --git a/payment/src/main/java/com/smore/payment/payment/domain/document/PgApproveLog.java b/payment/src/main/java/com/smore/payment/payment/domain/document/PgApproveLog.java deleted file mode 100644 index 3df1184b..00000000 --- a/payment/src/main/java/com/smore/payment/payment/domain/document/PgApproveLog.java +++ /dev/null @@ -1,18 +0,0 @@ -//package com.smore.payment.payment.domain.document; -// -//import lombok.AllArgsConstructor; -//import lombok.Builder; -//import lombok.Getter; -//import lombok.NoArgsConstructor; -//import org.springframework.data.mongodb.core.mapping.Document; -// -//@Document("payment_logs") -//@Getter -//@NoArgsConstructor -//@AllArgsConstructor -//@Builder -//public class PgApproveLog { -// -// -//} -// diff --git a/payment/src/main/java/com/smore/payment/payment/domain/event/PaymentApprovedEvent.java b/payment/src/main/java/com/smore/payment/payment/domain/event/PaymentApprovedEvent.java new file mode 100644 index 00000000..4350780a --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/event/PaymentApprovedEvent.java @@ -0,0 +1,14 @@ +package com.smore.payment.payment.domain.event; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +public record PaymentApprovedEvent( + UUID orderId, + UUID paymentId, + BigDecimal amount, + LocalDateTime approvedAt, + UUID idempotencyKey +) { +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/application/event/outbound/SettlementCalculatedEvent.java b/payment/src/main/java/com/smore/payment/payment/domain/event/SettlementCalculatedEvent.java similarity index 73% rename from payment/src/main/java/com/smore/payment/payment/application/event/outbound/SettlementCalculatedEvent.java rename to payment/src/main/java/com/smore/payment/payment/domain/event/SettlementCalculatedEvent.java index 258fcb37..d090e5ea 100644 --- a/payment/src/main/java/com/smore/payment/payment/application/event/outbound/SettlementCalculatedEvent.java +++ b/payment/src/main/java/com/smore/payment/payment/domain/event/SettlementCalculatedEvent.java @@ -1,4 +1,4 @@ -package com.smore.payment.payment.application.event.outbound; +package com.smore.payment.payment.domain.event; import java.math.BigDecimal; import java.util.UUID; @@ -7,4 +7,5 @@ public record SettlementCalculatedEvent( Long sellerId, BigDecimal settlementAmount, UUID idempotencyKey -) {} +) { +} diff --git a/payment/src/main/java/com/smore/payment/payment/domain/model/Payment.java b/payment/src/main/java/com/smore/payment/payment/domain/model/Payment.java index a6ca61a0..1c3e76a7 100644 --- a/payment/src/main/java/com/smore/payment/payment/domain/model/Payment.java +++ b/payment/src/main/java/com/smore/payment/payment/domain/model/Payment.java @@ -1,5 +1,8 @@ package com.smore.payment.payment.domain.model; +import com.smore.payment.payment.domain.event.PaymentApprovedEvent; +import com.smore.payment.payment.domain.event.SettlementCalculatedEvent; + import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.UUID; @@ -254,11 +257,26 @@ public static Payment reconstruct( public UUID getCategoryId() { return categoryId; } public String getAuctionType() { return auctionType; } - public void updateRefund(BigDecimal refundAmount, LocalDateTime refundedAt) { + public void updateRefund(String reason, BigDecimal refundAmount, LocalDateTime refundedAt, String cancelTransactionKey, BigDecimal refundableAmount, String status) { + this.refund = new PaymentRefund(reason, refundAmount, refundedAt, cancelTransactionKey, refundableAmount); + this.status = PaymentStatus.of(status); + } + public PaymentApprovedEvent createApprovedEvent() { + return new PaymentApprovedEvent( + orderId, + id, + amount, + approvedAt, + idempotencyKey + ); } - public void updateRefund(String reason, BigDecimal refundAmount, LocalDateTime refundedAt, String cancelTransactionKey, BigDecimal refundableAmount) { - this.refund = new PaymentRefund(reason, refundAmount, refundedAt, cancelTransactionKey, refundableAmount); + public SettlementCalculatedEvent createSettlementCalculatedEvent(BigDecimal settlementAmount) { + return new SettlementCalculatedEvent( + sellerId, + settlementAmount, + idempotencyKey + ); } } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/model/TemporaryPayment.java b/payment/src/main/java/com/smore/payment/payment/domain/model/TemporaryPayment.java similarity index 87% rename from payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/model/TemporaryPayment.java rename to payment/src/main/java/com/smore/payment/payment/domain/model/TemporaryPayment.java index 3f9e9e2a..c97875c5 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/model/TemporaryPayment.java +++ b/payment/src/main/java/com/smore/payment/payment/domain/model/TemporaryPayment.java @@ -1,4 +1,4 @@ -package com.smore.payment.payment.infrastructure.persistence.redis.model; +package com.smore.payment.payment.domain.model; import lombok.AllArgsConstructor; import lombok.Getter; @@ -23,6 +23,7 @@ public class TemporaryPayment { private UUID categoryId; private String auctionType; private LocalDateTime expiredAt; + private PgResponseResult pgResponseResult; public static TemporaryPayment create( UUID idempotencyKey, @@ -42,7 +43,8 @@ public static TemporaryPayment create( sellerId, categoryId, auctionType, - expiredAt + expiredAt, + null ); } @@ -51,7 +53,6 @@ public void validateApproval(BigDecimal requestAmount) { throw new IllegalArgumentException("요청 금액이 null입니다."); } - // BigDecimal은 equals 대신 compareTo 사용 권장 if (this.amount.compareTo(requestAmount) != 0) { throw new IllegalArgumentException( String.format("결제 금액이 일치하지 않습니다. 예상: %s, 요청: %s", @@ -63,4 +64,8 @@ public void validateApproval(BigDecimal requestAmount) { throw new IllegalStateException("결제 승인 시간이 만료되었습니다."); } } + + public boolean hasPgApprovalResult() { + return pgResponseResult != null; + } } \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/repository/MongoRepository.java b/payment/src/main/java/com/smore/payment/payment/domain/repository/MongoRepository.java deleted file mode 100644 index fa9b967c..00000000 --- a/payment/src/main/java/com/smore/payment/payment/domain/repository/MongoRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.smore.payment.payment.domain.repository; - -//import com.smore.payment.payment.domain.document.PgApproveLog; -import org.springframework.stereotype.Repository; - -@Repository -public interface MongoRepository { - -// void savePgApproveLog(PgApproveLog pgApproveLog); -} diff --git a/payment/src/main/java/com/smore/payment/payment/domain/repository/OutboxRepository.java b/payment/src/main/java/com/smore/payment/payment/domain/repository/OutboxRepository.java deleted file mode 100644 index 63dd9ebd..00000000 --- a/payment/src/main/java/com/smore/payment/payment/domain/repository/OutboxRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.smore.payment.payment.domain.repository; - -import com.smore.payment.global.outbox.OutboxMessage; -import org.springframework.stereotype.Repository; - -@Repository -public interface OutboxRepository { - void save(OutboxMessage outboxMessage); -} diff --git a/payment/src/main/java/com/smore/payment/payment/domain/repository/RedisRepository.java b/payment/src/main/java/com/smore/payment/payment/domain/repository/RedisRepository.java deleted file mode 100644 index a2d9e814..00000000 --- a/payment/src/main/java/com/smore/payment/payment/domain/repository/RedisRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.smore.payment.payment.domain.repository; - -import com.smore.payment.payment.infrastructure.persistence.redis.model.TemporaryPayment; -import org.springframework.stereotype.Repository; - -import java.util.Optional; -import java.util.UUID; - -@Repository -public interface RedisRepository { - Optional findByOrderId(UUID uuid); - - void deleteByOrderId(UUID orderId); - - void save(TemporaryPayment temp); - - boolean existsByOrderId(UUID orderId); -} diff --git a/payment/src/main/java/com/smore/payment/payment/domain/service/FeeCalculator.java b/payment/src/main/java/com/smore/payment/payment/domain/service/FeeCalculator.java new file mode 100644 index 00000000..38aca624 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/service/FeeCalculator.java @@ -0,0 +1,23 @@ +package com.smore.payment.payment.domain.service; + +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; + +@Component +public class FeeCalculator { + + public BigDecimal calculateSettlementAmount(BigDecimal approvedAmount, FeePolicyDecision decision) { + BigDecimal fee; + + switch (decision.feeType()) { + case "RATE" -> fee = approvedAmount.multiply(decision.rate()); + case "FIXED" -> fee = decision.fixedAmount(); + case "MIXED" -> fee = approvedAmount.multiply(decision.rate()) + .add(decision.fixedAmount()); + default -> throw new IllegalArgumentException("Unknown feeType: " + decision.feeType()); + } + + return approvedAmount.subtract(fee); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/service/FeePolicyDecision.java b/payment/src/main/java/com/smore/payment/payment/domain/service/FeePolicyDecision.java new file mode 100644 index 00000000..553ec08a --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/service/FeePolicyDecision.java @@ -0,0 +1,10 @@ +package com.smore.payment.payment.domain.service; + +import java.math.BigDecimal; + +public record FeePolicyDecision( + String feeType, + BigDecimal rate, + BigDecimal fixedAmount +) { +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/service/RefundCalculator.java b/payment/src/main/java/com/smore/payment/payment/domain/service/RefundCalculator.java new file mode 100644 index 00000000..42831085 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/service/RefundCalculator.java @@ -0,0 +1,34 @@ +package com.smore.payment.payment.domain.service; + +import com.smore.payment.payment.application.event.inbound.PaymentRefundEvent; +import com.smore.payment.payment.application.facade.dto.CancelPolicyResult; +import com.smore.payment.payment.application.facade.dto.RefundPolicyResult; +import com.smore.payment.payment.domain.model.Payment; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +public class RefundCalculator { + + public RefundDecision decide(Payment payment, PaymentRefundEvent event, + CancelPolicyResult cancelResult, RefundPolicyResult refundResult) { + + boolean cancelAvailable = cancelResult.cancellable(payment.getApprovedAt(), event.publishedAt()); + if (cancelAvailable) { + BigDecimal refundAmount = cancelResult.calculateCancelFee(event.refundAmount()); + return RefundDecision.success(refundAmount); + } + + if (refundResult == null) { + return RefundDecision.failure("환불 정책을 찾을 수 없습니다."); + } + + if (!refundResult.refundable(payment.getApprovedAt(), event.publishedAt())) { + return RefundDecision.failure("환불이 불가능 한 상품입니다."); + } + + BigDecimal refundAmount = refundResult.calculateRefundFee(event.refundAmount()); + return RefundDecision.success(refundAmount); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/service/RefundDecision.java b/payment/src/main/java/com/smore/payment/payment/domain/service/RefundDecision.java new file mode 100644 index 00000000..43b3564c --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/service/RefundDecision.java @@ -0,0 +1,14 @@ +package com.smore.payment.payment.domain.service; + +import java.math.BigDecimal; + +public record RefundDecision(boolean refundable, BigDecimal refundAmount, String failureReason) { + + public static RefundDecision success(BigDecimal refundAmount) { + return new RefundDecision(true, refundAmount, null); + } + + public static RefundDecision failure(String failureReason) { + return new RefundDecision(false, null, failureReason); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementAmountCalculator.java b/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementAmountCalculator.java new file mode 100644 index 00000000..8403ef58 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementAmountCalculator.java @@ -0,0 +1,24 @@ +package com.smore.payment.payment.domain.service; + +import com.smore.payment.payment.application.facade.dto.FeePolicyResult; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +public class SettlementAmountCalculator { + + public BigDecimal calculate(BigDecimal approvedAmount, FeePolicyResult policy) { + BigDecimal fee; + + switch (policy.feeType()) { + case "RATE" -> fee = approvedAmount.multiply(policy.rate()); + case "FIXED" -> fee = policy.fixedAmount(); + case "MIXED" -> fee = approvedAmount.multiply(policy.rate()) + .add(policy.fixedAmount()); + default -> throw new IllegalArgumentException("Unknown feeType: " + policy.feeType()); + } + + return approvedAmount.subtract(fee); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementValidationResult.java b/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementValidationResult.java new file mode 100644 index 00000000..019ec4c6 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementValidationResult.java @@ -0,0 +1,5 @@ +package com.smore.payment.payment.domain.service; + +public record SettlementValidationResult(boolean valid, boolean duplicated, String failureReason) { + +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementValidationService.java b/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementValidationService.java new file mode 100644 index 00000000..c9b7e8f7 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/domain/service/SettlementValidationService.java @@ -0,0 +1,37 @@ +package com.smore.payment.payment.domain.service; + +import com.smore.payment.payment.application.event.inbound.PaymentSettlementRequestEvent; +import com.smore.payment.payment.application.port.out.SellerSettlementLedgerRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +@RequiredArgsConstructor +public class SettlementValidationService { + + private final SellerSettlementLedgerRepository sellerSettlementLedgerRepository; + + public SettlementValidationResult validate(PaymentSettlementRequestEvent event) { + if (sellerSettlementLedgerRepository.existsByIdempotencyKey(event.idempotencyKey())) { + return new SettlementValidationResult(false, true, null); + } + + if (event.accountNumber() == null || event.accountNumber().isBlank()) { + return new SettlementValidationResult(false, false, "계좌 번호가 잘못 입력되었습니다."); + } + + if (event.amount() == null || event.amount().compareTo(BigDecimal.ZERO) <= 0) { + return new SettlementValidationResult(false, false, "정산 금액이 올바르지 않습니다."); + } + + BigDecimal balance = sellerSettlementLedgerRepository.calculateBalance(event.userId()); + if (balance.compareTo(event.amount()) < 0 + || balance.compareTo(BigDecimal.valueOf(1_000_000_000L)) > 0) { + return new SettlementValidationResult(false, false, "출금 가능 잔액이 부족합니다."); + } + + return new SettlementValidationResult(true, false, null); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/api_inbox/ApiInboxRepository.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/api_inbox/ApiInboxRepository.java new file mode 100644 index 00000000..57915192 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/api_inbox/ApiInboxRepository.java @@ -0,0 +1,30 @@ +package com.smore.payment.payment.infrastructure.api_inbox; + +import com.smore.payment.payment.application.port.in.ApprovePaymentResult; +import com.smore.payment.payment.application.port.out.ApiInboxPort; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class ApiInboxRepository implements ApiInboxPort { + + private final RedisTemplate redisTemplate; + private static final Duration TTL = Duration.ofMinutes(30); + + public Optional find(String key) { + return Optional.ofNullable(redisTemplate.opsForValue().get(keyOf(key))); + } + + public void save(String key, ApprovePaymentResult result) { + redisTemplate.opsForValue().set(keyOf(key), result, TTL); + } + + private String keyOf(String idempotencyKey) { + return "api-inbox:payment:approve:" + idempotencyKey; + } +} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/KafkaConfig.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/KafkaConfig.java index 10ea965f..c6db3f0f 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/KafkaConfig.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/KafkaConfig.java @@ -32,6 +32,9 @@ public class KafkaConfig { @Value("${topic.order.refund-fail}") private String orderRefundFailTopic; + @Value("${topic.order.refund-dlt}") + private String orderRefundDltTopic; + @Value("${topic.seller.success}") private String sellerSuccessTopic; @@ -88,6 +91,14 @@ public NewTopic paymentRefundFailTopic() { .build(); } + @Bean + public NewTopic paymentRefundDltTopic() { + return TopicBuilder.name(orderRefundDltTopic) + .partitions(3) + .replicas(3) + .build(); + } + @Bean public NewTopic sellerSuccessTopic() { return TopicBuilder.name(sellerSuccessTopic) diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisConfig.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisConfig.java index 5be4ee60..52b8d2ae 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisConfig.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisConfig.java @@ -3,7 +3,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.smore.payment.payment.infrastructure.persistence.redis.model.TemporaryPayment; +import com.smore.payment.payment.application.port.in.ApprovePaymentResult; +import com.smore.payment.payment.domain.model.TemporaryPayment; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -40,4 +41,28 @@ public RedisTemplate temporaryPaymentRedisTemplate( template.afterPropertiesSet(); return template; } + + @Bean + public RedisTemplate approvePaymentResultRedisTemplate( + RedisConnectionFactory connectionFactory) { + + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + Jackson2JsonRedisSerializer serializer = + new Jackson2JsonRedisSerializer<>(objectMapper, ApprovePaymentResult.class); + + template.setKeySerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + + template.setValueSerializer(serializer); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisKeyExpirationListenerConfig.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisKeyExpirationListenerConfig.java index 1b87a02b..f2dbe648 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisKeyExpirationListenerConfig.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/RedisKeyExpirationListenerConfig.java @@ -1,6 +1,6 @@ package com.smore.payment.payment.infrastructure.config; -import com.smore.payment.payment.infrastructure.persistence.redis.RedisKeyExpirationListener; +import com.smore.payment.payment.infrastructure.redis.RedisKeyExpirationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/WebClientConfig.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/WebClientConfig.java index e07b7583..f35cd210 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/config/WebClientConfig.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/config/WebClientConfig.java @@ -21,7 +21,7 @@ public class WebClientConfig { @Bean public WebClient tossApproveWebClient() { String encoded = Base64.getEncoder() - .encodeToString(("test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6:") + .encodeToString((pgSecretKey) .getBytes(StandardCharsets.UTF_8)); return WebClient.builder() @@ -35,7 +35,7 @@ public WebClient tossApproveWebClient() { public WebClient tossFailWebClient() { String encoded = Base64.getEncoder() - .encodeToString(("test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6:") + .encodeToString((pgSecretKey) .getBytes(StandardCharsets.UTF_8)); return WebClient.builder() diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentApprovedOutboxPublisher.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentApprovedOutboxPublisher.java index 2f618ce4..b80e74b4 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentApprovedOutboxPublisher.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentApprovedOutboxPublisher.java @@ -1,8 +1,8 @@ package com.smore.payment.payment.infrastructure.kafka; -import com.smore.payment.global.outbox.OutboxStatus; -import com.smore.payment.payment.infrastructure.persistence.jpa.model.outbox.OutboxEntity; -import com.smore.payment.payment.infrastructure.persistence.jpa.repository.outbox.OutboxJpaRepository; +import com.smore.payment.shared.outbox.OutboxStatus; +import com.smore.payment.payment.infrastructure.persistence.outbox.OutboxEntity; +import com.smore.payment.payment.infrastructure.persistence.outbox.OutboxJpaRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -20,7 +20,6 @@ public class PaymentApprovedOutboxPublisher { private final MessageBrokerPublisher messageBrokerPublisher; @Scheduled(fixedDelay = 5000) - @Transactional public void publish() { log.info("이벤트 발행 시작"); List pendingMessages = outboxJpaRepository.findTop50ByStatusOrderByCreatedAtAsc(OutboxStatus.PENDING); @@ -29,10 +28,9 @@ public void publish() { } @Scheduled(fixedDelay = 30000) - @Transactional public void retry() { log.info("이벤트 재전송 시작"); - List pendingMessages = outboxJpaRepository.findTop50ByStatusOrderByCreatedAtAsc(OutboxStatus.FAILED); + List pendingMessages = outboxJpaRepository.findTop50ByStatusAndRetryCountGreaterThanOrderByCreatedAtAsc(OutboxStatus.FAILED, 0); log.info("재전송 아웃박스 찾아오기 {}", pendingMessages.size()); send(pendingMessages); } @@ -51,8 +49,10 @@ private void send(List pendingMessages) { } catch (Exception e) { - if (message.getRetryCount() <= 1) { + if (message.getRetryCount() < 1) { message.markAsFailed(); + message.resetRetryCount(); + log.warn("out box 재전송 실패 : {}", message.getId()); } else { message.decreaseRetryCount(); } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRefundEventConsumer.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRefundEventConsumer.java index 8c71d67b..e0f9f3b1 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRefundEventConsumer.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRefundEventConsumer.java @@ -2,11 +2,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.smore.payment.payment.application.PaymentRefundService; import com.smore.payment.payment.application.event.inbound.PaymentRefundEvent; -import com.smore.payment.payment.application.event.inbound.PaymentRequestedEvent; -import com.smore.payment.payment.infrastructure.kafka.dto.PaymentCreateRequestEvent; +import com.smore.payment.payment.application.port.in.RefundPaymentUseCase; import com.smore.payment.payment.infrastructure.kafka.dto.PaymentRefundRequestEvent; +import com.smore.payment.payment.infrastructure.persistence.inbox.RefundInboxProcessor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.kafka.annotation.KafkaListener; @@ -14,23 +13,35 @@ import org.springframework.stereotype.Component; import java.math.BigDecimal; -import java.time.LocalDateTime; @Slf4j @Component @RequiredArgsConstructor public class PaymentRefundEventConsumer { - private final PaymentRefundService paymentRefundService; + private final RefundInboxProcessor refundInboxProcessor; + private final RefundPaymentUseCase paymentRefundService; private final ObjectMapper objectMapper; - @KafkaListener(topics = "order.refund.v1") + @KafkaListener( + topics = "order.refund.v1", + groupId = "consumer.refund.group", + concurrency = "3" + ) public void handle(String message, Acknowledgment ack) throws JsonProcessingException { PaymentRefundRequestEvent event = objectMapper.readValue(message, PaymentRefundRequestEvent.class); log.info("PaymentRefundRequestEvent 수신: orderId={}, amount={}", event.getOrderId(), event.getRefundAmount()); + + boolean start = refundInboxProcessor.startOrSkip(event); + if (!start) { + // 이미 완료 된 이벤트 또는 처리 중인 이벤트 + ack.acknowledge(); + return; + } + PaymentRefundEvent paymentRequestedEvent = PaymentRefundEvent.of( event.getOrderId(), event.getUserId(), @@ -43,6 +54,9 @@ public void handle(String message, Acknowledgment ack) throws JsonProcessingExce ); paymentRefundService.refund(paymentRequestedEvent); + ack.acknowledge(); } + + } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRequestedEventConsumer.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRequestedEventConsumer.java index 9893cd83..b3227ea9 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRequestedEventConsumer.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentRequestedEventConsumer.java @@ -22,7 +22,11 @@ public class PaymentRequestedEventConsumer { private final CreateTemporaryPaymentService createTemporaryPaymentService; private final ObjectMapper objectMapper; - @KafkaListener(topics = "order.created.v1") + @KafkaListener( + topics = "order.created.v1", + groupId = "consumer.payment.group", + concurrency = "3" + ) public void handle(String message, Acknowledgment ack) throws JsonProcessingException { PaymentCreateRequestEvent event = objectMapper.readValue(message, PaymentCreateRequestEvent.class); diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentSettlementEventConsumer.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentSettlementEventConsumer.java index 3bb66745..15293f92 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentSettlementEventConsumer.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/PaymentSettlementEventConsumer.java @@ -2,8 +2,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.smore.payment.payment.application.PaymentSettlementService; import com.smore.payment.payment.application.event.inbound.PaymentSettlementRequestEvent; +import com.smore.payment.payment.application.port.in.SettlePaymentUseCase; import com.smore.payment.payment.infrastructure.kafka.dto.SettlementRequestEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,10 +16,14 @@ @RequiredArgsConstructor public class PaymentSettlementEventConsumer { - private final PaymentSettlementService paymentSettlementService; + private final SettlePaymentUseCase paymentSettlementService; private final ObjectMapper objectMapper; - @KafkaListener(topics = "seller.settlement.v1") + @KafkaListener( + topics = "seller.settlement.v1", + groupId = "consumer.settlement.group", + concurrency = "3" + ) public void handle(String message, Acknowledgment ack) throws JsonProcessingException { SettlementRequestEvent event = objectMapper.readValue(message, SettlementRequestEvent.class); @@ -33,7 +37,7 @@ public void handle(String message, Acknowledgment ack) throws JsonProcessingExce event.getCreatedAt() ); - paymentSettlementService.settlement(paymentSettlementRequestEvent); + paymentSettlementService.settle(paymentSettlementRequestEvent); ack.acknowledge(); } } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentCreateRequestEvent.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentCreateRequestEvent.java index 347d4b50..845944c1 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentCreateRequestEvent.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentCreateRequestEvent.java @@ -1,21 +1,23 @@ package com.smore.payment.payment.infrastructure.kafka.dto; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import java.time.LocalDateTime; import java.util.UUID; @Getter -@AllArgsConstructor +@Setter +@NoArgsConstructor public class PaymentCreateRequestEvent { - private final UUID orderId; - private final Long userId; - private final Integer totalAmount; - private final UUID categoryId; - private final String saleType; - private final Long sellerId; - private final UUID idempotencyKey; - private final LocalDateTime publishedAt; - private final LocalDateTime expiresAt; + private UUID orderId; + private Long userId; + private Integer totalAmount; + private UUID categoryId; + private String saleType; + private Long sellerId; + private UUID idempotencyKey; + private LocalDateTime publishedAt; + private LocalDateTime expiresAt; } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentRefundRequestEvent.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentRefundRequestEvent.java index 2ca5e097..e88051ba 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentRefundRequestEvent.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/PaymentRefundRequestEvent.java @@ -1,20 +1,22 @@ package com.smore.payment.payment.infrastructure.kafka.dto; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import java.time.LocalDateTime; import java.util.UUID; @Getter -@AllArgsConstructor +@Setter +@NoArgsConstructor public class PaymentRefundRequestEvent { - private final UUID orderId; - private final Long userId; - private final UUID refundId; - private final UUID paymentId; - private final Integer refundAmount; - private final UUID idempotencyKey; - private final String reason; - private final LocalDateTime publishedAt; + private UUID orderId; + private Long userId; + private UUID refundId; + private UUID paymentId; + private Integer refundAmount; + private UUID idempotencyKey; + private String reason; + private LocalDateTime publishedAt; } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/SettlementRequestEvent.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/SettlementRequestEvent.java index 40afa6c7..593ea3c0 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/SettlementRequestEvent.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/kafka/dto/SettlementRequestEvent.java @@ -1,19 +1,21 @@ package com.smore.payment.payment.infrastructure.kafka.dto; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.UUID; @Getter -@AllArgsConstructor +@Setter +@NoArgsConstructor public class SettlementRequestEvent { - private final Long userId; - private final BigDecimal amount; - private final String accountNumber; - private final UUID idempotencyKey; - private final LocalDateTime createdAt; + private Long userId; + private BigDecimal amount; + private String accountNumber; + private UUID idempotencyKey; + private LocalDateTime createdAt; } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInbox.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInbox.java new file mode 100644 index 00000000..c64c009e --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInbox.java @@ -0,0 +1,124 @@ +package com.smore.payment.payment.infrastructure.persistence.inbox; + +import com.smore.payment.payment.infrastructure.kafka.dto.PaymentRefundRequestEvent; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table( + name = "refund_inbox", + uniqueConstraints = { + @UniqueConstraint( + name = "uk_refund_inbox_refund_id", + columnNames = "refundId" + ) + } +) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RefundInbox { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Version + private Long version; + + @Column(nullable = false) + private UUID refundId; + + @Column(nullable = false) + private UUID orderId; + + @Column(nullable = false) + private UUID paymentId; + + @Column + private UUID idempotencyKey; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private RefundInboxStatus status; + + @Column(nullable = false) + private int retryCount; + + private String lastError; + + private String pgRefundKey; + + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + + public static RefundInbox create(PaymentRefundRequestEvent event) { + RefundInbox inbox = new RefundInbox(); + inbox.refundId = event.getRefundId(); + inbox.orderId = event.getOrderId(); + inbox.paymentId = event.getPaymentId(); + inbox.idempotencyKey = event.getIdempotencyKey() == null ? null : event.getIdempotencyKey(); + inbox.status = RefundInboxStatus.RECEIVED; + inbox.retryCount = 0; + inbox.createdAt = LocalDateTime.now(); + inbox.updatedAt = LocalDateTime.now(); + return inbox; + } + + public void markProcessing() { + if (this.status == RefundInboxStatus.FINALIZED) return; + if (this.status == RefundInboxStatus.PROCESSING) return; // 이미 누가 잡음 + this.status = RefundInboxStatus.PROCESSING; + setUpdatedAt(); + } + + public void markPolicyPassed() { + this.status = RefundInboxStatus.POLICY_PASSED; + setUpdatedAt(); + } + + public void markPgRequested() { + this.status = RefundInboxStatus.PG_REQUESTED; + setUpdatedAt(); + } + + public void markPgSucceeded(String pgRefundKey) { + this.status = RefundInboxStatus.PG_SUCCEEDED; + this.pgRefundKey = pgRefundKey; + setUpdatedAt(); + } + + public void markFinalized() { + this.status = RefundInboxStatus.FINALIZED; + setUpdatedAt(); + } + + public void markFailed(String error) { + this.status = RefundInboxStatus.FAILED; + this.lastError = error; + setUpdatedAt(); + } + + public void increaseRetry(String error) { + this.retryCount++; + this.lastError = error; + setUpdatedAt(); + } + + public boolean isFinalized() { + return this.status == RefundInboxStatus.FINALIZED; + } + + public boolean isProcessing() { + return this.status == RefundInboxStatus.PROCESSING; + } + + private void setUpdatedAt() { + this.updatedAt = LocalDateTime.now(); + } +} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxProcessor.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxProcessor.java new file mode 100644 index 00000000..9b816b29 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxProcessor.java @@ -0,0 +1,88 @@ +package com.smore.payment.payment.infrastructure.persistence.inbox; + +import com.smore.payment.payment.application.event.outbound.PaymentRefundFailedEvent; +import com.smore.payment.payment.application.port.out.OutboxPort; +import com.smore.payment.payment.infrastructure.kafka.dto.PaymentRefundRequestEvent; +import com.smore.payment.shared.outbox.OutboxMessageCreator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; + +@Slf4j +@Service +@RequiredArgsConstructor +public class RefundInboxProcessor { + + private final RefundInboxRepository refundInboxRepository; + private final OutboxPort outboxPort; + private final OutboxMessageCreator outboxMessageCreator; + + private static final Integer MAX_RETRY = 3; + + + @Transactional + public boolean startOrSkip(PaymentRefundRequestEvent event) { + RefundInbox inbox = refundInboxRepository.findByRefundId(event.getRefundId()) + .orElseGet(() -> { + try { + return refundInboxRepository.save(RefundInbox.create(event)); + } catch (DataIntegrityViolationException e) { + return refundInboxRepository.findByRefundId(event.getRefundId()) + .orElseThrow(() -> e); + } + }); + + // 이미 완료면 처리할 필요 없음 + if (inbox.isFinalized()) { + return false; + } + + // 이미 누가 처리 중이면 skip + if (inbox.isProcessing()) { + return false; + } + + if (inbox.getRetryCount() >= MAX_RETRY) { + log.error("재시도 횟수 한계. refundId={}", event.getRefundId()); + + publishRefundDlt(event, inbox); + return false; + } + + try { + inbox.markProcessing(); + refundInboxRepository.save(inbox); + return true; + } catch (ObjectOptimisticLockingFailureException e) { + return false; + } + } + + private void publishRefundDlt(PaymentRefundRequestEvent event, RefundInbox inbox) { + + String reason = String.format( + "환불 자동 재시도 한계 초과 (retryCount=%d)", + inbox.getRetryCount() + ); + + + outboxPort.save( + outboxMessageCreator.refundDlt( + PaymentRefundFailedEvent.of( + event.getOrderId(), + event.getRefundId(), + BigDecimal.valueOf(event.getRefundAmount()), + reason + ) + ) + ); + + log.error("Refund moved to DLT. refundId={}, reason={}", + event.getRefundId(), reason); + } +} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxRepository.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxRepository.java new file mode 100644 index 00000000..766daa87 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxRepository.java @@ -0,0 +1,11 @@ +package com.smore.payment.payment.infrastructure.persistence.inbox; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface RefundInboxRepository extends JpaRepository { + + Optional findByRefundId(UUID refundId); +} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxStatus.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxStatus.java new file mode 100644 index 00000000..fabba44a --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/inbox/RefundInboxStatus.java @@ -0,0 +1,11 @@ +package com.smore.payment.payment.infrastructure.persistence.inbox; + +public enum RefundInboxStatus { + RECEIVED, + PROCESSING, + POLICY_PASSED, + PG_REQUESTED, + PG_SUCCEEDED, + FINALIZED, + FAILED +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/model/payment/PaymentEntity.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/model/payment/PaymentEntity.java index 89c1440a..8ecc0b6c 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/model/payment/PaymentEntity.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/model/payment/PaymentEntity.java @@ -13,7 +13,12 @@ import java.util.UUID; @Entity -@Table(name = "payments") +@Table( + name = "payments", + uniqueConstraints = { + @UniqueConstraint(name = "uk_payments_idempotency_key", columnNames = "idempotency_key") + } +) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class PaymentEntity { diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/outbox/OutboxRepositoryImpl.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/outbox/OutboxRepositoryImpl.java deleted file mode 100644 index 6dfe8415..00000000 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/outbox/OutboxRepositoryImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.smore.payment.payment.infrastructure.persistence.jpa.repository.outbox; - -import com.smore.payment.global.outbox.OutboxMessage; -import com.smore.payment.payment.domain.repository.OutboxRepository; -import com.smore.payment.payment.infrastructure.persistence.jpa.mapper.OutboxMapper; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class OutboxRepositoryImpl implements OutboxRepository { - - private final OutboxJpaRepository outboxJpaRepository; - private final OutboxMapper outboxMapper; - - @Override - public void save(OutboxMessage outboxMessage) { - outboxJpaRepository.save(outboxMapper.toEntity(outboxMessage)); - } -} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/payment/PaymentRepositoryImpl.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/payment/PaymentRepositoryImpl.java index e2dc9a31..87906b05 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/payment/PaymentRepositoryImpl.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/payment/PaymentRepositoryImpl.java @@ -2,11 +2,12 @@ import com.smore.payment.payment.domain.model.Payment; import com.smore.payment.payment.domain.model.PaymentRefund; -import com.smore.payment.payment.domain.repository.PaymentRepository; +import com.smore.payment.payment.application.port.out.PaymentRepository; import com.smore.payment.payment.infrastructure.persistence.jpa.mapper.PaymentMapper; import com.smore.payment.payment.infrastructure.persistence.jpa.model.payment.PaymentEntity; import com.smore.payment.payment.infrastructure.persistence.jpa.model.payment.PaymentRefundJpa; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -21,8 +22,14 @@ public class PaymentRepositoryImpl implements PaymentRepository { @Override public void save(Payment payment) { - paymentJpaRepository.save(paymentMapper.toEntity(payment)); - } + try { + paymentJpaRepository.save(paymentMapper.toEntity(payment)); + } catch (DataIntegrityViolationException e) { + throw new IllegalStateException( + "이미 처리된 결제 요청입니다. idempotencyKey=" + payment.getIdempotencyKey(), + e + ); + }} @Override public Optional findByIdempotencyKey(UUID idempotencyKey) { diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/PaymentAuditEventType.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/PaymentAuditEventType.java new file mode 100644 index 00000000..879dd96d --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/PaymentAuditEventType.java @@ -0,0 +1,17 @@ +package com.smore.payment.payment.infrastructure.persistence.mongo.model; + +public enum PaymentAuditEventType { + TEMPORARY_PAYMENT_CREATED, + PAYMENT_APPROVAL_REQUESTED, + PG_APPROVAL_REQUESTED, + PG_APPROVAL_SUCCEEDED, + PG_APPROVAL_FAILED, + PAYMENT_APPROVAL_SUCCEEDED, + PAYMENT_APPROVAL_FAILED, + PAYMENT_REFUND_REQUESTED, + PG_REFUND_REQUESTED, + PG_REFUND_SUCCEEDED, + PG_REFUND_FAILED, + PAYMENT_REFUND_SUCCEEDED, + PAYMENT_REFUND_FAILED +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/PaymentAuditLog.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/PaymentAuditLog.java new file mode 100644 index 00000000..94fe8dcd --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/PaymentAuditLog.java @@ -0,0 +1,55 @@ +package com.smore.payment.payment.infrastructure.persistence.mongo.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Document(collection = "payment_audit_logs") +public class PaymentAuditLog { + + @Id + private String id; + + @Indexed + private UUID orderId; + + @Indexed + private UUID paymentId; + + private UUID refundId; + + private UUID idempotencyKey; + + private Long userId; + + private Long sellerId; + + private UUID categoryId; + + private String auctionType; + + private BigDecimal amount; + + private String paymentKey; + + private String pgOrderId; + + private PaymentAuditEventType eventType; + + private String description; + + @Builder.Default + private LocalDateTime occurredAt = LocalDateTime.now(); +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/SellerSettlementLedger.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/SellerSettlementLedger.java index 8c4674a4..946030ed 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/SellerSettlementLedger.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/model/SellerSettlementLedger.java @@ -3,6 +3,7 @@ import lombok.Builder; import lombok.Getter; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import java.math.BigDecimal; @@ -25,6 +26,7 @@ public class SellerSettlementLedger { private UUID paymentId; // 결제 기반 발생이면 기록 (nullable) + @Indexed(unique = true) private UUID idempotencyKey; // 중복 요청 방지용 key private LocalDateTime timestamp; // 기록 시간 diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/MongoRepositoryImpl.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/MongoRepositoryImpl.java deleted file mode 100644 index 50bb4dad..00000000 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/MongoRepositoryImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.smore.payment.payment.infrastructure.persistence.mongo.repository; - -//import com.smore.payment.payment.domain.document.PgApproveLog; -import com.smore.payment.payment.domain.repository.MongoRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class MongoRepositoryImpl implements MongoRepository { - - -// @Override -// public void savePgApproveLog(PgApproveLog pgApproveLog) { -// paymentMongoRepository.save(pgApproveLog); -// } -} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentAuditLogRepository.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentAuditLogRepository.java new file mode 100644 index 00000000..7f4376c6 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentAuditLogRepository.java @@ -0,0 +1,7 @@ +package com.smore.payment.payment.infrastructure.persistence.mongo.repository; + +import com.smore.payment.payment.infrastructure.persistence.mongo.model.PaymentAuditLog; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface PaymentAuditLogRepository extends MongoRepository { +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentAuditLogRepositoryAdapter.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentAuditLogRepositoryAdapter.java new file mode 100644 index 00000000..48eb8ff0 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentAuditLogRepositoryAdapter.java @@ -0,0 +1,18 @@ +package com.smore.payment.payment.infrastructure.persistence.mongo.repository; + +import com.smore.payment.payment.application.port.out.PaymentAuditLogPort; +import com.smore.payment.payment.infrastructure.persistence.mongo.model.PaymentAuditLog; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class PaymentAuditLogRepositoryAdapter implements PaymentAuditLogPort { + + private final PaymentAuditLogRepository paymentAuditLogRepository; + + @Override + public void save(PaymentAuditLog auditLog) { + paymentAuditLogRepository.save(auditLog); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentMongoRepository.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentMongoRepository.java deleted file mode 100644 index 95265aac..00000000 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/PaymentMongoRepository.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.smore.payment.payment.infrastructure.persistence.mongo.repository; - - -public interface PaymentMongoRepository { -} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/SellerSettlementLedgerRepositoryImpl.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/SellerSettlementLedgerRepositoryImpl.java index 8b09e66e..a73fdf29 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/SellerSettlementLedgerRepositoryImpl.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/mongo/repository/SellerSettlementLedgerRepositoryImpl.java @@ -1,8 +1,9 @@ package com.smore.payment.payment.infrastructure.persistence.mongo.repository; -import com.smore.payment.payment.domain.repository.SellerSettlementLedgerRepository; +import com.smore.payment.payment.application.port.out.SellerSettlementLedgerRepository; import com.smore.payment.payment.infrastructure.persistence.mongo.model.SellerSettlementLedger; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Repository; import java.math.BigDecimal; @@ -43,6 +44,10 @@ public void saveLedger( .timestamp(LocalDateTime.now()) .build(); - sellerSettlementLedgerMongoRepository.save(ledger); + try { + sellerSettlementLedgerMongoRepository.save(ledger); + } catch (DuplicateKeyException e) { + // 동일한 idempotencyKey로 이미 기록된 경우 - 멱등성을 보장하기 위해 무시 + } } } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxAdapter.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxAdapter.java new file mode 100644 index 00000000..47fcca4d --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxAdapter.java @@ -0,0 +1,22 @@ +package com.smore.payment.payment.infrastructure.persistence.outbox; + +import com.smore.payment.shared.outbox.OutboxMessage; +import com.smore.payment.payment.application.port.out.OutboxPort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class OutboxAdapter implements OutboxPort { + + private final OutboxJpaRepository outboxJpaRepository; + private final OutboxMapper outboxMapper; + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void save(OutboxMessage outboxMessage) { + outboxJpaRepository.save(outboxMapper.toEntity(outboxMessage)); + } +} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/model/outbox/OutboxEntity.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxEntity.java similarity index 89% rename from payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/model/outbox/OutboxEntity.java rename to payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxEntity.java index 06292ed5..06d3ca50 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/model/outbox/OutboxEntity.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxEntity.java @@ -1,6 +1,6 @@ -package com.smore.payment.payment.infrastructure.persistence.jpa.model.outbox; +package com.smore.payment.payment.infrastructure.persistence.outbox; -import com.smore.payment.global.outbox.OutboxStatus; +import com.smore.payment.shared.outbox.OutboxStatus; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; @@ -69,4 +69,8 @@ public void markAsFailed() { public void decreaseRetryCount() { this.retryCount--; } + + public void resetRetryCount() { + this.retryCount = 0; + } } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/outbox/OutboxJpaRepository.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxJpaRepository.java similarity index 50% rename from payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/outbox/OutboxJpaRepository.java rename to payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxJpaRepository.java index 6a25bbb8..fc05aaa5 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/repository/outbox/OutboxJpaRepository.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxJpaRepository.java @@ -1,11 +1,12 @@ -package com.smore.payment.payment.infrastructure.persistence.jpa.repository.outbox; +package com.smore.payment.payment.infrastructure.persistence.outbox; -import com.smore.payment.global.outbox.OutboxStatus; -import com.smore.payment.payment.infrastructure.persistence.jpa.model.outbox.OutboxEntity; +import com.smore.payment.shared.outbox.OutboxStatus; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface OutboxJpaRepository extends JpaRepository { List findTop50ByStatusOrderByCreatedAtAsc(OutboxStatus status); + + List findTop50ByStatusAndRetryCountGreaterThanOrderByCreatedAtAsc(OutboxStatus status, Integer retryCount); } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/mapper/OutboxMapper.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxMapper.java similarity index 82% rename from payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/mapper/OutboxMapper.java rename to payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxMapper.java index 633b5359..08023332 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/jpa/mapper/OutboxMapper.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/outbox/OutboxMapper.java @@ -1,7 +1,6 @@ -package com.smore.payment.payment.infrastructure.persistence.jpa.mapper; +package com.smore.payment.payment.infrastructure.persistence.outbox; -import com.smore.payment.global.outbox.OutboxMessage; -import com.smore.payment.payment.infrastructure.persistence.jpa.model.outbox.OutboxEntity; +import com.smore.payment.shared.outbox.OutboxMessage; import org.springframework.stereotype.Component; @Component diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/repository/RedisRepositoryImpl.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/repository/RedisRepositoryImpl.java deleted file mode 100644 index 2999c7de..00000000 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/repository/RedisRepositoryImpl.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.smore.payment.payment.infrastructure.persistence.redis.repository; - -import com.smore.payment.payment.infrastructure.persistence.redis.model.TemporaryPayment; -import com.smore.payment.payment.domain.repository.RedisRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Repository; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.Optional; -import java.util.UUID; - -@Slf4j -@Repository -@RequiredArgsConstructor -public class RedisRepositoryImpl implements RedisRepository { - - private final RedisTemplate temporaryPaymentRedisTemplate; - - @Override - public Optional findByOrderId(UUID orderId) { - log.info("레디스 찾기 시작: {}", orderId); - - TemporaryPayment temp = temporaryPaymentRedisTemplate.opsForValue() - .get(orderId.toString()); - - log.info("레디스 결과: {}", temp); - - return Optional.ofNullable(temp); - } - - @Override - public void deleteByOrderId(UUID orderId) { - temporaryPaymentRedisTemplate.delete(orderId.toString()); - } - - @Override - public void save(TemporaryPayment temp) { - Duration ttl = Duration.between(LocalDateTime.now(), temp.getExpiredAt()); - - if (ttl.isNegative() || ttl.isZero()) { - throw new IllegalArgumentException("만료 시간이 현재 시간보다 과거입니다."); - } - - temporaryPaymentRedisTemplate.opsForValue().set(temp.getOrderId().toString(), temp, ttl); - } - - @Override - public boolean existsByOrderId(UUID orderId) { - return temporaryPaymentRedisTemplate.hasKey(orderId.toString()); - } -} diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/FailedPaymentHandler.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/redis/FailedPaymentHandler.java similarity index 70% rename from payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/FailedPaymentHandler.java rename to payment/src/main/java/com/smore/payment/payment/infrastructure/redis/FailedPaymentHandler.java index c4341f4b..239cb03e 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/FailedPaymentHandler.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/redis/FailedPaymentHandler.java @@ -1,9 +1,9 @@ -package com.smore.payment.payment.infrastructure.persistence.redis; +package com.smore.payment.payment.infrastructure.redis; -import com.smore.payment.global.outbox.OutboxMessage; -import com.smore.payment.global.outbox.OutboxMessageCreator; +import com.smore.payment.shared.outbox.OutboxMessage; +import com.smore.payment.shared.outbox.OutboxMessageCreator; import com.smore.payment.payment.application.event.outbound.PaymentFailedEvent; -import com.smore.payment.payment.domain.repository.OutboxRepository; +import com.smore.payment.payment.application.port.out.OutboxPort; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -16,7 +16,7 @@ public class FailedPaymentHandler { private final OutboxMessageCreator outboxMessageCreator; - private final OutboxRepository outboxRepository; + private final OutboxPort outboxPort; public void handleExpiredKey(String key) { @@ -31,6 +31,6 @@ public void handleExpiredKey(String key) { // Outbox 생성 → 저장 OutboxMessage outboxMessage = outboxMessageCreator.paymentFailed(event); - outboxRepository.save(outboxMessage); + outboxPort.save(outboxMessage); } } diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/RedisKeyExpirationListener.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/redis/RedisKeyExpirationListener.java similarity index 79% rename from payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/RedisKeyExpirationListener.java rename to payment/src/main/java/com/smore/payment/payment/infrastructure/redis/RedisKeyExpirationListener.java index 7364f053..1b4d5ff2 100644 --- a/payment/src/main/java/com/smore/payment/payment/infrastructure/persistence/redis/RedisKeyExpirationListener.java +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/redis/RedisKeyExpirationListener.java @@ -1,4 +1,4 @@ -package com.smore.payment.payment.infrastructure.persistence.redis; +package com.smore.payment.payment.infrastructure.redis; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,6 +19,11 @@ public void onMessage(Message message, byte[] pattern) { String expiredKey = message.toString(); // 저장했던 key 문자열 log.info("[REDIS] TTL expired key = {}", expiredKey); + if (!expiredKey.startsWith("api-inbox:payment:approve:")) { + log.debug("TTL expired but ignored key={}", expiredKey); + return; + } + try { failedPaymentHandler.handleExpiredKey(expiredKey); } catch (Exception e) { diff --git a/payment/src/main/java/com/smore/payment/payment/infrastructure/redis/TemporaryPaymentAdapter.java b/payment/src/main/java/com/smore/payment/payment/infrastructure/redis/TemporaryPaymentAdapter.java new file mode 100644 index 00000000..50096d6f --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/infrastructure/redis/TemporaryPaymentAdapter.java @@ -0,0 +1,94 @@ +package com.smore.payment.payment.infrastructure.redis; + +import com.smore.payment.payment.application.port.out.TemporaryPaymentPort; +import com.smore.payment.payment.domain.model.TemporaryPayment; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class TemporaryPaymentAdapter implements TemporaryPaymentPort { + + private final RedisTemplate temporaryPaymentRedisTemplate; + + @Override + public Optional findByOrderId(UUID orderId) { + log.info("레디스 찾기 시작: {}", orderId); + + String tempKey = tempKey(orderId); + String approvedKey = approvedKey(orderId); + + TemporaryPayment temp = temporaryPaymentRedisTemplate.opsForValue().get(tempKey); + + if (temp == null) { + temp = temporaryPaymentRedisTemplate.opsForValue().get(approvedKey); + } + + log.info("레디스 결과: {}", temp); + + return Optional.ofNullable(temp); + } + + @Override + public void deleteByOrderId(UUID orderId) { + temporaryPaymentRedisTemplate.delete(tempKey(orderId)); + temporaryPaymentRedisTemplate.delete(approvedKey(orderId)); + } + + @Override + public void save(TemporaryPayment temp) { + Duration ttl = Duration.between(LocalDateTime.now(), temp.getExpiredAt()); + + if (ttl.isNegative() || ttl.isZero()) { + throw new IllegalArgumentException("만료 시간이 현재 시간보다 과거입니다."); + } + temporaryPaymentRedisTemplate.opsForValue() + .set(tempKey(temp.getOrderId()), temp, ttl); + } + + @Override + public void update(TemporaryPayment temp) { + + String oldKey = tempKey(temp.getOrderId()); + String newKey = approvedKey(temp.getOrderId()); + + Boolean exists = temporaryPaymentRedisTemplate.hasKey(oldKey); + if (!exists) { + throw new IllegalStateException( + "TemporaryPayment가 Redis에 존재하지 않습니다. orderId=" + temp.getOrderId() + ); + } + + temporaryPaymentRedisTemplate.rename(oldKey, newKey); + + temporaryPaymentRedisTemplate.opsForValue().set(newKey, temp); + + log.info( + "TemporaryPayment 승인 상태로 승격. key={}, pgApprovedAt={}", + newKey, + temp.getPgResponseResult() != null ? temp.getPgResponseResult().approvedAt() : null + ); + } + + @Override + public boolean existsByOrderId(UUID orderId) { + return temporaryPaymentRedisTemplate.hasKey(tempKey(orderId)) || + temporaryPaymentRedisTemplate.hasKey(approvedKey(orderId)); + } + + private String tempKey(UUID orderId) { + return "payment:temp:" + orderId; + } + + private String approvedKey(UUID orderId) { + return "payment:pg:approved:" + orderId; + } +} diff --git a/payment/src/main/java/com/smore/payment/payment/presentation/PaymentController.java b/payment/src/main/java/com/smore/payment/payment/presentation/PaymentController.java index 27ec0e98..500d5127 100644 --- a/payment/src/main/java/com/smore/payment/payment/presentation/PaymentController.java +++ b/payment/src/main/java/com/smore/payment/payment/presentation/PaymentController.java @@ -1,10 +1,11 @@ package com.smore.payment.payment.presentation; import com.smore.common.response.ApiResponse; -import com.smore.payment.payment.application.CreatePaymentService; -import com.smore.payment.payment.domain.model.Payment; +import com.smore.payment.payment.application.port.in.ApprovePaymentResult; +import com.smore.payment.payment.application.port.in.ApprovePaymentUseCase; import com.smore.payment.payment.presentation.dto.request.ApprovePaymentRequestDto; import com.smore.payment.payment.presentation.dto.response.ApprovePaymentResponseDto; +import com.smore.payment.payment.presentation.mapper.PaymentDtoMapper; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -15,16 +16,16 @@ @RequestMapping("/api/v1/payments") public class PaymentController { - private final CreatePaymentService createPaymentService; + private final ApprovePaymentUseCase approvePaymentUseCase; + private final PaymentDtoMapper paymentDtoMapper; @PostMapping("/approve") public ResponseEntity approvePayment( @Valid @RequestBody ApprovePaymentRequestDto request ) { - Payment payment = createPaymentService.approve(request.toCommand()); - return ResponseEntity.ok(ApiResponse.ok(ApprovePaymentResponseDto.from(payment))); + ApprovePaymentResult result = approvePaymentUseCase.approve(paymentDtoMapper.toCommand(request)); + ApprovePaymentResponseDto response = paymentDtoMapper.toResponseDto(result); + return ResponseEntity.ok(ApiResponse.ok(response)); } - - } diff --git a/payment/src/main/java/com/smore/payment/payment/presentation/dto/request/ApprovePaymentRequestDto.java b/payment/src/main/java/com/smore/payment/payment/presentation/dto/request/ApprovePaymentRequestDto.java index 285bfb3d..e6cde950 100644 --- a/payment/src/main/java/com/smore/payment/payment/presentation/dto/request/ApprovePaymentRequestDto.java +++ b/payment/src/main/java/com/smore/payment/payment/presentation/dto/request/ApprovePaymentRequestDto.java @@ -1,6 +1,5 @@ package com.smore.payment.payment.presentation.dto.request; -import com.smore.payment.payment.application.command.ApprovePaymentCommand; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -19,6 +18,9 @@ public class ApprovePaymentRequestDto { @NotNull private UUID orderId; + @NotNull + private UUID idempotencyKey; + @NotNull @DecimalMin(value = "0.0", inclusive = false) private BigDecimal amount; @@ -28,8 +30,4 @@ public class ApprovePaymentRequestDto { @NotBlank private String pgOrderId; - - public ApprovePaymentCommand toCommand() { - return new ApprovePaymentCommand(orderId,amount,paymentKey,pgOrderId); - } } diff --git a/payment/src/main/java/com/smore/payment/payment/presentation/dto/response/ApprovePaymentResponseDto.java b/payment/src/main/java/com/smore/payment/payment/presentation/dto/response/ApprovePaymentResponseDto.java index 71fb9399..9e15eb89 100644 --- a/payment/src/main/java/com/smore/payment/payment/presentation/dto/response/ApprovePaymentResponseDto.java +++ b/payment/src/main/java/com/smore/payment/payment/presentation/dto/response/ApprovePaymentResponseDto.java @@ -1,6 +1,5 @@ package com.smore.payment.payment.presentation.dto.response; -import com.smore.payment.payment.domain.model.Payment; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -21,17 +20,4 @@ public class ApprovePaymentResponseDto { private BigDecimal approvedAmount; private LocalDateTime approvedAt; - - public static ApprovePaymentResponseDto from(Payment payment) { - return new ApprovePaymentResponseDto( - true, - payment.getStatus().name(), - - payment.getId().toString(), - payment.getOrderId().toString(), - - payment.getAmount(), - payment.getApprovedAt() - ); - } } diff --git a/payment/src/main/java/com/smore/payment/payment/presentation/mapper/PaymentDtoMapper.java b/payment/src/main/java/com/smore/payment/payment/presentation/mapper/PaymentDtoMapper.java new file mode 100644 index 00000000..993dce97 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/payment/presentation/mapper/PaymentDtoMapper.java @@ -0,0 +1,32 @@ +package com.smore.payment.payment.presentation.mapper; + +import com.smore.payment.payment.application.port.in.ApprovePaymentCommand; +import com.smore.payment.payment.application.port.in.ApprovePaymentResult; +import com.smore.payment.payment.presentation.dto.request.ApprovePaymentRequestDto; +import com.smore.payment.payment.presentation.dto.response.ApprovePaymentResponseDto; +import org.springframework.stereotype.Component; + +@Component +public class PaymentDtoMapper { + + public ApprovePaymentCommand toCommand(ApprovePaymentRequestDto requestDto) { + return new ApprovePaymentCommand( + requestDto.getOrderId(), + requestDto.getIdempotencyKey(), + requestDto.getAmount(), + requestDto.getPaymentKey(), + requestDto.getPgOrderId() + ); + } + + public ApprovePaymentResponseDto toResponseDto(ApprovePaymentResult result) { + return new ApprovePaymentResponseDto( + true, + result.status(), + result.paymentId().toString(), + result.orderId().toString(), + result.approvedAmount(), + result.approvedAt() + ); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/application/CancelPolicyService.java b/payment/src/main/java/com/smore/payment/policy/cancel/application/CancelPolicyService.java similarity index 84% rename from payment/src/main/java/com/smore/payment/cancelpolicy/application/CancelPolicyService.java rename to payment/src/main/java/com/smore/payment/policy/cancel/application/CancelPolicyService.java index 2b8c55e6..94e280a6 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/application/CancelPolicyService.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/application/CancelPolicyService.java @@ -1,9 +1,9 @@ -package com.smore.payment.cancelpolicy.application; +package com.smore.payment.policy.cancel.application; -import com.smore.payment.cancelpolicy.application.command.CreateCancelPolicyCommand; -import com.smore.payment.cancelpolicy.application.query.GetCancelPolicyQuery; -import com.smore.payment.cancelpolicy.domain.model.CancelPolicy; -import com.smore.payment.cancelpolicy.domain.repository.CancelPolicyRepository; +import com.smore.payment.policy.cancel.application.command.CreateCancelPolicyCommand; +import com.smore.payment.policy.cancel.application.query.GetCancelPolicyQuery; +import com.smore.payment.policy.cancel.domain.model.CancelPolicy; +import com.smore.payment.policy.cancel.domain.repository.CancelPolicyRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/application/command/CreateCancelPolicyCommand.java b/payment/src/main/java/com/smore/payment/policy/cancel/application/command/CreateCancelPolicyCommand.java similarity index 73% rename from payment/src/main/java/com/smore/payment/cancelpolicy/application/command/CreateCancelPolicyCommand.java rename to payment/src/main/java/com/smore/payment/policy/cancel/application/command/CreateCancelPolicyCommand.java index cde77251..76b15ae4 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/application/command/CreateCancelPolicyCommand.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/application/command/CreateCancelPolicyCommand.java @@ -1,6 +1,6 @@ -package com.smore.payment.cancelpolicy.application.command; +package com.smore.payment.policy.cancel.application.command; -import com.smore.payment.cancelpolicy.domain.model.*; +import com.smore.payment.policy.cancel.domain.model.*; import java.time.Duration; diff --git a/payment/src/main/java/com/smore/payment/policy/cancel/application/query/GetCancelPolicyQuery.java b/payment/src/main/java/com/smore/payment/policy/cancel/application/query/GetCancelPolicyQuery.java new file mode 100644 index 00000000..7584a303 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/policy/cancel/application/query/GetCancelPolicyQuery.java @@ -0,0 +1,11 @@ +package com.smore.payment.policy.cancel.application.query; + +import com.smore.payment.policy.cancel.domain.model.CancelTargetType; +import com.smore.payment.policy.cancel.domain.model.TargetKey; + +public record GetCancelPolicyQuery( + CancelTargetType cancelTargetType, + TargetKey targetKey +) { + +} diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFeeRate.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFeeRate.java similarity index 92% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFeeRate.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFeeRate.java index a7074847..1733f47e 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFeeRate.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFeeRate.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; import java.math.BigDecimal; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFeeType.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFeeType.java similarity index 91% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFeeType.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFeeType.java index f50bc201..897d462c 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFeeType.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFeeType.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; import lombok.Getter; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFixedAmount.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFixedAmount.java similarity index 90% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFixedAmount.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFixedAmount.java index 7656cc08..9d71be87 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelFixedAmount.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelFixedAmount.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; import java.math.BigDecimal; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelPolicy.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelPolicy.java similarity index 98% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelPolicy.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelPolicy.java index 059da397..c8b31399 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelPolicy.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelPolicy.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; import java.time.Duration; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelTargetType.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelTargetType.java similarity index 86% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelTargetType.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelTargetType.java index e994285f..93bddf71 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/CancelTargetType.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/CancelTargetType.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; public enum CancelTargetType { CATEGORY, MERCHANT, AUCTION_TYPE, USER_TYPE; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKey.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKey.java similarity index 62% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKey.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKey.java index cb0d6d4c..ecfb28c3 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKey.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKey.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; public interface TargetKey { Object getTargetKey(); diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyLong.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyLong.java similarity index 81% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyLong.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyLong.java index c8bf3d29..1df057d1 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyLong.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyLong.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; public record TargetKeyLong(Long value) implements TargetKey { @Override diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyString.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyString.java similarity index 80% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyString.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyString.java index 66f70731..b7018233 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyString.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyString.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; public record TargetKeyString(String value) implements TargetKey { @Override diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyUUID.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyUUID.java similarity index 82% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyUUID.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyUUID.java index 74e704a3..03e56d35 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyUUID.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/model/TargetKeyUUID.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.cancel.domain.model; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/repository/CancelPolicyRepository.java b/payment/src/main/java/com/smore/payment/policy/cancel/domain/repository/CancelPolicyRepository.java similarity index 66% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/repository/CancelPolicyRepository.java rename to payment/src/main/java/com/smore/payment/policy/cancel/domain/repository/CancelPolicyRepository.java index 96e3a69d..ed562955 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/repository/CancelPolicyRepository.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/domain/repository/CancelPolicyRepository.java @@ -1,8 +1,8 @@ -package com.smore.payment.cancelpolicy.domain.repository; +package com.smore.payment.policy.cancel.domain.repository; -import com.smore.payment.cancelpolicy.domain.model.CancelPolicy; -import com.smore.payment.cancelpolicy.domain.model.CancelTargetType; -import com.smore.payment.cancelpolicy.domain.model.TargetKey; +import com.smore.payment.policy.cancel.domain.model.CancelPolicy; +import com.smore.payment.policy.cancel.domain.model.CancelTargetType; +import com.smore.payment.policy.cancel.domain.model.TargetKey; import org.springframework.stereotype.Repository; import java.util.Optional; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/mapper/CancelPolicyMapper.java b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/mapper/CancelPolicyMapper.java similarity index 81% rename from payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/mapper/CancelPolicyMapper.java rename to payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/mapper/CancelPolicyMapper.java index ef3629ec..7322a955 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/mapper/CancelPolicyMapper.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/mapper/CancelPolicyMapper.java @@ -1,15 +1,13 @@ -package com.smore.payment.cancelpolicy.infrastructure.persistence.mapper; +package com.smore.payment.policy.cancel.infrastructure.persistence.mapper; -import com.smore.payment.cancelpolicy.domain.model.*; -import com.smore.payment.cancelpolicy.infrastructure.persistence.model.CancelFeeRateJpa; -import com.smore.payment.cancelpolicy.infrastructure.persistence.model.CancelFixedAmountJpa; -import com.smore.payment.cancelpolicy.infrastructure.persistence.model.CancelPolicyEntity; +import com.smore.payment.policy.cancel.infrastructure.persistence.model.CancelFeeRateJpa; +import com.smore.payment.policy.cancel.infrastructure.persistence.model.CancelFixedAmountJpa; +import com.smore.payment.policy.cancel.infrastructure.persistence.model.CancelPolicyEntity; +import com.smore.payment.policy.cancel.domain.model.*; import org.springframework.stereotype.Component; import java.util.UUID; -import static com.smore.payment.cancelpolicy.domain.model.CancelTargetType.*; - @Component public class CancelPolicyMapper { diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelFeeRateJpa.java b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelFeeRateJpa.java similarity index 85% rename from payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelFeeRateJpa.java rename to payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelFeeRateJpa.java index 006d7b68..471b236d 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelFeeRateJpa.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelFeeRateJpa.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.infrastructure.persistence.model; +package com.smore.payment.policy.cancel.infrastructure.persistence.model; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelFixedAmountJpa.java b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelFixedAmountJpa.java similarity index 86% rename from payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelFixedAmountJpa.java rename to payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelFixedAmountJpa.java index 9307a955..5e1bb290 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelFixedAmountJpa.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelFixedAmountJpa.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.infrastructure.persistence.model; +package com.smore.payment.policy.cancel.infrastructure.persistence.model; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelPolicyEntity.java b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelPolicyEntity.java similarity index 85% rename from payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelPolicyEntity.java rename to payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelPolicyEntity.java index 0c005706..40b0d017 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/model/CancelPolicyEntity.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/model/CancelPolicyEntity.java @@ -1,8 +1,8 @@ -package com.smore.payment.cancelpolicy.infrastructure.persistence.model; +package com.smore.payment.policy.cancel.infrastructure.persistence.model; -import com.smore.payment.cancelpolicy.domain.model.CancelFeeType; -import com.smore.payment.cancelpolicy.domain.model.CancelTargetType; -import com.smore.payment.global.entity.BaseEntity; +import com.smore.payment.policy.cancel.domain.model.CancelFeeType; +import com.smore.payment.policy.cancel.domain.model.CancelTargetType; +import com.smore.payment.shared.entity.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -29,7 +29,7 @@ public class CancelPolicyEntity extends BaseEntity { @Column(name = "target_key", nullable = false, updatable = false) private String targetKey; - @Column(name = "cancel_limit_minutes") + @Column(name = "cancel_limit_minutes", columnDefinition = "interval") private Duration cancelLimitMinutes; @Enumerated(EnumType.STRING) diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/repository/CancelPolicyJpaRepository.java b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/repository/CancelPolicyJpaRepository.java similarity index 58% rename from payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/repository/CancelPolicyJpaRepository.java rename to payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/repository/CancelPolicyJpaRepository.java index b2ccf7b5..08242734 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/repository/CancelPolicyJpaRepository.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/repository/CancelPolicyJpaRepository.java @@ -1,7 +1,7 @@ -package com.smore.payment.cancelpolicy.infrastructure.persistence.repository; +package com.smore.payment.policy.cancel.infrastructure.persistence.repository; -import com.smore.payment.cancelpolicy.domain.model.CancelTargetType; -import com.smore.payment.cancelpolicy.infrastructure.persistence.model.CancelPolicyEntity; +import com.smore.payment.policy.cancel.domain.model.CancelTargetType; +import com.smore.payment.policy.cancel.infrastructure.persistence.model.CancelPolicyEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/repository/CancelPolicyRepositoryImpl.java b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/repository/CancelPolicyRepositoryImpl.java similarity index 79% rename from payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/repository/CancelPolicyRepositoryImpl.java rename to payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/repository/CancelPolicyRepositoryImpl.java index 24d5633a..2980e2dc 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/infrastructure/persistence/repository/CancelPolicyRepositoryImpl.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/infrastructure/persistence/repository/CancelPolicyRepositoryImpl.java @@ -1,11 +1,11 @@ -package com.smore.payment.cancelpolicy.infrastructure.persistence.repository; - -import com.smore.payment.cancelpolicy.domain.model.CancelPolicy; -import com.smore.payment.cancelpolicy.domain.model.CancelTargetType; -import com.smore.payment.cancelpolicy.domain.model.TargetKey; -import com.smore.payment.cancelpolicy.domain.repository.CancelPolicyRepository; -import com.smore.payment.cancelpolicy.infrastructure.persistence.mapper.CancelPolicyMapper; -import com.smore.payment.cancelpolicy.infrastructure.persistence.model.CancelPolicyEntity; +package com.smore.payment.policy.cancel.infrastructure.persistence.repository; + +import com.smore.payment.policy.cancel.domain.model.CancelPolicy; +import com.smore.payment.policy.cancel.domain.model.CancelTargetType; +import com.smore.payment.policy.cancel.domain.model.TargetKey; +import com.smore.payment.policy.cancel.domain.repository.CancelPolicyRepository; +import com.smore.payment.policy.cancel.infrastructure.persistence.mapper.CancelPolicyMapper; +import com.smore.payment.policy.cancel.infrastructure.persistence.model.CancelPolicyEntity; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/presentation/CancelPolicyController.java b/payment/src/main/java/com/smore/payment/policy/cancel/presentation/CancelPolicyController.java similarity index 80% rename from payment/src/main/java/com/smore/payment/cancelpolicy/presentation/CancelPolicyController.java rename to payment/src/main/java/com/smore/payment/policy/cancel/presentation/CancelPolicyController.java index 1ea2f344..ae32c99d 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/presentation/CancelPolicyController.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/presentation/CancelPolicyController.java @@ -1,12 +1,12 @@ -package com.smore.payment.cancelpolicy.presentation; +package com.smore.payment.policy.cancel.presentation; import com.smore.common.response.ApiResponse; -import com.smore.payment.cancelpolicy.application.CancelPolicyService; -import com.smore.payment.cancelpolicy.application.query.GetCancelPolicyQuery; -import com.smore.payment.cancelpolicy.domain.model.*; -import com.smore.payment.cancelpolicy.presentation.dto.request.CreateCancelPolicyRequestDto; -import com.smore.payment.cancelpolicy.presentation.dto.response.GetCancelPolicyResponseDto; -import com.smore.payment.global.config.UserContextHolder; +import com.smore.payment.policy.cancel.application.CancelPolicyService; +import com.smore.payment.policy.cancel.application.query.GetCancelPolicyQuery; +import com.smore.payment.policy.cancel.presentation.dto.request.CreateCancelPolicyRequestDto; +import com.smore.payment.policy.cancel.presentation.dto.response.GetCancelPolicyResponseDto; +import com.smore.payment.policy.cancel.domain.model.*; +import com.smore.payment.shared.config.UserContextHolder; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/presentation/dto/request/CreateCancelPolicyRequestDto.java b/payment/src/main/java/com/smore/payment/policy/cancel/presentation/dto/request/CreateCancelPolicyRequestDto.java similarity index 87% rename from payment/src/main/java/com/smore/payment/cancelpolicy/presentation/dto/request/CreateCancelPolicyRequestDto.java rename to payment/src/main/java/com/smore/payment/policy/cancel/presentation/dto/request/CreateCancelPolicyRequestDto.java index 9b7fa716..d72e08d1 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/presentation/dto/request/CreateCancelPolicyRequestDto.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/presentation/dto/request/CreateCancelPolicyRequestDto.java @@ -1,7 +1,7 @@ -package com.smore.payment.cancelpolicy.presentation.dto.request; +package com.smore.payment.policy.cancel.presentation.dto.request; -import com.smore.payment.cancelpolicy.application.command.CreateCancelPolicyCommand; -import com.smore.payment.cancelpolicy.domain.model.*; +import com.smore.payment.policy.cancel.application.command.CreateCancelPolicyCommand; +import com.smore.payment.policy.cancel.domain.model.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/presentation/dto/response/GetCancelPolicyResponseDto.java b/payment/src/main/java/com/smore/payment/policy/cancel/presentation/dto/response/GetCancelPolicyResponseDto.java similarity index 89% rename from payment/src/main/java/com/smore/payment/cancelpolicy/presentation/dto/response/GetCancelPolicyResponseDto.java rename to payment/src/main/java/com/smore/payment/policy/cancel/presentation/dto/response/GetCancelPolicyResponseDto.java index c66da75d..a49e96f7 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/presentation/dto/response/GetCancelPolicyResponseDto.java +++ b/payment/src/main/java/com/smore/payment/policy/cancel/presentation/dto/response/GetCancelPolicyResponseDto.java @@ -1,6 +1,6 @@ -package com.smore.payment.cancelpolicy.presentation.dto.response; +package com.smore.payment.policy.cancel.presentation.dto.response; -import com.smore.payment.cancelpolicy.domain.model.CancelPolicy; +import com.smore.payment.policy.cancel.domain.model.CancelPolicy; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/application/FeePolicyService.java b/payment/src/main/java/com/smore/payment/policy/fee/application/FeePolicyService.java similarity index 83% rename from payment/src/main/java/com/smore/payment/feepolicy/application/FeePolicyService.java rename to payment/src/main/java/com/smore/payment/policy/fee/application/FeePolicyService.java index dce49a4f..3bf1bab2 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/application/FeePolicyService.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/application/FeePolicyService.java @@ -1,9 +1,9 @@ -package com.smore.payment.feepolicy.application; +package com.smore.payment.policy.fee.application; -import com.smore.payment.feepolicy.application.command.CreateFeePolicyCommand; -import com.smore.payment.feepolicy.application.query.GetFeePolicyQuery; -import com.smore.payment.feepolicy.domain.model.FeePolicy; -import com.smore.payment.feepolicy.domain.repository.FeePolicyRepository; +import com.smore.payment.policy.fee.application.command.CreateFeePolicyCommand; +import com.smore.payment.policy.fee.application.query.GetFeePolicyQuery; +import com.smore.payment.policy.fee.domain.model.FeePolicy; +import com.smore.payment.policy.fee.domain.repository.FeePolicyRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/application/command/CreateFeePolicyCommand.java b/payment/src/main/java/com/smore/payment/policy/fee/application/command/CreateFeePolicyCommand.java similarity index 62% rename from payment/src/main/java/com/smore/payment/feepolicy/application/command/CreateFeePolicyCommand.java rename to payment/src/main/java/com/smore/payment/policy/fee/application/command/CreateFeePolicyCommand.java index 6b05b2f9..67413e99 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/application/command/CreateFeePolicyCommand.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/application/command/CreateFeePolicyCommand.java @@ -1,6 +1,6 @@ -package com.smore.payment.feepolicy.application.command; +package com.smore.payment.policy.fee.application.command; -import com.smore.payment.feepolicy.domain.model.*; +import com.smore.payment.policy.fee.domain.model.*; public record CreateFeePolicyCommand( TargetType targetType, diff --git a/payment/src/main/java/com/smore/payment/policy/fee/application/query/GetFeePolicyQuery.java b/payment/src/main/java/com/smore/payment/policy/fee/application/query/GetFeePolicyQuery.java new file mode 100644 index 00000000..e62355dc --- /dev/null +++ b/payment/src/main/java/com/smore/payment/policy/fee/application/query/GetFeePolicyQuery.java @@ -0,0 +1,11 @@ +package com.smore.payment.policy.fee.application.query; + +import com.smore.payment.policy.fee.domain.model.TargetKey; +import com.smore.payment.policy.fee.domain.model.TargetType; + +public record GetFeePolicyQuery( + TargetType targetType, + TargetKey targetKey +) { + +} diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeePolicy.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeePolicy.java similarity index 98% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeePolicy.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeePolicy.java index a6091fa3..1e5c6a82 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeePolicy.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeePolicy.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeeRate.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeeRate.java similarity index 92% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeeRate.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeeRate.java index 7a86edde..0ec35f3e 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeeRate.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeeRate.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; import java.math.BigDecimal; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeeType.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeeType.java similarity index 91% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeeType.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeeType.java index 5358a72f..eda9904b 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FeeType.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FeeType.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; import lombok.Getter; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FixedAmount.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FixedAmount.java similarity index 90% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/FixedAmount.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/FixedAmount.java index cf644a5e..d1334f40 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/FixedAmount.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/FixedAmount.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; import java.math.BigDecimal; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKey.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKey.java similarity index 64% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKey.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKey.java index 5a52f832..9ec62aaf 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKey.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKey.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; public interface TargetKey { Object getTargetKey(); diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKeyLong.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKeyLong.java similarity index 82% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKeyLong.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKeyLong.java index a30580b7..a71f4b4c 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKeyLong.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKeyLong.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; public record TargetKeyLong(Long value) implements TargetKey { @Override diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKeyUUID.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKeyUUID.java similarity index 83% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKeyUUID.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKeyUUID.java index 3d5cf823..742fcf1c 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetKeyUUID.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetKeyUUID.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetType.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetType.java similarity index 86% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetType.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetType.java index 3fe7256d..d6d345c1 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/model/TargetType.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/model/TargetType.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.domain.model; +package com.smore.payment.policy.fee.domain.model; public enum TargetType { CATEGORY, MERCHANT, USER_TYPE; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/domain/repository/FeePolicyRepository.java b/payment/src/main/java/com/smore/payment/policy/fee/domain/repository/FeePolicyRepository.java similarity index 66% rename from payment/src/main/java/com/smore/payment/feepolicy/domain/repository/FeePolicyRepository.java rename to payment/src/main/java/com/smore/payment/policy/fee/domain/repository/FeePolicyRepository.java index befc6efe..58bb10a1 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/domain/repository/FeePolicyRepository.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/domain/repository/FeePolicyRepository.java @@ -1,8 +1,8 @@ -package com.smore.payment.feepolicy.domain.repository; +package com.smore.payment.policy.fee.domain.repository; -import com.smore.payment.feepolicy.domain.model.FeePolicy; -import com.smore.payment.feepolicy.domain.model.TargetKey; -import com.smore.payment.feepolicy.domain.model.TargetType; +import com.smore.payment.policy.fee.domain.model.FeePolicy; +import com.smore.payment.policy.fee.domain.model.TargetKey; +import com.smore.payment.policy.fee.domain.model.TargetType; import org.springframework.stereotype.Repository; import java.util.Optional; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/mapper/FeePolicyMapper.java b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/mapper/FeePolicyMapper.java similarity index 81% rename from payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/mapper/FeePolicyMapper.java rename to payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/mapper/FeePolicyMapper.java index 8b85bdce..32b1dd6d 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/mapper/FeePolicyMapper.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/mapper/FeePolicyMapper.java @@ -1,9 +1,9 @@ -package com.smore.payment.feepolicy.infrastructure.persistence.mapper; +package com.smore.payment.policy.fee.infrastructure.persistence.mapper; -import com.smore.payment.feepolicy.domain.model.*; -import com.smore.payment.feepolicy.infrastructure.persistence.model.FeePolicyEntity; -import com.smore.payment.feepolicy.infrastructure.persistence.model.FeeRateJpa; -import com.smore.payment.feepolicy.infrastructure.persistence.model.FixedAmountJpa; +import com.smore.payment.policy.fee.infrastructure.persistence.model.FeePolicyEntity; +import com.smore.payment.policy.fee.infrastructure.persistence.model.FeeRateJpa; +import com.smore.payment.policy.fee.infrastructure.persistence.model.FixedAmountJpa; +import com.smore.payment.policy.fee.domain.model.*; import org.springframework.stereotype.Component; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FeePolicyEntity.java b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FeePolicyEntity.java similarity index 85% rename from payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FeePolicyEntity.java rename to payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FeePolicyEntity.java index 3fc7fc40..de7862b1 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FeePolicyEntity.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FeePolicyEntity.java @@ -1,8 +1,8 @@ -package com.smore.payment.feepolicy.infrastructure.persistence.model; +package com.smore.payment.policy.fee.infrastructure.persistence.model; -import com.smore.payment.feepolicy.domain.model.FeeType; -import com.smore.payment.feepolicy.domain.model.TargetType; -import com.smore.payment.global.entity.BaseEntity; +import com.smore.payment.policy.fee.domain.model.FeeType; +import com.smore.payment.policy.fee.domain.model.TargetType; +import com.smore.payment.shared.entity.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FeeRateJpa.java b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FeeRateJpa.java similarity index 85% rename from payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FeeRateJpa.java rename to payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FeeRateJpa.java index 2f9e1d8d..93291a0c 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FeeRateJpa.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FeeRateJpa.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.infrastructure.persistence.model; +package com.smore.payment.policy.fee.infrastructure.persistence.model; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FixedAmountJpa.java b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FixedAmountJpa.java similarity index 86% rename from payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FixedAmountJpa.java rename to payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FixedAmountJpa.java index d472c4be..cf63f347 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/model/FixedAmountJpa.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/model/FixedAmountJpa.java @@ -1,4 +1,4 @@ -package com.smore.payment.feepolicy.infrastructure.persistence.model; +package com.smore.payment.policy.fee.infrastructure.persistence.model; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/repository/FeePolicyJpaRepository.java b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/repository/FeePolicyJpaRepository.java similarity index 57% rename from payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/repository/FeePolicyJpaRepository.java rename to payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/repository/FeePolicyJpaRepository.java index 47368708..93990791 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/repository/FeePolicyJpaRepository.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/repository/FeePolicyJpaRepository.java @@ -1,7 +1,7 @@ -package com.smore.payment.feepolicy.infrastructure.persistence.repository; +package com.smore.payment.policy.fee.infrastructure.persistence.repository; -import com.smore.payment.feepolicy.domain.model.TargetType; -import com.smore.payment.feepolicy.infrastructure.persistence.model.FeePolicyEntity; +import com.smore.payment.policy.fee.domain.model.TargetType; +import com.smore.payment.policy.fee.infrastructure.persistence.model.FeePolicyEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/repository/FeePolicyRepositoryImpl.java b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/repository/FeePolicyRepositoryImpl.java similarity index 79% rename from payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/repository/FeePolicyRepositoryImpl.java rename to payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/repository/FeePolicyRepositoryImpl.java index ec929c5a..40b2ff8e 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/infrastructure/persistence/repository/FeePolicyRepositoryImpl.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/infrastructure/persistence/repository/FeePolicyRepositoryImpl.java @@ -1,11 +1,11 @@ -package com.smore.payment.feepolicy.infrastructure.persistence.repository; - -import com.smore.payment.feepolicy.domain.model.FeePolicy; -import com.smore.payment.feepolicy.domain.model.TargetKey; -import com.smore.payment.feepolicy.domain.model.TargetType; -import com.smore.payment.feepolicy.domain.repository.FeePolicyRepository; -import com.smore.payment.feepolicy.infrastructure.persistence.mapper.FeePolicyMapper; -import com.smore.payment.feepolicy.infrastructure.persistence.model.FeePolicyEntity; +package com.smore.payment.policy.fee.infrastructure.persistence.repository; + +import com.smore.payment.policy.fee.domain.model.FeePolicy; +import com.smore.payment.policy.fee.domain.model.TargetKey; +import com.smore.payment.policy.fee.domain.model.TargetType; +import com.smore.payment.policy.fee.domain.repository.FeePolicyRepository; +import com.smore.payment.policy.fee.infrastructure.persistence.mapper.FeePolicyMapper; +import com.smore.payment.policy.fee.infrastructure.persistence.model.FeePolicyEntity; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/presentation/FeePolicyController.java b/payment/src/main/java/com/smore/payment/policy/fee/presentation/FeePolicyController.java similarity index 69% rename from payment/src/main/java/com/smore/payment/feepolicy/presentation/FeePolicyController.java rename to payment/src/main/java/com/smore/payment/policy/fee/presentation/FeePolicyController.java index 2ff1f0f1..1799467d 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/presentation/FeePolicyController.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/presentation/FeePolicyController.java @@ -1,17 +1,15 @@ -package com.smore.payment.feepolicy.presentation; +package com.smore.payment.policy.fee.presentation; import com.smore.common.response.ApiResponse; -import com.smore.payment.feepolicy.application.FeePolicyService; -import com.smore.payment.feepolicy.application.command.CreateFeePolicyCommand; -import com.smore.payment.feepolicy.application.query.GetFeePolicyQuery; -import com.smore.payment.feepolicy.domain.model.FeePolicy; -import com.smore.payment.feepolicy.domain.model.TargetKeyLong; -import com.smore.payment.feepolicy.domain.model.TargetKeyUUID; -import com.smore.payment.feepolicy.domain.model.TargetType; -import com.smore.payment.feepolicy.presentation.dto.request.CreateFeePolicyRequestDto; -import com.smore.payment.feepolicy.presentation.dto.request.GetFeePolicyRequestDto; -import com.smore.payment.feepolicy.presentation.dto.response.GetFeePolicyResponseDto; -import com.smore.payment.global.config.UserContextHolder; +import com.smore.payment.policy.fee.application.FeePolicyService; +import com.smore.payment.policy.fee.application.query.GetFeePolicyQuery; +import com.smore.payment.policy.fee.domain.model.FeePolicy; +import com.smore.payment.policy.fee.domain.model.TargetKeyLong; +import com.smore.payment.policy.fee.domain.model.TargetKeyUUID; +import com.smore.payment.policy.fee.domain.model.TargetType; +import com.smore.payment.policy.fee.presentation.dto.request.CreateFeePolicyRequestDto; +import com.smore.payment.policy.fee.presentation.dto.response.GetFeePolicyResponseDto; +import com.smore.payment.shared.config.UserContextHolder; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/request/CreateFeePolicyRequestDto.java b/payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/request/CreateFeePolicyRequestDto.java similarity index 84% rename from payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/request/CreateFeePolicyRequestDto.java rename to payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/request/CreateFeePolicyRequestDto.java index b5f335fe..e9c6b6d0 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/request/CreateFeePolicyRequestDto.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/request/CreateFeePolicyRequestDto.java @@ -1,7 +1,7 @@ -package com.smore.payment.feepolicy.presentation.dto.request; +package com.smore.payment.policy.fee.presentation.dto.request; -import com.smore.payment.feepolicy.application.command.CreateFeePolicyCommand; -import com.smore.payment.feepolicy.domain.model.*; +import com.smore.payment.policy.fee.application.command.CreateFeePolicyCommand; +import com.smore.payment.policy.fee.domain.model.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/request/GetFeePolicyRequestDto.java b/payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/request/GetFeePolicyRequestDto.java similarity index 67% rename from payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/request/GetFeePolicyRequestDto.java rename to payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/request/GetFeePolicyRequestDto.java index 89e35985..4aacb254 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/request/GetFeePolicyRequestDto.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/request/GetFeePolicyRequestDto.java @@ -1,10 +1,10 @@ -package com.smore.payment.feepolicy.presentation.dto.request; +package com.smore.payment.policy.fee.presentation.dto.request; -import com.smore.payment.feepolicy.application.query.GetFeePolicyQuery; -import com.smore.payment.feepolicy.domain.model.TargetKey; -import com.smore.payment.feepolicy.domain.model.TargetKeyLong; -import com.smore.payment.feepolicy.domain.model.TargetKeyUUID; -import com.smore.payment.feepolicy.domain.model.TargetType; +import com.smore.payment.policy.fee.application.query.GetFeePolicyQuery; +import com.smore.payment.policy.fee.domain.model.TargetKey; +import com.smore.payment.policy.fee.domain.model.TargetKeyLong; +import com.smore.payment.policy.fee.domain.model.TargetKeyUUID; +import com.smore.payment.policy.fee.domain.model.TargetType; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/response/GetFeePolicyResponseDto.java b/payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/response/GetFeePolicyResponseDto.java similarity index 87% rename from payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/response/GetFeePolicyResponseDto.java rename to payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/response/GetFeePolicyResponseDto.java index 4240a827..48796429 100644 --- a/payment/src/main/java/com/smore/payment/feepolicy/presentation/dto/response/GetFeePolicyResponseDto.java +++ b/payment/src/main/java/com/smore/payment/policy/fee/presentation/dto/response/GetFeePolicyResponseDto.java @@ -1,6 +1,6 @@ -package com.smore.payment.feepolicy.presentation.dto.response; +package com.smore.payment.policy.fee.presentation.dto.response; -import com.smore.payment.feepolicy.domain.model.FeePolicy; +import com.smore.payment.policy.fee.domain.model.FeePolicy; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/application/RefundPolicyService.java b/payment/src/main/java/com/smore/payment/policy/refund/application/RefundPolicyService.java similarity index 84% rename from payment/src/main/java/com/smore/payment/refundpolicy/application/RefundPolicyService.java rename to payment/src/main/java/com/smore/payment/policy/refund/application/RefundPolicyService.java index 7ce4a327..5965a244 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/application/RefundPolicyService.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/application/RefundPolicyService.java @@ -1,9 +1,9 @@ -package com.smore.payment.refundpolicy.application; +package com.smore.payment.policy.refund.application; -import com.smore.payment.refundpolicy.application.command.CreateRefundPolicyCommand; -import com.smore.payment.refundpolicy.application.query.GetRefundPolicyQuery; -import com.smore.payment.refundpolicy.domain.model.RefundPolicy; -import com.smore.payment.refundpolicy.domain.repository.RefundPolicyRepository; +import com.smore.payment.policy.refund.application.command.CreateRefundPolicyCommand; +import com.smore.payment.policy.refund.application.query.GetRefundPolicyQuery; +import com.smore.payment.policy.refund.domain.model.RefundPolicy; +import com.smore.payment.policy.refund.domain.repository.RefundPolicyRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/application/command/CreateRefundPolicyCommand.java b/payment/src/main/java/com/smore/payment/policy/refund/application/command/CreateRefundPolicyCommand.java similarity index 58% rename from payment/src/main/java/com/smore/payment/refundpolicy/application/command/CreateRefundPolicyCommand.java rename to payment/src/main/java/com/smore/payment/policy/refund/application/command/CreateRefundPolicyCommand.java index cb7e2aad..9a2e5327 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/application/command/CreateRefundPolicyCommand.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/application/command/CreateRefundPolicyCommand.java @@ -1,10 +1,8 @@ -package com.smore.payment.refundpolicy.application.command; +package com.smore.payment.policy.refund.application.command; -import com.smore.payment.refundpolicy.domain.model.*; -import com.smore.payment.refundpolicy.presentation.dto.request.CreateRefundPolicyRequestDto; +import com.smore.payment.policy.refund.domain.model.*; import java.time.Duration; -import java.util.UUID; public record CreateRefundPolicyCommand( RefundTargetType refundTargetType, diff --git a/payment/src/main/java/com/smore/payment/policy/refund/application/query/GetRefundPolicyQuery.java b/payment/src/main/java/com/smore/payment/policy/refund/application/query/GetRefundPolicyQuery.java new file mode 100644 index 00000000..1a941296 --- /dev/null +++ b/payment/src/main/java/com/smore/payment/policy/refund/application/query/GetRefundPolicyQuery.java @@ -0,0 +1,9 @@ +package com.smore.payment.policy.refund.application.query; + +import com.smore.payment.policy.refund.domain.model.RefundTargetType; +import com.smore.payment.policy.refund.domain.model.TargetKey; + +public record GetRefundPolicyQuery( + RefundTargetType refundTargetType, + TargetKey targetKey +) {} diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFeeRate.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFeeRate.java similarity index 92% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFeeRate.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFeeRate.java index 16f793ee..1d4749a8 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFeeRate.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFeeRate.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; import java.math.BigDecimal; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFeeType.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFeeType.java similarity index 89% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFeeType.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFeeType.java index d57d4d2e..14b0ea71 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFeeType.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFeeType.java @@ -1,6 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; - -import lombok.Getter; +package com.smore.payment.policy.refund.domain.model; public enum RefundFeeType { RATE("비율 수수료 (예: 5%)"), diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFixedAmount.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFixedAmount.java similarity index 90% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFixedAmount.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFixedAmount.java index 012c942c..932f4e28 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundFixedAmount.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundFixedAmount.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; import java.math.BigDecimal; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundPolicy.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundPolicy.java similarity index 98% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundPolicy.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundPolicy.java index 12ee73ac..d410b05e 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundPolicy.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundPolicy.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; import java.time.Duration; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundTargetType.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundTargetType.java similarity index 86% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundTargetType.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundTargetType.java index 74d9fe89..ac84371a 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/RefundTargetType.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/RefundTargetType.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; public enum RefundTargetType { CATEGORY, MERCHANT, AUCTION_TYPE, USER_TYPE; diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKey.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKey.java similarity index 62% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKey.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKey.java index 34112a14..5473f981 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKey.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKey.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; public interface TargetKey { Object getTargetKey(); diff --git a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyLong.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyLong.java similarity index 81% rename from payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyLong.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyLong.java index 7f1a3427..951bf256 100644 --- a/payment/src/main/java/com/smore/payment/cancelpolicy/domain/model/TargetKeyLong.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyLong.java @@ -1,4 +1,4 @@ -package com.smore.payment.cancelpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; public record TargetKeyLong(Long value) implements TargetKey { @Override diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyString.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyString.java similarity index 80% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyString.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyString.java index 2faf3e17..e9922833 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyString.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyString.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; public record TargetKeyString(String value) implements TargetKey { @Override diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyUUID.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyUUID.java similarity index 82% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyUUID.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyUUID.java index 2f7f81b2..a5c942d1 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/model/TargetKeyUUID.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/model/TargetKeyUUID.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.domain.model; +package com.smore.payment.policy.refund.domain.model; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/domain/repository/RefundPolicyRepository.java b/payment/src/main/java/com/smore/payment/policy/refund/domain/repository/RefundPolicyRepository.java similarity index 66% rename from payment/src/main/java/com/smore/payment/refundpolicy/domain/repository/RefundPolicyRepository.java rename to payment/src/main/java/com/smore/payment/policy/refund/domain/repository/RefundPolicyRepository.java index 5a6b4a40..1f0427e0 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/domain/repository/RefundPolicyRepository.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/domain/repository/RefundPolicyRepository.java @@ -1,8 +1,8 @@ -package com.smore.payment.refundpolicy.domain.repository; +package com.smore.payment.policy.refund.domain.repository; -import com.smore.payment.refundpolicy.domain.model.RefundPolicy; -import com.smore.payment.refundpolicy.domain.model.RefundTargetType; -import com.smore.payment.refundpolicy.domain.model.TargetKey; +import com.smore.payment.policy.refund.domain.model.RefundPolicy; +import com.smore.payment.policy.refund.domain.model.RefundTargetType; +import com.smore.payment.policy.refund.domain.model.TargetKey; import org.springframework.stereotype.Repository; import java.util.Optional; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/mapper/RefundPolicyMapper.java b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/mapper/RefundPolicyMapper.java similarity index 84% rename from payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/mapper/RefundPolicyMapper.java rename to payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/mapper/RefundPolicyMapper.java index 878da9ab..013b0b5e 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/mapper/RefundPolicyMapper.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/mapper/RefundPolicyMapper.java @@ -1,9 +1,9 @@ -package com.smore.payment.refundpolicy.infrastructure.persistence.mapper; +package com.smore.payment.policy.refund.infrastructure.persistence.mapper; -import com.smore.payment.refundpolicy.domain.model.*; -import com.smore.payment.refundpolicy.infrastructure.persistence.model.RefundFeeRateJpa; -import com.smore.payment.refundpolicy.infrastructure.persistence.model.RefundFixedAmountJpa; -import com.smore.payment.refundpolicy.infrastructure.persistence.model.RefundPolicyEntity; +import com.smore.payment.policy.refund.domain.model.*; +import com.smore.payment.policy.refund.infrastructure.persistence.model.RefundFeeRateJpa; +import com.smore.payment.policy.refund.infrastructure.persistence.model.RefundFixedAmountJpa; +import com.smore.payment.policy.refund.infrastructure.persistence.model.RefundPolicyEntity; import org.springframework.stereotype.Component; import java.util.UUID; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundFeeRateJpa.java b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundFeeRateJpa.java similarity index 85% rename from payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundFeeRateJpa.java rename to payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundFeeRateJpa.java index 9dd59aab..0600c940 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundFeeRateJpa.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundFeeRateJpa.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.infrastructure.persistence.model; +package com.smore.payment.policy.refund.infrastructure.persistence.model; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundFixedAmountJpa.java b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundFixedAmountJpa.java similarity index 86% rename from payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundFixedAmountJpa.java rename to payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundFixedAmountJpa.java index 12694667..f00244f1 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundFixedAmountJpa.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundFixedAmountJpa.java @@ -1,4 +1,4 @@ -package com.smore.payment.refundpolicy.infrastructure.persistence.model; +package com.smore.payment.policy.refund.infrastructure.persistence.model; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundPolicyEntity.java b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundPolicyEntity.java similarity index 84% rename from payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundPolicyEntity.java rename to payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundPolicyEntity.java index 13e97894..04ebe739 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/model/RefundPolicyEntity.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/model/RefundPolicyEntity.java @@ -1,9 +1,8 @@ -package com.smore.payment.refundpolicy.infrastructure.persistence.model; +package com.smore.payment.policy.refund.infrastructure.persistence.model; -import com.smore.payment.global.entity.BaseEntity; -import com.smore.payment.refundpolicy.domain.model.RefundFeeType; -import com.smore.payment.refundpolicy.domain.model.RefundTargetType; -import com.smore.payment.refundpolicy.domain.model.TargetKey; +import com.smore.payment.shared.entity.BaseEntity; +import com.smore.payment.policy.refund.domain.model.RefundFeeType; +import com.smore.payment.policy.refund.domain.model.RefundTargetType; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -30,7 +29,7 @@ public class RefundPolicyEntity extends BaseEntity { @Column(name = "target_key", nullable = false, updatable = false) private String targetKey; - @Column(name = "refund_period_days") + @Column(name = "refund_period_days", columnDefinition = "interval") private Duration refundPeriodDays; @Enumerated(EnumType.STRING) diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/repository/RefundPolicyJpaRepository.java b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/repository/RefundPolicyJpaRepository.java similarity index 57% rename from payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/repository/RefundPolicyJpaRepository.java rename to payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/repository/RefundPolicyJpaRepository.java index 55cc0fdd..08db21b2 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/repository/RefundPolicyJpaRepository.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/repository/RefundPolicyJpaRepository.java @@ -1,7 +1,7 @@ -package com.smore.payment.refundpolicy.infrastructure.persistence.repository; +package com.smore.payment.policy.refund.infrastructure.persistence.repository; -import com.smore.payment.refundpolicy.domain.model.RefundTargetType; -import com.smore.payment.refundpolicy.infrastructure.persistence.model.RefundPolicyEntity; +import com.smore.payment.policy.refund.domain.model.RefundTargetType; +import com.smore.payment.policy.refund.infrastructure.persistence.model.RefundPolicyEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/repository/RefundPolicyRepositoryImpl.java b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/repository/RefundPolicyRepositoryImpl.java similarity index 77% rename from payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/repository/RefundPolicyRepositoryImpl.java rename to payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/repository/RefundPolicyRepositoryImpl.java index d9b075bf..467ea6f3 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/infrastructure/persistence/repository/RefundPolicyRepositoryImpl.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/infrastructure/persistence/repository/RefundPolicyRepositoryImpl.java @@ -1,12 +1,11 @@ -package com.smore.payment.refundpolicy.infrastructure.persistence.repository; - -import com.smore.payment.cancelpolicy.domain.model.CancelTargetType; -import com.smore.payment.refundpolicy.domain.model.RefundPolicy; -import com.smore.payment.refundpolicy.domain.model.RefundTargetType; -import com.smore.payment.refundpolicy.domain.model.TargetKey; -import com.smore.payment.refundpolicy.domain.repository.RefundPolicyRepository; -import com.smore.payment.refundpolicy.infrastructure.persistence.mapper.RefundPolicyMapper; -import com.smore.payment.refundpolicy.infrastructure.persistence.model.RefundPolicyEntity; +package com.smore.payment.policy.refund.infrastructure.persistence.repository; + +import com.smore.payment.policy.refund.domain.model.RefundPolicy; +import com.smore.payment.policy.refund.domain.model.RefundTargetType; +import com.smore.payment.policy.refund.domain.model.TargetKey; +import com.smore.payment.policy.refund.domain.repository.RefundPolicyRepository; +import com.smore.payment.policy.refund.infrastructure.persistence.mapper.RefundPolicyMapper; +import com.smore.payment.policy.refund.infrastructure.persistence.model.RefundPolicyEntity; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/presentation/RefundPolicyController.java b/payment/src/main/java/com/smore/payment/policy/refund/presentation/RefundPolicyController.java similarity index 80% rename from payment/src/main/java/com/smore/payment/refundpolicy/presentation/RefundPolicyController.java rename to payment/src/main/java/com/smore/payment/policy/refund/presentation/RefundPolicyController.java index c9037155..b62eecfa 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/presentation/RefundPolicyController.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/presentation/RefundPolicyController.java @@ -1,12 +1,12 @@ -package com.smore.payment.refundpolicy.presentation; +package com.smore.payment.policy.refund.presentation; import com.smore.common.response.ApiResponse; -import com.smore.payment.global.config.UserContextHolder; -import com.smore.payment.refundpolicy.application.RefundPolicyService; -import com.smore.payment.refundpolicy.application.query.GetRefundPolicyQuery; -import com.smore.payment.refundpolicy.domain.model.*; -import com.smore.payment.refundpolicy.presentation.dto.request.CreateRefundPolicyRequestDto; -import com.smore.payment.refundpolicy.presentation.dto.response.GetRefundPolicyResponseDto; +import com.smore.payment.policy.refund.domain.model.*; +import com.smore.payment.shared.config.UserContextHolder; +import com.smore.payment.policy.refund.application.RefundPolicyService; +import com.smore.payment.policy.refund.application.query.GetRefundPolicyQuery; +import com.smore.payment.policy.refund.presentation.dto.request.CreateRefundPolicyRequestDto; +import com.smore.payment.policy.refund.presentation.dto.response.GetRefundPolicyResponseDto; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/presentation/dto/request/CreateRefundPolicyRequestDto.java b/payment/src/main/java/com/smore/payment/policy/refund/presentation/dto/request/CreateRefundPolicyRequestDto.java similarity index 87% rename from payment/src/main/java/com/smore/payment/refundpolicy/presentation/dto/request/CreateRefundPolicyRequestDto.java rename to payment/src/main/java/com/smore/payment/policy/refund/presentation/dto/request/CreateRefundPolicyRequestDto.java index ee0a040b..138015a7 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/presentation/dto/request/CreateRefundPolicyRequestDto.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/presentation/dto/request/CreateRefundPolicyRequestDto.java @@ -1,7 +1,7 @@ -package com.smore.payment.refundpolicy.presentation.dto.request; +package com.smore.payment.policy.refund.presentation.dto.request; -import com.smore.payment.refundpolicy.application.command.CreateRefundPolicyCommand; -import com.smore.payment.refundpolicy.domain.model.*; +import com.smore.payment.policy.refund.application.command.CreateRefundPolicyCommand; +import com.smore.payment.policy.refund.domain.model.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/presentation/dto/response/GetRefundPolicyResponseDto.java b/payment/src/main/java/com/smore/payment/policy/refund/presentation/dto/response/GetRefundPolicyResponseDto.java similarity index 89% rename from payment/src/main/java/com/smore/payment/refundpolicy/presentation/dto/response/GetRefundPolicyResponseDto.java rename to payment/src/main/java/com/smore/payment/policy/refund/presentation/dto/response/GetRefundPolicyResponseDto.java index 403c0518..f470418b 100644 --- a/payment/src/main/java/com/smore/payment/refundpolicy/presentation/dto/response/GetRefundPolicyResponseDto.java +++ b/payment/src/main/java/com/smore/payment/policy/refund/presentation/dto/response/GetRefundPolicyResponseDto.java @@ -1,6 +1,6 @@ -package com.smore.payment.refundpolicy.presentation.dto.response; +package com.smore.payment.policy.refund.presentation.dto.response; -import com.smore.payment.refundpolicy.domain.model.RefundPolicy; +import com.smore.payment.policy.refund.domain.model.RefundPolicy; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/payment/src/main/java/com/smore/payment/refundpolicy/application/query/GetRefundPolicyQuery.java b/payment/src/main/java/com/smore/payment/refundpolicy/application/query/GetRefundPolicyQuery.java deleted file mode 100644 index 81647010..00000000 --- a/payment/src/main/java/com/smore/payment/refundpolicy/application/query/GetRefundPolicyQuery.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.smore.payment.refundpolicy.application.query; - -import com.smore.payment.refundpolicy.domain.model.RefundTargetType; -import com.smore.payment.refundpolicy.domain.model.TargetKey; - -public record GetRefundPolicyQuery( - RefundTargetType refundTargetType, - TargetKey targetKey -) {} diff --git a/payment/src/main/java/com/smore/payment/global/config/AuditorAwareImpl.java b/payment/src/main/java/com/smore/payment/shared/config/AuditorAwareImpl.java similarity index 85% rename from payment/src/main/java/com/smore/payment/global/config/AuditorAwareImpl.java rename to payment/src/main/java/com/smore/payment/shared/config/AuditorAwareImpl.java index 41dc65b1..837447e8 100644 --- a/payment/src/main/java/com/smore/payment/global/config/AuditorAwareImpl.java +++ b/payment/src/main/java/com/smore/payment/shared/config/AuditorAwareImpl.java @@ -1,11 +1,10 @@ -package com.smore.payment.global.config; +package com.smore.payment.shared.config; import lombok.NonNull; import org.springframework.data.domain.AuditorAware; import org.springframework.stereotype.Component; import java.util.Optional; -import java.util.UUID; @Component public class AuditorAwareImpl implements AuditorAware { diff --git a/payment/src/main/java/com/smore/payment/global/config/JpaAuditingConfig.java b/payment/src/main/java/com/smore/payment/shared/config/JpaAuditingConfig.java similarity index 83% rename from payment/src/main/java/com/smore/payment/global/config/JpaAuditingConfig.java rename to payment/src/main/java/com/smore/payment/shared/config/JpaAuditingConfig.java index 09a9d1df..c41012f1 100644 --- a/payment/src/main/java/com/smore/payment/global/config/JpaAuditingConfig.java +++ b/payment/src/main/java/com/smore/payment/shared/config/JpaAuditingConfig.java @@ -1,4 +1,4 @@ -package com.smore.payment.global.config; +package com.smore.payment.shared.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/payment/src/main/java/com/smore/payment/global/config/UserContextHolder.java b/payment/src/main/java/com/smore/payment/shared/config/UserContextHolder.java similarity index 81% rename from payment/src/main/java/com/smore/payment/global/config/UserContextHolder.java rename to payment/src/main/java/com/smore/payment/shared/config/UserContextHolder.java index 5b19e4ab..c76b7ed5 100644 --- a/payment/src/main/java/com/smore/payment/global/config/UserContextHolder.java +++ b/payment/src/main/java/com/smore/payment/shared/config/UserContextHolder.java @@ -1,6 +1,4 @@ -package com.smore.payment.global.config; - -import java.util.UUID; +package com.smore.payment.shared.config; public class UserContextHolder { private static final ThreadLocal userIdHolder = new ThreadLocal<>(); diff --git a/payment/src/main/java/com/smore/payment/global/entity/BaseEntity.java b/payment/src/main/java/com/smore/payment/shared/entity/BaseEntity.java similarity index 93% rename from payment/src/main/java/com/smore/payment/global/entity/BaseEntity.java rename to payment/src/main/java/com/smore/payment/shared/entity/BaseEntity.java index 05721a4b..3a481248 100644 --- a/payment/src/main/java/com/smore/payment/global/entity/BaseEntity.java +++ b/payment/src/main/java/com/smore/payment/shared/entity/BaseEntity.java @@ -1,10 +1,9 @@ -package com.smore.payment.global.entity; +package com.smore.payment.shared.entity; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; -import java.util.UUID; import lombok.Getter; import org.springframework.data.annotation.CreatedBy; diff --git a/payment/src/main/java/com/smore/payment/global/outbox/OutboxMessage.java b/payment/src/main/java/com/smore/payment/shared/outbox/OutboxMessage.java similarity index 78% rename from payment/src/main/java/com/smore/payment/global/outbox/OutboxMessage.java rename to payment/src/main/java/com/smore/payment/shared/outbox/OutboxMessage.java index 7b712b4f..4a4e069f 100644 --- a/payment/src/main/java/com/smore/payment/global/outbox/OutboxMessage.java +++ b/payment/src/main/java/com/smore/payment/shared/outbox/OutboxMessage.java @@ -1,9 +1,5 @@ -package com.smore.payment.global.outbox; +package com.smore.payment.shared.outbox; -import com.smore.payment.payment.application.event.outbound.PaymentApprovedEvent; -import com.smore.payment.payment.application.event.outbound.PaymentFailedEvent; -import com.smore.payment.global.util.JsonUtil; -import com.smore.payment.payment.application.event.outbound.SettlementCalculatedEvent; import lombok.Getter; import java.time.LocalDateTime; diff --git a/payment/src/main/java/com/smore/payment/global/outbox/OutboxMessageCreator.java b/payment/src/main/java/com/smore/payment/shared/outbox/OutboxMessageCreator.java similarity index 71% rename from payment/src/main/java/com/smore/payment/global/outbox/OutboxMessageCreator.java rename to payment/src/main/java/com/smore/payment/shared/outbox/OutboxMessageCreator.java index 056ebe71..a3847ede 100644 --- a/payment/src/main/java/com/smore/payment/global/outbox/OutboxMessageCreator.java +++ b/payment/src/main/java/com/smore/payment/shared/outbox/OutboxMessageCreator.java @@ -1,7 +1,13 @@ -package com.smore.payment.global.outbox; - -import com.smore.payment.global.util.JsonUtil; -import com.smore.payment.payment.application.event.outbound.*; +package com.smore.payment.shared.outbox; + +import com.smore.payment.shared.util.JsonUtil; +import com.smore.payment.payment.application.event.outbound.PaymentFailedEvent; +import com.smore.payment.payment.application.event.outbound.PaymentRefundFailedEvent; +import com.smore.payment.payment.application.event.outbound.PaymentRefundSucceededEvent; +import com.smore.payment.payment.application.event.outbound.SettlementFailedEvent; +import com.smore.payment.payment.application.event.outbound.SettlementSuccessEvent; +import com.smore.payment.payment.domain.event.PaymentApprovedEvent; +import com.smore.payment.payment.domain.event.SettlementCalculatedEvent; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -30,6 +36,9 @@ public class OutboxMessageCreator { @Value("${topic.order.refund-fail}") private String orderRefundFailTopic; + @Value("${topic.order.refund-dlt}") + private String orderRefundDltTopic; + @Value("${topic.seller.success}") private String sellerSuccessTopic; @@ -68,7 +77,7 @@ public OutboxMessage paymentFailed(PaymentFailedEvent event) { public OutboxMessage settlementCalculated(SettlementCalculatedEvent event) { return new OutboxMessage( "SETTLEMENT", - UUID.randomUUID(), + event.idempotencyKey(), event.getClass().getSimpleName(), event.idempotencyKey(), sellerApprovedTopic, @@ -107,7 +116,7 @@ public OutboxMessage paymentRefunded(PaymentRefundSucceededEvent event) { public OutboxMessage settlementCompleted(SettlementSuccessEvent event) { return new OutboxMessage( "SETTLEMENT_SUCCESS", - UUID.randomUUID(), + event.idempotencyKey(), event.getClass().getSimpleName(), UUID.randomUUID(), sellerSuccessTopic, @@ -120,7 +129,7 @@ public OutboxMessage settlementCompleted(SettlementSuccessEvent event) { public OutboxMessage settlementFailed(SettlementFailedEvent event) { return new OutboxMessage( "SETTLEMENT_FAILED", - UUID.randomUUID(), + event.idempotencyKey(), event.getClass().getSimpleName(), UUID.randomUUID(), sellerFailedTopic, @@ -133,7 +142,7 @@ public OutboxMessage settlementFailed(SettlementFailedEvent event) { public OutboxMessage settlementDlt(SettlementFailedEvent event) { return new OutboxMessage( "SETTLEMENT_FAILED", - UUID.randomUUID(), + event.idempotencyKey(), event.getClass().getSimpleName(), UUID.randomUUID(), sellerDltTopic, @@ -142,4 +151,30 @@ public OutboxMessage settlementDlt(SettlementFailedEvent event) { OutboxStatus.FAILED ); } + + public OutboxMessage refundDlt(PaymentRefundFailedEvent event) { + return new OutboxMessage( + "REFUND_FAILED", + event.orderId(), + event.getClass().getSimpleName(), + UUID.randomUUID(), + orderRefundDltTopic, + jsonUtil.jsonToString(event), + 3, + OutboxStatus.FAILED + ); + } + + public OutboxMessage refundRetry(PaymentRefundFailedEvent event) { + return new OutboxMessage( + "REFUND_RETRY", + event.refundId(), + event.getClass().getSimpleName(), + UUID.randomUUID(), + "order.refund.v1", + jsonUtil.jsonToString(event), + 3, + OutboxStatus.FAILED + ); + }; } diff --git a/payment/src/main/java/com/smore/payment/global/outbox/OutboxStatus.java b/payment/src/main/java/com/smore/payment/shared/outbox/OutboxStatus.java similarity index 60% rename from payment/src/main/java/com/smore/payment/global/outbox/OutboxStatus.java rename to payment/src/main/java/com/smore/payment/shared/outbox/OutboxStatus.java index 950567d1..6475fd56 100644 --- a/payment/src/main/java/com/smore/payment/global/outbox/OutboxStatus.java +++ b/payment/src/main/java/com/smore/payment/shared/outbox/OutboxStatus.java @@ -1,4 +1,4 @@ -package com.smore.payment.global.outbox; +package com.smore.payment.shared.outbox; public enum OutboxStatus { PENDING, diff --git a/payment/src/main/java/com/smore/payment/global/util/JsonUtil.java b/payment/src/main/java/com/smore/payment/shared/util/JsonUtil.java similarity index 95% rename from payment/src/main/java/com/smore/payment/global/util/JsonUtil.java rename to payment/src/main/java/com/smore/payment/shared/util/JsonUtil.java index 92e1cf77..a9c9b8a3 100644 --- a/payment/src/main/java/com/smore/payment/global/util/JsonUtil.java +++ b/payment/src/main/java/com/smore/payment/shared/util/JsonUtil.java @@ -1,4 +1,4 @@ -package com.smore.payment.global.util; +package com.smore.payment.shared.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/payment/src/main/resources/application-dev.yml b/payment/src/main/resources/application-dev.yml index 3db15768..1e6d1172 100644 --- a/payment/src/main/resources/application-dev.yml +++ b/payment/src/main/resources/application-dev.yml @@ -6,6 +6,7 @@ spring: activate: on-profile: dev import: "optional:configserver:" + cloud: config: discovery: @@ -19,7 +20,7 @@ spring: sql: init: - mode: always + mode: never datasource: url: jdbc:postgresql://postgresql:5432/payment @@ -31,24 +32,36 @@ spring: minimum-idle: 2 idle-timeout: 30000 pool-name: HikariPool + + flyway: + enabled: true + locations: classpath:db/migration + baseline-on-migrate: true + validate-on-migrate: true + data: mongodb: - uri: mongodb://mongo_user:mongo_pass@mongo:27017/my_mongo_db?authSource=admin + uri: mongodb://root:examplepassword@mongodb:27017/default?authSource=admin auto-index-creation: true redis: host: redis-stack port: 6379 - + password: "" + database: 0 + ssl: + enabled: false jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect hibernate: - ddl-auto: create-drop + ddl-auto: validate show-sql: true properties: hibernate: format_sql: true - defer-datasource-initialization: true + type: + preferred_duration_jdbc_type: INTERVAL_SECOND + kafka: # 로컬에서 Docker로 띄운 브로커 외부 포트에 맞춰 설정 (docker-compose의 EXTERNAL 매핑) bootstrap-servers: kafka-1:9092,kafka-2:9092,kafka-3:9092 @@ -89,6 +102,7 @@ topic: failed: payment.failed.v1 refund: payment.refund.v1 refund-fail: payment.refund-fail.v1 + refund-dlt: payment.refund-dlt.v1 seller: approved: payment.settlement.v1 success: payment.settlement-success.v1 diff --git a/payment/src/main/resources/application.yml b/payment/src/main/resources/application.yml index 14d84f84..aaf38ad4 100644 --- a/payment/src/main/resources/application.yml +++ b/payment/src/main/resources/application.yml @@ -2,11 +2,15 @@ spring: application: name: payment-service - management: endpoints: web: - discovery: - enabled: true exposure: - include: "*" \ No newline at end of file + include: prometheus,health,info + metrics: + tags: + application: payment-service + endpoint: + health: + show-details: always + show-components: always \ No newline at end of file diff --git a/payment/src/main/resources/db/migration/V1__init_payment_schema.sql b/payment/src/main/resources/db/migration/V1__init_payment_schema.sql new file mode 100644 index 00000000..5ffd8d9d --- /dev/null +++ b/payment/src/main/resources/db/migration/V1__init_payment_schema.sql @@ -0,0 +1,130 @@ +CREATE TABLE fee_policies +( + id UUID PRIMARY KEY, + target_type VARCHAR(50) NOT NULL, + target_key VARCHAR(255) NOT NULL, + fee_type VARCHAR(50) NOT NULL, + rate NUMERIC(10, 4), + fixed_amount NUMERIC(19, 2), + active BOOLEAN NOT NULL, + create_by BIGINT, + created_at TIMESTAMP, + updated_by BIGINT, + updated_at TIMESTAMP, + deleted_by BIGINT, + deleted_at TIMESTAMP +); + +CREATE TABLE cancel_policies +( + id UUID PRIMARY KEY, + cancel_target_type VARCHAR(50) NOT NULL, + target_key VARCHAR(255) NOT NULL, + cancel_limit_minutes INTERVAL, + cancel_fee_type VARCHAR(50) NOT NULL, + rate NUMERIC(10, 4), + fixed_amount NUMERIC(19, 2), + cancellable BOOLEAN NOT NULL, + active BOOLEAN NOT NULL, + create_by BIGINT, + created_at TIMESTAMP, + updated_by BIGINT, + updated_at TIMESTAMP, + deleted_by BIGINT, + deleted_at TIMESTAMP +); + +CREATE TABLE refund_policies +( + id UUID PRIMARY KEY, + refund_target_type VARCHAR(50) NOT NULL, + target_key VARCHAR(255) NOT NULL, + refund_period_days INTERVAL, + refund_fee_type VARCHAR(50) NOT NULL, + rate NUMERIC(10, 4), + fixed_amount NUMERIC(19, 2), + refundable BOOLEAN NOT NULL, + active BOOLEAN NOT NULL, + create_by BIGINT, + created_at TIMESTAMP, + updated_by BIGINT, + updated_at TIMESTAMP, + deleted_by BIGINT, + deleted_at TIMESTAMP +); + +CREATE TABLE payments +( + id UUID PRIMARY KEY, + idempotency_key UUID NOT NULL, + user_id BIGINT NOT NULL, + order_id UUID NOT NULL, + amount NUMERIC(19, 2) NOT NULL, + payment_method VARCHAR(50), + status VARCHAR(50), + approved_at TIMESTAMP, + card_issuer_code VARCHAR(50), + card_acquirer_code VARCHAR(50), + card_number VARCHAR(50), + installment_months INTEGER, + interest_free BOOLEAN, + approve_no VARCHAR(50), + card_type VARCHAR(50), + card_owner_type VARCHAR(50), + card_acquire_status VARCHAR(50), + card_amount NUMERIC(19, 2), + failure_reason VARCHAR(255), + failed_at TIMESTAMP, + cancel_reason VARCHAR(255), + cancel_amount NUMERIC(19, 2), + cancelled_at TIMESTAMP, + refund_reason VARCHAR(255), + refund_amount NUMERIC(19, 2), + refunded_at TIMESTAMP, + pg_cancel_transaction_key VARCHAR(255), + refundable_amount NUMERIC(19, 2), + pg_provider VARCHAR(100), + payment_key VARCHAR(100), + pg_order_id VARCHAR(100), + pg_order_name VARCHAR(255), + pg_transaction_key VARCHAR(100), + pg_status VARCHAR(100), + currency VARCHAR(10), + pg_total_amount NUMERIC(19, 2), + pg_balance_amount NUMERIC(19, 2), + seller_id BIGINT NOT NULL, + category UUID NOT NULL, + auction_type VARCHAR(50), + CONSTRAINT uk_payments_idempotency_key UNIQUE (idempotency_key) +); + +CREATE TABLE outboxes +( + id BIGSERIAL PRIMARY KEY, + aggregate_type VARCHAR(100), + aggregate_id UUID, + event_type VARCHAR(100), + idempotency_key UUID, + topic_name VARCHAR(255), + payload TEXT, + retry_count INTEGER, + status VARCHAR(50), + created_at TIMESTAMP +); + +CREATE TABLE refund_inbox +( + id BIGSERIAL PRIMARY KEY, + version BIGINT, + refund_id UUID NOT NULL, + order_id UUID NOT NULL, + payment_id UUID NOT NULL, + idempotency_key UUID, + status VARCHAR(50) NOT NULL, + retry_count INTEGER NOT NULL, + last_error VARCHAR(255), + pg_refund_key VARCHAR(255), + created_at TIMESTAMP, + updated_at TIMESTAMP, + CONSTRAINT uk_refund_inbox_refund_id UNIQUE (refund_id) +); \ No newline at end of file diff --git a/payment/src/main/resources/data.sql b/payment/src/main/resources/db/migration/V2__insert_initial_data.sql similarity index 54% rename from payment/src/main/resources/data.sql rename to payment/src/main/resources/db/migration/V2__insert_initial_data.sql index bd7eec02..df02559f 100644 --- a/payment/src/main/resources/data.sql +++ b/payment/src/main/resources/db/migration/V2__insert_initial_data.sql @@ -12,17 +12,28 @@ INSERT INTO fee_policies ( created_at, updated_at ) VALUES - ( - '1b5e6f7a-8b9c-4dad-9e0f-1a2b3c4d5e6f', - 'CATEGORY', - '11111111-1111-1111-1111-111111111111', - 'RATE', - 0.02, - NULL, - true, - now(), - now() - ); + ( + gen_random_uuid(), + 'MERCHANT', + '2001', + 'RATE', + 0.02, + 500.0, + true, + now(), + now() + ), + ( + gen_random_uuid(), + 'MERCHANT', + '2002', + 'RATE', + 0.02, + 500.0, + true, + now(), + now() + ); -- Cancel policies INSERT INTO cancel_policies ( @@ -39,10 +50,23 @@ INSERT INTO cancel_policies ( updated_at ) VALUES ( - '3d8e9f0a-1b2c-4d3e-9f4a-5b6c7d8e9f0a', + gen_random_uuid(), + 'MERCHANT', + '2001', + interval '30 minutes', -- 3 days = 259,200 seconds = 259,200,000,000,000 ns + 'MIXED', + 0.04, + 300, + false, + true, + now(), + now() + ), + ( + gen_random_uuid(), 'AUCTION_TYPE', 'BID', - 1800000000000, -- 30 minutes = 1800초 = 1,800,000,000,000 ns + interval '30 minutes', -- 30 minutes = 1800초 = 1,800,000,000,000 ns 'MIXED', 0.03, 250, @@ -52,10 +76,10 @@ INSERT INTO cancel_policies ( now() ), ( - '3d8e9f0a-1b2c-4d3e-9f4a-5b6c7d8e9f0b', + gen_random_uuid(), 'AUCTION_TYPE', 'AUCTION', - 10000000000, -- 1 minutes = 1800초 = 1,800,000,000,000 ns + interval '30 minutes', -- 1 minutes = 1800초 = 1,800,000,000,000 ns 'RATE', 100.0, 0.0, @@ -80,23 +104,36 @@ INSERT INTO refund_policies ( updated_at ) VALUES ( - '5f0a1b2c-3d4e-5f6a-9b7c-8d9e0f1a2b3c', + gen_random_uuid(), + 'MERCHANT', + '2001', + interval '3 days', -- 3 days = 259,200 seconds = 259,200,000,000,000 ns + 'MIXED', + 0.04, + 300, + false, + true, + now(), + now() + ), + ( + gen_random_uuid(), 'AUCTION_TYPE', 'BID', - 259200000000000, -- 3 days = 259,200 seconds = 259,200,000,000,000 ns + interval '3 days', -- 3 days = 259,200 seconds = 259,200,000,000,000 ns 'MIXED', 0.04, 300, - false, + true, true, now(), now() ), ( - '5f0a1b2c-3d4e-5f6a-9b7c-8d9e0f1a2b3d', + gen_random_uuid(), 'AUCTION_TYPE', 'AUCTION', - 259200000000000, -- 3 days = 259,200 seconds = 259,200,000,000,000 ns + interval '3 days', -- 3 days = 259,200 seconds = 259,200,000,000,000 ns 'MIXED', 0.04, 300,