From a38e2d03f841bca04356b3845db32e072ccefb2a Mon Sep 17 00:00:00 2001 From: JuseungL <121665437+JuseungL@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:23:27 +0900 Subject: [PATCH] =?UTF-8?q?FEAT:=20=ED=86=A0=EC=8A=A4=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EB=A8=BC=EC=B8=A0=20=EC=97=B0=EB=8F=99=20=EB=B0=8F=20Git=20Act?= =?UTF-8?q?ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 82 +++++++++++++ .../member/controller/MemberController.java | 10 ++ .../DrinkGuide/api/member/domain/Member.java | 13 +- .../controller/PaymentsController.java | 114 +++++++++--------- .../api/payments/domain/PaymentStatus.java | 11 ++ .../api/payments/domain/PaymentsHistory.java | 25 +++- .../payments/dto/request/PaymentRequest.java | 13 -- .../request/PaymentsApproveRequestDto.java | 7 ++ .../request/PaymentsInitializeRequestDto.java | 6 + .../payments/dto/response/CheckoutDto.java | 5 + .../api/payments/dto/response/EasyPayDto.java | 7 ++ .../PaymentsApproveApiResponseDto.java | 48 ++++++++ .../response/PaymentsApproveResponseDto.java | 44 +++++++ .../api/payments/dto/response/ReceiptDto.java | 5 + .../repository/PaymentsRepostory.java | 14 +++ .../service/PaymentsCommandService.java | 63 ++++++++++ .../DrinkGuide/common/jwt/JwtFilter.java | 3 +- .../common/response/ErrorStatus.java | 7 +- .../common/response/SuccessStatus.java | 10 +- 19 files changed, 398 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentStatus.java delete mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentRequest.java create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsApproveRequestDto.java create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsInitializeRequestDto.java create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/response/CheckoutDto.java create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/response/EasyPayDto.java create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveApiResponseDto.java create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveResponseDto.java create mode 100644 src/main/java/lion6/DrinkGuide/api/payments/dto/response/ReceiptDto.java diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..de8e85c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,82 @@ +name: CI/CD with Git Actions & Docker Hub + +# 워크플로우 트리거: 브랜치에 대한 push 이벤트 발생 시 실행 +on: + push: + branches: + - main +# pull_request: +# branches: +# - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + # 소스 코드를 체크아웃 + - name: Checkout source code + uses: actions/checkout@v3 + + # application.yml 파일 생성 + - name: Set application.yml + run: | + mkdir -p ./src/main/resources + echo "${{ secrets.APPLICATION_YML }}" > ./src/main/resources/application.yml + + # Open JDK 17 설정 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + # Gradle 실행 권한 부여 + - name: Grant execute permission to gradlew + run: chmod +x ./gradlew + + # Spring Boot 애플리케이션을 Gradle로 빌드 (테스트 제외) + - name: Build with Gradle + run: ./gradlew clean build -x test + + # Docker Hub에 로그인 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Spring Boot 애플리케이션의 Docker 이미지를 빌드하고 Docker Hub에 푸시 + - name: Build and push Spring Boot app Docker image + run: | + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:backend --platform linux/amd64 . + docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:backend + + # EC2 인스턴스에서 Docker 이미지를 pull하고 애플리케이션을 배포 + - name: Deploy at EC2 instance + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SSH_HOST }} + username: ubuntu + key: ${{ secrets.SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" -p "${{ secrets.DOCKERHUB_TOKEN }}" + + CONTAINER_NAME="backend" + + sudo docker stop $(sudo docker ps -q) || true + + sudo docker rm $(sudo docker ps -a -q) || true + + sudo docker rmi $(sudo docker images -q) || true + + sudo docker pull "${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:backend" + + sudo docker run -d \ + --name "$CONTAINER_NAME" \ + -p 80:8080 \ + --log-driver=awslogs \ + --log-opt awslogs-group=/fledge/backend \ + --log-opt awslogs-region=ap-northeast-2 \ + --log-opt awslogs-stream="$CONTAINER_NAME" \ + "${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:backend" diff --git a/src/main/java/lion6/DrinkGuide/api/member/controller/MemberController.java b/src/main/java/lion6/DrinkGuide/api/member/controller/MemberController.java index 7aada9a..fd3163c 100644 --- a/src/main/java/lion6/DrinkGuide/api/member/controller/MemberController.java +++ b/src/main/java/lion6/DrinkGuide/api/member/controller/MemberController.java @@ -41,6 +41,16 @@ public ResponseEntity> getIsSubscribe(Pr return ApiResponse.success(GET_IS_SUBSCRIBE_SUCCESS, memberQueryService.getIsSubscribe(memberId)); } + /** + * 맴버 정보 조회 + */ + @GetMapping + public ResponseEntity> getMember(Principal principal) { + Long memberId = MemberUtil.getMemberId(principal); + return ApiResponse.success(GET_IS_SUBSCRIBE_SUCCESS, memberQueryService.getIsSubscribe(memberId)); + } + + /** * UI Type 변경 */ diff --git a/src/main/java/lion6/DrinkGuide/api/member/domain/Member.java b/src/main/java/lion6/DrinkGuide/api/member/domain/Member.java index 735578d..559a645 100644 --- a/src/main/java/lion6/DrinkGuide/api/member/domain/Member.java +++ b/src/main/java/lion6/DrinkGuide/api/member/domain/Member.java @@ -1,6 +1,7 @@ package lion6.DrinkGuide.api.member.domain; import jakarta.persistence.*; +import lion6.DrinkGuide.api.payments.domain.PaymentsHistory; import lion6.DrinkGuide.common.config.auditing.entity.BaseTimeEntity; import lombok.AccessLevel; import lombok.Builder; @@ -8,6 +9,8 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @Getter @@ -44,6 +47,9 @@ public class Member extends BaseTimeEntity { @Column(name = "ui_type", nullable = false) private Boolean UiType = false; // false: 어둡게, true: 밝게 + @OneToMany(mappedBy = "member") + private List tradeHistories = new ArrayList<>(); + @Builder // OAuth2.0 로그인 시 자동 입력되는 정보 private Member(String provider, String providerId, String name, String email, RoleType roleType){ this.provider = provider; @@ -54,7 +60,12 @@ private Member(String provider, String providerId, String name, String email, Ro } public void subscribe() { this.isSubscribe = true; - this.expirationDate = LocalDateTime.now().plusMonths(1); + this.expirationDate = LocalDateTime.now() + .plusMonths(1) + .withHour(23) + .withMinute(59) + .withSecond(59) + .withNano(0); } public void updateUiType() { this.UiType = !this.UiType; } public void updateRefreshToken(String refreshToken) {this.refreshToken = refreshToken;} diff --git a/src/main/java/lion6/DrinkGuide/api/payments/controller/PaymentsController.java b/src/main/java/lion6/DrinkGuide/api/payments/controller/PaymentsController.java index 7d25088..5fdb2fa 100644 --- a/src/main/java/lion6/DrinkGuide/api/payments/controller/PaymentsController.java +++ b/src/main/java/lion6/DrinkGuide/api/payments/controller/PaymentsController.java @@ -1,79 +1,73 @@ package lion6.DrinkGuide.api.payments.controller; -import lion6.DrinkGuide.api.payments.dto.request.PaymentRequest; +import lion6.DrinkGuide.api.payments.dto.request.PaymentsApproveRequestDto; +import lion6.DrinkGuide.api.payments.dto.request.PaymentsInitializeRequestDto; +import lion6.DrinkGuide.api.payments.service.PaymentsCommandService; +import lion6.DrinkGuide.common.response.ApiResponse; import lion6.DrinkGuide.common.util.MemberUtil; -import okhttp3.*; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RequestBody; import java.io.IOException; import java.security.Principal; -@Controller -@RequestMapping("/api/v1/payment") -public class PaymentsController { - - private static final String SECRET_KEY = "test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6"; +import static lion6.DrinkGuide.common.response.SuccessStatus.INITIALIZE_PAYMENTS_SUCCESS; +import static lion6.DrinkGuide.common.response.SuccessStatus.PAYMENTS_APPROVAL_SUCCESS; +@RestController +@RequestMapping("/api/v1/payments") +@RequiredArgsConstructor +public class PaymentsController { + private final PaymentsCommandService paymentsCommandService; /** - * 결제 위젯 렌더링 Thymleaf + * orderId와 customerName기반으로 결제 대기 정보 저장 */ - @GetMapping("/") - public String checkout(Principal principal, Model model) { + @PostMapping + public ResponseEntity> initializePayments(Principal principal, @RequestBody PaymentsInitializeRequestDto paymentInitializeRequestDto) { Long memberId = MemberUtil.getMemberId(principal);// 또는 필요한 다른 속성을 사용 - model.addAttribute("memberId", memberId); - return "checkout"; + paymentsCommandService.initializePayments(memberId, paymentInitializeRequestDto); + return ApiResponse.success(INITIALIZE_PAYMENTS_SUCCESS); } /** * 결제 승인 API */ - @PostMapping("/confirm") - public String confirm(@org.springframework.web.bind.annotation.RequestBody PaymentRequest paymentRequest, Model model) { - OkHttpClient client = new OkHttpClient(); - - okhttp3.RequestBody body = okhttp3.RequestBody.create( - MediaType.parse("application/json"), - "{\"orderId\":\"" + paymentRequest.getOrderId() + "\",\"amount\":" + paymentRequest.getAmount() + ",\"paymentKey\":\"" + paymentRequest.getPaymentKey() + "\"}" - ); - String encryptedSecretKey = "Basic " + java.util.Base64.getEncoder().encodeToString((SECRET_KEY + ":").getBytes()); - - Request request = new Request.Builder() - .url("https://api.tosspayments.com/v1/payments/confirm") - .post(body) - .addHeader("Authorization", encryptedSecretKey) - .addHeader("Content-Type", "application/json") - .build(); - - try (Response response = client.newCall(request).execute()) { - if (response.isSuccessful()) { - System.out.println("response = " + response); - model.addAttribute("message", "결제가 완료되었습니다."); - return "success"; - } else { - model.addAttribute("message", "결제에 실패했습니다."); - return "fail"; - } - } catch (IOException e) { - e.printStackTrace(); - model.addAttribute("message", "오류가 발생했습니다."); - return "fail"; - } + @PatchMapping("/approve") + public ResponseEntity> approvePayments(@RequestBody PaymentsApproveRequestDto paymentsApproveRequestDto) throws IOException, InterruptedException { + System.out.println("-------------------------컨트롤러 실행됨-------------------------"); + paymentsCommandService.approvePayments(paymentsApproveRequestDto); + return ApiResponse.success(PAYMENTS_APPROVAL_SUCCESS); } +} - /** - * 성공 시 보낼 Thymleaf 페이지 - */ - @GetMapping("/success") - public String success() { - return "success"; - } - /** - * 실패 시 보낼 Thymleaf 페이지 - */ - @GetMapping("/fail") - public String fail() { - return "fail"; - } -} +//OkHttpClient client = new OkHttpClient(); +// +//okhttp3.RequestBody body = okhttp3.RequestBody.create( +// MediaType.parse("application/json"), +// "{\"orderId\":\"" + paymentRequest.getOrderId() + "\",\"amount\":" + paymentRequest.getAmount() + ",\"paymentKey\":\"" + paymentRequest.getPaymentKey() + "\"}" +//); +//String encryptedSecretKey = "Basic " + java.util.Base64.getEncoder().encodeToString((SECRET_KEY + ":").getBytes()); +// +//Request request = new Request.Builder() +// .url("https://api.tosspayments.com/v1/payments/confirm") +// .post(body) +// .addHeader("Authorization", encryptedSecretKey) +// .addHeader("Content-Type", "application/json") +// .build(); +// +// try (Response response = client.newCall(request).execute()) { +// if (response.isSuccessful()) { +// System.out.println("response = " + response); +// model.addAttribute("message", "결제가 완료되었습니다."); +// return "success"; +// } else { +// model.addAttribute("message", "결제에 실패했습니다."); +// return "fail"; +// } +// } catch (IOException e) { +// e.printStackTrace(); +// model.addAttribute("message", "오류가 발생했습니다."); +// return "fail"; +// } \ No newline at end of file diff --git a/src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentStatus.java b/src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentStatus.java new file mode 100644 index 0000000..83a369b --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentStatus.java @@ -0,0 +1,11 @@ +package lion6.DrinkGuide.api.payments.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum PaymentStatus { + PENDING("PENDING"), FAILED("FAILED"), APPROVED("APPROVED"); + private final String key; +} \ No newline at end of file diff --git a/src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentsHistory.java b/src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentsHistory.java index f209bf8..21668fc 100644 --- a/src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentsHistory.java +++ b/src/main/java/lion6/DrinkGuide/api/payments/domain/PaymentsHistory.java @@ -1,12 +1,16 @@ package lion6.DrinkGuide.api.payments.domain; import jakarta.persistence.*; +import lion6.DrinkGuide.api.member.domain.Member; import lion6.DrinkGuide.common.config.auditing.entity.BaseTimeEntity; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -15,23 +19,32 @@ public class PaymentsHistory extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) - private String paymentType; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; @Column(nullable = false) private String orderId; - @Column(nullable = false) + @Column(nullable = true) private String paymentKey; - @Column(nullable = false) + @Column(nullable = true) private int amount; + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private PaymentStatus paymentStatus = PaymentStatus.PENDING; + @Builder - public PaymentsHistory(String paymentType, String orderId, String paymentKey, int amount) { - this.paymentType = paymentType; + public PaymentsHistory(Member member, String orderId) { + this.member = member; this.orderId = orderId; + } + + public void approvePaymentsHistory(String paymentKey, int amount) { this.paymentKey = paymentKey; this.amount = amount; + this.paymentStatus = PaymentStatus.APPROVED; } } diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentRequest.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentRequest.java deleted file mode 100644 index 661174b..0000000 --- a/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package lion6.DrinkGuide.api.payments.dto.request; - - -import lombok.Getter; -import lombok.Setter; - -@Getter @Setter -public class PaymentRequest { - private String paymentKey; - private String orderId; - private int amount; - private Long memberId; -} \ No newline at end of file diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsApproveRequestDto.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsApproveRequestDto.java new file mode 100644 index 0000000..7c355bb --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsApproveRequestDto.java @@ -0,0 +1,7 @@ +package lion6.DrinkGuide.api.payments.dto.request; + +public record PaymentsApproveRequestDto ( + String orderId, + String paymentKey, + int amount +){} \ No newline at end of file diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsInitializeRequestDto.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsInitializeRequestDto.java new file mode 100644 index 0000000..7dd6d1e --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/dto/request/PaymentsInitializeRequestDto.java @@ -0,0 +1,6 @@ +package lion6.DrinkGuide.api.payments.dto.request; + +public record PaymentsInitializeRequestDto( + String orderId +) { +} diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/response/CheckoutDto.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/CheckoutDto.java new file mode 100644 index 0000000..3254778 --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/CheckoutDto.java @@ -0,0 +1,5 @@ +package lion6.DrinkGuide.api.payments.dto.response; +public record CheckoutDto( + String url +) {} + diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/response/EasyPayDto.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/EasyPayDto.java new file mode 100644 index 0000000..3606f0b --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/EasyPayDto.java @@ -0,0 +1,7 @@ +package lion6.DrinkGuide.api.payments.dto.response; + +public record EasyPayDto( + String provider, + int amount, + int discountAmount +) {} diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveApiResponseDto.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveApiResponseDto.java new file mode 100644 index 0000000..e1c49e7 --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveApiResponseDto.java @@ -0,0 +1,48 @@ +package lion6.DrinkGuide.api.payments.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.time.LocalDateTime; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record PaymentsApproveApiResponseDto( + boolean success, + String message, + String mId, + String lastTransactionKey, + String paymentKey, + String orderId, + String orderName, + int taxExemptionAmount, + String status, + String requestedAt, + String approvedAt, + boolean useEscrow, + boolean cultureExpense, + Object card, + Object virtualAccount, // Object로 처리 + Object transfer, // Object로 처리 + String mobilePhone, + String giftCertificate, + String cashReceipt, + Object cashReceipts, // Object로 처리 + Object discount, // Object로 처리 + Object cancels, // Object로 처리 + String secret, + String type, + EasyPayDto easyPay, + String country, + Object failure, + boolean isPartialCancelable, + ReceiptDto receipt, + CheckoutDto checkout, + String currency, + int totalAmount, + int balanceAmount, + int suppliedAmount, + int vat, + int taxFreeAmount, + String method, + String version +) { +} diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveResponseDto.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveResponseDto.java new file mode 100644 index 0000000..1999a28 --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/PaymentsApproveResponseDto.java @@ -0,0 +1,44 @@ +package lion6.DrinkGuide.api.payments.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.time.LocalDateTime; +@JsonIgnoreProperties(ignoreUnknown = true) +public record PaymentsApproveResponseDto( + String mId, + String lastTransactionKey, + String paymentKey, + String orderId, + String orderName, + int taxExemptionAmount, + String status, + LocalDateTime requestedAt, + LocalDateTime approvedAt, + boolean useEscrow, + boolean cultureExpense, + Object card, + Object virtualAccount, // Object로 처리 + Object transfer, // Object로 처리 + String mobilePhone, + String giftCertificate, + String cashReceipt, + Object cashReceipts, // Object로 처리 + Object discount, // Object로 처리 + Object cancels, // Object로 처리 + String secret, + String type, + EasyPayDto easyPay, + String country, + Object failure, + boolean isPartialCancelable, + ReceiptDto receipt, + CheckoutDto checkout, + String currency, + int totalAmount, + int balanceAmount, + int suppliedAmount, + int vat, + int taxFreeAmount, + String method, + String version +) {} diff --git a/src/main/java/lion6/DrinkGuide/api/payments/dto/response/ReceiptDto.java b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/ReceiptDto.java new file mode 100644 index 0000000..8f7ce29 --- /dev/null +++ b/src/main/java/lion6/DrinkGuide/api/payments/dto/response/ReceiptDto.java @@ -0,0 +1,5 @@ +package lion6.DrinkGuide.api.payments.dto.response; + +public record ReceiptDto( + String url +) {} \ No newline at end of file diff --git a/src/main/java/lion6/DrinkGuide/api/payments/repository/PaymentsRepostory.java b/src/main/java/lion6/DrinkGuide/api/payments/repository/PaymentsRepostory.java index 45a652e..23c62d6 100644 --- a/src/main/java/lion6/DrinkGuide/api/payments/repository/PaymentsRepostory.java +++ b/src/main/java/lion6/DrinkGuide/api/payments/repository/PaymentsRepostory.java @@ -1,8 +1,22 @@ package lion6.DrinkGuide.api.payments.repository; +import lion6.DrinkGuide.api.member.domain.Member; import lion6.DrinkGuide.api.payments.domain.PaymentsHistory; +import lion6.DrinkGuide.common.exception.NotFoundException; +import lion6.DrinkGuide.common.response.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +import static lion6.DrinkGuide.common.response.ErrorStatus.NOT_FOUND_PAYMENTS_HISTORY; + public interface PaymentsRepostory extends JpaRepository { + Optional findPaymentsHistoryByOrderId(String orderId); + + default PaymentsHistory findPaymentsHistoryByOrderIdOrThrow(String orderId) { + return findPaymentsHistoryByOrderId(orderId) + .orElseThrow(() -> new NotFoundException(NOT_FOUND_PAYMENTS_HISTORY.getMessage())); + } + } diff --git a/src/main/java/lion6/DrinkGuide/api/payments/service/PaymentsCommandService.java b/src/main/java/lion6/DrinkGuide/api/payments/service/PaymentsCommandService.java index 499401d..2f51739 100644 --- a/src/main/java/lion6/DrinkGuide/api/payments/service/PaymentsCommandService.java +++ b/src/main/java/lion6/DrinkGuide/api/payments/service/PaymentsCommandService.java @@ -1,11 +1,74 @@ package lion6.DrinkGuide.api.payments.service; +import com.fasterxml.jackson.databind.ObjectMapper; +import lion6.DrinkGuide.api.member.domain.Member; +import lion6.DrinkGuide.api.member.repository.MemberRepository; +import lion6.DrinkGuide.api.payments.domain.PaymentsHistory; +import lion6.DrinkGuide.api.payments.dto.request.PaymentsApproveRequestDto; +import lion6.DrinkGuide.api.payments.dto.request.PaymentsInitializeRequestDto; +import lion6.DrinkGuide.api.payments.dto.response.PaymentsApproveApiResponseDto; +import lion6.DrinkGuide.api.payments.dto.response.PaymentsApproveResponseDto; +import lion6.DrinkGuide.api.payments.repository.PaymentsRepostory; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; 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; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + @Service @Transactional @RequiredArgsConstructor public class PaymentsCommandService { + private final MemberRepository memberRepository; + private final PaymentsRepostory paymentsRepostory; + @Value("${payments.toss.test-secret-key}") + private String secretKey; + + public void initializePayments(Long memberId, PaymentsInitializeRequestDto paymentsInitializeRequestDto) { + System.out.println("memberId = " + memberId); + Member member = memberRepository.findMemberByIdOrThrow(memberId); + + PaymentsHistory paymentsHistory = PaymentsHistory.builder() + .member(member) + .orderId(paymentsInitializeRequestDto.orderId()) + .build(); + paymentsRepostory.save(paymentsHistory); + } + + public void approvePayments(PaymentsApproveRequestDto paymentsApproveRequestDto) throws IOException, InterruptedException { + System.out.println("-------------------------서비스 실행됨-------------------------"); + + String beforeEncoding = secretKey + ":"; + String afterEnconding = Base64.getEncoder().encodeToString(beforeEncoding.getBytes(StandardCharsets.UTF_8)); + System.out.println("afterEnconding = " + afterEnconding); + // JSON 요청 본문을 생성 + String requestBody = String.format( + "{\"paymentKey\":\"%s\",\"orderId\":\"%s\",\"amount\":%d}", + paymentsApproveRequestDto.paymentKey(), + paymentsApproveRequestDto.orderId(), + paymentsApproveRequestDto.amount() + ); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.tosspayments.com/v1/payments/confirm")) + .header("Authorization", "Basic " + afterEnconding) + .header("Content-Type", "application/json") + .method("POST", HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + System.out.println("-------------------------API 요청 실행됨-------------------------"); + HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); + + ObjectMapper objectMapper = new ObjectMapper(); + PaymentsApproveApiResponseDto paymentsApproveApiResponseDto = objectMapper.readValue(response.body(), PaymentsApproveApiResponseDto.class); + PaymentsHistory paymentsHistory = paymentsRepostory.findPaymentsHistoryByOrderIdOrThrow(paymentsApproveApiResponseDto.orderId()); + paymentsHistory.approvePaymentsHistory(paymentsApproveApiResponseDto.paymentKey(), paymentsApproveRequestDto.amount()); + paymentsHistory.getMember().subscribe(); + } } diff --git a/src/main/java/lion6/DrinkGuide/common/jwt/JwtFilter.java b/src/main/java/lion6/DrinkGuide/common/jwt/JwtFilter.java index f63390d..00c0274 100644 --- a/src/main/java/lion6/DrinkGuide/common/jwt/JwtFilter.java +++ b/src/main/java/lion6/DrinkGuide/common/jwt/JwtFilter.java @@ -42,7 +42,8 @@ public class JwtFilter extends OncePerRequestFilter { "/v3/api-docs/swagger-config", "/v3/api-docs/**", -// "/api/v1/payment/**" +// "/api/v1/payments", + "/api/v1/payments/**" }; @Override diff --git a/src/main/java/lion6/DrinkGuide/common/response/ErrorStatus.java b/src/main/java/lion6/DrinkGuide/common/response/ErrorStatus.java index 6003282..3f630ee 100644 --- a/src/main/java/lion6/DrinkGuide/common/response/ErrorStatus.java +++ b/src/main/java/lion6/DrinkGuide/common/response/ErrorStatus.java @@ -51,12 +51,7 @@ public enum ErrorStatus { * 404 NOT_FOUND */ NOT_FOUND_MEMBER("해당하는 유저가 없습니다."), - NOT_FOUND_REPORT("해당하는 보고서가 없습니다."), - NOT_FOUND_THREAD("해당하는 쓰레드가 없습니다."), - NOT_FOUND_CONTENT("해당하는 게시물이 없습니다."), - NOT_FOUND_COMMENT("해당하는 답글이 없습니다."), - NOT_FOUND_TRADE_HISTORY("해당하는 거래 내역이 없습니다."), - UNEXIST_COMMENT_LIKE("좋아요를 누르지 않은 답글입니다."), + NOT_FOUND_PAYMENTS_HISTORY("진행중인 결제 기록이 없습니다."), /** diff --git a/src/main/java/lion6/DrinkGuide/common/response/SuccessStatus.java b/src/main/java/lion6/DrinkGuide/common/response/SuccessStatus.java index 8aeb6ff..8155515 100644 --- a/src/main/java/lion6/DrinkGuide/common/response/SuccessStatus.java +++ b/src/main/java/lion6/DrinkGuide/common/response/SuccessStatus.java @@ -9,7 +9,7 @@ @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public enum SuccessStatus { /** - * member + * Member */ SIGNUP_SUCCESS(HttpStatus.CREATED, "회원가입 성공"), SIGNIN_SUCCESS(HttpStatus.OK, "로그인 성공"), @@ -19,7 +19,13 @@ public enum SuccessStatus { UPDATE_UI_TYPE_SUCCESS(HttpStatus.OK, "UI Type 업데이트 성공"), /** - * contact + * (Toss) Payments History + */ + INITIALIZE_PAYMENTS_SUCCESS(HttpStatus.CREATED, "토스 페이먼츠 초기화 성공"), + PAYMENTS_APPROVAL_SUCCESS(HttpStatus.OK, "토스 페이먼츠 결제 승인 성공"), + + /** + * Contact */ CREATE_CONTACT_SUCCESS(HttpStatus.CREATED, "문의 등록 성공") ;