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