Skip to content

[FEAT] 페이플 결제결과 웹훅 수신 엔드포인트 (#536) - #537

Merged
minij02 merged 1 commit into
developfrom
feat/#536-payple-webhook
Aug 9, 2026
Merged

[FEAT] 페이플 결제결과 웹훅 수신 엔드포인트 (#536)#537
minij02 merged 1 commit into
developfrom
feat/#536-payple-webhook

Conversation

@minij02

@minij02 minij02 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

closes #536

📌 기능 설명

페이플 파트너 관리자 〉 기본정보의 결제결과 수신 URL (가맹점 미수신 결과) 에 등록한 경로를 구현합니다.

https://promptplace.kro.kr/api/prompts/purchases/payple-webhook

브라우저가 결제창에서 돌아오지 못한 결제(창 강제 종료, 네트워크 끊김, 앱 전환 실패)를 서버-투-서버로 보완해 결제결과 누락을 방지합니다.

📌 구현 내용

1. 웹훅 전용 경로 분리 — 리다이렉트 금지

기존 POST /payple-resultPCD_RST_URL(결제창 리턴 URL) 겸용입니다. 운영 .envPURCHASE_RESULT_REDIRECT_URL 이 설정돼 있어 302 를 반환하는데, 페이플은 이를 수신 실패로 보고 재전송을 반복할 수 있습니다.

리다이렉트 없이 항상 200 을 주는 POST /payple-webhook 을 분리했습니다. 처리 실패 시에만 500 을 반환해 페이플 재전송을 유도합니다 (기존 payout-webhook 과 동일 규약).

2. PCD_PAY_COFURL 폴백 — 이게 없으면 웹훅 전건 실패

공식 문서의 결제완료 웹훅 예시(카드/계좌 공통)에서 PCD_PAY_URL"" 이고 최종 결제요청 URL 은 PCD_PAY_COFURL 로 옵니다. verifyPayplePaymentpayUrl 이 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 대상에 추가.

멱등성은 handlePaypleResultfindExistingPurchase 를 그대로 재사용해 /complete 와 동시 도착해도 중복 구매가 생기지 않습니다.

📌 구현 결과

라우터만 띄워 응답 코드 확인 (DB 미접촉 분기):

✅ 웹훅 취소이벤트: 200 (기대 200)
✅ 웹훅 비성공: 200 (기대 200)
✅ 웹훅 빈 페이로드: 200 (기대 200)
✅ 대조군 /payple-result: 302 (302 = 웹훅 부적합 확인)

도메인 가드 — 아래 전부 차단 확인:
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 에 매칭할 컬럼도 없습니다. 간편/정기결제 도입 시 별도 이슈로 다루면 됩니다.

브라우저가 결제창에서 돌아오지 못한 결제를 서버-투-서버로 보완한다.
파트너 관리자에 등록한 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와 동시 도착해도 중복 구매가 생기지 않는다.
@minij02 minij02 self-assigned this Aug 9, 2026
@minij02
minij02 merged commit a5866ba into develop Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant