From d5bea3db47afb54e14ec67b68f2b0e3c9010205b Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:12:28 +0900 Subject: [PATCH 01/27] =?UTF-8?q?Refactor=20:=20viewCount=20Long-=20>=20lo?= =?UTF-8?q?ng=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 변경 내용 1 : PrePersist 함수 삭제 - 변경 내용 2 : viewCount null 체크 삭제 - 변경 내용 3 : viewCount Long -> long 이유 : Long을 쓰면 null을 고려해야 하지만, long을 쓰면 null을 고려할 필요가 없어짐 --- .../auction/domain/auction/model/Auction.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java b/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java index c141b035..93607251 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java @@ -56,14 +56,7 @@ public class Auction extends BaseEntity { private List bids = new ArrayList<>(); @Column(nullable = false) - private Long viewCount; - - @PrePersist - protected void onCreate() { - if (viewCount == null) { - viewCount = 0L; // 기본값 설정 - } - } + private long viewCount = 0L; public static Auction create(LocalDateTime startTime, LocalDateTime endTime, BigDecimal startingBid, AuctionCategory auctionCategory, AuctionStatus auctionStatus, User seller) { Auction newAuction = Auction.builder() @@ -88,9 +81,6 @@ public void addUser(User seller) { } public void incrementViewCount() { - if (viewCount == null) { - viewCount = 0L; - } this.viewCount += 1; } From 853c04872f5b7e1e929674f086395efa75ee01e8 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:30:56 +0900 Subject: [PATCH 02/27] =?UTF-8?q?Fix=20:=20=ED=8E=98=EC=9D=B4=EC=A7=95=20e?= =?UTF-8?q?lement=20=EC=B9=B4=EC=9A=B4=ED=8A=B8=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -변경 사항 1 : 페이징 카운트 처리에서 생겼던 전체 size를 잘못 계산하던 에러 수정 --- .../support/AuctionCriteriaRepositoryImpl.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java b/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java index 18e085be..4fa75f16 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java @@ -29,12 +29,10 @@ public Page getAuctionsByFilters(Pageable pageable, AuctionRequest.Sear List result = buildQueryWithPaginationAndSorting(builder, pageable, sortOrder); // int 오버플로 주의 -// int total = queryFactory -// .selectFrom(auction) -// .where(builder) -// .fetch().size(); - - long total = result.size(); + int total = queryFactory + .selectFrom(auction) + .where(builder) + .fetch().size(); return new PageImpl<>(result, pageable, total); } From bf0bfca3747809500decd14f16e2d1eaddf94eae Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:16:56 +0900 Subject: [PATCH 03/27] =?UTF-8?q?Feat=20:=20payment=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추가한 내용 : 1. Payment 엔티티 구성 2. payment 관련 enum Type 구성 - payStatus, payType 3. payment DTO 구성 4. Payment 관련 api 메세지 구성 --- .../common/constant/ApiResponseMessages.java | 3 + .../payment/dto/request/PaymentRequest.java | 39 +++++++++++++ .../payment/dto/response/PaymentResponse.java | 4 ++ .../domain/payment/model/PayStatus.java | 24 ++++++++ .../auction/domain/payment/model/PayType.java | 25 +++++++++ .../auction/domain/payment/model/Payment.java | 55 +++++++++++++++++++ 6 files changed, 150 insertions(+) create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java diff --git a/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java b/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java index a8963f05..23506b50 100644 --- a/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java +++ b/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java @@ -31,4 +31,7 @@ public class ApiResponseMessages { public static final String REVIEW_RETRIEVED = "Review successfully retrieved"; // Additional messages can be defined as needed + + // PAYMENT + public static final String PAYMENT_PREPARED_SUCCESS = "Payment prepared successfully"; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java new file mode 100644 index 00000000..15821b19 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java @@ -0,0 +1,39 @@ +package com.tasksprints.auction.domain.payment.dto.request; + +import com.tasksprints.auction.domain.payment.model.PayType; +import com.tasksprints.auction.domain.payment.model.Payment; +import lombok.*; + +import java.math.BigDecimal; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PaymentRequest { + private BigDecimal amount; + private String orderId; + private String orderName; + private PayType payType; +// private String successUrl; +// private String failUrl; + + @Getter + @AllArgsConstructor + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Prepare { + private String orderId; + private BigDecimal amount; + } + + public Payment toEntity() { + return Payment.builder() + .amount(amount) + .tossOrderId(orderId) + .orderName(orderName) + .payType(payType) + .build(); + } + + +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java new file mode 100644 index 00000000..35eee9f8 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java @@ -0,0 +1,4 @@ +package com.tasksprints.auction.domain.payment.dto.response; + +public class PaymentResponse { +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java b/src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java new file mode 100644 index 00000000..ad4b757f --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java @@ -0,0 +1,24 @@ +package com.tasksprints.auction.domain.payment.model; + +import lombok.Getter; + +@Getter +public enum PayStatus { + PENDING("결제 진행중"), + APPROVED("결제 완료"), + CANCELED("결제 취소"), + FAILED("결제 실패"); + + private final String status; + + PayStatus(String status) {this.status = status;} + + public static PayStatus fromString(String status) { + for (PayStatus payStatus : PayStatus.values()) { + if (payStatus.name().equalsIgnoreCase(status)) { + return payStatus; + } + } + throw new IllegalArgumentException("Unknown PayStatus: " + status); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java b/src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java new file mode 100644 index 00000000..de06eb0c --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java @@ -0,0 +1,25 @@ +package com.tasksprints.auction.domain.payment.model; + +import lombok.Getter; + +@Getter +public enum PayType { + CARD("카드 결제"), + BANK_TRANSFER("계좌 이체"), + MOBILE_PAYMENT("모바일 결제"); + + private final String payType; + + PayType(String payType) { + this.payType = payType; + } + + public static PayType fromString(String type) { + for (PayType payType : PayType.values()) { + if (payType.name().equalsIgnoreCase(type)) { + return payType; + } + } + throw new IllegalArgumentException("Unknown PayType: " + type); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java b/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java new file mode 100644 index 00000000..62cd81a5 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java @@ -0,0 +1,55 @@ +package com.tasksprints.auction.domain.payment.model; + +import com.tasksprints.auction.common.entity.BaseEntityWithUpdate; +import com.tasksprints.auction.domain.wallet.model.Wallet; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Payment extends BaseEntityWithUpdate { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long paymentId; + + @Column(nullable = false, unique = true) + private String tossPaymentKey; + + @Column(nullable = false) + private String tossOrderId; + + @Column(nullable = false) + private String orderName; + +// @ManyToOne(fetch = FetchType.LAZY) +// private Wallet wallet; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private PayType payType; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private PayStatus payStatus; + + @Column(nullable = false) + private BigDecimal amount; + + @Column(nullable = true) + private String failReason; + + @Column(nullable = true) + private String cancelReason; + + +} From 0431fb4911bfb017c2c4c6defa87b69bd96fd071 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:21:05 +0900 Subject: [PATCH 04/27] =?UTF-8?q?Feat=20:=20=EC=84=B8=EC=85=98=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=80=EC=9E=A5=20=EC=97=94=EB=93=9C=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추가한 내용 : 1. controller, service, repository 레이어 구성 2. orderId, amount를 세션에 임시 저장하는 엔드포인트 구성 --- .../api/payment/paymentController.java | 29 +++++++++++++++++++ .../payment/repository/PaymentRepository.java | 8 +++++ .../repository/TossPayRepositoryImpl.java | 4 +++ .../payment/service/PaymentService.java | 9 ++++++ .../payment/service/PaymentServiceImpl.java | 14 +++++++++ 5 files changed, 64 insertions(+) create mode 100644 src/main/java/com/tasksprints/auction/api/payment/paymentController.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/repository/PaymentRepository.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java diff --git a/src/main/java/com/tasksprints/auction/api/payment/paymentController.java b/src/main/java/com/tasksprints/auction/api/payment/paymentController.java new file mode 100644 index 00000000..4ae2cd33 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/api/payment/paymentController.java @@ -0,0 +1,29 @@ +package com.tasksprints.auction.api.payment; + +import com.tasksprints.auction.common.constant.ApiResponseMessages; +import com.tasksprints.auction.common.response.ApiResult; +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.service.PaymentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/payment") +public class paymentController { + private final PaymentService paymentService; + + @PostMapping("/prepare") + @Operation(summary = "Temporarily stores the payment element", description = "Save orderID and amount in session") + @ApiResponse(responseCode = "200", description = "Payment prepared successfully") + public ResponseEntity> preparePayment(HttpSession session, @RequestBody PaymentRequest.Prepare prepareRequest) { + paymentService.prepare(session, prepareRequest); + return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.PAYMENT_PREPARED_SUCCESS)); + } + + +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/repository/PaymentRepository.java b/src/main/java/com/tasksprints/auction/domain/payment/repository/PaymentRepository.java new file mode 100644 index 00000000..cbc9456c --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/repository/PaymentRepository.java @@ -0,0 +1,8 @@ +package com.tasksprints.auction.domain.payment.repository; + +import com.tasksprints.auction.domain.payment.model.Payment; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PaymentRepository extends JpaRepository { + +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java new file mode 100644 index 00000000..982c0eee --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java @@ -0,0 +1,4 @@ +package com.tasksprints.auction.domain.payment.repository; + +public class TossPayRepositoryImpl { +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java new file mode 100644 index 00000000..93a0d13e --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java @@ -0,0 +1,9 @@ +package com.tasksprints.auction.domain.payment.service; + + +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import jakarta.servlet.http.HttpSession; + +public interface PaymentService { + public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest); +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java new file mode 100644 index 00000000..681beed0 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java @@ -0,0 +1,14 @@ +package com.tasksprints.auction.domain.payment.service; + +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import jakarta.servlet.http.HttpSession; +import org.springframework.stereotype.Service; + +@Service +public class PaymentServiceImpl implements PaymentService { + @Override + public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest) { + session.setAttribute("orderId", prepareRequest.getOrderId()); + session.setAttribute("amount", prepareRequest.getAmount()); + } +} From e4213bcd773b17cc475fe9f2ae16cd54e58b31fd Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:20:58 +0900 Subject: [PATCH 05/27] =?UTF-8?q?Feat=20:=20wallet=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추가한 내용 : 1. wallet 엔티티 구성 2. wallet 컨트롤러, 서비스, 레포지터리 레이어 구성 3. user 엔티티와 wallet 엔티티간 양방향 관계 설정 및 연관관계 편의 메서드 구현 -> 연관 관계 주인 쪽 user에서 처리 4. wallet 엔티티와 payment 엔티티간 양방향 관계 설정 및 연관관계 편의 메서드 구현 -> 연관 관계 주인 쪽 payment에서 처리 미완 : 1. user 엔티티 create 시에 wallet도 생성되게 처리 해야 한다. 2. wallet 의 식별자를 uuid로 해야 할지 고민해야 함. --- .../auction/api/wallet/WalletController.java | 4 ++ .../auction/domain/payment/model/Payment.java | 10 +++-- .../auction/domain/user/model/User.java | 10 +++++ .../InSufficientBalanceException.java | 7 ++++ .../exception/WalletNotFoundException.java | 7 ++++ .../auction/domain/wallet/model/Wallet.java | 41 +++++++++++++++++++ .../wallet/repository/WalletRepository.java | 8 ++++ .../domain/wallet/service/WalletService.java | 8 ++++ .../wallet/service/WalletServiceImpl.java | 4 ++ 9 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/tasksprints/auction/api/wallet/WalletController.java create mode 100644 src/main/java/com/tasksprints/auction/domain/wallet/exception/InSufficientBalanceException.java create mode 100644 src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletNotFoundException.java create mode 100644 src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java create mode 100644 src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java create mode 100644 src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java create mode 100644 src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java diff --git a/src/main/java/com/tasksprints/auction/api/wallet/WalletController.java b/src/main/java/com/tasksprints/auction/api/wallet/WalletController.java new file mode 100644 index 00000000..77757598 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/api/wallet/WalletController.java @@ -0,0 +1,4 @@ +package com.tasksprints.auction.api.wallet; + +public class WalletController { +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java b/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java index 62cd81a5..71481edc 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java @@ -31,8 +31,9 @@ public class Payment extends BaseEntityWithUpdate { @Column(nullable = false) private String orderName; -// @ManyToOne(fetch = FetchType.LAZY) -// private Wallet wallet; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "wallet_id") + private Wallet wallet; @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -51,5 +52,8 @@ public class Payment extends BaseEntityWithUpdate { @Column(nullable = true) private String cancelReason; - + public void addWallet(Wallet wallet) { + this.wallet = wallet; + wallet.addPayment(this); + } } diff --git a/src/main/java/com/tasksprints/auction/domain/user/model/User.java b/src/main/java/com/tasksprints/auction/domain/user/model/User.java index 4db7e595..58f08607 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/model/User.java +++ b/src/main/java/com/tasksprints/auction/domain/user/model/User.java @@ -2,6 +2,7 @@ import com.tasksprints.auction.common.entity.BaseEntityWithUpdate; import com.tasksprints.auction.domain.auction.model.Auction; +import com.tasksprints.auction.domain.wallet.model.Wallet; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.SQLRestriction; @@ -40,6 +41,10 @@ public class User extends BaseEntityWithUpdate { @OneToMany(mappedBy = "seller", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @Builder.Default private List auctions = new ArrayList<>(); + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "wallet_id") + private Wallet wallet; // 추후 추가 // @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) // @Builder.Default @@ -79,4 +84,9 @@ public void delete() { public void addAuction(Auction auction) { this.auctions.add(auction); } + + public void addWallet(Wallet wallet) { + this.wallet = wallet; + wallet.addUser(this); + } } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/exception/InSufficientBalanceException.java b/src/main/java/com/tasksprints/auction/domain/wallet/exception/InSufficientBalanceException.java new file mode 100644 index 00000000..2af6c8f5 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/wallet/exception/InSufficientBalanceException.java @@ -0,0 +1,7 @@ +package com.tasksprints.auction.domain.wallet.exception; + +public class InSufficientBalanceException extends RuntimeException { + public InSufficientBalanceException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletNotFoundException.java b/src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletNotFoundException.java new file mode 100644 index 00000000..76461711 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletNotFoundException.java @@ -0,0 +1,7 @@ +package com.tasksprints.auction.domain.wallet.exception; + +public class WalletNotFoundException extends RuntimeException { + public WalletNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java new file mode 100644 index 00000000..3313f211 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java @@ -0,0 +1,41 @@ +package com.tasksprints.auction.domain.wallet.model; + +import com.tasksprints.auction.common.entity.BaseEntityWithUpdate; +import com.tasksprints.auction.domain.payment.model.Payment; +import com.tasksprints.auction.domain.user.model.User; +import jakarta.persistence.*; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class Wallet extends BaseEntityWithUpdate { + /** + 지갑 식별키를 uuid로 변경 고려 + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id = UUID.randomUUID(); + */ + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private BigDecimal balance; + + @Column(nullable = false) + private String userName; + + @OneToMany(mappedBy = "wallet", fetch = FetchType.LAZY) + private List payments = new ArrayList<>(); + + @OneToOne(mappedBy = "wallet", fetch = FetchType.LAZY) + private User user; + + public void addPayment(Payment payment) { + this.payments.add(payment); + } + + public void addUser(User user) { + this.user = user; + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java b/src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java new file mode 100644 index 00000000..132adca0 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java @@ -0,0 +1,8 @@ +package com.tasksprints.auction.domain.wallet.repository; + +import com.tasksprints.auction.domain.wallet.model.Wallet; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WalletRepository extends JpaRepository { + +} diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java new file mode 100644 index 00000000..efb0c3c9 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java @@ -0,0 +1,8 @@ +package com.tasksprints.auction.domain.wallet.service; + +public interface WalletService { + //충전 + void chargeMoney(); + //잔액 충분한지? + boolean isSufficientMoney(); +} diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java new file mode 100644 index 00000000..af9580d4 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java @@ -0,0 +1,4 @@ +package com.tasksprints.auction.domain.wallet.service; + +public class WalletServiceImpl { +} From 61b0401d99bb79f311f6af9e08d0a9d3eec20e71 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:24:31 +0900 Subject: [PATCH 06/27] =?UTF-8?q?Test=20:=20=EC=84=B8=EC=85=98=EC=97=90=20?= =?UTF-8?q?=EA=B0=92=20=EC=9E=84=EC=8B=9C=20=EC=A0=80=EC=9E=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -추가된 내용 : 1. 세션에 orderId, amount 값을 임시 저장한 것이 제대로 저장되었는지 테스트 이유 : 결제 최종 승인 요청 전에 값이 변조되지 않았는지 검증하기 위함 미흡 : 결제 요청 받았을 때, 세션에 값이 저장되지 않았을 경우에 대한 예외 처리 미완 테스트 코드에 대한 부족함이 느껴져 찜찜함이 있다. --- .../auction/api/PaymentControllerTest.java | 52 +++++++++++++++++++ .../repository/PaymentRepositoryTest.java | 4 ++ .../service/PaymentServiceImplTest.java | 51 ++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java create mode 100644 src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java create mode 100644 src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java diff --git a/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java b/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java new file mode 100644 index 00000000..795fc224 --- /dev/null +++ b/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java @@ -0,0 +1,52 @@ +package com.tasksprints.auction.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tasksprints.auction.api.payment.PaymentController; +import com.tasksprints.auction.common.constant.ApiResponseMessages; +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.service.PaymentService; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.web.servlet.MockMvc; + +import java.math.BigDecimal; + + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(PaymentController.class) +@MockBean(JpaMetamodelMappingContext.class) +public class PaymentControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private PaymentService paymentService; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean MockHttpSession session; + @Test + @DisplayName("결제 전 임시 값 저장") + public void 결제_전_임시_값_저장() throws Exception { + PaymentRequest.Prepare prepareRequest = new PaymentRequest.Prepare("test1", BigDecimal.valueOf(1000.00)); + + mockMvc.perform(post("/api/v1/payment/prepare") + .session(session) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(prepareRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value(ApiResponseMessages.PAYMENT_PREPARED_SUCCESS)); + + } +} diff --git a/src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java b/src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java new file mode 100644 index 00000000..e94f6a9e --- /dev/null +++ b/src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java @@ -0,0 +1,4 @@ +package com.tasksprints.auction.domain.payment.repository; + +public class PaymentRepositoryTest { +} diff --git a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java new file mode 100644 index 00000000..9762bf49 --- /dev/null +++ b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java @@ -0,0 +1,51 @@ +package com.tasksprints.auction.domain.payment.service; + +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpSession; + +import java.math.BigDecimal; +import static org.assertj.core.api.Assertions.*; + + +@ExtendWith(MockitoExtension.class) +public class PaymentServiceImplTest { + + @InjectMocks + private PaymentServiceImpl paymentService; + +// @Mock +// private MockHttpSession MockSession; + + @Nested + @DisplayName("결제 전 세션 임시 저장 테스트") + class 임시_저장_테스트{ + @Test + void 결제_요청을_받았을_때_세션에_값이_저장되면_성공한다 () { + //given + MockHttpSession session = new MockHttpSession(); + String orderId = "testOrderId"; + BigDecimal amount = BigDecimal.valueOf(1000.00); + PaymentRequest.Prepare prepareRequest = new PaymentRequest.Prepare(orderId, amount); + //when + paymentService.prepare(session, prepareRequest); + //then + assertThat(session.getAttribute("orderId")).isEqualTo(orderId); + assertThat(session.getAttribute("amount")).isEqualTo(amount); + } + @Test + void 결제_요청을_받았을_때_세션에_값이_저장되지_않으면_예외_처리 () { + //given + //when + //then + } + } +} From 42bf230a26b2bd302bcdd4c0a7bf10d30f820263 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:17:00 +0900 Subject: [PATCH 07/27] =?UTF-8?q?Rename=20:=20PaymentController=20?= =?UTF-8?q?=EC=86=8C=EB=AC=B8=EC=9E=90=20->=20=EB=8C=80=EB=AC=B8=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 단순 파일명 변경 --- .../com/tasksprints/auction/api/payment/paymentController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/tasksprints/auction/api/payment/paymentController.java b/src/main/java/com/tasksprints/auction/api/payment/paymentController.java index 4ae2cd33..188da461 100644 --- a/src/main/java/com/tasksprints/auction/api/payment/paymentController.java +++ b/src/main/java/com/tasksprints/auction/api/payment/paymentController.java @@ -14,7 +14,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/payment") -public class paymentController { +public class PaymentController { private final PaymentService paymentService; @PostMapping("/prepare") From 50a73674710078aadce51c1b3762bbba827458ec Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:22:17 +0900 Subject: [PATCH 08/27] . . --- .gitignore | 4 + .../auction/AuctionApplication.java | 3 +- .../api/payment/paymentController.java | 80 +++++++++++++++++ .../auction/common/config/PaymentConfig.java | 31 +++++++ .../payment/dto/request/PaymentRequest.java | 9 ++ .../payment/dto/response/PaymentResponse.java | 11 +++ .../exception/InvalidSessionException.java | 7 ++ .../PaymentDataMismatchException.java | 7 ++ .../payment/service/PaymentService.java | 6 ++ .../payment/service/PaymentServiceImpl.java | 32 +++++++ .../user/dto/response/UserDetailResponse.java | 2 + .../domain/user/service/UserServiceImpl.java | 16 +++- .../auction/domain/wallet/model/Wallet.java | 23 ++++- src/main/resources/application.yml | 11 ++- src/main/resources/templates/test.html | 89 +++++++++++++++++++ .../service/PaymentServiceImplTest.java | 2 +- .../domain/user/UserServiceImplTest.java | 2 - 17 files changed, 325 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/exception/InvalidSessionException.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentDataMismatchException.java create mode 100644 src/main/resources/templates/test.html diff --git a/.gitignore b/.gitignore index c2065bc2..8595cb35 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ out/ ### VS Code ### .vscode/ + + +### yml file ### +/src/main/resources/application-payment.yml diff --git a/src/main/java/com/tasksprints/auction/AuctionApplication.java b/src/main/java/com/tasksprints/auction/AuctionApplication.java index 69bb978d..f8d5bf32 100644 --- a/src/main/java/com/tasksprints/auction/AuctionApplication.java +++ b/src/main/java/com/tasksprints/auction/AuctionApplication.java @@ -4,10 +4,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import java.util.Arrays; + @EnableJpaAuditing @SpringBootApplication public class AuctionApplication { - public static void main(String[] args) { SpringApplication.run(AuctionApplication.class, args); } diff --git a/src/main/java/com/tasksprints/auction/api/payment/paymentController.java b/src/main/java/com/tasksprints/auction/api/payment/paymentController.java index 188da461..ddcc9280 100644 --- a/src/main/java/com/tasksprints/auction/api/payment/paymentController.java +++ b/src/main/java/com/tasksprints/auction/api/payment/paymentController.java @@ -1,21 +1,41 @@ package com.tasksprints.auction.api.payment; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tasksprints.auction.common.config.PaymentConfig; import com.tasksprints.auction.common.constant.ApiResponseMessages; import com.tasksprints.auction.common.response.ApiResult; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; +import com.tasksprints.auction.domain.payment.exception.InvalidSessionException; +import com.tasksprints.auction.domain.payment.exception.PaymentDataMismatchException; import com.tasksprints.auction.domain.payment.service.PaymentService; +import com.tasksprints.auction.domain.user.dto.response.UserDetailResponse; +import com.tasksprints.auction.domain.user.model.User; +import com.tasksprints.auction.domain.user.repository.UserRepository; +import com.tasksprints.auction.domain.user.service.UserServiceImpl; +import com.tasksprints.auction.domain.wallet.model.Wallet; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.servlet.http.HttpSession; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.io.IOException; +import java.math.BigDecimal; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; + @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/payment") public class PaymentController { + private final PaymentConfig paymentConfig; private final PaymentService paymentService; + private final UserServiceImpl userService; + private static final HttpClient httpClient = HttpClient.newHttpClient(); + private final UserRepository userRepository; @PostMapping("/prepare") @Operation(summary = "Temporarily stores the payment element", description = "Save orderID and amount in session") @@ -25,5 +45,65 @@ public ResponseEntity> preparePayment(HttpSession session, @Re return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.PAYMENT_PREPARED_SUCCESS)); } + @Transactional + @PostMapping("/confirm") + public ResponseEntity confirmPayment(HttpSession session, @RequestBody PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { + //세션 유효 검증 및 결제 요청 전 후로 세션 값 변경 됐는지 검증 + validateSession(session); + validatePaymentConfirmRequest(confirmRequest, session); + + //실제 토스페이먼츠로 결제 승인 요청 + HttpResponse response = paymentService.requestConfirm(httpClient, confirmRequest); + + //토스 결제 승인 성공 + ObjectMapper objectMapper = new ObjectMapper(); + if (response.statusCode() == 200) { + try { + //토스에서 받은 Response의 Payment 객체를 Dto에 담기 + PaymentResponse paymentResponse = objectMapper.readValue(response.body(), PaymentResponse.class); + //일단 , 하드 코딩 -> 나중에 jwt에서 userId 얻을 것 + Long userId = 1L; + // UserDetailResponse foundUserDto = userService.getUserDetailsById(userId); + String walletId = userService.getUserDetailsById(userId).getWalletId(); + + //결제 정보를 지갑에 저장하고, 저장이 안 됐을 시에는 throw Exception + + + } catch (Exception e) { + //repo로 저장 실패 시에는 결제를 취소해야 함 -> 토스페이로 결제 취소 요청 + } + //결제가 성공적, 결제 기록 저장했고 충전 로직까지 처리 + } + + //토스 결제 실패 -> 에러 메시지를 return | else 구문은 필요 없는데 일단 의미상 둠 + else { + //클라이언트로 Response + } + return null; + } + + private void validatePaymentConfirmRequest(PaymentRequest.Confirm confirmRequest, HttpSession session){ + String savedOrderId = (String) session.getAttribute("orderId"); + BigDecimal savedAmount = (BigDecimal) session.getAttribute("amount"); + + if (!confirmRequest.getOrderId().equals(savedOrderId) || !confirmRequest.getAmount().equals(savedAmount)) { + throw new PaymentDataMismatchException("Payment data mismatch"); + } + } + + private void validateSession(HttpSession session) { + if (session == null) { + throw new InvalidSessionException("Invalid session"); + } + String savedOrderId = (String) session.getAttribute("orderId"); + BigDecimal savedAmount = (BigDecimal) session.getAttribute("amount"); + + if (savedOrderId == null || savedAmount == null) { + //에러 처리 + throw new InvalidSessionException("Invalid session"); + } + + } + } diff --git a/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java b/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java new file mode 100644 index 00000000..8149e6aa --- /dev/null +++ b/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java @@ -0,0 +1,31 @@ +package com.tasksprints.auction.common.config; + +import lombok.Getter; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import java.util.Base64; + +@Configuration +@Getter +public class PaymentConfig { + @Value("${payment.toss.test_client_api_key}") + private String testClientApiKey; + + @Value("${payment.toss.test_secret_api_key}") + private String testSecretApiKey; + + @Value("${payment.toss.success_url}") + private String successUrl; + + @Value("${payment.toss.fail_url}") + private String failUrl; + + public static final String URL = "https://api.tosspayments.com/v1/payments/"; + + public String getAuthorizations() { + String encodedKey = Base64.getEncoder().encodeToString((testSecretApiKey + ":").getBytes()); + return "Basic " + encodedKey; + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java index 15821b19..329992fa 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java @@ -26,6 +26,15 @@ public static class Prepare { private BigDecimal amount; } + @Getter + @AllArgsConstructor + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Confirm { + private String orderId; + private BigDecimal amount; + private String tossPaymentKey; + } + public Payment toEntity() { return Payment.builder() .amount(amount) diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java index 35eee9f8..d6e4ae73 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java @@ -1,4 +1,15 @@ package com.tasksprints.auction.domain.payment.dto.response; +import java.math.BigDecimal; + public class PaymentResponse { + private String payType; + private BigDecimal amount; + private String orderName; + private String orderId; + private String customerEmail; + private String customerName; + private String successUrl; + private String failUrl; + } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/exception/InvalidSessionException.java b/src/main/java/com/tasksprints/auction/domain/payment/exception/InvalidSessionException.java new file mode 100644 index 00000000..4342867a --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/exception/InvalidSessionException.java @@ -0,0 +1,7 @@ +package com.tasksprints.auction.domain.payment.exception; + +public class InvalidSessionException extends RuntimeException { + public InvalidSessionException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentDataMismatchException.java b/src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentDataMismatchException.java new file mode 100644 index 00000000..de29bb44 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentDataMismatchException.java @@ -0,0 +1,7 @@ +package com.tasksprints.auction.domain.payment.exception; + +public class PaymentDataMismatchException extends RuntimeException { + public PaymentDataMismatchException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java index 93a0d13e..5a6988e2 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java @@ -4,6 +4,12 @@ import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import jakarta.servlet.http.HttpSession; +import java.io.IOException; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; + public interface PaymentService { + public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest); + public HttpResponse requestConfirm(HttpClient httpClient, PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java index 681beed0..e67c29e1 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java @@ -1,14 +1,46 @@ package com.tasksprints.auction.domain.payment.service; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tasksprints.auction.common.config.PaymentConfig; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.repository.PaymentRepository; import jakarta.servlet.http.HttpSession; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + @Service +@RequiredArgsConstructor public class PaymentServiceImpl implements PaymentService { + + private final PaymentConfig paymentConfig; + private final PaymentRepository paymentRepository; + @Override public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest) { session.setAttribute("orderId", prepareRequest.getOrderId()); session.setAttribute("amount", prepareRequest.getAmount()); } + + @Override + public HttpResponse requestConfirm(HttpClient httpClient, PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { + ObjectMapper objectMapper = new ObjectMapper(); + String requestBody = objectMapper.writeValueAsString(confirmRequest); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("")) + .header("Authorization", paymentConfig.getAuthorizations()) + .header("Content-Type", "application/json") + .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + } } diff --git a/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java b/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java index 58f2ec21..e27e6fe8 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java @@ -12,6 +12,7 @@ public class UserDetailResponse { private String email; private String password; private String nickName; + private String walletId; private UserDetailResponse(User user) { this.id = user.getId(); @@ -19,6 +20,7 @@ private UserDetailResponse(User user) { this.email = user.getEmail(); this.password = user.getPassword(); this.nickName = user.getNickName(); + this.walletId = String.valueOf(user.getWallet().getId()); } public static UserDetailResponse of(User user) { diff --git a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java index ad9d0b6b..00cfe6ac 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java @@ -6,10 +6,14 @@ import com.tasksprints.auction.domain.user.exception.UserNotFoundException; import com.tasksprints.auction.domain.user.model.User; import com.tasksprints.auction.domain.user.repository.UserRepository; +import com.tasksprints.auction.domain.wallet.model.Wallet; +import com.tasksprints.auction.domain.wallet.repository.WalletRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Service @@ -17,12 +21,17 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; + private final WalletRepository walletRepository; +// @Transactional @Override public UserDetailResponse createUser(UserRequest.Register request) { User user = User.create(request.getName(), request.getEmail(), request.getPassword(), request.getNickname()); - User newUser = userRepository.save(user); + +// Wallet wallet = this.createWalletForUser(newUser); +// newUser.addWallet(wallet); + // walletRepository.save(wallet); cascade = CascadeType.PERSIST 옵션으로 처리 return UserDetailResponse.of(newUser); } @@ -59,4 +68,9 @@ public void deleteUser(Long id) { userRepository.save(user); // 상태 업데이트를 저장 } +// private Wallet createWalletForUser(User user) { +// return Optional.ofNullable(Wallet.create(user)) +// .orElseThrow(() -> new WalletCreationException("Failed to create wallet for user: " + user.getEmail())); +// } + } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java index 3313f211..ad61810e 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java @@ -4,18 +4,26 @@ import com.tasksprints.auction.domain.payment.model.Payment; import com.tasksprints.auction.domain.user.model.User; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor public class Wallet extends BaseEntityWithUpdate { /** - 지갑 식별키를 uuid로 변경 고려 - @GeneratedValue(strategy = GenerationType.AUTO) - private UUID id = UUID.randomUUID(); - */ + 지갑 식별키를 uuid로 변경 고려 + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id = UUID.randomUUID(); + */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -31,6 +39,13 @@ public class Wallet extends BaseEntityWithUpdate { @OneToOne(mappedBy = "wallet", fetch = FetchType.LAZY) private User user; + public static Wallet create(User user) { + return Wallet + .builder() + .userName(user.getName()) + .user(user) + .build(); + } public void addPayment(Payment payment) { this.payments.add(payment); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a6bb2474..9cacbe48 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,7 +20,11 @@ spring: jpa: hibernate: ddl-auto: create-drop - show-sql: true + properties: + hibernate: + format_sql: true +# default_batch_fetch_size: 100 + database-platform: org.hibernate.dialect.H2Dialect thymeleaf: enabled: true @@ -30,6 +34,10 @@ spring: encoding: UTF-8 cache: false + profiles: + include: + -payment + springdoc: api-docs: path: /v1/api-docs @@ -46,3 +54,4 @@ logging: server: port: 8080 + diff --git a/src/main/resources/templates/test.html b/src/main/resources/templates/test.html new file mode 100644 index 00000000..9bade732 --- /dev/null +++ b/src/main/resources/templates/test.html @@ -0,0 +1,89 @@ + + + + + + + + +
+ + +
+ +
+ +
+ + + + + + diff --git a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java index 9762bf49..315538ed 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java @@ -18,7 +18,6 @@ @ExtendWith(MockitoExtension.class) public class PaymentServiceImplTest { - @InjectMocks private PaymentServiceImpl paymentService; @@ -35,6 +34,7 @@ class 임시_저장_테스트{ String orderId = "testOrderId"; BigDecimal amount = BigDecimal.valueOf(1000.00); PaymentRequest.Prepare prepareRequest = new PaymentRequest.Prepare(orderId, amount); + //when paymentService.prepare(session, prepareRequest); //then diff --git a/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java index 95c78348..3989d968 100644 --- a/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java @@ -19,10 +19,8 @@ public class UserServiceImplTest { @Mock private UserRepository userRepository; - @InjectMocks private UserServiceImpl userService; - private User existingUser; @BeforeEach From 6a26c845dc1ca66d536a0d592b38cbf35e0d340e Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:11:47 +0900 Subject: [PATCH 09/27] =?UTF-8?q?feat=20:=20User=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20Wallet=EB=8F=84=20=EA=B0=99=EC=9D=B4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경한 내용 : 1. 트랜잭션 단위로 User 생성과 Wallet 생성을 묶어서 처리 2, 연관 관계 편의 메서드로 User와 Wallet 간 객체 참조 연결 3. User의 영속성이 Wallet으로 전파되도록 cascade.ALL 설정 --- .../auction/domain/user/model/User.java | 7 +++++- .../domain/user/service/UserService.java | 3 +++ .../domain/user/service/UserServiceImpl.java | 23 +++++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/domain/user/model/User.java b/src/main/java/com/tasksprints/auction/domain/user/model/User.java index 58f08607..6343794a 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/model/User.java +++ b/src/main/java/com/tasksprints/auction/domain/user/model/User.java @@ -42,7 +42,7 @@ public class User extends BaseEntityWithUpdate { @Builder.Default private List auctions = new ArrayList<>(); - @OneToOne(fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "wallet_id") private Wallet wallet; // 추후 추가 @@ -61,6 +61,11 @@ public static User create(String name, String email, String password, String nic .nickName(nickName) .build(); } +// public Wallet createWalletForUser(User user) { +// Wallet wallet = new Wallet(); +// wallet.setUser(user); +// return wallet; +// } public void setAuctions(List auctions) { this.auctions = auctions; diff --git a/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java b/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java index 63755627..1457d7f7 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java +++ b/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java @@ -3,6 +3,7 @@ import com.tasksprints.auction.domain.user.dto.request.UserRequest; import com.tasksprints.auction.domain.user.dto.response.UserDetailResponse; import com.tasksprints.auction.domain.user.dto.response.UserSummaryResponse; +import com.tasksprints.auction.domain.user.model.User; import java.util.List; @@ -16,4 +17,6 @@ public interface UserService { UserDetailResponse updateUser(Long id, UserRequest.Update user); void deleteUser(Long id); + + User findUserById(Long id); } diff --git a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java index 00cfe6ac..493aaa2a 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java @@ -6,6 +6,7 @@ import com.tasksprints.auction.domain.user.exception.UserNotFoundException; import com.tasksprints.auction.domain.user.model.User; import com.tasksprints.auction.domain.user.repository.UserRepository; +import com.tasksprints.auction.domain.wallet.exception.WalletCreationException; import com.tasksprints.auction.domain.wallet.model.Wallet; import com.tasksprints.auction.domain.wallet.repository.WalletRepository; import jakarta.transaction.Transactional; @@ -23,15 +24,14 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final WalletRepository walletRepository; -// @Transactional + @Transactional @Override public UserDetailResponse createUser(UserRequest.Register request) { User user = User.create(request.getName(), request.getEmail(), request.getPassword(), request.getNickname()); + Wallet wallet = this.createWalletForUser(user); + user.addWallet(wallet); User newUser = userRepository.save(user); - -// Wallet wallet = this.createWalletForUser(newUser); -// newUser.addWallet(wallet); - // walletRepository.save(wallet); cascade = CascadeType.PERSIST 옵션으로 처리 + // walletRepository.save(wallet); cascade = CascadeType.ALL 옵션으로 처리 return UserDetailResponse.of(newUser); } @@ -68,9 +68,14 @@ public void deleteUser(Long id) { userRepository.save(user); // 상태 업데이트를 저장 } -// private Wallet createWalletForUser(User user) { -// return Optional.ofNullable(Wallet.create(user)) -// .orElseThrow(() -> new WalletCreationException("Failed to create wallet for user: " + user.getEmail())); -// } + @Override + public User findUserById(Long id){ + return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("User not found with id " + id)); + } + + private Wallet createWalletForUser(User user) { + return Optional.ofNullable(Wallet.create(user)) + .orElseThrow(() -> new WalletCreationException("Failed to create wallet for user: " + user.getEmail())); + } } From 8a08bec2812a9091e1dd9dbd9c3bcc3a8c23d44b Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:23:47 +0900 Subject: [PATCH 10/27] =?UTF-8?q?feat=20:=20Wallet=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추가한 내용 : 1. wallet 충전 메서드 2. wallet 생성 예외 메시지 --- .../exception/WalletCreationException.java | 7 ++++++ .../auction/domain/wallet/model/Wallet.java | 5 ++++ .../domain/wallet/service/WalletService.java | 6 ++++- .../wallet/service/WalletServiceImpl.java | 24 ++++++++++++++++++- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletCreationException.java diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletCreationException.java b/src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletCreationException.java new file mode 100644 index 00000000..be09e573 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/wallet/exception/WalletCreationException.java @@ -0,0 +1,7 @@ +package com.tasksprints.auction.domain.wallet.exception; + +public class WalletCreationException extends RuntimeException { + public WalletCreationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java index ad61810e..846cdd29 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java @@ -42,6 +42,7 @@ public class Wallet extends BaseEntityWithUpdate { public static Wallet create(User user) { return Wallet .builder() + .balance(BigDecimal.ZERO) .userName(user.getName()) .user(user) .build(); @@ -53,4 +54,8 @@ public void addPayment(Payment payment) { public void addUser(User user) { this.user = user; } + + public void chargeBalance(BigDecimal amount) { + this.balance = this.balance.add(amount); + } } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java index efb0c3c9..639b7087 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java @@ -1,8 +1,12 @@ package com.tasksprints.auction.domain.wallet.service; +import com.tasksprints.auction.domain.wallet.model.Wallet; + +import java.math.BigDecimal; + public interface WalletService { //충전 - void chargeMoney(); + void chargeMoney(Wallet wallet, BigDecimal amount); //잔액 충분한지? boolean isSufficientMoney(); } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java index af9580d4..4f4a574a 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java @@ -1,4 +1,26 @@ package com.tasksprints.auction.domain.wallet.service; -public class WalletServiceImpl { +import com.tasksprints.auction.domain.wallet.model.Wallet; +import com.tasksprints.auction.domain.wallet.repository.WalletRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +@RequiredArgsConstructor +public class WalletServiceImpl implements WalletService { + private final WalletRepository walletRepository; + + @Override + public void chargeMoney(Wallet wallet, BigDecimal amount) { + wallet.chargeBalance(amount); + } + + @Override + public boolean isSufficientMoney() { + return false; + } + + } From 804ef09f69467030e014b6ea4cb336bbd4393770 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:32:35 +0900 Subject: [PATCH 11/27] =?UTF-8?q?fix=20:=20=EC=9E=84=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이유 : git이 paymentController -> PaymentController로 바꾼 것을 인식하지 못함 --- ...roller.java => TempPaymentController.java} | 57 +++++-------------- 1 file changed, 13 insertions(+), 44 deletions(-) rename src/main/java/com/tasksprints/auction/api/payment/{paymentController.java => TempPaymentController.java} (58%) diff --git a/src/main/java/com/tasksprints/auction/api/payment/paymentController.java b/src/main/java/com/tasksprints/auction/api/payment/TempPaymentController.java similarity index 58% rename from src/main/java/com/tasksprints/auction/api/payment/paymentController.java rename to src/main/java/com/tasksprints/auction/api/payment/TempPaymentController.java index ddcc9280..1a8021e1 100644 --- a/src/main/java/com/tasksprints/auction/api/payment/paymentController.java +++ b/src/main/java/com/tasksprints/auction/api/payment/TempPaymentController.java @@ -8,18 +8,22 @@ import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; import com.tasksprints.auction.domain.payment.exception.InvalidSessionException; import com.tasksprints.auction.domain.payment.exception.PaymentDataMismatchException; +import com.tasksprints.auction.domain.payment.exception.PaymentUserNotFoundException; +import com.tasksprints.auction.domain.payment.model.Payment; +import com.tasksprints.auction.domain.payment.repository.PaymentRepository; import com.tasksprints.auction.domain.payment.service.PaymentService; -import com.tasksprints.auction.domain.user.dto.response.UserDetailResponse; import com.tasksprints.auction.domain.user.model.User; import com.tasksprints.auction.domain.user.repository.UserRepository; import com.tasksprints.auction.domain.user.service.UserServiceImpl; import com.tasksprints.auction.domain.wallet.model.Wallet; +import com.tasksprints.auction.domain.wallet.service.WalletServiceImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.servlet.http.HttpSession; -import jakarta.transaction.Transactional; + import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.io.IOException; @@ -31,11 +35,7 @@ @RequiredArgsConstructor @RequestMapping("/api/v1/payment") public class PaymentController { - private final PaymentConfig paymentConfig; private final PaymentService paymentService; - private final UserServiceImpl userService; - private static final HttpClient httpClient = HttpClient.newHttpClient(); - private final UserRepository userRepository; @PostMapping("/prepare") @Operation(summary = "Temporarily stores the payment element", description = "Save orderID and amount in session") @@ -45,43 +45,17 @@ public ResponseEntity> preparePayment(HttpSession session, @Re return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.PAYMENT_PREPARED_SUCCESS)); } - @Transactional @PostMapping("/confirm") public ResponseEntity confirmPayment(HttpSession session, @RequestBody PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { - //세션 유효 검증 및 결제 요청 전 후로 세션 값 변경 됐는지 검증 validateSession(session); validatePaymentConfirmRequest(confirmRequest, session); - //실제 토스페이먼츠로 결제 승인 요청 - HttpResponse response = paymentService.requestConfirm(httpClient, confirmRequest); - - //토스 결제 승인 성공 - ObjectMapper objectMapper = new ObjectMapper(); - if (response.statusCode() == 200) { - try { - //토스에서 받은 Response의 Payment 객체를 Dto에 담기 - PaymentResponse paymentResponse = objectMapper.readValue(response.body(), PaymentResponse.class); - //일단 , 하드 코딩 -> 나중에 jwt에서 userId 얻을 것 - Long userId = 1L; - // UserDetailResponse foundUserDto = userService.getUserDetailsById(userId); - String walletId = userService.getUserDetailsById(userId).getWalletId(); - - //결제 정보를 지갑에 저장하고, 저장이 안 됐을 시에는 throw Exception - - - } catch (Exception e) { - //repo로 저장 실패 시에는 결제를 취소해야 함 -> 토스페이로 결제 취소 요청 - } - //결제가 성공적, 결제 기록 저장했고 충전 로직까지 처리 - } - - //토스 결제 실패 -> 에러 메시지를 return | else 구문은 필요 없는데 일단 의미상 둠 - else { - //클라이언트로 Response - } - return null; + HttpResponse response = paymentService.sendPaymentRequestToTossPayment(confirmRequest); + //토스페이먼츠로 보낸 결제 승인 요청에 대한 response 리턴 + return paymentService.handleTossPaymentResponse(confirmRequest, response); } + private void validatePaymentConfirmRequest(PaymentRequest.Confirm confirmRequest, HttpSession session){ String savedOrderId = (String) session.getAttribute("orderId"); BigDecimal savedAmount = (BigDecimal) session.getAttribute("amount"); @@ -92,17 +66,12 @@ private void validatePaymentConfirmRequest(PaymentRequest.Confirm confirmRequest } private void validateSession(HttpSession session) { - if (session == null) { - throw new InvalidSessionException("Invalid session"); - } + if (session == null) {throw new InvalidSessionException("Invalid session");} + String savedOrderId = (String) session.getAttribute("orderId"); BigDecimal savedAmount = (BigDecimal) session.getAttribute("amount"); - if (savedOrderId == null || savedAmount == null) { - //에러 처리 - throw new InvalidSessionException("Invalid session"); - } - + if (savedOrderId == null || savedAmount == null) {throw new InvalidSessionException("Invalid session");} } From a89ccbdbae13b2c2e3b44b785591a8bea465ede0 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:33:19 +0900 Subject: [PATCH 12/27] =?UTF-8?q?fix=20:=20PaymentController=EB=A1=9C=20?= =?UTF-8?q?=EA=B3=A0=EC=B9=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 임시 이름에서 다시 변경 --- .../{TempPaymentController.java => PaymentController.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/com/tasksprints/auction/api/payment/{TempPaymentController.java => PaymentController.java} (100%) diff --git a/src/main/java/com/tasksprints/auction/api/payment/TempPaymentController.java b/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java similarity index 100% rename from src/main/java/com/tasksprints/auction/api/payment/TempPaymentController.java rename to src/main/java/com/tasksprints/auction/api/payment/PaymentController.java From 1da494ac8e8aec9deaa906acaf7ae2ed6f0a869a Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:14:59 +0900 Subject: [PATCH 13/27] =?UTF-8?q?refactor=20:=20payment=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경한 내용 1. Controller 로직 -> Service 레이어로 옮김 2. PayType, PayStatus 같은 Enum Type의 메서드명, 변수명을 변경 3. PaymentRequest, Response 토스 페이먼츠 변수명에 맞게 변경 3. paymentConfig url 변수명 변경 ++ 4. chargeMoney에서 save()까지 책임 옮김 --- .../auction/common/config/PaymentConfig.java | 2 +- .../payment/dto/request/PaymentRequest.java | 4 +- .../payment/dto/response/PaymentResponse.java | 16 +++- .../domain/payment/model/PayStatus.java | 19 ++-- .../auction/domain/payment/model/PayType.java | 25 +++-- .../payment/service/PaymentService.java | 5 +- .../payment/service/PaymentServiceImpl.java | 91 +++++++++++++++++-- .../wallet/service/WalletServiceImpl.java | 1 + 8 files changed, 131 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java b/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java index 8149e6aa..f76649e1 100644 --- a/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java +++ b/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java @@ -22,7 +22,7 @@ public class PaymentConfig { @Value("${payment.toss.fail_url}") private String failUrl; - public static final String URL = "https://api.tosspayments.com/v1/payments/"; + public static final String CONFIRM_URL = "https://api.tosspayments.com/v1/payments/confirm"; public String getAuthorizations() { String encodedKey = Base64.getEncoder().encodeToString((testSecretApiKey + ":").getBytes()); diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java index 329992fa..bc9c9a23 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java @@ -15,8 +15,6 @@ public class PaymentRequest { private String orderId; private String orderName; private PayType payType; -// private String successUrl; -// private String failUrl; @Getter @AllArgsConstructor @@ -32,7 +30,7 @@ public static class Prepare { public static class Confirm { private String orderId; private BigDecimal amount; - private String tossPaymentKey; + private String paymentKey; } public Payment toEntity() { diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java index d6e4ae73..76ed3ac5 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java @@ -1,15 +1,21 @@ package com.tasksprints.auction.domain.payment.dto.response; -import java.math.BigDecimal; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import java.math.BigDecimal; +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) public class PaymentResponse { + @JsonProperty("method") private String payType; + private String paymentKey; + @JsonProperty("totalAmount") private BigDecimal amount; private String orderName; private String orderId; - private String customerEmail; - private String customerName; - private String successUrl; - private String failUrl; + private String status; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java b/src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java index ad4b757f..e3de7c55 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/model/PayStatus.java @@ -4,21 +4,26 @@ @Getter public enum PayStatus { - PENDING("결제 진행중"), - APPROVED("결제 완료"), + READY("결제 생성 초기상태 - 결제 진행중"), + IN_PROGRESS("결제 승인 대기중"), + WAITING_FOR_DEPOSIT("가상 계좌 입금 대기중"), + PARTIAL_CANCELED("승인된 결제 부분 취소"), + EXPIRED("결제 유효 시간 초과"), + DONE("결제 완료"), + ABORTED("결제 승인 실패"), CANCELED("결제 취소"), FAILED("결제 실패"); - private final String status; + private final String displayName; - PayStatus(String status) {this.status = status;} + PayStatus(String displayName) {this.displayName = displayName;} - public static PayStatus fromString(String status) { + public static PayStatus fromString(String value) { for (PayStatus payStatus : PayStatus.values()) { - if (payStatus.name().equalsIgnoreCase(status)) { + if (payStatus.name().equalsIgnoreCase(value)) { return payStatus; } } - throw new IllegalArgumentException("Unknown PayStatus: " + status); + throw new IllegalArgumentException("Unknown PayStatus: " + value); } } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java b/src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java index de06eb0c..ae4a3d3f 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/model/PayType.java @@ -5,21 +5,28 @@ @Getter public enum PayType { CARD("카드 결제"), + VIRTUAL_ACCOUNT("가상계좌"), + SIMPLE_PAYMENT("간편결제"), + MOBILE_PAYMENT("휴대폰"), BANK_TRANSFER("계좌 이체"), - MOBILE_PAYMENT("모바일 결제"); + CULTURE_GIFT_CERTIFICATE("문화상품권"), + BOOK_CULTURE_CERTIFICATE("도서문화상품권"), + GAME_CULTURE_CERTIFICATE("게임문화상품권"); - private final String payType; - PayType(String payType) { - this.payType = payType; + private final String displayName; + + PayType(String displayName) { + this.displayName = displayName; } - public static PayType fromString(String type) { - for (PayType payType : PayType.values()) { - if (payType.name().equalsIgnoreCase(type)) { - return payType; + + public static PayType fromString(String value) { + for (PayType type : PayType.values()) { + if (type.displayName.equals(value)) { + return type; } } - throw new IllegalArgumentException("Unknown PayType: " + type); + throw new IllegalArgumentException("Unknown PayType: " + value); } } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java index 5a6988e2..94bc1107 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java @@ -3,6 +3,7 @@ import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import jakarta.servlet.http.HttpSession; +import org.springframework.http.ResponseEntity; import java.io.IOException; import java.net.http.HttpClient; @@ -11,5 +12,7 @@ public interface PaymentService { public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest); - public HttpResponse requestConfirm(HttpClient httpClient, PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; + public HttpResponse sendPaymentRequestToTossPayment(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; + public HttpResponse requestPaymentApprovalCancellation(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; + public ResponseEntity handleTossPaymentResponse(PaymentRequest.Confirm confirmRequest, HttpResponse response) throws IOException, InterruptedException ; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java index e67c29e1..d33f07a4 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java @@ -1,14 +1,24 @@ package com.tasksprints.auction.domain.payment.service; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.tasksprints.auction.common.config.PaymentConfig; +import com.tasksprints.auction.common.constant.ApiResponseMessages; +import com.tasksprints.auction.common.response.ApiResult; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; +import com.tasksprints.auction.domain.payment.model.Payment; import com.tasksprints.auction.domain.payment.repository.PaymentRepository; +import com.tasksprints.auction.domain.user.model.User; +import com.tasksprints.auction.domain.user.service.UserServiceImpl; +import com.tasksprints.auction.domain.wallet.model.Wallet; +import com.tasksprints.auction.domain.wallet.service.WalletServiceImpl; import jakarta.servlet.http.HttpSession; -import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.net.URI; @@ -16,13 +26,22 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; + @Service @RequiredArgsConstructor +@Slf4j public class PaymentServiceImpl implements PaymentService { private final PaymentConfig paymentConfig; private final PaymentRepository paymentRepository; - + private final WalletServiceImpl walletService; + private final UserServiceImpl userService; + + private final HttpClient httpClient; + private final ObjectMapper objectMapper; + + private static final Long FIXED_USER_ID = 2L; + @Override public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest) { session.setAttribute("orderId", prepareRequest.getOrderId()); @@ -30,12 +49,11 @@ public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest) } @Override - public HttpResponse requestConfirm(HttpClient httpClient, PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { - ObjectMapper objectMapper = new ObjectMapper(); + public HttpResponse sendPaymentRequestToTossPayment(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { String requestBody = objectMapper.writeValueAsString(confirmRequest); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("")) + .uri(URI.create(PaymentConfig.CONFIRM_URL)) .header("Authorization", paymentConfig.getAuthorizations()) .header("Content-Type", "application/json") .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) @@ -43,4 +61,65 @@ public HttpResponse requestConfirm(HttpClient httpClient, PaymentRequest return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); } + + @Transactional(rollbackFor = Exception.class) + @Override + public ResponseEntity handleTossPaymentResponse(PaymentRequest.Confirm confirmRequest, HttpResponse response) throws IOException, InterruptedException { + if (response.statusCode() == 200) { + return handlePaymentSuccess(confirmRequest, response); + } + return handlePaymentFailure(response); + } + + private ResponseEntity> handlePaymentSuccess(PaymentRequest.Confirm confirmRequest, HttpResponse response) throws IOException, InterruptedException { + //결제에 성공했더라도 결제 정보 저장에 실패하면 트랜잭션 롤백한다 + try { + PaymentResponse paymentResponseFromToss = objectMapper.readValue(response.body(), PaymentResponse.class); + Payment payment = Payment.create(paymentResponseFromToss); + + Wallet paymentUserWallet = this.getWalletFromUserId(FIXED_USER_ID); + payment.addWallet(paymentUserWallet); + + paymentRepository.save(payment); + walletService.chargeMoney(paymentUserWallet, payment.getAmount()); + + log.info("결제 성공.. 충전!"); + return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.PAYMENT_SUCCESS, paymentResponseFromToss)); + } catch (Exception e) { + System.out.println(e.getMessage()); + requestPaymentApprovalCancellation(confirmRequest); + log.info("결제 정보 저장 실패, 토스페이로 결제 취소 요청"); + throw e; + } + } + + private ResponseEntity handlePaymentFailure(HttpResponse response) { + log.info("결제 실패"); + return ResponseEntity.status(response.statusCode()).body(response.body()); + } + + @Override + public HttpResponse requestPaymentApprovalCancellation(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { + ObjectNode object = objectMapper.createObjectNode(); + object.put("cancelReason", "결제 도중 오류 발생"); + + String requestBody = objectMapper.writeValueAsString(object); + String insertPaymentKeyIntoUrl = String.format("https://api.tosspayments.com/v1/payments/%s/cancel", confirmRequest.getPaymentKey()); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(insertPaymentKeyIntoUrl)) + .header("Authorization", paymentConfig.getAuthorizations()) + .header("Content-Type", "application/json") +// .header("Idempotency-key", "멱등키") + .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + } + + private Wallet getWalletFromUserId(Long userId) { + User paymentUser = userService.findUserById(userId); + return paymentUser.getWallet(); + } + } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java index 4f4a574a..920cccce 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java @@ -15,6 +15,7 @@ public class WalletServiceImpl implements WalletService { @Override public void chargeMoney(Wallet wallet, BigDecimal amount) { wallet.chargeBalance(amount); + walletRepository.save(wallet); } @Override From c4cd219b76e1ae3c2a9a3fe27dce94b98e8ad8dc Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:17:39 +0900 Subject: [PATCH 14/27] =?UTF-8?q?feat=20:=20Payment=20create=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C,=20API=20=EB=A9=94=EC=84=B8=EC=A7=80,=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추가한 내용 : 1. Payment 엔티티에 create 메서드 추가 2. Payment 관련 Api 메세지 추가 3. Payment 관련 예외 메세지 추가 4. Http Client를 Config에서 Bean 등록 --- .../auction/common/config/HttpClientConfig.java | 14 ++++++++++++++ .../common/constant/ApiResponseMessages.java | 1 + .../exception/PaymentUserNotFoundException.java | 7 +++++++ .../auction/domain/payment/model/Payment.java | 12 ++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 src/main/java/com/tasksprints/auction/common/config/HttpClientConfig.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentUserNotFoundException.java diff --git a/src/main/java/com/tasksprints/auction/common/config/HttpClientConfig.java b/src/main/java/com/tasksprints/auction/common/config/HttpClientConfig.java new file mode 100644 index 00000000..325d8285 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/common/config/HttpClientConfig.java @@ -0,0 +1,14 @@ +package com.tasksprints.auction.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.net.http.HttpClient; + +@Configuration +public class HttpClientConfig { + @Bean + public HttpClient httpClient() { + return HttpClient.newHttpClient(); + } +} diff --git a/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java b/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java index 23506b50..9804ce3d 100644 --- a/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java +++ b/src/main/java/com/tasksprints/auction/common/constant/ApiResponseMessages.java @@ -34,4 +34,5 @@ public class ApiResponseMessages { // PAYMENT public static final String PAYMENT_PREPARED_SUCCESS = "Payment prepared successfully"; + public static final String PAYMENT_SUCCESS = "Payment completed and wallet charged successfully"; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentUserNotFoundException.java b/src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentUserNotFoundException.java new file mode 100644 index 00000000..d840b871 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/exception/PaymentUserNotFoundException.java @@ -0,0 +1,7 @@ +package com.tasksprints.auction.domain.payment.exception; + +public class PaymentUserNotFoundException extends RuntimeException { + public PaymentUserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java b/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java index 71481edc..4669e460 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/model/Payment.java @@ -1,6 +1,7 @@ package com.tasksprints.auction.domain.payment.model; import com.tasksprints.auction.common.entity.BaseEntityWithUpdate; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; import com.tasksprints.auction.domain.wallet.model.Wallet; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -52,6 +53,17 @@ public class Payment extends BaseEntityWithUpdate { @Column(nullable = true) private String cancelReason; + public static Payment create(PaymentResponse paymentResponse) { + return Payment.builder() + .tossOrderId(paymentResponse.getOrderId()) + .tossPaymentKey(paymentResponse.getPaymentKey()) + .orderName(paymentResponse.getOrderName()) + .payType(PayType.fromString(paymentResponse.getPayType())) + .payStatus(PayStatus.fromString(paymentResponse.getStatus())) + .amount(paymentResponse.getAmount()) + .build(); + } + public void addWallet(Wallet wallet) { this.wallet = wallet; wallet.addPayment(this); From c7c5e1a6e3a6a154f135b02b1be65c8f46177ac5 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:26:08 +0900 Subject: [PATCH 15/27] =?UTF-8?q?fix=20:=20UserDetailResponse=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EA=B0=AF=EC=88=98=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=97=90=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경한 내용 : 필요없는 인자를 커밋해서 빌드시 오류가 나던 것을 해결 --- .../auction/domain/user/dto/response/UserDetailResponse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java b/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java index e27e6fe8..65f4b8cb 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java @@ -12,7 +12,7 @@ public class UserDetailResponse { private String email; private String password; private String nickName; - private String walletId; +// private String walletId; private UserDetailResponse(User user) { this.id = user.getId(); @@ -20,7 +20,7 @@ private UserDetailResponse(User user) { this.email = user.getEmail(); this.password = user.getPassword(); this.nickName = user.getNickName(); - this.walletId = String.valueOf(user.getWallet().getId()); +// this.walletId = String.valueOf(user.getWallet().getId()); } public static UserDetailResponse of(User user) { From 2a69f5e3613590889f1152b4e6aa24ed724eaea7 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:46:18 +0900 Subject: [PATCH 16/27] =?UTF-8?q?refactor=20:=20User.create=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A5=BC=20createWithWallet=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경한 내용 : 1. User.create 메서드를 createWithWallet 메서드로 변경하면서 Wallet 생성 부분까지 한 번에 User를 생성하면서 맡도록 했음 2. Service 레이어에서 Wallet 생성 책임을 User에 넘겼음 3. 그로인해 AuctionInitializer 코드 변경(지갑 생성 부분) 4. UserDetailsResponse walletId 필드 추가 --- .../common/initializer/AuctionInitializer.java | 4 ++-- .../user/dto/response/UserDetailResponse.java | 4 ++-- .../tasksprints/auction/domain/user/model/User.java | 7 +++++-- .../domain/user/service/UserServiceImpl.java | 13 +++++-------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/common/initializer/AuctionInitializer.java b/src/main/java/com/tasksprints/auction/common/initializer/AuctionInitializer.java index c9ea2a40..e77b2094 100644 --- a/src/main/java/com/tasksprints/auction/common/initializer/AuctionInitializer.java +++ b/src/main/java/com/tasksprints/auction/common/initializer/AuctionInitializer.java @@ -37,7 +37,7 @@ public AuctionInitializer(UserRepository userRepository, AuctionRepository aucti } private void createDummyUser() { - User user1 = User.create("name", "email@email.com", "password", "NickName"); + User user1 = User.createWithWallet("name", "email@email.com", "password", "NickName"); userRepository.save(user1); } @@ -58,7 +58,7 @@ private void createDummyProduct(User user, Auction auction) { @Override @Transactional public void run(ApplicationArguments args) throws Exception { - User user = userRepository.save(User.create("name", "email@email.com", "password", "NickName")); + User user = userRepository.save(User.createWithWallet("name", "email@email.com", "password", "NickName")); // 각 제품에 대해 새로운 경매를 생성 for (int i = 0; i < 100; i++) { diff --git a/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java b/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java index 65f4b8cb..e27e6fe8 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/user/dto/response/UserDetailResponse.java @@ -12,7 +12,7 @@ public class UserDetailResponse { private String email; private String password; private String nickName; -// private String walletId; + private String walletId; private UserDetailResponse(User user) { this.id = user.getId(); @@ -20,7 +20,7 @@ private UserDetailResponse(User user) { this.email = user.getEmail(); this.password = user.getPassword(); this.nickName = user.getNickName(); -// this.walletId = String.valueOf(user.getWallet().getId()); + this.walletId = String.valueOf(user.getWallet().getId()); } public static UserDetailResponse of(User user) { diff --git a/src/main/java/com/tasksprints/auction/domain/user/model/User.java b/src/main/java/com/tasksprints/auction/domain/user/model/User.java index 6343794a..ffe09e2c 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/model/User.java +++ b/src/main/java/com/tasksprints/auction/domain/user/model/User.java @@ -53,13 +53,16 @@ public class User extends BaseEntityWithUpdate { /** * @descripton static factory pattern을 적용하여, 구현 */ - public static User create(String name, String email, String password, String nickName) { - return User.builder() + public static User createWithWallet(String name, String email, String password, String nickName) { + User user = User.builder() .name(name) .email(email) .password(password) .nickName(nickName) .build(); + Wallet wallet = Wallet.create(user); + user.addWallet(wallet); + return user; } // public Wallet createWalletForUser(User user) { // Wallet wallet = new Wallet(); diff --git a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java index 493aaa2a..9d5484b7 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java @@ -27,11 +27,8 @@ public class UserServiceImpl implements UserService { @Transactional @Override public UserDetailResponse createUser(UserRequest.Register request) { - User user = User.create(request.getName(), request.getEmail(), request.getPassword(), request.getNickname()); - Wallet wallet = this.createWalletForUser(user); - user.addWallet(wallet); + User user = User.createWithWallet(request.getName(), request.getEmail(), request.getPassword(), request.getNickname()); User newUser = userRepository.save(user); - // walletRepository.save(wallet); cascade = CascadeType.ALL 옵션으로 처리 return UserDetailResponse.of(newUser); } @@ -73,9 +70,9 @@ public User findUserById(Long id){ return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("User not found with id " + id)); } - private Wallet createWalletForUser(User user) { - return Optional.ofNullable(Wallet.create(user)) - .orElseThrow(() -> new WalletCreationException("Failed to create wallet for user: " + user.getEmail())); - } +// private Wallet createWalletForUser(User user) { +// return Optional.ofNullable(Wallet.create(user)) +// .orElseThrow(() -> new WalletCreationException("Failed to create wallet for user: " + user.getEmail())); +// } } From 89210418851eb5e9b2720ac4da1d2704598370a4 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:48:30 +0900 Subject: [PATCH 17/27] =?UTF-8?q?test=20:=20Wallet=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20User=20=EC=AA=BD=EC=97=90=EC=84=9C=20=ED=95=98?= =?UTF-8?q?=EB=A9=B4=EC=84=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경한 내용 : 1. wallet을 User쪽에서 한 번에 생성하면서 테스트 코드를 조금 변경함 2. UserDetailsResponse에 walletId 필드를 추가해서 테스트코드의 생성자의 파라미터로 walletId 필드 추가해줌 --- .../auction/api/UserControllerTest.java | 6 +++--- .../auction/domain/user/UserServiceImplTest.java | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/tasksprints/auction/api/UserControllerTest.java b/src/test/java/com/tasksprints/auction/api/UserControllerTest.java index 091985ab..3b3dfac5 100644 --- a/src/test/java/com/tasksprints/auction/api/UserControllerTest.java +++ b/src/test/java/com/tasksprints/auction/api/UserControllerTest.java @@ -48,7 +48,7 @@ class SuccessfulTests { @Test @DisplayName("POST /api/v1/user - 성공") void registerUser() throws Exception { - UserDetailResponse userDetailResponse = new UserDetailResponse(1L, "John", "john@example.com", "password", "john123"); + UserDetailResponse userDetailResponse = new UserDetailResponse(1L, "John", "john@example.com", "password", "john123", "1L"); Mockito.when(userService.createUser(any(UserRequest.Register.class))).thenReturn(userDetailResponse); @@ -67,7 +67,7 @@ void registerUser() throws Exception { @Test @DisplayName("GET /api/v1/user/{id} - 성공") void getUserById() throws Exception { - UserDetailResponse userDetailResponse = new UserDetailResponse(1L, "John", "john@example.com", "password", "john123"); + UserDetailResponse userDetailResponse = new UserDetailResponse(1L, "John", "john@example.com", "password", "john123", "1L"); Mockito.when(userService.getUserDetailsById(anyLong())).thenReturn(userDetailResponse); @@ -97,7 +97,7 @@ void getAllUsers() throws Exception { @Test @DisplayName("PUT /api/v1/user - 성공") void updateUser() throws Exception { - UserDetailResponse userDetailResponse = new UserDetailResponse(1L, "John Updated", "john@example.com", "newpassword", "john123updated"); + UserDetailResponse userDetailResponse = new UserDetailResponse(1L, "John Updated", "john@example.com", "newpassword", "john123updated", "1L"); Mockito.when(userService.updateUser(anyLong(), any(UserRequest.Update.class))).thenReturn(userDetailResponse); diff --git a/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java index 3989d968..37db4e07 100644 --- a/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/user/UserServiceImplTest.java @@ -6,11 +6,13 @@ import com.tasksprints.auction.domain.user.model.User; import com.tasksprints.auction.domain.user.repository.UserRepository; import com.tasksprints.auction.domain.user.service.UserServiceImpl; +import com.tasksprints.auction.domain.wallet.model.Wallet; import org.junit.jupiter.api.*; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.math.BigDecimal; import java.util.Optional; import static org.mockito.Mockito.*; @@ -22,6 +24,7 @@ public class UserServiceImplTest { @InjectMocks private UserServiceImpl userService; private User existingUser; + private Wallet existingWallet; @BeforeEach void setUp() { @@ -32,7 +35,17 @@ void setUp() { .nickName("testNick") .password("testPassword") .email("test@example.com") + .wallet(existingWallet) .build(); + + existingWallet = Wallet.builder() + .id(1L) + .balance(BigDecimal.ZERO) + .userName(existingUser.getName()) + .user(existingUser) + .build(); + + existingUser.addWallet(existingWallet); } @Nested @@ -45,7 +58,6 @@ void shouldCreateNewUser() { // Arrange UserRequest.Register request = new UserRequest.Register("testUser", "test@example.com", "testPassword", "testNick"); when(userRepository.save(any(User.class))).thenReturn(existingUser); - // Act UserDetailResponse createdUser = userService.createUser(request); From 823634839ae362d621f7fa7fbb3f684bc8de947b Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Sun, 1 Dec 2024 19:20:35 +0900 Subject: [PATCH 18/27] =?UTF-8?q?refactor=20:=20=EA=B2=B0=EC=A0=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경 및 추가한 내용 : 1. 인터페이스로 HttpClient 같은 의존성에 간접 의존하도록 바꿈 -> 변경에 유연하도록 2. 토스페이로부터 온 응답을 HttpResponse에서 직접 만든 Response(Object)로 변환해서 처리 -> 테스트 시 객체 생성이 쉬워지도록 3. userId를 통해 Wallet 반환받는 메서드를 쿼리로 변경 -> user.getWallet() 에서 JPQL 쿼리로 변경 미완 : 테스트 추가 필요 --- .../api/payment/PaymentController.java | 24 +++-- .../auction/domain/payment/api/Response.java | 28 +++++ .../domain/payment/client/ClientWrapper.java | 29 +++++ .../payment/client/HttpClientWrapper.java | 10 ++ .../domain/payment/client/PaymentClient.java | 14 +++ .../payment/client/TossPaymentClientImpl.java | 93 ++++++++++++++++ .../payment/dto/request/PaymentRequest.java | 7 ++ .../dto/response/PaymentErrorResponse.java | 26 +++++ .../payment/dto/response/PaymentResponse.java | 6 ++ .../payment/service/PaymentService.java | 9 +- .../payment/service/PaymentServiceImpl.java | 101 +++++------------- .../auction/domain/wallet/model/Wallet.java | 1 + .../wallet/repository/WalletRepository.java | 5 +- .../domain/wallet/service/WalletService.java | 6 +- .../wallet/service/WalletServiceImpl.java | 6 ++ 15 files changed, 277 insertions(+), 88 deletions(-) create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/api/Response.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/client/HttpClientWrapper.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/client/PaymentClient.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImpl.java create mode 100644 src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java diff --git a/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java b/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java index 1a8021e1..9d2bc18e 100644 --- a/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java +++ b/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java @@ -4,7 +4,9 @@ import com.tasksprints.auction.common.config.PaymentConfig; import com.tasksprints.auction.common.constant.ApiResponseMessages; import com.tasksprints.auction.common.response.ApiResult; +import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; import com.tasksprints.auction.domain.payment.exception.InvalidSessionException; import com.tasksprints.auction.domain.payment.exception.PaymentDataMismatchException; @@ -46,17 +48,23 @@ public ResponseEntity> preparePayment(HttpSession session, @Re } @PostMapping("/confirm") - public ResponseEntity confirmPayment(HttpSession session, @RequestBody PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { + public ResponseEntity confirmPayment(HttpSession session, @RequestBody PaymentRequest.Confirm confirmRequest, @RequestParam Long userId) throws IOException, InterruptedException { validateSession(session); validatePaymentConfirmRequest(confirmRequest, session); - HttpResponse response = paymentService.sendPaymentRequestToTossPayment(confirmRequest); + Response response = paymentService.sendPaymentRequest(confirmRequest); //토스페이먼츠로 보낸 결제 승인 요청에 대한 response 리턴 - return paymentService.handleTossPaymentResponse(confirmRequest, response); + Response objectResponse = paymentService.handleTossPaymentResponse(userId, confirmRequest, response); + + if (objectResponse.isSuccess()) { + PaymentResponse paymentResponse = (PaymentResponse) objectResponse.getBody(); + return ResponseEntity.ok(ApiResult.success("결제가 성공적으로 처리되었습니다.", paymentResponse)); + } + return ResponseEntity.status(response.getStatusCode()).body(response.getBody()); } - private void validatePaymentConfirmRequest(PaymentRequest.Confirm confirmRequest, HttpSession session){ + private void validatePaymentConfirmRequest(PaymentRequest.Confirm confirmRequest, HttpSession session) { String savedOrderId = (String) session.getAttribute("orderId"); BigDecimal savedAmount = (BigDecimal) session.getAttribute("amount"); @@ -66,12 +74,16 @@ private void validatePaymentConfirmRequest(PaymentRequest.Confirm confirmRequest } private void validateSession(HttpSession session) { - if (session == null) {throw new InvalidSessionException("Invalid session");} + if (session == null) { + throw new InvalidSessionException("Invalid session"); + } String savedOrderId = (String) session.getAttribute("orderId"); BigDecimal savedAmount = (BigDecimal) session.getAttribute("amount"); - if (savedOrderId == null || savedAmount == null) {throw new InvalidSessionException("Invalid session");} + if (savedOrderId == null || savedAmount == null) { + throw new InvalidSessionException("Invalid session"); + } } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/api/Response.java b/src/main/java/com/tasksprints/auction/domain/payment/api/Response.java new file mode 100644 index 00000000..41482d29 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/api/Response.java @@ -0,0 +1,28 @@ +package com.tasksprints.auction.domain.payment.api; + +import lombok.Getter; + +@Getter +public class Response { + private final int statusCode; + private final T body; + + public Response(int statusCode, T body) { + this.statusCode = statusCode; + this.body = body; + + } + + public static Response success(int statusCode, T body) { + return new Response<>(statusCode, body); + } + + public static Response failure(int statusCode, T body) { + return new Response<>(statusCode, body); + } + + public boolean isSuccess() { + return statusCode == 200; + } + +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java b/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java new file mode 100644 index 00000000..331728d8 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java @@ -0,0 +1,29 @@ +package com.tasksprints.auction.domain.payment.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tasksprints.auction.domain.payment.api.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + + +import java.io.IOException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +@Component +@RequiredArgsConstructor +public class ClientWrapper implements HttpClientWrapper{ + private final HttpClient httpClient; + private final ObjectMapper objectMapper; + + @Override + public Response send(HttpRequest request) throws IOException, InterruptedException { + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + return Response.success(response.statusCode(), response.body()); + } + return Response.failure(response.statusCode(), response.body()); + } +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/HttpClientWrapper.java b/src/main/java/com/tasksprints/auction/domain/payment/client/HttpClientWrapper.java new file mode 100644 index 00000000..9471b6f3 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/HttpClientWrapper.java @@ -0,0 +1,10 @@ +package com.tasksprints.auction.domain.payment.client; + +import com.tasksprints.auction.domain.payment.api.Response; + +import java.io.IOException; +import java.net.http.HttpRequest; + +public interface HttpClientWrapper { + Response send(HttpRequest request) throws IOException, InterruptedException; +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/PaymentClient.java b/src/main/java/com/tasksprints/auction/domain/payment/client/PaymentClient.java new file mode 100644 index 00000000..33873d25 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/PaymentClient.java @@ -0,0 +1,14 @@ +package com.tasksprints.auction.domain.payment.client; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.tasksprints.auction.domain.payment.api.Response; +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; + +import java.io.IOException; +import java.net.http.HttpResponse; + +public interface PaymentClient { + Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; + Response cancelPaymentApproval(PaymentRequest.Cancel cancelRequest) throws IOException, InterruptedException; +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImpl.java new file mode 100644 index 00000000..ca124f4f --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImpl.java @@ -0,0 +1,93 @@ +package com.tasksprints.auction.domain.payment.client; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.tasksprints.auction.common.config.PaymentConfig; +import com.tasksprints.auction.domain.payment.api.Response; +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; + +@Component +@RequiredArgsConstructor +public class TossPaymentClientImpl implements PaymentClient { + + private final PaymentConfig paymentConfig; + private final ObjectMapper objectMapper; + private final HttpClientWrapper httpClientWrapper; + + @Override + public Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { + String requestBody = buildDtoToRequestBody(confirmRequest); + HttpRequest request = createHttpRequest(requestBody); + Response response = paymentRequestToTossPay(request); + + if (response.isSuccess()) { + PaymentResponse paymentResponse = objectMapper.readValue(response.getBody(), PaymentResponse.class); + return Response.success(response.getStatusCode(), paymentResponse); + } + PaymentErrorResponse errorResponse = objectMapper.readValue(response.getBody(), PaymentErrorResponse.class); + return Response.failure(response.getStatusCode(), errorResponse); + } + + private String buildDtoToRequestBody(PaymentRequest.Confirm confirmRequest) throws JsonProcessingException { + return objectMapper.writeValueAsString(confirmRequest); + } + + private HttpRequest createHttpRequest(String requestBody) { + return HttpRequest.newBuilder() + .uri(URI.create(PaymentConfig.CONFIRM_URL)) + .header("Authorization", paymentConfig.getAuthorizations()) + .header("Content-Type", "application/json") + .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + } + + private Response paymentRequestToTossPay(HttpRequest request) throws IOException, InterruptedException { + return requestToTossPay(request); + } + + @Override + public Response cancelPaymentApproval(PaymentRequest.Cancel cancelRequest) throws IOException, InterruptedException { + String requestBody = createCancelRequestBody(); + HttpRequest request = buildCancelHttpRequest(cancelRequest, requestBody); + Response response = requestToTossPay(request); + + if (response.isSuccess()) { + PaymentResponse paymentResponse = objectMapper.readValue(response.getBody(), PaymentResponse.class); + return Response.success(response.getStatusCode(), paymentResponse); + } + PaymentErrorResponse errorResponse = objectMapper.readValue(response.getBody(), PaymentErrorResponse.class); + return Response.failure(response.getStatusCode(), errorResponse); + + } + + private String createCancelRequestBody() throws JsonProcessingException { + ObjectNode object = objectMapper.createObjectNode(); + object.put("cancelReason", "결제 도중 오류 발생"); + return objectMapper.writeValueAsString(object); + } + + private HttpRequest buildCancelHttpRequest(PaymentRequest.Cancel cancelRequest, String requestBody) { + String insertPaymentKeyIntoUrl = String.format("https://api.tosspayments.com/v1/payments/%s/cancel", cancelRequest.getPaymentKey()); + return HttpRequest.newBuilder() + .uri(URI.create(insertPaymentKeyIntoUrl)) + .header("Authorization", paymentConfig.getAuthorizations()) + .header("Content-Type", "application/json") + // .header("Idempotency-key", "멱등키") + .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + } + + private Response requestToTossPay(HttpRequest request) throws IOException, InterruptedException { + return httpClientWrapper.send(request); + } + +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java index bc9c9a23..8fee6d93 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/request/PaymentRequest.java @@ -33,6 +33,13 @@ public static class Confirm { private String paymentKey; } + @Getter + @AllArgsConstructor + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Cancel { + private String paymentKey; + } + public Payment toEntity() { return Payment.builder() .amount(amount) diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java new file mode 100644 index 00000000..0d7b7067 --- /dev/null +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java @@ -0,0 +1,26 @@ +package com.tasksprints.auction.domain.payment.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PaymentErrorResponse { + private String version; + private String traceId; + private ErrorDetail error; + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ErrorDetail { + private String code; + private String message; + } + +} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java index 76ed3ac5..7aaab351 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentResponse.java @@ -3,11 +3,17 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import java.math.BigDecimal; @Getter +@Builder @JsonIgnoreProperties(ignoreUnknown = true) +@NoArgsConstructor +@AllArgsConstructor public class PaymentResponse { @JsonProperty("method") private String payType; diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java index 94bc1107..4ebccef2 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java @@ -1,18 +1,17 @@ package com.tasksprints.auction.domain.payment.service; +import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import jakarta.servlet.http.HttpSession; import org.springframework.http.ResponseEntity; import java.io.IOException; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; public interface PaymentService { public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest); - public HttpResponse sendPaymentRequestToTossPayment(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; - public HttpResponse requestPaymentApprovalCancellation(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; - public ResponseEntity handleTossPaymentResponse(PaymentRequest.Confirm confirmRequest, HttpResponse response) throws IOException, InterruptedException ; + public Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; + public Response cancelPaymentApproval(PaymentRequest.Cancel cancelRequest) throws IOException, InterruptedException; + public Response handleTossPaymentResponse(Long userId, PaymentRequest.Confirm confirmRequest, Response response) throws IOException, InterruptedException ; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java index d33f07a4..2e75348d 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java @@ -1,125 +1,78 @@ package com.tasksprints.auction.domain.payment.service; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.tasksprints.auction.common.config.PaymentConfig; -import com.tasksprints.auction.common.constant.ApiResponseMessages; -import com.tasksprints.auction.common.response.ApiResult; +import com.tasksprints.auction.domain.payment.api.Response; +import com.tasksprints.auction.domain.payment.client.PaymentClient; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; import com.tasksprints.auction.domain.payment.model.Payment; import com.tasksprints.auction.domain.payment.repository.PaymentRepository; -import com.tasksprints.auction.domain.user.model.User; -import com.tasksprints.auction.domain.user.service.UserServiceImpl; import com.tasksprints.auction.domain.wallet.model.Wallet; -import com.tasksprints.auction.domain.wallet.service.WalletServiceImpl; +import com.tasksprints.auction.domain.wallet.service.WalletService; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; @Service @RequiredArgsConstructor @Slf4j public class PaymentServiceImpl implements PaymentService { - - private final PaymentConfig paymentConfig; private final PaymentRepository paymentRepository; - private final WalletServiceImpl walletService; - private final UserServiceImpl userService; - - private final HttpClient httpClient; - private final ObjectMapper objectMapper; - - private static final Long FIXED_USER_ID = 2L; + private final WalletService walletService; + private final PaymentClient paymentClient; @Override public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest) { session.setAttribute("orderId", prepareRequest.getOrderId()); session.setAttribute("amount", prepareRequest.getAmount()); } - @Override - public HttpResponse sendPaymentRequestToTossPayment(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { - String requestBody = objectMapper.writeValueAsString(confirmRequest); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(PaymentConfig.CONFIRM_URL)) - .header("Authorization", paymentConfig.getAuthorizations()) - .header("Content-Type", "application/json") - .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) - .build(); + public Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException{ + return paymentClient.sendPaymentRequest(confirmRequest); + } - return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + @Override + public Response cancelPaymentApproval(PaymentRequest.Cancel cancelRequest) throws IOException, InterruptedException { + return paymentClient.cancelPaymentApproval(cancelRequest); } @Transactional(rollbackFor = Exception.class) @Override - public ResponseEntity handleTossPaymentResponse(PaymentRequest.Confirm confirmRequest, HttpResponse response) throws IOException, InterruptedException { - if (response.statusCode() == 200) { - return handlePaymentSuccess(confirmRequest, response); + public Response handleTossPaymentResponse(Long userId, PaymentRequest.Confirm confirmRequest, Response response) throws IOException, InterruptedException { + if (response.getStatusCode() == 200) { + return handlePaymentSuccess(userId, confirmRequest, response); } return handlePaymentFailure(response); } - private ResponseEntity> handlePaymentSuccess(PaymentRequest.Confirm confirmRequest, HttpResponse response) throws IOException, InterruptedException { + private Response handlePaymentSuccess(Long userId, PaymentRequest.Confirm confirmRequest, Response response) throws IOException, InterruptedException { //결제에 성공했더라도 결제 정보 저장에 실패하면 트랜잭션 롤백한다 try { - PaymentResponse paymentResponseFromToss = objectMapper.readValue(response.body(), PaymentResponse.class); + PaymentResponse paymentResponseFromToss = (PaymentResponse) response.getBody(); Payment payment = Payment.create(paymentResponseFromToss); - Wallet paymentUserWallet = this.getWalletFromUserId(FIXED_USER_ID); - payment.addWallet(paymentUserWallet); + Wallet paidUserWallet = walletService.getWalletByUserId(userId); + payment.addWallet(paidUserWallet); paymentRepository.save(payment); - walletService.chargeMoney(paymentUserWallet, payment.getAmount()); - - log.info("결제 성공.. 충전!"); - return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.PAYMENT_SUCCESS, paymentResponseFromToss)); + walletService.chargeMoney(paidUserWallet, payment.getAmount()); + return response; } catch (Exception e) { - System.out.println(e.getMessage()); - requestPaymentApprovalCancellation(confirmRequest); - log.info("결제 정보 저장 실패, 토스페이로 결제 취소 요청"); + log.error("결제 처리 중 예외 발생: {}", e.getMessage(), e); + String paymentKey = confirmRequest.getPaymentKey(); + PaymentRequest.Cancel cancelRequest = new PaymentRequest.Cancel(paymentKey); + + paymentClient.cancelPaymentApproval(cancelRequest); throw e; } } - private ResponseEntity handlePaymentFailure(HttpResponse response) { + Response handlePaymentFailure(Response response) { log.info("결제 실패"); - return ResponseEntity.status(response.statusCode()).body(response.body()); + return response; } - - @Override - public HttpResponse requestPaymentApprovalCancellation(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException { - ObjectNode object = objectMapper.createObjectNode(); - object.put("cancelReason", "결제 도중 오류 발생"); - - String requestBody = objectMapper.writeValueAsString(object); - String insertPaymentKeyIntoUrl = String.format("https://api.tosspayments.com/v1/payments/%s/cancel", confirmRequest.getPaymentKey()); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(insertPaymentKeyIntoUrl)) - .header("Authorization", paymentConfig.getAuthorizations()) - .header("Content-Type", "application/json") -// .header("Idempotency-key", "멱등키") - .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) - .build(); - - return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - } - - private Wallet getWalletFromUserId(Long userId) { - User paymentUser = userService.findUserById(userId); - return paymentUser.getWallet(); - } - } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java index 846cdd29..1a14a7ee 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/model/Wallet.java @@ -34,6 +34,7 @@ public class Wallet extends BaseEntityWithUpdate { private String userName; @OneToMany(mappedBy = "wallet", fetch = FetchType.LAZY) + @Builder.Default private List payments = new ArrayList<>(); @OneToOne(mappedBy = "wallet", fetch = FetchType.LAZY) diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java b/src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java index 132adca0..cfe4e83c 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/repository/WalletRepository.java @@ -2,7 +2,10 @@ import com.tasksprints.auction.domain.wallet.model.Wallet; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface WalletRepository extends JpaRepository { - + @Query("SELECT u.wallet FROM users u WHERE u.id = :userId") + Wallet getWalletByUserId(@Param("userId") Long userId); } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java index 639b7087..0383a46b 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletService.java @@ -5,8 +5,10 @@ import java.math.BigDecimal; public interface WalletService { - //충전 + void chargeMoney(Wallet wallet, BigDecimal amount); - //잔액 충분한지? boolean isSufficientMoney(); + + Wallet getWalletByUserId(Long userId); + } diff --git a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java index 920cccce..7325c3ab 100644 --- a/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImpl.java @@ -1,5 +1,7 @@ package com.tasksprints.auction.domain.wallet.service; +import com.tasksprints.auction.domain.user.model.User; +import com.tasksprints.auction.domain.user.service.UserService; import com.tasksprints.auction.domain.wallet.model.Wallet; import com.tasksprints.auction.domain.wallet.repository.WalletRepository; import lombok.RequiredArgsConstructor; @@ -23,5 +25,9 @@ public boolean isSufficientMoney() { return false; } + @Override + public Wallet getWalletByUserId(Long userId) { + return walletRepository.getWalletByUserId(userId); + } } From f1ff1faa05b988f4b1fb9dacafa9b4f0858191bc Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Sun, 1 Dec 2024 19:22:22 +0900 Subject: [PATCH 19/27] =?UTF-8?q?test=20:=20=EA=B2=B0=EC=A0=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추가한 내용 : paymentKey 세션 임시 저장 확인 테스트 결제 상태에 따른 메서드 처리 확인 테스트 Wallet 조회, 충전 관련 테스트 추가 등등 미완 : 컨트롤러 테스트 추가 필 --- .../payment/client/ClientWrapperTest.java | 42 ++++ .../client/TossPaymentClientImplTest.java | 123 +++++++++++ .../repository/PaymentRepositoryTest.java | 64 ++++++ .../service/PaymentServiceImplTest.java | 192 ++++++++++++++++-- .../repository/WalletRepositoryTest.java | 48 +++++ .../wallet/service/WalletServiceImplTest.java | 62 ++++++ 6 files changed, 512 insertions(+), 19 deletions(-) create mode 100644 src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java create mode 100644 src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImplTest.java create mode 100644 src/test/java/com/tasksprints/auction/domain/wallet/repository/WalletRepositoryTest.java create mode 100644 src/test/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImplTest.java diff --git a/src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java b/src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java new file mode 100644 index 00000000..5f039038 --- /dev/null +++ b/src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java @@ -0,0 +1,42 @@ +package com.tasksprints.auction.domain.payment.client; + +import com.tasksprints.auction.domain.payment.api.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ClientWrapperTest { + + @Mock + private HttpClientWrapper clientWrapper; + + @BeforeEach + void setUp() { + + } + + @Test + void send가_성공적으로_응답을_받음() throws IOException, InterruptedException { + //given + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://example.com")) + .build(); + Response mockResponse = Response.success(200, "test Body"); + when(clientWrapper.send(request)).thenReturn(mockResponse); + //when + Response result = clientWrapper.send(request); + //then + assertEquals(200, result.getStatusCode()); + assertEquals("test Body", result.getBody()); + } +} diff --git a/src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImplTest.java new file mode 100644 index 00000000..f4762f32 --- /dev/null +++ b/src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImplTest.java @@ -0,0 +1,123 @@ +package com.tasksprints.auction.domain.payment.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tasksprints.auction.common.config.PaymentConfig; +import com.tasksprints.auction.domain.payment.api.Response; +import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.math.BigDecimal; +import java.net.http.HttpRequest; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TossPaymentClientImplTest { + + @Mock + private HttpClientWrapper httpClientWrapper; + private PaymentConfig paymentConfig; + private ObjectMapper objectMapper; + private PaymentClient tossPaymentClient; + + @BeforeEach + void setUp() { + paymentConfig = new PaymentConfig() { + @Override + public String getAuthorizations() { + return "https://api.test.com/payments"; + } + }; + objectMapper = new ObjectMapper(); + tossPaymentClient = new TossPaymentClientImpl(paymentConfig, objectMapper, httpClientWrapper); + + } + + @Nested + @DisplayName("결제 요청 테스트") + class payment_request_test { + @Test + void payment_request의_응답이_200번_코드라면_PaymentResponse_타입을_리턴받는다() throws IOException, InterruptedException { + //given + PaymentRequest.Confirm confirmRequest = new PaymentRequest.Confirm("orderId", BigDecimal.TEN, "paymentKey"); + String responseBody = "{ \"status\": \"COMPLETED\", \"paymentKey\": \"abc123\",\"orderName\": \"Sample Order\", \"amount\": 5000 }"; + Response mockResponse = Response.success(200, responseBody); + + when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); + //when + Response response = tossPaymentClient.sendPaymentRequest(confirmRequest); + //then + Assertions.assertThat(response.getStatusCode()).isEqualTo(200); + Assertions.assertThat(response.getBody()).isInstanceOf(PaymentResponse.class); + + } + + @Test + void payment_request의_응답이_200번_코드가_아니면_PaymentErrorResponse_타입을_리턴받는다() throws IOException, InterruptedException { + //given + PaymentRequest.Confirm confirmRequest = new PaymentRequest.Confirm("orderId", BigDecimal.TEN, "paymentKey"); + String responseBody = "{\"version\": \"2022-11-16\", \"traceId\": \"{traceId}\", " + + "\"error\": {\"code\": \"{CODE}\", \"message\": \"{MESSAGE}\"}}"; + + Response mockResponse = Response.failure(404, responseBody); + + when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); + //when + Response response = tossPaymentClient.sendPaymentRequest(confirmRequest); + //then + Assertions.assertThat(response.getStatusCode()).isEqualTo(404); + Assertions.assertThat(response.getBody()).isInstanceOf(PaymentErrorResponse.class); + + } + } + + @Nested + @DisplayName("결제 취소 요청 테스트") + class payment_cancel_test { + @Test + void cancel_request의_응답이_200번_코드라면_PaymentResponse_타입을_리턴받는다() throws IOException, InterruptedException { + //given + PaymentRequest.Cancel cancelRequest = new PaymentRequest.Cancel("paymentKey"); + String responseBody = "{ \"status\": \"CANCELED\", \"paymentKey\": \"abc123\",\"orderName\": \"Sample Order\", \"amount\": 5000 }"; + Response mockResponse = Response.success(200, responseBody); + + when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); + //when + Response response = tossPaymentClient.cancelPaymentApproval(cancelRequest); + //then + Assertions.assertThat(response.getStatusCode()).isEqualTo(200); + Assertions.assertThat(response.getBody()).isInstanceOf(PaymentResponse.class); + + } + @Test + void cancel_request의_응답이_200번이_아니라면_PaymentErrorResponse_타입을_리턴받는다() throws IOException, InterruptedException { + //given + PaymentRequest.Cancel cancelRequest = new PaymentRequest.Cancel("paymentKey"); + String responseBody = "{\"version\": \"2022-11-16\", \"traceId\": \"{traceId}\", " + + "\"error\": {\"code\": \"{CODE}\", \"message\": \"{MESSAGE}\"}}"; + Response mockResponse = Response.success(400, responseBody); + + when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); + //when + Response response = tossPaymentClient.cancelPaymentApproval(cancelRequest); + //then + Assertions.assertThat(response.getStatusCode()).isEqualTo(400); + Assertions.assertThat(response.getBody()).isInstanceOf(PaymentErrorResponse.class); + + } + + } + + +} diff --git a/src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java b/src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java index e94f6a9e..4aecc0b7 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/repository/PaymentRepositoryTest.java @@ -1,4 +1,68 @@ package com.tasksprints.auction.domain.payment.repository; +import com.tasksprints.auction.common.config.QueryDslConfig; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; +import com.tasksprints.auction.domain.payment.model.PayStatus; +import com.tasksprints.auction.domain.payment.model.PayType; +import com.tasksprints.auction.domain.payment.model.Payment; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import java.math.BigDecimal; + + +@DataJpaTest +@Import(QueryDslConfig.class) public class PaymentRepositoryTest { + + @Autowired + private PaymentRepository paymentRepository; + + @Nested + class 결제_저장_테스트 { + @Test + void 결제_응답이_올바르게_저장되면_성공한다() { + //given + PaymentResponse paymentResponseFromToss = PaymentResponse.builder() + .payType("간편결제") + .paymentKey("testPaymentKey") + .amount(BigDecimal.ZERO) + .orderName("testOrderName") + .orderId("testOrderId") + .status("DONE") + .build(); + + Payment payment = Payment.create(paymentResponseFromToss); + //when + Payment savedPayment = paymentRepository.save(payment); + //then + Assertions.assertThat(savedPayment.getPaymentId()).isNotNull(); + Assertions.assertThat(savedPayment.getPayType()).isEqualTo(PayType.SIMPLE_PAYMENT); + Assertions.assertThat(savedPayment.getTossPaymentKey()).isEqualTo("testPaymentKey"); + Assertions.assertThat(savedPayment.getAmount()).isEqualTo(BigDecimal.ZERO); + Assertions.assertThat(savedPayment.getPayStatus()).isEqualTo(PayStatus.DONE); + + } + + @Test + void 결제_응답을_저장_시_create_메서드_처리_도중_ENUM에_없는_값이_들어오면_예외가_발생한다() { + PaymentResponse paymentResponseFromToss = PaymentResponse.builder() + .payType("잘못된 결제수단 입력") + .paymentKey("testPaymentKey") + .amount(BigDecimal.ZERO) + .orderName("testOrderName") + .orderId("testOrderId") + .status("잘못된 결제 상태 입력") + .build(); + + Assertions.assertThatThrownBy(() -> Payment.create(paymentResponseFromToss)) + .isInstanceOf(IllegalArgumentException.class); + + } + } + } diff --git a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java index 315538ed..aae3c0d1 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java @@ -1,51 +1,205 @@ package com.tasksprints.auction.domain.payment.service; +import com.tasksprints.auction.domain.payment.api.Response; +import com.tasksprints.auction.domain.payment.client.PaymentClient; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; - +import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; +import com.tasksprints.auction.domain.payment.model.Payment; +import com.tasksprints.auction.domain.payment.repository.PaymentRepository; +import com.tasksprints.auction.domain.user.model.User; +import com.tasksprints.auction.domain.wallet.model.Wallet; +import com.tasksprints.auction.domain.wallet.service.WalletService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; - +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockHttpSession; +import java.io.IOException; import java.math.BigDecimal; -import static org.assertj.core.api.Assertions.*; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class PaymentServiceImplTest { + @Spy @InjectMocks private PaymentServiceImpl paymentService; + @Mock + private WalletService walletService; + @Mock + private PaymentClient paymentClient; + @Mock + private PaymentRepository paymentRepository; + + private MockHttpSession session; + + private User user; + private Wallet wallet; + + @BeforeEach + void setUp() { + session = new MockHttpSession(); -// @Mock -// private MockHttpSession MockSession; + wallet = Wallet.builder() + .id(1L) + .balance(BigDecimal.ZERO) + .userName("testUser") + .build(); + + user = User.builder() + .id(1L) + .name("testUser") + .nickName("test") + .password("password") + .email("test@naver.com") + .wallet(wallet) + .build(); + + + } @Nested @DisplayName("결제 전 세션 임시 저장 테스트") - class 임시_저장_테스트{ + class 임시_저장_테스트 { @Test - void 결제_요청을_받았을_때_세션에_값이_저장되면_성공한다 () { - //given - MockHttpSession session = new MockHttpSession(); - String orderId = "testOrderId"; - BigDecimal amount = BigDecimal.valueOf(1000.00); - PaymentRequest.Prepare prepareRequest = new PaymentRequest.Prepare(orderId, amount); - - //when - paymentService.prepare(session, prepareRequest); - //then - assertThat(session.getAttribute("orderId")).isEqualTo(orderId); - assertThat(session.getAttribute("amount")).isEqualTo(amount); + void 결제_요청을_받았을_때_세션에_값이_저장되면_성공한다() { + //given + String orderId = "testOrderId"; + BigDecimal amount = BigDecimal.valueOf(1000.00); + PaymentRequest.Prepare prepareRequest = new PaymentRequest.Prepare(orderId, amount); + + //when + paymentService.prepare(session, prepareRequest); + //then + assertThat(session.getAttribute("orderId")).isEqualTo(orderId); + assertThat(session.getAttribute("amount")).isEqualTo(amount); } + @Test - void 결제_요청을_받았을_때_세션에_값이_저장되지_않으면_예외_처리 () { + void 결제_요청을_받았을_때_세션에_값이_저장되지_않으면_예외_처리() { //given //when //then } } + + @Nested + @DisplayName("토스_페이_응답_처리") + class handleTossPayResponse { + @Test + void 결제_성공_시_지갑에_돈을_충전한다() throws IOException, InterruptedException { + //given + PaymentRequest.Confirm confirmRequest = new PaymentRequest.Confirm("orderId", BigDecimal.valueOf(50000), "paymentKey"); + PaymentResponse paymentResponse = PaymentResponse.builder() + .payType("카드 결제") + .paymentKey("paymentKey") + .amount(BigDecimal.valueOf(50000)) + .orderName("Sample Order") + .orderId("orderId") + .status("DONE") + .build(); + + Response successResponse = Response.success(200, paymentResponse); + + when(walletService.getWalletByUserId(user.getId())).thenReturn(wallet); + doNothing().when(walletService).chargeMoney(eq(wallet), eq(BigDecimal.valueOf(50000))); + + //when + paymentService.handleTossPaymentResponse(1L, confirmRequest, successResponse); + + //then + verify(walletService).chargeMoney(eq(wallet), eq(BigDecimal.valueOf(50000))); + verify(paymentRepository).save(any(Payment.class)); + } + + @Test + void 결제_성공_후_결제_정보_저장을_실패하면_결제_취소_요청을_보낸다() throws IOException, InterruptedException { + // given + PaymentRequest.Confirm confirmRequest = new PaymentRequest.Confirm("orderId", BigDecimal.valueOf(50000), "paymentKey"); + PaymentResponse paymentResponse = PaymentResponse.builder() + .payType("카드 결제") + .paymentKey("paymentKey") + .amount(BigDecimal.valueOf(50000)) + .orderName("Sample Order") + .orderId("orderId") + .status("DONE") + .build(); + + Response successResponse = Response.success(200, paymentResponse); + + when(walletService.getWalletByUserId(user.getId())).thenReturn(wallet); + when(paymentRepository.save(any(Payment.class))) + .thenThrow(new RuntimeException("결제 정보 저장 실패")); + + // when + assertThrows(RuntimeException.class, () -> + paymentService.handleTossPaymentResponse(1L, confirmRequest, successResponse) + ); + + // then + verify(paymentClient).cancelPaymentApproval(any(PaymentRequest.Cancel.class)); + //예외 발생으로 chargeMoney전에 throw 됐을 것 + verify(walletService, times(0)).chargeMoney(any(Wallet.class), any(BigDecimal.class)); + } + + @Test + void 결제_실패_시_handlePaymentFailure_를_실행한다() throws IOException, InterruptedException { + //given + PaymentRequest.Confirm confirmRequest = new PaymentRequest.Confirm("orderId", BigDecimal.valueOf(50000), "paymentKey"); + PaymentErrorResponse errorResponse = PaymentErrorResponse.builder() + .version("2022-11-16") + .traceId("{traceId}") + .error(PaymentErrorResponse.ErrorDetail.builder() + .code("{CODE}") + .message("{MESSAGE}") + .build() + ) + .build(); + Response failureResponse = Response.failure(404, errorResponse); + + //when + paymentService.handleTossPaymentResponse(1L, confirmRequest, failureResponse); + + //then + verify(paymentService).handlePaymentFailure(failureResponse); + } + + } +// @Test +// @DisplayName("handleTossPaymentResponse Test") +// void 토스페이로_httpRequest가_전송되면_성공한다() throws IOException, InterruptedException { +// // given +// PaymentRequest.Confirm confirmRequest = new PaymentRequest.Confirm("orderId123", BigDecimal.valueOf(1000), "paymentKey123"); +// when(objectMapper.writeValueAsString(confirmRequest)).thenReturn("{\"orderId\":\"orderId123\",\"amount\":1000,\"paymentKey\":\"paymentKey123\"}"); +// when(paymentConfig.getAuthorizations()).thenReturn("test-token"); +// when(mockResponse.statusCode()).thenReturn(200); +// when(mockResponse.body()).thenReturn("{\"status\":\"SUCCESS\"}"); +// when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))) +// .thenReturn(mockResponse); +// +// // when +// HttpResponse response = paymentService.sendPaymentRequest(confirmRequest); +// +// // then +// assertNotNull(response); +// assertEquals(200, response.statusCode()); +// assertEquals("{\"status\":\"SUCCESS\"}", response.body()); +// verify(httpClient, times(1)).send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class)); +// verify(paymentConfig, times(1)).getAuthorizations(); +// } + + } diff --git a/src/test/java/com/tasksprints/auction/domain/wallet/repository/WalletRepositoryTest.java b/src/test/java/com/tasksprints/auction/domain/wallet/repository/WalletRepositoryTest.java new file mode 100644 index 00000000..bf358704 --- /dev/null +++ b/src/test/java/com/tasksprints/auction/domain/wallet/repository/WalletRepositoryTest.java @@ -0,0 +1,48 @@ +package com.tasksprints.auction.domain.wallet.repository; + +import com.tasksprints.auction.common.config.QueryDslConfig; +import com.tasksprints.auction.domain.user.model.User; +import com.tasksprints.auction.domain.user.repository.UserRepository; +import com.tasksprints.auction.domain.wallet.model.Wallet; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import static org.junit.jupiter.api.Assertions.*; +@DataJpaTest +@Import(QueryDslConfig.class) +class WalletRepositoryTest { + @Autowired + WalletRepository walletRepository; + @Autowired + UserRepository userRepository; + + private User user; + + @BeforeEach + void setUp() { + user = User.createWithWallet( + "testName", + "testEmail", + "testPassword", + "testNickName" + ); + user = userRepository.save(user); + } + + @Test + @DisplayName("getWalletByUserId 테스트 : JPQL로 UserId를 통해 조회했을 시 Wallet이 리턴되면 성공한다") + void JPQL로_UserId를_통해_조회했을_시_Wallet이_리턴되면_성공한다() { + //given + Long userId = user.getId(); + //when + Wallet foundWallet = walletRepository.getWalletByUserId(userId); + //then + assertNotNull(foundWallet); + assertEquals(user.getWallet().getId(), foundWallet.getId()); + } + +} diff --git a/src/test/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImplTest.java new file mode 100644 index 00000000..2618fb93 --- /dev/null +++ b/src/test/java/com/tasksprints/auction/domain/wallet/service/WalletServiceImplTest.java @@ -0,0 +1,62 @@ +package com.tasksprints.auction.domain.wallet.service; + +import com.tasksprints.auction.domain.wallet.model.Wallet; +import com.tasksprints.auction.domain.wallet.repository.WalletRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class WalletServiceImplTest { + + @InjectMocks + private WalletServiceImpl walletService; + + @Mock + private WalletRepository walletRepository; + + private Wallet wallet; + + @BeforeEach + void setUp() { + wallet = Wallet.builder() + .id(1L) + .balance(BigDecimal.ZERO) + .userName("testUser") + .build(); + } + + @Test + void 충전_메서드_실행_시_지갑의_balance가_증가하면_성공한다() { + //given + BigDecimal amount = BigDecimal.valueOf(1000); + //when + walletService.chargeMoney(wallet, amount); + //then + assertEquals(amount, wallet.getBalance()); + verify(walletRepository, times(1)).save(wallet); + } + + @Test + @DisplayName("getWalletByUserId 테스트 ") + void UserId로_Wallet이_조회되면_성공한다() { + //given + Long walletId = 1L; + when(walletRepository.getWalletByUserId(walletId)).thenReturn(wallet); + //when + Wallet wallet = walletService.getWalletByUserId(1L); + //then + assertNotNull(wallet); + assertEquals(walletId, wallet.getId()); + } +} From 812cb55c58fee6dc1a0f2846cb2d921827600422 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:35:02 +0900 Subject: [PATCH 20/27] =?UTF-8?q?test=20:=20PaymentController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추가한 내용 : 1. 컨트롤러 테스트 추가 2. ExceptionHandler에 Invalid Session, PaymentDataMismatch 예외 추가 400번 반환 3. Service 테스트 삭제 -> 안 쓰이던 테스트 --- .../handler/GlobalExceptionHandler.java | 14 ++ .../auction/api/PaymentControllerTest.java | 169 +++++++++++++++++- .../service/PaymentServiceImplTest.java | 28 --- 3 files changed, 176 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/common/handler/GlobalExceptionHandler.java b/src/main/java/com/tasksprints/auction/common/handler/GlobalExceptionHandler.java index 4b8a90aa..670739c3 100644 --- a/src/main/java/com/tasksprints/auction/common/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/tasksprints/auction/common/handler/GlobalExceptionHandler.java @@ -8,6 +8,8 @@ import com.tasksprints.auction.domain.auction.exception.InvalidAuctionTimeException; import com.tasksprints.auction.domain.bid.exception.BidNotFoundException; import com.tasksprints.auction.domain.bid.exception.InvalidBidAmountException; +import com.tasksprints.auction.domain.payment.exception.InvalidSessionException; +import com.tasksprints.auction.domain.payment.exception.PaymentDataMismatchException; import com.tasksprints.auction.domain.product.exception.ProductNotFoundException; import com.tasksprints.auction.domain.user.exception.UserNotFoundException; import org.springframework.http.HttpStatus; @@ -61,6 +63,18 @@ public ResponseEntity> handleAuctionEndedException(AuctionEnde return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResult.failure(message)); } + @ExceptionHandler(InvalidSessionException.class) + public ResponseEntity> handleInvalidSessionException(InvalidSessionException ex) { + String message = "Invalid Session Error. "; + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResult.failure(message)); + } + + @ExceptionHandler(PaymentDataMismatchException.class) + public ResponseEntity> PaymentDataMismatchException(PaymentDataMismatchException ex) { + String message = "Session Data Mismatch Error. "; + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResult.failure(message)); + } + @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity> handleIllegalArgumentException(IllegalArgumentException ex) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResult.failure(ex.getMessage())); diff --git a/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java b/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java index 795fc224..3a74bc0d 100644 --- a/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java +++ b/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java @@ -3,11 +3,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.tasksprints.auction.api.payment.PaymentController; import com.tasksprints.auction.common.constant.ApiResponseMessages; +import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; +import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; +import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; +import com.tasksprints.auction.domain.payment.exception.InvalidSessionException; +import com.tasksprints.auction.domain.payment.exception.PaymentDataMismatchException; import com.tasksprints.auction.domain.payment.service.PaymentService; - +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -18,13 +25,17 @@ import java.math.BigDecimal; - +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(PaymentController.class) @MockBean(JpaMetamodelMappingContext.class) + public class PaymentControllerTest { @Autowired private MockMvc mockMvc; @@ -32,21 +43,165 @@ public class PaymentControllerTest { @MockBean private PaymentService paymentService; - @Autowired - private ObjectMapper objectMapper; + @MockBean + MockHttpSession session; + + @BeforeEach + void setup() { + session = new MockHttpSession(); + } - @MockBean MockHttpSession session; @Test @DisplayName("결제 전 임시 값 저장") public void 결제_전_임시_값_저장() throws Exception { - PaymentRequest.Prepare prepareRequest = new PaymentRequest.Prepare("test1", BigDecimal.valueOf(1000.00)); + String jsonRequest = """ + { + "orderId": "test1", + "amount": 1000.00 + } + """; mockMvc.perform(post("/api/v1/payment/prepare") .session(session) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(prepareRequest))) + .content(jsonRequest)) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value(ApiResponseMessages.PAYMENT_PREPARED_SUCCESS)); } + + @Nested + class sessionTest { + + + @Test + void 결제_전_세션_값이_null인_경우_예외가_발생한다() throws Exception { + // Given + String jsonRequest = """ + { + "orderId": "12345", + "amount": 10000 + } + """; + + // When & Then + mockMvc.perform(post("/api/v1/payment/confirm") + .session(session) + .param("userId", "1") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isBadRequest()) + .andExpect(result -> { + Exception resolvedException = result.getResolvedException(); + assertNotNull(resolvedException); + assertInstanceOf(InvalidSessionException.class, resolvedException); + }); + } + + @Test + void 결제_전_세션_OrderId와_Request의_OrderId가_다른_경우_예외가_발생한다() throws Exception { + // Given + String jsonRequest = """ + { + "orderId": "12345", + "amount": 10000 + } + """; + MockHttpSession session = new MockHttpSession(); + session.setAttribute("orderId", "changed-OrderId"); + session.setAttribute("amount", BigDecimal.valueOf(10000)); // + + // When & Then + mockMvc.perform(post("/api/v1/payment/confirm") + .session(session) + .param("userId", "1") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isBadRequest()) + .andExpect(result -> { + Exception resolvedException = result.getResolvedException(); + assertNotNull(resolvedException); + assertInstanceOf(PaymentDataMismatchException.class, resolvedException); + assertEquals("Payment data mismatch", resolvedException.getMessage()); + + }); + } + } + + @Test + @DisplayName("결제 승인 성공 시 HTTP 200 응답을 반환한다") + void 결제_승인_성공_시_응답() throws Exception { + // Given + String jsonRequest = """ + { + "orderId": "12345", + "amount": 10000 + } + """; + + MockHttpSession session = new MockHttpSession(); + session.setAttribute("orderId", "12345"); + session.setAttribute("amount", BigDecimal.valueOf(10000)); + + PaymentResponse successPaymentResponse = new PaymentResponse("CARD", "paymentKey", BigDecimal.valueOf(10000), "Test Order", "12345", "DONE"); + Response mockResponse = Response.success(200, successPaymentResponse); + + when(paymentService.sendPaymentRequest(any())).thenReturn(mockResponse); + when(paymentService.handleTossPaymentResponse(anyLong(), any(), any())) + .thenReturn(mockResponse); + + // When / Then + mockMvc.perform(post("/api/v1/payment/confirm") + .session(session) + .param("userId", "1") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("결제가 성공적으로 처리되었습니다.")) + .andExpect(jsonPath("$.data.orderId").value("12345")) + .andExpect(jsonPath("$.data.totalAmount").value(10000)); + } + + @Test + @DisplayName("결제 승인 성공 시 HTTP 400 응답을 반환한다") + void 결제_승인_실패_시_응답() throws Exception { + // Given + String jsonRequest = """ + { + "orderId": "12345", + "amount": 10000 + } + """; + + MockHttpSession session = new MockHttpSession(); + session.setAttribute("orderId", "12345"); + session.setAttribute("amount", BigDecimal.valueOf(10000)); + + PaymentErrorResponse failurePaymentResponse = PaymentErrorResponse.builder() + .version("2022-11-16") + .traceId("{traceId}") + .error(PaymentErrorResponse.ErrorDetail.builder() + .code("{CODE}") + .message("{MESSAGE}") + .build() + ) + .build(); + Response mockResponse = Response.failure(400, failurePaymentResponse); + + when(paymentService.sendPaymentRequest(any())).thenReturn(mockResponse); + when(paymentService.handleTossPaymentResponse(anyLong(), any(), any())) + .thenReturn(mockResponse); + + // When / Then + mockMvc.perform(post("/api/v1/payment/confirm") + .session(session) + .param("userId", "1") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error.code").value("{CODE}")) + .andExpect(jsonPath("$.error.message").value("{MESSAGE}")); + } + } diff --git a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java index aae3c0d1..440adb64 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java @@ -88,12 +88,6 @@ class 임시_저장_테스트 { assertThat(session.getAttribute("amount")).isEqualTo(amount); } - @Test - void 결제_요청을_받았을_때_세션에_값이_저장되지_않으면_예외_처리() { - //given - //when - //then - } } @Nested @@ -178,28 +172,6 @@ class handleTossPayResponse { } } -// @Test -// @DisplayName("handleTossPaymentResponse Test") -// void 토스페이로_httpRequest가_전송되면_성공한다() throws IOException, InterruptedException { -// // given -// PaymentRequest.Confirm confirmRequest = new PaymentRequest.Confirm("orderId123", BigDecimal.valueOf(1000), "paymentKey123"); -// when(objectMapper.writeValueAsString(confirmRequest)).thenReturn("{\"orderId\":\"orderId123\",\"amount\":1000,\"paymentKey\":\"paymentKey123\"}"); -// when(paymentConfig.getAuthorizations()).thenReturn("test-token"); -// when(mockResponse.statusCode()).thenReturn(200); -// when(mockResponse.body()).thenReturn("{\"status\":\"SUCCESS\"}"); -// when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))) -// .thenReturn(mockResponse); -// -// // when -// HttpResponse response = paymentService.sendPaymentRequest(confirmRequest); -// -// // then -// assertNotNull(response); -// assertEquals(200, response.statusCode()); -// assertEquals("{\"status\":\"SUCCESS\"}", response.body()); -// verify(httpClient, times(1)).send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class)); -// verify(paymentConfig, times(1)).getAuthorizations(); -// } } From bf34aed702fe9e5eabd2afda84222411f9eee51a Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:49:37 +0900 Subject: [PATCH 21/27] =?UTF-8?q?refactor=20:=20PaymentResponse=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경한 내용 : 토스 api 버전에 따라 response 형식이 달라서 맞추기 위함. @JsonAlias를 통해 error object의 루트 레벨을 평탄화하여 code, message를 필드로 추가 --- .../dto/response/PaymentErrorResponse.java | 26 ++++++++++++------- .../auction/api/PaymentControllerTest.java | 11 +++----- .../service/PaymentServiceImplTest.java | 7 ++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java index 0d7b7067..123c42fb 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java @@ -1,5 +1,7 @@ package com.tasksprints.auction.domain.payment.dto.response; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -9,18 +11,24 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class PaymentErrorResponse { private String version; private String traceId; - private ErrorDetail error; + @JsonAlias({"code", "error.code"}) + private String code; + @JsonAlias({"message", "error.message"}) + private String message; - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class ErrorDetail { - private String code; - private String message; - } +// private ErrorDetail error; +// +// @Builder +// @Getter +// @NoArgsConstructor +// @AllArgsConstructor +// public static class ErrorDetail { +// private String code; +// private String message; +// } } diff --git a/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java b/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java index 3a74bc0d..5baef70b 100644 --- a/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java +++ b/src/test/java/com/tasksprints/auction/api/PaymentControllerTest.java @@ -181,11 +181,8 @@ class sessionTest { PaymentErrorResponse failurePaymentResponse = PaymentErrorResponse.builder() .version("2022-11-16") .traceId("{traceId}") - .error(PaymentErrorResponse.ErrorDetail.builder() - .code("{CODE}") - .message("{MESSAGE}") - .build() - ) + .code("{CODE}") + .message("{MESSAGE}") .build(); Response mockResponse = Response.failure(400, failurePaymentResponse); @@ -200,8 +197,8 @@ class sessionTest { .contentType(MediaType.APPLICATION_JSON) .content(jsonRequest)) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error.code").value("{CODE}")) - .andExpect(jsonPath("$.error.message").value("{MESSAGE}")); + .andExpect(jsonPath("$.code").value("{CODE}")) + .andExpect(jsonPath("$.message").value("{MESSAGE}")); } } diff --git a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java index 440adb64..75466dc0 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java @@ -156,11 +156,8 @@ class handleTossPayResponse { PaymentErrorResponse errorResponse = PaymentErrorResponse.builder() .version("2022-11-16") .traceId("{traceId}") - .error(PaymentErrorResponse.ErrorDetail.builder() - .code("{CODE}") - .message("{MESSAGE}") - .build() - ) + .code("{CODE}") + .message("{MESSAGE}") .build(); Response failureResponse = Response.failure(404, errorResponse); From 01ff861541443a5f063f26254971b9ab7bb1830c Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:49:36 +0900 Subject: [PATCH 22/27] =?UTF-8?q?test=20:=20ClientWrapper=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수정한 내용 : 1) Mocking을 실수로 잘못해서 성공할 수 밖에 없는 테스트를 작성함 -> HttpResponse를 Response로 변환해서 성공적으로 반환하는지 검증하게 수정 --- .../payment/client/ClientWrapperTest.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java b/src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java index 5f039038..8ea3231b 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/client/ClientWrapperTest.java @@ -4,21 +4,28 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; import java.net.URI; +import java.net.http.HttpClient; import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ClientWrapperTest { - @Mock - private HttpClientWrapper clientWrapper; + private HttpClient httpClient; + @Mock + private HttpResponse mockHttpResponse; + @InjectMocks + private ClientWrapper clientWrapper; @BeforeEach void setUp() { @@ -31,10 +38,16 @@ void setUp() { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://example.com")) .build(); - Response mockResponse = Response.success(200, "test Body"); - when(clientWrapper.send(request)).thenReturn(mockResponse); + + when(mockHttpResponse.statusCode()).thenReturn(200); + when(mockHttpResponse.body()).thenReturn("test Body"); + + when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))) + .thenReturn(mockHttpResponse); + //when Response result = clientWrapper.send(request); + //then assertEquals(200, result.getStatusCode()); assertEquals("test Body", result.getBody()); From b60e5cb1a5713ce0fe7cef9e40473786da7a4e00 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:11:28 +0900 Subject: [PATCH 23/27] =?UTF-8?q?delete=20:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) 주석 처리된 부분 삭제 2) 사용되지 않은 클래스 삭제 --- .../auction/domain/payment/client/ClientWrapper.java | 2 -- .../payment/dto/response/PaymentErrorResponse.java | 11 ----------- .../payment/repository/TossPayRepositoryImpl.java | 4 ---- .../domain/payment/service/PaymentService.java | 2 -- .../domain/payment/service/PaymentServiceImpl.java | 5 ----- 5 files changed, 24 deletions(-) delete mode 100644 src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java b/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java index 331728d8..159d8f6e 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java @@ -5,7 +5,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; - import java.io.IOException; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -15,7 +14,6 @@ @RequiredArgsConstructor public class ClientWrapper implements HttpClientWrapper{ private final HttpClient httpClient; - private final ObjectMapper objectMapper; @Override public Response send(HttpRequest request) throws IOException, InterruptedException { diff --git a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java index 123c42fb..c24ac52d 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/dto/response/PaymentErrorResponse.java @@ -20,15 +20,4 @@ public class PaymentErrorResponse { @JsonAlias({"message", "error.message"}) private String message; -// private ErrorDetail error; -// -// @Builder -// @Getter -// @NoArgsConstructor -// @AllArgsConstructor -// public static class ErrorDetail { -// private String code; -// private String message; -// } - } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java deleted file mode 100644 index 982c0eee..00000000 --- a/src/main/java/com/tasksprints/auction/domain/payment/repository/TossPayRepositoryImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tasksprints.auction.domain.payment.repository; - -public class TossPayRepositoryImpl { -} diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java index 4ebccef2..fc9f65ca 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentService.java @@ -4,7 +4,6 @@ import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import jakarta.servlet.http.HttpSession; -import org.springframework.http.ResponseEntity; import java.io.IOException; @@ -12,6 +11,5 @@ public interface PaymentService { public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest); public Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; - public Response cancelPaymentApproval(PaymentRequest.Cancel cancelRequest) throws IOException, InterruptedException; public Response handleTossPaymentResponse(Long userId, PaymentRequest.Confirm confirmRequest, Response response) throws IOException, InterruptedException ; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java index 2e75348d..82eabc48 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java @@ -35,11 +35,6 @@ public Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest return paymentClient.sendPaymentRequest(confirmRequest); } - @Override - public Response cancelPaymentApproval(PaymentRequest.Cancel cancelRequest) throws IOException, InterruptedException { - return paymentClient.cancelPaymentApproval(cancelRequest); - } - @Transactional(rollbackFor = Exception.class) @Override public Response handleTossPaymentResponse(Long userId, PaymentRequest.Confirm confirmRequest, Response response) throws IOException, InterruptedException { From 32e11407712fa28a1a735a109118af59664f81e2 Mon Sep 17 00:00:00 2001 From: na0th <105775683+na0th@users.noreply.github.com> Date: Sat, 14 Dec 2024 17:24:28 +0900 Subject: [PATCH 24/27] =?UTF-8?q?refactor=20:=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=AA=85,=20PaymentConfig=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 바뀐 내용 : 1)PaymentClient -> PaymentApiSerializer API 직렬화, 역직렬화만 담당하는 클래스라는 인터페이스명 2)PaymentConfig -> PaymentProperites로 변경 config보다는 Properties 성격 --- .../api/payment/PaymentController.java | 14 ------------- .../PaymentProperties.java} | 18 ++++++----------- .../domain/payment/client/ClientWrapper.java | 1 - ...tClient.java => PaymentApiSerializer.java} | 5 +---- ...ntClientImpl.java => TossPaymentImpl.java} | 13 ++++++------ .../payment/service/PaymentServiceImpl.java | 8 ++++---- ...ImplTest.java => TossPaymentImplTest.java} | 20 +++++++++---------- .../service/PaymentServiceImplTest.java | 8 +++----- 8 files changed, 31 insertions(+), 56 deletions(-) rename src/main/java/com/tasksprints/auction/common/{config/PaymentConfig.java => properties/PaymentProperties.java} (54%) rename src/main/java/com/tasksprints/auction/domain/payment/client/{PaymentClient.java => PaymentApiSerializer.java} (69%) rename src/main/java/com/tasksprints/auction/domain/payment/client/{TossPaymentClientImpl.java => TossPaymentImpl.java} (90%) rename src/test/java/com/tasksprints/auction/domain/payment/client/{TossPaymentClientImplTest.java => TossPaymentImplTest.java} (86%) diff --git a/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java b/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java index 9d2bc18e..82dbd29f 100644 --- a/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java +++ b/src/main/java/com/tasksprints/auction/api/payment/PaymentController.java @@ -1,37 +1,23 @@ package com.tasksprints.auction.api.payment; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.tasksprints.auction.common.config.PaymentConfig; import com.tasksprints.auction.common.constant.ApiResponseMessages; import com.tasksprints.auction.common.response.ApiResult; import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; -import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; import com.tasksprints.auction.domain.payment.exception.InvalidSessionException; import com.tasksprints.auction.domain.payment.exception.PaymentDataMismatchException; -import com.tasksprints.auction.domain.payment.exception.PaymentUserNotFoundException; -import com.tasksprints.auction.domain.payment.model.Payment; -import com.tasksprints.auction.domain.payment.repository.PaymentRepository; import com.tasksprints.auction.domain.payment.service.PaymentService; -import com.tasksprints.auction.domain.user.model.User; -import com.tasksprints.auction.domain.user.repository.UserRepository; -import com.tasksprints.auction.domain.user.service.UserServiceImpl; -import com.tasksprints.auction.domain.wallet.model.Wallet; -import com.tasksprints.auction.domain.wallet.service.WalletServiceImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.math.BigDecimal; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; @RestController @RequiredArgsConstructor diff --git a/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java b/src/main/java/com/tasksprints/auction/common/properties/PaymentProperties.java similarity index 54% rename from src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java rename to src/main/java/com/tasksprints/auction/common/properties/PaymentProperties.java index f76649e1..ca6a1650 100644 --- a/src/main/java/com/tasksprints/auction/common/config/PaymentConfig.java +++ b/src/main/java/com/tasksprints/auction/common/properties/PaymentProperties.java @@ -1,25 +1,19 @@ -package com.tasksprints.auction.common.config; +package com.tasksprints.auction.common.properties; import lombok.Getter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; import java.util.Base64; -@Configuration +@Component +@ConfigurationProperties(prefix = "payment.toss") @Getter -public class PaymentConfig { - @Value("${payment.toss.test_client_api_key}") +public class PaymentProperties { private String testClientApiKey; - - @Value("${payment.toss.test_secret_api_key}") private String testSecretApiKey; - - @Value("${payment.toss.success_url}") private String successUrl; - - @Value("${payment.toss.fail_url}") private String failUrl; public static final String CONFIRM_URL = "https://api.tosspayments.com/v1/payments/confirm"; diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java b/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java index 159d8f6e..fafee92d 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/ClientWrapper.java @@ -1,6 +1,5 @@ package com.tasksprints.auction.domain.payment.client; -import com.fasterxml.jackson.databind.ObjectMapper; import com.tasksprints.auction.domain.payment.api.Response; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/PaymentClient.java b/src/main/java/com/tasksprints/auction/domain/payment/client/PaymentApiSerializer.java similarity index 69% rename from src/main/java/com/tasksprints/auction/domain/payment/client/PaymentClient.java rename to src/main/java/com/tasksprints/auction/domain/payment/client/PaymentApiSerializer.java index 33873d25..79cd5aff 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/client/PaymentClient.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/PaymentApiSerializer.java @@ -1,14 +1,11 @@ package com.tasksprints.auction.domain.payment.client; -import com.fasterxml.jackson.core.JsonProcessingException; import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; -import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; import java.io.IOException; -import java.net.http.HttpResponse; -public interface PaymentClient { +public interface PaymentApiSerializer { Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException; Response cancelPaymentApproval(PaymentRequest.Cancel cancelRequest) throws IOException, InterruptedException; } diff --git a/src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentImpl.java similarity index 90% rename from src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImpl.java rename to src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentImpl.java index ca124f4f..d2d74101 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/client/TossPaymentImpl.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.tasksprints.auction.common.config.PaymentConfig; +import com.tasksprints.auction.common.properties.PaymentProperties; import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; @@ -17,9 +17,9 @@ @Component @RequiredArgsConstructor -public class TossPaymentClientImpl implements PaymentClient { +public class TossPaymentImpl implements PaymentApiSerializer { - private final PaymentConfig paymentConfig; + private final PaymentProperties paymentProperties; private final ObjectMapper objectMapper; private final HttpClientWrapper httpClientWrapper; @@ -43,8 +43,8 @@ private String buildDtoToRequestBody(PaymentRequest.Confirm confirmRequest) thro private HttpRequest createHttpRequest(String requestBody) { return HttpRequest.newBuilder() - .uri(URI.create(PaymentConfig.CONFIRM_URL)) - .header("Authorization", paymentConfig.getAuthorizations()) + .uri(URI.create(PaymentProperties.CONFIRM_URL)) + .header("Authorization", paymentProperties.getAuthorizations()) .header("Content-Type", "application/json") .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) .build(); @@ -79,7 +79,7 @@ private HttpRequest buildCancelHttpRequest(PaymentRequest.Cancel cancelRequest, String insertPaymentKeyIntoUrl = String.format("https://api.tosspayments.com/v1/payments/%s/cancel", cancelRequest.getPaymentKey()); return HttpRequest.newBuilder() .uri(URI.create(insertPaymentKeyIntoUrl)) - .header("Authorization", paymentConfig.getAuthorizations()) + .header("Authorization", paymentProperties.getAuthorizations()) .header("Content-Type", "application/json") // .header("Idempotency-key", "멱등키") .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) @@ -91,3 +91,4 @@ private Response requestToTossPay(HttpRequest request) throws IOExceptio } } + diff --git a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java index 82eabc48..6940efe2 100644 --- a/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImpl.java @@ -1,7 +1,7 @@ package com.tasksprints.auction.domain.payment.service; import com.tasksprints.auction.domain.payment.api.Response; -import com.tasksprints.auction.domain.payment.client.PaymentClient; +import com.tasksprints.auction.domain.payment.client.PaymentApiSerializer; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; import com.tasksprints.auction.domain.payment.model.Payment; @@ -23,7 +23,7 @@ public class PaymentServiceImpl implements PaymentService { private final PaymentRepository paymentRepository; private final WalletService walletService; - private final PaymentClient paymentClient; + private final PaymentApiSerializer paymentApiSerializer; @Override public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest) { @@ -32,7 +32,7 @@ public void prepare(HttpSession session, PaymentRequest.Prepare prepareRequest) } @Override public Response sendPaymentRequest(PaymentRequest.Confirm confirmRequest) throws IOException, InterruptedException{ - return paymentClient.sendPaymentRequest(confirmRequest); + return paymentApiSerializer.sendPaymentRequest(confirmRequest); } @Transactional(rollbackFor = Exception.class) @@ -61,7 +61,7 @@ private Response handlePaymentSuccess(Long userId, PaymentRequest.Confir String paymentKey = confirmRequest.getPaymentKey(); PaymentRequest.Cancel cancelRequest = new PaymentRequest.Cancel(paymentKey); - paymentClient.cancelPaymentApproval(cancelRequest); + paymentApiSerializer.cancelPaymentApproval(cancelRequest); throw e; } } diff --git a/src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentImplTest.java similarity index 86% rename from src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImplTest.java rename to src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentImplTest.java index f4762f32..90d4ddae 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentClientImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/client/TossPaymentImplTest.java @@ -1,7 +1,7 @@ package com.tasksprints.auction.domain.payment.client; import com.fasterxml.jackson.databind.ObjectMapper; -import com.tasksprints.auction.common.config.PaymentConfig; +import com.tasksprints.auction.common.properties.PaymentProperties; import com.tasksprints.auction.domain.payment.api.Response; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; @@ -23,24 +23,24 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class TossPaymentClientImplTest { +class TossPaymentImplTest { @Mock private HttpClientWrapper httpClientWrapper; - private PaymentConfig paymentConfig; + private PaymentProperties paymentProperties; private ObjectMapper objectMapper; - private PaymentClient tossPaymentClient; + private PaymentApiSerializer tossPaymentApiSerializer; @BeforeEach void setUp() { - paymentConfig = new PaymentConfig() { + paymentProperties = new PaymentProperties() { @Override public String getAuthorizations() { return "https://api.test.com/payments"; } }; objectMapper = new ObjectMapper(); - tossPaymentClient = new TossPaymentClientImpl(paymentConfig, objectMapper, httpClientWrapper); + tossPaymentApiSerializer = new TossPaymentImpl(paymentProperties, objectMapper, httpClientWrapper); } @@ -56,7 +56,7 @@ class payment_request_test { when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); //when - Response response = tossPaymentClient.sendPaymentRequest(confirmRequest); + Response response = tossPaymentApiSerializer.sendPaymentRequest(confirmRequest); //then Assertions.assertThat(response.getStatusCode()).isEqualTo(200); Assertions.assertThat(response.getBody()).isInstanceOf(PaymentResponse.class); @@ -74,7 +74,7 @@ class payment_request_test { when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); //when - Response response = tossPaymentClient.sendPaymentRequest(confirmRequest); + Response response = tossPaymentApiSerializer.sendPaymentRequest(confirmRequest); //then Assertions.assertThat(response.getStatusCode()).isEqualTo(404); Assertions.assertThat(response.getBody()).isInstanceOf(PaymentErrorResponse.class); @@ -94,7 +94,7 @@ class payment_cancel_test { when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); //when - Response response = tossPaymentClient.cancelPaymentApproval(cancelRequest); + Response response = tossPaymentApiSerializer.cancelPaymentApproval(cancelRequest); //then Assertions.assertThat(response.getStatusCode()).isEqualTo(200); Assertions.assertThat(response.getBody()).isInstanceOf(PaymentResponse.class); @@ -110,7 +110,7 @@ class payment_cancel_test { when(httpClientWrapper.send(any(HttpRequest.class))).thenReturn(mockResponse); //when - Response response = tossPaymentClient.cancelPaymentApproval(cancelRequest); + Response response = tossPaymentApiSerializer.cancelPaymentApproval(cancelRequest); //then Assertions.assertThat(response.getStatusCode()).isEqualTo(400); Assertions.assertThat(response.getBody()).isInstanceOf(PaymentErrorResponse.class); diff --git a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java index 75466dc0..d936f11e 100644 --- a/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/payment/service/PaymentServiceImplTest.java @@ -1,7 +1,7 @@ package com.tasksprints.auction.domain.payment.service; import com.tasksprints.auction.domain.payment.api.Response; -import com.tasksprints.auction.domain.payment.client.PaymentClient; +import com.tasksprints.auction.domain.payment.client.PaymentApiSerializer; import com.tasksprints.auction.domain.payment.dto.request.PaymentRequest; import com.tasksprints.auction.domain.payment.dto.response.PaymentErrorResponse; import com.tasksprints.auction.domain.payment.dto.response.PaymentResponse; @@ -23,8 +23,6 @@ import java.io.IOException; import java.math.BigDecimal; -import java.nio.file.Files; -import java.nio.file.Paths; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -40,7 +38,7 @@ public class PaymentServiceImplTest { @Mock private WalletService walletService; @Mock - private PaymentClient paymentClient; + private PaymentApiSerializer paymentApiSerializer; @Mock private PaymentRepository paymentRepository; @@ -144,7 +142,7 @@ class handleTossPayResponse { ); // then - verify(paymentClient).cancelPaymentApproval(any(PaymentRequest.Cancel.class)); + verify(paymentApiSerializer).cancelPaymentApproval(any(PaymentRequest.Cancel.class)); //예외 발생으로 chargeMoney전에 throw 됐을 것 verify(walletService, times(0)).chargeMoney(any(Wallet.class), any(BigDecimal.class)); } From 93bfc6fcf4c0518dd3647dea7ce535d006c37c01 Mon Sep 17 00:00:00 2001 From: taehyun <126179088+KNU-K@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:47:19 +0900 Subject: [PATCH 25/27] =?UTF-8?q?refactor(user)=20:=20method=20=EB=AA=85?= =?UTF-8?q?=20conflict=20=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/service/UserService.java | 2 +- .../domain/user/service/UserServiceImpl.java | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java b/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java index 0a062802..9ac79052 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java +++ b/src/main/java/com/tasksprints/auction/domain/user/service/UserService.java @@ -19,5 +19,5 @@ public interface UserService { void deleteUser(Long id); - User findUserById(Long id); + User getUserById(Long id); } diff --git a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java index 7b946a53..4f96751b 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/user/service/UserServiceImpl.java @@ -22,12 +22,11 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; - private final WalletRepository walletRepository; - @Transactional @Override public UserDetailResponse createUser(UserRequest.Register request) { - User user = User.createWithWallet(request.getName(), request.getEmail(), request.getPassword(), request.getNickname()); + User user = User.create(request.getName(), request.getEmail(), request.getPassword(), request.getNickname()); + User newUser = userRepository.save(user); return UserDetailResponse.of(newUser); } @@ -66,14 +65,8 @@ public void deleteUser(Long id) { } @Override - public User findUserById(Long id){ - return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("User not found with id " + id)); + public User getUserById(Long id) { + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found with id " + id)); } - -// private Wallet createWalletForUser(User user) { -// return Optional.ofNullable(Wallet.create(user)) -// .orElseThrow(() -> new WalletCreationException("Failed to create wallet for user: " + user.getEmail())); -// } - - } From 3b9e98a1152452d1a9058bafe9c8d96eb3395df0 Mon Sep 17 00:00:00 2001 From: taehyun <126179088+KNU-K@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:52:45 +0900 Subject: [PATCH 26/27] =?UTF-8?q?refactor(user)=20:=20create=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/tasksprints/auction/domain/user/model/User.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/tasksprints/auction/domain/user/model/User.java b/src/main/java/com/tasksprints/auction/domain/user/model/User.java index ffe09e2c..a440bb98 100644 --- a/src/main/java/com/tasksprints/auction/domain/user/model/User.java +++ b/src/main/java/com/tasksprints/auction/domain/user/model/User.java @@ -45,11 +45,20 @@ public class User extends BaseEntityWithUpdate { @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "wallet_id") private Wallet wallet; + // 추후 추가 // @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) // @Builder.Default // private List bids = new ArrayList<>(); + public static User create(String name, String email, String password, String nickName) { + return User.builder() + .name(name) + .email(email) + .password(password) + .nickName(nickName) + .build(); + } /** * @descripton static factory pattern을 적용하여, 구현 */ From b77535eba717660957807f802312147a1b3b6146 Mon Sep 17 00:00:00 2001 From: taehyun <126179088+KNU-K@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:57:33 +0900 Subject: [PATCH 27/27] =?UTF-8?q?config(application)=20:=20profiles=20?= =?UTF-8?q?=EA=B5=AC=EB=AC=B8=20=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b2ceae33..ff1c0f23 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,9 +36,6 @@ spring: profiles: include: -jwt - - profiles: - include: -payment springdoc: