Functional Requirements - Football Field Booking System
Version: 1.0
Last Updated: January 2025
Total Requirements: 30
Requirements Priority Levels
- P0 (Critical): Must-have for MVP, blocks launch
- P1 (High): Important for MVP, but can workaround
- P2 (Medium): Nice-to-have for MVP, can defer
- P3 (Low): Future enhancement
FR-1: User Management
FR-1.1: User Registration
ID: FR-1.1
Priority: P0 (Critical)
User Story: As a new user, I want to register an account so that I can book fields.
Acceptance Criteria:
- [ ] User can register with email and password
- [ ] User can register via Google OAuth
- [ ] User can register via Facebook OAuth
- [ ] Email verification is required before first login
- [ ] Password must meet complexity requirements:
- Minimum 8 characters
- At least 1 uppercase letter
- At least 1 lowercase letter
- At least 1 number
- At least 1 special character
- [ ] User receives welcome email after successful registration
- [ ] Duplicate email addresses are rejected with clear error message
- [ ] User profile is created in User Service
- [ ] User data is synced to Keycloak
Business Rules:
- Default role: Customer
- Email must be verified within 24 hours or account is deleted
- Username must be unique (email is username)
Dependencies: Keycloak integration, Email service
API Endpoints: POST /auth/register
Estimated Effort: 5 story points
FR-1.2: User Login
ID: FR-1.2
Priority: P0 (Critical)
User Story: As a registered user, I want to login so that I can access my account.
Acceptance Criteria:
- [ ] User can login with email/password
- [ ] User can login via Google OAuth
- [ ] User can login via Facebook OAuth
- [ ] JWT access token (15 min expiry) and refresh token (7 days) issued
- [ ] Invalid credentials return 401 with message "Invalid email or password"
- [ ] Account lockout after 5 failed attempts within 15 minutes
- [ ] Locked account can be unlocked after 30 minutes or password reset
- [ ] User can remember login (refresh token stored securely)
- [ ] Login attempt is logged (audit trail)
Business Rules:
- Unverified email cannot login
- Locked accounts cannot login until unlocked
- Only one active session per user (optional: multi-device)
Dependencies: Keycloak integration
API Endpoints: POST /auth/login
Estimated Effort: 5 story points
FR-1.3: User Profile Management
ID: FR-1.3
Priority: P1 (High)
User Story: As a user, I want to manage my profile information.
Acceptance Criteria:
- [ ] User can view profile information (name, email, phone, avatar, preferences)
- [ ] User can update name (2-50 characters)
- [ ] User can update phone (valid phone format with country code)
- [ ] User can upload avatar (max 5MB, jpg/png only)
- [ ] Avatar is resized to 400x400px automatically
- [ ] User can change password (requires current password)
- [ ] User can update notification preferences (email, SMS)
- [ ] Changes are validated before saving
- [ ] User receives confirmation after successful update
- [ ] Profile cache is invalidated after update
Business Rules:
- Email cannot be changed (requires new verification flow)
- Profile updates are reflected immediately
- Old avatars are deleted to save storage
API Endpoints:
GET /users/mePUT /users/mePUT /users/me/passwordPOST /users/me/avatar
Estimated Effort: 3 story points
FR-1.4: User Roles & Permissions
ID: FR-1.4
Priority: P0 (Critical)
User Story: As a system, I need to distinguish between different user types and control access.
Roles:
| Role | Permissions | How to Get |
|---|---|---|
| Customer | - Book fields - View own bookings - Cancel own bookings - Update own profile |
Default on registration |
| Field Owner | - All Customer permissions - Add/edit own fields - View bookings for own fields - Manage field availability |
Apply + Admin approval |
| Admin | - All permissions - Manage all users - Manage all fields - Manage all bookings - View analytics |
Assigned by system admin |
Acceptance Criteria:
- [ ] User is assigned Customer role by default on registration
- [ ] Field Owner role requires approval workflow
- [ ] Admin role can only be assigned by existing admin
- [ ] Roles determine accessible API endpoints (RBAC enforced)
- [ ] Unauthorized access returns 403 Forbidden
- [ ] Role changes are logged for audit
Business Rules:
- User can have multiple roles
- Customer → Field Owner requires admin approval
- Field Owner status can be revoked by admin
Dependencies: Keycloak roles configuration
Estimated Effort: 5 story points
FR-1.5: Password Reset
ID: FR-1.5
Priority: P1 (High)
User Story: As a user, I want to reset my password if I forget it.
Acceptance Criteria:
- [ ] User can request password reset via email
- [ ] Reset link is sent to user's registered email
- [ ] Reset link contains secure token (JWT)
- [ ] Reset link expires after 1 hour
- [ ] User can set new password via reset link
- [ ] New password must meet complexity requirements
- [ ] Old password is invalidated immediately
- [ ] All active sessions are terminated
- [ ] User receives confirmation email after password change
- [ ] Password reset attempts are rate-limited (3 per hour)
Business Rules:
- Token is single-use only
- Password cannot be same as last 3 passwords
- Email must be verified to reset password
API Endpoints:
POST /auth/forgot-passwordPOST /auth/reset-password
Estimated Effort: 3 story points
FR-1.6: User Account Deletion
ID: FR-1.6
Priority: P2 (Medium)
User Story: As a user, I want to delete my account and all my data.
Acceptance Criteria:
- [ ] User can request account deletion from profile settings
- [ ] User must confirm deletion (enter password)
- [ ] Active/upcoming bookings prevent deletion
- [ ] User data is soft-deleted (marked as deleted, not physically removed)
- [ ] User receives confirmation email
- [ ] Account can be restored within 30 days by contacting support
- [ ] After 30 days, data is permanently deleted (GDPR compliance)
- [ ] User's reviews/ratings are anonymized (Phase 2)
Business Rules:
- Cannot delete if active bookings exist
- Can delete if only completed bookings exist
- Refunds must be processed before deletion
API Endpoints: DELETE /users/me
Estimated Effort: 3 story points
FR-2: Field Management
FR-2.1: Field Registration
ID: FR-2.1
Priority: P0 (Critical)
User Story: As a field owner, I want to register my field on the platform.
Acceptance Criteria:
- [ ] Owner can create field listing via form
- [ ] Required fields: name, address, location (lat/lng), sport type, price per hour
- [ ] Optional fields: description, amenities, rules, operating hours
- [ ] Support multiple images upload (max 10, 5MB each, jpg/png)
- [ ] Images are automatically compressed and resized
- [ ] Address auto-complete with Google Maps API
- [ ] Location (lat/lng) is auto-populated from address
- [ ] Field requires admin approval before going live
- [ ] Owner receives email when field is approved/rejected
- [ ] Draft fields can be saved and edited before submission
Business Rules:
- Sport types: Football 5v5, 7v7, 11v11
- Price range: 50,000 - 1,000,000 VND per hour
- Operating hours: Must be reasonable (e.g., 6 AM - 11 PM)
- Admin approval required for all new fields
API Endpoints: POST /fields
Estimated Effort: 8 story points
FR-2.2: Field Information Management
ID: FR-2.2
Priority: P1 (High)
User Story: As a field owner, I want to update my field information.
Acceptance Criteria:
- [ ] Owner can edit all field details (name, description, price, etc.)
- [ ] Owner can add new images (up to 10 total)
- [ ] Owner can delete existing images
- [ ] Owner can reorder images (first image is cover photo)
- [ ] Owner can set different prices for different time periods (e.g., peak hours)
- [ ] Owner can set availability hours per day of week
- [ ] Owner can temporarily disable field (mark as unavailable)
- [ ] Changes are reflected immediately after save
- [ ] Field cache is invalidated after updates
- [ ] Owner receives confirmation after successful update
Business Rules:
- Cannot delete all images (minimum 1 required)
- Price changes do not affect existing bookings
- Disabling field cancels pending bookings (with refund)
API Endpoints:
PUT /fields/:idPOST /fields/:id/imagesDELETE /fields/:id/images/:imageIdPUT /fields/:id/availability
Estimated Effort: 5 story points
FR-2.3: Field Search
ID: FR-2.3
Priority: P0 (Critical)
User Story: As a customer, I want to search for available fields near me.
Acceptance Criteria:
- [ ] Search by location (radius in km)
- [ ] Filter by sport type (5v5, 7v7, 11v11)
- [ ] Filter by price range (min, max)
- [ ] Filter by available date and time
- [ ] Filter by amenities (parking, shower, changing room, lighting)
- [ ] Results are sorted by distance (nearest first) by default
- [ ] Alternative sorting: price (low to high, high to low), rating (Phase 2)
- [ ] Pagination support (20 items per page)
- [ ] Show distance from user's location
- [ ] Show starting price for each field
- [ ] Search performance < 200ms (p95)
- [ ] Results are cached for 5 minutes
Business Rules:
- Only approved and active fields are shown
- Only fields with availability for selected time are shown
- Default radius: 5 km
API Endpoints: GET /fields/search
Query Parameters:
?lat=10.762622
&lng=106.660172
&radius=5000
&sportType=football_7
&minPrice=50000
&maxPrice=200000
&date=2024-01-20
&startTime=10:00
&endTime=12:00
&amenities=parking,shower
&sortBy=distance
&page=1
&limit=20
Estimated Effort: 8 story points
FR-2.4: Field Availability
ID: FR-2.4
Priority: P0 (Critical)
User Story: As a customer, I want to see real-time field availability for a specific date.
Acceptance Criteria:
- [ ] Display available time slots for selected date
- [ ] Show booked slots (grayed out, unavailable)
- [ ] Show price for each time slot
- [ ] Support different prices for different times (peak/off-peak)
- [ ] Minimum booking duration: 1 hour
- [ ] Maximum booking duration: 8 hours
- [ ] Time slots are in 30-minute increments
- [ ] Availability updates in real-time (cache invalidation)
- [ ] Consider field operating hours
- [ ] Consider booking buffer time (15 min between bookings)
- [ ] Cache availability data (Redis, 1 min TTL)
- [ ] Invalidate cache when booking is created/cancelled
Business Rules:
- Availability = Operating hours - Booked slots
- Buffer time prevents back-to-back bookings
- Expired pending bookings are considered available
API Endpoints: GET /fields/:id/availability?date=2024-01-20
Response Example:
{
"fieldId": "uuid",
"date": "2024-01-20",
"slots": [
{
"startTime": "10:00",
"endTime": "11:00",
"available": true,
"price": 150000
},
{
"startTime": "11:00",
"endTime": "12:00",
"available": false,
"bookingId": "uuid"
}
]
}
Estimated Effort: 8 story points
FR-2.5: Field Details
ID: FR-2.5
Priority: P1 (High)
User Story: As a customer, I want to view detailed information about a field.
Acceptance Criteria:
- [ ] Display all field information (name, description, price, amenities)
- [ ] Show image gallery with lightbox
- [ ] Display location on interactive map (Google Maps)
- [ ] Show exact address and directions link
- [ ] Display amenities with icons
- [ ] Show field rules and policies
- [ ] Display operating hours
- [ ] Show contact information (optional)
- [ ] Display reviews and average rating (Phase 2)
- [ ] Show "Similar Fields Nearby" section (3 fields)
- [ ] Include "Book Now" button
- [ ] Share field via social media (Phase 2)
Business Rules:
- Only approved fields are publicly accessible
- Disabled fields show "Currently Unavailable" message
API Endpoints: GET /fields/:id
Estimated Effort: 5 story points
FR-3: Booking Management
FR-3.1: Create Booking
ID: FR-3.1
Priority: P0 (Critical)
User Story: As a customer, I want to book a field for a specific time.
Acceptance Criteria:
- [ ] Customer selects field, date, start time, end time
- [ ] System validates field availability in real-time
- [ ] System acquires distributed lock (Redis) before creating booking
- [ ] System checks for overlapping bookings with pessimistic lock
- [ ] If no conflicts, create booking with status PENDING
- [ ] Calculate total price (hours × price per hour)
- [ ] Release distributed lock after booking creation
- [ ] User is redirected to payment page
- [ ] Booking expires if not paid within 15 minutes
- [ ] Expired bookings are automatically cancelled
- [ ] Email confirmation sent after payment (not immediately)
Business Rules:
- Minimum booking duration: 1 hour
- Maximum booking duration: 8 hours
- Booking must be at least 1 hour in advance
- No overlapping bookings allowed (enforced by lock + DB constraint)
- Price is locked at time of booking (won't change if field updates price)
API Endpoints: POST /bookings
Request:
{
"fieldId": "uuid",
"startTime": "2024-01-20T10:00:00Z",
"endTime": "2024-01-20T12:00:00Z",
"notes": "Birthday party, need extra time for setup"
}
Response:
{
"bookingId": "uuid",
"status": "PENDING",
"totalPrice": 300000,
"expiresAt": "2024-01-20T09:15:00Z",
"paymentUrl": "https://payment-gateway.com/checkout/xyz"
}
Estimated Effort: 13 story points
FR-3.2: View Bookings
ID: FR-3.2
Priority: P1 (High)
User Story: As a user, I want to view my booking history.
Acceptance Criteria:
- [ ] Customer can view own bookings only
- [ ] Field owner can view bookings for their fields only
- [ ] Admin can view all bookings
- [ ] Filter by status (pending, confirmed, cancelled, completed, expired)
- [ ] Filter by date range (start date, end date)
- [ ] Sort by booking date (newest first, oldest first)
- [ ] Sort by field name (alphabetical)
- [ ] Pagination support (20 items per page)
- [ ] Show booking summary (field name, date/time, price, status)
- [ ] Click to view booking details
Business Rules:
- Customers see only their bookings
- Field owners see bookings for fields they own
- Admin sees all bookings
API Endpoints:
GET /bookings/me(customer)GET /bookings/fields/:fieldId(owner)GET /bookings(admin)
Query Parameters:
?status=confirmed
&startDate=2024-01-01
&endDate=2024-01-31
&sortBy=bookingDate
&sortOrder=desc
&page=1
&limit=20
Estimated Effort: 5 story points
FR-3.3: Cancel Booking
ID: FR-3.3
Priority: P1 (High)
User Story: As a customer, I want to cancel my booking if my plans change.
Acceptance Criteria:
- [ ] Customer can cancel confirmed booking only
- [ ] Cancellation allowed up to 24 hours before start time
- [ ] System calculates refund amount based on policy
- [ ] Booking status changed to CANCELLED
- [ ] Field availability is updated (slot becomes available)
- [ ] Refund is automatically processed via Payment Service
- [ ] Customer receives cancellation confirmation email
- [ ] Field owner receives notification of cancellation
- [ ] Cancel button is disabled if within 24h of start time
- [ ] Provide cancellation reason (optional)
Refund Policy:
- More than 48 hours before: 100% refund
- 24-48 hours before: 50% refund
- Less than 24 hours: No refund (no cancellation allowed)
Business Rules:
- Cannot cancel completed bookings
- Cannot cancel already cancelled bookings
- Cannot cancel if start time has passed
API Endpoints: PUT /bookings/:id/cancel
Request:
{
"reason": "Weather forecast shows heavy rain"
}
Estimated Effort: 8 story points
FR-3.4: Booking Conflict Detection
ID: FR-3.4
Priority: P0 (Critical)
User Story: As a system, I must prevent double bookings at all costs.
Acceptance Criteria:
- [ ] Acquire Redis distributed lock before checking availability
- [ ] Lock key format:
booking:lock:${fieldId}:${date} - [ ] Lock timeout: 30 seconds
- [ ] If lock acquisition fails, retry 3 times with exponential backoff
- [ ] After acquiring lock, check for overlapping bookings in database
- [ ] Use SQL query with
FOR UPDATE(pessimistic lock) - [ ] Overlapping condition:
(new_start < existing_end) AND (new_end > existing_start) - [ ] If overlap found, reject with 409 Conflict error
- [ ] If no overlap, create booking within same transaction
- [ ] Release lock after transaction completes (success or failure)
- [ ] Handle lock timeout gracefully (return 503 Service Unavailable)
- [ ] Log all lock operations for debugging
Technical Implementation:
async createBooking(data: CreateBookingDto) {
const lockKey = `booking:lock:${data.fieldId}:${data.date}`;
const lock = await this.redis.acquireLock(lockKey, 30000);
try {
// Check overlaps with pessimistic lock
const overlap = await this.bookingRepo
.createQueryBuilder()
.where('fieldId = :fieldId', { fieldId: data.fieldId })
.andWhere('status IN (:...statuses)', {
statuses: ['PENDING', 'CONFIRMED']
})
.andWhere('(startTime < :endTime AND endTime > :startTime)', {
startTime: data.startTime,
endTime: data.endTime,
})
.setLock('pessimistic_write')
.getOne();
if (overlap) {
throw new ConflictException('Time slot already booked');
}
// Create booking
return await this.bookingRepo.save(data);
} finally {
await lock.release();
}
}
Estimated Effort: 8 story points
FR-3.5: Booking Status Workflow
ID: FR-3.5
Priority: P0 (Critical)
User Story: As a system, I need to manage booking lifecycle states correctly.
Status Flow:
PENDING → CONFIRMED → COMPLETED
↓ ↓
EXPIRED CANCELLED
Status Definitions:
- PENDING: Booking created, awaiting payment (15 min timeout)
- CONFIRMED: Payment successful, booking is confirmed
- EXPIRED: Payment timeout, booking auto-cancelled
- CANCELLED: User or admin cancelled booking
- COMPLETED: Booking end time has passed
Transitions:
| From | To | Trigger | Event Emitted |
|---|---|---|---|
| PENDING | CONFIRMED | Payment success | booking.confirmed |
| PENDING | EXPIRED | 15 min timeout | booking.expired |
| CONFIRMED | CANCELLED | User cancels | booking.cancelled |
| CONFIRMED | COMPLETED | Time passed | booking.completed |
Acceptance Criteria:
- [ ] Only valid transitions are allowed (validated in code)
- [ ] Invalid transitions return 400 Bad Request
- [ ] Status changes trigger Kafka events
- [ ] Events are consumed by Notification Service
- [ ] Scheduled job runs every minute to:
- [ ] Expire PENDING bookings after 15 min
- [ ] Complete CONFIRMED bookings after end time
- [ ] Status history is logged (audit trail)
- [ ] Booking cannot be modified after CONFIRMED status
Estimated Effort: 5 story points
FR-3.6: Booking Modification
ID: FR-3.6
Priority: P2 (Medium)
User Story: As a customer, I want to modify my booking if I need to change the time.
Acceptance Criteria:
- [ ] Customer can change booking time (date, start time, end time)
- [ ] Customer can add/update notes
- [ ] Modification allowed up to 24 hours before original start time
- [ ] System checks availability for new time slot
- [ ] System calculates price difference (can be positive, negative, or zero)
- [ ] If price increased, customer must pay difference
- [ ] If price decreased, refund is issued
- [ ] Modification requires field owner approval (optional)
- [ ] Customer receives confirmation email with updated details
- [ ] Original booking is updated (not new booking created)
Business Rules:
- Cannot modify within 24h of start time
- Cannot change field (must cancel and create new booking)
- Can modify unlimited times (before 24h cutoff)
- Price is recalculated based on current field pricing
API Endpoints: PUT /bookings/:id
Request:
{
"startTime": "2024-01-20T14:00:00Z",
"endTime": "2024-01-20T16:00:00Z",
"notes": "Updated: Need 2 hours instead of 1"
}
Estimated Effort: 8 story points
FR-3.7: Booking Reminders
ID: FR-3.7
Priority: P2 (Medium)
User Story: As a customer, I want to receive reminders so I don't forget my booking.
Acceptance Criteria:
- [ ] Email reminder sent 24 hours before booking start
- [ ] SMS reminder sent 2 hours before booking start (optional, costs money)
- [ ] Reminders include:
- [ ] Field name and address
- [ ] Booking date and time
- [ ] Directions link (Google Maps)
- [ ] Contact information
- [ ] Cancellation policy reminder
- [ ] User can opt-out of reminders in profile settings
- [ ] Scheduled job runs every hour to send due reminders
- [ ] Reminders are only sent for CONFIRMED bookings
- [ ] Reminder status is tracked (sent, failed)
- [ ] Failed reminders are retried (max 3 attempts)
Business Rules:
- No reminders for PENDING, CANCELLED, EXPIRED bookings
- Opt-out applies to all future bookings
- SMS reminders require verified phone number
Estimated Effort: 5 story points
FR-3.8: Booking Receipt
ID: FR-3.8
Priority: P1 (High)
User Story: As a customer, I want a receipt for my booking for record-keeping.
Acceptance Criteria:
- [ ] Generate PDF receipt with professional layout
- [ ] Include company logo and branding
- [ ] Include booking details:
- [ ] Booking ID
- [ ] Field name and address
- [ ] Date and time
- [ ] Duration
- [ ] Price breakdown
- [ ] Include payment information:
- [ ] Payment method
- [ ] Transaction ID
- [ ] Payment date
- [ ] Amount paid
- [ ] Include QR code for verification
- [ ] Email receipt automatically after payment confirmation
- [ ] Allow download from booking details page
- [ ] Allow re-download anytime from booking history
- [ ] Receipt is immutable (cannot be edited)
API Endpoints: GET /bookings/:id/receipt
Response: PDF file (application/pdf)
Estimated Effort: 5 story points
FR-4: Payment Processing
FR-4.1: Payment Integration
ID: FR-4.1
Priority: P0 (Critical)
User Story: As a customer, I want to pay for my booking securely online.
Payment Gateways:
- Stripe: International credit/debit cards
- VNPay: Vietnam domestic payment methods (bank transfer, e-wallets)
Acceptance Criteria:
- [ ] Support credit/debit card payments (Visa, Mastercard, Amex)
- [ ] Support bank transfer via VNPay
- [ ] Support e-wallets (Momo, ZaloPay) via VNPay
- [ ] Secure payment flow (redirect to gateway, return with result)
- [ ] PCI-DSS compliant (never store card details)
- [ ] Payment timeout after 15 minutes
- [ ] User can retry payment if failed
- [ ] Automatic retry for transient failures (network issues)
- [ ] Payment gateway errors are handled gracefully
- [ ] User receives clear error messages
Business Rules:
- Payment is required to confirm booking
- Payment amount matches booking total price exactly
- Payment is non-refundable after cancellation cutoff
- Partial payments not supported
API Endpoints:
POST /payments/createPOST /payments/confirm
Estimated Effort: 13 story points
FR-4.2: Payment Webhook Handling
ID: FR-4.2
Priority: P0 (Critical)
User Story: As a system, I need to receive and process payment confirmations from gateways.
Acceptance Criteria:
- [ ] Receive webhooks from Stripe at
/webhooks/stripe - [ ] Receive webhooks from VNPay at
/webhooks/vnpay - [ ] Verify webhook signatures (Stripe: HMAC-SHA256, VNPay: MD5)
- [ ] Reject webhooks with invalid signatures
- [ ] Implement idempotency (prevent duplicate processing)
- [ ] Use idempotency key (payment transaction ID)
- [ ] On payment success:
- [ ] Update booking status to CONFIRMED
- [ ] Update payment status to COMPLETED
- [ ] Emit
payment.completedevent (Kafka) - [ ] Invalidate field availability cache
- [ ] On payment failure:
- [ ] Keep booking status as PENDING
- [ ] Update payment status to FAILED
- [ ] Allow user to retry payment
- [ ] Log all webhook events to MongoDB (audit trail)
- [ ] Handle late/duplicate webhooks gracefully
- [ ] Return 200 OK to gateway within 5 seconds
Security:
- Validate webhook source IP (whitelist)
- Verify signature before processing
- Rate limit webhook endpoints
API Endpoints:
POST /webhooks/stripePOST /webhooks/vnpay
Estimated Effort: 8 story points
FR-4.3: Payment Status Tracking
ID: FR-4.3
Priority: P1 (High)
User Story: As a user, I want to track the status of my payment in real-time.
Payment Statuses:
- PENDING: Payment initiated, awaiting confirmation
- PROCESSING: Payment is being processed by gateway
- COMPLETED: Payment successful
- FAILED: Payment failed
- REFUNDED: Payment refunded (partial or full)
Acceptance Criteria:
- [ ] User can view payment status on booking details page
- [ ] Payment status updates in real-time (polling every 3 seconds)
- [ ] Show payment method used
- [ ] Show transaction ID
- [ ] Show amount paid
- [ ] Show payment date/time
- [ ] Show refund amount (if applicable)
- [ ] Payment history shows all attempts (including failed)
- [ ] User can download payment receipt
API Endpoints: GET /payments/:id
Response:
{
"paymentId": "uuid",
"bookingId": "uuid",
"status": "COMPLETED",
"amount": 300000,
"currency": "VND",
"method": "STRIPE",
"transactionId": "pi_1234567890",
"paidAt": "2024-01-20T08:30:00Z",
"refundAmount": 0
}
Estimated Effort: 3 story points
FR-4.4: Refund Processing
ID: FR-4.4
Priority: P1 (High)
User Story: As a customer, I want to receive refunds automatically when I cancel a booking.
Acceptance Criteria:
- [ ] Automatic refund triggered when booking is cancelled
- [ ] Refund amount calculated based on cancellation policy
- [ ] Manual refund available for admin (full or partial)
- [ ] Refund to original payment method (Stripe, VNPay)
- [ ] Refund processing time: 5-7 business days (gateway dependent)
- [ ] User notified via email when refund is initiated
- [ ] User notified via email when refund is completed
- [ ] Refund status tracked (pending, completed, failed)
- [ ] Failed refunds are retried automatically
- [ ] Admin can retry failed refunds manually
- [ ] Refund transaction logged for audit
Refund Policy (reminder):
-
48h before: 100% refund
- 24-48h before: 50% refund
- <24h before: 0% refund
API Endpoints:
POST /payments/:id/refund(automatic, called by Booking Service)POST /payments/:id/refund/manual(admin only)
Estimated Effort: 8 story points
FR-4.5: Payment Receipts
ID: FR-4.5
Priority: P1 (High)
User Story: As a customer, I want a payment receipt for tax and accounting purposes.
Acceptance Criteria:
- [ ] Generate PDF receipt with invoice format
- [ ] Include VAT information (10% VAT in Vietnam)
- [ ] Include company tax ID (for field owner)
- [ ] Include payment details:
- [ ] Invoice number (unique)
- [ ] Payment date
- [ ] Payment method
- [ ] Transaction ID
- [ ] Amount breakdown (subtotal, VAT, total)
- [ ] Include customer information (name, email)
- [ ] Include booking information (field, date, time)
- [ ] Email receipt automatically after payment
- [ ] Allow download from payment history
- [ ] Receipt is PDF/A format (archival standard)
API Endpoints: GET /payments/:id/receipt
Response: PDF file (application/pdf)
Estimated Effort: 5 story points
FR-5: Notifications
FR-5.1: Email Notifications
ID: FR-5.1
Priority: P1 (High)
User Story: As a user, I want to receive email notifications for important events.
Email Types:
- Welcome Email (after registration)
- Welcome message
- Email verification link
-
Getting started guide
-
Email Verification (after registration)
- Verification link (24h expiry)
-
Instructions
-
Booking Confirmation (after payment)
- Booking details
- Field information
- Directions link
- Cancellation policy
-
Receipt attached
-
Payment Confirmation (after payment)
- Payment amount
- Transaction ID
-
Receipt link
-
Booking Reminder (24h and 2h before)
- Booking details
- Directions
-
Contact information
-
Cancellation Confirmation (after cancellation)
- Cancellation details
- Refund information
-
Refund timeline
-
Refund Notification (after refund)
- Refund amount
- Refund method
- Processing time
Acceptance Criteria:
- [ ] All emails use branded HTML templates
- [ ] Templates are responsive (mobile-friendly)
- [ ] Include unsubscribe link (footer)
- [ ] Emails sent within 1 minute of event
- [ ] Track email delivery status (sent, delivered, bounced, opened)
- [ ] Failed emails are retried (max 3 attempts)
- [ ] User can customize notification preferences
- [ ] Emails are sent asynchronously (BullMQ queue)
Technical:
- Nodemailer with SMTP
- Handlebars for templates
- BullMQ for async sending
- Store templates in database (Phase 2)
Estimated Effort: 8 story points
FR-5.2: SMS Notifications
ID: FR-5.2
Priority: P2 (Medium)
User Story: As a user, I want to receive SMS for urgent updates.
SMS Types:
- Booking Confirmation
-
"Booking confirmed for [Field] on [Date] at [Time]. Total: [Price] VND"
-
Booking Reminder (2h before)
-
"Reminder: Your booking at [Field] starts in 2 hours at [Time]"
-
Cancellation Confirmation
- "Booking cancelled for [Field] on [Date]. Refund: [Amount] VND"
Acceptance Criteria:
- [ ] SMS sent to verified phone numbers only
- [ ] Include booking ID for reference
- [ ] Character limit: 160 characters (1 SMS unit)
- [ ] Cost optimization: Only critical notifications
- [ ] User can opt-out of SMS notifications
- [ ] SMS status tracked (sent, delivered, failed)
- [ ] Failed SMS are retried (max 2 attempts)
- [ ] SMS sent asynchronously (BullMQ queue)
Technical:
- Twilio API
- SMS templates with placeholders
- Cost tracking per SMS
Estimated Effort: 5 story points
FR-5.3: Push Notifications
ID: FR-5.3
Priority: P3 (Low)
User Story: As a mobile app user, I want push notifications for instant updates.
Status: Deferred to Phase 2 (requires mobile app)
FR-6: Search & Discovery
FR-6.1: Homepage Search
ID: FR-6.1
Priority: P1 (High)
User Story: As a visitor, I want to quickly find fields near me from the homepage.
Acceptance Criteria:
- [ ] Search widget on homepage (prominent placement)
- [ ] Detect user location via browser geolocation API
- [ ] Allow manual location entry (address search)
- [ ] Quick filters accessible without clicking:
- [ ] Sport type (5v5, 7v7, 11v11)
- [ ] Date picker (today, tomorrow, custom)
- [ ] Price range slider
- [ ] "Search" button triggers search
- [ ] Show top 10 nearby fields on map
- [ ] Show results in list view
- [ ] One-click booking (quick book button)
- [ ] Save search preferences in localStorage
Business Rules:
- Default date: Today
- Default radius: 5 km
- Default sort: Distance (nearest first)
Estimated Effort: 5 story points
FR-6.2: Advanced Filters
ID: FR-6.2
Priority: P2 (Medium)
User Story: As a customer, I want to filter search results by multiple criteria.
Filters:
- Distance (1km, 3km, 5km, 10km, 20km)
- Price range (slider: 0 - 1,000,000 VND)
- Sport type (checkboxes: 5v5, 7v7, 11v11)
- Amenities (checkboxes: parking, shower, changing room, lighting, food court)
- Operating hours (open now, open 24h)
- Rating (Phase 2: 4+ stars, 3+ stars)
- Availability (available now, available today)
Acceptance Criteria:
- [ ] Filter panel on left side of search results
- [ ] Combine multiple filters (AND logic)
- [ ] Real-time result updates (no page refresh)
- [ ] Show filter count badge (e.g., "3 filters applied")
- [ ] Clear all filters button
- [ ] Save filter preferences (localStorage)
- [ ] Filter state persists on page refresh
- [ ] Mobile: Filters in bottom sheet
Estimated Effort: 5 story points
FR-6.3: Sorting Options
ID: FR-6.3
Priority: P2 (Medium)
User Story: As a customer, I want to sort search results by different criteria.
Sort Options:
- Distance (nearest first, farthest first)
- Price (low to high, high to low)
- Rating (highest first) - Phase 2
- Popularity (most booked) - Phase 2
- Newest (recently added)
Acceptance Criteria:
- [ ] Sort dropdown in results header
- [ ] Default sort: Distance (nearest first)
- [ ] Results update immediately on sort change
- [ ] Remember last sort preference (localStorage)
- [ ] Sort order shown in dropdown (arrow icons)
- [ ] Fast sorting (< 100ms)
Estimated Effort: 3 story points
Summary
Requirements Count by Category
| Category | Total | P0 | P1 | P2 | P3 |
|---|---|---|---|---|---|
| User Management | 6 | 3 | 2 | 1 | 0 |
| Field Management | 5 | 3 | 2 | 0 | 0 |
| Booking Management | 8 | 4 | 3 | 1 | 0 |
| Payment Processing | 5 | 2 | 3 | 0 | 0 |
| Notifications | 3 | 0 | 1 | 1 | 1 |
| Search & Discovery | 3 | 0 | 1 | 2 | 0 |
| TOTAL | 30 | 12 | 12 | 5 | 1 |
Estimated Total Effort
- MVP (P0 + P1): ~150 story points (~15 sprints / 30 weeks with team of 2)
- Phase 1 Complete (+ P2): ~175 story points (~17 sprints / 34 weeks)
MVP Scope (Must Have for Launch)
P0 Requirements (12):
- FR-1.1, FR-1.2, FR-1.4 (User auth & roles)
- FR-2.1, FR-2.3, FR-2.4 (Field listing & search)
- FR-3.1, FR-3.4, FR-3.5 (Booking creation & conflicts)
- FR-4.1, FR-4.2 (Payment integration)
P1 Requirements (12):
- FR-1.3, FR-1.5 (Profile & password reset)
- FR-2.2, FR-2.5 (Field management & details)
- FR-3.2, FR-3.3, FR-3.8 (Booking history, cancel, receipt)
- FR-4.3, FR-4.4, FR-4.5 (Payment tracking & refunds)
- FR-5.1 (Email notifications)
- FR-6.1 (Homepage search)
Document Version: 1.0
Last Updated: 2025-01-13
Next Review: 2025-02-13
Owner: Product Owner