#+TITLE: Customer Billing Portal PRD
#+AUTHOR: Athena (ReportBurster Guru & Data Modeling & Business Analysis Expert)
* Customer Billing Portal PRD
:PROPERTIES:
:Author: Athena (FlowKraft AI Crew)
:Date: 2026-02-18
:Status: Draft
:Stack: Grails + PostgreSQL
:Target: 1000+ customers
:END:
** Goal
Simple billing portal for customers to view and pay bills online. Auto-creates customers from monthly ReportBurster bursts. Full self-service user account management.
** Constraints
- Simplest solution that works
- No caching
- No over-engineering
- Production: PostgreSQL | Dev: SQLite
* Data Model
** ER Diagram
#+begin_src plantuml :file er-diagram.png
@startuml entity "Customer" as customer { *id : Long -- name : String email : String created_at : Timestamp updated_at : Timestamp } entity "User" as user { *id : Long -- *customer_id : Long *email : String *password : String enabled : Boolean created_at : Timestamp updated_at : Timestamp } entity "Bill" as bill { *id : Long -- *customer_id : Long bill_number : String amount : BigDecimal due_date : Date status : Enum pdf_path : String created_at : Timestamp updated_at : Timestamp } customer ||--o{ user : "has many" customer ||--o{ bill : "has many" note right of bill Status: PAID, UNPAID, OVERDUE end note @enduml
#+end_src
** Grails Models
Customer.groovy
#+begin_src groovy
class Customer { String name String email Date dateCreated Date lastUpdated static hasMany = [users: User, bills: Bill] static constraints = { name blank: false email email: true, unique: true, blank: false } }
#+end_src
User.groovy
#+begin_src groovy
class User { String email String password boolean enabled = false static belongsTo = [customer: Customer] static constraints = { email email: true, unique: true, blank: false password blank: false, password: true enabled nullable: false } static mapping = { password column: '`password`' } }
#+end_src
Bill.groovy
#+begin_src groovy
class Bill { String billNumber BigDecimal amount Date dueDate BillStatus status = BillStatus.UNPAID String pdfPath static belongsTo = [customer: Customer] static constraints = { billNumber blank: false, unique: true amount min: 0.0 dueDate nullable: false pdfPath nullable: true } enum BillStatus { PAID, UNPAID, OVERDUE } }
#+end_src
* User Stories
** Account Management
US-AM-1: Self-service registration
- As a customer user
- I want to register my own account
- So I can access the billing portal without admin intervention
- Acceptance: User enters email, creates password, links to existing customer via customer email
US-AM-2: Email verification
- As a newly registered user
- I want to verify my email
- So my account becomes enabled
- Acceptance: Click link in email, account enabled=true
US-AM-3: Password reset
- As a user
- I want to reset my forgotten password
- So I can regain access
- Acceptance: Request reset → email link → set new password
US-AM-4: Login
- As a user
- I want to login with email and password
- So I can access my bills
- Acceptance: Valid credentials → dashboard; Invalid → error message
** Billing Portal
US-BP-1: View bills (newest first)
- As a customer user
- I want to see all my bills sorted by date descending
- So I can quickly find recent bills
- Acceptance: List shows bill number, amount, due date, status
US-BP-2: Filter by status
- As a customer user
- I want to filter bills by paid/unpaid/overdue
- So I can focus on what needs attention
- Acceptance: Filter buttons show matching bills
US-BP-3: Download PDF
- As a customer user
- I want to download my bill PDF
- So I have a copy for my records
- Acceptance: Download button serves PDF file
US-BP-4: Pay online
- As a customer user
- I want to pay my bill online
- So my account becomes current
- Acceptance: Payment → status changes to PAID
** Payment from Email
US-PE-1: Direct payment link
- As a customer
- I want to click a link in the billing email to pay directly
- So I can pay without logging in
- Acceptance: Unique token in email URL → payment page → pay
** Admin Interface
US-AD-1: Manage customers
- As an admin
- I want to CRUD customers
- So I can correct data when needed
- Acceptance: Full CRUD with validation
US-AD-2: Manage users
- As an admin
- I want to CRUD user accounts
- So I can help users when self-service fails
- Acceptance: Full CRUD, can enable/disable accounts
US-AD-3: Manage bills
- As an admin
- I want to CRUD bills
- So I can correct billing errors
- Acceptance: Full CRUD, can change status manually
US-AD-4: View all bills
- As an admin
- I want to see all bills sorted newest first
- So I can monitor billing activity
- Acceptance: List with customer, bill number, amount, status
** Automation (ReportBurster Integration)
US-AU-1: Auto-create customer
- When ReportBurster generates a bill
- If customer email doesn't exist
- System creates new customer record
- Acceptance: New row in Customer table with email from burst token
US-AU-2: Create bill record
- When ReportBurster generates a bill
- System creates Bill record with PDF path
- Acceptance: New row in Bill table linked to customer
US-AU-3: Send email with payment link
- After bill is created
- System sends email with unique payment URL
- Acceptance: Email contains ${billNumber} and ${paymentLink}
** Email Infrastructure
- SMTP: corporate email server
- From address: [email protected]
- Bounce handling: log and flag for admin review
* Technical Stack
- Framework: Grails (Groovy)
- Database: PostgreSQL (production), SQLite (dev)
- Authentication: Spring Security
- Email: Spring Mail + SMTP
- PDF: ReportBurster-generated files
- Payment: Integration point (v1 may redirect to external payment processor)
* Implementation Notes
** Priority 1 (Core functionality)
- Data model and migrations
- User registration + email verification
- Customer self-registration (link to existing customer)
- Bill listing (newest first)
- Login/password reset
- Admin CRUD for customers, users, bills
** Priority 2 (Payment)
- Direct payment link from email
- Online payment integration (placeholder for payment processor)
- Bill status updates after payment
** Priority 3 (Automation)
- ReportBurster script to auto-create customers and bills
- Email sending with payment links
- SMTP configuration and testing
** Security
- Passwords hashed (BCrypt)
- HTTPS in production
- Session timeout
- CSRF protection enabled by default in Grails
** Performance (at 1000+ customers)
- Indexes on customer.email, bill.due_date, bill.status, bill.customer_id
- Database connection pooling (HikariCP - default in Grails)
- No caching (as requested)
* Success Criteria
- Customers can self-register and verify email
- Customers can view and pay bills
- ReportBurster auto-creates customers and bills
- Admin can manage all entities
- Email delivery reliable for 1000+ monthly bills
- Simple to maintain and extend