Files
HeuJung 21fc114f37 fix(core,extensions): 보안 결함 3건과 이중저장소·레이아웃 중복키 결함군 폐쇄
KISA 제보 3건(KVE-2026-2010/2011/2018)과 그 동일 계열 형제 결함을 전수 조치하고,
그 과정에서 드러난 두 결함군을 함께 닫는다.

- 검증 시점과 연결 시점이 host 를 다르게 읽던 SSRF 통로를 정규화 SSoT 한 곳으로 모았다
- 세션을 여는 지점(2FA 완료·토큰 재발급)이 잠금 검사를 거치지 않아 계정 잠금이 우회됐다
- 인증도 서명도 없는 브라우저 리턴 콜백이 주문 상태를 바꾸던 통로를 4 PG 전부에서 닫고,
 소유권을 대조하는 close-report 를 토스에도 신설했다. 그 결과 정리 주체를 잃는
 결제창 미완료 주문은 만료 자동취소가 거둔다
- 저장소 A(_local)에만 쓰는 경로가 B 의 값을 조용히 덮던 회귀를 정본 writer 로 닫았다
 (engine-v1.63.5). 한 방향만 보던 정적 검사에 반대 방향 축과 양방향 계약 테스트를 더했다
- 레이아웃 JSON 의 같은 객체 중복 키가 앞선 선언을 오류 없이 삼키던 결함군을 닫았다
2026-09-02 17:36:11 +09:00

12 KiB

Payment API 레퍼런스

소유: plugin sirsoft-tosspayments · 생성: php artisan api:docgen (실측 기반). @generated 블록은 재생성 시 갱신되며, 사람이 작성한 설명은 보존됩니다.


TL;DR (5초 요약)

1. 이 문서는 실제 API 호출로 실측한 Payment 엔드포인트 레퍼런스입니다
2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(raw HTTP) + 실측 응답 필드 표 + 응답 예시(envelope)
3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다
4. 갱신: 코드 변경 후 php artisan api:docgen 재실행
5. 설명(TODO) 칸은 사람이 채웁니다

GET /plugins/sirsoft-tosspayments/payment/fail

  • 라우트명: web.plugins.sirsoft-tosspayments.payment.fail
  • 컨트롤러: Plugins\Sirsoft\Tosspayments\Controllers\PaymentCallbackController@fail
  • 인증/권한: 공개 (인증 불필요)

요청 파라미터

이름 위치 타입 필수 허용값 용도
code query string 아니오 — 토스 실패 코드 (예: PAY_PROCESS_CANCELED, REJECT_CARD_COMPANY).
message query string 아니오 — 토스가 내려준 실패 사유 메시지.
orderId query string 아니오 — 주문번호. 실패 페이지로 함께 전달해 어떤 주문의 결제가 실패했는지 표시한다.

요청 예시

GET /plugins/sirsoft-tosspayments/payment/fail?code=%EC%98%88%EC%8B%9C%EA%B0%92&message=%EC%98%88%EC%8B%9C%EA%B0%92&orderId=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1
Host: api.example.com
Accept: application/json

응답 필드 (data 내부)

이 엔드포인트는 JSON 을 반환하지 않는다. 브라우저를 실패 페이지로 302 리다이렉트한다.

응답 예시

HTTP/1.1 302 Found
Location: /shop/checkout?error=PAY_PROCESS_CANCELED&orderId=20260711-000001

에러 응답

상태코드 의미 발생 조건
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

토스 결제창에서 결제가 실패하거나 사용자가 결제를 취소하면 브라우저가 이 URL 로 리다이렉트된다.

JSON 을 반환하지 않는다. 실패 사유를 로그로 기록한 뒤, 실패 페이지(기본 /shop/checkout, 설정 redirect_fail_url)로 실패 코드·메시지·주문번호를 query 로 실어 302 리다이렉트한다. 모든 파라미터가 선택값이라 없어도 4xx 를 반환하지 않는다.

HTTP/1.1 302 Found
Location: /shop/checkout?error=PAY_PROCESS_CANCELED&orderId=20260711-000001

주의사항:

  • 이 엔드포인트는 주문 상태를 변경하지 않는다. 인증도 서명도 없는 GET 이고 orderId·code 가 전부 쿼리스트링에서 오므로, 실패 처리를 수행하면 링크 하나로 남의 결제대기 주문을 취소시킬 수 있다. 결제 실패 기록은 구매자 정보를 대조하는 POST /api/plugins/sirsoft-tosspayments/payment/close-report 가 담당하며, 실패 화면에 도착한 프론트엔드가 그 경로로 보고한다.
  • 결제창을 띄우기 전 단계에서 사용자가 취소한 경우(SDK USER_CANCEL)는 이 콜백을 타지 않고 프론트엔드에서 직접 처리된다.

POST /api/plugins/sirsoft-tosspayments/payment/close-report

  • 라우트명: api.plugins.sirsoft-tosspayments.payment.close-report
  • 컨트롤러: Plugins\Sirsoft\Tosspayments\Controllers\PaymentCloseReportController@store
  • 인증/권한: 공개 (인증 불필요)

요청 파라미터

이름 위치 타입 필수 허용값 용도
orderId body string 예 max 40 결제창을 닫거나 결제가 거절된 대상 주문의 주문번호. 서버가 이 값으로 주문을 조회해 결제 실패/취소 이력을 기록한다.
amount body integer 예 min 1 결제 금액. 저장된 주문 청구액과 일치하는지 검증한다.
buyer_email body string 아니오 max 255 구매자 이메일. 주문의 구매자 정보와 대조해 본인 요청인지 확인한다.
buyer_phone body string 아니오 max 30 구매자 전화번호. 주문의 구매자 정보와 대조해 본인 요청인지 확인한다.
code body string 아니오 max 60 토스 실패 코드. 값이 있으면 결제 거절로, 없으면 결제창 닫힘으로 기록한다.
reason body string 아니오 max 160 사람이 읽을 실패 사유. 취소 이력에 남는다.

요청 예시

POST /api/plugins/sirsoft-tosspayments/payment/close-report HTTP/1.1
Host: api.example.com
Accept: application/json
Content-Type: application/json

{
    "orderId": "20260711-000001",
    "amount": 10000,
    "buyer_email": "buyer@example.com",
    "buyer_phone": "01012345678",
    "code": "REJECT_CARD_COMPANY",
    "reason": "카드사에서 승인을 거절했습니다."
}

응답 필드 (data 내부)

필드 타입 실측 예시값 용도/설명
status string recorded 처리 결과. 기록했으면 recorded, 대상이 아니어서 넘어갔으면 ignored.
reason string order_not_payable status 가 ignored 일 때만 포함. 무시 사유(order_not_payable / payment_already_paid).

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "성공적으로 처리되었습니다.",
    "data": {
        "status": "recorded"
    }
}

에러 응답

상태코드 의미 발생 조건
403 Forbidden 요청의 구매자 정보(buyer_email / buyer_phone)가 주문의 구매자와 일치하지 않는 경우
404 Not Found orderId 에 해당하는 주문이 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지), 주문 통화가 청구 불가, 금액이 주문 청구액과 불일치
429 Too Many Requests 동일 IP·orderId 조합에서 분당 20회를 초과해 요청한 경우

설명

주문을 실패로 전이시키는 유일한 결제창 경로입니다. 브라우저 리턴 콜백(/payment/fail)은 인증도 서명도 없고 주문번호가 쿼리스트링으로 오므로 주문 상태를 바꾸지 않습니다. 정당한 결제 실패는 구매자 이메일·전화 대조를 통과한 이 요청으로만 기록되며, 이는 다른 결제사 플러그인(KCP·KG이니시스·나이스페이)의 close-report 와 같은 계약입니다.

프론트엔드는 결제창을 열기 직전에 구매자 정보를 브라우저 세션에 남겨 두었다가, 결제가 거절되어 실패 화면으로 돌아왔을 때 그 정보로 이 엔드포인트를 호출합니다. 결제창은 전체 페이지 이동으로 열리고 돌아오므로 화면의 컨텍스트는 그 사이 소실되기 때문입니다.

code 유무로 기록이 갈립니다 — 값이 있으면 결제 거절(failure_stage=payment_failed), 없으면 결제창 닫힘(failure_stage=window_closed)으로 남아 운영자가 원인을 구분할 수 있습니다.

이미 결제가 성립한 주문(payment_status=paid)과 결제 가능 상태가 아닌 주문은 성공 응답에 status: ignored 로 무시합니다 — 결제 성공 콜백(success → confirmPayment)과 경쟁할 때 주문/옵션 상태가 어긋나는 것을 차단합니다.

보고가 끝내 도달하지 못한 주문(브라우저를 바로 닫는 등)은 이커머스 모듈의 만료 주문 자동 정리가 최종 안전망으로 처리합니다.

GET /plugins/sirsoft-tosspayments/payment/success

  • 라우트명: web.plugins.sirsoft-tosspayments.payment.success
  • 컨트롤러: Plugins\Sirsoft\Tosspayments\Controllers\PaymentCallbackController@success
  • 인증/권한: 공개 (인증 불필요)

요청 파라미터

이름 위치 타입 필수 허용값 용도
paymentKey query string 예 — 토스가 발급한 결제 키. Confirm API 호출에 사용한다.
orderId query string 예 — 주문번호 (그누보드7 orders.order_number). SDK 호출 시 넘긴 값이 그대로 돌아온다.
amount query integer 예 min 1 결제 금액. 주문의 결제요청 금액과 대조하며, 불일치 시 결제를 승인하지 않는다.

요청 예시

GET /plugins/sirsoft-tosspayments/payment/success?paymentKey=%EC%98%88%EC%8B%9C%EA%B0%92&orderId=%EC%98%88%EC%8B%9C%EA%B0%92&amount=1 HTTP/1.1
Host: api.example.com
Accept: application/json

응답 필드 (data 내부)

이 엔드포인트는 JSON 을 반환하지 않는다. 브라우저를 SPA 페이지로 302 리다이렉트한다.

응답 예시

HTTP/1.1 302 Found
Location: /shop/orders/20260711-000001/complete

에러 응답

상태코드 의미 발생 조건
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

토스 결제창에서 결제가 성공하면 브라우저가 이 URL 로 리다이렉트된다. 결제 승인(Confirm) 을 수행하는 지점이다.

JSON 을 반환하지 않고 브라우저를 302 리다이렉트한다. 에러 상황에서도 4xx/5xx 를 반환하지 않고 실패 페이지(기본 /shop/checkout)로 리다이렉트하며 사유를 query 로 전달한다.

리다이렉트 대상 error query 발생 조건
주문완료 페이지 — 결제 승인 성공 (기본 /shop/orders/{orderId}/complete)
실패 페이지 order_not_found orderId 로 주문을 찾지 못한 경우
실패 페이지 amount_mismatch 콜백 amount 가 주문의 결제요청 금액과 다른 경우 (위변조 방어)
실패 페이지 confirm_failed 토스 Confirm API 호출이 실패한 경우 (message query 에 사유)
실패 페이지 (검증 실패) 필수 query 파라미터 누락/형식 오류 — 422 대신 실패 페이지로 리다이렉트한다

처리 순서:

  1. orderId 로 주문을 조회한다.
  2. 토스 Confirm API 를 호출해 결제를 확정한다 (sirsoft-tosspayments.payment.before_confirm / after_confirm 훅 발화).
  3. 응답 status 로 분기한다:
    • WAITING_FOR_DEPOSIT(가상계좌) — 아직 입금 전이므로 결제완료 처리하지 않고 계좌 정보만 저장한다. 실제 결제완료는 입금통보 웹훅(/webhook/deposit)이 담당한다. 웹훅 secret 은 이 Confirm 응답에만 내려오므로 payment_meta.toss_secret 에 저장해 웹훅 대조에 사용한다.
    • 그 외(카드·계좌이체·휴대폰·간편결제) — 즉시 결제완료(completePayment) 처리하고 카드 승인번호·영수증 URL 등을 기록한다.
  4. SPA 주문완료 페이지로 리다이렉트한다.

주의사항:

  • 금액 대조가 위변조 방어의 핵심이다. query 의 amount 를 그대로 신뢰하지 않고 주문의 결제요청 금액과 대조하며, 불일치 시 PaymentAmountMismatchException 으로 승인을 중단한다.
  • 리다이렉트 대상 URL 은 플러그인 설정(redirect_success_url / redirect_fail_url)으로 바꿀 수 있다 (기본 /shop/orders/{orderId}/complete, /shop/checkout).