참고

구독 흐름

계약 생성부터 회차 청구·해지까지 구독 흐름을 한눈에 따라가요.

개별 API는 구독 계약 / 가입, 구독 > 운영 / 회차 에서 확인해요. 이 문서는 라이프사이클을 한 장으로 봐요.

개념·정책 배경 (먼저 정해야 할 것)

구독 구현 전에 블로그 가이드를 봐요.

이 문서의 위치

구독 사이드바는 구독 메뉴 안의 계약 정의·가입·운영 그룹으로 나뉘어 있어요. 이 구독 메뉴는 이 문서를 라이프사이클 단일 소스로 참조해요.

전체 시퀀스

참여자: 고객 | 가맹점 프론트 | 가맹점 서버 | 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.paused` 는 이 자리에 오지 않아요

결제 실패로 멈춘 것은 subscription.hold_on 이에요. subscription.paused 는 관리자가 일시정지한 것​​이라 대응이 달라요 — paused 에 결제수단 변경 안내를 붙이면 정상 이용 중인 고객에게 오안내가 나가요.

자동 해지인데 키 이름이 `manual_terminated` 예요

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 고객 철회로 종료

API 흐름: 요청 목록 · 요청 승인/거절.

흔한 실수

1웹훅만으로 서비스 활성화 판단

// ❌ 위험 — 웹훅이 지연되면 고객이 결제했는데 서비스 못 씀javascript

결제 완료는 API 응답과 웹훅 둘 다​​로 확인해요. 동기 응답으로 1차 활성화 후, 웹훅으로 최종 동기화.

2구독 상태와 회차 상태 혼동

구독 상태(승인 대기/구독 중/일시정지/해지)와 회차 상태(예정/성공/실패/취소)는 분리된 축​​이에요. 구독이 subscribing(1)이어도 해당 월의 회차가 bill_failed(-1)일 수 있어요. DB에도 두 축을 분리해서 저장해요. 두 축의 숫자가 겹쳐 보이지만 서로 다른 표라는 점도 주의해요 — 구독 -2 는 일시정지, 회차 -2 는 재생성 취소예요.

3재시도 중인 회차를 '실패'로 즉시 취급

첫 결제 실패 후 바로 서비스를 차단하면 일시적 카드사 오류로 이탈이 발생해요. 회차가 bill_failure_delayed(6)로 재시도를 기다리는 동안은 유예 기간으로 두고, subscription.hold_on(또는 .terminated)이 올 때 차단하는 쪽이 안전해요.

4해지 시점을 '즉시' 로 고정

"기간 종료까지 서비스 유지 vs 즉시 차단" 은 정책 결정이에요. 구독 해지 정책 을 먼저 정하고 코드에 반영해요.

참고 문서