Zum Hauptinhalt springen

Tuition 모듈

학원 수강료(학원비) 수납 전 과정을 담당하는 모듈입니다. 보호자 정보 관리, 청구서(Invoice) 발행, 결제(Payment) 추적, 현금영수증 처리, 연체 자동 감지를 포함합니다.

모듈 개요

항목내용
Gradle 서브프로젝트modules/tuition
베이스 패키지com.lumie.tuition
데이터베이스PostgreSQL (멀티테넌트 RLS)
외부 결제 포트TuitionBillingGatewayPort (PaySsam / 결제선생 seam)
스케줄러OverdueNotificationScheduler (매일 09:00 KST)

주요 책임

  • 보호자(Guardian) 등록·수정·삭제 및 학생-보호자 연결
  • 수강료 청구서(TuitionInvoice) 발행·취소
  • 결제(TuitionPayment) 등록·캡처·환불
  • 현금영수증(CashReceipt) 발행 (Popbill 연동)
  • 매일 09:00에 연체 청구서(ISSUEDOVERDUE) 자동 감지

결제 게이트웨이 seam

직접 Toss 수강료 결제 연동은 제거되었습니다(ff9db43). 현재 외부 결제는 TuitionBillingGatewayPort를 통해 추상화되어 있으며, 기본 구현체는 NotConfiguredTuitionBillingGateway(오류 발생)입니다. PaySsam(결제선생) 어댑터로 단일 파일 교체 시 즉시 라이브 전환이 가능합니다.

public interface TuitionBillingGatewayPort {
// 보호자 핸드폰으로 청구서 발송 (orderId: "TUITION-{invoiceId}-{suffix}")
SendInvoiceResult sendInvoice(String orderId, String guardianPhone, String guardianName,
String title, BigDecimal amount, Instant dueDate);
// 외부 청구서 수납 상태 조회
CollectionStatusResult queryStatus(String externalInvoiceId);
}

이 포트의 구현체는 반드시 @Transactional 경계 밖에서 호출해야 합니다 (HARD_RULES_CROSS).

도메인 모델

Guardian 엔티티

@Entity @Table(name = "guardians")
public class Guardian extends TenantScopedEntity {
Long id; @Version Long version;
String name;
String phone; // 010-XXXX-XXXX
String email;
String relationship; // 부, 모, 조부모 등
}

StudentGuardian 연결 테이블

student_guardians 테이블이 학생과 보호자를 N:M으로 연결합니다. isPrimary 플래그로 주 보호자를 지정합니다.

TuitionInvoice 엔티티

@Entity @Table(name = "tuition_invoices")
public class TuitionInvoice extends TenantScopedEntity {
Long id; @Version Long version;
Long studentId;
Long guardianId;
Long classEnrollmentId;
String title;
List<LineItem> items; // 항목별 금액 (JSONB)
Money totalAmount;
Money vatAmount;
TuitionInvoiceStatus status; // DRAFT | ISSUED | PAID | OVERDUE | CANCELLED
Instant issuedAt;
Instant dueDate;
Instant paidAt;
Instant cancelledAt;
String cancellationReason;
String idempotencyKey; // 고유 인덱스 — 중복 발행 방지
}

TuitionInvoiceStatus 상태 전이

DRAFT → ISSUED → PAID
↘ OVERDUE → PAID
DRAFT / ISSUED / OVERDUE → CANCELLED

TuitionPayment 엔티티

@Entity @Table(name = "tuition_payments")
public class TuitionPayment extends TenantScopedEntity {
Long id; @Version Long version;
Long tuitionInvoiceId;
String orderId; // 고유 인덱스 (TUITION-{invoiceId}-...)
String pgTransactionId;
Money amount;
TuitionPaymentMethod method; // CARD | CASH | TRANSFER | VIRTUAL_ACCOUNT 등
TuitionPaymentStatus status; // PENDING | CAPTURED | FAILED | CANCELLED
Instant capturedAt;
Money refundedAmount;
String refundReason;
Instant refundedAt;
Long refundedByStaffId;
}

CashReceipt 엔티티

cash_receipts 테이블 — Popbill 현금영수증 발행 이력을 저장합니다.

REST API

보호자 API — 기본 경로: /v1/guardians

메서드경로설명
POST/v1/guardians보호자 등록 (201 Created)
GET/v1/guardians/\{id\}보호자 상세 조회
GET/v1/guardians보호자 목록 (페이지)
PATCH/v1/guardians/\{id\}보호자 정보 수정
DELETE/v1/guardians/\{id\}보호자 삭제
POST/v1/guardians/\{guardianId\}/link학생-보호자 연결 (?studentId=&isPrimary=)
DELETE/v1/guardians/\{guardianId\}/link학생-보호자 연결 해제 (?studentId=)

청구서 API — 기본 경로: /v1/tuition-invoices

메서드경로설명
POST/v1/tuition-invoices청구서 발행 (201, Idempotency-Key 지원)
GET/v1/tuition-invoices/\{id\}청구서 상세 조회
GET/v1/tuition-invoices청구서 목록 (?studentId= 필터, 페이지)
POST/v1/tuition-invoices/\{id\}/cancel청구서 취소

정렬 허용 필드

createdAt, dueDate, totalAmount, status

결제 API — 기본 경로: /v1/tuition-payments

메서드경로설명
POST/v1/tuition-payments결제 등록 (201, Idempotency-Key 지원)
GET/v1/tuition-payments/\{id\}결제 상세 조회
GET/v1/tuition-payments결제 목록 (페이지)
POST/v1/tuition-payments/\{id\}/refund환불 처리

스케줄러

OverdueNotificationScheduler는 ShedLock(tuition-overdue-notify)으로 보호되어 멀티파드 환경에서 중복 실행을 방지합니다.

cron = "0 0 9 * * *" (매일 09:00 KST)
lockAtMostFor = PT20M
lockAtLeastFor = PT1M

모든 활성 테넌트를 순회하며 ISSUED 상태에서 dueDate가 경과한 청구서를 OVERDUE로 전환합니다. 원장(OWNER) 알림 디스패치는 Task 5에서 NotificationService 연동 예정입니다.

멀티테넌시

guardians, student_guardians, tuition_invoices, tuition_payments, cash_receipts 테이블 모두 tenant_id BIGINT NOT NULL + RLS 정책이 적용됩니다.

관련 문서