application.yml 에서 DB를 연동하고
portone:
imp_key:
imp_secret:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/database
username:
password:
코드를 실행했는데 테이블은 잘 연동되어 생성되는데 포트원에서 받은 imp_uid 값을 통해 조회한 값들이 DB에 저장되지 않는 문제가 발생
DB 테이블이 생성되는거 보면 DB 연동 문제는 아님..
entity - PaymentGateway.java
package com.dangsim.pg.entity;
import static lombok.AccessLevel.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.hibernate.annotations.Check;
import com.dangsim.common.entity.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "payment_gateway")
@Check(constraints = "amount >= 1 AND amount <= 1000000")
@Getter
@NoArgsConstructor(access = PROTECTED)
public class PaymentGateway extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "payment_gateway_id")
private Long id;
@Size(max = 255)
// @Nullable
@Column(name = "imp_uid", length = 255)
private String impUid;
@Size(max = 40)
// @Nullable
@Column(name = "merchant_uid", length = 40, unique = true)
private String merchantUid;
@Size(max = 30)
// @NotNull
// nullable
@Column(name = "pay_method", length = 30)
private String payMethod;
@Size(max = 30)
// @NotNull
@Column(name = "pg_provider", length = 30)
private String pgProvider;
@Size(max = 50)
// nullable
@Column(name = "pg_tid", length = 30)
private String pgTid;
@Size(max = 30)
@Column(name = "pg_id", length = 30)
private String pgId;
// @NotNull
// nullable
@Column(name = "amount")
private BigDecimal amount; // BigDecimal
@Size(max = 10)
// @NotNull
// nullable
@Column(name = "currency", length = 10)
private String currency;
@Size(max = 30)
// @NotNull
@Column(name = "apply_num", length = 30)
private String applyNum;
@Size(max = 30)
// @NotNull
@Column(name = "buyer_name", length = 30)
private String buyerName;
@Size(max = 10)
// @NotNull
@Column(name = "card_code")
private String cardCode;
@Size(max = 50)
@Column(name = "card_name", length = 50)
private String cardName;
@Size(max = 20)
@Column(name = "card_number_masked", length = 20)
private String cardNumberMasked;
@Column(name = "card_quota")
private int cardQuota;
@Column(name = "card_number")
private String cardNumber;
// @NotNull
@Column(name = "status")
private PaymentGatewayStatus status;
@Column(name = "card_type")
private String cardType;
@Column(name = "start_at")
private LocalDateTime startedAt; // 결제 시작시점
@Column(name = "paid_at")
private LocalDateTime paidAt; // 결제 완료 시점
@Column(name = "canceled_at")
private LocalDateTime canceledAt;
@Column(name = "failed_at")
private LocalDateTime failedAt;
@Builder(access = PRIVATE)
private PaymentGateway(String impUid, String merchantUid, String payMethod, String pgProvider,
String pgTid, String pgId, BigDecimal amount, String currency,
String applyNum, String buyerName, String cardCode, String cardName,
Integer cardQuota, String cardNumber, PaymentGatewayStatus status, String cardType,
LocalDateTime startedAt, LocalDateTime paidAt, LocalDateTime canceledAt, LocalDateTime failedAt) {
this.impUid = impUid;
this.merchantUid = merchantUid;
this.payMethod = payMethod;
this.pgProvider = pgProvider;
this.pgTid = pgTid;
this.pgId = pgId;
this.amount = amount;
this.currency = currency;
this.applyNum = applyNum;
this.buyerName = buyerName;
this.cardCode = cardCode;
this.cardName = cardName;
this.cardQuota = cardQuota;
this.cardNumber = cardNumber;
this.status = status;
this.cardType = cardType;
this.startedAt = startedAt;
this.paidAt = paidAt;
this.canceledAt = canceledAt;
this.failedAt = failedAt;
}
public static PaymentGateway of(String impUid, String merchantUid, String payMethod, String pgProvider,
String pgTid, String pgId, BigDecimal amount, String currency,
String applyNum, String buyerName, String cardCode, String cardName,
Integer cardQuota, String cardNumber, PaymentGatewayStatus status, String cardType,
LocalDateTime startedAt, LocalDateTime paidAt,
LocalDateTime canceledAt, LocalDateTime failedAt) {
return PaymentGateway.builder()
.impUid(impUid)
.merchantUid(merchantUid)
.payMethod(payMethod)
.pgProvider(pgProvider)
.pgTid(pgTid)
.pgId(pgId)
.amount(amount)
.currency(currency)
.applyNum(applyNum)
.buyerName(buyerName)
.cardCode(cardCode)
.cardName(cardName)
.cardQuota(cardQuota)
.cardNumber(cardNumber)
.status(status)
.cardType(cardType)
.startedAt(startedAt)
.paidAt(paidAt)
.canceledAt(canceledAt)
.failedAt(failedAt)
.build();
}
}
service - PaymentGatewayService.java
package com.dangsim.pg.service;
import com.dangsim.pg.dto.InicisResponse;
import com.dangsim.pg.dto.PortOneTokenResponse;
import com.dangsim.pg.entity.PaymentGateway;
import com.dangsim.pg.entity.PaymentGatewayStatus;
import com.dangsim.pg.repository.PaymentGatewayRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import static com.dangsim.common.util.DateTimeFormatUtils.parseDateTime;
@Service
@RequiredArgsConstructor
public class PaymentGatewayService {
@Value("${portone.imp_key}")
private String apiKey;
@Value("${portone.imp_secret}")
private String apiSecret;
@PostConstruct
public void init() {
// System.out.println("apiKey = " + apiKey);
// System.out.println("apiSecret = " + apiSecret);
}
private final RestTemplate restTemplate;
private final PaymentGatewayRepository paymentGatewayRepository;
// 1. token 요청
public String getAccessToken() {
String url = "https://api.iamport.kr/users/getToken";
Map<String, String> body = new HashMap<>();
body.put("imp_key", apiKey);
body.put("imp_secret", apiSecret);
ObjectMapper objectMapper = new ObjectMapper(); // json 형태로 직렬화
String requestBody;
try {
requestBody = objectMapper.writeValueAsString(body);
} catch (Exception e) {
throw new RuntimeException(e);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
ResponseEntity<PortOneTokenResponse> response = restTemplate.postForEntity(url, entity, PortOneTokenResponse.class);
// response : http 응답 전체를 감싸는 객체
// TODO 만약 access_token을 요청했는데 반환값이 code != 0 일 때 예외 처리
return response.getBody().getResponse().getAccess_token();
}
@Transactional
public PaymentGateway verifyPaymentDetail(BigDecimal clientAmount, String impUid) {
String token = getAccessToken();
// System.out.println("token = " + token);
String url = "https://api.iamport.kr/payments/" + impUid;
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<InicisResponse> response = restTemplate.exchange(url, HttpMethod.GET, entity, InicisResponse.class); // 포트원에 요청 후 응답받음
InicisResponse.Response paymentData = response.getBody().getResponse();
// 포트원 서버에서 받은 결제 금액
BigDecimal portOneAmount = BigDecimal.valueOf(paymentData.getAmount());
// 1. 프론트에서 받은 금액과 서버에서 조회한 금액 비교
if (clientAmount.compareTo(portOneAmount) != 0) {
throw new IllegalArgumentException("결제 금액이 일치하지 않습니다.");
}
// 여기서 우선 200 return
// 2. 금액이 같으면 PaymentGateway 엔티티 생성
PaymentGateway paymentGateway = PaymentGateway.of(
paymentData.getImp_uid(),
paymentData.getMerchant_uid(),
paymentData.getPay_method(),
paymentData.getPg_provider(),
paymentData.getPg_tid(),
paymentData.getPg_id(),
BigDecimal.valueOf(paymentData.getAmount()),
paymentData.getCurrency(),
paymentData.getApply_num(),
paymentData.getBuyer_name(),
paymentData.getCard_code(),
paymentData.getCard_name(),
Integer.valueOf(paymentData.getCard_quota()),
paymentData.getCard_number(),
PaymentGatewayStatus.valueOf(paymentData.getStatus().toUpperCase()),
paymentData.getCard_type(),
parseDateTime(paymentData.getStarted_at()),
parseDateTime(paymentData.getPaid_at()),
parseDateTime(paymentData.getCanceled_at()),
parseDateTime(paymentData.getFailed_at())
);
return paymentGatewayRepository.save(paymentGateway);
// PaymentGateway savedPayment = paymentGatewayRepository.save(paymentGateway);
// paymentGatewayRepository.flush(); // 추가
// return savedPayment;
}
}
디버깅을 했는데 엔티티는 생성하는데 save가 안됨
그러다 디버깅을 계속 해보다가 입력받는 값을 봤는데
card_quota (할부) 값을 공식 api 문서에는 Integer로 받는다고 쓰여있었는데 String으로 받아지더라..
https://portone.gitbook.io/docs/api/api-1/api-1#id
그래서 다음과 같이 수정했더니
entity - PaymentGateway.java
package com.dangsim.pg.entity;
import static lombok.AccessLevel.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.hibernate.annotations.Check;
import com.dangsim.common.entity.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "payment_gateway")
@Check(constraints = "amount >= 1 AND amount <= 1000000")
@Getter
@NoArgsConstructor(access = PROTECTED)
public class PaymentGateway extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "payment_gateway_id")
private Long id;
@Size(max = 255)
// @Nullable
@Column(name = "imp_uid", length = 255)
private String impUid;
@Size(max = 40)
// @Nullable
@Column(name = "merchant_uid", length = 40, unique = true)
private String merchantUid;
@Size(max = 30)
// @NotNull
// nullable
@Column(name = "pay_method", length = 30)
private String payMethod;
@Size(max = 30)
// @NotNull
@Column(name = "pg_provider", length = 30)
private String pgProvider;
@Size(max = 50)
// nullable
@Column(name = "pg_tid", length = 30)
private String pgTid;
@Size(max = 30)
@Column(name = "pg_id", length = 30)
private String pgId;
// @NotNull
// nullable
@Column(name = "amount")
private BigDecimal amount; // BigDecimal
@Size(max = 10)
// @NotNull
// nullable
@Column(name = "currency", length = 10)
private String currency;
@Size(max = 30)
// @NotNull
@Column(name = "apply_num", length = 30)
private String applyNum;
@Size(max = 30)
// @NotNull
@Column(name = "buyer_name", length = 30)
private String buyerName;
@Size(max = 10)
// @NotNull
@Column(name = "card_code")
private String cardCode;
@Size(max = 50)
@Column(name = "card_name", length = 50)
private String cardName;
@Size(max = 20)
@Column(name = "card_number_masked", length = 20)
private String cardNumberMasked;
@Column(name = "card_quota")
// int라매!!!!
private String cardQuota;
@Column(name = "card_number")
private String cardNumber;
// @NotNull
@Column(name = "status")
private PaymentGatewayStatus status;
@Column(name = "card_type")
private String cardType;
@Column(name = "start_at")
private LocalDateTime startedAt; // 결제 시작시점
@Column(name = "paid_at")
private LocalDateTime paidAt; // 결제 완료 시점
@Column(name = "canceled_at")
private LocalDateTime canceledAt;
@Column(name = "failed_at")
private LocalDateTime failedAt;
@Builder(access = PRIVATE)
private PaymentGateway(String impUid, String merchantUid, String payMethod, String pgProvider,
String pgTid, String pgId, BigDecimal amount, String currency,
String applyNum, String buyerName, String cardCode, String cardName,
String cardQuota, String cardNumber, PaymentGatewayStatus status, String cardType,
LocalDateTime startedAt, LocalDateTime paidAt, LocalDateTime canceledAt, LocalDateTime failedAt) {
this.impUid = impUid;
this.merchantUid = merchantUid;
this.payMethod = payMethod;
this.pgProvider = pgProvider;
this.pgTid = pgTid;
this.pgId = pgId;
this.amount = amount;
this.currency = currency;
this.applyNum = applyNum;
this.buyerName = buyerName;
this.cardCode = cardCode;
this.cardName = cardName;
this.cardQuota = cardQuota;
this.cardNumber = cardNumber;
this.status = status;
this.cardType = cardType;
this.startedAt = startedAt;
this.paidAt = paidAt;
this.canceledAt = canceledAt;
this.failedAt = failedAt;
}
public static PaymentGateway of(String impUid, String merchantUid, String payMethod, String pgProvider,
String pgTid, String pgId, BigDecimal amount, String currency,
String applyNum, String buyerName, String cardCode, String cardName,
String cardQuota, String cardNumber, PaymentGatewayStatus status, String cardType,
LocalDateTime startedAt, LocalDateTime paidAt,
LocalDateTime canceledAt, LocalDateTime failedAt) {
return PaymentGateway.builder()
.impUid(impUid)
.merchantUid(merchantUid)
.payMethod(payMethod)
.pgProvider(pgProvider)
.pgTid(pgTid)
.pgId(pgId)
.amount(amount)
.currency(currency)
.applyNum(applyNum)
.buyerName(buyerName)
.cardCode(cardCode)
.cardName(cardName)
.cardQuota(cardQuota)
.cardNumber(cardNumber)
.status(status)
.cardType(cardType)
.startedAt(startedAt)
.paidAt(paidAt)
.canceledAt(canceledAt)
.failedAt(failedAt)
.build();
}
}
service - PaymentGatewayService.java
package com.dangsim.pg.service;
import com.dangsim.pg.dto.InicisResponse;
import com.dangsim.pg.dto.PortOneTokenResponse;
import com.dangsim.pg.entity.PaymentGateway;
import com.dangsim.pg.entity.PaymentGatewayStatus;
import com.dangsim.pg.repository.PaymentGatewayRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import static com.dangsim.common.util.DateTimeFormatUtils.parseDateTime;
@Service
@RequiredArgsConstructor
public class PaymentGatewayService {
@Value("${portone.imp_key}")
private String apiKey;
@Value("${portone.imp_secret}")
private String apiSecret;
@PostConstruct
public void init() {
// System.out.println("apiKey = " + apiKey);
// System.out.println("apiSecret = " + apiSecret);
}
private final RestTemplate restTemplate;
private final PaymentGatewayRepository paymentGatewayRepository;
// 1. token 요청
public String getAccessToken() {
String url = "https://api.iamport.kr/users/getToken";
Map<String, String> body = new HashMap<>();
body.put("imp_key", apiKey);
body.put("imp_secret", apiSecret);
ObjectMapper objectMapper = new ObjectMapper(); // json 형태로 직렬화
String requestBody;
try {
requestBody = objectMapper.writeValueAsString(body);
} catch (Exception e) {
throw new RuntimeException(e);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
ResponseEntity<PortOneTokenResponse> response = restTemplate.postForEntity(url, entity, PortOneTokenResponse.class);
// response : http 응답 전체를 감싸는 객체
// TODO 만약 access_token을 요청했는데 반환값이 code != 0 일 때 예외 처리
return response.getBody().getResponse().getAccess_token();
}
@Transactional
public PaymentGateway verifyPaymentDetail(BigDecimal clientAmount, String impUid) {
String token = getAccessToken();
// System.out.println("token = " + token);
String url = "https://api.iamport.kr/payments/" + impUid;
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<InicisResponse> response = restTemplate.exchange(url, HttpMethod.GET, entity, InicisResponse.class); // 포트원에 요청 후 응답받음
InicisResponse.Response paymentData = response.getBody().getResponse();
// 포트원 서버에서 받은 결제 금액
BigDecimal portOneAmount = BigDecimal.valueOf(paymentData.getAmount());
// 1. 프론트에서 받은 금액과 서버에서 조회한 금액 비교
if (clientAmount.compareTo(portOneAmount) != 0) {
throw new IllegalArgumentException("결제 금액이 일치하지 않습니다.");
}
// 여기서 우선 200 return
// 2. 금액이 같으면 PaymentGateway 엔티티 생성
PaymentGateway paymentGateway = PaymentGateway.of(
paymentData.getImp_uid(),
paymentData.getMerchant_uid(),
paymentData.getPay_method(),
paymentData.getPg_provider(),
paymentData.getPg_tid(),
paymentData.getPg_id(),
BigDecimal.valueOf(paymentData.getAmount()),
paymentData.getCurrency(),
paymentData.getApply_num(),
paymentData.getBuyer_name(),
paymentData.getCard_code(),
paymentData.getCard_name(),
// Integer.valueOf(paymentData.getCard_quota()),
paymentData.getCard_quota(),
paymentData.getCard_number(),
PaymentGatewayStatus.valueOf(paymentData.getStatus().toUpperCase()),
paymentData.getCard_type(),
parseDateTime(paymentData.getStarted_at()),
parseDateTime(paymentData.getPaid_at()),
parseDateTime(paymentData.getCanceled_at()),
parseDateTime(paymentData.getFailed_at())
);
return paymentGatewayRepository.save(paymentGateway);
// PaymentGateway savedPayment = paymentGatewayRepository.save(paymentGateway);
// paymentGatewayRepository.flush(); // 추가
// return savedPayment;
}
}
db 에 잘 저장되는거 확인
'프로젝트 > 유레카_미니프로젝트' 카테고리의 다른 글
[결제] 포트원(구 아임포트) API 연동 및 구현 - 1. 포트원 API 연동하여 결제기능 구현 (React, Spring) (0) | 2025.05.22 |
---|---|
[1. 결제] 포트원(구 아임포트) API 연동 및 구현 - 0. 결제 시스템 이해하기 (0) | 2025.05.07 |
[0. 환경설정] 리액트 환경 설정 (0) | 2025.05.03 |