Skip to content

Commit d889683

Browse files
authored
Fix: PaymentLog와 Payment를 연결하여 결제 건 매칭이 가능하도록 수정 (#417)
1 parent a99c3ed commit d889683

5 files changed

Lines changed: 135 additions & 1 deletion

File tree

roome/src/main/java/com/roome/domain/payment/dto/PaymentLogResponseDto.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.roome.domain.payment.dto;
22

3+
import com.roome.domain.payment.entity.Payment;
34
import com.roome.domain.payment.entity.PaymentLog;
45
import com.roome.domain.payment.entity.PaymentStatus;
56
import lombok.AllArgsConstructor;
@@ -16,15 +17,20 @@
1617
public class PaymentLogResponseDto {
1718

1819
private Long id;
20+
private Long paymentId; // 연결된 결제 건 ID (매칭/추적용)
21+
private String orderId; // 연결된 결제의 주문 ID
1922
private int amount;
2023
private int earnedPoints;
2124
private String paymentKey;
2225
private LocalDateTime createdAt;
2326
private PaymentStatus status;
2427

2528
public static PaymentLogResponseDto from(PaymentLog paymentLog, PaymentStatus status) {
29+
Payment payment = paymentLog.getPayment();
2630
return PaymentLogResponseDto.builder()
2731
.id(paymentLog.getId())
32+
.paymentId(payment != null ? payment.getId() : null)
33+
.orderId(payment != null ? payment.getOrderId() : null)
2834
.amount(paymentLog.getAmount())
2935
.earnedPoints(paymentLog.getEarnedPoints())
3036
.paymentKey(paymentLog.getPaymentKey())

roome/src/main/java/com/roome/domain/payment/entity/PaymentLog.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ public class PaymentLog {
2222
@JoinColumn(name = "user_id", nullable = false)
2323
private User user; // 결제한 사용자
2424

25+
// 이 로그가 속한 결제 건 (로그를 Payment와 매칭하기 위한 연결)
26+
// (기존 로그 호환을 위해 nullable, 신규 로그는 항상 설정)
27+
@ManyToOne(fetch = FetchType.LAZY)
28+
@JoinColumn(name = "payment_id")
29+
private Payment payment;
30+
2531
@Column(nullable = false)
2632
private int amount; // 결제 금액
2733

roome/src/main/java/com/roome/domain/payment/repository/PaymentLogRepository.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66
import org.springframework.data.domain.Page;
77
import org.springframework.data.domain.Pageable;
88
import org.springframework.data.jpa.repository.JpaRepository;
9+
import org.springframework.data.jpa.repository.Query;
10+
import org.springframework.data.repository.query.Param;
911

1012
public interface PaymentLogRepository extends JpaRepository<PaymentLog, Long> {
1113

1214
// 특정 사용자(userId)의 결제 기록 조회
1315
List<PaymentLog> findByUserId(Long userId);
1416

17+
// 연결된 결제(payment)를 함께 조회하여 조회 시 N+1을 방지한다.
18+
// 기존 로그(payment=null) 호환을 위해 LEFT JOIN FETCH를 사용한다.
19+
@Query(value = "SELECT pl FROM PaymentLog pl LEFT JOIN FETCH pl.payment WHERE pl.user = :user",
20+
countQuery = "SELECT COUNT(pl) FROM PaymentLog pl WHERE pl.user = :user")
21+
Page<PaymentLog> findByUserWithPayment(@Param("user") User user, Pageable pageable);
22+
1523
Page<PaymentLog> findByUser(User user, Pageable pageable);
1624

1725
// 특정 결제 키(paymentKey)로 결제 내역 조회

roome/src/main/java/com/roome/domain/payment/service/PaymentService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public List<PaymentLogResponseDto> getPaymentHistory(Long userId, int page, int
331331
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
332332

333333
PageRequest pageRequest = PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createdAt"));
334-
Page<PaymentLog> paymentLogs = paymentLogRepository.findByUser(user, pageRequest);
334+
Page<PaymentLog> paymentLogs = paymentLogRepository.findByUserWithPayment(user, pageRequest);
335335

336336
return paymentLogs.stream()
337337
.map(log -> {
@@ -347,6 +347,7 @@ public List<PaymentLogResponseDto> getPaymentHistory(Long userId, int page, int
347347
private void savePaymentLog(Payment payment, String paymentKey) {
348348
PaymentLog paymentLog = PaymentLog.builder()
349349
.user(payment.getUser())
350+
.payment(payment)
350351
.amount(payment.getAmount())
351352
.earnedPoints(payment.getPurchasedPoints())
352353
.paymentKey(paymentKey)
@@ -357,6 +358,7 @@ private void savePaymentLog(Payment payment, String paymentKey) {
357358
private void saveRefundLog(Payment payment, int refundAmount, String paymentKey) {
358359
PaymentLog refundLog = PaymentLog.builder()
359360
.user(payment.getUser())
361+
.payment(payment)
360362
.amount(-refundAmount)
361363
.earnedPoints(-payment.getPurchasedPoints())
362364
.paymentKey(paymentKey)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.roome.domain.payment.repository;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.roome.domain.config.TestQueryDslConfig;
6+
import com.roome.domain.payment.entity.Payment;
7+
import com.roome.domain.payment.entity.PaymentLog;
8+
import com.roome.domain.payment.entity.PaymentStatus;
9+
import com.roome.domain.user.entity.Provider;
10+
import com.roome.domain.user.entity.Status;
11+
import com.roome.domain.user.entity.User;
12+
import com.roome.domain.user.repository.UserRepository;
13+
import com.roome.global.config.JpaConfig;
14+
import jakarta.persistence.EntityManager;
15+
import java.time.LocalDateTime;
16+
import java.util.List;
17+
import org.junit.jupiter.api.DisplayName;
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
21+
import org.springframework.context.annotation.Import;
22+
import org.springframework.data.domain.Page;
23+
import org.springframework.data.domain.PageRequest;
24+
import org.springframework.data.domain.Sort;
25+
import org.springframework.test.context.ActiveProfiles;
26+
27+
@ActiveProfiles("test")
28+
@Import({TestQueryDslConfig.class, JpaConfig.class})
29+
@DataJpaTest
30+
class PaymentLogRepositoryTest {
31+
32+
@Autowired
33+
private PaymentLogRepository paymentLogRepository;
34+
35+
@Autowired
36+
private PaymentRepository paymentRepository;
37+
38+
@Autowired
39+
private UserRepository userRepository;
40+
41+
@Autowired
42+
private EntityManager entityManager;
43+
44+
@Test
45+
@DisplayName("결제 로그는 연결된 Payment를 통해 주문 건과 매칭할 수 있다.")
46+
void paymentLog_LinkedToPayment() {
47+
// given
48+
User user = userRepository.save(createUser("user@gmail.com", "provId-1"));
49+
Payment payment = paymentRepository.save(payment(user, "order-1"));
50+
paymentLogRepository.save(PaymentLog.builder()
51+
.user(user).payment(payment)
52+
.amount(5_000).earnedPoints(550).paymentKey("pk1")
53+
.build());
54+
entityManager.flush();
55+
entityManager.clear();
56+
57+
// when
58+
Page<PaymentLog> result = paymentLogRepository.findByUserWithPayment(
59+
user, PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt")));
60+
61+
// then: 로그에서 연결된 결제 건의 orderId를 추적할 수 있다
62+
List<PaymentLog> logs = result.getContent();
63+
assertThat(logs).hasSize(1);
64+
assertThat(logs.get(0).getPayment()).isNotNull();
65+
assertThat(logs.get(0).getPayment().getOrderId()).isEqualTo("order-1");
66+
}
67+
68+
@Test
69+
@DisplayName("payment가 연결되지 않은 기존 로그도 조회에서 누락되지 않는다 (LEFT JOIN).")
70+
void legacyLog_WithoutPayment_StillReturned() {
71+
// given: payment 연결이 없는 레거시 로그
72+
User user = userRepository.save(createUser("legacy@gmail.com", "provId-2"));
73+
paymentLogRepository.save(PaymentLog.builder()
74+
.user(user).payment(null)
75+
.amount(1_000).earnedPoints(100).paymentKey("pk-legacy")
76+
.build());
77+
entityManager.flush();
78+
entityManager.clear();
79+
80+
// when
81+
Page<PaymentLog> result = paymentLogRepository.findByUserWithPayment(
82+
user, PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt")));
83+
84+
// then
85+
assertThat(result.getContent()).hasSize(1);
86+
assertThat(result.getContent().get(0).getPayment()).isNull();
87+
}
88+
89+
private Payment payment(User user, String orderId) {
90+
return Payment.builder()
91+
.user(user)
92+
.orderId(orderId)
93+
.amount(5_000)
94+
.purchasedPoints(550)
95+
.status(PaymentStatus.SUCCESS)
96+
.build();
97+
}
98+
99+
private User createUser(String email, String providerId) {
100+
return User.builder()
101+
.email(email)
102+
.name("user")
103+
.nickname("nickname")
104+
.profileImage("profile")
105+
.provider(Provider.GOOGLE)
106+
.providerId(providerId)
107+
.status(Status.ONLINE)
108+
.lastLogin(LocalDateTime.of(2025, 1, 1, 1, 1))
109+
.refreshToken("refToken")
110+
.build();
111+
}
112+
}

0 commit comments

Comments
 (0)