> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meterengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 결제 취소

> 승인된 결제를 전부 또는 일부 취소하는 방법과 취소 뒤 인보이스와 수납 상태가 어떻게 되는지 설명합니다.

필요 권한: 취소 요청은 admin(전체 관리), 취소 내역 조회는 read(조회)

결제 취소는 승인된 결제 시도의 금액을 토스페이먼츠에 돌려 달라고 요청하는 일입니다. 한 결제를 여러 번 나눠 일부씩 취소할 수 있고, 남은 금액을 한 번에 전부 취소할 수도 있습니다.

> [!WARNING]
>
> 결제를 취소해도 확정 인보이스의 금액(`supply_amount`, `tax_amount`, `total_amount`)은 바뀌지 않습니다. 취소액은 인보이스와 따로 기록됩니다. 확정한 인보이스를 고치는 방법은 없습니다. 자세한 내용은 [확정 뒤 바뀌는 것과 안 바뀌는 것](https://docs.meterengine.com/billing/after-finalization.md)을 보세요.

## 취소할 수 있는 결제

* 결과가 `done`(승인)인 결제 시도만 취소할 수 있습니다. 결과 확인 중(`pending`)이거나 실패한 시도는 취소할 수 없습니다.
* 남은 결제 금액은 시도 금액에서 이미 취소를 마친(`done`) 금액을 뺀 값입니다. 그보다 큰 금액은 취소할 수 없습니다.
* 한 결제 시도에 결과 확인 중인 취소가 있는 동안에는 새 취소를 요청할 수 없습니다.
* 취소는 그 결제를 요청할 때 쓴 토스페이먼츠 키로 요청됩니다. API 키의 모드가 그 결제의 결제 모드와 다르면 거절됩니다.

> [!WARNING]
>
> 취소는 되돌릴 수 없고, 취소한 인보이스로 다시 결제를 받을 방법도 없습니다. 일부 취소든 전액 취소든 그 인보이스에는 승인된 결제가 남아 있으므로 새 결제를 요청하면 409 `invoice_already_paid`로 거절됩니다. 금액을 다시 받아야 한다면 MeterEngine 밖에서 처리해야 합니다.

## 콘솔에서 취소하기

**1. 수납 상세로 들어간다**

'수납과 취소' 화면에서 달을 고르고 고객 이름을 누릅니다.

**2. 취소할 승인 결제를 찾는다**

승인된 결제마다 '승인 결제 취소' 구역이 있고, 승인액, 취소 완료, 잔액과 취소 내역이 나옵니다.

**3. 취소 방식을 고른다**

남은 금액 전부를 돌려주려면 '잔액 전액 취소'를, 일부만 돌려주려면 '일부 금액 취소'를 누릅니다. '일부 금액 취소'에서는 '취소 금액 (원)'에 잔액보다 작은 금액을 넣습니다. 잔액 전부는 '잔액 전액 취소'로만 취소합니다.

**4. 사유를 적고 요청한다**

'취소 사유'를 적고 요청 버튼을 누릅니다. 일부 취소는 '입력한 금액 취소 요청', 전액 취소는 잔액이 앞에 붙은 '전액 취소 요청' 버튼입니다. 사유는 토스페이먼츠에 그대로 전달됩니다.

결과가 바로 확인되지 않으면 '취소 결과 확인 중'이 표시됩니다. 확정될 때까지 추가 취소를 받지 않으니 잠시 뒤 내역을 새로 조회하세요.

## API로 취소하기

**1. 승인된 결제 시도의 id를 찾는다**

`GET /v1/invoices/{id}/payment-attempts`로 인보이스의 결제 시도를 조회하고 `status`가 `done`인 시도의 `id`를 고릅니다.

```bash cURL theme={null}
curl "https://meterengine.com/v1/invoices/7b0c2f4e-5a1d-4c8e-9f3b-2d6a8e1c4b70/payment-attempts" \
  -H "Authorization: Bearer me_test_xxxxxxxx"
```

**2. 멱등 키를 만들어 저장한다**

취소 요청마다 UUID를 하나 만들어 도입사 쪽에 저장합니다. 응답을 받지 못해 다시 보낼 때 같은 값을 씁니다.

**3. 취소를 요청한다**

`POST /v1/payment-attempts/{id}/cancellations`에 사유와 금액을 보냅니다. `amount`를 빼면 남은 결제 금액 전부를 취소합니다.

```bash cURL theme={null}
curl -X POST "https://meterengine.com/v1/payment-attempts/b2f5d8e3-7c4a-4b9f-8d2e-6a3c9f8b1d24/cancellations" \
  -H "Authorization: Bearer me_test_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9a7c3e1f-2b4d-4f6a-8c0e-1d3f5b7a9c2e" \
  -d '{"amount": 11000, "reason": "사용량 정정에 따른 일부 환불"}'
```

```javascript Node theme={null}
// 이 취소에 만들어 저장해 둔 UUID. 다시 보낼 때도 같은 값을 쓴다.
const cancellationKey = "9a7c3e1f-2b4d-4f6a-8c0e-1d3f5b7a9c2e";
const res = await fetch(
  "https://meterengine.com/v1/payment-attempts/b2f5d8e3-7c4a-4b9f-8d2e-6a3c9f8b1d24/cancellations",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer me_test_xxxxxxxx",
      "Content-Type": "application/json",
      "Idempotency-Key": cancellationKey,
    },
    body: JSON.stringify({ amount: 11000, reason: "사용량 정정에 따른 일부 환불" }),
  },
);
const cancellation = await res.json();
```

**4. 결과를 확인한다**

응답은 결과와 관계없이 201이고 `status`로 결과를 구분합니다.

```json 응답 201 theme={null}
{
  "id": "c3a6e9f4-8d5b-4cae-9e3f-7b4d0a9c2e35",
  "payment_attempt_id": "b2f5d8e3-7c4a-4b9f-8d2e-6a3c9f8b1d24",
  "mode": "test",
  "amount": 11000,
  "reason": "사용량 정정에 따른 일부 환불",
  "status": "done",
  "failure_code": null,
  "failure_message": null,
  "requested_at": "2026-11-05T10:00:00+09:00",
  "completed_at": "2026-11-05T10:00:01+09:00"
}
```

| status | 뜻 | 할 일 |
| - | - | - |
| `done` | 토스페이먼츠가 취소했습니다 | 없음 |
| `failed` | 토스페이먼츠가 취소를 거절했습니다. `failure_code`와 `failure_message`가 있습니다 | 사유를 확인하고, 다시 요청하려면 새 멱등 키로 보냅니다 |
| `pending` | 토스페이먼츠 응답으로 결과를 확인하지 못했습니다 | 같은 요청을 새 키로 보내지 말고 결과가 정해질 때까지 내역을 조회합니다 |

결과 확인 중인 취소는 MeterEngine이 토스페이먼츠에 다시 물어 `done`이나 `failed`로 정합니다. 요청하고 10분이 지나도 토스페이먼츠에 맞는 취소 거래가 없으면 `failed`와 `NOT_FOUND_CANCELLATION`으로 끝납니다. 확인 작업의 주기는 [시간 규칙](https://docs.meterengine.com/concepts/time-rules.md)에 있습니다.

`failure_code`에는 토스페이먼츠의 취소 거절 코드(예: `NOT_CANCELABLE_AMOUNT`)가 그대로 들어가고, 아래 값은 MeterEngine이 정한 것입니다.

| failure\_code | 뜻 |
| - | - |
| `HTTP_<상태>` | 토스페이먼츠 거절 응답에 코드가 없어 HTTP 상태를 남겼습니다 |
| `NOT_FOUND_CANCELLATION` | 요청하고 10분이 지나도 토스페이먼츠 결제 조회에 이 취소에 맞는 거래가 없었습니다. 토스페이먼츠 코드가 아닙니다 |
| `toss_payments_credential_missing` | 토스페이먼츠를 부르기 전에 그 결제 모드의 키가 없었습니다 |

취소 내역은 `GET /v1/payment-attempts/{id}/cancellations`로 조회합니다. 요청 시각이 늦은 것부터 `payment_cancellations` 배열로 나옵니다.

## 멱등 키

`Idempotency-Key`는 선택이지만 보내기를 권합니다. 키 없이 보낸 요청이 응답 없이 끊긴 뒤 다시 보내면, 첫 요청이 이미 취소됐을 때 남은 금액에서 한 번 더 취소될 수 있습니다. 키 형식(UUID만)과 충돌 code, 다른 API와 다른 점은 [멱등 규칙](https://docs.meterengine.com/api-basics/idempotency.md)에 있습니다. 취소에서 따로 챙길 것은 아래와 같습니다.

* 같은 키로 다시 보내면 처음 만든 취소를 지금 상태 그대로 돌려줍니다. 처음 것이 `failed`였으면 `failed`가 그대로 돌아오니, 다시 취소하려면 새 키를 씁니다.
* `amount`를 뺀 요청과 같은 금액을 적은 요청은 다른 요청으로 봅니다. 다시 보낼 때는 처음 본문을 그대로 보냅니다.

## 오류

| 상태 | code | 뜻 |
| - | - | - |
| 400 | `validation_error` | 경로의 id나 `Idempotency-Key`가 UUID가 아니거나, `amount`가 양수가 아니거나, `reason`이 비었거나 200자를 넘습니다 |
| 400 | `malformed_request_body` | 본문을 JSON으로 읽을 수 없거나 `amount`가 정수가 아닙니다 |
| 404 | `payment_attempt_not_found` | 그런 결제 시도가 없습니다. 다른 업무 환경의 시도도 이렇게 보입니다 |
| 409 | `payment_attempt_not_cancelable` | 시도가 `done`이 아닙니다 |
| 409 | `payment_attempt_fully_canceled` | 이미 전액 취소해 남은 결제 금액이 0원입니다 |
| 409 | `payment_cancellation_in_progress` | 이 시도에 결과 확인 중인 취소가 있습니다 |
| 409 | `payment_cancellation_amount_exceeded` | `amount`가 남은 결제 금액보다 큽니다 |
| 409 | `payment_cancellation_idempotency_conflict` | 같은 `Idempotency-Key`를 다른 취소 요청에 썼습니다 |
| 409 | `payment_mode_mismatch` | API 키의 모드가 취소할 결제의 모드와 다릅니다 |

오류 응답 형식은 [오류 응답과 X-Request-ID](https://docs.meterengine.com/api-basics/errors.md)에 있습니다.

## 취소 뒤 상태

취소를 마치면 인보이스별 결제 상태(`GET /v1/invoice-payments`)의 `canceled_amount`가 늘고 `status`가 일부 취소면 `partially_canceled`(부분 취소), 전액 취소면 `canceled`(전액 취소)가 됩니다. 수납 상태 값 전체는 [수납 상태와 대사 확인](https://docs.meterengine.com/payments/reconciliation.md)에 있습니다.

취소가 `done`이 되면 `payment_canceled` 웹훅이 갑니다. `amount`에는 이번 취소 금액이 실립니다. 형식은 [웹훅 이벤트 유형과 data](https://docs.meterengine.com/webhooks/events.md)에 있습니다.

고객 조회 링크 화면에 취소가 어떻게 보이는지는 [고객에게 인보이스 보여 주기](https://docs.meterengine.com/payments/invoice-links.md)에 있습니다.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.