티스토리 뷰
2022년 11월 17일 velog에 포스팅한 내용을 이전한 게시글입니다.
문제 정의
- Axios post방식을 통해 넘어온 데이터를 2개의 테이블에 저장해야하는 상황. (결제 내역, 예매 내역)
- json 형식으로 넘어오는 json 데이터 객체를 서버에서 데이터를 어떻게 처리할지 고안 필요.
제약 조건
- jsonArray가 아닌 jsonObject로 요청.
- 데이터를 담는 객체인 DTO(Vo)를 새로 만들지 않고 기본 테이블을 활용.
위 조건으로 진행하는 이유는 기존 자주 사용하던 방식을 탈피한 새로운 방식으로 데이터 처리를 해보고자 제약을 두었습니다.
문제 해결 절차
1. front에서 아임포트를 통해 결제를 성공 후 결제내역과 예매내역을 json 형태로 만들어 데이터 저장을 요청.
2. 여러가지 시도를 통해 Jackson라이브러리의 JsonNode 객체를 매개변수로하여 원하는 json 내부의 json객체를 찾아 뽑아낼 수 있음을 알게됨. 이를 활용하여 원하는 객체를 뽑고 ObjectMapper(Jackson라이브러리) 객체를 생성자를 통해 호출하여 JsonNode 객체를 java객체로 변환.
@PostMapping("/complete")
public ResponseEntity<String> paymentComplete(@RequestBody JsonNode json_node) throws IOException {
// 방법1.jackson 사용
ObjectMapper mapper =new ObjectMapper();
Payment payment = mapper.treeToValue(json_node.get("payment"),Payment.class);
Booking booking = mapper.treeToValue(json_node.get("booking"),Booking.class);
3. 아임포트에 토큰을 요청하여 응답 받은 후 해당 토큰으로 완료된 결제 정보 중 금액만 받아와 취소시 매개변수로 사용.
5. 포인트 검증
//1. 아임포트 토큰 생성
String token = paymentService.getToken();
//2. 토큰으로 결제 완료된 주문 정보 호출하여 결제 완료된 금액
int amount = paymentService.paymentInfo(payment.getImp_uid(), token);
try {
int my_point = myPageService.selectPoint("1");
int used_point = payment.getPay_use_point();
//3-1. 사용된 포인트가 유저가 기존에 보유한 포인트보다 많을 경우
if (my_point < used_point) {
paymentService.paymentCancle(token, payment.getImp_uid(), amount, "사용 가능 포인트 부족");
return new ResponseEntity<String>("[결제 취소] 사용가능한 포인트가 부족합니다.", HttpStatus.BAD_REQUEST);
}
//4. 특별한 문제가 없을 경우 payment 데이터 저장 paymentService.insertPaymentData(payment); //5. booking 데이터 저장 bookingService.insertBookingData(booking); return new ResponseEntity<>("주문이 완료되었습니다", HttpStatus.OK); } catch (Exception e){ paymentService.paymentCancle(token, payment.getImp_uid(),amount,"결제 예러");
return new ResponseEntity<>("결제 에러", HttpStatus.BAD_REQUEST);
}
}
비즈니스 로직 메서드(Service)
paymentInfo
결제번호와 토큰을 통해 아임포트에 저장된 결제 내역을 가지고 오는 역할을 수행하는 메서드. 기존에 Json-simple 라이브러리를 보통 사용하였으나 이번은 조금 더 간결하게 json을 파싱할 수 있는 Gson이라는 구글 오픈소스 라이브러리 알게되어 사용. Gson을 사용해 json을 java 객체로 변환한 뒤 getter를 사용하여 원하는 값 (가격) 을 반환.
//JSON내부의 response를 파싱할 객체
@ToString
@Getter
private class Response{
public PaymentInfo getResponse() {
return response;
}
private PaymentInfo response;
}
//JSON에서 금액을 가져오기위한 객체
@ToString
@Getter
private class PaymentInfo{
public int getAmount() {
return amount;
}
private int amount;
}
public int paymentInfo(String imp_uid, String access_token) throws IOException {
HttpsURLConnection conn = null;
//아임포트 결제정보 API 호출(결제번호)
URL url = new URL("https://api.iamport.kr/payments/" + imp_uid);
conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", access_token);
conn.setDoOutput(true);
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
Gson gson = new Gson();
//json -> Object
Response response = gson.fromJson(br.readLine(), Response.class);
br.close();//버퍼 닫기
conn.disconnect();//HttpURLConnection 연결 끊기.
return response.getResponse().getAmount();
}
paymentCancle
결제 취소를 실행하는 메서드. OutputStream을 통해 데이터를 전달하여 아임포트 api에 취소 요청.
public void paymentCancle(String access_token, String imp_uid, int amount ,String reason) throws IOException {
//HttpURLConnection 인스턴스 객체 생성
HttpsURLConnection conn = null;
//URL 검증 및 객체 생성
URL url = new URL("https://api.iamport.kr/payments/cancel");
//URL 연결(웹페이지 URL 연결)
conn = (HttpsURLConnection) url.openConnection();
//요청방식 선택
conn.setRequestMethod("POST");
// 타입설정(application/json) 형식으로 전송 (Request Body 전달시 application/json로 서버에 전달.)
conn.setRequestProperty("Content-type","application/json");
// 서버 Response Data를 JSON 형식의 타입으로 요청.
conn.setRequestProperty("Accept","application/json");
// 권한을 access_token 으로 부여
conn.setRequestProperty("Authorization",access_token);
// OutputStream으로 POST 데이터를 넘겨주겠다는 옵션.
conn.setDoOutput(true);
JsonObject json = new JsonObject();
json.addProperty("reason", reason);
json.addProperty("imp_uid", imp_uid);
json.addProperty("amount", amount);
json.addProperty("checksum", amount);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
bw.write(json.toString());
//flush는 쉽게 말해서 stream에 남아 있는 데이터를 강제로 내보내는 역할
bw.flush();
bw.close();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(),"UTF-8"));
br.close();
conn.disconnect();
}
getToken
@value 어노테이션을 통해 yml파일에 표시된 아임포트 api key와 imp_secret 값을 받은 클래스 변수를 json 객체에 넣어 아임포트에 요청. 요청을 통해 응답 받은 json 객체 내부의 access token을 Gson으로 파싱하여 반환.
@Value("${imp_key}")
private String impKey;
@Value("${imp_secret}")
private String impSecret;
public String getToken() throws IOException {
HttpsURLConnection conn = null;
URL url = new URL("https://api.iamport.kr/users/getToken");
conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setDoOutput(true);
JSONObject json = new JSONObject();
json.put("imp_key", impKey);
json.put("imp_secret", impSecret);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
bw.write(json.toString());
bw.flush();
bw.close();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
Gson gson = new Gson();
String response = gson.fromJson(br.readLine(), Map.class).get("response").toString();
System.out.println(response);
String token = gson.fromJson(response, Map.class).get("access_token").toString();
br.close();
conn.disconnect();
return token;
}
참고자료 출처
- https://velog.io/@jiandme/SpringBoot-배달의민족-25-결제API아임포트-연동-주문-환불
- https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=klh1514&logNo=120190269672 (flush 참고)
- https://hianna.tistory.com/629 (Gson 라이브러리 참고)
- https://csw7432.tistory.com/entry/Java-Input-Output-Stream
- https://triest.tistory.com/14 (HttpURLConnetion 참고)
- https://velog.io/@jduckling_1024/Value로-application.yml의-값-가져오기
'Spring' 카테고리의 다른 글
Spring은 Event를 어떻게 구현했을까? (1) | 2025.01.19 |
---|---|
[Spring] OpenFeign 기본부터 알아보기 (0) | 2024.10.13 |
[SpringBoot] 결제, 아임포트 문제 해결 과정 (1) (0) | 2023.03.18 |
- Total
- Today
- Yesterday