Notice
Recent Posts
Recent Comments
Link
개발 무지렁이
[Spring Boot] 카카오페이(KakaoPay) 단건 결제 기능 구현, 결제 준비 API 및 결제 승인 API 본문
Backend/스프링부트
[Spring Boot] 카카오페이(KakaoPay) 단건 결제 기능 구현, 결제 준비 API 및 결제 승인 API
Gaejirang-e 2023. 10. 3. 12:39 🪛 Application.yml
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
devtools:
livereload:
enabled: true
restart:
enabled: true
datasource:
url: jdbc:h2:tcp://localhost/~/test
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
server:
port: [지정한 포트번호]
my:
admin: [kakao developers에서 발급받은 admin key]
📜 payment.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>카카오 결제</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
#btn-kakao {
background-color: #FAE300;
color: #3C1E1E;
font-weight: 800;
border: none;
border-radius: 12px;
padding: 10px 20px;
cursor: pointer;
}
</style>
</head>
<body>
<button id="btn-kakao">kakao pay <i class="fa-solid fa-comment"></i></button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<script>
$(function() {
$("#btn-kakao").click(function() {
$.ajax({
url: '/pay/ready',
type: 'get',
data: {
item_name: '초코파이',
quantity: "4",
total_amount: "8800",
tax_free_amount: "0",
},
success: function(response) {
alert(response.next_redirect_pc_url); //kakao payment api에서 알아서 응답해주는 url
location.href = response.next_redirect_pc_url;
},
error: function(xhr, err, status) {
console.log(xhr.responseText);
alert(err + "이(가) 발생했습니다: " + status);
}
});
});
});
</script>
</body>
</html>
🖤 카카오 페이(단건결제): 결제 준비 API -> (비즈니스 로직) -> 결제 승인 API
(준비 API와 승인 API 사이에 우리의 로직을 넣을 수 있다.)
(준비 API와 승인 API 사이에 우리의 로직을 넣을 수 있다.)
𐂂 결제 준비 요청
📦 요청에 필요한 정보 (필수만 *)
- cid 가맹점코드, 10자 (String)
- partner_order_id 가맹점 주문번호, 최대 100자 (String)
- partner_user_id 가맹점 회원, 최대 100자 (String)
- item_name 상품명, 최대 100자 (String)
- quantity 상품수량 (Integer)
- total_amount 상품 총액 (Integer)
- tax_free_amount 상품 비과세 금액 (Integer)
- approval_url 결제 성공시 redirect url, 최대 255자 (String)
- cancel_url 결제 취소시 redirect url, 최대 255자 (String)
- fail_url 결제 실패시 redirect url, 최대 255자(String)
📦 KAKAO PAY가 응답해주는 정보
- tid 결제 고유 번호 (String)
- next_redirect_pc_url 요청한 클라이언트가 PC 웹일 경우, QR코드로 이동하는 url (String)
- created_at 결제 준비 요청 시간 (Date)
- cid 가맹점코드, 10자 (String)
- partner_order_id 가맹점 주문번호, 최대 100자 (String)
- partner_user_id 가맹점 회원, 최대 100자 (String)
- item_name 상품명, 최대 100자 (String)
- quantity 상품수량 (Integer)
- total_amount 상품 총액 (Integer)
- tax_free_amount 상품 비과세 금액 (Integer)
- approval_url 결제 성공시 redirect url, 최대 255자 (String)
- cancel_url 결제 취소시 redirect url, 최대 255자 (String)
- fail_url 결제 실패시 redirect url, 최대 255자(String)
📦 KAKAO PAY가 응답해주는 정보
- tid 결제 고유 번호 (String)
- next_redirect_pc_url 요청한 클라이언트가 PC 웹일 경우, QR코드로 이동하는 url (String)
- created_at 결제 준비 요청 시간 (Date)
📦 KakaoPayReadyResponse.java
@Getter
@Setter
@ToString
public class KakaoPayReadyResponse {
private String tid;
private String next_redirect_pc_url;
private Date created_at;
}
📜 PayController.java
@Slf4j
@RequiredArgsConstructor
@Controller
@RequestMapping("/pay")
public class PayController {
private final PayService payService;
@GetMapping("/ready")
public @ResponseBody KakaoPayReadyResponse kakaoPayReady(@RequestParam Map<String, Object> params) {
//클라이언트에서 jquery ajax로 넘긴 정보가 잘 넘어왔는지 확인
log.info("item_name: " + params.get("item_name"));
log.info("quantity: " + params.get("quantity"));
log.info("total_amount: " + params.get("total_amount"));
log.info("tax_free_amount: " + params.get("tax_free_amount"));
KakaoPayReadyResponse readyResponse = payService.kakaoPayReady(params); //kakaoPay 요청양식에 따라 요청객체 만들어 보내는 메서드(밑에서 구현)
log.info(readyResponse.toString()); //kakaoPay가 준비요청 후 보내준 정보 확인
return readyResponse;
}
}
𖠃 결제 승인 요청
📦 요청에 필요한 정보 (필수만 *)
- cid 가맹점코드, 10자 (String)
- tid 결제 고유번호 (String)
- partner_order_id 가맹점 주문번호, 최대 100자 (String)
- partner_user_id 가맹점 회원, 최대 100자 (String)
- pg_token 결제 승인시 요청을 인증하는 토큰 (String)
📦 KAKAO PAY가 응답해주는 정보
- aid 요청 고유번호 (String)
- tid 결제 고유번호 (String)
- cid 가맹점 코드, 10자 (String)
- sid 정기 결제용 id (String)
- partner_order_id 가맹점 주문번호, 최대 100자 (String)
- partner_user_id 가맹점 회원, 최대 100자 (String)
- payment_method_type 결제수단 (CARD or MONEY / String)
- item_name 상품명, 최대 100자 (String)
- quantity 상품수량 (Integer)
- created_at 결제 준비 요청 시각 (String)
- approved_at 결제 승인 시각 (String)
- amount 전체 결제 금액, 비과세 금액, 부가세 금액, 사용한 포인트 금액, 할인금액 (Amount)
- cid 가맹점코드, 10자 (String)
- tid 결제 고유번호 (String)
- partner_order_id 가맹점 주문번호, 최대 100자 (String)
- partner_user_id 가맹점 회원, 최대 100자 (String)
- pg_token 결제 승인시 요청을 인증하는 토큰 (String)
📦 KAKAO PAY가 응답해주는 정보
- aid 요청 고유번호 (String)
- tid 결제 고유번호 (String)
- cid 가맹점 코드, 10자 (String)
- sid 정기 결제용 id (String)
- partner_order_id 가맹점 주문번호, 최대 100자 (String)
- partner_user_id 가맹점 회원, 최대 100자 (String)
- payment_method_type 결제수단 (CARD or MONEY / String)
- item_name 상품명, 최대 100자 (String)
- quantity 상품수량 (Integer)
- created_at 결제 준비 요청 시각 (String)
- approved_at 결제 승인 시각 (String)
- amount 전체 결제 금액, 비과세 금액, 부가세 금액, 사용한 포인트 금액, 할인금액 (Amount)
📦 Amount.java
@Getter
@Setter
@ToString
public class Amount {
private Integer total;
private Integer tax_free;
private Integer vat;
private Integer point;
private Integer discount;
}
📦 KakaoPayApproveResponse.java
@Getter
@Setter
@ToString
public class KaokaoPayApproveResponse {
private String aid;
private String tid;
private String cid;
private String sid;
private String partner_order_id;
private String partner_user_id;
private String payment_method_type;
private String item_name;
private String item_code;
private Integer quantity;
private String created_at;
private String approved_at;
private Amount amount;
}
📜 PayController.java
@Slf4j
@RequiredArgsConstructor
@Controller
@RequestMapping("/pay")
public class PayController {
private final PayService payService;
@GetMapping("/ready")
public @ResponseBody KakaoPayReadyResponse kakaoPayReady(@RequestParam Map<String, Object> params) {
...
}
@GetMapping("/success")
public String kakaoPayApprove(@RequestParam("pg_token") String pgToken) { //pgToken 알아서 들어온다
KakaoPayApproveResponse approveResponse = payService.kakaoPayApprove(pgToken); //kakaoPay 요청양식에 따라 요청객체 만들어 보내는 메서드(밑에서 구현)
return "pay_completed"; //결제 승인 후 redirect 할 페이지 (알아서 구현)
}
}
➼ PayService.java / kakaoPay 요청양식에 따라 요청객체 만들어 보내는 메서드 포함(준비, 승인)
@Slf4j
@RequiredArgsConstructor
@Service
public class PayService {
@Value("${my.admin}")
private String ADMIN_KEY;
private KakaoPayReadyResponse readyResponse;
public KakaoPayReadyResponse kakaoPayReady(Map<String, Object> params) {
MultiValueMap<String,Object> payParams = new LinkedMultiValueMap<>();
payParams.add("cid", "TC0ONETIME"); //테스트 결제는 가맹점 코드로 'TC0ONETIME'를 사용
payParams.add("partner_order_id", "KA2020338445"); //일단 아무값이나 hard coding.
payParams.add("partner_user_id", "kakaopayTest"); //일단 아무값이나 hard coding.
payParams.add("item_name", params.get("item_name"));
payParams.add("quantity", params.get("quantity"));
payParams.add("total_amount", params.get("total_amount"));
payParams.add("tax_free_amount", params.get("tax_free_amount"));
payParams.add("approval_url", "http://localhost:83/pay/success"); //결제 성공시 넘어갈 url
payParams.add("cancel_url", "http://localhost:83/pay/cancel"); //결제 취소시 넘어갈 url
payParams.add("fail_url", "http://localhost:83/pay/fail"); //결제 실패시 넘어갈 url
HttpEntity<Map> requestEntity = new HttpEntity<>(payParams, this.getHeaders());
RestTemplate template = new RestTemplate();
String url = "https://kapi.kakao.com/v1/payment/ready";
readyResponse = template.postForObject(
url,
requestEntity,
KakaoPayReadyResponse.class
);
return readyResponse;
}
@Transactional
public KakaoPayApproveResponse kakaoPayApprove(String pgToken) {
MultiValueMap<String, Object> payParams = new LinkedMultiValueMap<>();
payParams.add("cid", "TC0ONETIME"); //테스트 결제는 가맹점 코드로 'TC0ONETIME'를 사용
payParams.add("tid", kakaoPayReadyResponse.getTid());
payParams.add("partner_order_id", "KA2020338445"); //일단 아무값이나 hard coding.
payParams.add("partner_user_id", "kakaopayTest"); //일단 아무값이나 hard coding.
payParams.add("pg_token", pgToken);
HttpEntity<Map> requestEntity = new HttpEntity<>(payParams, this.getHeaders());
RestTemplate template = new RestTemplate();
String url = "https://kapi.kakao.com/v1/payment/approve";
KakaoApproveResponse approveResponse = template.postForObject(
url,
requestEntity,
KakaoPayApproveResponse.class
);
/*
데이터베이스 저장 및 비즈니스 로직
...
*/
return approveResponse;
}
private HttpHeaders getHeades() {
HttpHeaders headers = new HttpHeaders();
String auth = "KakaoAK " + ADMIN_KEY;
headers.set("Authorization", auth);
headers.set("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
return headers;
}
}
🦉 MultiValueMap / LinkedMultiValueMap (구현객체 中 하나)
: 하나의 키에 여러개의 값(동일한 타입)을 매핑(다중 매핑)할 수 있는 Map 인터페이스의 확장된 구조를 말한다.
구현 객체 중의 하나인 LinkedMultiValueMap은 순서가 유지되는 Linked List를 사용하여 데이터를 저장한다.
🦉 HttpEntity
: Http 요청/응답의 객체를 나타내는 인터페이스다.
new HttpEntity([본문데이터], [헤더])
🦉 RestTemplate
: Rest방식으로 API를 호출할 수 있는 🌱 Spring Framework의 내장 클래스다.
RESTful 웹서비스를 호출하고 응답을 처리하는데 사용된다.
(HTTP 메서드 (GET, POST, PUT, DELETE 등) 지원)
ex. postForObject([요청을 보낼 url], [본문데이터], [원격서버로부터 받을 응답의 데이터타입]);
: HTTP POST 요청을 통해 데이터를 전송하고,
서버로부터 받은 응답을 지정한 객체타입으로 파싱해서 받을 수 있다.
: 하나의 키에 여러개의 값(동일한 타입)을 매핑(다중 매핑)할 수 있는 Map 인터페이스의 확장된 구조를 말한다.
구현 객체 중의 하나인 LinkedMultiValueMap은 순서가 유지되는 Linked List를 사용하여 데이터를 저장한다.
🦉 HttpEntity
: Http 요청/응답의 객체를 나타내는 인터페이스다.
new HttpEntity([본문데이터], [헤더])
🦉 RestTemplate
: Rest방식으로 API를 호출할 수 있는 🌱 Spring Framework의 내장 클래스다.
RESTful 웹서비스를 호출하고 응답을 처리하는데 사용된다.
(HTTP 메서드 (GET, POST, PUT, DELETE 등) 지원)
ex. postForObject([요청을 보낼 url], [본문데이터], [원격서버로부터 받을 응답의 데이터타입]);
: HTTP POST 요청을 통해 데이터를 전송하고,
서버로부터 받은 응답을 지정한 객체타입으로 파싱해서 받을 수 있다.
'Backend > 스프링부트' 카테고리의 다른 글
[Spring Boot] 공통 관심사항을 모아놓고, 원하는 곳에 적용하는 AOP(Aspect Oriented Programming)와 이를 가능하게 하는 프록시 객체(가짜객체) (0) | 2023.09.27 |
---|---|
[Spring Boot] 인코딩 및 암호화 알고리즘을 통해 비밀키 생성, 이를 사용해 Jwt의 헤더와 페이로드를 서명 (무결성 보장) (0) | 2023.09.27 |
[Spring Boot] 컨트롤러에서 정적/동적 콘텐츠를 클라이언트에 넘겨주는 방식 (0) | 2023.09.08 |
[Spring Boot] Pageable 인터페이스와 페이징(Paging) 처리 (0) | 2023.06.04 |
[Spring Boot] 설정정보(개발용, 배포용, 테스트용) (0) | 2023.01.12 |
Comments