Skip to content

Latest commit

 

History

History
309 lines (215 loc) · 20.2 KB

File metadata and controls

309 lines (215 loc) · 20.2 KB

Feature Specification: P2P Payment Request

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."


Clarifications

Session 2026-04-09

  • 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.

Technology Stack

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

User Scenarios & Testing

User Story 1 — Create a Payment Request (Priority: P1)

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:

  1. 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 status PENDING, generates a unique ID and shareable link, and redirects to the request detail view.
  2. Given authenticated user on the request creation screen, When user submits amount $0 or negative, Then system rejects with a validation error: "Amount must be greater than $0.00".
  3. 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".
  4. 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".
  5. 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.
  6. 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".

User Story 2 — View Request Management Dashboard (Priority: P1)

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:

  1. 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.
  2. 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.
  3. Given user on dashboard, When user filters by status "PENDING", Then only PENDING requests are displayed.
  4. Given user on dashboard, When user searches "alice", Then only requests involving "alice" (in recipient/requester name or contact) are displayed.

User Story 3 — Pay a Request (Priority: P1)

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:

  1. 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.
  2. Given request status is PAID, When requester views the same request, Then status shows PAID with a paid timestamp.
  3. Given authenticated user who is NOT the recipient, When user attempts to pay, Then system rejects with authorization error.

User Story 4 — Decline a Request (Priority: P2)

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:

  1. Given authenticated recipient viewing a PENDING incoming request, When user clicks "Decline", Then request status becomes DECLINED immediately.
  2. Given request status is DECLINED, When requester views the same request, Then status shows DECLINED.
  3. Given authenticated user who is NOT the recipient, When user attempts to decline, Then system rejects with authorization error.

User Story 5 — Cancel a Request (Priority: P2)

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:

  1. Given authenticated requester viewing a PENDING outgoing request, When user clicks "Cancel", Then request status becomes CANCELED.
  2. Given request status is PAID, When requester views the request, Then "Cancel" option is not available.
  3. Given authenticated user who is NOT the requester, When user attempts to cancel, Then system rejects with authorization error.

User Story 6 — View Request Detail (Priority: P2)

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:

  1. Given a PENDING request, When recipient views detail, Then amount, note, requester info, timestamps, expiration countdown, "Pay" and "Decline" buttons are displayed.
  2. Given a PENDING request, When requester views detail, Then the same info is shown with "Cancel" button instead of Pay/Decline.
  3. Given a PAID request, When any party views detail, Then status shows "Paid", paid timestamp is displayed, and no action buttons are shown.
  4. Given a request accessed via shareable link by an unauthenticated user, When viewing, Then request details are visible but no action buttons are available.

User Story 7 — Request Expiration (Priority: P2)

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:

  1. Given a request created 8 days ago still in PENDING status, When any user views the request, Then status is displayed as EXPIRED.
  2. Given an expired request, When recipient attempts to pay, Then system rejects: "This request has expired".
  3. Given a PENDING request created 6 days ago, When user views the detail, Then expiration countdown shows approximately "1 day remaining".

User Story 8 — Shareable Link Access (Priority: P3)

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:

  1. 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.
  2. Given a shareable link, When authenticated user whose email matches the recipient visits, Then Pay and Decline actions are available.
  3. 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.

User Story 9 — Authentication (Priority: P3)

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:

  1. Given a demo user exists in the system, When user logs in with demo credentials, Then user is authenticated and redirected to the dashboard.
  2. Given an unauthenticated user, When user attempts to access the dashboard, Then user is redirected to the login page.
  3. Given an authenticated user, When user logs out, Then session is terminated and user is redirected to login.

Edge Cases

  • 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).

Requirements

Functional Requirements

  • 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).

Key Entities

  • 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 (formerly senderId)
    • recipientId — reference to recipient user (nullable; populated if contact matches an existing user)
    • recipientContact — email or phone string as entered by the requester
    • amountCents — integer, amount in cents (e.g., 2500 = $25.00)
    • currency — fixed: USD
    • note — optional text, max 280 characters
    • status — enum: PENDING, PAID, DECLINED, EXPIRED, CANCELED
    • shareToken — unique token for shareable link
    • createdAt — UTC timestamp
    • expiresAt — UTC timestamp (createdAt + 7 days)
    • paidAt — nullable UTC timestamp
    • declinedAt — nullable UTC timestamp
    • canceledAt — nullable UTC timestamp

Request Status State Machine

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 --> [*]
Loading

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

Success Criteria

Measurable Outcomes

  • 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.

Assumptions

  • 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 recipientContact matches 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.

Out of Scope

  • 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