핵심 요약
- 구독 신청(purchase/transfer) 이후 단계인 승인·거절·철회·조회 5 API 를 한 페이지에 모은다
- 관리자가 신청을 받아 승인(구독 시작)·거절(계약 전 차단) 하고, 신청자는 처리 전 철회 할 수 있어요
- 현재 상태 확인은 목록·상세 로 조회해요
이런 상황이라면
관리자 콘솔에서 "가입 대기 중인 구독 10건" 중 하나를 승인해 달라는 요청이 들어왔어요. 관리자가 승인 버튼을 누르면 구독이 시작되고 첫 회차 결제가 실행돼야 해요. 반대로 "이 요청은 사유가 부족해서 거절" 하는 케이스, 사용자가 "취소할래요" 하고 철회하는 케이스, 그리고 지금까지 쌓인 신청을 목록으로 훑는 케이스 — 이 4 가지 시나리오를 한 번에 봐요.
이 페이지에서 승인·거절·철회·목록·상세 5 API 를 연결해서 봐요.
1신청 승인
구독 변경 요청(해지)을 승인해요. 관리자 권한이 필요해요.
API 엔드포인트
https://api.bootapi.com/v1/order-subscription-requests/:request_history_idBasic Auth (supervisor 권한 필요)승인과 거절은 같은 엔드포인트를 쓰고, 요청 본문의 approval 값(approve / reject)으로 구분해요. 이 API는 관리자(Supervisor) 권한이 필요해요.
승인 시나리오
- 해지 요청 승인: 구독이 종료되고 수수료가 정산돼요
- 수수료 조정 승인: 관리자가 수수료를 조정하여 승인할 수 있어요
플로우
- 해지 요청 접수 -> 관리자 확인 / 수수료 조정
- 관리자 확인 / 수수료 조정 -> approve / API 호출
- approve / API 호출 -> 해지 완료 / 환불 처리 (승인)
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
request_history_id |
String | 필수 | 요청 고유 ID (URL 경로) |
approval |
String | 필수 | approve 고정 (거절은 reject) |
reason |
String | 선택 | 승인 사유 (내부 기록용) |
price |
Number | 선택 | 중도인수 승인 시 조정 금액 |
tax_free_price |
Number | 선택 | 중도인수 승인 시 비과세 금액 |
termination_fee |
Number | 선택 | 해지 승인 시 조정 수수료 |
last_bill_refund_price |
Number | 선택 | 해지 승인 시 마지막 회차 환불 금액 |
final_fee |
Number | 선택 | 해지 승인 시 최종 청구 금액 |
service_end_at |
String | 선택 | 해지 승인 시 서비스 종료일 |
코드 예제
curl -X PUT "https://api.bootapi.com/v1/order-subscription-requests/{request_history_id}" \
-H "Authorization: Basic {base64(client_key:secret_key)}" \
-H "Content-Type: application/json" \
-d '{
"approval": "approve",
"reason": "승인 처리합니다"
}'bashconst { BootpayCommerce } = require('@bootpay/backend-js');
const commerce = new BootpayCommerce({
client_key: '{client_key}',
secret_key: '{secret_key}'
});
const response = await commerce.orderSubscriptionRequest.approve(
'req_001',
{ admin_comment: '승인 처리합니다' }
);
console.log(response);javascriptfrom bootpay_backend.commerce import BootpayCommerce
commerce = BootpayCommerce('{client_key}', '{secret_key}')
response = commerce.order_subscription_request_approve(
'req_001',
{'admin_comment': '승인 처리합니다'}
)
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
$response = $commerce->orderSubscriptionRequestApprove('req_001', [
'admin_comment' => '승인 처리합니다'
]);
print_r($response);phpimport kr.co.bootpay.store.BootpayStore;
import kr.co.bootpay.store.model.request.TokenPayload;
import kr.co.bootpay.store.model.response.BootpayStoreResponse;
import kr.co.bootpay.store.model.request.orderSubscriptionRequest.OrderSubscriptionRequestUpdateParams;
TokenPayload tokenPayload = new TokenPayload("{client_key}", "{secret_key}");
BootpayStore commerce = new BootpayStore(tokenPayload).withToken();
OrderSubscriptionRequestUpdateParams params = new OrderSubscriptionRequestUpdateParams();
params.orderSubscriptionRequestHistoryId = "req_001";
params.approval = "approve";
params.reason = "승인 처리합니다";
BootpayStoreResponse response = commerce.orderSubscriptionRequest.update(params);
System.out.println(response);javarequire 'bootpay-backend-ruby'
commerce = BootpayStore::RestClient.new(client_key: '{client_key}', secret_key: '{secret_key}')
response = commerce.order_subscription_request_update(
request_history_id: 'req_001',
approval: 'approve',
reason: '승인 처리합니다'
)
puts responserubycommerce := bootpay.NewCommerceApi("{client_key}", "{secret_key}")
response, err := commerce.OrderSubscriptionRequestApprove("req_001", map[string]interface{}{
"admin_comment": "승인 처리합니다",
})
fmt.Println(response)govar commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
var response = await commerce.OrderSubscriptionRequestApprove("req_001", new {
admin_comment = "승인 처리합니다"
});
Console.WriteLine(response);csharp응답
{
"request_history_id": "req_001",
"status": 1,
"adjusted_fee": null,
"admin_comment": "승인 처리합니다",
"approved_at": "2025-07-11T15:00:00Z",
"approved_by": "admin_001"
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| request_history_id | String | 요청 고유 ID |
| status | Integer | 변경된 상태 (1: 승인됨) |
| adjusted_fee | Number | 조정된 수수료 (조정 시) |
| admin_comment | String | 관리자 메모 |
| approved_at | String | 승인 처리 시각 |
| approved_by | String | 승인 처리자 ID |
에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_REQUEST_HISTORY_NOT_FOUND |
계약변경 요청 내역을 찾을 수 없어요. | request_history_id를 확인해요 |
ORDER_SUBSCRIPTION_REQUEST_HISTORY_ALREADY_PROCESSED |
이미 처리된 요청이에요. | 새로운 요청을 생성해요 |
2신청 거절
구독 변경 요청(해지, 인수, 이전)을 거절해요. 관리자 권한이 필요해요.
API 엔드포인트
https://api.bootapi.com/v1/order-subscription-requests/:request_history_idBasic Auth (supervisor 권한 필요)승인과 같은 엔드포인트예요. 요청 본문의 approval을 reject로 보내면 거절 처리돼요. 이 API는 관리자(Supervisor) 권한이 필요해요.
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
request_history_id |
String | 필수 | 요청 고유 ID (URL 경로) |
approval |
String | 필수 | reject 고정 |
reason |
String | 선택 | 거절 사유 |
코드 예제
curl -X PUT "https://api.bootapi.com/v1/order-subscription-requests/{request_history_id}" \
-H "Authorization: Basic {base64(client_key:secret_key)}" \
-H "Content-Type: application/json" \
-d '{
"approval": "reject",
"reason": "최소 구독 기간 미충족"
}'bashconst { BootpayCommerce } = require('@bootpay/backend-js');
const commerce = new BootpayCommerce({
client_key: '{client_key}',
secret_key: '{secret_key}'
});
const response = await commerce.orderSubscriptionRequest.reject(
'req_001',
{
reject_reason: '최소 구독 기간 미충족',
admin_comment: '3개월 미만 해지 불가 정책'
}
);
console.log(response);javascriptfrom bootpay_backend.commerce import BootpayCommerce
commerce = BootpayCommerce('{client_key}', '{secret_key}')
response = commerce.order_subscription_request_reject(
'req_001',
{
'reject_reason': '최소 구독 기간 미충족',
'admin_comment': '3개월 미만 해지 불가 정책'
}
)
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
$response = $commerce->orderSubscriptionRequestReject('req_001', [
'reject_reason' => '최소 구독 기간 미충족',
'admin_comment' => '3개월 미만 해지 불가 정책'
]);
print_r($response);phpimport kr.co.bootpay.store.BootpayStore;
import kr.co.bootpay.store.model.request.TokenPayload;
import kr.co.bootpay.store.model.response.BootpayStoreResponse;
import kr.co.bootpay.store.model.request.orderSubscriptionRequest.OrderSubscriptionRequestUpdateParams;
TokenPayload tokenPayload = new TokenPayload("{client_key}", "{secret_key}");
BootpayStore commerce = new BootpayStore(tokenPayload).withToken();
OrderSubscriptionRequestUpdateParams params = new OrderSubscriptionRequestUpdateParams();
params.orderSubscriptionRequestHistoryId = "req_001";
params.approval = "reject";
params.reason = "최소 구독 기간 미충족 — 3개월 미만 해지 불가 정책";
BootpayStoreResponse response = commerce.orderSubscriptionRequest.update(params);
System.out.println(response);javarequire 'bootpay-backend-ruby'
commerce = BootpayStore::RestClient.new(client_key: '{client_key}', secret_key: '{secret_key}')
response = commerce.order_subscription_request_update(
request_history_id: 'req_001',
approval: 'reject',
reason: '최소 구독 기간 미충족 — 3개월 미만 해지 불가 정책'
)
puts responserubycommerce := bootpay.NewCommerceApi("{client_key}", "{secret_key}")
response, err := commerce.OrderSubscriptionRequestReject("req_001", map[string]interface{}{
"reject_reason": "최소 구독 기간 미충족",
"admin_comment": "3개월 미만 해지 불가 정책",
})
fmt.Println(response)govar commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
var response = await commerce.OrderSubscriptionRequestReject("req_001", new {
reject_reason = "최소 구독 기간 미충족",
admin_comment = "3개월 미만 해지 불가 정책"
});
Console.WriteLine(response);csharp응답
{
"request_history_id": "req_001",
"status": -1,
"reject_reason": "최소 구독 기간 미충족",
"admin_comment": "3개월 미만 해지 불가 정책",
"rejected_at": "2025-07-11T15:00:00Z",
"rejected_by": "admin_001"
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| request_history_id | String | 요청 고유 ID |
| status | Integer | 변경된 상태 (-1: 거절됨) |
| reject_reason | String | 거절 사유 |
| admin_comment | String | 관리자 내부 메모 |
| rejected_at | String | 거절 처리 시각 |
| rejected_by | String | 거절 처리자 ID |
에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_REQUEST_HISTORY_NOT_FOUND |
계약변경 요청 내역을 찾을 수 없어요. | request_history_id를 확인해요 |
ORDER_SUBSCRIPTION_REQUEST_HISTORY_ALREADY_PROCESSED |
이미 처리된 요청이에요. | 새로운 요청을 생성해요 |
3신청 철회
접수된 요청을 철회하는 로직은 서버 모델(request_withdraw)에 있지만 v1 REST 엔드포인트로 연결돼 있지 않아요. 공개된 라우트 중 withdraw는 주문 취소(PUT /v1/order/cancel/:id/withdraw) 하나뿐이에요.
철회가 필요하면 관리자 화면에서 처리하거나, 위 거절(approval: "reject")로 요청을 마감해요.
철회 가능 상태 (참고)
| 상태 코드 | 상태명 | 철회 가능 |
|---|---|---|
0 |
대기 | 가능 |
1 |
승인 대기 | 가능 |
-1 |
거절됨 | 불가 |
-2 |
처리 완료 | 불가 |
4신청 목록
고객이 제출한 구독 관련 요청(해지, 중도 인수, 승계 등) 목록을 조회하는 API예요. 관리자는 요청 목록을 통해 전체 현황을 파악하고 승인/거절 처리를 진행할 수 있어요.
API 엔드포인트
https://api.bootapi.com/v1/order-subscription-requestsBasic Auth요청 유형
| 코드 | 설명 |
|---|---|
| 1 | 해지 요청 |
| 2 | 중도 인수 요청 |
| 3 | 이전(승계) 요청 |
요청 상태
| 코드 | 설명 |
|---|---|
| 0 | 대기중 |
| 1 | 사용자 확인 대기 |
| 2 | 승인됨 |
| -1 | 거절됨 |
| -2 | 철회됨 |
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
project_id |
String | 선택 | 넘기면 프로젝트 전체 검색(supervisor), 안 넘기면 본인 요청만 조회 |
keyword |
String | 선택 | 검색어 (요청 ID, 사용자명 등) |
request_type |
Integer | 선택 | 요청 유형 필터 (1: 해지, 2: 중도 인수, 3: 승계) |
status |
Integer | 선택 | 요청 상태 필터 |
order_subscription_id |
String | 선택 | 특정 구독 계약의 요청만 조회 |
user_id |
String | 선택 | 특정 고객의 요청만 조회 |
user_group_id |
String | 선택 | 특정 그룹의 요청만 조회 |
s_at |
String | 선택 | 검색 시작일 |
e_at |
String | 선택 | 검색 종료일 |
page |
Integer | 선택 | 페이지 번호 (기본값: 1) |
limit |
Integer | 선택 | 페이지당 데이터 수 (기본값: 20, 최대 100) |
project_id를 넘기지 않으면 로그인한 본인의 요청 목록만 최신순으로 돌려주고, keyword·status·page 등 나머지 필터는 무시돼요. 관리자 화면처럼 전체를 검색하려면 project_id를 함께 넘겨요.
코드 예제
curl -X GET "https://api.bootapi.com/v1/order-subscription-requests?project_id={project_id}&page=1&limit=10" \
-H "Authorization: Basic {base64(client_key:secret_key)}" \
-H "Content-Type: application/json"bashconst { BootpayCommerce } = require('@bootpay/backend-js');
const commerce = new BootpayCommerce({
client_key: '{client_key}',
secret_key: '{secret_key}'
});
const response = await commerce.orderSubscriptionRequest.list({
page: 1,
limit: 10,
status: [0, 1] // 대기중, 사용자 확인 대기
});
console.log(response);javascriptfrom bootpay_backend.commerce import BootpayCommerce
commerce = BootpayCommerce('{client_key}', '{secret_key}')
response = commerce.order_subscription_request_list({
'page': 1,
'limit': 10,
'status': [0, 1]
})
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
$response = $commerce->orderSubscriptionRequestList([
'page' => 1,
'limit' => 10,
'status' => [0, 1]
]);
print_r($response);phpimport kr.co.bootpay.store.BootpayStore;
import kr.co.bootpay.store.model.request.TokenPayload;
import kr.co.bootpay.store.model.response.BootpayStoreResponse;
import kr.co.bootpay.store.model.request.orderSubscriptionRequest.OrderSubscriptionRequestListParams;
TokenPayload tokenPayload = new TokenPayload("{client_key}", "{secret_key}");
BootpayStore commerce = new BootpayStore(tokenPayload).withToken();
OrderSubscriptionRequestListParams params = new OrderSubscriptionRequestListParams();
params.page = 1;
params.limit = 10;
params.status = 0;
BootpayStoreResponse response = commerce.orderSubscriptionRequest.list(params);
System.out.println(response);javarequire 'bootpay-backend-ruby'
commerce = BootpayStore::RestClient.new(client_key: '{client_key}', secret_key: '{secret_key}')
response = commerce.order_subscription_request_list(
page: 1,
limit: 10,
status: 0
)
puts responserubycommerce := bootpay.NewCommerceApi("{client_key}", "{secret_key}")
response, err := commerce.OrderSubscriptionRequestList(map[string]interface{}{
"page": 1,
"limit": 10,
"status": []int{0, 1},
})
fmt.Println(response)govar commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
var response = await commerce.OrderSubscriptionRequestList(new {
page = 1,
limit = 10,
status = new[] { 0, 1 }
});
Console.WriteLine(response);csharp응답
성공 응답
{
"count": 25,
"list": [
{
"request_history_id": "687a1b2c3d4e5f6789012345",
"type": 1,
"status": 0,
"order_subscription_id": "sub_abc123",
"user_id": "user_001",
"created_at": "2025-07-11T10:00:00Z"
}
]
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| count | Number | 조회된 데이터의 총 개수 |
| list | Array | 요청 목록 |
| list[].request_history_id | String | 요청 고유 ID |
| list[].type | Integer | 요청 유형 |
| list[].status | Integer | 요청 상태 |
| list[].order_subscription_id | String | 관련 구독 계약 ID |
| list[].user_id | String | 요청자 ID |
| list[].created_at | String | 요청 생성일 |
에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_NOT_FOUND |
구독결제 건을 찾을 수 없어요. | order_subscription_id를 확인해요 |
5신청 상세
특정 구독 요청의 상세 정보를 조회하는 API예요. 요청 사유, 관련 구독 정보, 수수료 계산 내역, 처리 히스토리 등을 확인할 수 있어요.
API 엔드포인트
https://api.bootapi.com/v1/order-subscription-requests/:request_history_idBasic Auth (supervisor 권한 필요)조회 가능한 정보
- 요청 유형 및 상태
- 요청자 정보 (이름, 이메일, 유저 ID)
- 요청 사유 (고객 작성 텍스트)
- 관련 구독 정보 (상품명, 상태, 회차)
- 금액 정보 (해지 수수료, 인수 금액 등)
- 관리자 조정 항목 및 코멘트
- 처리 히스토리 로그
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
request_history_id |
String | 필수 | 요청 고유 ID (URL 파라미터) |
코드 예제
curl -X GET "https://api.bootapi.com/v1/order-subscription-requests/{request_history_id}" \
-H "Authorization: Basic {base64(client_key:secret_key)}" \
-H "Content-Type: application/json"bashconst { BootpayCommerce } = require('@bootpay/backend-js');
const commerce = new BootpayCommerce({
client_key: '{client_key}',
secret_key: '{secret_key}'
});
const response = await commerce.orderSubscriptionRequest.detail('687a1b2c3d4e5f6789012345');
console.log(response);javascriptfrom bootpay_backend.commerce import BootpayCommerce
commerce = BootpayCommerce('{client_key}', '{secret_key}')
response = commerce.order_subscription_request_detail('687a1b2c3d4e5f6789012345')
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
$response = $commerce->orderSubscriptionRequestDetail('687a1b2c3d4e5f6789012345');
print_r($response);phpimport kr.co.bootpay.store.BootpayStore;
import kr.co.bootpay.store.model.request.TokenPayload;
import kr.co.bootpay.store.model.response.BootpayStoreResponse;
TokenPayload tokenPayload = new TokenPayload("{client_key}", "{secret_key}");
BootpayStore commerce = new BootpayStore(tokenPayload).withToken();
BootpayStoreResponse response =
commerce.orderSubscriptionRequest.detail("687a1b2c3d4e5f6789012345");
System.out.println(response);javarequire 'bootpay-backend-ruby'
commerce = BootpayStore::RestClient.new(client_key: '{client_key}', secret_key: '{secret_key}')
response = commerce.order_subscription_request_detail(
request_history_id: '687a1b2c3d4e5f6789012345'
)
puts responserubycommerce := bootpay.NewCommerceApi("{client_key}", "{secret_key}")
response, err := commerce.OrderSubscriptionRequestDetail("687a1b2c3d4e5f6789012345")
fmt.Println(response)govar commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
var response = await commerce.OrderSubscriptionRequestDetail("687a1b2c3d4e5f6789012345");
Console.WriteLine(response);csharp응답
성공 응답
{
"request_history_id": "687a1b2c3d4e5f6789012345",
"type": 1,
"status": 0,
"reason": "서비스 이용 종료 희망",
"order_subscription_id": "sub_abc123",
"order_subscription": {
"product_name": "프리미엄 플랜",
"status": 1,
"current_cycle": 6
},
"user_id": "user_001",
"username": "홍길동",
"termination_fee": 50000,
"adjusted_fee": null,
"admin_comment": null,
"history_logs": [
{
"action": "created",
"at": "2025-07-11T10:00:00Z"
}
],
"created_at": "2025-07-11T10:00:00Z",
"updated_at": "2025-07-11T10:00:00Z"
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| request_history_id | String | 요청 고유 ID |
| type | Integer | 요청 유형 (1: 해지, 2: 중도 인수, 3: 승계) |
| status | Integer | 요청 상태 |
| reason | String | 요청 사유 |
| order_subscription_id | String | 관련 구독 계약 ID |
| order_subscription | Object | 구독 계약 상세 정보 |
| user_id | String | 요청자 ID |
| username | String | 요청자명 |
| termination_fee | Number | 예상 해지 수수료 |
| adjusted_fee | Number | 조정된 수수료 (관리자 조정 시) |
| admin_comment | String | 관리자 코멘트 |
| history_logs | Array | 처리 히스토리 로그 |
| created_at | String | 요청 생성일 |
| updated_at | String | 최종 수정일 |
요청 유형
| 코드 | 설명 |
|---|---|
| 1 | 해지 요청 |
| 2 | 중도 인수 요청 |
| 3 | 이전(승계) 요청 |
에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_REQUEST_HISTORY_NOT_FOUND |
계약변경 요청 내역을 찾을 수 없어요. | request_history_id를 확인해요 |
ORDER_SUBSCRIPTION_REQUEST_HISTORY_ALREADY_PROCESSED |
이미 처리된 요청이에요. | 새로운 요청을 생성해요 |
다음 단계 — 구독 운영으로
계약이 승인되면 그다음은 구독 > 운영 영역이에요. 회차가 자동으로 돌기 시작하므로 아래 순서로 이동해요.
전체 라이프사이클은 구독 흐름 설계에서 단일 도식으로 확인해요.
