구독 계약에 조정 항목을 추가해 금액을 조정하는 API예요. 설치비, 할인, 추가 요금 같은 항목을 회차에 반영할 때 사용해요. 한 회차만, 여러 회차 범위, 특정 회차부터 끝까지 세 가지 방식으로 걸 수 있어요.
API 엔드포인트
https://api.bootapi.com/v1/order_subscriptions/:id/adjustmentsBasic Auth (supervisor 권한 필요)회차를 지정하는 세 가지 방법
| 방법 | 보내는 값 | 결과 |
|---|---|---|
| 한 회차 | duration: 5 |
5회차에만 조정 1건 |
| 범위 | duration_from: 3, duration_to: 7 |
3·4·5·6·7회차에 각각 1건씩 (총 5건) |
| 무제한 | duration_from: 3, is_unlimited: true |
3회차부터 계약이 끝날 때까지 계속 적용 (레코드는 1건) |
범위로 걸면 회차 수만큼 조정 레코드가 생겨서, 나중에 빼려면 하나씩 지워야 해요. 무제한은 "3회차 이후 전부"라는 규칙 하나만 저장해두고 청구서를 만들 때마다 적용해요. 그래서 계약이 몇 회차든 레코드는 하나고, 지우면 한 번에 사라져요.
이미 결제가 끝난 회차에는 그 시점 금액이 그대로 남아야 하니, 무제한 조정을 지울 때 결제 완료 회차에는 같은 금액의 개별 조정을 자동으로 만들어둬요.
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
id |
String | 필수 | 구독 계약 ID (URL 경로) |
name |
String | 필수 | 조정 항목명 (예: 설치비, 할인) |
price |
Number | 필수 | 조정 금액. 0은 보낼 수 없어요 (음수는 할인) |
tax_free_price |
Number | 선택 | 면세 금액 (기본 0) |
type |
Number | 선택 | 조정 타입. 생략하면 price > 0이면 2(추가금액), 아니면 1(회차별 할인)로 자동 판정돼요 |
duration |
Number | 선택 | 적용 회차 (기본 1). duration_from을 보내면 무시돼요 |
duration_from |
Number | 선택 | 범위 시작 회차. 생략하면 duration 값을 써요 |
duration_to |
Number | 선택 | 범위 끝 회차. 생략하면 duration_from과 같아요. is_unlimited가 true면 무시돼요 |
is_unlimited |
Boolean | 선택 | true면 duration_from 회차부터 계약이 끝날 때까지 적용해요 (기본 false) |
코드 예제
const { BootpayCommerce } = require('@bootpay/backend-js')
const commerce = new BootpayCommerce({
client_key: '{client_key}',
secret_key: '{secret_key}'
})
const response = await commerce.orderSubscriptionBill.adjustment.create('687a1b2c3d4e5f6789012345', {
name: '3개월 할인',
price: -5000,
tax_free_price: 0,
type: 1,
duration_from: 3,
duration_to: 7
})
console.log(response)javascriptfrom bootpay_backend.commerce import BootpayCommerce
commerce = BootpayCommerce('{client_key}', '{secret_key}')
response = commerce.order_subscription_adjustment_create('687a1b2c3d4e5f6789012345', {
'name': '3개월 할인',
'price': -5000,
'tax_free_price': 0,
'type': 1,
'duration_from': 3,
'duration_to': 7
})
print(response)pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi('{client_key}', '{secret_key}');
$response = $commerce->orderSubscriptionAdjustmentCreate('687a1b2c3d4e5f6789012345', [
'name' => '3개월 할인',
'price' => -5000,
'tax_free_price' => 0,
'type' => 1,
'duration_from' => 3,
'duration_to' => 7
]);
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.pojo.SOrderSubscriptionAdjustment;
TokenPayload tokenPayload = new TokenPayload("{client_key}", "{secret_key}");
BootpayStore commerce = new BootpayStore(tokenPayload).withToken();
SOrderSubscriptionAdjustment adjustment = new SOrderSubscriptionAdjustment();
adjustment.name = "3개월 할인";
adjustment.price = -5000.0;
adjustment.taxFreePrice = 0.0;
adjustment.type = 1;
adjustment.durationFrom = 3;
adjustment.durationTo = 7;
BootpayStoreResponse response = commerce.orderSubscriptionAdjustment.create(
"687a1b2c3d4e5f6789012345", adjustment
);
System.out.println(response);javarequire 'bootpay-backend-ruby'
commerce = BootpayStore::RestClient.new(client_key: '{client_key}', secret_key: '{secret_key}')
response = commerce.order_subscription_adjustment_create(
order_subscription_id: '687a1b2c3d4e5f6789012345',
name: '3개월 할인',
price: -5000,
tax_free_price: 0,
type: 1,
duration_from: 3,
duration_to: 7
)
puts responserubycommerce := bootpay.NewCommerceApi("{client_key}", "{secret_key}")
response, err := commerce.OrderSubscriptionAdjustmentCreate("687a1b2c3d4e5f6789012345", map[string]interface{}{
"name": "3개월 할인",
"price": -5000,
"tax_free_price": 0,
"type": 1,
"duration_from": 3,
"duration_to": 7,
})
fmt.Println(response)govar commerce = new BootpayCommerceApi("{client_key}", "{secret_key}");
var response = await commerce.OrderSubscriptionAdjustmentCreate("687a1b2c3d4e5f6789012345", new {
name = "3개월 할인",
price = -5000,
tax_free_price = 0,
type = 1,
duration_from = 3,
duration_to = 7
});
Console.WriteLine(response);csharp응답
구독 계약 상세 전체를 돌려줘요. 내용 변경 응답과 같은 형태라서 상품·회차·수령인 정보가 모두 들어 있어요. 아래는 조정과 관련된 부분만 추린 예시예요.
{
"order_subscription_id": "687a1b2c3d4e5f6789012345",
"price": 45000,
"order_subscription_adjustments": [
{
"order_subscription_adjustment_id": "6965fc104cb8149d07712533",
"name": "3개월 할인",
"price": -5000,
"tax_free_price": 0,
"type": 1,
"duration": 3,
"is_unlimited": false
}
],
"order_subscription_bills": [
{ "duration": 3, "price": 40000, "status": 1 }
]
}jsonis_unlimited가 true인 항목은 duration이 시작 회차를 뜻해요. 그 회차 이후 모든 청구서에 적용돼요.
조정 타입
| 값 | 설명 |
|---|---|
1 |
회차별 할인 적용 |
2 |
추가금액 (설치비 등) |
3 |
주기 할인 적용 |
4 |
추가 비용 적용 |
5 |
매회차 추가비용 (모든 할인 적용 후) |
10 |
구독 상품 금액 쿠폰 |
11 |
배송비 쿠폰 |
적용 규칙
조정할 수 있는 회차의 상한이 있어요. 총 회차가 정해진 계약은 그 총 회차까지만 조정할 수 있어요. 계약 밖 회차에 조정을 걸어도 청구서가 만들어지지 않기 때문이에요. 총 회차가 무제한인 계약은 60회차까지 걸 수 있어요.
이미 결제가 끝난 회차는 조정할 수 없어요. 그 회차는 청구가 확정된 상태라 금액이 바뀌지 않아요. duration_from을 결제 완료 회차보다 뒤로 잡아주세요.
범위 조정은 전부 되거나 전부 안 돼요. 3~7회차를 요청했는데 6회차에서 최종 금액이 음수가 되면, 3·4·5회차도 저장되지 않고 요청 전체가 거절돼요. 최종 금액은 기준금액 + 배송비 + 그 회차에 적용되는 모든 조정으로 계산하는데, 이때 먼저 걸어둔 무제한 조정도 함께 더해져요.
이미 생성된 회차에 조정 항목이 추가되면 기존 회차가 삭제 후 재생성될 수 있어요. 결제 예정·결제 실패·지연 대기·재시도 대기·일시정지 상태의 청구서는 금액이 즉시 다시 계산돼요.
에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
ORDER_SUBSCRIPTION_INVALID |
구독결제 건을 찾을 수 없어요. | order_subscription_id를 확인해요 |
ORDER_SUBSCRIPTION_ADJUST_PRICE_INVALID |
유효하지 않은 조정 금액이에요. | price에 0이 아닌 값을 보내요 |
SUBSCRIPTION_ADJUSTMENT_DURATION_INVALID |
유효하지 않은 회차예요. | 1 이상이면서 계약 총 회차(무제한 계약은 60) 이하로 보내요 |
SUBSCRIPTION_ADJUSTMENT_DUPLICATE |
같은 조정 항목이 이미 있어요. | 같은 회차에 같은 이름·타입의 조정이 있는지 확인해요 |
SUBSCRIPTION_ADJUSTMENT_PAYMENT_COMPLETED |
이미 결제가 완료된 회차예요. | duration_from을 결제 완료 회차 다음으로 잡아요 |
SUBSCRIPTION_ADJUSTMENT_NEGATIVE_PRICE |
결제 금액이 음수가 돼요. | 할인 폭을 줄이거나 기존 조정을 먼저 정리해요 |
SUBSCRIPTION_ADJUSTMENT_NEGATIVE_TAX_FREE |
비과세 금액이 음수가 돼요. | tax_free_price를 확인해요 |
SUBSCRIPTION_ADJUSTMENT_TAX_FREE_OVER_PRICE |
비과세 금액이 결제 금액을 넘어요. | tax_free_price를 결제 금액 이하로 보내요 |
SUBSCRIPTION_ADJUSTMENT_NOT_FOUND |
조정 항목을 찾을 수 없어요. | 삭제 시 order_subscription_adjustment_id를 확인해요 |
