결제 취소 API

결제 취소 API를 사용해서 승인된 결제를 취소하고 환불하는 기능을 구현해보세요.

API를 사용하기 위해 필요한 키 정보와 인증 방식, 보안에 대한 정보는 API 사용하기에서 자세히 알아보세요.

결제 취소 요청하기

승인된 결제를 취소하려면 결제 승인 요청 결과로 발급 받은 paymentKey와 결제 취소 이유인 cancelReason이 필요합니다.

결제 취소 API 엔드포인트에 paymentKey를 Path 파라미터로 추가하고 cancelReason은 Request Body 파라미터로 추가하세요.

요청
curl --request POST \
  --url https://api.tosspayments.com/v1/payments/WxdQtiCTz74W3Im2a_vqD/cancel \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --data '{"cancelReason":"고객이 취소를 원함"}'

응답으로 Payment 객체cancels 필드에 취소 객체가 배열로 돌아옵니다.

응답
{
"mId": "tosspayments",
"version": "1.4",
"transactionKey": "",
"paymentKey": "",
"orderId": "",
"orderName": "토스 티셔츠 외 2건",
"currency": "KRW",
"method": "카드",
"status": "CANCELED",
"requestedAt": "2022-01-01T11:31:29+09:00",
"approvedAt": "2022-01-01T11:31:51+09:00",
"useEscrow": false,
"cultureExpense": false,
"card": {
"company": "우리",
"number": "100212*****12",
"installmentPlanMonths": 0,
"isInterestFree": false,
"interestPayer": null,
"approveNo": "00000000",
"useCardPoint": false,
"cardType": "신용",
"ownerType": "개인",
"acquireStatus": "READY",
"receiptUrl": "https://dashboard.tosspayments.com/sales-slip?transactionId=bGqvzDSq5OoimabqhwIGRNk5Ks4A%2B2pzVwKxOP0WsjnZ6FZiUqVa4RbnVeqVlxsd&ref=PX"
},
"virtualAccount": null,
"transfer": null,
"mobilePhone": null,
"giftCertificate": null,
"foreignEasyPay": null,
"cashReceipt": null,
"discount": null,
"cancels": [
{
"cancelReason": "고객이 취소를 원함",
"canceledAt": "2022-01-01T11:32:04+09:00",
"cancelAmount": 10000,
"taxFreeAmount": 0,
"taxAmount": null,
"refundableAmount": 0
}
],
"secret": null,
"type": "NORMAL",
"easyPay": "토스페이",
"country": "KR",
"failure": null,
"totalAmount": 10000,
"balanceAmount": 0,
"suppliedAmount": 0,
"vat": 0,
"taxFreeAmount": 0
}
JSON

같은 paymentKey를 이용한 취소 요청이 1초에 2번 이상 연속으로 들어오면 첫 번째 요청만 처리됩니다.

부분 취소하기

결제 금액 중 일부만 취소하려면 cancelAmount에 취소할 금액을 추가해서 결제 취소 API를 요청합니다.

cancelAmount에 값을 넣지 않으면 전액 취소됩니다.

요청
curl --request POST \
  --url https://api.tosspayments.com/v1/payments/h__greVcS1OZxT1Dy_WBE/cancel \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --data '{"cancelReason":"고객이 취소를 원함","cancelAmount":1000}'

부분 취소를 여러 번 하면 아래와 같이 cancels 필드에 취소 객체가 여러 개 쌓입니다.

응답
// Payment 객체
{
// ...
"cancels": [
{
"cancelAmount": 1000,
"cancelReason": "고객이 취소를 원함",
"taxFreeAmount": 0,
"taxAmount": null,
"refundableAmount": 9000,
"canceledAt": "2022-01-01T23:23:52+09:00"
},
{
"cancelAmount": 1000,
"cancelReason": "고객이 다른 품목도 취소를 원함",
"taxFreeAmount": 0,
"taxAmount": null,
"refundableAmount": 8000,
"canceledAt": "2022-01-02T20:00:00+09:00"
}
]
// ...
}
JSON

Case 1. 가상계좌 결제를 취소할 때 환불 계좌 정보 추가하기

가상계좌 결제를 취소할 때는 환불 받을 고객의 계좌 정보를 취소 요청에 포함해야 합니다.

refundReceiveAccount에 환불 받을 고객의 계좌 정보를 포함해서 결제 취소를 요청하세요. 요청한 계좌로 취소 금액이 환불됩니다.

요청
curl --request POST \
  --url https://api.tosspayments.com/v1/payments/1Y49VfzAot_2KS-Ok61R7/cancel \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --data '{"cancelReason":"고객이 취소를 원함","cancelAmount":10000,"refundReceiveAccount":{"bank":"우리","accountNumber":"1000123456789","holderName":"김토페"}}'

응답은 다른 취소 요청과 동일하게 Payment 객체cancels로 돌아옵니다.

응답
{
"mId": "tvivarepublica",
"version": "1.4",
"transactionKey": "",
"paymentKey": "",
"orderId": "",
"orderName": "토스 티셔츠 외 2건",
"currency": "KRW",
"method": "가상계좌",
"status": "PARTIAL_CANCELED",
"requestedAt": "2022-01-01T11:48:53+09:00",
"approvedAt": "2022-01-01T11:49:35+09:00",
"useEscrow": false,
"cultureExpense": false,
"card": null,
"virtualAccount": {
"accountNumber": "X6505831718354",
"accountType": "일반",
"bank": "우리",
"customerName": "김토스",
"dueDate": "2022-01-03T11:48:53+09:00",
"expired": true,
"settlementStatus": "INCOMPLETED",
"refundStatus": "NONE"
},
"transfer": null,
"mobilePhone": null,
"giftCertificate": null,
"cashReceipt": null,
"discount": null,
"cancels": [
{
"cancelReason": "고객 변심",
"canceledAt": "2022-01-01T11:51:04+09:00",
"cancelAmount": 10000,
"taxFreeAmount": 0,
"taxAmount": null,
"refundableAmount": 0
}
],
"secret": null,
"type": "NORMAL",
"easyPay": null,
"country": "KR",
"failure": null,
"totalAmount": 10000,
"balanceAmount": 0,
"suppliedAmount": 0,
"vat": 0,
"taxFreeAmount": 0
}
JSON

Case 2. 결제 취소 요청을 더 안전하게 처리하고 싶을 때

환불할 수 있는 금액 정보인 refundableAmount 파라미터를 요청에 포함하면 취소 요청을 안전하게 처리할 수 있습니다.

취소 요청에 포함된 refundableAmount와 실제 환불할 수 있는 잔액 정보가 서로 다르면 응답으로 에러가 돌아옵니다. 아래 예시를 확인해보세요.

부분 취소에서 refundableAmount 사용하기

고객이 15,000원을 결제한 뒤 5,000원을 취소하고 싶습니다. 이 때 취소할 수 있는 금액은 전액이므로 결제 취소를 요청할 때 refundableAmount 값으로 15000을 넣습니다.

요청
curl --request POST \
  --url https://api.tosspayments.com/v1/payments/fPGxdC2rs4AoPcTk_-SeW/cancel \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --data '{"cancelReason":"고객이 취소를 원함","cancelAmount":5000,"refundReceiveAccount":{"bank":"우리","accountNumber":"1000123456789","holderName":"김토페"},"refundableAmount":15000}'

5,000원이 성공적으로 부분 취소되어 응답으로 돌아오는 refundableAmount는 원래 결제 금액인 15,000원에서 취소된 5,000원을 뺀 10,000원 입니다.

응답
{
"mId": "tosspayments",
"version": "1.4",
"transactionKey": "",
"paymentKey": "",
"orderId": "",
"orderName": "토스 티셔츠 외 2건",
"currency": "KRW",
"method": "카드",
"status": "CANCELED",
"requestedAt": "2022-01-01T11:31:29+09:00",
"approvedAt": "2022-01-01T11:31:51+09:00",
"useEscrow": false,
"cultureExpense": false,
"card": {
"company": "우리",
"number": "100212*****12",
"installmentPlanMonths": 0,
"isInterestFree": false,
"interestPayer": null,
"approveNo": "00000000",
"useCardPoint": false,
"cardType": "신용",
"ownerType": "개인",
"acquireStatus": "READY",
"receiptUrl": "https://dashboard.tosspayments.com/sales-slip?transactionId=bGqvzDSq5OoimabqhwIGRNk5Ks4A%2B2pzVwKxOP0WsjnZ6FZiUqVa4RbnVeqVlxsd&ref=PX"
},
"virtualAccount": null,
"transfer": null,
"mobilePhone": null,
"giftCertificate": null,
"foreignEasyPay": null,
"cashReceipt": null,
"discount": null,
"cancels": [
{
"cancelReason": "고객이 취소를 원함",
"canceledAt": "2022-01-01T11:32:04+09:00",
"cancelAmount": 5000,
"taxFreeAmount": 0,
"taxAmount": null,
"refundableAmount": 10000
}
],
"secret": null,
"type": "NORMAL",
"easyPay": "토스페이",
"country": "KR",
"failure": null,
"totalAmount": 15000,
"balanceAmount": 5000,
"suppliedAmount": 4545,
"vat": 455,
"taxFreeAmount": 0
}
JSON

이후 고객이 한 번 더 5,000원을 취소하려고 합니다. 앞서 돌아온 환불 가능한 잔액은 10,000원이므로 두 번째 취소를 할 때는 refundableAmount 값으로 10000을 넣어줍니다.

요청
curl --request POST \
  --url https://api.tosspayments.com/v1/payments/fPGxdC2rs4AoPcTk_-SeW/cancel \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --data '{"cancelReason":"고객이 다른 품목도 취소를 원함","cancelAmount":5000,"refundReceiveAccount":{"bank":"우리","accountNumber":"1000123456789","holderName":"김토페"},"refundableAmount":10000}'

응답으로 cancels에 결제 취소 기록이 돌아옵니다. 마지막에 쌓인 취소 기록의 refundableAmount 값은 전체 결제 금액인 15,000원에서 두 번의 부분 취소 금액을 뺀 5000원 입니다.

응답
{
// ...
"cancels": [
{
"cancelReason": "고객이 취소를 원함",
"canceledAt": "2022-01-01T11:32:04+09:00",
"cancelAmount": 5000,
"taxFreeAmount": 0,
"taxAmount": null,
"refundableAmount": 10000
},
{
"cancelReason": "고객이 다른 품목도 취소를 원함",
"canceledAt": "2022-01-02T20:00:00+09:00",
"cancelAmount": 5000,
"taxFreeAmount": 0,
"taxAmount": null,
"refundableAmount": 5000
}
],
// ...
}
JSON

이렇게 현재 환불할 수 있는 잔액을 refundableAmount 값으로 담아 보내고, 응답으로 돌아오는 값을 확인해서 결제 취소가 중복으로 요청되거나 결제된 금액보다 더 많이 취소하는 상황을 방지하는 데 활용할 수 있습니다.


결제 취소 API를 자세히 알아보세요

결제 취소 API를 직접 실행해보세요

내용이 도움 되셨나요?
  • 더 궁금한 내용이 있나요?
  • 코드 샘플을 참고하세요
  • 기술지원이 필요한가요?
    디스코드 채팅|이메일 보내기