# 결제 취소

가맹점 서버는 승인 완료된 결제를 전부 또는 일부 취소해야 할 때 [결제 취소 API](/api-reference/refund)를 호출합니다.

<Callout type="caution" title="point3 API는 서버에서 호출하세요">
  API 키를 가맹점 화면의 코드, URL 또는 메시지에 포함하지 마세요.
</Callout>

## 요청 전 확인

1. 취소할 금액과 세금 구성을 계산합니다.
2. 같은 취소 요청이 중복 처리되지 않도록 가맹점 주문과 요청 식별값을 저장합니다.

다음 조건에서는 새 취소를 요청할 수 없습니다.

- 기존 취소가 `PROCESSING` 상태인 경우
- 결제가 취소 가능 상태가 아니거나 취소 가능 잔액이 없는 경우
- 취소 총액·면세액·부가세가 각각의 취소 가능 잔액을 초과하는 경우
- 매일 23:30 이상 00:30 미만(KST)의 EOB 차단 시간대인 경우

## 금액과 사유

| 필드 | 요청 여부 | 규칙 |
| --- | --- | --- |
| `refundAmount` | 필수 | 1 이상의 정수인 취소 총액 |
| `refundTaxFreeAmount` | 필수 | 0 이상의 정수인 취소 면세 금액 |
| `refundVat` | 필수 | 0 이상의 정수인 취소 부가세 금액 |
| `reason` | 필수 | 최대 200자인 취소 사유 |

`refundTaxFreeAmount + refundVat`은 `refundAmount`를 초과할 수 없습니다.

결제 취소의 `refundAmount`, `refundTaxFreeAmount`, `refundVat`는 취소하려는 부분의 금액과 세금 구성을 반영해야 하며, 결제 세션 생성과 달리 자동으로 세금 값을 계산하지 않으므로 호출 시 주의해야 합니다. 원 결제의 구성은 [세금 처리](/tax-processing)를 참고하세요.

```json title="요청 예시"
{
  "refundAmount": 10000,
  "refundTaxFreeAmount": 0,
  "refundVat": 909,
  "reason": "고객 요청"
}
```

<Callout type="caution" title="취소 금액과 세금 구성은 직접 계산하세요">
  결제 취소 API는 `refundAmount`, `refundTaxFreeAmount`, `refundVat`를 자동으로 계산하지 않습니다. 원 결제의 세금 구성과 취소할 금액을 확인한 뒤 요청하세요.
</Callout>

## 선택적 멱등 키

`Idempotency-Key` 헤더는 선택 사항이며 최대 300자입니다. 키는 가맹점별로 24시간 동안 중복 요청 방지에 사용됩니다. 서버는 같은 키를 다시 받으면 기존 응답을 재생하지 않고 `409 REFUND_DUPLICATE_REQUEST`를 반환합니다.

하나의 논리적 취소 요청에는 하나의 키만 만들고 요청 정보와 함께 저장하세요. 타임아웃이나 응답 유실처럼 결과가 불확실할 때 새 키로 바꾸면 같은 취소가 다시 실행될 수 있습니다.

<Callout type="caution" title="불확실한 결과를 무조건 다시 요청하지 마세요">
  `Idempotency-Key`는 기존 결과를 돌려주는 재실행 기능이 아닙니다. `REFUND_TEMPORARY_UNAVAILABLE`, `REFUND_DUPLICATE_REQUEST`, 타임아웃 또는 응답 유실 뒤에는 새 키로 바꾸거나 같은 취소 생성 요청을 자동으로 다시 호출하지 마세요. 먼저 상태 조회 API를 사용하세요.
</Callout>

## 결과 처리

| 응답 | 처리 방법 |
| --- | --- |
| `200` | 응답의 `paymentSessionId`와 요청값을 확인하고 환불 항목 `id`, 금액, 세금, 시각을 저장한 뒤 취소 완료 처리 |
| `409` | `result.code`를 확인해 결과 미확정, 중복 요청 또는 정책상 요청 불가를 구분 |
| `422` | 취소가 명시적으로 거절된 상태로 처리하고 응답 오류 코드와 메시지 기록 |
| `400`, `401`, `404` | 요청값, 인증 정보, 결제 세션 연결을 확인하는 연동 오류로 처리 |
| `500`, `503`, 네트워크 오류 또는 응답 유실 | 성공·실패를 추정하지 않고 상태 조회 API로 확인 |

## 상태 조회와 처리 재개

취소 생성 결과가 불확실하거나 현재 취소 가능 금액을 확인해야 할 때 다음 조회 API를 호출합니다.

```http title="결제 취소 상태 조회"
GET https://api.point3.io/refunds/v1/{sessionId}
```

조회 응답은 원 결제 금액, 남은 취소 가능 금액, 새 취소 가능 여부와 개별 취소 이력을 반환합니다.

| 상태값 | 설명 |
| --- | --- |
| `status: fullyRefunded` | 원 결제 금액이 모두 취소된 상태 |
| `status: partiallyRefunded` | 요청된 부분 취소가 완료된 상태 |
| `status: refundable` | 처리 중이거나 완료된 취소가 없는 일반적인 상태 |
| `status: processing` | 취소가 모두 완료되지 않은 상태 |
| `refunds[].status: completed` | 해당 취소 항목이 완료 처리된 상태 |
| `refunds[].status: failed` | 해당 취소 항목이 실패한 상태 |

조회 결과에 `processing` 상태인 활성 취소가 있으면 다음 API로 기존 처리를 재개해야 합니다.

```http title="결제 취소 처리 재개"
POST https://api.point3.io/refunds/v1/{sessionId}/resume
```

상태 조회 후 재개 API를 호출하기 전에 취소 처리가 완료될 수 있습니다. 이 경우 별도 처리를 재개하지 않고 최신 취소 상태를 200으로 반환합니다.

### 주요 409 오류 코드

| `result.code` | 의미 | 처리 방법 |
| --- | --- | --- |
| `REFUND_TEMPORARY_UNAVAILABLE` | 외부 처리 결과 미확정. 일시 장애 시에는 `503`으로 반환 | 원래 키와 응답 식별값을 보존하고 상태 조회 후 처리 중인 항목만 재개 |
| `REFUND_DUPLICATE_REQUEST` | 동일 키를 24시간 안에 재사용 | 상태 조회로 기존 요청 결과를 확인하고 새 취소를 만들지 않음 |
| `REFUND_ACTIVE_REQUEST_EXISTS` | 현재 처리 중인 취소 존재 | 새 취소를 만들지 않고 상태 조회 후 기존 처리를 재개 |
| `REFUND_NOT_IN_REFUNDABLE_STATE` | 취소 가능한 상태가 아님 | 결제, 취소 상태를 확인하고 요청 종료 |
| `EOB_WINDOW_BLOCKED` | 23:30 이상 00:30 미만(KST)의 요청 차단 | 00:30 이후 별도의 새 취소 시도로 요청 |

전체 요청·응답 스키마는 [결제 취소 API 레퍼런스](/api-reference/refund)에서 확인하세요.
