Created: 2026-04-09
Status: Draft
Constitution: constitution.md
Input: User description: "Build a P2P payment request feature for a consumer fintech app — prototype focused on correctness, clarity, and speed."
- Q: What technology stack should be used for this project? → A: Next.js 16 + TypeScript, Tailwind CSS + shadcn/ui, Supabase (Postgres + Auth), Prisma ORM, Playwright E2E, Vercel deployment.
- Q: How should recipient contact matching work when linking a request to an existing user? → A: Email only, case-insensitive. Phone-based requests remain unclaimed until a user with matching email exists.
- Q: What canonical term should be used for the user who creates a payment request? → A: "Requester" is the canonical domain term in spec, code, and tests. UI copy may use natural language (e.g., "sent request").
- Q: What format should be required for phone number validation? → A: E.164 format only.
- Q: How should the demo users and seeded data be structured? → A: 3 demo users (Alice, Bob, Charlie) with simple email/password auth and a representative dataset of requests across all statuses, leaving enough PENDING requests to test interactive flows.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) + TypeScript |
| UI | Tailwind CSS + shadcn/ui |
| Database | Supabase (managed PostgreSQL) |
| ORM | Prisma |
| Authentication | Supabase Auth |
| E2E Testing | Playwright (with video recording) |
| Deployment | Vercel |
A user opens the app, navigates to the "Request Money" flow, enters a recipient's email or phone number, an amount, and an optional note. The system validates the inputs, creates a payment request, and presents the user with a unique shareable link.
Why this priority: This is the foundational action. Without request creation, no other flow exists.
Independent Test: Create a request with valid inputs and verify the request appears in the outgoing dashboard with status PENDING, a unique ID, and a shareable link.
Acceptance Scenarios:
- Given authenticated user on the request creation screen, When user submits valid recipient email, amount
$25.00, and note "Dinner split", Then system creates a request with statusPENDING, generates a unique ID and shareable link, and redirects to the request detail view. - Given authenticated user on the request creation screen, When user submits amount
$0or negative, Then system rejects with a validation error: "Amount must be greater than $0.00". - Given authenticated user on the request creation screen, When user submits an invalid email/phone format, Then system rejects with a validation error: "Enter a valid email address or phone number".
- Given authenticated user on the request creation screen, When user submits their own email/phone as recipient, Then system rejects with a validation error: "You cannot request money from yourself".
- Given authenticated user on the request creation screen, When user submits a note exceeding 280 characters, Then system rejects with a validation error or truncates with warning.
- Given authenticated user on the request creation screen, When user submits an amount with more than 2 decimal places (e.g.,
$25.123), Then system rejects with a validation error: "Amount must have at most 2 decimal places".
A user opens their dashboard and sees two tabs/sections: Outgoing (requests they sent) and Incoming (requests they received). Each list shows recipient/requester, amount, status, and creation date. The user can filter by status and search by recipient/requester name or note text.
Why this priority: The dashboard is the primary navigation hub. Users need to see and manage all their requests.
Independent Test: Seed a user with multiple requests in various states. Verify both tabs render correctly, filters narrow results, and search matches expected entries.
Acceptance Scenarios:
- Given authenticated user with 5 outgoing requests (2 PENDING, 1 PAID, 1 DECLINED, 1 EXPIRED), When user views outgoing tab, Then all 5 requests are listed with correct recipient, amount, status, and date.
- Given authenticated user with incoming requests, When user views incoming tab, Then PENDING requests show "Pay" and "Decline" action buttons; terminal-state requests show only status.
- Given user on dashboard, When user filters by status "PENDING", Then only PENDING requests are displayed.
- Given user on dashboard, When user searches "alice", Then only requests involving "alice" (in recipient/requester name or contact) are displayed.
A user opens an incoming PENDING request and clicks "Pay". The system shows a loading/processing state for 2–3 seconds, then displays a success confirmation. The request status updates to PAID in both requester and recipient views.
Why this priority: Payment fulfillment is the core product action — the entire system exists to enable this flow.
Independent Test: As recipient, pay a PENDING request. Verify loading state appears, confirmation is shown, status changes to PAID, and the requester's dashboard reflects the update.
Acceptance Scenarios:
- Given authenticated recipient viewing a PENDING incoming request, When user clicks "Pay", Then system displays a loading/processing indicator for 2–3 seconds, then shows a success confirmation, and the request status becomes
PAID. - Given request status is
PAID, When requester views the same request, Then status showsPAIDwith a paid timestamp. - Given authenticated user who is NOT the recipient, When user attempts to pay, Then system rejects with authorization error.
A user opens an incoming PENDING request and clicks "Decline". The request status updates to DECLINED immediately. Both requester and recipient views reflect the new status.
Why this priority: Decline is the counterpart to Pay — required for a complete lifecycle, but less complex than payment simulation.
Independent Test: As recipient, decline a PENDING request. Verify status changes to DECLINED in both views.
Acceptance Scenarios:
- Given authenticated recipient viewing a PENDING incoming request, When user clicks "Decline", Then request status becomes
DECLINEDimmediately. - Given request status is
DECLINED, When requester views the same request, Then status showsDECLINED. - Given authenticated user who is NOT the recipient, When user attempts to decline, Then system rejects with authorization error.
A user opens an outgoing PENDING request they created and clicks "Cancel". The request status updates to CANCELED. The option is not available for requests in terminal states.
Why this priority: Requesters need the ability to retract pending requests. Required for complete lifecycle coverage.
Independent Test: As requester, cancel a PENDING outgoing request. Verify status changes to CANCELED. Verify cancel is not available on non-PENDING requests.
Acceptance Scenarios:
- Given authenticated requester viewing a PENDING outgoing request, When user clicks "Cancel", Then request status becomes
CANCELED. - Given request status is
PAID, When requester views the request, Then "Cancel" option is not available. - Given authenticated user who is NOT the requester, When user attempts to cancel, Then system rejects with authorization error.
A user clicks on any request to see its full detail: amount, note, requester, recipient, timestamps, current status, and expiration countdown (if PENDING). Actions are contextually displayed based on role (requester vs recipient) and current status.
Why this priority: Detail view is the action surface for Pay/Decline/Cancel — essential but subordinate to those actions.
Independent Test: Open a request detail. Verify all fields are displayed accurately. Verify correct actions for requester vs recipient. Verify expiration countdown for PENDING requests.
Acceptance Scenarios:
- Given a PENDING request, When recipient views detail, Then amount, note, requester info, timestamps, expiration countdown, "Pay" and "Decline" buttons are displayed.
- Given a PENDING request, When requester views detail, Then the same info is shown with "Cancel" button instead of Pay/Decline.
- Given a PAID request, When any party views detail, Then status shows "Paid", paid timestamp is displayed, and no action buttons are shown.
- Given a request accessed via shareable link by an unauthenticated user, When viewing, Then request details are visible but no action buttons are available.
Requests that remain PENDING for 7 days automatically transition to EXPIRED. The expiration is evaluated dynamically on read (no background job). Expired requests cannot be paid or declined.
Why this priority: Expiration prevents stale requests from lingering. Required by spec, but behavior is passive (evaluated on read).
Independent Test: Create a request with a creation timestamp 8 days in the past. Verify it shows as EXPIRED. Verify Pay/Decline are disabled.
Acceptance Scenarios:
- Given a request created 8 days ago still in
PENDINGstatus, When any user views the request, Then status is displayed asEXPIRED. - Given an expired request, When recipient attempts to pay, Then system rejects: "This request has expired".
- Given a PENDING request created 6 days ago, When user views the detail, Then expiration countdown shows approximately "1 day remaining".
Each payment request has a unique shareable link. Anyone with the link can view the request details. However, only the authenticated recipient (whose email matches the request's recipient contact) can perform actions (Pay/Decline).
Why this priority: Nice-to-have for sharing requests outside the app. Lower priority than core lifecycle flows.
Independent Test: Access a shareable link as an unauthenticated user — verify read-only view. Access as the matching authenticated user — verify actions are available.
Acceptance Scenarios:
- Given a shareable link for a PENDING request, When an unauthenticated user visits the link, Then request details (amount, note, requester, status) are visible but no actions are available.
- Given a shareable link, When authenticated user whose email matches the recipient visits, Then Pay and Decline actions are available.
- Given a shareable link, When authenticated user whose email does NOT match the recipient visits, Then request details are visible but no actions are available.
Users authenticate via a simplified mechanism (mock auth or magic-link style). Demo users are pre-seeded for testing. No real email delivery is required.
Why this priority: Auth is infrastructure — required for the system to work but not the core feature being evaluated.
Independent Test: Log in as a demo user and verify access to dashboard. Attempt to access protected routes unauthenticated and verify redirect.
Acceptance Scenarios:
- Given a demo user exists in the system, When user logs in with demo credentials, Then user is authenticated and redirected to the dashboard.
- Given an unauthenticated user, When user attempts to access the dashboard, Then user is redirected to the login page.
- Given an authenticated user, When user logs out, Then session is terminated and user is redirected to login.
- Expired request payment attempt: System must reject with clear message "This request has expired" and not allow state change.
- Terminal state action attempt: Any action on a PAID, DECLINED, EXPIRED, or CANCELED request must be rejected. The system must not expose action buttons for terminal states.
- Invalid recipient contact: Malformed email or phone must be caught at input validation before request creation.
- Invalid amount: Zero, negative, or >2 decimal places must be rejected with specific error messages.
- Self-request: User cannot send a request to their own email/phone.
- Unauthorized actions: Non-recipient attempting Pay/Decline, non-requester attempting Cancel — must return authorization error.
- Request not found: Invalid or nonexistent request ID returns 404 with user-friendly message.
- Duplicate requests: Allowed. A user may send multiple requests to the same recipient for the same amount. Each is an independent entity.
- Concurrent state change: If two users attempt to change the same request state simultaneously, only the first should succeed (optimistic or database-level enforcement).
- FR-001: System MUST allow authenticated users to create payment requests with recipient contact (email or phone), amount, and optional note.
- FR-002: System MUST validate that amount is > 0 with at most 2 decimal places.
- FR-003: System MUST validate recipient contact. Emails must be valid format. Phone numbers must strictly adhere to E.164 format (e.g., +14155552671). Recipient matching to existing users is performed by case-insensitive email comparison only.
- FR-004: System MUST prevent users from creating requests to their own contact information.
- FR-005: System MUST enforce a maximum note length of 280 characters.
- FR-006: System MUST generate a unique ID and shareable token for each payment request.
- FR-007: System MUST store monetary amounts as integer cents internally (no floating-point). Display as formatted currency in the UI.
- FR-008: System MUST enforce the state machine: only PENDING requests may transition to PAID, DECLINED, EXPIRED, or CANCELED.
- FR-009: System MUST NOT allow any transitions out of terminal states (PAID, DECLINED, EXPIRED, CANCELED).
- FR-010: System MUST restrict Pay and Decline actions to the request recipient only.
- FR-011: System MUST restrict Cancel action to the request requester only.
- FR-012: System MUST simulate payment processing with a loading state of 2–3 seconds before confirming PAID status.
- FR-013: System MUST evaluate expiration dynamically on read: any PENDING request older than 7 days is treated as EXPIRED.
- FR-014: System MUST display an expiration countdown on PENDING request detail views.
- FR-015: System MUST provide a dashboard with outgoing and incoming request lists, filterable by status and searchable by contact/note.
- FR-016: System MUST allow unauthenticated read-only access to request details via shareable link.
- FR-017: System MUST only allow actions (Pay/Decline) via shareable link if the authenticated user's email matches the recipient contact.
- FR-018: System MUST provide 3 demo users (Alice, Bob, Charlie) pre-seeded with a representative dataset of requests across all states (PENDING, PAID, DECLINED, EXPIRED, CANCELED), ensuring enough PENDING requests remain to test interactive flows.
- FR-019: System MUST use Supabase Auth for authentication. Demo users are pre-seeded in the Supabase project. No real email delivery required for demo evaluation.
- FR-020: System MUST be responsive (functional on mobile and desktop viewports).
- User: Represents an authenticated user. Key attributes: unique ID, name, email, phone. Demo users are seeded at startup.
- PaymentRequest: Core domain entity. Attributes:
id— unique identifier (UUID or similar)requesterId— reference to the user who created the request (formerlysenderId)recipientId— reference to recipient user (nullable; populated if contact matches an existing user)recipientContact— email or phone string as entered by the requesteramountCents— integer, amount in cents (e.g., 2500 = $25.00)currency— fixed:USDnote— optional text, max 280 charactersstatus— enum:PENDING,PAID,DECLINED,EXPIRED,CANCELEDshareToken— unique token for shareable linkcreatedAt— UTC timestampexpiresAt— UTC timestamp (createdAt + 7 days)paidAt— nullable UTC timestampdeclinedAt— nullable UTC timestampcanceledAt— nullable UTC timestamp
stateDiagram-v2
[*] --> PENDING : Request Created
PENDING --> PAID : Recipient Pays
PENDING --> DECLINED : Recipient Declines
PENDING --> EXPIRED : 7 Days Elapsed (evaluated on read)
PENDING --> CANCELED : Requester Cancels
PAID --> [*]
DECLINED --> [*]
EXPIRED --> [*]
CANCELED --> [*]
Transition Rules:
| From | To | Actor | Condition |
|---|---|---|---|
| PENDING | PAID | Recipient | Request not expired |
| PENDING | DECLINED | Recipient | Request not expired |
| PENDING | EXPIRED | System | now() > expiresAt evaluated on read |
| PENDING | CANCELED | Requester | — |
- SC-001: Users can complete the full request lifecycle: create → view → act (pay/decline/cancel) → verify updated state. All transitions are consistent.
- SC-002: All 9 user stories pass their acceptance scenarios in automated E2E tests.
- SC-003: State machine invariants hold: no transitions out of terminal states, no unauthorized actions pass.
- SC-004: UI accurately reflects backend state at all times — requester and recipient views are consistent.
- SC-005: E2E test suite produces screen recordings demonstrating all critical flows.
- SC-006: System is deployable to a public URL and testable without local setup.
- SC-007: Demo users and seeded data allow immediate evaluation without manual data entry.
- A-001: No real payment processing is performed. Payment is simulated with a timed delay (2–3 seconds) and always succeeds.
- A-002: No email, SMS, or push notifications are sent. Shareable links are generated but must be manually shared.
- A-003: Recipient matching is email-only and case-insensitive. If
recipientContactmatches an existing user's email (lowercased), the request is linked to that user and appears in their incoming dashboard. Phone-number-based requests remain unclaimed until a user with a matching email is associated. - A-004: If the recipient contact does not match any existing user, the request remains "unclaimed". It is viewable via shareable link but actions require authentication with a matching email.
- A-005: Single currency only: USD. All amounts are in USD cents internally.
- A-006: Authentication uses Supabase Auth with pre-seeded demo users. No real email delivery; demo credentials are used directly.
- A-007: Expiration is evaluated dynamically on read. No background workers, cron jobs, or scheduled tasks.
- A-008: Real-time updates are not required. Page refresh or polling is acceptable for state synchronization.
- A-009: All 3 demo users (Alice, Bob, Charlie) are seeded at application startup with predictable email and password credentials.
- A-010: The system targets modern browsers (Chrome, Firefox, Safari, Edge latest 2 versions). No IE support.
- Real financial transactions or payment gateway integrations
- Email, SMS, or push notification delivery
- Multi-currency support
- Fraud detection, KYC, or AML compliance
- Ledger or account balance system
- Advanced authentication (OAuth, SSO, MFA)
- Real-time updates (WebSockets, SSE)
- File attachments on requests
- Recurring or scheduled payment requests
- User profile management beyond basic demo setup