Credit System
Credit Lifecycle
Section titled “Credit Lifecycle”GRANT → credits added when enrollment activated (payment confirmed)RESERVE → credit blocked when session bookedCONSUME → credit used when session completed or penalty appliedRELEASE → credit returned when session cancelled within policyEXPIRE → credits expired (enrollment validity period)Credit Ledger
Section titled “Credit Ledger”The credit_ledger table is immutable and append-only. Every credit operation creates a new entry.
Idempotency
Section titled “Idempotency”idempotencyKey with partial UNIQUE index (WHERE NOT NULL). Format:
RESERVE:{enrollmentId}:{sessionId}:{studentId}CONSUME:{enrollmentId}:{sessionId}:{studentId}RELEASE:{enrollmentId}:{sessionId}:{studentId}GRANT:{enrollmentId}:{reference}EXPIRE:{enrollmentId}:{minuteKey}Duplicate inserts are caught via PG error 23505 and silently skipped.
SessionCreditHandler
Section titled “SessionCreditHandler”Never update enrollment counters directly. Always use SessionCreditHandler:
// On session booked → reserve creditawait SessionCreditHandler.onReserve(enrollmentId, sessionId, studentId);
// On session completed → consume creditawait SessionCreditHandler.onCompleted(enrollmentId, sessionId, studentId);
// On session cancelled → release creditawait SessionCreditHandler.onCanceled(enrollmentId, sessionId, studentId);
// On no-show → consume credit (penalty)await SessionCreditHandler.onNoShow(enrollmentId, sessionId, studentId);The handler orchestrates:
- CreditLedgerService — inserts ledger entry (with idempotency)
- EnrollmentService — updates session counters
Belt-and-suspenders: pre-checks for existing entries before calling the ledger.
Enrollment Session Accounting
Section titled “Enrollment Session Accounting”sessionsTotal = total sessions purchasedsessionsScheduled = booked/reserved (not yet completed)sessionsCompleted = completedsessionsCancelled = cancelled (credit returned)sessionsForfeited = forfeited (no-show, late cancel — credit consumed)remaining = sessionsTotal - sessionsScheduled - sessionsCompleted - sessionsCancelled - sessionsForfeitedPolicy Engine
Section titled “Policy Engine”Cancellation Policies
Section titled “Cancellation Policies”Per-service rules stored in cancellation_policies table (JSONB rules column).
Resolution cascade:
- Service-specific policy
- Teacher default policy (
isDefault=true) - Null (allow all)
Rule Evaluation
Section titled “Rule Evaluation”evaluateCancellation() builds context and evaluates rules in order (first match wins):
Context:
hoursBefore— hours until session startssessionsCompleted— total completed sessionssessionsRemaining— credits leftrescheduleCount— times rescheduled
Actions:
| Action | Effect |
|---|---|
full_refund | RELEASE credit |
partial_penalty | CONSUME partial credit |
forfeit | CONSUME full credit |
block | Cancellation not allowed |
allow_free | RELEASE credit |
Pre-built Templates
Section titled “Pre-built Templates”| Template | Description |
|---|---|
individual_relaxed | Flexible for 1:1 sessions |
group_strict | Strict for group classes |
course_no_refund | No refund for courses |
subscription_flexible | Flexible for subscriptions |
Student Preview
Section titled “Student Preview”Dry-run endpoints show the student what will happen before confirming:
GET /student/sessions/:id/cancellation-previewGET /student/sessions/:id/reschedule-preview