Skip to main content

Functional requirements

Requirements use the identifiers below in stories, acceptance criteria, and tests.

Catalog and pricing​

IDRequirement
FR-1The public catalog lists active services and active packages.
FR-2Each package has a name, a duration in minutes, and a price in USD cents.
FR-3The server computes every order total from the stored package price. The client cannot submit a price that the server trusts.
FR-4An order item stores the package name and unit price at purchase time. Later catalog edits do not change that snapshot.

Identity​

IDRequirement
FR-5Email addresses are stored trimmed and in lowercase, and are unique.
FR-6A new payer is created as a customer in PENDING_SETUP. Verified payment sends a single-use account setup link.
FR-7An email that already belongs to an active customer must sign in. The system attaches the booking to that account and does not send account setup.
FR-8Passwords are stored only as Argon2id hashes. Refresh tokens are stored as hashes. Access tokens are short-lived signed tokens and are not stored.
FR-9Password recovery uses a single-use hashed token.
FR-10An administrator must pass multi-factor authentication before using administrative actions in production.

Booking and payment​

IDRequirement
FR-11Creating a booking also creates one order in PENDING_PAYMENT and one booking in PENDING_PAYMENT.
FR-12Checkout is created with Stripe in USD for the server-calculated total.
FR-13The success or cancel redirect never marks an order paid and never approves a booking.
FR-14A verified provider webhook is deduplicated with UNIQUE(provider, event_id). Replaying an event does not apply the payment twice.
FR-15A verified successful payment marks the order paid and the booking PENDING_APPROVAL, then notifies the photographer.
FR-16The photographer approves a paid booking into CONFIRMED or rejects it.
FR-17Rejecting a paid booking starts a full refund of the captured USD payment and moves the booking to REJECTED.
FR-18Cancelling a confirmed booking whose payment was captured starts the same full USD refund as a rejection. A completed booking is not refunded by this workflow.
FR-19Failed or abandoned payments leave the booking in PENDING_PAYMENT or mark the payment failed. The customer can retry. A retry adds a payment attempt on the same order.
IDRequirement
FR-20Photographs are uploaded to a private bucket. The database stores the object key, thumbnail key, and status.
FR-21A gallery has a publication status of draft, published, or archived, and a separate access status of pending approval, approved, or revoked.
FR-22Publishing a gallery does not approve access. Access stays pending until the photographer approves it.
FR-23Every view and download checks, on the server, that the caller owns the gallery, publication is PUBLISHED, access is APPROVED, and the photo is READY.
FR-24The API issues a short-lived signed URL only after FR-23 passes. The initial lifetime is 5 minutes.
FR-25Revoking access writes a review row, sets access to REVOKED, and stops new signed URLs. URLs that were already issued remain valid until they expire.
FR-26Each approval, rejection, or revocation appends a gallery_access_reviews row. The current status used for authorization stays on galleries.
FR-27A customer with approved access can rate each photo once, from 1 to 5, with optional feedback. The photographer receives a notification.

Referrals and discounts​

IDRequirement
FR-28Each active customer has one referral code.
FR-29A customer can be referred once. Self-referral is rejected.
FR-30The reward rate is the active policy's basis points. 10,000 basis points equals 100 percent. The allowed range is 0 through 10,000.
FR-31The eligible amount is the service subtotal after discounts. Taxes and travel are excluded. Refunded service amounts reduce the eligible amount.
FR-32The reward in cents uses integer half-up rounding: (eligible_cents * basis_points + 5000) / 10000.
FR-33One qualifying reward exists for a referral and qualifying order. The same order cannot fund two rewards.
FR-34A reward becomes spendable when its ledger credit is posted. That happens when the qualifying booking is confirmed and the order remains paid.
FR-35Redemption applies the available balance as a discount on a future booking, inside one database transaction, and cannot spend the same balance twice.
FR-36A refund posts a ledger reversal. Ledger rows are never updated or deleted.

Notifications, jobs, and audit​

IDRequirement
FR-37The system notifies the photographer when a payment is verified and when a customer rates a photo.
FR-38The system emails the customer when gallery access is approved.
FR-39Email delivery is tracked separately from the in-app notification.
FR-40Thumbnail generation, email sending, refund calls, and reward expiry run as retryable background jobs.
FR-41Administrative and security actions append an audit record. Audit rows are not updated or deleted.

Authorization​

IDRequirement
FR-42Customer routes require an authenticated customer and ownership of the booking, order, gallery, or rating.
FR-43Approval, publication, refund, policy, and audit routes require an administrator who has completed multi-factor authentication.
FR-44Gallery authorization is enforced by the API. Hiding a link in Next.js is not the control.