에러 코드
CROSS Pay 에러는 SDK/runtime error와 REST API error로 나뉩니다. 브라우저 SDK는 가능한 경우 다음 형태의 구조화된 에러를 전달합니다.
{
type: 'validation_error',
code: 'INVALID_REDIRECT_URL',
message: 'redirect URL is not allowed',
retryable: false,
requestId: 'req_...',
checkoutId: 'cs_...'
}
SDK error type
| type | 설명 | 기본 처리 |
|---|---|---|
network_error | CDN/runtime/API 네트워크 실패 | retryable이면 재시도 버튼 표시 |
validation_error | 요청 값, URL, currency/country 검증 실패 | 입력 수정 안내 |
payment_error | 결제 거절, 만료, 취소, 온체인 실패 | 결제수단 변경 또는 새 세션 유도 |
authentication_error | API key/session 인증 실패 | 서버 설정 점검 |
rate_limit_error | 요청 한도 초과 | Retry-After 또는 백오프 적용 |
server_error | CROSS Pay 또는 provider 서버 오류 | 잠시 후 재시도 |
sdk_error | SDK 로드/위젯 생명주기 오류 | 연동 코드와 CSP 점검 |
Network / SDK load
| code | retryable | 설명 |
|---|---|---|
NETWORK_TIMEOUT | ✓ | 네트워크 요청이 제한 시간 안에 끝나지 않음 |
NETWORK_OFFLINE | ✓ | 브라우저가 offline 상태 |
SDK_NOT_LOADED | ✓ | runtime script가 아직 준비되지 않음 |
WIDGET_LOAD_FAILED | ✓ | widget iframe/runtime 로드 실패 |
SSR_UNSUPPORTED | 서버 환경에서 브라우저 전용 결제 메서드 호출 | |
INVALID_SCRIPT_URL | 허용되지 않은 cross-pay.js URL | |
HTTP_NOT_ALLOWED | 운영 script/redirect URL에 HTTP 사용 |
SDK 클래 스 기준으로는 ScriptLoadFailedError, ScriptLoadTimeoutError, ScriptLoadAbortedError, NamespaceNotAvailableError, CrossPaySDKError를 구분할 수 있습니다.
Validation
| code | 설명 |
|---|---|
MISSING_PARAM | 필수 파라미터 누락 |
INVALID_FORMAT | 형식 검증 실패 |
INVALID_AMOUNT | 금액이 0 이하이거나 허용 범위를 벗어남 |
INVALID_CURRENCY | currency 형식이 유효하지 않음 |
UNSUPPORTED_CURRENCY | CROSS Pay가 처리하지 않는 currency |
UNSUPPORTED_COUNTRY | 지원하지 않거나 비활성화된 country |
INVALID_RESPONSE | runtime/API 응답 shape이 예상과 다름 |
INVALID_REDIRECT_URL | 위험하거나 allowlist에 없는 redirect URL |
Payment
| code | retryable | 설명 |
|---|---|---|
CHECKOUT_EXPIRED | checkout session TTL 만료. 새 POST /v1/payments 필요 | |
PAYMENT_DECLINED | provider가 결제를 거절 | |
PAYMENT_CANCELLED | 사용자가 결제를 취소하거나 창을 닫음 | |
USER_REJECTED | 사용자 취소를 SDK가 정규화한 코드 | |
PAYMENT_FAILED | 결제 실패 일반 | |
INSUFFICIENT_FUNDS | 잔액 부족 | |
POLLING_TIMEOUT | ✓ | 상태 확인이 제한 시간 내 완료되지 않음 |
POPUP_BLOCKED | 브라우저가 popup을 차단 | |
WALLET_INIT_FAILED | ✓ | embedded wallet 초기화 실패 |
WRONG_CHAIN | 지갑 네트워크가 결제 chain과 다름 | |
ONCHAIN_TX_FAILED | 온체인 트랜잭션 실패 또는 revert |
Widget lifecycle
| code | 설명 |
|---|---|
AMOUNT_NOT_SET | widgets.setAmount() 호출 전 submit 시도 |
WIDGET_NOT_MOUNTED | 결제수단 widget이 mount되지 않은 상태에서 submit/update 호출 |
WIDGET_LOAD_FAILED | widget iframe 로드 또는 bridge 준비 실패 |
REST API 대표 코드
REST API 에러 응답은 { code, message } 형태이며,
원본은 internal/api/errors.go (cross-pay-api) 에서 정의됩니다.
4xx (요청 측 오류)
| code | HTTP | 설명 |
|---|---|---|
BAD_REQUEST | 400 | 요청 body/query/header 검증 실패 |
INVALID_AMOUNT | 400 | 금액 형식 오류 또는 통화 소수점 초과 |
INVALID_CURRENCY | 400 | currency 코드가 유효하지 않거나 미지원 |
UNAUTHORIZED | 401 | merchant API key 누락 또는 유효하지 않음 |
FORBIDDEN | 403 | 다른 머천트의 리소스 접근 시도 |
NOT_FOUND | 404 | 대상 리소스를 찾을 수 없음 |
CHECKOUT_NOT_FOUND | 404 | checkout id를 찾을 수 없음 |
PAYMENT_METHOD_NOT_FOUND | 404 | 선택한 결제수단이 존재하지 않거나 비활성 |
CONFLICT | 409 | 일반 충돌 |
IDEMPOTENCY_CONFLICT | 409 | 같은 idempotency key를 다른 request body로 재사용 |
INVALID_CHECKOUT_STATE | 409 | terminal 상태의 checkout에 SelectQuote 호출 |
INVALID_PAYMENT_STATE | 409 | 현재 결제 상태에서 허용되지 않는 작업 (예: PAID cancel) |
INVALID_TRANSITION | 409 | 상태 전이 위반. 예: PROCESSING 결제 cancel — PG와 race 회피 |
CHECKOUT_EXPIRED | 410 | checkout TTL 만료 — 새 POST /v1/payments 필요 |
SESSION_GONE | 410 | on-chain session 종료 또는 payment terminal 상태 |
UNPROCESSABLE | 422 | 요청은 형식상 valid 하나 의미상 처리 불가 |
UNSUPPORTED_CURRENCY | 422 | 결제수단/국가 조합에서 지원하지 않는 통화 |
UNSUPPORTED_COUNTRY | 422 | 미등록 또는 비활성 국가 |
PAYMENT_TOKEN_NOT_AVAILABLE | 422 | 해당 체인의 토큰 매핑이 활성화되지 않음 |
PG_NOT_AVAILABLE | 422 | 선택한 결제수단의 PG가 해당 checkout에 사용 불가 |
REFUND_EXCEEDS_REMAINING | 409 | 환불 금액이 잔여 환불 가능액을 초과 |
REFUND_PROVIDER_UNAVAILABLE | 503 | PG의 환불 채널이 일시 사용 불가 |
RATE_LIMITED | 429 | 요청 한도 초과 |
5xx (서버 측 오류)
| code | HTTP | 설명 |
|---|---|---|
INTERNAL_ERROR | 500 | 서버 내부 오류 |
SERVER_ERROR | 500 | (legacy alias) INTERNAL_ERROR |
NOT_IMPLEMENTED | 501 | 라우트는 노출되어 있으나 미구현 |
PG_UPSTREAM_ERROR | 502 | provider upstream 오류 |
SERVICE_UNAVAILABLE | 503 | 일시적 서비스 가용성 부재 |
INVALID_TRANSITION vs INVALID_PAYMENT_STATE두 코드 모두 409 이지만 의도는 다릅니다.
INVALID_TRANSITION— 상태 머신 위반 (예:PROCESSING → CANCELLED시도). 자세한 라이프사이클은 Webhooks Overview.INVALID_PAYMENT_STATE— 작업 자체가 결제 상태에 부합하지 않음 (예:PAID에 대해 단순 lookup이 아닌 mutation 시도).
머천트는 두 코드를 별도 분기로 처리하기보다 "현 상태에서 불가" 로 한꺼번에 다루는 것이 일반적입니다.
FDS deny
/v1/checkout/**는 FDS 차단도 HTTP 200으로 응답합니다. 이때 body에는 Payment/Quote 객체가 없고 fds envelope만 포함됩니다.
{
"fds": {
"outcome": "DENY",
"code": "COUNTRY_BLOCKED",
"message": "Payment is blocked by risk policy",
"risk_level": "HIGH",
"risk_score": 92,
"rule": "country_allowlist"
}
}
SDK와 checkout UI는 HTTP status가 아니라 fds.outcome으로 분기해야 합니다. 알 수 없는 fds.code는 generic block으로 처리하세요.