서류를 엄청 많이 넣었지만 면접도 못가고 서류탈락만 하게된다.. 면접이라도 볼 수 있지 않을까 해서 이력서를 준비하고 넣고 하지만 서류열람 알림이 뜨고 기대하고 실망하기만 반복된다.
그래도 꾸준히 어제보다 나은 내일을 위해 더 열심히 공부하고 스팩 쌓고 이력서를 계속 넣어야겠다. 언젠간 날 알아봐줄 회사가 나타날거라 믿는다! 날 알아봐준 그 회사에 성장에 모든걸 쏟아부을것이다.
카카오페이 결제 후 승인 요청 시, pg_token 값이 백엔드에서 정상적으로 전달되지 않아 403 Forbidden 오류가 발생했다.
또한, MissingServletRequestParameterException이 발생하며 백엔드에서 'pg_token'이 누락되었다고 인식했다.
현재 이런식으로 한번 더 결제가 반복되는 현상과 pg_token이 프론트에서 백엔드로 가지않는 현상이 발생된다!!
생각을 해봤다.. 음..아니 전달하는 코드인데 왜 전달이 안될까? 혹시 이럴때 마다 가능성이있는 보내는 형식 문제인가? 그래서 못 받는거지 라고 생각이 됐다
POSTMAN으로 백엔드 테스트를 해봤더니 403에러가 뜬다.. 보통 Security 문제일 확률이 높다고 생각하는데 아니 그럴 일이 없다 CORS설정등 다했기 때문에!
그러면 내 코드에서 문제점이 있나 살펴봤더니 웬걸 백엔드에서 RequestParam으로 받고있었네?
보안적으로도 RequestBody가 더 안전하다 (쿼리 파라미터는 URL에 남아서 노출될 가능성 있음)
그리고 POST/PUT은 RequestBody를 주로 사용하는데 왜 자연스럽게 RequestParam을 썼을까 생각한다. 그래도 금방 발견해서 정말 뿌듯했다.
프론트엔드 문제 해결
pg_token을 정상적으로 가져오는지 console.log()를 통해 확인
백엔드 문제 해결
@RequestParam으로 pg_token을 받는 구조였지만, 프론트에서 Body로 전송했기 때문에 @RequestBody로 변경하여 해결
백엔드 (PaymentController.java)
@PostMapping("/success")
public ResponseEntity<String> paymentSuccess(@RequestBody PaymentRequestDTO request,
@RequestHeader("Authorization") String token) {
String pgToken = request.getPgToken();
Long postId = request.getPostId();
String tid = request.getTid();
System.out.println("Received pg_token: " + pgToken);
System.out.println("Received postId: " + postId);
System.out.println("Received tid: " + tid);
kakaoPayService.approvePayment(postId, userId, tid, pgToken);
return ResponseEntity.ok("결제가 성공적으로 완료되었습니다.");
}
결국 되어버린 결제..!
데이터가 들어오는게 이렇게 행복한 일이라니!
테스트이고, 개인프로젝트지만 정말 신기하고 재미있는 경험이다... 스타트업쪽은 포트원이라는 온라인 간편결제로 한다니깐 나중에 이걸 바탕으로 시도해봐야겠다
그럼 이제 남은건 중복 요청 문제이다
웹에서 시도해봤더니 한번 승인되고 한번 거절되고 했다
POSTMAN으로 테스트했을때, 됐으니 백엔드 문제가 아닌 프론트에서 중복 요청을 하겠구나 라고 생각하고 일단 난 백엔드니깐 백엔드에서 중복요청을 방지하는 로직을 추가하면 더욱더 보안에 좋을 것 같아서 백엔드 중복 요청 방지하는 로직을 추가했다
백엔드 추가 내용:
boolean existsByPostIdAndUserIdAndPaymentStatus(Long postId, Long userId, String paymentStatus);
boolean isAlreadyPaid = paymentRepository.existsByPostIdAndUserIdAndPaymentStatus(postId, userId, "SUCCESS");
if (isAlreadyPaid) {
throw new RuntimeException("이미 결제된 내역입니다.");
}
역시 예상했던 것 처럼 해결은 안됐지만, 혹시 모를 방지도 되니 로직을 추가하였다.
🧐 원인 분석
✔ 백엔드에서 중복 요청을 방지하는 로직을 추가했지만, 애초에 중복 요청이 발생하는 원인이 프론트에 있었음.
✔ useEffect가 searchParams를 감지하며 두 번 실행됨 → 같은 요청이 두 번 전송됨
✔ 결국, 프론트에서 중복 실행을 막아야 해결 가능
🔍 해결 방법 (프론트 수정)
✅ useRef를 사용해 중복 요청 방지
✅ useCallback을 활용하여 useEffect 내부에서 중복 실행되지 않도록 처리
import React, { useEffect, useRef, useCallback } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import axios from "axios";
const PaymentSuccess = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const isRequestSent = useRef(false); // 중복 요청 방지용 useRef 추가
// API 호출 로직을 useCallback으로 감싸기
const handlePaymentApproval = useCallback(() => {
if (isRequestSent.current) return; // 이미 요청을 보냈다면 실행하지 않음
isRequestSent.current = true; // 첫 번째 실행 이후 true로 변경
const pgToken = searchParams.get("pg_token");
const postId = searchParams.get("postId");
const tid = localStorage.getItem("tid");
const token = localStorage.getItem("token");
if (!pgToken || !postId || !tid) {
alert("결제 정보가 올바르지 않습니다.");
navigate("/");
return;
}
axios.post(
`http://localhost:8080/payment/success`,
{ pg_token: pgToken, postId: postId, tid: tid },
{ headers: { Authorization: `Bearer ${token}` } }
)
.then(() => {
alert("결제가 완료되었습니다!");
localStorage.removeItem("tid");
navigate(`/post/${postId}`);
})
.catch((error) => {
console.error("결제 승인 실패:", error.response ? error.response.data : error);
alert("결제 승인에 실패했습니다.");
navigate("/");
});
}, [searchParams, navigate]); // 의존성 배열 추가 (ESLint 경고 방지)
useEffect(() => {
handlePaymentApproval();
}, [handlePaymentApproval]); // useEffect에서 useCallback 사용
return <div>결제 승인 중...</div>;
};
export default PaymentSuccess;
✅ 해결된 점
✔ 백엔드, 프론트에서 중복 실행 방지
✔ useEffect가 한 번만 실행됨 → 중복 승인 요청 사라짐
✔ 카카오페이 결제 승인 정상 작동
📌 결론
✅ "프론트에서 발생한 문제이지만, 백엔드에도 로직을 추가하기!"
✅ 앞으로 useEffect 사용 시 중복 실행을 방지하는 방법을 고려해야 함.
✅ useRef와 useCallback을 적극 활용하면 중복 요청을 방지할 수 있음.
이제 결제 승인 요청이 한 번만 실행되며, 정상적으로 완료되었다!
이제 다음 예정은 is_Paid 가 1이면 게시물이 결제하기버튼 안보이고, 결제가 된건데
이게 user 마다 그래야하는데 게시물 is_Paid가 true가 되면, 모든 user들이 통합적이로 보이는지? 이거에 대한 의문을 해결하는 것과, 오류없는지 확인한번 다 돌리고 게시글부분 댓글과 좋아요 되면 구현 더 하고
둘중 선택해서 하거나 둘다 할것같다.
그리고 나는 java개발자가 아닌 백엔드 개발자로서 golang도 설치하여 맛만보고, node.js로 앱이나 만들까 고민중이다.
오오 정말 멋진데요 :3 성장 응원하겠습니다!