카카오페이 결제승인과정에서의 오류 해결문의

다음과 같이 작성할 때 오류가 생긴 이유/원인에 대한 것과 해결방안 문의드립니다.
package com.goldensnitch.qudditch.service;

import com.goldensnitch.qudditch.dto.CustomerOrder;
import com.goldensnitch.qudditch.dto.payment.PaymentResponse;
import com.goldensnitch.qudditch.mapper.CustomerOrderProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

// 카카오계정
// 카카오 페이( Kakao pay) 기능구현
@Service
public class PaymentService {
private final RestTemplate restTemplate;

@Autowired
private CustomerOrderProductMapper customerOrderProductMapper;

// 카카오페이 결제 요청
@Value("${kakao.pay.ready.url}") private String kakaoPayReadyUrl;
// 결제 승인
@Value("${kakao.pay.approve.url}") private String kakaoPayApproveUrl;
// 결제 취소
@Value("${kakao.pay.cancel.url}") private String kakaoPayCancelUrl;
// 카카오페이 API 사용을 위한 인증 키
@Value("${kakao.pay.authorization}") private String kakaoPayAuthorization;
// 가맹점 코드
@Value("${kakao.pay.cid}") private String cid;

// RestTemplate 주입을 통한 HTTP 클라이언트 초기화
@Autowired
public PaymentService(RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
}

// 결제 준비를 시작하고 사용자를 결제 페이지로 리디렉션하는 URL을 반환하는 메소드
public String initiatePayment(String cid, String partnerOrderId, String partnerUserId,
                              String itemName, Integer quantity, Integer totalAmount,
                              Integer taxFreeAmount, String approvalUrl, String cancelUrl,
                              String failUrl) {
    HttpHeaders headers = new HttpHeaders();
    // "Authorization" 헤더에 카카오페이 인증 키 추가
    headers.add("Authorization", "KakaoAK " + kakaoPayAuthorization);
    // 요청 본문의 "Content-Type"을 "application/x-www-form-urlencoded"로 설정

    // headers.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    // PaymentRequest 객체 대신 MultiValueMap을 사용하여 요청 파라미터를 설정
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("cid", cid);
    map.add("partner_order_id", partnerOrderId);
    map.add("partner_user_id", partnerUserId);
    map.add("item_name", itemName);
    map.add("quantity", quantity.toString());
    map.add("total_amount", totalAmount.toString());
    map.add("tax_free_amount", taxFreeAmount.toString());
    map.add("approval_url", approvalUrl);
    map.add("cancel_url", cancelUrl);
    map.add("fail_url", failUrl);

    HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers);

    try {
        ResponseEntity<PaymentResponse> responseEntity = restTemplate.exchange(
                kakaoPayReadyUrl, HttpMethod.POST, entity, PaymentResponse.class);

        PaymentResponse paymentResponse = responseEntity.getBody();
        if (paymentResponse != null) {
            return paymentResponse.getNext_redirect_pc_url();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "Error";
}

public String approvePayment(String pgToken, String tid, String partnerOrderId, String partnerUserId) {
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "KakaoAK " + kakaoPayAuthorization);
    headers.setContentType(MediaType.APPLICATION_JSON);

    // JSON 형식으로 요청 바디 구성
    Map<String, Object> requestBody = new HashMap<>();
    requestBody.put("cid", cid);
    requestBody.put("tid", tid);
    requestBody.put("pg_token", pgToken);
    requestBody.put("partner_order_id", partnerOrderId);
    requestBody.put("partner_user_id", partnerUserId);

    HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);

    try {
        ResponseEntity<PaymentResponse> responseEntity = restTemplate.exchange(
                kakaoPayApproveUrl, HttpMethod.POST, entity, PaymentResponse.class);

        PaymentResponse paymentResponse = responseEntity.getBody();
        if (paymentResponse != null) {
            return "Payment approved successfully.";
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "Error during payment approval.";
}

private void updateOrderStatus(Integer orderId, String tid) {
    // Placeholder method. Implement the logic to update the order's status or save the transaction ID (`tid`) to the order in your database.
    CustomerOrder order = customerOrderProductMapper.findById(orderId);
    if (order != null) {
        order.setTid(tid);
        customerOrderProductMapper.update(order); // Assuming an `update` method exists to update the order
    }
}

// 결제 취소 메서드
public PaymentResponse cancelPayment(String tid, String cancelAmount, String cancelTaxFreeAmount) {
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "KakaoAK " + kakaoPayAuthorization);
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
    parameters.add("cid", cid); // 가맹점 코드
    parameters.add("tid", tid); // 결제 고유 번호
    parameters.add("cancel_amount", cancelAmount); // 취소 금액
    parameters.add("cancel_tax_free_amount", cancelTaxFreeAmount); // 비과세 금액

    HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(parameters, headers);

    ResponseEntity<PaymentResponse> response = restTemplate.exchange(
            kakaoPayCancelUrl, HttpMethod.POST, entity, PaymentResponse.class);

    return response.getBody();
}

}

package com.goldensnitch.qudditch.controller;

import com.goldensnitch.qudditch.dto.payment.PaymentRequest;
import com.goldensnitch.qudditch.dto.payment.PaymentResponse;
import com.goldensnitch.qudditch.service.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(“api/payment”)
public class PaymentController {

@Autowired
private PaymentService paymentService;

@Autowired
public PaymentController(PaymentService paymentService) {
    this.paymentService = paymentService;
}

@PostMapping("/initiate")
public ResponseEntity<?> initiatePayment(@RequestBody PaymentRequest paymentRequest) {
    try {
        // 사용자로부터 받은 정보와 주문 ID를 기반으로 결제 초기화
        paymentRequest.setCid("TC0ONETIME");

        String redirectUrl = paymentService.initiatePayment(
                paymentRequest.getCid(),
                paymentRequest.getPartner_order_id(),
                paymentRequest.getPartner_user_id(),
                paymentRequest.getItem_name(),
                paymentRequest.getQuantity(),
                paymentRequest.getTotal_amount(),
                paymentRequest.getTax_free_amount(),
                "http://localhost:8080/api/payment/approval", // Updated to include orderId in the approvalUrl
                paymentRequest.getCancel_url(),
                paymentRequest.getFail_url()
        );

        if (!"Error".equals(redirectUrl)) {
            return ResponseEntity.ok().body(redirectUrl);
        } else {
            return ResponseEntity.badRequest().body("Failed to initiate payment");
        }
    } catch (Exception e) {
        return ResponseEntity.internalServerError().body("Error initiating payment: " + e.getMessage());
    }
}

@GetMapping("/approval")
public ResponseEntity<?> approvePayment(@RequestParam("pg_token") String pgToken,
                                        @RequestBody String tid,
                                        @RequestBody String partnerOrderId,
                                        @RequestBody String partnerUserId) {
    try {
        String result = paymentService.approvePayment(pgToken, tid, partnerOrderId, partnerUserId);
        if ("Payment approved successfully.".equals(result)) {
            return ResponseEntity.ok().body("Payment approved successfully.");
        } else {
            return ResponseEntity.badRequest().body(result);
        }
    } catch (Exception e) {
        return ResponseEntity.badRequest().body("Payment approval failed: " + e.getMessage());
    }
}

@PostMapping("/cancel")
public ResponseEntity<?> cancelPayment(@RequestParam String tid,
                                       @RequestParam String cancelAmount,
                                       @RequestParam String cancelTaxFreeAmount) {
    try {
        PaymentResponse paymentResponse = paymentService.cancelPayment(tid, cancelAmount, cancelTaxFreeAmount);
        if (paymentResponse != null) {
            // 결제 취소가 성공적으로 이루어졌을 경우의 처리 로직
            return ResponseEntity.ok().body(paymentResponse);
        } else {
            // 결제 취소 요청이 실패했을 경우의 처리 로직
            return ResponseEntity.badRequest().body("Failed to cancel payment");
        }
    } catch (Exception e) {
        // 예외 처리 로직
        return ResponseEntity.internalServerError().body("Error canceling payment: " + e.getMessage());
    }
}

}

[오류]
GET “/api/payment/approval?pg_token=d5c100e2bc10d0ac23b4”, parameters={masked}
2024-03-19T11:34:27.531+09:00 DEBUG 18856 — [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.goldensnitch.qudditch.controller.PaymentController#approvePayment(String, String, String, String)
2024-03-19T11:34:27.562+09:00 DEBUG 18856 — [nio-8080-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Read “application/octet-stream” to []
2024-03-19T11:34:27.566+09:00 WARN 18856 — [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<?> com.goldensnitch.qudditch.controller.PaymentController.approvePayment(java.lang.String,java.lang.String,java.lang.String,java.lang.String)]
2024-03-19T11:34:27.567+09:00 DEBUG 18856 — [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed 400 BAD_REQUEST
2024-03-19T11:34:27.575+09:00 DEBUG 18856 — [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : “ERROR” dispatch for GET “/error?pg_token=d5c100e2bc10d0ac23b4”, parameters={masked}
2024-03-19T11:34:27.575+09:00 DEBUG 18856 — [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
2024-03-19T11:34:27.607+09:00 DEBUG 18856 — [nio-8080-exec-3] o.s.w.s.v.ContentNegotiatingViewResolver : Selected ‘text/html’ given [text/html, text/html;q=0.8]
2024-03-19T11:34:27.608+09:00 DEBUG 18856 — [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Exiting from “ERROR” dispatch, status 400

안녕하세요.

카카오페이 API 관련 문의 게시판이 카카오페이 디벨로퍼스 포럼으로 이전되었음을 안내드립니다.
따라서, 모든 API 관련 문의와 토의는 앞으로 해당 포럼에서 처리될 예정입니다.
불편을 드려 죄송합니다만, 카카오페이 API와 관련된 모든 문의나 논의 사항은 카카오페이 디벨로퍼스 포럼을 방문하셔서 문의해 주시기를 부탁드립니다.

카카오페이 디벨로퍼스 포럼