Add planning documents for Forte music store platform
17 domain design docs covering architecture, accounts, inventory, rentals, lessons, repairs, POS, payments, batch repairs, delivery, billing, accounting, deployment, licensing, installer, and backend tech architecture. Plus implementation roadmap (doc 18) and personnel management (doc 19). Key design decisions documented: - company/location model (multi-tenant + multi-location) - member entity (renamed from student to support multiple adults) - Stripe vs Global Payments billing ownership differences - User/location/terminal licensing model - Valkey 8 instead of Redis
This commit is contained in:
403
planning/11_Domain_Billing_Date_Management.md
Normal file
403
planning/11_Domain_Billing_Date_Management.md
Normal file
@@ -0,0 +1,403 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Billing Date Management
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
Billing date management covers how recurring subscription charges are scheduled, how customers request date changes, and how the system handles proration when dates shift. This applies to both lesson enrollments and instrument rentals.
|
||||
|
||||
|
||||
|
||||
Stripe owns the billing schedule via the billing_cycle_anchor on each subscription. The application manages the billing_anchor_day preference in its own database and calls Stripe's API to apply changes. All date changes are fully audited and reversible by creating a new change entry.
|
||||
|
||||
|
||||
|
||||
# 2. Billing Anchor Day
|
||||
|
||||
The billing anchor day is the day of month a subscription renews and the customer is charged. It is capped at day 28 to avoid issues with February and months shorter than 31 days. Customers requesting the 29th, 30th, or 31st are set to the 28th with a staff note explaining why.
|
||||
|
||||
|
||||
|
||||
Scenario
|
||||
|
||||
Behavior
|
||||
|
||||
New enrollment / rental
|
||||
|
||||
Default anchor = day of start date, capped at 28
|
||||
|
||||
Customer requests change
|
||||
|
||||
Staff updates anchor — Stripe prorates, new cycle begins on new day
|
||||
|
||||
Consolidating subscriptions
|
||||
|
||||
One anchor adjusted to match other — proration applied to adjusted subscription
|
||||
|
||||
Splitting consolidated billing
|
||||
|
||||
Line item removed, new standalone subscription created on requested day
|
||||
|
||||
AIM migration
|
||||
|
||||
Anchor set to match existing AIM billing date where known
|
||||
|
||||
Paused subscription
|
||||
|
||||
Change applied immediately, no proration — takes effect when subscription resumes
|
||||
|
||||
|
||||
|
||||
# 3. Schema
|
||||
|
||||
## 3.1 rental and enrollment — additional columns
|
||||
|
||||
These four columns are added to both the rental and enrollment tables:
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
billing_anchor_day
|
||||
|
||||
integer
|
||||
|
||||
Day of month (1–28) charge occurs
|
||||
|
||||
billing_anchor_changed_at
|
||||
|
||||
timestamptz
|
||||
|
||||
When anchor was last changed
|
||||
|
||||
billing_anchor_changed_by
|
||||
|
||||
uuid FK
|
||||
|
||||
Employee who made the change
|
||||
|
||||
billing_anchor_change_reason
|
||||
|
||||
text
|
||||
|
||||
Required justification — mandatory field
|
||||
|
||||
|
||||
|
||||
## 3.2 billing_anchor_change_log
|
||||
|
||||
Append-only audit log of every billing date change. Records are never updated or deleted.
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
entity_type
|
||||
|
||||
enum
|
||||
|
||||
rental | enrollment
|
||||
|
||||
entity_id
|
||||
|
||||
uuid
|
||||
|
||||
rental.id or enrollment.id
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
For reporting
|
||||
|
||||
stripe_subscription_id
|
||||
|
||||
varchar
|
||||
|
||||
Stripe subscription affected
|
||||
|
||||
previous_anchor_day
|
||||
|
||||
integer
|
||||
|
||||
Old billing day
|
||||
|
||||
new_anchor_day
|
||||
|
||||
integer
|
||||
|
||||
New billing day
|
||||
|
||||
proration_amount
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Credit or charge applied — sourced from Stripe response
|
||||
|
||||
proration_direction
|
||||
|
||||
enum
|
||||
|
||||
credit | charge | none
|
||||
|
||||
subscription_was_paused
|
||||
|
||||
boolean
|
||||
|
||||
True if change made while subscription paused
|
||||
|
||||
changed_by
|
||||
|
||||
uuid FK
|
||||
|
||||
Employee who made the change
|
||||
|
||||
reason
|
||||
|
||||
text
|
||||
|
||||
Required justification
|
||||
|
||||
stripe_invoice_id
|
||||
|
||||
varchar
|
||||
|
||||
Proration invoice from Stripe if applicable
|
||||
|
||||
bulk_change_id
|
||||
|
||||
uuid
|
||||
|
||||
Groups entries from a single bulk change operation
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 4. Proration Logic
|
||||
|
||||
When a billing anchor day changes mid-cycle Stripe calculates the proration automatically. The application captures the result and records it in billing_anchor_change_log. Staff should preview the proration amount before confirming the change.
|
||||
|
||||
|
||||
|
||||
## 4.1 Moving to an Earlier Day
|
||||
|
||||
Example: Currently billed on the 20th, customer requests the 5th.Today is the 12th. Monthly rate: $50.Customer has paid through the 20th.New cycle: 12th → 5th of next month (24 days).Stripe issues a proration credit for unused days already paid.New cycle begins on the 5th of next month.
|
||||
|
||||
|
||||
|
||||
## 4.2 Moving to a Later Day
|
||||
|
||||
Example: Currently billed on the 5th, customer requests the 20th.Today is the 12th. Monthly rate: $50.Customer has paid through the 5th.Extended period: 5th → 20th = 15 extra days.Stripe issues a proration charge for the extended period.New cycle begins on the 20th.
|
||||
|
||||
|
||||
|
||||
## 4.3 Consolidating Two Subscriptions
|
||||
|
||||
Example: Rental A billed on the 5th, Rental B billed on the 20th.Staff consolidates — both will bill on the 5th.Rental B subscription anchor updated to 5th.Proration applied to Rental B for the period shift.Rental B added as line item to Rental A's subscription.Original Rental B standalone subscription cancelled.Both now charge together on the 5th.
|
||||
|
||||
|
||||
|
||||
## 4.4 Splitting a Consolidated Subscription
|
||||
|
||||
Example: Rental A and Rental B both on the 5th (consolidated).Customer wants Rental B moved to the 20th.Rental B line item removed from consolidated subscription.New standalone subscription created for Rental B, anchor = 20th.Proration applied for Rental B's partial period.Rental A continues unchanged on the 5th.
|
||||
|
||||
|
||||
|
||||
# 5. Edge Cases
|
||||
|
||||
## 5.1 Pending Invoice Within 48 Hours of Billing
|
||||
|
||||
If a billing date change is requested within 48 hours of the current anchor day Stripe may already be generating the upcoming invoice. The system must handle this explicitly.
|
||||
|
||||
- System checks for pending Stripe invoices before allowing anchor change
|
||||
|
||||
- If pending invoice exists within 48-hour window, staff is shown a warning
|
||||
|
||||
- Staff can choose to: wait until after invoice clears, or proceed and void/recreate the pending invoice
|
||||
|
||||
- Voiding a pending invoice requires manager approval — logged in audit trail
|
||||
|
||||
- Recommended default: block change until after billing date passes, then apply
|
||||
|
||||
|
||||
|
||||
## 5.2 Failed Payment Then Date Change
|
||||
|
||||
If a payment has failed and the customer calls to request a billing date change, the outstanding balance must be resolved first.
|
||||
|
||||
- System checks for outstanding failed invoices on account before allowing anchor change
|
||||
|
||||
- If failed invoice exists, staff is blocked from changing anchor until resolved
|
||||
|
||||
- Resolution options: collect payment on failed invoice, write off with manager approval, or payment plan
|
||||
|
||||
- Once resolved, anchor change proceeds normally
|
||||
|
||||
- Prevents customers from using date changes to evade failed payment follow-up
|
||||
|
||||
|
||||
|
||||
## 5.3 Paused Subscriptions
|
||||
|
||||
If a lesson or rental subscription is paused when a date change is requested, the change is applied immediately but no proration is generated since no active billing is occurring.
|
||||
|
||||
- Anchor day updated in database immediately
|
||||
|
||||
- Stripe subscription anchor updated via API
|
||||
|
||||
- proration_direction = 'none' recorded in change log
|
||||
|
||||
- subscription_was_paused = true flagged in log for clarity
|
||||
|
||||
- When subscription resumes it bills on the new anchor day
|
||||
|
||||
|
||||
|
||||
## 5.4 Rent-to-Own Equity on Date Change
|
||||
|
||||
Equity in a rent-to-own rental is calculated per payment received, not per calendar month. A billing date change shifts when the next payment posts but does not affect total equity earned or the buyout calculation.
|
||||
|
||||
- Equity per payment = monthly_rate × rto_equity_percent — unchanged by date shift
|
||||
|
||||
- rto_equity_accumulated updated after each successful Stripe payment webhook
|
||||
|
||||
- If customer is within one payment of buyout eligibility, staff should be notified at time of date change
|
||||
|
||||
- Buyout amount remains: rto_purchase_price minus rto_equity_accumulated
|
||||
|
||||
|
||||
|
||||
## 5.5 Bulk Date Change — Multiple Subscriptions
|
||||
|
||||
A parent account with multiple children may have several lesson and rental subscriptions all billing on different dates. The system supports changing all subscriptions on an account to a single anchor day in one operation.
|
||||
|
||||
|
||||
|
||||
### Bulk Change Preview Screen
|
||||
|
||||
Before confirming a bulk change, staff is shown a summary screen displaying:
|
||||
|
||||
- Each subscription being changed — entity type, description, current anchor, new anchor
|
||||
|
||||
- Proration amount per subscription — credit or charge
|
||||
|
||||
- Net total proration across all subscriptions — credit or charge to customer
|
||||
|
||||
- Warning if any subscription has a pending invoice or failed payment
|
||||
|
||||
- Confirmation required before any Stripe API calls are made
|
||||
|
||||
|
||||
|
||||
### Bulk Change Execution
|
||||
|
||||
- All changes grouped under a shared bulk_change_id in billing_anchor_change_log
|
||||
|
||||
- Stripe API calls made sequentially — not in parallel to avoid rate limit issues
|
||||
|
||||
- If any Stripe call fails, completed changes are rolled back via Stripe API
|
||||
|
||||
- Staff shown which subscriptions succeeded and which failed if partial failure occurs
|
||||
|
||||
- Full rollback preferred — partial bulk changes create confusion for customer billing
|
||||
|
||||
|
||||
|
||||
### Bulk Change Schema
|
||||
|
||||
The bulk_change_id UUID is generated at the start of the operation and written to every billing_anchor_change_log entry created during that operation. This allows the full bulk change to be queried and audited as a single unit.
|
||||
|
||||
|
||||
|
||||
# 6. Business Rules
|
||||
|
||||
- Billing anchor day must be between 1 and 28 inclusive — enforced at API and UI level
|
||||
|
||||
- Requests for 29th, 30th, 31st are automatically capped to 28th with staff notification
|
||||
|
||||
- Every anchor change requires a reason — reason field is mandatory
|
||||
|
||||
- Change log record written before Stripe API call — if Stripe fails, log is rolled back
|
||||
|
||||
- Failed payment on account blocks anchor change until resolved
|
||||
|
||||
- Pending invoice within 48 hours triggers warning — staff must acknowledge before proceeding
|
||||
|
||||
- Bulk changes require preview confirmation before execution
|
||||
|
||||
- Bulk changes rolled back fully if any subscription fails
|
||||
|
||||
- Paused subscription date changes produce no proration
|
||||
|
||||
- Rent-to-own equity is unaffected by date changes
|
||||
|
||||
- All change log records are immutable — no updates or deletes permitted
|
||||
|
||||
|
||||
|
||||
# 7. API Operations
|
||||
|
||||
Endpoint
|
||||
|
||||
Description
|
||||
|
||||
GET /billing/anchor/preview
|
||||
|
||||
Preview proration for proposed anchor change — no changes made
|
||||
|
||||
POST /billing/anchor/change
|
||||
|
||||
Execute single anchor change for one rental or enrollment
|
||||
|
||||
GET /billing/anchor/bulk-preview
|
||||
|
||||
Preview proration summary for bulk change across all account subscriptions
|
||||
|
||||
POST /billing/anchor/bulk-change
|
||||
|
||||
Execute bulk anchor change for all selected subscriptions on account
|
||||
|
||||
GET /billing/anchor/history/:entityId
|
||||
|
||||
Return full anchor change history for a rental or enrollment
|
||||
|
||||
|
||||
|
||||
# 8. Reporting
|
||||
|
||||
- Billing date changes by employee — frequency and reason breakdown
|
||||
|
||||
- Proration credits issued by month — financial impact tracking
|
||||
|
||||
- Proration charges issued by month
|
||||
|
||||
- Accounts with frequent date changes — potential indicator of billing issues
|
||||
|
||||
- Bulk changes log — full history of multi-subscription operations
|
||||
|
||||
- Failed payment + date change attempts — flagged for manager review
|
||||
Reference in New Issue
Block a user