Aller au contenu

Story CP-026: Billing Cycle + XOF Compliance

Module: control-plane Slice: Slice 9c from architecture (split from CP-009) Brand context: Papillon HR Suite Priority: 9.2 Depends on: CP-009 Estimated complexity: M


Objective

Implement billing cycle management (monthly/annual with the "14 months for 12" annual deal) and enforce XOF zero-decimal compliance across all billing APIs and UI. Add the billing cycle toggle to the subscription page.


Backend Scope

Entities & Records

BillingCycle enum: MONTHLY, ANNUAL

Annual billing rule: tenant pays 12 months upfront and receives 14 months of service (2 months free). The invoice covers a 14-month billing period; billing_period_end = billing_period_start + 14 months.

Payment entity (tenant DB): Already defined in CP-009. Used here for manual payment recording on annual invoices.

Service Layer

BillingService.java (additions): - chooseBillingCycle(UUID tenantId, BillingCycle cycle) → void — Switch between MONTHLY and ANNUAL. Annual: charges 12 months upfront, unlocks 14-month billing period. Change takes effect on next billing cycle.

API Endpoints

Method Path Request Body Response Auth
POST /api/v1/billing/cycle {cycle: "MONTHLY"\|"ANNUAL"} 204 ROLE_DG

Validation Rules

  • All monetary amounts: BigDecimal, zero decimal places (XOF)
  • Annual invoice amount = 12 × monthly price (not 14)
  • Annual billing period = 14 months from start date
  • Cycle change takes effect at next billing period (no mid-cycle changes)

Multi-Tenant Considerations

  • Billing cycle stored per tenant in tenant DB
  • Annual invoices follow same FNE-compliant numbering as monthly

Frontend Scope

Components

Billing cycle toggle added to the subscription dashboard page (from CP-025).

UI States

State Behavior French Copy
Monthly selected Monthly cycle active "Facturation mensuelle"
Annual selected Annual cycle active with savings badge "Facturation annuelle"
Confirm switch Confirmation dialog "Passer à la facturation annuelle ? Vous serez facturé pour 12 mois et bénéficierez de 14 mois de service."
Success Toast + page update "Facturation annuelle — prochaine échéance : {date}"

Interactions

  • Billing cycle toggle on subscription page: "Facturation mensuelle" ↔ "Facturation annuelle"
  • Selecting annual shows confirmation dialog with savings info
  • After confirmation, page updates to show annual billing info + savings label

French micro-copy:

Key French Text
billing.cycle.annual "Facturation annuelle"
billing.cycle.monthly "Facturation mensuelle"
billing.cycle.savings "Économie : 2 mois offerts"
billing.cycle.switch_confirm "Passer à la facturation annuelle ? Vous serez facturé pour 12 mois et bénéficierez de 14 mois de service."

Acceptance Criteria

AC-081: All amounts in XOF (zero decimals)
Given any billing API response
When monetary values are returned
Then all amounts have zero decimal places (e.g., 15000, not 15000.00)

AC-082: Annual billing — 14 months for price of 12
Given a tenant with billing cycle ANNUAL
When the annual invoice is generated
Then the invoice covers a 14-month billing period (billing_period_end = billing_period_start + 14 months)
And the invoiced amount equals 12 × the monthly price for the tenant's active modules and segment
And the invoice line descriptions indicate the annual rate

AC-083: Switch billing cycle to annual
Given I am DG and navigate to /admin/subscription
When I select "Facturation annuelle" and confirm
Then a POST to /api/v1/billing/cycle with {"cycle": "ANNUAL"} is sent
And the page shows "Facturation annuelle — prochaine échéance : {date}"
And the savings label shows "Économie : 2 mois offerts"

OHADA & Regulatory Rules

  • XOF zero decimal: XOF and XAF are zero-decimal currencies. 15 000 FCFA, never 15 000,00.
  • BigDecimal ONLY: All financial calculations use BigDecimal. Never double/float.
  • FNE compliance: Annual invoices follow the same FNE-compliant sequential numbering as monthly invoices.

Standards & Conventions

  • docs/standards/java-spring-guidelines.md — §BigDecimal for financials
  • docs/standards/react-typescript-guidelines.md — §Toggle components, §Confirmation dialogs
  • docs/standards/api-guidelines.md — §Financial API patterns

Testing Requirements

Unit Tests

  • BillingCycle enum serialization/deserialization
  • Annual invoice amount = 12 × monthly (not 14)
  • Annual billing period = start + 14 months
  • All monetary BigDecimal values have scale 0 (zero decimals)

Integration Tests

  • Switch billing cycle: MONTHLY → ANNUAL → verify next invoice is annual
  • XOF compliance: all API responses return zero-decimal amounts
  • Annual invoice generation end-to-end

What QA Will Validate

  • Billing cycle toggle displays correctly
  • Confirmation dialog for annual switch
  • Savings label "Économie : 2 mois offerts" appears after switch
  • All amounts display with XOF formatting (space as thousands separator, zero decimals)

Out of Scope

  • Invoice generation core logic — CP-009
  • Subscription dashboard layout — CP-025
  • Module add/remove — CP-025
  • Mid-cycle proration — V2
  • Automatic billing cycle renewal — V2

Definition of Done

  • [ ] All backend endpoints implemented and returning correct responses
  • [ ] Billing cycle toggle renders correctly at 360px, 768px, 1024px
  • [ ] French micro-copy matches design spec exactly
  • [ ] All acceptance criteria pass manually
  • [ ] Unit tests written and passing
  • [ ] Integration tests written and passing
  • [ ] Multi-tenant isolation verified (no cross-tenant data leaks)
  • [ ] No TypeScript any — all types explicit
  • [ ] No Java raw types — all generics explicit
  • [ ] API responses follow standard envelope format
  • [ ] Audit trail entries created for billing cycle changes