구독 계약을 해지해요.
고객이 해지 요청
구독 계약의 중도 해지를 요청해요. 구독 설정에 따라 즉시 해지되거나 관리자 승인 후 해지돼요.
해지 처리 방식
| 설정 | 동작 | 설명 |
|---|---|---|
use_termination_approval: false |
즉시 해지 | 요청 즉시 구독 종료 |
use_termination_approval: true |
승인 대기 | 관리자 승인 후 해지 |
플로우
- 해지 요청 -> 승인 필요?
- 승인 필요? -> 즉시 해지 / 환불 처리 (false)
- 승인 필요? -> 승인 대기 (true)
- 승인 대기 -> 관리자 승인 / 해지 + 환불 (승인)
API 엔드포인트
PUT
https://api.bootapi.com/v1/order_subscriptions/:id/terminate/requestBasic Auth요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
id |
String | 필수 | 구독 계약 ID (URL 경로) |
reason |
String | 선택 | 중도 해지 사유 |
코드 예제
const { BootpayCommerce } = require('@bootpay/backend-js')
const commerce = new BootpayCommerce({
client_key: 'your-commerce-client-key',
secret_key: 'your-commerce-secret-key',
mode: 'production'
})
const response = await commerce.orderSubscription.terminateRequest(
'687a1b2c3d4e5f6789012345',
{ reason: '개인 사정으로 인한 해지 요청' }
)
console.log(response)javascriptfrom bootpay_backend import BootpayCommerce
commerce = BootpayCommerce(
client_key='your-commerce-client-key',
secret_key='your-commerce-secret-key',
mode='production'
)
response = commerce.order_subscription.terminate_request(
'687a1b2c3d4e5f6789012345',
reason='개인 사정으로 인한 해지 요청'
)
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("your-commerce-client-key", "your-commerce-secret-key");
$response = $commerce->orderSubscription->terminateRequest('687a1b2c3d4e5f6789012345', [
'reason' => '개인 사정으로 인한 해지 요청'
]);
print_r($response);phpimport kr.co.bootpay.store.BootpayStore;
import kr.co.bootpay.store.model.request.TokenPayload;
TokenPayload tp = new TokenPayload("your-commerce-client-key", "your-commerce-secret-key");
BootpayStore commerce = new BootpayStore(tp);
HashMap<String, Object> params = new HashMap<>();
params.put("reason", "개인 사정으로 인한 해지 요청");
String response = commerce.orderSubscription.terminateRequest("687a1b2c3d4e5f6789012345", params);
System.out.println(response);javacommerce = BootpayStore::Api.new('your-commerce-client-key', 'your-commerce-secret-key')
response = commerce.order_subscription_terminate_request(
'687a1b2c3d4e5f6789012345',
reason: '개인 사정으로 인한 해지 요청'
)
puts responserubycommerce := bootpay.NewCommerceApi("your-commerce-client-key", "your-commerce-secret-key")
response, err := commerce.OrderSubscription.TerminateRequest("687a1b2c3d4e5f6789012345", bootpay.OrderSubscriptionTerminateRequestParams{
Reason: "개인 사정으로 인한 해지 요청",
})
fmt.Println(response)gousing Bootpay.Commerce;
var commerce = new BootpayCommerceApi("your-commerce-client-key", "your-commerce-secret-key");
var response = await commerce.OrderSubscription.TerminateRequest("687a1b2c3d4e5f6789012345", new {
reason = "개인 사정으로 인한 해지 요청"
});
Console.WriteLine(response);csharp응답
성공 응답
{
"id": "687a1b2c3d4e5f6789012345",
"status": 3,
"request_type": 3,
"termination_fee": 0,
"refund_amount": 15000,
"terminated_at": "2025-07-11T15:00:00Z"
}json응답 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
id |
String | 구독 계약 고유 ID |
status |
Integer | 변경된 구독 상태 (3: 해지) |
request_type |
Integer | 요청 타입 (3: 중도해지) |
termination_fee |
Integer | 해지 수수료 |
refund_amount |
Integer | 예상 환불 금액 |
terminated_at |
String | 해지 처리 시각 |
에러 코드
공통 에러
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_TERMINATE_UNABLE |
이미 해지되었거나 해지할 수 없는 구독 상태예요 | 구독 상태를 확인해요 |
SUBSCRIPTION_TERMINATION_NOT_ALLOWED |
이미 중도해지 되었거나, 정책상 중도해지 요청을 할 수 없는 상태예요. | 구독 상태를 확인해요 |
SUBSCRIPTION_TERMINATION_REASON_REQUIRED |
중도해지 사유를 입력해야 해요. | reason 파라미터를 입력해요 |
use_termination 설정이 false인 구독 상품은 해지 요청이 불가능해요. 부트페이 관리자 상품 설정에서 구독 설정을 확인해요.
관리자가 해지 처리
관리자가 직접 구독 계약을 해지해요. 고객 요청 없이 관리자가 즉시 해지할 수 있으며, 수수료와 환불 금액을 직접 설정할 수 있어요.
이 API는 관리자(Supervisor) 권한이 필요해요. 해지된 구독은 복구할 수 없어요.
고객 요청 vs 관리자 직접 해지
| 구분 | 고객 요청 | 관리자 직접 해지 |
|---|---|---|
| API | 고객 해지 요청 | 이 API |
| 승인 과정 | 관리자 승인 필요 (설정에 따라) | 즉시 해지 |
| 수수료 설정 | 자동 계산 | 관리자가 직접 설정 가능 |
| 환불 금액 | 자동 계산 | 관리자가 직접 설정 가능 |
API 엔드포인트
POST
https://api.bootapi.com/v1/order_subscriptions/:order_subscription_id/terminateBasic Auth요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
order_subscription_id |
String | 필수 | 구독 계약 ID (URL 파라미터) |
reason |
String | 선택 | 해지 사유 |
termination_fee |
Number | 선택 | 해지 수수료 (관리자 직접 설정) |
last_bill_refund_price |
Number | 선택 | 마지막 회차 환불 금액 |
service_end_at |
String | 선택 | 서비스 종료일 (ISO 8601, 미입력 시 즉시) |
코드 예제
curl -X POST "https://api.bootapi.com/v1/order_subscriptions/{order_subscription_id}/terminate" \
-H "Authorization: Basic {base64(client_key:secret_key)}" \
-H "Content-Type: application/json" \
-d '{
"reason": "서비스 정책 변경에 따른 해지",
"termination_fee": 0,
"last_bill_refund_price": 15000
}'bashconst { BootpayCommerce } = require('@bootpay/backend-js');
const commerce = new BootpayCommerce({
client_key: '{client_key}',
secret_key: '{secret_key}'
});
const response = await commerce.orderSubscription.adminTerminate(
'sub_abc123',
{
reason: '서비스 정책 변경에 따른 해지',
termination_fee: 0,
last_bill_refund_price: 15000
}
);
console.log(response);javascriptfrom bootpay_backend.commerce import BootpayCommerce
commerce = BootpayCommerce('{client_key}', '{secret_key}')
response = commerce.order_subscription_admin_terminate(
'sub_abc123',
{
'reason': '서비스 정책 변경에 따른 해지',
'termination_fee': 0,
'last_bill_refund_price': 15000
}
)
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
$response = $commerce->orderSubscriptionAdminTerminate('sub_abc123', [
'reason' => '서비스 정책 변경에 따른 해지',
'termination_fee' => 0,
'last_bill_refund_price' => 15000
]);
print_r($response);phpBootpayCommerceApi commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
HashMap<String, Object> params = new HashMap<>();
params.put("reason", "서비스 정책 변경에 따른 해지");
params.put("termination_fee", 0);
params.put("last_bill_refund_price", 15000);
HashMap<String, Object> response = commerce.orderSubscriptionAdminTerminate("sub_abc123", params);
System.out.println(response);javacommerce = BootpayStore::Api.new('{client_key}', '{secret_key}')
response = commerce.order_subscription_admin_terminate('sub_abc123', {
reason: '서비스 정책 변경에 따른 해지',
termination_fee: 0,
last_bill_refund_price: 15000
})
puts responserubycommerce := bootpay.NewCommerceApi("{client_key}", "{secret_key}")
response, err := commerce.OrderSubscriptionAdminTerminate("sub_abc123", map[string]interface{}{
"reason": "서비스 정책 변경에 따른 해지",
"termination_fee": 0,
"last_bill_refund_price": 15000,
})
fmt.Println(response)govar commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
var response = await commerce.OrderSubscriptionAdminTerminate("sub_abc123", new {
reason = "서비스 정책 변경에 따른 해지",
termination_fee = 0,
last_bill_refund_price = 15000
});
Console.WriteLine(response);csharp응답
{
"id": "sub_abc123",
"status": 3,
"reason": "서비스 정책 변경에 따른 해지",
"termination_fee": 0,
"refund_amount": 15000,
"terminated_at": "2025-07-11T15:00:00Z",
"service_end_at": "2025-07-11T15:00:00Z"
}json응답 필드 설명
| 파라미터 | 타입 | 설명 |
|---|---|---|
| id | String | 구독 계약 ID |
| status | Integer | 변경된 상태 (3: 해지) |
| reason | String | 해지 사유 |
| termination_fee | Number | 적용된 해지 수수료 |
| refund_amount | Number | 환불 금액 |
| terminated_at | String | 해지 처리 시각 |
| service_end_at | String | 서비스 종료 시각 |
에러 코드
공통 에러
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_NOT_FOUND |
구독결제 건을 찾을 수 없어요. | order_subscription_id를 확인해요 |
ORDER_SUBSCRIPTION_TERMINATE_APPROVE_ERROR |
구독해지 승인이 실패했어요. | 관리자에 문의해요 |
ORDER_SUBSCRIPTION_TERMINATE_CRITICAL_ERROR |
구독해지 승인 과정에서 치명적인 오류가 발생했어요. | 관리자에 문의해요 |
ORDER_SUBSCRIPTION_TERMINATE_CANNOT_ABLE |
구독해지를 승인할 수 없는 상태예요. | 구독 상태를 확인해요 |
