[FEAT] 페이플 결제결과 웹훅 수신 엔드포인트 (#536) - #537
Merged
Merged
Conversation
브라우저가 결제창에서 돌아오지 못한 결제를 서버-투-서버로 보완한다. 파트너 관리자에 등록한 URL: /api/prompts/purchases/payple-webhook - PCD_RST_URL 겸용인 /payple-result는 PURCHASE_RESULT_REDIRECT_URL이 설정된 운영에서 302를 반환해 웹훅 수신에 부적합. 리다이렉트 없이 항상 200을 주는 전용 경로로 분리하고, 처리 실패 시에만 500으로 페이플 재전송을 유도한다. - 웹훅 페이로드는 PCD_PAY_URL이 빈 문자열이라 기존 키 누락 가드에 걸려 재검증이 전건 실패했다. PCD_PAY_COFURL 폴백 추가. - 재검증 대상 URL이 요청 본문에서 오므로 payple.kr 도메인 + https 검사 추가. 인증 없는 공개 웹훅이 같은 함수를 타므로 없으면 SSRF. - 취소완료 이벤트는 PCD_USER_DEFINE1이 없어 결제 처리 로직을 태울 수 없다. 환불 정본은 admin-refund 워크플로(#533)라 로그만 남긴다. - 웹훅 페이로드의 PCD_PAYER_HP/EMAIL을 로그 마스킹 대상에 추가. 멱등성은 handlePaypleResult의 findExistingPurchase를 그대로 재사용해 /complete와 동시 도착해도 중복 구매가 생기지 않는다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #536
📌 기능 설명
페이플 파트너 관리자 〉 기본정보의 결제결과 수신 URL (가맹점 미수신 결과) 에 등록한 경로를 구현합니다.
브라우저가 결제창에서 돌아오지 못한 결제(창 강제 종료, 네트워크 끊김, 앱 전환 실패)를 서버-투-서버로 보완해 결제결과 누락을 방지합니다.
📌 구현 내용
1. 웹훅 전용 경로 분리 — 리다이렉트 금지
기존
POST /payple-result는PCD_RST_URL(결제창 리턴 URL) 겸용입니다. 운영.env에PURCHASE_RESULT_REDIRECT_URL이 설정돼 있어 302 를 반환하는데, 페이플은 이를 수신 실패로 보고 재전송을 반복할 수 있습니다.리다이렉트 없이 항상 200 을 주는
POST /payple-webhook을 분리했습니다. 처리 실패 시에만 500 을 반환해 페이플 재전송을 유도합니다 (기존payout-webhook과 동일 규약).2.
PCD_PAY_COFURL폴백 — 이게 없으면 웹훅 전건 실패공식 문서의 결제완료 웹훅 예시(카드/계좌 공통)에서
PCD_PAY_URL은""이고 최종 결제요청 URL 은PCD_PAY_COFURL로 옵니다.verifyPayplePayment는payUrl이 falsy 면 키 누락으로 던지므로, 폴백 없이는 재검증이 100% 실패합니다.3. 재검증 대상 URL 도메인 가드 (SSRF)
verifyPayplePayment는 요청 본문의 호스트로 POST 합니다. 지금까지는 브라우저 리턴 경로 전용이라 노출이 제한적이었지만, 인증 없는 공개 웹훅이 같은 함수를 타므로 임의 호스트로 요청을 유도할 수 있습니다.https+payple.kr도메인 검사를 추가했습니다.4. 취소완료 이벤트는 로그만
같은 URL 로 취소완료(
PAYC0000)도 들어오지만PCD_USER_DEFINE1(prompt_id/user_id)이 없어 결제 처리 로직을 태울 수 없습니다. 환불 정본은 admin-refund 워크플로(#533)이므로 기록만 남깁니다.5. 로그 마스킹 — 웹훅 페이로드에 실려 오는
PCD_PAYER_HP/PCD_PAYER_EMAIL을 redact 대상에 추가.멱등성은
handlePaypleResult의findExistingPurchase를 그대로 재사용해/complete와 동시 도착해도 중복 구매가 생기지 않습니다.📌 구현 결과
라우터만 띄워 응답 코드 확인 (DB 미접촉 분기):
도메인 가드 — 아래 전부 차단 확인:
https://evil.com/https://evil-payple.kr/https://payple.kr.evil.com/http://cpay.payple.kr(평문) /http://169.254.169.254(메타데이터) / HOST+URL 조합 경로 / 값 없음pnpm build통과.📌 논의하고 싶은 점
1.
PCD_PAY_COFURL로 재검증하는 게 맞는지 실제 웹훅으로 검증 필요문서상
PCD_PAY_COFURL은 "최종 결제요청 URL" 입니다. 브라우저 리턴 경로의PCD_PAY_HOST + PCD_PAY_URL과 같은 대상이라고 보고 폴백을 걸었지만, 실제 웹훅 페이로드로 확인하지는 못했습니다.빗나가더라도 폴백 없는 현재 상태(전건 실패)보다 나빠지지는 않고, 실패 시 OID 가 로그에 남아 수동 대사가 가능합니다. 다만 dev 계정으로 실제 웹훅을 한 번 받아보고 확정하는 게 좋겠습니다.
2. 500 반환 = 재전송 유도, 동기 처리라 타임아웃 여지
재검증(axios 10s) + DB 트랜잭션을 동기로 처리한 뒤 응답합니다. 페이플 웹훅 타임아웃 규격이 문서에 없어, 짧다면 "즉시 200 후 비동기 처리" 로 바꿔야 합니다. 대신 그 경우 실패 건의 재전송을 못 받습니다. 현재는 재전송 가능성을 택했습니다.
3. 웹훅 인증 없음
페이플이 서명/IP 를 제공하지 않아
payout-webhook과 마찬가지로 무인증입니다. 금액은 서버 가격과 대조하고 재검증도 거치므로 위조 생성은 막히지만, IP allowlist 를 nginx 단에 걸어두면 더 안전합니다.4. env 추가 없음 — 신규 코드가 읽는 환경변수는 0건입니다. 다만 dev 계정(데모) 파트너 관리자에도 웹훅 URL 을 따로 등록해야 합니다 (
https://promptplace-dev.kro.kr/api/prompts/purchases/payple-webhook). 계정별 설정입니다.5. 해지결과 수신 URL 칸은 비워뒀습니다 — 구매 결제가
PCD_PAY_WORK: 'PAY'단건결제라PCD_PAYER_ID빌링키를 저장하지 않고 schema 에 매칭할 컬럼도 없습니다. 간편/정기결제 도입 시 별도 이슈로 다루면 됩니다.