공통 규약
- 인증
Authorization: PortOne {API_SECRET}- 요청 형식
- 변경 요청(POST·PUT·PATCH·DELETE)은 JSON 본문으로 보냅니다. 조회 요청(GET)은 같은 형태의 JSON을
requestBody쿼리 파라미터에 담습니다 — 예:GET /subscriptions?requestBody={"page":{"number":0,"size":20}} - 멱등성
Idempotency-Key헤더를 변경 요청에 사용합니다. 같은 키로 다시 호출하면 저장된 응답을 그대로 돌려주며 유효 기간은 24시간입니다. 처리 중인 키로 다시 호출하면 거절됩니다. 결제가 일어나는 요청(고객 등록·구독 생성·인보이스 생성·재시도·재생성)에는 반드시 사용하시기를 권장합니다.- 리소스 id
- 상품·할인·고객·구독의
id는 가맹점이 직접 정합니다. 영문·숫자와. _ -만 쓸 수 있고 64자까지입니다. 고객을 뺀 나머지는 생략하면 자동으로 부여합니다. - 페이징
- 요청
page { number, size }—number는 0부터. 응답pageInfo { number, size, totalCount }. - 에러
{ type, message, … }형태이며type은 대문자 스네이크 표기입니다. 결제 실패는PAYMENT_FAILED { paymentId, pgCode, pgMessage }로 내려갑니다.- 일시
- ISO 8601에 KST 오프셋을 포함합니다. 분 단위로 의미를 갖는 필드(
billingAt등)는 초가 0입니다. - 금액
- 통화의 최소 단위 정수로 표기하고
currency(ISO 4217)를 함께 내려줍니다. 부가세는 KRW에서만 계산합니다 — 청구 금액 ÷ 1.1이 공급가액, 나머지가 부가세(원 단위 반올림)이며, KRW 외 통화는 부가세가 0원입니다. 할인으로 청구 금액이 0원이 되면 결제를 시도하지 않고 완료 처리합니다. - 결제수단
- 카드 빌링키만 지원합니다. 인보이스의 결제수단은
CARD_BILLING으로 고정입니다. - 청구 처리
- 청구 예정 일시가 지나면 몇 분 안에 인보이스를 만들고 결제합니다. 정확히 그 시각은 아닙니다. 지난 회차를 건너뛰지 않으므로, 어떤 이유로 청구가 밀렸더라도 회차마다 인보이스가 하나씩 만들어집니다.
- 결과 확인
- 결제 요청은 보냈지만 결과를 받지 못한 인보이스는
PENDING으로 잠시 머뭅니다. 10분쯤 뒤 결제 상태를 확인해 완료나 실패로 정리하므로 같은 건이 두 번 결제되지 않습니다. - 변경 반영
- 구독·상품·할인 변경은 모두 다음 결제부터 반영됩니다. 일할 계산(비례배분)은 하지 않으며 이미 결제된 인보이스는 바뀌지 않습니다.
- metadata
- 상품·고객·구독에 가맹점이 원하는 키-값을 붙일 수 있습니다. 목록 조회에서 필터로도 쓰이며, 보낸 키-값이 모두 일치하는 건만 걸러집니다. 키는 50개까지, 키는 40자까지, 값은 500자까지입니다.
시작 흐름
고객을 등록하려면 빌링키가 먼저 있어야 합니다. 빌링키는 브라우저 SDK에서 발급받습니다.
- 빌링키 발급 — 브라우저 SDK의 빌링키 발급 메서드(
requestIssueBillingKey)로 카드 빌링키를 발급받습니다. - 고객 등록 — 발급받은 빌링키로
POST /customers를 호출해 고객을 등록합니다.subscription을 함께 보내면 구독까지 한 번에 만들 수 있습니다. - 구독 생성 — 이미 등록된 고객에 구독을 추가할 때는
POST /subscriptions를 사용합니다.
빌링키를 교체할 때는 SDK로 새 빌링키를 발급받은 뒤 POST /customers/{id}/payment-method/revoke로 기존 빌링키를 해지하고, POST /customers로 새 빌링키를 등록합니다. 기존 빌링키가 살아 있는 상태에서 새 빌링키를 등록하면 CustomerHasBillingKey로 거절됩니다.
상품 /products
구독을 등록하기 위해 상품을 등록합니다. 결제 주기와 가격은 상품에서 정해집니다.
product 필드
| 필드 | 타입 | 설명 | 변경 |
|---|---|---|---|
| id | string | 가맹점이 지정하거나 자동 부여 | 불가 |
| name | string | 상품명 | PATCH |
| description | string? | 설명 | PATCH |
| pricingType | enum | RECURRING · ONE_TIME | 불가 |
| interval | enum? | 결제 주기. RECURRING일 때만 존재 | 불가 |
| amount | int | 1 이상. currency와 함께 사용 | PATCH (다음 결제부터) |
| currency | string | ISO 4217 | 불가 |
| billingPaused | bool | 청구 중지 여부 | pause · resume-billing |
| archived | bool | 아카이브 여부 | archive (복구 불가) |
| metadata | object | 가맹점이 붙이는 키-값 | PATCH |
엔드포인트
- 요청
{ "id": "prd_english", "name": "영어 심화", "description": "주 2회 화상 수업", "pricingType": "RECURRING", "interval": "MONTHLY", "amount": 99000, "currency": "KRW", "metadata": { "category": "class" } }- 규칙
id·description·metadata는 선택입니다.id를 생략하면 자동으로 부여합니다.RECURRING이면interval이 필수이고,ONE_TIME이면 넣을 수 없습니다.amount는 1 이상입니다.- 이미 있는
id면 409입니다.
- 응답
- product
- 규칙
아카이브된 상품도 조회됩니다. 없으면 404입니다.
- 응답
- product
- 요청
{ "page": { "number": 0, "size": 20 }, "archived": false, "metadata": { "category": "class" } }- 규칙
archived기본값은false로 아카이브된 상품은 제외됩니다. 청구가 중지된 상품은billingPaused: true로 목록에 포함됩니다.metadata는 선택이며 보낸 키-값이 모두 일치하는 상품만 걸러냅니다. 생성일 역순입니다.- 응답
- product[] + pageInfo
- 요청
{ "name": "영어 심화 (2026)", "description": "…", "amount": 109000 }- 규칙
- 보낸 필드만 바뀝니다.
id·pricingType·interval은 바꿀 수 없습니다.- 금액 변경은 다음 결제부터 적용됩니다. 인보이스는 결제 시점의 가격을 씁니다.
- 아카이브된 상품은 409입니다.
- 응답
- product
- 규칙
이 상품이 담긴 활성 구독의 인보이스 생성과 결제가 멈추고, 신규 구독 등록도 막힙니다. 구독과 고객 정보는 바뀌지 않습니다. 이미 중지 상태면 아무 일도 하지 않습니다.
- 응답
- product
- 규칙
이 상품이 담긴 구독은 다음에 도래하는 청구일부터 생성·결제를 재개합니다. 재개하는 시점에 각 구독의 다음 청구 예정일을 지금 이후 첫 회차로 다시 잡으므로, 중지 기간에 지나간 회차는 청구하지 않습니다. 다시 잡은 날짜가 구독 종료일을 넘으면 그 구독은 종료됩니다. 중지 상태가 아니면 아무 일도 하지 않습니다.
- 응답
- product
- 규칙
목록에서 숨기고 신규 구독 등록을 막습니다. 이미 이 상품을 담고 있는 구독은 계속 청구됩니다. 되돌릴 수 없고 같은
id를 다시 쓸 수 없습니다.- 응답
- product
할인 /discounts
구독에 붙여 인보이스 금액을 깎는 규칙입니다. 지속 기간은 날짜가 아니라 회차로 관리합니다. 한 구독에 서로 다른 할인을 여러 개 붙일 수 있습니다.
discount 필드
| 필드 | 타입 | 설명 | 변경 |
|---|---|---|---|
| id | string | 가맹점이 지정하거나 자동 부여 | 불가 |
| name | string | 인보이스와 화면에 표시되는 이름 | PATCH |
| type | enum | PERCENT · AMOUNT | 불가 |
| value | int | PERCENT는 1~100, AMOUNT는 정수 금액 | 불가 |
| currency | string? | ISO 4217. AMOUNT일 때 필수이며 PERCENT에는 없습니다 | 불가 |
| appliesTo | object | { "type": "SUBSCRIPTION" }이면 인보이스 전체에, { "type": "PRODUCTS", "productIds": [...] }이면 지정한 상품 항목에만 적용. 자세한 계산은 아래 할인 계산을 봐 주세요 | 불가 |
| duration | object | { "type": "ONCE" } · { "type": "CYCLES", "count": n } · { "type": "FOREVER" } | 불가 |
| redeemBy | date? | 적용 마감일. 이 날이 지나면 구독에 새로 붙일 수 없습니다 | PATCH |
| archived | bool | 보관 여부 | archive · unarchive |
할인 계산
- 정률(PERCENT)을 먼저 적용합니다. 구독에 붙인 순서대로, 적용 범위에 드는 항목마다 금액을 곱해 깎습니다. 항목별로 1원 단위 반올림합니다.
- 정액(AMOUNT)은 그다음입니다. 전체 범위 할인은 합계에서 한 번 빼고, 상품 지정 할인은 대상 항목마다 한 번씩 뺍니다(수량과 무관). 각 항목 금액과 남은 합계를 넘지 않습니다.
- 적용 범위에 드는 항목이 인보이스에 하나도 없으면 그 할인은 반영되지 않고 잔여 회차도 줄지 않습니다.
- 합계가 0원 아래로 내려가지 않습니다. 0원이 되면 결제 없이 완료 처리합니다.
appliedDiscount 필드 구독에 붙은 할인
| 필드 | 타입 | 설명 |
|---|---|---|
| discountId | string | 붙어 있는 할인의 참조 |
| appliedAt | datetime | 구독에 연결한 일시 |
| remainingCount | int | null | 남은 적용 회차. ONCE는 1, CYCLES(n)은 n에서 시작해 할인이 실제로 반영된 인보이스가 만들어질 때마다 1씩 줄어듭니다. FOREVER는 null이며 소진되지 않습니다 |
| status | enum | ACTIVE · EXHAUSTED(회차 소진) · REMOVED(제거됨) |
엔드포인트
- 요청
{ "id": "disc_welcome10", "name": "첫 가입 10%", "type": "PERCENT", "value": 10, "appliesTo": { "type": "SUBSCRIPTION" }, "duration": { "type": "CYCLES", "count": 3 }, "redeemBy": "2026-12-31" }- 규칙
id·redeemBy·appliesTo는 선택입니다.appliesTo기본값은SUBSCRIPTION입니다.PRODUCTS로 지정하면productIds가 하나 이상 필요하고, 그 상품이 있고 아카이브되지 않았어야 합니다.duration.type은ONCE·CYCLES·FOREVER중 하나이고,CYCLES는count(1 이상)가 필수입니다.PERCENT는 1~100이며currency를 넣지 않습니다.AMOUNT는 1 이상이고currency가 필수입니다.redeemBy가 오늘(KST)보다 이전이면 거절합니다.
- 응답
- discount
- 규칙
보관된 할인도 조회됩니다. 없으면 404입니다.
- 응답
- discount
- 요청
{ "page": { "number": 0, "size": 20 }, "archived": false }- 규칙
archived기본값은false입니다. 생성일 역순입니다.- 응답
- discount[] + pageInfo
- 요청
{ "name": "첫 가입 10% (연장)", "redeemBy": "2027-03-31" }- 규칙
이름과 적용 마감일만 바꿀 수 있습니다. 할인 조건(
type·value·currency·duration·appliesTo)은 만든 뒤에 바꿀 수 없습니다 — 조건을 바꾸려면 새 할인을 만들어 주세요.- 응답
- discount
- 규칙
새로 연결하는 것만 막습니다. 이미 붙어 있는 구독에서는 잔여 회차까지 그대로 적용됩니다. 이미 보관 상태면 아무 일도 하지 않습니다.
- 응답
- discount
- 규칙
다시 신규 연결이 가능해집니다.
- 응답
- discount
고객 /customers
고객은 빌링키와 함께 등록합니다. 빌링키 없이 고객만 만들 수는 없으며, 고객당 빌링키는 하나입니다.
customer 필드
| 필드 | 타입 | 설명 | 변경 |
|---|---|---|---|
| id | string | 가맹점이 지정. 항상 필수 | 불가 |
| name | string | 신규 등록 시 필수 | POST · PATCH |
| string? | 선택. 결제 결과 인보이스를 받는 주소이며, 메일을 보내는 구독을 만들려면 반드시 있어야 합니다(없으면 CustomerEmailRequired) | POST · PATCH | |
| phone | string? | 선택 | POST · PATCH |
| businessRegistrationNumber | string? | 사업자번호. 선택, 숫자 10자리 | POST · PATCH |
| status | enum | ACTIVE 하나뿐입니다 | 시스템 |
| billingKey | string? | 현재 빌링키. 고객당 1개 | 등록 · 해지 시 null |
| metadata | object | 가맹점이 붙이는 키-값 | POST · PATCH |
엔드포인트
- 요청
{ "id": "cust_001", "name": "홍길동", "email": "hong@example.com", "phone": "010-1234-5678", "businessRegistrationNumber": "1234567890", "billingKey": "billing-key-…", "metadata": { "grade": "vip" }, "subscription": { "items": [ { "productId": "prd_english", "quantity": 1 } ], "discountIds": ["disc_welcome10"] } }- 규칙
- 고객을 새로 만들거나, 이미 있으면 보낸 필드만 갱신합니다.
billingKey는 항상 필수이고 신규 고객이면name도 필수입니다. - 빌링키는 그 상점이 소유한 카드 빌링키여야 합니다. 아니면
BillingKeyNotFound입니다. - 다른 고객이 쓰고 있는 빌링키면
BillingKeyAlreadyRegistered { customerId }입니다. - 이 API는 등록만 합니다. 살아 있는 빌링키를 가진 고객이면
CustomerHasBillingKey로 거절하므로 교체하려면 먼저 해지해야 합니다. 이미 해지된 빌링키를 들고 있으면 새 빌링키로 바뀝니다. subscription을 함께 보내면 구독까지 한 번에 만듭니다. 필드는 구독 생성과 같고, 구독이 실패하면 고객도 만들어지지 않습니다.
- 고객을 새로 만들거나, 이미 있으면 보낸 필드만 갱신합니다.
- 응답
- customer (+ subscription)
- 규칙
없으면 404입니다.
- 응답
- customer
- 요청
{ "page": { "number": 0, "size": 20 }, "metadata": { "grade": "vip" } }- 규칙
metadata는 선택입니다. 생성일 역순입니다.- 응답
- customer[] + pageInfo
- 요청
{ "page": { "number": 0, "size": 20 }, "status": "ACTIVE" }- 규칙
status는 선택입니다. 생성일 역순이고, 고객이 없으면 404입니다.- 응답
- subscription[] + pageInfo
- 요청
{ "page": { "number": 0, "size": 20 }, "from": "2026-06-09T00:00:00+09:00", "to": "2026-09-07T23:59:59+09:00" }- 규칙
from·to는 선택이며billingAt을 기준으로 거릅니다. 기본값은from90일 전,to현재입니다. 그 고객의 모든 구독 인보이스를billingAt역순으로 돌려줍니다.- 응답
- invoice[] + pageInfo
- 요청
{ "name": "홍길동", "email": "hong@example.com", "phone": "010-1234-5678", "businessRegistrationNumber": "1234567890", "metadata": { "grade": "vip" } }- 규칙
모든 필드가 선택이며 보낸 필드만 바뀝니다.
id와 빌링키는 바꿀 수 없습니다. 이메일 변경은 다음 결제 결과 인보이스부터 반영됩니다. 사업자번호는 숫자 10자리 형식을 검사합니다.- 응답
- customer
- 요청
{ "reason": "고객 요청" }- 규칙
reason은 선택이지만 일부 결제사가 필수로 요구하므로 채워 보내시길 권합니다.- 현재 빌링키를 해지하고
billingKey를null로 만듭니다. 이미 해지된 빌링키는 성공으로 처리합니다. - 해지에 실패하면
BillingKeyHasSchedules또는BillingKeyRevokeFailed { pgCode, pgMessage }입니다. - 구독은
ACTIVE로 남지만, 청구일이 와도 빌링키가 없으면 인보이스를 만들지 않고 건너뜁니다. - 다시 등록하려면 SDK로 빌링키를 새로 발급받아
POST /customers를 호출합니다.
- 응답
- customer
구독 /subscriptions
청구 시작일이 곧 구독 시작일입니다. 종료일을 지정하면 그 날짜까지만 청구하고 자동으로 종료합니다. 구독을 만들려면 고객에게 유효한 빌링키가 있어야 합니다.
subscription 필드
| 필드 | 타입 | 설명 | 변경 |
|---|---|---|---|
| id | string | 가맹점이 지정하거나 자동 부여 | 불가 |
| customerId | string | 고객. 결제에는 그 고객의 현재 빌링키를 사용합니다 | 불가 |
| items[] | { productId, quantity }[] | 상품과 수량. 수량은 1 이상 | PATCH (다음 결제부터) |
| interval | enum? | 담긴 정기 상품들의 공통 주기. 일회성 상품만 있는 구독은 값이 없습니다 | 불가 |
| currency | string | 담긴 상품에서 정해집니다. 통화가 섞이면 구독을 만들 수 없습니다 | 불가 |
| billStartDate | date | 청구 시작일이자 구독 시작일. 청구 기준일의 최초 출처이며 기본값은 오늘 | 불가 |
| endDate | date? | 구독 종료일. 다음 청구 예정일이 이 날짜를 넘으면 인보이스를 만들지 않고 자동 종료합니다. 종료 판정은 청구 직후뿐 아니라 종료일 변경·재개·첫 회차 회수처럼 다음 청구일을 다시 잡는 모든 시점에 일어납니다. null이면 무기한 | PATCH |
| anchorDate | date | 청구 기준일. 기본값은 billStartDate이고 PATCH의 nextBillingDate로만 바뀝니다(말일 보정으로는 바뀌지 않습니다) | PATCH (nextBillingDate) |
| anchorTime | HH:MM | 청구 시각. 구독을 만든 시각(KST 분 단위)으로 정해지며 가맹점이 입력하지 않습니다 | 불가 |
| lastBilledAt | datetime? | 마지막으로 청구한 일시 | 시스템 |
| nextBillingAt | datetime? | 다음 청구 예정 일시. 일시중지·종료 상태면 값이 없습니다 | 시스템 |
| discounts[] | appliedDiscount[] | 붙어 있는 할인 목록. 개수 제한은 없고 같은 할인을 중복해서 붙일 수는 없습니다 | 할인 추가 · 제거 |
| sendInvoiceEmail | bool | 결제 결과 인보이스 메일 발송 여부. 기본값은 고객 이메일이 있으면 true | PATCH |
| status | enum | ACTIVE · INCOMPLETE · PAUSED · ENDED | pause · resume · cancel |
| endedAt endReason | datetime? · enum? | 종료 일시와 사유 — CANCELED(해지) · ONE_TIME_EXHAUSTED(일회성 소진) · END_DATE_REACHED(종료일 도달) | 시스템 |
| metadata | object | 가맹점이 붙이는 키-값 | PATCH |
엔드포인트
- 요청
{ "id": "sub_001", "customerId": "cust_001", "items": [ { "productId": "prd_english", "quantity": 1 } ], "discountIds": ["disc_welcome10"], "billStartDate": "2026-10-01", "endDate": "2027-09-30", "sendInvoiceEmail": true, "metadata": { "planName": "basic" } }- 규칙
id·discountIds·billStartDate·endDate·sendInvoiceEmail·metadata는 선택입니다.billStartDate기본값은 오늘이고 지난 날짜는 거절합니다.endDate는billStartDate와 같거나 뒤여야 합니다.- 고객에게 유효한 빌링키가 있어야 합니다.
- 담긴 정기 상품은 주기가 모두 같아야 하고, 통화도 모두 같아야 합니다. 아카이브·청구 중지 상품은 담을 수 없습니다.
- 일회성 상품만으로도 만들 수 있으며, 이때는 결제 주기가 없습니다.
- 할인은 있어야 하고, 보관되지 않았고, 적용 마감일 이내여야 합니다.
PRODUCTS형은 그 상품이items에 있어야 하고 같은 할인을 중복 지정할 수 없습니다. sendInvoiceEmail기본값은 고객 이메일이 있으면true입니다.true인데 이메일이 없으면CustomerEmailRequired입니다.- 같은
id로 다시 호출하면 409이고 결제가 다시 나가지 않습니다.id를 지정하지 않는다면Idempotency-Key로 중복 생성을 막아 주세요.
당일 시작이면 첫 인보이스를 바로 결제합니다. 결제가 실패해도 요청은 성공(200)이고 구독은INCOMPLETE, 인보이스는FAILED로 남습니다. 그 인보이스를 재시도·재생성하거나 수동 완납으로 처리해 첫 회차 대금을 받으면 구독이ACTIVE로 돌아가고, 다음 청구 예정일이 지금 이후 첫 회차로 다시 잡힙니다.- 응답
- subscription (+ 첫 invoice)
- 규칙
붙어 있는 할인 목록(각 항목의 잔여 회차와 상태 포함),
nextBillingAt,endDate가 함께 내려갑니다. 없으면 404입니다.- 응답
- subscription
- 요청
{ "page": { "number": 0, "size": 20 }, "status": "ACTIVE", "customerId": "cust_001", "metadata": { "planName": "basic" } }- 규칙
status·customerId·metadata가 모두 선택입니다. 생성일 역순입니다.- 응답
- subscription[] + pageInfo
- 요청
{ "items": [ { "productId": "prd_english", "quantity": 2 } ], "endDate": "2028-03-31", "nextBillingDate": "2026-10-20", "sendInvoiceEmail": false, "metadata": { "planName": "pro" } }- 규칙
- 보낸 필드만 바뀌고, 변경은 다음 결제부터 반영됩니다. 결제 직전까지 바꿀 수 있습니다.
items는 전체 교체입니다. 추가·삭제·수량 변경을 한 번에 보냅니다.- 교체해도 주기와 통화는 기존 구독과 같아야 하고, 아카이브·청구 중지 상품은 담을 수 없습니다.
endDate를null로 보내면 무기한으로 되돌립니다.- 종료일을 앞당겨 다음 청구 예정일이 그 날짜를 넘게 되면 남은 청구가 없으므로 구독이 그 자리에서 종료됩니다(
END_DATE_REACHED). - 종료된 구독이면 409입니다.
nextBillingDate로 정기결제일을 옮깁니다.YYYY-MM-DD날짜만 받고 시각은 넣지 않습니다. 오늘보다 뒤이고 종료일 이내여야 합니다. 지정한 날짜가 새 청구 기준일이 되며 기준 시각과 회차 번호는 그대로입니다. 이후 회차는 이 기준일에서 다시 계산됩니다.- 응답
- subscription
- 요청
{ "discountId": "disc_retention20" }- 규칙
같은 할인이 이미 붙어 있으면 409이고, 다른 할인은 개수 제한 없이 계속 추가할 수 있습니다.
PRODUCTS형이면 그 상품이 구독에 담겨 있어야 합니다. 적용 마감일과 보관 여부, 통화 일치를 검사합니다. 잔여 회차는 지속 방식에 따라 초기화됩니다. 다음 결제부터 적용되며 결제 직전까지 추가할 수 있습니다.- 응답
- subscription
- 규칙
구독에 붙어 있는 할인을
discountId·잔여 회차·상태·연결 일시와 함께 돌려줍니다. 제거된 할인은 빠집니다.- 응답
- appliedDiscount[]
- 규칙
지정한 할인 하나만 제거합니다. 다음 결제부터 그 할인이 적용되지 않습니다. 붙어 있지 않으면 아무 일도 하지 않습니다.
- 응답
- subscription
- 규칙
본문이 없습니다. 기간을 정하지 않고 중지하며, 중지 중에는 인보이스 생성과 결제가 없습니다.
ACTIVE가 아니면 409입니다.- 응답
- subscription
- 규칙
본문이 없습니다. 청구 기준일과 기준 시각을 그대로 유지하므로, 재개한 날짜와 무관하게 원래 정기결제일에 다음 인보이스를 만들어 결제합니다. 재개 시점에 즉시 결제하지 않으며 중지 중 건너뛴 회차도 소급하지 않습니다. 다시 잡은 청구일이 종료일을 넘으면 재개 대신 구독이 종료됩니다.
PAUSED가 아니면 409입니다.- 응답
- subscription
- 규칙
즉시 종료합니다. 이후 인보이스는 만들어지지 않습니다. 이미 종료된 구독이면 아무 일도 하지 않습니다.
- 응답
- subscription
- 규칙
- 다음 결제 때 만들어질 인보이스를 계산만 해서 보여주고 저장하지 않습니다.
- 구독의 현재 상태(담긴 상품과 현재 가격, 일회성 상품 포함 여부, 할인, 수량)로 계산하므로 결제 직전까지 값이 바뀔 수 있습니다.
- 다음 인보이스를 만들지 않을 상태면
null입니다 — 일시중지·종료, 청구 중지 상품 포함, 빌링키 없음, 종료일 초과.
- 응답
- { billingAt, sequence, items[], discounts[], totalAmount, supplyAmount, vatAmount }
- 요청
{ "page": { "number": 0, "size": 20 }, "from": "2026-06-09T00:00:00+09:00", "to": "2026-09-07T23:59:59+09:00" }- 규칙
from·to는 선택이며billingAt기준입니다. 기본값은from90일 전,to현재이고billingAt역순으로 내려갑니다.- 응답
- invoice[] + pageInfo
인보이스 /invoices
인보이스를 미리 만들어 두지 않습니다. 청구 예정 일시가 되면 그 시점의 구독 상태로 인보이스를 만들고 곧바로 결제합니다. 수동 청구도 같은 방식으로 생성과 동시에 결제합니다.
invoice 필드
| 필드 | 타입 | 설명 |
|---|---|---|
| id | string | 자동 부여 |
| subscriptionId | string | 수동 청구도 반드시 구독에 속합니다. 구독 없는 단독 인보이스는 없습니다 |
| sequence | int? | 정기 청구의 회차 번호(1부터). 수동 청구는 null |
| regenerationSeq | int | 기본 0. 재생성할 때마다 1씩 증가합니다 |
| status | enum | PENDING(결제 진행 중) · PAID · FAILED · CANCELED · PAID_MANUALLY |
| source | enum | AUTO(정기 청구) · MANUAL(수동 청구) |
| billingAt | datetime | 청구 기준 일시. 정기 청구는 구독의 기준일·기준 시각, 수동 청구는 요청한 시각 |
| items | array | 생성 시점에 확정되는 스냅샷. productId+quantity+unitPrice 또는 name+unitPrice+quantity |
| discounts | array | 적용된 할인 스냅샷 { discountId, name, type, value, productIds?, discountAmt }. 정기 청구에만 자동 적용됩니다 |
| currency | string | ISO 4217 |
| supplyAmount vatAmount totalAmount | int | KRW에서만 부가세를 계산하며, 그 외 통화는 vatAmount가 0입니다 |
| memo | string? | 수동 청구와 수동 완납 시 남기는 메모 |
| paidAt | datetime? | 결제 완료 또는 수동 완납 처리 시각 |
| payment | object? | 결제 건 정보 { paymentId, billingKey, failure? }. 카드로 결제를 시도한 인보이스에만 있으며 0원 인보이스와 수동 완납 건에는 없습니다. paymentId로 결제 내역을 조회할 수 있습니다 |
| lastFailure | object? | 마지막 결제 실패 사유 { pgCode, pgMessage }. 결제가 완료되거나 수동 완납으로 정리되면 지워집니다 |
엔드포인트
- 규칙
없으면 404입니다.
- 응답
- invoice
- 요청
{ "subscriptionId": "sub_001", "items": [ { "productId": "prd_english", "quantity": 1 }, { "name": "교재비", "unitPrice": 15000, "quantity": 1 } ], "memo": "9월 교재 추가" }- 규칙
subscriptionId와items가 필수이고memo는 선택입니다.- 항목은
productId+quantity로 보내면 단가를 상품에서 채웁니다. 등록된 상품이 아니면name+unitPrice+quantity로 직접 적습니다. - 상품으로 보낸 항목은 그 상품이 있어야 하고, 청구가 중지된 상품은 담을 수 없습니다.
- 생성과 동시에 결제합니다.
- 회차가 없고(
sequence는null) 구독의 회차 번호와 다음 청구 예정일도 바뀌지 않습니다. - 구독에 붙은 할인은 자동으로 적용되지 않습니다.
- 종료된 구독이면 409입니다. 일시중지·미완성 구독에는 청구할 수 있습니다.
- 응답
- invoice
- 규칙
- 실패한 인보이스에만 쓸 수 있습니다. 아니면 409입니다.
- 같은 결제 건으로 다시 시도하고 결과를 응답에 담아 돌려줍니다. 이미 결제된 건이면 결제 완료 상태로 맞춥니다.
- 결제에는 고객의 현재 빌링키를 씁니다. 카드를 새로 등록했다면 새 카드로 시도하며, 빌링키가 없으면
CustomerHasNoBillingKey입니다. - 시도하는 동안 인보이스는
PENDING이라 같은 건에 취소·수동 완납을 할 수 없습니다. - 첫 회차가 이 요청으로 결제되면
INCOMPLETE였던 구독이ACTIVE로 돌아갑니다.
- 응답
- invoice
- 규칙
- 실패한 인보이스에만 쓸 수 있습니다. 아니면 409입니다.
- 취소 상태로 바뀌고, 그 인보이스에 반영됐던 할인의 잔여 회차를 구독에 돌려줍니다.
- 회차를 다 써서 소진된 할인은 다시 쓸 수 있는 상태가 됩니다.
- 응답
- invoice
- 규칙
- 취소된 인보이스에만 쓸 수 있습니다. 아니면 409입니다.
- 구독의 현재 상태(상품의 현재 가격·할인 등)로 같은 회차의 인보이스를 새로 만들어 즉시 결제합니다.
regenerationSeq가 1 늘어나고 원본은 취소 상태로 남습니다.- 금액을 직접 받지 않습니다. 금액을 바꾸려면 구독이나 상품·할인을 먼저 고친 뒤 호출해 주세요.
- 같은 원본으로 두 번 호출하면
InvoiceAlreadyRegenerated입니다. - 회차가 없는 수동 청구는 다시 계산할 기준이 없어 대상이 아닙니다. 같은 내용으로 새로 청구해 주세요.
- 종료된 구독이거나 고객에게 빌링키가 없으면 거절합니다.
- 응답
- invoice
- 요청
{ "memo": "9/5 계좌 입금 확인" }- 규칙
- 계좌 입금처럼 API 밖에서 받은 대금을 정리할 때 씁니다.
memo는 선택입니다. - 실패한 인보이스에만 쓸 수 있습니다. 아니면 409입니다.
- 수동 완납 상태가 되고 처리 시각이
paidAt에 기록됩니다. - 이전 결제 실패 사유는 지워지고 결제 정보(
payment)도 내려가지 않습니다. - 첫 회차를 이렇게 처리하면
INCOMPLETE였던 구독이ACTIVE로 돌아갑니다.
- 계좌 입금처럼 API 밖에서 받은 대금을 정리할 때 씁니다.
- 응답
- invoice
값 목록
| 구분 | 값 |
|---|---|
| product.pricingType | RECURRING · ONE_TIME |
| product.interval | DAILY · DAYS(n) · WEEKLY · MONTHLY · MONTHS(n) · YEARLY |
| discount.type | PERCENT(1~100) · AMOUNT(1 이상, currency 필수) |
| discount.appliesTo.type | SUBSCRIPTION · PRODUCTS |
| discount.duration.type | ONCE · CYCLES(count 필요) · FOREVER |
| appliedDiscount.status | ACTIVE · EXHAUSTED · REMOVED |
| customer.status | ACTIVE |
| subscription.status | ACTIVE · INCOMPLETE(첫 결제 실패) · PAUSED · ENDED |
| subscription.endReason | CANCELED · ONE_TIME_EXHAUSTED · END_DATE_REACHED |
| invoice.status | PENDING(결제 진행 중) · PAID · FAILED · CANCELED · PAID_MANUALLY |
| invoice.source | AUTO · MANUAL |
| 결제수단 | CARD_BILLING (카드 자동이체) |
검색어와 일치하는 엔드포인트가 없습니다.