서버 연동

결제 취소

취소와 환불 요청을 같은 서버 호출 흐름으로 처리해요.

이 문서는 서버에서 결제 취소 API를 호출하는 방법을 설명해요. 주문 취소, 고객 환불, 오결제 처리 시 전액 취소와 부분 취소를 서버에서 처리해야 해요.

영상으로 보기

결제 취소하기: 전체취소와 부분취소 · 9분 16초

API 엔드포인트

POSThttps://api.bootpay.co.kr/v2/cancelBasic Auth

요청 파라미터

파라미터 타입 필수 설명
receipt_id String 필수 취소할 결제의 영수증 ID
cancel_price Integer 선택 부분 취소 금액 (미입력 시 전액 취소)
cancel_tax_free Integer 선택 취소 면세 금액. 미입력 시 취소 비율만큼 자동 안분돼요
cancel_deposit_price Integer 선택 취소할 컵보증금 금액
cancel_id String 선택 가맹점 취소 고유 ID (중복 방지)
cancel_username String 선택 취소자명
cancel_message String 선택 취소 사유
cancel_requester String 선택 취소 요청자 정보
refund Object 선택 가상계좌 환불 계좌 정보 (bank_code, bank_account, bank_username)
items Array 선택 취소 후 남는 상품 목록. 원스토어 서드파티 연동에서 부분취소할 때는 필수예요
cancel_tax_free를 생략하면

cancel_tax_free를 보내지 않으면 서버가 면세금액 × (취소요청금액 ÷ 현재 결제금액) 만큼 자동으로 안분해 취소해요. 면세 금액을 정확히 지정해야 하는 건이라면 직접 값을 넣어요.

cancel_id를 생략하면 멱등성이 없어요

cancel_id를 보내지 않으면 서버가 매 요청마다 UUID를 새로 생성해요. 즉 같은 요청을 두 번 보내면 두 번 취소돼요. 네트워크 재시도까지 감안해 중복 취소를 막으려면 가맹점이 직접 cancel_id를 지정​​해야 해요. 이미 쓴 cancel_id로 다시 요청하면 RC_CANCEL_ID_ALREADY_EXIST(2029)로 거절돼요.

코드 예제

import { Bootpay } from '@bootpay/backend-js'

Bootpay.setConfiguration({
    client_key: '[ Client Key ]',
    secret_key: '[ Secret Key ]'
})

try {
    const response = await Bootpay.cancelPayment({
        receipt_id: '[ receipt_id ]',
        cancel_price: 1000,
        cancel_tax_free: 0,
        cancel_id: '[ 가맹점 취소 고유 ID ]',
        cancel_username: '홍길동',
        cancel_message: '고객 요청에 의한 취소'
    })
    console.log(response)
} catch (e) {
    console.log(e)
}javascript

응답

취소 요청 결과도 status를 기준으로 처리해요. status: 20이면 전액 취소가 완료된 상태예요.

응답의 price는 원래 결제금액이 아니라 취소분을 뺀 잔액​(원 결제금액 − 누적 취소금액)이에요. 그래서 전액 취소가 끝나면 price0이 되고, 취소한 금액은 cancelled_price에 누적돼요.

{
  "receipt_id": "6244f60c1fc19202e42e8c4e",
  "order_id": "1648686604470",
  "price": 0,
  "cancelled_price": 1000,
  "status": 20,
  "status_locale": "결제취소완료",
  "cancelled_at": "2025-01-16T10:30:00+09:00"
}json
부분취소는 status가 20이 되지 않아요

잔액이 남는 부분취소는 결제 상태를 그대로 유지해요. 즉 응답의 status1(결제완료) 이고, status: 20은 잔액이 0이 되는 전액 취소에서만 나와요. 부분취소 여부는 status가 아니라 cancelled_price > 0 && price > 0 으로 판정해요.

실패 응답의 payload

취소 실패 응답의 payload에는 PG가 내려준 원본 응답이 그대로 담겨요. 필드 이름과 구조는 PG마다 다르므로 고정 스키마를 가정하지 말고, 운영 디버깅용으로 통째로 로깅해요. pg_error_code에는 PG 응답 코드가 들어와요.

응답 필드

취소 성공 응답은 결제 조회 응답과 같은 구조예요. 자주 쓰는 필드는 아래와 같아요.

필드 타입 설명
receipt_id String Bootpay 영수증 ID
order_id String 가맹점 주문번호
price Number 취소분을 뺀 잔액 (전액 취소 시 0)
tax_free Number 취소분을 뺀 잔여 면세 금액
cancelled_price Number 누적 취소 금액
cancelled_tax_free Number 누적 취소 면세 금액
order_name String 주문명
company_name String 가맹점명
currency String 통화 (KRW 등)
pg String PG명
method / method_symbol String 결제수단 한글명 / 영문 심볼
method_origin / method_origin_symbol String 간편결제 내부에서 실제 사용된 원 결제수단
status Number 결제 상태 (전액취소 20, 부분취소 후 1)
status_locale String 상태 한글 표기
purchased_at String 결제 완료 시각
cancelled_at String 전액 취소 시각. 부분취소만 있었던 건에는 담기지 않아요
requested_at String 결제 요청 시각
receipt_url String 영수증 URL
sandbox Boolean 샌드박스 결제 여부
metadata Object 결제 요청 시 넣은 메타데이터
cancel_id String 요청한 취소 고유 ID (API 버전 2.5 이상)
last_cancelled_at String 마지막 취소 시각 (API 버전 2.5 이상)
값이 없는 필드는 빠져요

응답은 값이 비어 있는 필드를 제거한 뒤 내려와요. 필드 존재를 전제로 파싱하면 안 돼요.

에러 코드

공통 에러

인증·권한 관련 에러는 에러 코드표를 참고해요.

코드 메시지 대처 방법
RC_NOT_FOUND (2000) 영수증 정보를 찾지 못했습니다 receipt_id가 올바른지 확인해요
RC_ALREADY_CANCELLED (2027) 이미 취소 처리된 결제이다 결제 상태를 먼저 조회한다
RC_NOT_ABLE_CANCEL (2028) 결제 완료 상태가 아니라 취소할 수 없다 결제 상태를 확인해요
RC_CANCEL_PRICE_NOT_ZERO (2051) 취소금액이 0보다 커야 한다 cancel_price를 0보다 큰 값으로 설정해요
RC_CANCEL_PRICE_OVER (2052) 요청된 취소금액이 취소 가능 금액보다 큽니다. 남은금액: {잔액}, 취소요청금액: {요청금액} — 실제 금액이 런타임에 채워져요 기준은 원 결제금액이 아니라 취소 가능 잔액​(price)이에요. 조회 응답의 price를 확인 후 재시도해요
RC_CANCEL_TAX_FREE_OVER (2053) 요청된 면세 취소 금액이 취소 가능한 면세 금액보다 큽니다. 취소가능한 면세금액: {잔여 면세}, 요청된 면세취소금액: {요청 면세} — 실제 금액이 런타임에 채워져요 기준은 잔여 면세 금액​(tax_free)이에요. cancel_tax_free를 확인해요
RC_CANCEL_ID_ALREADY_EXIST (2029) 동일한 cancel_id로 이미 취소 요청됨 다른 cancel_id를 사용해요
RC_PM_CANCEL_NOT_SUPPORT (2030) PG 결제수단에서 취소 API 미지원 PG사에 취소 지원 여부를 확인해요
RC_PM_P_CANCEL_NOT_SUPPORT (2031) PG 결제수단에서 부분취소 미지원 전액 취소를 시도하거나 PG사에 문의해요
RC_CANCEL_METHOD_NOT_FOUND (2033) 취소 구현이 되지 않은 PG이다 PG사에 문의해요
RC_CANCEL_SERVER_ERROR (2032) 결제 취소 오류 잠시 후 재시도하거나 부트페이에 문의해요
RC_CANCEL_CRITICAL_ERROR (2086) 결제 취소 중 치명적 오류 발생 부트페이 관리자에 문의해요
RC_NOT_ISSUED (2039) 가상계좌 입금 대기 상태가 아닙니다. 가상계좌 발급취소(입금 전 발급 철회) 전용 에러예요. 입금이 끝난 건은 일반 취소 API로 처리해요
RC_ISSUE_CANCEL_ERROR (2066) 가상계좌 발급 취소 처리 중 오류 (실패 원인이 메시지에 실려요) 메시지의 원인을 확인하고 재시도하거나 부트페이에 문의해요
RC_ISSUE_CANCEL_METHOD_NOT_FOUND (2067) 가상계좌 발급 취소 API를 지원하지 않는 PG입니다. 해당 PG는 발급취소를 지원하지 않아요. 입금 기한 만료를 기다리거나 PG사에 문의해요

부분 취소는 일부 PG사에서 지원하지 않을 수 있어요. 부분 취소 미지원 PG사에서는 전액 취소만 가능해요.

부분취소·전표 매입 후 취소 시 안내

전체취소는 즉시 처리되어 카드사에서 카드 소유주에게 취소 알림이 바로 발송돼요. 그러나 부분취소 또는 전표 매입 후 취소​(후취소)의 경우, 영업일 기준 3~5일 후 취소 정보가 카드 소유주에게 전달돼요. 이 기간 동안 고객이 취소 여부를 인지하기 어려울 수 있으므로, 취소 처리 후 고객에게 별도로 안내하는 것을 권장해요.

결제수단별 취소 정책

결제수단 부분취소 취소 가능 기간 비고
카드 O 180일 이내 당일 결제: 즉시 취소 / 전표 매입 후: 영업일 3~5일 소요
계좌이체 O 180일 이내 즉시 처리
가상계좌 일부 PG만 1년 이내 CMS 이체 특약 필요, 취소 시 약 300원 수수료 발생
휴대폰 X 당월만 이전 달 결제는 취소 불가
네이버페이 O 3년(1,095일) 이내
카카오페이 O 카드 1년 / 머니 5년 이내
페이코 O 카드 180일 / 계좌 90일 이내
휴대폰 결제는 당월만 취소돼요

휴대폰 소액결제는 통신사 정산 주기 때문에 결제한 달(당월) 안에서만 취소할 수 있고, 부분취소도 지원하지 않아요. 달이 바뀐 뒤에는 PG를 통한 취소가 막히므로, 이런 건은 가맹점이 고객에게 직접 환불(계좌 송금 등)​​해야 해요. 같은 이유로, 표의 취소 가능 기간이 지났거나 가상계좌처럼 별도 특약이 없는 건도 PG 자동 취소가 불가능하니 직접 환불 흐름을 마련해 둬요.

가상계좌 환불

가상계좌 취소 시 refund 객체에 환불 계좌 정보(bank_code, bank_account, bank_username)를 반드시 포함해야 해요. 환불 계좌 전달이 구현된 PG는 이니시스, 웰컴페이먼츠, 나이스페이먼츠, KICC(이지페이), 키움페이, 모빌리언스예요. KCP·다날은 구현돼 있지 않아요.

취소 기한 초과 시

결제수단별 취소 가능 기간이 경과한 건은 PG사를 통한 자동 취소가 불가능해요. 가맹점에서 직접 고객에게 환불 처리해야 해요.