1111import lombok .RequiredArgsConstructor ;
1212import lombok .extern .slf4j .Slf4j ;
1313import org .springframework .stereotype .Component ;
14+ import org .springframework .transaction .annotation .Propagation ;
15+ import org .springframework .transaction .annotation .Transactional ;
1416
1517import java .time .LocalDateTime ;
1618import java .util .List ;
@@ -26,7 +28,8 @@ public class PaymentService {
2628 private final OrderRepository orderRepository ;
2729 private final PgGateway pgGateway ;
2830
29- public PaymentInfo requestPayment (String userId , PaymentCommand .RequestPayment command ) {
31+ @ Transactional
32+ public Payment createPayment (String userId , PaymentCommand .RequestPayment command ) {
3033 log .info ("[Payment] 결제 요청 시작 - orderNo: {}, amount: {}" , command .orderId (), command .amount ());
3134
3235 Order order = orderRepository .findByOrderNo (command .orderId ())
@@ -60,35 +63,33 @@ public PaymentInfo requestPayment(String userId, PaymentCommand.RequestPayment c
6063 throw new CoreException (ErrorType .CONFLICT , "이미 처리 중인 결제입니다: " + order .getOrderNo ());
6164 }
6265 log .info ("[Payment] Payment 엔티티 생성 - id: {}, orderNo: {}" , payment .getId (), order .getOrderNo ());
66+ return payment ;
67+ }
6368
64- try {
65- PgGateway .PgPaymentCommand pgCommand = new PgGateway .PgPaymentCommand (
66- order .getOrderNo (),
67- command .cardType (),
68- command .cardNo (),
69- command .amount (),
70- command .callbackUrl ()
71- );
72-
73- CompletableFuture <PgGateway .PgPaymentResult > future = pgGateway .requestPayment (userId , pgCommand );
74- PgGateway .PgPaymentResult pgResult = future .get (3 , TimeUnit .SECONDS );
69+ public PaymentInfo requestPayment (String userId , PaymentCommand .RequestPayment command ) {
70+ Payment payment = createPayment (userId , command );
7571
76- if ( pgResult . transactionKey () != null ) {
77- payment . assignTransactionKey ( pgResult . transactionKey ());
78- log . info ( "[Payment] PG 결제 요청 성공 - transactionKey: {}" , pgResult . transactionKey ());
79- } else {
80- payment . markAsRequiresRetry ();
81- log . warn ( "[Payment] PG 장애로 재시도 필요 - orderNo: {}" , order . getOrderNo ());
82- }
72+ PgGateway . PgPaymentCommand pgCommand = new PgGateway . PgPaymentCommand (
73+ command . orderId (),
74+ command . cardType (),
75+ command . cardNo (),
76+ command . amount (),
77+ command . callbackUrl ()
78+ );
8379
80+ try {
81+ CompletableFuture <PgGateway .PgPaymentResult > future = pgGateway .requestPayment (userId , pgCommand );
82+ PgGateway .PgPaymentResult pgResult = future .get (2 , TimeUnit .SECONDS );
83+ applyPgResult (payment .getId (), pgResult );
8484 } catch (Exception e ) {
85- log .error ("[Payment] PG 결제 요청 실패 - orderNo: {}" , order . getOrderNo (), e );
86- payment .markAsRequiresRetry ( );
85+ log .error ("[Payment] PG 결제 요청 실패 - orderNo: {}" , command . orderId (), e );
86+ markRequiresRetry ( payment .getId () );
8787 }
8888
89- return PaymentInfo .from (payment );
89+ return PaymentInfo .from (findById ( payment . getId ()) );
9090 }
9191
92+ @ Transactional
9293 public PaymentInfo processCallback (String userId , PaymentCommand .ProcessCallback command ) {
9394 log .info ("[Payment] 콜백 처리 시작 - transactionKey: {}, status: {}" ,
9495 command .transactionKey (), command .status ());
@@ -116,16 +117,14 @@ public PaymentInfo syncPaymentStatus(String userId, String transactionKey) {
116117 try {
117118 PgGateway .PgPaymentDetail pgDetail = pgGateway .getPaymentStatus (userId , transactionKey );
118119 PaymentStatus pgStatus = PaymentStatus .valueOf (pgDetail .status ().name ());
119- payment .updateFromPg (pgStatus , pgDetail .reason ());
120-
120+ applyPgDetail (payment .getId (), pgStatus , pgDetail .reason ());
121121 log .info ("[Payment] 상태 동기화 완료 - orderNo: {}, status: {}" ,
122- payment .getOrder ().getOrderNo (), payment .getStatus ());
123-
122+ payment .getOrder ().getOrderNo (), pgStatus );
124123 } catch (Exception e ) {
125124 log .error ("[Payment] 상태 조회 실패 - transactionKey: {}" , transactionKey , e );
126125 }
127126
128- return PaymentInfo .from (payment );
127+ return PaymentInfo .from (findById ( payment . getId ()) );
129128 }
130129
131130 public PaymentInfo getPaymentByOrderNo (String orderNo ) {
@@ -151,4 +150,60 @@ public List<PaymentInfo> getPaymentsRequiringRetry() {
151150 .map (PaymentInfo ::from )
152151 .toList ();
153152 }
153+
154+ @ Transactional (readOnly = true )
155+ public Payment findByOrderNo (String orderNo ) {
156+ Order order = orderRepository .findByOrderNo (orderNo )
157+ .orElseThrow (() -> new CoreException (ErrorType .NOT_FOUND ,
158+ "주문을 찾을 수 없습니다: " + orderNo ));
159+ return paymentRepository .findByOrder (order )
160+ .orElseThrow (() -> new CoreException (ErrorType .NOT_FOUND ,
161+ "결제 정보를 찾을 수 없습니다: " + orderNo ));
162+ }
163+
164+ @ Transactional (propagation = Propagation .REQUIRES_NEW )
165+ public Payment applyPgResult (Long paymentId , PgGateway .PgPaymentResult pgResult ) {
166+ Payment payment = findById (paymentId );
167+
168+ if (pgResult .transactionKey () != null ) {
169+ if (!payment .hasTransactionKey ()) {
170+ payment .assignTransactionKey (pgResult .transactionKey ());
171+ log .info ("[Payment] PG 결제 요청 성공 - transactionKey: {}" , pgResult .transactionKey ());
172+ } else {
173+ log .info ("[Payment] PG 결제 요청 성공(기존 key 유지) - transactionKey: {}" , payment .getTransactionKey ());
174+ }
175+ } else {
176+ payment .markAsRequiresRetry ();
177+ log .warn ("[Payment] PG 장애로 재시도 필요 - orderNo: {}" , payment .getOrder ().getOrderNo ());
178+ }
179+ return payment ;
180+ }
181+
182+ @ Transactional (propagation = Propagation .REQUIRES_NEW )
183+ public Payment applyPgDetail (Long paymentId , PaymentStatus pgStatus , String reason ) {
184+ Payment payment = findById (paymentId );
185+ payment .updateFromPg (pgStatus , reason );
186+ return payment ;
187+ }
188+
189+ @ Transactional (propagation = Propagation .REQUIRES_NEW )
190+ public Payment markRequiresRetry (Long paymentId ) {
191+ Payment payment = findById (paymentId );
192+ payment .markAsRequiresRetry ();
193+ return payment ;
194+ }
195+
196+ @ Transactional (readOnly = true )
197+ public Payment findById (Long paymentId ) {
198+ return paymentRepository .findById (paymentId )
199+ .orElseThrow (() -> new CoreException (ErrorType .NOT_FOUND ,
200+ "결제 정보를 찾을 수 없습니다: id=" + paymentId ));
201+ }
202+
203+ @ Transactional (propagation = Propagation .REQUIRES_NEW )
204+ public Payment cancelPayment (Long paymentId , String reason ) {
205+ Payment payment = findById (paymentId );
206+ payment .markAsCancelled (reason );
207+ return payment ;
208+ }
154209}
0 commit comments