Billing 모듈
모듈 개요
billing 모듈은 Lumie 플랫폼의 구독 결제와 알림톡 크레딧을 관리합니다. 결제 경로는 두 가지로 명확히 분리됩니다.
| 경로 | 대상 | 상태 |
|---|---|---|
| 구독 결제 | Lumie SaaS 요금제(구독) | Toss Payments 연동 — 운영 중 |
| 수강료 수납 | 학원 ↔ 학생/보호자 간 수강료 | TuitionBillingGatewayPort 시임 — PaySsam("결제선생") 어댑터 대기 중 |
수강료 직접 Toss 연동(Toss sub-merchant/빌링키 방식)은 commit ff9db43에서 제거되었습니다. 수납 로직은 modules/tuition이 담당하고, 결제 대행사와의 통신 지점은 TuitionBillingGatewayPort 단일 파일 교체로 활성화됩니다.
- 배포: lumie-backend 모놀리스의
modules/billing - 데이터베이스: PostgreSQL
public스키마 (구독, 요금제, 인보이스, 알림톡 크레딧) - 결제 게이트웨이: Toss Payments (
TossPaymentClient) — 구독 과금 전용 - 주요 의존성:
TenantService(internal-api)
주요 기능
1. 요금제 관리
- 4단계 요금제 (FREE, BASIC, PRO, ENTERPRISE)
- 요금제별 제한사항 및 기능 관리
- 월간/연간 요금 설정
2. 구독 관리
- 구독 생성, 변경, 취소, 취소 철회
- 무료 구독 자동 생성
- 구독 상태 관리 (ACTIVE, CANCELLED, SUSPENDED 등)
- 예약 플랜 변경, 월간 자동 과금 스케줄러
3. 결제 처리 (구독 전용 — Toss Payments)
- 빌링키 등록/관리 (
/v1/billing-keys) - 결제 확인 및 Toss 웹훅 처리 (
/internal/webhooks/toss) - 인보이스 생성 및 관리
- 결제 내역 조회
4. 알림톡 크레딧
- 크레딧 잔액 조회 및 충전 (
/v1/alimtalk/credits) - 자동 충전 설정/해제
- Toss Payments를 통한 크레딧 구매
5. 할당량 검증
- 학생 수, 학원 수, 스태프 수 제한
- OMR 월간 할당량 관리
BillingService(internal-api) 통해 다른 모듈이 조회
6. 수강료 수납 게이트웨이 시임 (modules/tuition)
TuitionBillingGatewayPort— PG-무관 아웃바운드 포트- 현재 구현체:
NotConfiguredTuitionBillingGateway(loud-fail 스텁) - PaySsam("결제선생") 어댑터 1-파일 교체로 활성화 예정
아키텍처
헥사고날 구조
modules/billing/src/main/java/com/lumie/billing/
├── domain/{entity,vo,repository,exception}/
├── application/{service,port/out,dto/{request,response}}/
└── adapter/
├── in/web/ # REST 컨트롤러
├── in/internal/ # BillingServiceAdapter (BillingService impl)
├── in/scheduling/ # 월간 자동 과금 스케줄러
└── out/{persistence,external} # JPA, TossPaymentClient, PopbillTaxInvoiceClient
도메인 모델
주요 엔티티: Plan, Subscription, Invoice, BillingKey, PaymentTransaction, AlimtalkCredit, TaxInvoice
// Plan — 요금제 (FREE / BASIC / PRO / ENTERPRISE)
// 관련 VO: PlanLimits (maxStudents, maxAcademies, maxAdmins, omrMonthlyQuota)
// PlanFeatures (customDomains, whiteLabeling, ...)
// Subscription — 테넌트별 구독 레코드
// 상태: SubscriptionStatus (ACTIVE, TRIALING, PAST_DUE, SUSPENDED, CANCELLED 등)
// 예약 플랜 변경: pendingPlanChange 필드
// Invoice — 구독 청구서
// InvoiceStatus: PENDING, PAID, FAILED, REFUNDED
// 타임스탬프: Instant (UTC)
// BillingKey — Toss 빌링키 (구독 자동 과금용)
// BillingKeyStatus: ACTIVE, REVOKED
// AlimtalkCredit — 알림톡 발송 크레딧 잔액 + 자동 충전 설정
API 명세
REST 엔드포인트 (구독 결제)
| 메서드 | 경로 | 설명 |
|---|---|---|
GET | /v1/plans | 요금제 목록 조회 |
POST | /v1/subscriptions | 구독 생성 (Idempotency-Key 필수) |
GET | /v1/subscriptions/{tenantSlug} | 구독 조회 |
PATCH | /v1/subscriptions/{tenantSlug} | 플랜 변경 예약 |
DELETE | /v1/subscriptions/{tenantSlug} | 구독 취소 |
DELETE | /v1/subscriptions/{tenantSlug}/cancellation | 취소 철회 |
POST | /v1/billing-keys | 빌링키 등록 (Idempotency-Key 필수) |
GET | /v1/billing-keys | 빌링키 목록 |
GET | /v1/billing-keys/active | 활성 빌링키 조회 |
DELETE | /v1/billing-keys/{billingKeyId} | 빌링키 해지 |
POST | /v1/payments/confirm | 결제 확인 (Idempotency-Key 필수) |
GET | /v1/payments/history | 결제 내역 (현재 테넌트) |
GET | /v1/payments/history/{tenantSlug} | 결제 내역 (슬러그 지정) |
POST | /v1/billing/subscribe | 구독 시작 통합 플로우 |
GET | /v1/billing/config | 빌링 설정 조회 |
GET | /v1/alimtalk/credits | 크레딧 잔액 조회 |
POST | /v1/alimtalk/credits/recharge | 크레딧 충전 (Idempotency-Key 필수) |
PUT | /v1/alimtalk/credits/auto-recharge | 자동 충전 설정 |
DELETE | /v1/alimtalk/credits/auto-recharge | 자동 충전 해제 |
POST | /internal/webhooks/toss | Toss Payments 웹훅 (내부용) |
internal-api (BillingService)
libs/internal-api에 정의된 BillingService 인터페이스를 BillingServiceAdapter가 구현합니다. exam, staff 등 다른 모듈이 할당량 검증과 구독 정보를 in-process로 조회합니다.
// libs/internal-api/src/main/java/com/lumie/billing/api/BillingService.java
public interface BillingService {
Optional<SubscriptionData> getSubscription(String tenantSlug);
QuotaResult checkQuota(String tenantSlug, MetricType metricType);
Optional<PlanFeaturesData> getPlanFeatures(String planId);
SubscriptionData createFreeSubscription(Long tenantId, String tenantSlug);
}
// 할당량 확인 예시 (exam 모듈에서)
BillingService.QuotaResult result =
billingService.checkQuota(tenantSlug, MetricType.OMR_MONTHLY_QUOTA);
if (!result.allowed()) {
throw new ExamException(ExamErrorCode.OMR_QUOTA_EXCEEDED);
}
수강료 수납 — TuitionBillingGatewayPort 시임
수강료 수납은 modules/tuition이 담당합니다. billing 모듈은 직접 관여하지 않으며, merchant_profiles 테이블은 보존 상 태입니다.
포트 정의 (modules/tuition)
// tuition/application/port/out/TuitionBillingGatewayPort.java
public interface TuitionBillingGatewayPort {
// 보호자에게 청구서 전송 (PaySsam이 수납 대행)
SendInvoiceResult sendInvoice(String orderId, String guardianPhone, String guardianName,
String title, BigDecimal amount, Instant dueDate);
// 수납 상태 조회
CollectionStatusResult queryStatus(String externalInvoiceId);
}
현재 구현체 (스텁)
// tuition/adapter/out/external/NotConfiguredTuitionBillingGateway.java
@Component
public class NotConfiguredTuitionBillingGateway implements TuitionBillingGatewayPort {
// sendInvoice / queryStatus 호출 시 TuitionException(GATEWAY_NOT_CONFIGURED) throw
// PaySsam 어댑터로 이 파일을 교체하면 즉시 활성화
}
HARD_RULES_CROSS에 따라 sendInvoice/queryStatus는 반드시 @Transactional 경계 바깥에서 호출해야 합니다.