Skip to content

Commit

Permalink
FEAT: 토스 페이먼츠 연동 및 Git Actions
Browse files Browse the repository at this point in the history
  • Loading branch information
JuseungL committed Jul 30, 2024
1 parent e0f547c commit a38e2d0
Show file tree
Hide file tree
Showing 19 changed files with 398 additions and 89 deletions.
82 changes: 82 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ public ResponseEntity<ApiResponse<MemberSubscribeResponseDto>> getIsSubscribe(Pr
return ApiResponse.success(GET_IS_SUBSCRIBE_SUCCESS, memberQueryService.getIsSubscribe(memberId));
}

/**
* 맴버 정보 조회
*/
@GetMapping
public ResponseEntity<ApiResponse<MemberSubscribeResponseDto>> getMember(Principal principal) {
Long memberId = MemberUtil.getMemberId(principal);
return ApiResponse.success(GET_IS_SUBSCRIBE_SUCCESS, memberQueryService.getIsSubscribe(memberId));
}


/**
* UI Type 변경
*/
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/lion6/DrinkGuide/api/member/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
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;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
Expand Down Expand Up @@ -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<PaymentsHistory> tradeHistories = new ArrayList<>();

@Builder // OAuth2.0 로그인 시 자동 입력되는 정보
private Member(String provider, String providerId, String name, String email, RoleType roleType){
this.provider = provider;
Expand All @@ -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;}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ApiResponse<Object>> 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<ApiResponse<Object>> 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";
// }
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lion6.DrinkGuide.api.payments.dto.request;

public record PaymentsApproveRequestDto (
String orderId,
String paymentKey,
int amount
){}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lion6.DrinkGuide.api.payments.dto.request;

public record PaymentsInitializeRequestDto(
String orderId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package lion6.DrinkGuide.api.payments.dto.response;
public record CheckoutDto(
String url
) {}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lion6.DrinkGuide.api.payments.dto.response;

public record EasyPayDto(
String provider,
int amount,
int discountAmount
) {}
Loading

0 comments on commit a38e2d0

Please sign in to comment.