/** * @file lib/kis/error-codes.ts * @description KIS FAQ 오류코드(msg_cd) 문구를 공통으로 해석하는 유틸입니다. * @see https://apiportal.koreainvestment.com/faq-error-code 한국투자증권 공식 오류코드 기준 */ export const KIS_ERROR_CODE_REFERENCE_URL = "https://apiportal.koreainvestment.com/faq-error-code"; const KIS_ERROR_CODE_MESSAGE_MAP = { EGW00001: "일시적 오류가 발생했습니다.", EGW00002: "서버 에러가 발생했습니다.", EGW00003: "접근이 거부되었습니다.", EGW00004: "권한을 부여받지 않은 고객입니다.", EGW00101: "유효하지 않은 요청입니다.", EGW00102: "AppKey는 필수입니다.", EGW00103: "유효하지 않은 AppKey입니다.", EGW00104: "AppSecret은 필수입니다.", EGW00105: "유효하지 않은 AppSecret입니다.", EGW00106: "redirect_uri는 필수입니다.", EGW00107: "유효하지 않은 redirect_uri입니다.", EGW00108: "유효하지 않은 서비스구분(service)입니다.", EGW00109: "scope는 필수입니다.", EGW00110: "유효하지 않은 scope 입니다.", EGW00111: "유효하지 않은 state 입니다.", EGW00112: "유효하지 않은 grant 입니다.", EGW00113: "응답구분(response_type)은 필수입니다.", EGW00114: "지원하지 않는 응답구분(response_type)입니다.", EGW00115: "권한부여 타입(grant_type)은 필수입니다.", EGW00116: "지원하지 않는 권한부여 타입(grant_type)입니다.", EGW00117: "지원하지 않는 토큰 타입(token_type)입니다.", EGW00118: "유효하지 않은 code 입니다.", EGW00119: "code를 찾을 수 없습니다.", EGW00120: "기간이 만료된 code 입니다.", EGW00121: "유효하지 않은 token 입니다.", EGW00122: "token을 찾을 수 없습니다.", EGW00123: "기간이 만료된 token 입니다.", EGW00124: "유효하지 않은 session_key 입니다.", EGW00125: "session_key를 찾을 수 없습니다.", EGW00126: "기간이 만료된 session_key 입니다.", EGW00127: "제휴사번호(corpno)는 필수입니다.", EGW00128: "계좌번호(acctno)는 필수입니다.", EGW00129: "HTS_ID는 필수입니다.", EGW00130: "유효하지 않은 유저(user)입니다.", EGW00131: "유효하지 않은 hashkey입니다.", EGW00132: "Content-Type이 유효하지 않습니다.", EGW00201: "초당 거래건수를 초과하였습니다.", EGW00202: "GW라우팅 중 오류가 발생했습니다.", EGW00203: "OPS라우팅 중 오류가 발생했습니다.", EGW00204: "Internal Gateway 인스턴스를 잘못 입력했습니다.", EGW00205: "credentials_type이 유효하지 않습니다.(Bearer)", EGW00206: "API 사용 권한이 없습니다.", EGW00207: "IP 주소가 없거나 유효하지 않습니다.", EGW00208: "고객유형(custtype)이 유효하지 않습니다.", EGW00209: "일련번호(seq_no)가 유효하지 않습니다.", EGW00210: "법인고객의 경우 모의투자를 이용할 수 없습니다.", EGW00211: "고객명(personalname)은 필수 입니다.", EGW00212: "휴대전화번호(personalphone)는 필수 입니다.", EGW00213: "제휴사명(corpname)은 필수 입니다. / 모의투자 tr이 아닙니다.", EGW00300: "Gateway 라우팅 오류가 발생했습니다.", EGW00301: "연결 시간이 초과되었습니다. 직전 거래를 반드시 확인하세요.", EGW00302: "거래시간이 초과되었습니다. 직전 거래를 반드시 확인하세요.", EGW00303: "법인고객에게 허용되지 않은 IP접근입니다.", EGW00304: "고객식별키(법인 personalSeckey, 개인 appSecret)가 유효하지 않습니다.", OPSQ0001: "호출 전처리 오류 입니다.", OPSQ0002: "없는 서비스 코드 입니다.", OPSQ0003: "호출 오류 입니다.", OPSQ0004: "호출 후처리 오류 입니다.", OPSQ0005: "호출 후처리 오류 입니다.", OPSQ0006: "호출 후처리 오류 입니다.", OPSQ0007: "호출 후처리(헤더설정) 오류 입니다.", OPSQ0008: "호출 후처리(MCI전송) 오류 입니다.", OPSQ0009: "호출 후처리(MCI수신) 오류 입니다.", OPSQ0010: "호출 결과처리(리소스 부족) 오류 입니다.", OPSQ0011: "호출 결과처리(리소스 부족) 오류 입니다.", OPSQ1002: "세션 연결 오류.", OPSQ2000: "ERROR : INPUT INVALID_CHECK_ACNO", OPSQ2001: "ERROR : INPUT INVALID_CHECK_MRKT_DIV_CODE", OPSQ2002: "ERROR : INPUT INVALID_CHECK_FIELD_LENGTH", OPSQ2003: "ERROR : SET_MCI_SEND_DATA", OPSQ3001: "ERROR : RESPONSE_ADDITEMTOOBJECT", OPSQ3002: "ERROR : GET_CALL_PARAM_MCI_SEND_DATA_LEN", OPSQ3004: "ERROR : OUT_STRING_ARRAY ALLOC FAILED", OPSQ9995: "JSON PARSING ERROR : body not found", OPSQ9996: "JSON PARSING ERROR : header not found", OPSQ9997: "JSON PARSING ERROR : invalid json format", OPSQ9998: "JSON PARSING ERROR : seq_no not found", OPSQ9999: "JSON PARSING ERROR : tr_id not found", OPSP0000: "SUBSCRIBE SUCCESS", OPSP0001: "UNSUBSCRIBE SUCCESS", OPSP0002: "ALREADY IN SUBSCRIBE", OPSP0003: "UNSUBSCRIBE ERROR(not found!)", OPSP0007: "SUBSCRIBE INTERNAL ERROR", OPSP0008: "MAX SUBSCRIBE OVER", OPSP0009: "SUBSCRIBE ERROR : mci send failed", OPSP0010: "SUBSCRIBE WARNNING : invalid appkey", OPSP0011: "invalid approval(appkey) : NOT FOUND", OPSP8991: "SUBSCRIBE ERROR : invalid tr_id", OPSP8992: "SUBSCRIBE ERROR : invalid tr_key", OPSP8993: "JSON PARSING ERROR : invalid tr_key", OPSP8994: "JSON PARSING ERROR : personalseckey not found", OPSP8995: "JSON PARSING ERROR : appsecret not found", OPSP8996: "ALREADY IN USE appkey", OPSP8997: "JSON PARSING ERROR : invalid tr_type", OPSP8998: "JSON PARSING ERROR : invalid custtype", OPSP8999: "resource not available (ALLOC_CALL_PARAM)", OPSP9990: "JSON PARSING ERROR : tr_key not found", OPSP9991: "JSON PARSING ERROR : input not found", OPSP9992: "JSON PARSING ERROR : body not found", OPSP9993: "JSON PARSING ERROR : internal error", OPSP9994: "JSON PARSING ERROR : INVALID appkey", OPSP9995: "JSON PARSING ERROR : resource not available", OPSP9996: "JSON PARSING ERROR : appkey", OPSP9997: "JSON PARSING ERROR : custtype not found", OPSP9998: "JSON PARSING ERROR : header not found", OPSP9999: "JSON PARSING ERROR : invalid json format", } as const; export interface KisErrorGuide { code: string; message: string; referenceUrl: string; } interface BuildKisErrorDetailParams { message?: string; msgCode?: string; extraMessages?: Array; } function normalizeKisErrorCode(msgCode?: string) { return msgCode?.trim().toUpperCase() ?? ""; } /** * @description KIS msg_cd를 공식 FAQ 문구와 매칭합니다. * @param msgCode KIS 응답 msg_cd * @returns 코드/문구/참고 URL 정보. 없으면 null * @see lib/kis/client.ts kisGet/kisPost 비즈니스 오류 메시지 구성 * @see features/kis-realtime/stores/kisWebSocketStore.ts 실시간 제어 오류 안내문 구성 */ export function getKisErrorGuide(msgCode?: string): KisErrorGuide | null { const code = normalizeKisErrorCode(msgCode); if (!code) return null; const message = KIS_ERROR_CODE_MESSAGE_MAP[ code as keyof typeof KIS_ERROR_CODE_MESSAGE_MAP ]; if (!message) return null; return { code, message, referenceUrl: KIS_ERROR_CODE_REFERENCE_URL, }; } /** * @description KIS 오류 조각(msg1/msg_cd/부가메시지)을 사람이 읽기 쉬운 한 줄로 합칩니다. * @param params 오류 문자열 조합 입력값 * @returns 중복 제거된 상세 메시지 * @see lib/kis/token.ts buildTokenIssueDetail 토큰 발급/폐기 오류 상세 구성 * @see lib/kis/approval.ts issueKisApprovalKey 승인키 발급 오류 상세 구성 */ export function buildKisErrorDetail({ message, msgCode, extraMessages = [], }: BuildKisErrorDetailParams) { const tokens = new Set(); for (const raw of [...extraMessages, message]) { const normalized = raw?.trim(); if (normalized) tokens.add(normalized); } const guide = getKisErrorGuide(msgCode); const normalizedCode = normalizeKisErrorCode(msgCode); if (guide) { tokens.add(`${guide.code} (${guide.message})`); } else if (normalizedCode) { tokens.add(normalizedCode); } return [...tokens].join(" / "); }