Skip to Content

오류

모든 4xx/5xx 응답은 동일한 JSON envelope를 가집니다.

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" } ] }
  • code숫자 HTTP 상태(400, 401, 404, …). 일반 HTTP 레이어 처리에는 유용하지만, 분기 로직에는 대신 message로 분기하세요 — code는 예를 들어 invalid_inputpayment_method_not_supported(둘 다 400)를 구분하지 못합니다.
  • message — 내부 errors.go 상수에서 파생된 소문자 스네이크 케이스 센티넬 이름(예: INVALID_INPUT → "invalid_input"). 릴리스 간 안정적 입니다 — 이것으로 분기하세요.
  • details — 검증 오류 시 채워집니다. 클라이언트가 오류를 입력에 매핑할 수 있도록 { field, message } 객체 배열입니다. 그 외에는 생략됩니다.

현재 본문에 trace_id 또는 timestamp를 반환하지 않습니다. 이 페이지의 초기 초안에서 둘 다 약속했지만 — 그것은 희망 사항이었습니다. 서버 로그를 요청과 연관시켜야 하는 경우, 응답 Date 헤더와 게이트웨이 측 레이트 리밋 헤더(X-RateLimit-*)를 캡처하고 지원 티켓에 인용하세요.

게이트웨이 엣지 거부는 다른 형식을 사용합니다. 위의 envelope는 백엔드 서비스가 발생시키는 것입니다. 서비스에 도달하기 전에 게이트웨이에서 거부된 요청 — /b2b/v1/* 호출에 누락/잘못된 X-Signature, 알 수 없는 X-Client-ID, 또는 오래된 X-Timestamp — 는 { "error": "...", "message": "..." } 형태로 돌아옵니다. 여기서 error는 거친 슬러그(unauthorized / bad_request / service_unavailable)이고 message는 세부 사항을 전달합니다. 숫자 code도, details도 없습니다. 따라서 검증자는 먼저 HTTP 상태로 분기하고, message를 읽고, 요청이 게이트웨이를 통과한 후에만 code/details를 존재로 다루어야 합니다. 예시 게이트웨이 본문 (401):

{ "error": "unauthorized", "message": "invalid signature" }

HTTP 상태 → 일반적인 코드

HTTP일반적인 code의미
400INVALID_INPUT, MISSING_REQUIRED, INVALID_FORMAT, INVALID_LENGTH, INVALID_VALUE, INVALID_USER_STATUS, INVALID_USER_ROLE, PAYMENT_METHOD_NOT_SUPPORTED, AMOUNT_BELOW_MINIMUM잘못된 요청 — details를 확인
401INVALID_CREDENTIALS, INVALID_TOKEN, TOKEN_EXPIRED, INVALID_SIGNATURE (B2B); INVALID_OTP, OTP_EXPIRED, SESSION_EXPIRED (대시보드 / JWT만)인증 실패 — 잘못된 키, 만료된 타임스탬프, 잘못된 서명. OTP/SESSION_EXPIRED 코드는 대시보드 JWT 경로(/payment/v1/*)에서만 노출됩니다. 순수 B2B 통합에서는 볼 수 없습니다.
402INSUFFICIENT_CREDIT가맹점 선불 잔액 소진 — 재시도 전 충전 필요
403FORBIDDEN, IP_BLOCKED키는 유효하지만 이 호출에 대한 스코프/IP 허용 권한 없음
404NOT_FOUND, RECORD_NOT_FOUND, USER_NOT_FOUND, SESSION_NOT_FOUND리소스가 존재하지 않음(또는 가맹점에 대해 존재하지 않음)
409ALREADY_EXISTS, USER_ALREADY_EXISTS, SESSION_ALREADY_EXISTS다른 본문으로 멱등성 재시도, 또는 상태 머신이 전이를 거부함
429TOO_MANY_REQUESTS, TOO_MANY_ATTEMPTS레이트 리밋 도달. 백오프 후 재시도
500INTERNAL_SERVER_ERROR, DATABASE_CONNECTION_ERROR, DATABASE_QUERY_ERROR, DATABASE_TRANSACTION_ERROR, REDIS_CONNECTION_ERROR, REDIS_OPERATION_ERROR, EXTERNAL_SERVICE_ERROR저희 측 문제. 백오프로 재시도 안전. (TWILIO_SERVICE_ERROR / SENDGRID_SERVICE_ERROR와 같은 제공자별 코드는 내부적으로 존재하지만 대시보드 측 알림 흐름에만 노출되며 B2B 엔드포인트에는 노출되지 않습니다.)
503SERVICE_UNAVAILABLE다운스트림 종속성 다운. 백오프 후 재시도

전체 코드 레퍼런스

볼 수 있는 전체 code 값 집합(payment-service/pkg/errors/errors.go와 일치):

인증 및 세션 (401)

  • INVALID_CREDENTIALS — 사용자명/비밀번호 또는 API 키 조합 거부됨
  • INVALID_TOKEN — JWT/세션 토큰이 파싱 불가하거나 변조됨
  • TOKEN_EXPIRED — JWT가 exp를 지남
  • INVALID_OTP — OTP 불일치
  • OTP_EXPIRED — OTP가 허용 윈도우 이상 전에 발급됨
  • SESSION_EXPIRED — 대시보드 세션 만료
  • INVALID_SIGNATURE — B2B / 웹훅 호출의 HMAC 서명 불일치

권한 (403)

  • FORBIDDEN — 인증되었지만 역할/스코프/가맹점 경계가 액션을 차단함
  • IP_BLOCKED — IP가 남용 목록에 있음

찾을 수 없음 (404)

  • NOT_FOUND — 일반
  • RECORD_NOT_FOUND — 주어진 ID에 대한 행 누락
  • USER_NOT_FOUND — 사용자 조회 실패
  • SESSION_NOT_FOUND — 대시보드 세션 ID 인식 불가

충돌 (409)

  • ALREADY_EXISTS — 일반
  • USER_ALREADY_EXISTS — 가입이 고유 제약에 걸림
  • SESSION_ALREADY_EXISTS — 중복 세션 삽입

검증 (400)

  • INVALID_INPUT — 일반. details 확인
  • MISSING_REQUIRED — 필수 필드 누락
  • INVALID_FORMAT — 값이 예상 형식과 일치하지 않음(예: UUID, URL, email)
  • INVALID_LENGTH — 값이 너무 짧거나 너무 길음
  • INVALID_VALUE — 값이 허용된 enum/범위를 벗어남
  • INVALID_USER_STATUS — 사용자가 액션을 허용하지 않는 상태
  • INVALID_USER_ROLE — 역할에 액션 권한이 없음
  • PAYMENT_METHOD_NOT_SUPPORTED — 제공자/자산 조합이 가맹점에 활성화되지 않음
  • AMOUNT_BELOW_MINIMUM — 주문 금액이 네트워크별 또는 env 레벨 하한선 미만. 응답의 details.floor_usd에 구성된 하한선(USD)이 담겨 있으므로 바로 노출할 수 있습니다 — message 텍스트에도 동일한 내용이 표시됩니다.
  • INSUFFICIENT_BALANCE — 구매자 지갑이 전송 결제 자산을 충분히 보유하고 있지 않음.
  • INSUFFICIENT_GAS — 구매자 지갑에 전송을 브로드캐스트할 네이티브 가스가 부족함.

결제 / 청구 (402)

  • INSUFFICIENT_CREDIT — 가맹점 선불 잔액으로 가스/플랫폼 수수료를 커버할 수 없음. 대시보드에서 충전 후 재시도

레이트 리밋 (429)

  • TOO_MANY_REQUESTS — 게이트웨이 IP 레이트 리밋
  • TOO_MANY_ATTEMPTS — 동일 리소스에 반복된 실패 시도(예: OTP)가 스로틀 발동

스토리지 / 인프라 (500)

  • DATABASE_CONNECTION_ERROR — DB 도달 불가
  • DATABASE_QUERY_ERROR — 쿼리 플랜이 런타임에 실패
  • DATABASE_TRANSACTION_ERROR — 커밋/롤백 실패
  • REDIS_CONNECTION_ERROR — Redis 도달 불가
  • REDIS_OPERATION_ERROR — Redis 명령 실패
  • EXTERNAL_SERVICE_ERROR — 일반 서드파티 실패(아래에 버킷되지 않은 제공자)
  • TWILIO_SERVICE_ERROR — Twilio SMS / Verify 호출 실패
  • SENDGRID_SERVICE_ERROR — SendGrid 메일 전송 실패
  • INTERNAL_SERVER_ERROR — 예상치 못한 폴스루. 응답 Date 헤더 + X-RateLimit-*를 캡처하여 문의

가용성 (503)

  • SERVICE_UNAVAILABLE — 중요한 다운스트림이 비정상 보고 중. 백오프 + 재시도

검증 오류 (400)

문제가 잘못된 요청 본문인 경우 details는 배열이므로 오류를 필드에 다시 매핑할 수 있습니다.

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" }, { "field": "success_url", "message": "must be a valid https URL" } ] }

인증 오류 (401)

INVALID_SIGNATURE는 세 가지 별개의 실패 모드를 포함합니다 — 구분하는 유일한 방법은 양분 탐색입니다.

  • 잘못된 시크릿 키(env 변수 재확인)
  • 타임스탬프 드리프트 > 5분(NTP 동기화)
  • 잘못된 정규 문자열(가장 흔한 경우: \n 구분자 누락, 또는 보낸 것과 다른 파싱-후-재문자열화된 본문 서명)

정확한 서명 알고리즘은 인증을 참조하세요.

레이트 리밋 (429)

표면제한
모든 게이트웨이 경로(/b2b/v1/* 포함)게이트웨이의 IP당 — 500 req/min, 공유 버킷. 가맹점당이 아님.
공개 체크아웃(/checkout/*)더 엄격한 서브 버킷 — IP당 20 req/min

X-RateLimit-LimitX-RateLimit-Remaining모든 게이트웨이 레이트 리밋 응답(성공뿐 아니라)에 노출됩니다. IP에 대한 실시간 예산으로 다루세요.

레이트 리밋 응답은 Retry-After 헤더(윈도우가 재설정될 때까지의 초)를 포함합니다 — 이를 준수하세요. 폴백으로 지터를 사용한 백오프 — 1s 베이스 + 30s까지 지수적 증가 — 를 적용하세요.

레이트 리밋은 변경될 수 있습니다. 타당한 이유로 이에 도달하는 경우(예: 큰 과거 범위 리컨실리에이션), 문의하세요 — 벌크 엔드포인트는 로드맵에 있습니다.

잔액 부족 (402)

INSUFFICIENT_CREDIT(HTTP 402 Payment Required)는 가맹점의 선불 잔액이 수행하려는 작업에 대한 다음 가스 및 플랫폼 수수료를 커버할 수 없음을 의미합니다 — 일반적으로 암호화폐 결제 정산 또는 가스 후원 온체인 액션 실행. 가맹점 대시보드(Billing → Add credit)에서 충전한 후 작업을 재시도하세요. 진행 중인 작업은 대기 후 잔액이 클리어되면 자동으로 재개됩니다.

서버 오류 (5xx)

500은 요청을 처리할 수 없었음을 의미합니다. 백오프로 재시도하세요 — idempotency_key는 원래 요청이 부분적으로 성공했더라도 이중 청구를 방지합니다.

재시도가 1분 이내에 회복되지 않으면 구매자에게 일반적인 “결제 일시 사용 불가” 메시지를 노출하고 실패한 엔드포인트, 가맹점 ID, 응답 Date 헤더와 X-RateLimit-* 값, 대략적인 요청 시간을 지원팀에 문의하세요 — 이는 로그에서 관련 요청으로 피벗하기에 충분합니다.

웹훅 전달 오류

웹훅 전달은 별도의 실패 채널입니다 — 호출하는 것이 가맹점 서버가 아니므로 API 오류로 노출되지 않습니다. 전달이 non-2xx를 반환하거나 타임아웃되면, 0s, 1min, 5min, 15min, 1h, 6h(총 6회 시도 — 웹훅 → 개요와 동일한 스케줄)에서 지수 백오프 재시도를 위해 큐에 들어갑니다. 전달별 전체 이력은 대시보드의 Developers → Webhooks → [endpoint] → Delivery log 패널에 표시됩니다. 여섯 번째 시도가 실패하면 전달이 데드 레터링됩니다 — 대시보드는 서버가 정상화된 후 수동으로 재생할 수 있는 “Failed” 배지를 노출합니다.