구독 계약의 중도 인수를 요청해요. 기존 구독자가 남은 기간에 대한 구매 요청을 하면, 관리자 승인 후 처리돼요.
API 엔드포인트
사용자 요청 vs 관리자 요청
| 구분 | 사용자 요청 | 관리자 요청 |
|---|---|---|
| API | 이 문서 (POST /v1/order_subscriptions/requests/ing/purchase) |
PUT /v1/order_subscriptions/:id/purchase (supervisor 권한) |
| 호출 주체 | 가맹점 서버(Basic 인증). 고객 JWT 또는 user_id를 함께 보내면 구독 소유자와 대조해요 |
가맹점 서버(Basic 인증, supervisor 권한) |
| 승인 필요 | 설정에 따라 (use_purchase_fee_auto 면 자동 승인) |
아니요 — 즉시 처리 |
| 금액 조정 | 불가 — 서버 계산값과 같아야 해요 | 가능 |
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
order_subscription_id |
String | 필수 | 구독 계약 ID |
user_id |
String | 선택 | 로그인한 회원 ID 또는 이 몰의 외부 회원 ID. 보내면 구독 소유자와 대조해요. Bootpay-User-JWT 헤더로 보내도 돼요 |
price |
Number | 필수 | 인수 금액. 서버 계산값과 정확히 같아야 해요 |
tax_free_price |
Number | 필수 | 비과세 금액. 서버 계산값과 정확히 같아야 해요 (비과세가 없으면 0) |
reason |
String | 선택 | 요청 사유 |
금액 계산
서버는 남은 회차 수 × 회차 금액 × 인수 비율(템플릿의 purchase_fee_rate, 기본 0.2) 로 인수 금액을 계산하고, 보낸 price·tax_free_price 가 이 값과 다르면 거절해요. 비과세 금액도 남은 회차 수 × 회차 비과세 금액 × 인수 비율 이에요.
알려진 문제 — 중도인수 금액 계산 API가 에러를 돌려줘요
인수 금액을 미리 받는 GET /v1/order_subscriptions/requests/ing/calculate_purchase_price 는 지금 서버 오류(500)로 끝나요. 계산 API가 고쳐지기 전까지는 위 계산식으로 금액을 직접 구해 보내요. 무제한 회차 계약에 인수 요청을 보내도 거절 대신 서버 오류가 나요 — 무제한 회차 계약은 중도인수를 할 수 없어요.
코드 예제
curl -X POST "https://api.bootapi.com/v1/order_subscriptions/requests/ing/purchase" \
-H "Authorization: Basic {base64(client_key:secret_key)}" \
-H "Content-Type: application/json" \
-d '{
"order_subscription_id": "sub_abc123",
"price": 180000,
"tax_free_price": 0,
"reason": "중도 인수 요청"
}'bashconst { BootpayCommerce } = require('@bootpay/backend-js');
const commerce = new BootpayCommerce({
client_key: '{client_key}',
secret_key: '{secret_key}'
});
const response = await commerce.orderSubscriptionRequest.purchase({
order_subscription_id: 'sub_abc123',
price: 180000,
tax_free_price: 0,
reason: '중도 인수 요청'
});
console.log(response);javascriptfrom bootpay_backend.commerce import BootpayCommerce
commerce = BootpayCommerce('{client_key}', '{secret_key}')
response = commerce.order_subscription_request_purchase({
'order_subscription_id': 'sub_abc123',
'price': 180000,
'tax_free_price': 0,
'reason': '중도 인수 요청'
})
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
$response = $commerce->orderSubscriptionRequestPurchase([
'order_subscription_id' => 'sub_abc123',
'price' => 180000,
'tax_free_price' => 0,
'reason' => '중도 인수 요청'
]);
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.orderSubscription.request.ing.OrderSubscriptionPurchaseParams;
TokenPayload tokenPayload = new TokenPayload("{client_key}", "{secret_key}");
BootpayStore commerce = new BootpayStore(tokenPayload).withToken();
OrderSubscriptionPurchaseParams params = new OrderSubscriptionPurchaseParams();
params.orderSubscriptionId = "sub_abc123";
params.price = 180000.0;
params.taxFreePrice = 0.0;
params.reason = "중도 인수 요청";
BootpayStoreResponse response = commerce.orderSubscription.requestIng.purchase(params);
System.out.println(response);javarequire 'bootpay'
commerce = BootpayStore::RestClient.new(client_key: '{client_key}', secret_key: '{secret_key}')
response = commerce.order_subscription_requests_ing_purchase(
order_subscription_id: 'sub_abc123',
price: 180000,
tax_free_price: 0,
reason: '중도 인수 요청'
)
puts responserubycommerce := bootpay.NewCommerceApi("{client_key}", "{secret_key}")
response, err := commerce.OrderSubscriptionRequestPurchase(map[string]interface{}{
"order_subscription_id": "sub_abc123",
"price": 180000,
"tax_free_price": 0,
"reason": "중도 인수 요청",
})
fmt.Println(response)govar commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
var response = await commerce.OrderSubscriptionRequestPurchase(new {
order_subscription_id = "sub_abc123",
price = 180000,
tax_free_price = 0,
reason = "중도 인수 요청"
});
Console.WriteLine(response);csharp응답
응답은 요청 객체가 아니라 구독이에요
이 API 는 접수 결과만 돌려주는 게 아니라 요청이 반영된 구독 계약 전체를 돌려줘요. 아래는 자주 쓰는 항목만 추린 것이고, 실제 응답에는 구독 상세 필드가 모두 들어 있어요.
{
"order_subscription_id": "687a1b2c3d4e5f6789012345",
"order_name": "Professional 플랜 구독",
"status": "subscribing",
"approval_status": "approval_approved_auto",
"subscription_type": "regular_subscription",
"price": 75000,
"quantity": 1,
"current_duration": 6,
"total_subscription_duration": 12,
"service_start_at": "2025-08-01T00:00:00Z"
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| order_subscription_id | String | 구독 계약 ID |
| order_name | String | 구독 상품명 |
| status | String | 구독 상태 — 커머스 Enum 참고 |
| approval_status | String | 승인 상태 — 커머스 Enum 참고 |
| subscription_type | String | 구독 유형 |
| price | Number | 회차 결제 금액 |
| current_duration | Integer | 현재 회차 |
| total_subscription_duration | Integer | 총 회차 |
구독 상태 (status) · 승인 상태 (approval_status)
응답에 쓰인 값이에요. 전체 목록은 구독 상태·승인 상태를 봐요.
| 필드 | 키 | 값 | 설명 |
|---|---|---|---|
status |
subscribing |
1 |
구독 중 — 관리자 승인을 기다리는 동안에도 그대로예요 |
status |
mid_term_transfer_approved |
25 |
중도 인수 승인됨 — 템플릿이 자동 승인(use_purchase_fee_auto)일 때 요청 즉시 바뀌어요 |
subscription_type |
regular_subscription |
1 |
정기 구독 (전체) |
중도인수 요청은 승인 상태(approval_status)를 바꾸지 않아요. 요청 처리 상태는 신청 관리에서 request_status 로 확인해요.
에러 코드
공통 에러
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_NOT_FOUND |
구독결제 건을 찾을 수 없어요. | order_subscription_id를 확인해요 |
ORDER_SUBSCRIPTION_PURCHASE_STATUS_INVALID |
중도인수가 가능한 상태가 아니에요. | 구독 중 상태인지, 템플릿에서 중도인수(use_purchase)가 켜져 있는지 확인해요 |
ORDER_SUBSCRIPTION_PURCHASE_PRICE_INVALID |
중도인수 금액이 유효하지 않아요. | price 를 서버 계산값과 같게 보내요 |
ORDER_SUBSCRIPTION_PURCHASE_TAX_FREE_PRICE_INVALID |
중도인수 비과세 금액이 유효하지 않아요. | tax_free_price 를 서버 계산값과 같게 보내요 |
ORDER_SUBSCRIPTION_PURCHASE_REQUEST_DUPLICATE |
이미 대기 중인 중도인수 요청이 있어요. 관리자가 처리 중이니 잠시만 기다려라. | 기존 요청 처리를 기다려라 |
