개별 API는 구독 계약 / 가입, 구독 > 운영 / 회차 에서 확인해요. 이 문서는 라이프사이클을 한 장으로 봐요.
구독 구현 전에 블로그 가이드를 봐요.
- 자동결제는 언제 쓰나요? — 자동결제와 일반 결제의 차이
- 구독 상품 유형별 차이 — 콘텐츠·SaaS·정기배송별 운영 정책
- 구독 주기 설계 — 월/연/커스텀 주기 결정
- 결제 실패 재시도 정책 — 재시도 횟수·간격
- 구독 해지 정책 — 잔여 기간·환불·수수료
구독 사이드바는 구독 메뉴 안의 계약 정의·가입·운영 그룹으로 나뉘어 있어요. 이 구독 메뉴는 이 문서를 라이프사이클 단일 소스로 참조해요.
전체 시퀀스
참여자: 고객 | 가맹점 프론트 | 가맹점 서버 | Bootpay | PG사
| 단계 | 흐름 | 유형 | 비고 |
|---|---|---|---|
| 1 | 고객 -> 가맹점 프론트: 구독 상품 선택 | 실선 | |
| 2 | 가맹점 프론트 -> Bootpay: 빌링키 발급 SDK 호출 | 실선 | 카드 수집은 SDK ↔ PG 직접 |
| 3 | Bootpay -> PG사: 빌링키 발급 요청 | 실선 | |
| 4 | PG사 -> Bootpay: 빌링키 반환 | 점선 | |
| 5 | Bootpay -> 가맹점 프론트: billing_key 반환 |
점선 | |
| 6 | 가맹점 프론트 -> 가맹점 서버: billing_key + 구독 상품 정보 전달 |
실선 | |
| 7 | 가맹점 서버 -> Bootpay: 구독 계약 생성 | 실선 | 고객 × 구독 상품 × 빌링키 |
| 8 | 가맹점 서버 -> Bootpay: 계약 승인 | 실선 | 첫 회차 예약. 템플릿의 use_approval_auto 가 켜져 있으면 생략돼요 |
| 9 | Bootpay: 회차 스케줄러에 등록 | 실선 | status = subscribing(1) |
| 10 | Bootpay -> PG사: 매 회차 빌링키 결제 | 실선 | 자동 |
| 11 | Bootpay -> 가맹점 서버: 웹훅 (subscription.renewed / subscription.hold_on) |
실선 | |
| 12 | 가맹점 서버: 서비스 접근 제어 | 실선 | active/inactive 토글 |
11 단계의 subscription.renewed 와 아래 표의 subscription.expired 는 지금 발송되지 않고, 고객이 요청한 해지·일시정지에도 웹훅이 없어요. 현재 동작과 대신 확인하는 방법은 웹훅 처리 가이드의 알려진 문제에 모아 뒀어요. 만료는 계약 조회의 status(auto_terminated)로 확인해요.
구독 라이프사이클 상태 전이
구독 하나는 대기 → 구독 중 → (일시정지) → 해지/만료 흐름을 따라요. 상태 코드는 API 응답·웹훅에서 동일하게 쓰여요.
상태
| ID | 라벨 | 위치 | 타입 |
|---|---|---|---|
| pending | 승인 대기 (0) | col 0, row 0 | initial |
| active | 구독 중 (1) | col 1, row 0 | active |
| paused | 일시정지 (-2) | col 2, row 0 | warning |
| hold_on | 문제로 멈춤 (-3) | col 2, row 1 | warning |
| terminated | 해지 (20) | col 3, row 1 | end |
| expired | 자동 종료 (15) | col 3, row 0 | end |
| rejected | 거절 (-12) | col 0, row 1 | end |
전이 승인 대기 (0) -> 구독 중 (1) : 승인 승인 대기 (0) -> 거절 (-12) : 거절 구독 중 (1) -> 일시정지 (-2) : 일시정지 일시정지 (-2) -> 구독 중 (1) : 재개 구독 중 (1) -> 문제로 멈춤 (-3) : 결제 실패 자동 중단 구독 중 (1) -> 해지 (20) : 해지 요청 일시정지 (-2) -> 해지 (20) : 해지 요청 구독 중 (1) -> 자동 종료 (15) : 기간 종료
일시정지는 2 가 아니라 -2, 해지는 3 이 아니라 20, 만료는 4 가 아니라 15 예요.
-11 은 거절이 아니라 관리자 해지(admin_terminated)고, 거절은 -12 예요. 전체 값은 커머스 Enum이 원본이에요.
| 현재 | 다음 | 주체 | API |
|---|---|---|---|
contract_requested_completed (0) |
subscribing (1) |
관리자 | 신규 승인 |
contract_requested_completed (0) |
rejected (-12) |
관리자 | 구독 거절 |
subscribing (1) |
temporarily_paused (-2) |
고객·관리자 | 일시정지 |
temporarily_paused (-2) |
subscribing (1) |
고객 | 재개 |
subscribing (1) |
paused_due_to_issue (-3) |
자동 | 반복 결제 실패 (아래 재시도 흐름) |
subscribing (1) |
manual_terminated (20) |
고객·관리자·자동 | 해지 |
temporarily_paused (-2) |
manual_terminated (20) |
고객 | 해지 |
subscribing (1) |
admin_terminated (-11) |
관리자 | 관리자 해지 |
subscribing (1) |
auto_terminated (15) |
자동 | 기간 종료 |
manual_terminated · admin_terminated |
subscribing (1) |
관리자 | 해지 복구 — 공개 API 없음. subscription.restored 웹훅으로만 알 수 있어요 |
회차(빌링 사이클) 상태 (status)
구독이 승인되면 주기에 맞춰 회차가 생성돼요. 회차는 구독과 독립 상태를 가져요.
| 값 | 키 | 의미 | 다음 액션 |
|---|---|---|---|
2 |
bill_ready |
결제 예정 (스케줄러 등록됨) | 대기 |
1 |
bill_completed |
결제 완료 | 웹훅 subscription.renewed |
-1 |
bill_failed |
결제 실패 | 재시도 스케줄 |
6 |
bill_failure_delayed |
실패 후 재시도 대기 | 재시도 |
10 |
bill_cancelled |
회차 취소 (환불·관리자 취소) | — |
20 |
bill_terminated |
해지로 회차 취소 | — |
-11 |
bill_schedule_cancelled_by_payment_failure |
반복 결제 실패로 스케줄 취소 | 아래 재시도 흐름 참고 |
전체 값은 커머스 Enum에 있어요.
회차 조회·수정 API는 회차 조회 · 회차 금액 조정 참고.
자동 과금 재시도 흐름
결제 실패 시 Bootpay는 정책에 따라 재시도해요. 가맹점은 웹훅을 받아 서비스 접근만 제어하면 돼요.
흐름 스케줄 -> 결제 시도 결제 시도 -> 성공 결제 시도 -> 실패 실패 -> 재시도 대기 재시도 대기 -> 결제 시도 실패 -> 최종 실패
| 단계 | 주체 | 설명 |
|---|---|---|
| 결제 시도 | Bootpay | 회차 도래 시 빌링키로 자동 결제 |
| 성공 | Bootpay | 회차 bill_completed(1), 웹훅 subscription.renewed, 다음 회차 예약 |
| 실패 | Bootpay | 회차 bill_failed(-1) → bill_failure_delayed(6) 로 재시도 대기 |
| 재시도 대기 | Bootpay | 구독 템플릿의 재시도 설정에 따라 다시 결제 시도 |
| 최종 실패 | Bootpay | 재시도 횟수 초과 — 아래 두 갈래로 갈려요 |
최종 실패는 두 갈래예요
어느 쪽으로 갈지는 구독 템플릿의 설정이 정해요. 웹훅 이름도 상태도 다르니 둘 다 처리해야 해요.
| 템플릿 설정 | 구독 상태 | 웹훅 | 되돌리기 |
|---|---|---|---|
use_auto_unsubscribe 켜짐 |
manual_terminated (20) |
subscription.terminated |
관리자 복구만 가능 (subscription.restored) |
use_auto_pause 켜짐 |
paused_due_to_issue (-3) |
subscription.hold_on |
수동 복구 필요 — 고객이 결제수단을 고치고 복구 결제를 해야 해요 |
두 경우 모두 실패한 회차는 bill_schedule_cancelled_by_payment_failure(-11) 로 닫혀요.
결제 실패로 멈춘 것은 subscription.hold_on 이에요. subscription.paused 는 관리자가 일시정지한 것이라 대응이 달라요 — paused 에 결제수단 변경 안내를 붙이면 정상 이용 중인 고객에게 오안내가 나가요.
use_auto_unsubscribe 로 자동 해지된 구독도 상태 키는 manual_terminated(20) 예요. 「고객이 직접 해지」와 값이 같으니, 누가 끊었는지는 상태가 아니라 웹훅 수신 시점과 요청 이력으로 구분해요.
재시도 횟수·간격·최종 실패 후 동작(자동 중단 vs 자동 해지)은 구독 템플릿의 auto_unsubscribe_retry_count · use_auto_pause · use_auto_unsubscribe 로 정해요. 블로그의 결제 실패 재시도 정책 을 먼저 정리한 뒤 적용해요.
웹훅 이벤트 매핑
가맹점 서버는 아래 이벤트를 받아 서비스 상태와 DB를 동기화해요.
| 이벤트 | 발생 시점 | 가맹점이 할 일 |
|---|---|---|
subscription.requested_done |
구독 신청 접수 | 승인 대기 안내 |
subscription.approved |
계약 승인 완료 | service_active = true |
subscription.rejected |
관리자가 구독 신청을 거절 (rejected -12) |
서비스를 켜지 않고 고객 안내 |
subscription.renewed |
회차 결제 성공 | 다음 회차까지 서비스 유지, 결제일 갱신 |
subscription.paused |
관리자 일시정지 | 서비스 접근 일시 차단 |
subscription.resumed |
일시정지 해제 | service_active = true |
subscription.hold_on |
반복 결제 실패로 자동 중단 | 서비스 제한 + 결제수단 변경 안내 |
subscription.terminated |
반복 결제 실패로 자동 해지 | service_active = false, 잔여 기간 정책 적용 |
subscription.admin_terminated |
관리자 해지 (admin_terminated -11) |
service_active = false, 잔여 기간 정책 적용 |
subscription.expired |
약정 만료 | service_active = false |
subscription.restored |
관리자 해지 복구 | 서비스 재활성화 — payload.restore 로 회차·결제일 반영 |
회차 결제 성공은 billed 가 아니라 renewed, 결제 실패로 멈춘 것은 failed 가 아니라 hold_on 이에요. 환불 전용 구독 웹훅(refunded)은 없어요 — 회차 취소는 주문 쪽 order.cancelled · order.partial_cancelled 로 와요. 전체 목록은 주문 웹훅 설정이 원본이에요.
웹훅 엔드포인트 구현은 웹훅 처리 가이드 참고.
유예 기간(Grace Period) 설계
결제 실패 ~ 최종 실패 사이 구간을 유예 기간으로 두면 이탈률이 줄어들어요.
| 구간 | 서비스 상태 | 고객 커뮤니케이션 |
|---|---|---|
| 회차 실패 직후 | 유지 (grace) | 카드 업데이트 링크 이메일 |
| 재시도 1회 실패 후 | 유지 (grace) | 2차 알림 + 앱 내 배너 |
| 재시도 N회 실패 후 | 일시정지 | 최종 안내, 수동 재결제 안내 |
유예 기간 길이는 서비스 특성에 따라 달라요 (SaaS 3~7일, 콘텐츠 1~3일이 일반적).
고객 변경 요청 흐름 (request_status)
고객이 해지·인수·이전 같은 변경을 요청하면 관리자 승인 루프를 거쳐요.
흐름 고객 요청 -> 대기 (0) 요청됨 (request_requested) -> 승인 (request_approved) : 관리자 승인 요청됨 (request_requested) -> 자동 승인 (request_approved_auto) 승인 (request_approved) -> 완료 요청됨 (request_requested) -> 거절 (request_rejected) 요청됨 (request_requested) -> 철회 (request_withdrawn)
| 값 | 키 | 다음 |
|---|---|---|
0 |
request_requested |
관리자 승인/거절 |
1 |
request_approved |
실제 상태 변경 실행 |
2 |
request_approved_auto |
자동 승인, 즉시 실행 |
-1 |
request_rejected |
요청 종료 |
-2 |
request_withdrawn |
고객 철회로 종료 |
흔한 실수
1웹훅만으로 서비스 활성화 판단
// ❌ 위험 — 웹훅이 지연되면 고객이 결제했는데 서비스 못 씀javascript결제 완료는 API 응답과 웹훅 둘 다로 확인해요. 동기 응답으로 1차 활성화 후, 웹훅으로 최종 동기화.
2구독 상태와 회차 상태 혼동
구독 상태(승인 대기/구독 중/일시정지/해지)와 회차 상태(예정/성공/실패/취소)는 분리된 축이에요. 구독이 subscribing(1)이어도 해당 월의 회차가 bill_failed(-1)일 수 있어요. DB에도 두 축을 분리해서 저장해요. 두 축의 숫자가 겹쳐 보이지만 서로 다른 표라는 점도 주의해요 — 구독 -2 는 일시정지, 회차 -2 는 재생성 취소예요.
3재시도 중인 회차를 '실패'로 즉시 취급
첫 결제 실패 후 바로 서비스를 차단하면 일시적 카드사 오류로 이탈이 발생해요. 회차가 bill_failure_delayed(6)로 재시도를 기다리는 동안은 유예 기간으로 두고, subscription.hold_on(또는 .terminated)이 올 때 차단하는 쪽이 안전해요.
4해지 시점을 '즉시' 로 고정
"기간 종료까지 서비스 유지 vs 즉시 차단" 은 정책 결정이에요. 구독 해지 정책 을 먼저 정하고 코드에 반영해요.
