v1 API 상세: 상품
판매할 상품 정보를 등록해요. 가격, 이름, 구독 설정 등을 지정해요.
기존에 운영 중이던 상품 정보를 등록하는 API예요. 이전 시스템의 상품 데이터를 이전하거나, 오프라인/제휴 유입 상품을 등록할 때 사용해요.
API 엔드포인트
필요 권한: user:product_create
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
name |
String | 필수 | 상품명 (서버가 비어 있는지 검사하지 않으니 꼭 보내요) |
desc |
String | 선택 | 상품 요약 설명 |
content |
String | 선택 | 상품 상세 설명 (HTML 지원) |
images |
File[] | 선택 | 대표 이미지 파일. 파일을 올릴 때는 multipart/form-data 를 사용해요 |
category_id |
String | 선택 | 카테고리 ID |
display_price |
Integer | 필수 | 판매가 (표시가). 0보다 커야 해요 — 0원은 사용량 기반·수시결제 구독 상품만 돼요 |
tax_free_price |
Integer | 선택 | 비과세 가격 |
use_stock |
Boolean | 선택 | 재고 관리 사용 여부 |
stock |
Integer | 선택 | 재고 수량 |
use_subscription |
Boolean | 선택 | 구독 상품 여부 |
subscription_setting_id |
String | 선택 | 구독 설정 템플릿 ID |
usage_api_url |
String | 선택 | 사용량 조회 URL. 종량제(사용량 기반) 구독 상품이면 필수이고, 비어 있으면 SUBSCRIPTION_SETTIGN_NEED_USAGE_API_URL 로 거절돼요. 구독 설정이 아니라 상품에 저장해요 (종량제 구독) |
use_discount |
Boolean | 선택 | 할인 사용 여부 |
discount_price |
Integer | 선택 | 할인 금액 또는 할인율. 판매가에서 이만큼 빼요 (discount_price_type 참고) |
discount_price_type |
Integer | 선택 | 할인 단위 — 1 퍼센트 / 2 정액. 보내지 않으면 정액으로 계산해요 |
status_display |
Boolean | 선택 | 쇼핑몰 노출 여부 |
status_sale |
Boolean | 선택 | 판매 상태 |
ex_uid |
String | 선택 | 외부 시스템 고유 ID (external_uid 로 보내도 돼요) |
save_by |
Integer | 선택 | 저장 방식 (1 시스템 임시저장 / 2 사용자 임시저장 / 3 발행). 구독·배송 설정 검증은 3 을 명시해야 동작해요. 생략하면 발행 상태로 저장되지만 이 검증을 건너뛰어요. 1·2 로 저장한 상품은 목록·상세·주문에 나오지 않아요 |
use_subscription: true 로 구독 상품을 만들 때는 save_by: 3 을 함께 보내야 구독 설정·가격 검증이 실행돼요. 생략하면 발행 상태로 저장되지만 해당 검증을 건너뛰므로 구독 상품 등록에 사용하지 마세요.
save_by 는 저장 방식이에요 — 1 시스템 임시저장 / 2 사용자 임시저장 / 3 발행. 배송·구독 설정 검증은 3 일 때만 동작해요.
이 API 는 구독 기간별 가격(subscription_periods)을 받지 않아요. 옵션 없는 구독 상품을 save_by: 3 으로 발행하면 기간별 가격이 없어 PRODUCT_PRICE_INVALID(HTTP 409)로 거절돼요.
구독 상품은 관리자 > 상품 관리에서 만들고, 받은 product_id 를 체크아웃·구독에 써요.
코드 예제
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.product.create({
name: '드립백 커피 10개입',
desc: '매일 아침 한 잔',
display_price: 29900,
save_by: 3,
tax_type: 1,
ex_uid: 'shop-coffee-10',
status_visible: true,
status_display: true,
status_sale: true
})
console.log('product_id:', response.product_id)javascriptfrom bootpay_backend import BootpayCommerce
commerce = BootpayCommerce(
client_key='your-commerce-client-key',
secret_key='your-commerce-secret-key',
mode='production'
)
response = commerce.product.create(
name='드립백 커피 10개입',
desc='매일 아침 한 잔',
display_price=29900,
save_by=3,
tax_type=1,
ex_uid='shop-coffee-10',
status_visible=True,
status_display=True,
status_sale=True
)
print('product_id:', response['product_id'])pythonuse Bootpay\ServerPhp\BootpayCommerceApi;
$commerce = new BootpayCommerceApi("your-commerce-client-key", "your-commerce-secret-key");
$response = $commerce->productCreate([
'name' => '드립백 커피 10개입',
'desc' => '매일 아침 한 잔',
'display_price' => 29900,
'save_by' => 3,
'tax_type' => 1,
'ex_uid' => 'shop-coffee-10',
'status_visible' => true,
'status_display' => true,
'status_sale' => true
]);
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.SProduct;
TokenPayload tp = new TokenPayload("your-commerce-client-key", "your-commerce-secret-key");
BootpayStore commerce = new BootpayStore(tp).withToken();
SProduct product = new SProduct();
product.name = "드립백 커피 10개입";
// ⚠️ 상품 설명은 Java SDK 로 보낼 수 없어요 — SProduct.description 은 description 키로 나가는데
// 커머스 API 는 desc 를 읽어서 값이 조용히 버려져요. 설명은 관리자 화면이나 REST 직접 호출로 넣어요.
product.displayPrice = 29900.0;
product.statusDisplay = true;
product.statusSale = true;
BootpayStoreResponse response = commerce.product.create(product);
System.out.println(response);javarequire 'bootpay'
commerce = BootpayStore::RestClient.new(client_key: 'your-commerce-client-key', secret_key: 'your-commerce-secret-key')
response = commerce.product_create(
name: '드립백 커피 10개입',
desc: '매일 아침 한 잔',
display_price: 29900,
save_by: 3,
tax_type: 1,
ex_uid: 'shop-coffee-10',
status_visible: true,
status_display: true,
status_sale: true
)
puts responserubycommerce := bootpay.NewCommerceApi("your-commerce-client-key", "your-commerce-secret-key")
response, err := commerce.ProductCreate(map[string]interface{}{
"name": "드립백 커피 10개입",
"desc": "매일 아침 한 잔",
"display_price": 29900,
"save_by": 3,
"tax_type": 1,
"ex_uid": "shop-coffee-10",
"status_visible": true,
"status_display": true,
"status_sale": true,
})
fmt.Println(response)gousing Bootpay.Commerce;
var commerce = new BootpayCommerceApi("your-commerce-client-key", "your-commerce-secret-key");
var response = await commerce.ProductCreate(new {
name = "드립백 커피 10개입",
desc = "매일 아침 한 잔",
display_price = 29900,
save_by = 3,
tax_type = 1,
ex_uid = "shop-coffee-10",
status_visible = true,
status_display = true,
status_sale = true
});
Console.WriteLine(response);csharp응답
성공 응답
{
"product_id": "67c95e64d01640bb9859c629",
"save_by": 3
}json지금은 상품이 저장된 뒤에 SERVER_ERROR(HTTP 500)가 돌아와요. 같은 요청을 다시 보내면 상품이 하나 더 만들어질 수 있어요.
등록할 때 ex_uid 를 함께 보내요. 500 을 받으면 재시도하기 전에 상품 목록에서 ex_uid 로 저장 여부와 product_id 를 확인해요. 중복 생성 방지 여부를 ex_uid 만으로 단정하지 마세요.
에러 응답
HTTP/1.1 409 Conflict
Content-Type: application/json{
"error_code": "PRODUCT_PRICE_INVALID",
"message": "상품가격은 0원 이상이여야 합니다."
}json에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
PRODUCT_PRICE_INVALID |
상품가격은 0원 이상이어야 해요. (HTTP 409) | display_price 를 0보다 크게 넣어요. 0원은 사용량 기반·수시결제 구독 상품만 돼요 |
지금은 일반 상품의 display_price 가 0 이거나 없으면 PRODUCT_PRICE_INVALID 대신 SERVER_ERROR(HTTP 500)가 와요. display_price 는 항상 0보다 큰 값으로 보내요.
product_id는 주문서 요청 시 products[].product_id에 사용돼요.
