Acceptance Criteria: Drop — Fintech Payment App
Acceptance Criteria: Drop — Fintech Payment App
Project: Drop — Remittance + QR Payments
Version: 1.0
Date: 2026-02-23
Author: John (AI Director)
Status: Approved
Reviewers: Alem Bašić (CEO)
Document History
| Version |
Date |
Author |
Changes |
| 0.1 |
2026-02-23 |
John |
Initial AC based on integration tests and E2E test suite |
1. Purpose & Methodology
1.1 What Are Acceptance Criteria?
Acceptance criteria define the conditions under which Drop features are considered done and accepted by the business. They answer: "How will we know when this feature is done?"
Given [initial context / precondition]
When [action or event occurs]
Then [expected outcome observed]
And [additional chained outcomes]
1.3 Categories
| Category |
Description |
| Positive (Happy Path) |
System works with valid inputs |
| Negative (Sad Path) |
System handles invalid inputs gracefully |
| Edge Case |
Boundary conditions |
| Security |
Injection, auth, abuse prevention |
| Compliance |
Regulatory requirements |
2. Feature Acceptance Criteria
Module: Authentication & Onboarding
Feature: User Registration — 3-step Onboarding (FR-001)
Feature Description: New residents register via email + DOB validation → OTP → PIN. BankID replaces DOB in Phase 2.
Business Requirement: BR-001, BR-002
Linked User Stories: US-001
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-001 |
Successful registration |
Valid email, password ≥8 chars, Norwegian phone (+47), DOB ≥18 years |
User submits registration form |
201 created; user proceeds to OTP step; no password hash in response |
| AC-002 |
OTP verification passes |
Account created; OTP sent to phone |
User enters correct 6-digit OTP |
User proceeds to PIN setup |
| AC-003 |
PIN setup completes |
OTP verified |
User enters and confirms 4-digit PIN |
Account activated; JWT cookie set; redirect to dashboard |
Negative Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-004 |
Under-18 rejected |
DOB indicating age < 18 |
User submits registration |
422 with message "Du må være minst 18 år" |
| AC-005 |
Duplicate email |
Existing account for email |
User submits same email |
409 "Email already in use" |
| AC-006 |
Missing required field |
Registration form |
User submits without first_name |
422 validation error with field details array |
| AC-007 |
Short password |
Password < 8 characters |
User submits |
422 "Password must be at least 8 characters" |
| AC-008 |
Invalid phone format |
Non-+47 phone number |
User submits |
422 validation error |
Edge Cases:
| # |
Scenario |
Given |
When |
Then |
| AC-009 |
Boundary age (exactly 18) |
DOB = today minus 18 years exactly |
Registration submitted |
Account created successfully |
| AC-010 |
Invalid JSON body |
Malformed JSON in request |
POST /api/auth/register |
400 "Invalid JSON body" |
Security Acceptance Criteria:
| # |
Category |
Criterion |
| AC-011 |
Auth |
Password hash not returned in any API response |
| AC-012 |
Auth |
bcrypt used (hash starts with $2); SHA-256 rejected |
| AC-013 |
Rate limiting |
10+ registrations from same IP in 1 min → 429 |
Feature: User Login (FR-002)
Business Requirement: BR-001
Linked User Stories: US-002
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-020 |
Successful login |
Registered user with valid credentials |
POST /api/auth/login |
200; JWT in httpOnly cookie; user object returned |
| AC-021 |
Authenticated route access |
Valid JWT cookie |
GET /api/auth/me |
200; current user object returned |
Negative Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-022 |
Wrong password |
Registered user |
Submits incorrect password |
401 "Invalid email or password" (no enumeration) |
| AC-023 |
Non-existent email |
No account with that email |
Login submitted |
401 "Invalid email or password" (same error — no enumeration) |
| AC-024 |
Rate limiting |
10+ login attempts from same IP in 1 minute |
Next attempt |
429 rate limit response |
| AC-025 |
Missing credentials |
Empty email or password |
Login submitted |
400 "Email and password required" |
Edge Cases:
| # |
Scenario |
Given |
When |
Then |
| AC-026 |
Invalid JSON |
Malformed body |
POST /api/auth/login |
400 "Invalid JSON body" |
Module: Remittance (Send Money)
Feature: Remittance Transaction (FR-020)
Business Requirement: BR-003, BR-005
Linked User Stories: US-010
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-030 |
Successful remittance |
Authenticated + KYC-approved user; valid recipient; sufficient balance |
POST /api/transactions/remittance with amount=1000, currency=RSD |
201; transaction created; fee = 5 NOK (0.5%); transaction in history |
| AC-031 |
Fee calculation correct |
Amount = 2000 NOK |
Remittance submitted |
fee = 10 NOK; recipient_amount = 2000 NOK (gross) |
| AC-032 |
Transaction in history |
Successful remittance |
GET /api/transactions |
Transaction appears with status=completed |
Negative Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-033 |
Unauthenticated user |
No JWT cookie |
POST /api/transactions/remittance |
401 Unauthorized |
| AC-034 |
KYC not approved |
kyc_status=pending |
Remittance submitted |
403 "KYC verification required" |
| AC-035 |
Recipient not found |
Invalid recipientId |
Remittance submitted |
404 "Recipient not found" |
| AC-036 |
Insufficient balance |
Balance < (amount + fee) |
Remittance submitted |
402 "Insufficient balance" |
| AC-037 |
Amount below minimum |
amount = 99 NOK |
Submitted |
400 "Amount must be between 100 and 50000 NOK" |
| AC-038 |
Amount above maximum |
amount = 50001 NOK |
Submitted |
400 validation error |
| AC-039 |
Invalid amount (NaN) |
amount = "abc" |
Submitted |
400 "Invalid amount" |
Compliance Criteria:
| # |
Category |
Criterion |
| AC-040 |
AML |
Transaction > 50,000 NOK rejected; daily limits enforced |
| AC-041 |
Pass-through |
No balance column in users table; DB test verifies absence |
Feature: Exchange Rates API (FR-021)
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-050 |
All rates returned |
GET /api/rates |
Called |
6 NOK exchange rates returned (RSD, BAM, PKR, TRY, PLN, EUR) |
| AC-051 |
Single rate returned |
GET /api/rates/RSD |
Called |
NOK→RSD rate returned |
| AC-052 |
Case insensitive |
GET /api/rates/rsd (lowercase) |
Called |
Same result as /api/rates/RSD |
Negative Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-053 |
Unsupported currency |
GET /api/rates/XXX |
Called |
404 Not Found |
Module: QR Payments
Feature: QR Payment — Consumer (FR-030)
Business Requirement: BR-004, BR-005
Linked User Stories: US-020
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-060 |
Successful QR payment |
Authenticated + KYC-approved user; valid merchantId; sufficient balance |
POST /api/transactions/qr-payment with amount=129, merchantId=valid |
201; merchant_fee = 1.29 NOK (1%); transaction created |
| AC-061 |
Fee calculation |
amount = 200 NOK |
QR payment submitted |
merchant_fee = 2 NOK (1%) |
Negative Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-062 |
Invalid merchant |
Non-existent merchantId |
Payment submitted |
404 "Merchant not found" |
| AC-063 |
Amount < 1 NOK |
amount = 0 |
Submitted |
400 validation error |
| AC-064 |
Missing merchantId |
No merchantId in body |
Submitted |
400 "Merchant ID is required" |
| AC-065 |
Unauthenticated |
No JWT |
Submitted |
401 Unauthorized |
Feature: Merchant Registration + QR Generation (FR-031)
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-070 |
Merchant registered |
Authenticated user |
POST /api/merchants with business_name, bank_account |
Merchant created with unique QR code value |
| AC-071 |
QR code retrievable |
Registered merchant |
GET /api/merchants/me |
Merchant details + QR code returned |
Module: Security (Cross-cutting)
| # |
Scenario |
Given |
When |
Then |
| AC-080 |
XSS in name field |
<script>alert(1)</script> as firstName |
Registration submitted |
422 validation error; no script executed |
| AC-081 |
SQL injection in email |
'; DROP TABLE users;-- as email |
Registration submitted |
422 validation error; no DB mutation |
| AC-082 |
10KB password |
10,000 character password |
Registration submitted |
422 "Password too long" or validation error |
| AC-083 |
Unicode in name |
Bosnian chars (š, đ, ć, č, ž) in name |
Registration submitted |
201 created; name stored correctly |
| AC-084 |
Underage DOB |
DOB indicating 17 years old |
Registration submitted |
422 age validation error |
Module: Compliance
Feature: PCI-DSS Card Data Protection (FR-080, Cards feature)
| # |
Scenario |
Given |
When |
Then |
| AC-090 |
No CVV in DB |
Cards table |
DB schema check |
cards table has NO card_number or cvv columns |
| AC-091 |
No balance in users |
Users table |
DB schema check |
users table has NO balance column (pass-through model) |
| AC-092 |
Only last_four returned |
Authenticated user |
GET /api/cards/[id] |
Response contains last_four only; no full card number |
3. Integration Scenarios
| # |
Integration |
Scenario |
Expected Behavior |
Test Environment |
| INT-001 |
Sumsub KYC |
KYC webhook callback (approved) |
User kyc_status updated to 'approved' |
Mock webhook in integration tests |
| INT-002 |
BaaS PISP |
Payment initiation |
Transaction recorded; confirmation returned |
Mock PISP in NEXT_PUBLIC_SERVICE_MODE=mock |
| INT-003 |
BaaS AISP |
Balance read from bank |
User account balance returned from BaaS |
Mock AISP service |
| INT-004 |
Exchange rates |
Rate data missing |
Error handled gracefully; GET /api/rates/XXX → 404 |
Integration test |
| INT-005 |
BaaS unavailable |
BaaS API down |
System shows user-friendly error; transaction not created |
Mocked timeout in tests |
4. Non-Functional Acceptance Criteria
| # |
Criterion |
Target |
Test Method |
| NF-AC-001 |
bcrypt hashing time |
< 1,000ms |
api-benchmarks.test.ts |
| NF-AC-002 |
Rate limit check time |
< 50ms |
api-benchmarks.test.ts |
| NF-AC-003 |
DB SELECT query |
< 10ms |
api-benchmarks.test.ts |
| NF-AC-004 |
DB INSERT query |
< 20ms |
api-benchmarks.test.ts |
| NF-AC-005 |
50 concurrent rate limit calls |
< 2,000ms total |
api-benchmarks.test.ts |
4.2 Security
| # |
Criterion |
Target |
Test Method |
| NF-AC-010 |
SHA-256 passwords rejected |
verifyPassword returns false for SHA-256 hashes |
auth.test.ts |
| NF-AC-011 |
JWT tampered tokens rejected |
Invalid signature → exception |
auth.test.ts |
| NF-AC-012 |
Rate limiting blocks after limit |
11th request from same IP → 429 |
middleware.test.ts |
| NF-AC-013 |
Input validation completeness |
10+ validation test cases pass |
validation.test.ts |
| NF-AC-014 |
Foreign key constraints enabled |
Sessions cannot be created for non-existent users |
db.test.ts |
4.3 Compliance
| # |
Criterion |
Target |
Test Method |
| NF-AC-020 |
No balance column in users table |
users table schema has no 'balance' |
db.test.ts |
| NF-AC-021 |
No card_number/cvv in cards table |
cards table has no 'card_number' or 'cvv' |
db.test.ts |
| NF-AC-022 |
Transaction types limited |
Only 'remittance' and 'qr_payment' accepted |
db.test.ts |
5. UAT Scenario Mapping
| AC ID |
AC Description |
UAT Scenario ID |
Priority |
| AC-001 |
Successful registration |
UAT-001 |
Critical |
| AC-004 |
Under-18 rejected |
UAT-002 |
Critical |
| AC-020 |
Successful login |
UAT-003 |
Critical |
| AC-030 |
Successful remittance |
UAT-010 |
Critical |
| AC-060 |
Successful QR payment |
UAT-020 |
Critical |
| AC-090 |
No CVV in DB |
UAT-SEC-001 |
Critical |
6. Traceability to Requirements
| AC ID |
Acceptance Criterion |
FR Reference |
BR Reference |
US Reference |
| AC-001 |
Successful registration |
FR-001 |
BR-001 |
US-001 |
| AC-004 |
Under-18 rejected |
FR-001 |
BR-002 |
US-001 |
| AC-020 |
Successful login |
FR-002 |
BR-001 |
US-002 |
| AC-030 |
Successful remittance |
FR-020 |
BR-003, BR-005 |
US-010 |
| AC-060 |
Successful QR payment |
FR-030 |
BR-004, BR-005 |
US-020 |
| AC-090 |
No CVV in DB |
FR-080 |
BR-005, RUL-010 |
— |
| NF-AC-020 |
No balance column |
FR-001 |
BR-005, RUL-003 |
US-001 |
Full traceability matrix: [requirements-traceability-matrix.md](requirements-traceability-matrix.md)
Approval
| Role |
Name |
Date |
Signature |
| Author |
John (AI Director) |
2026-02-23 |
Approved (AI) |
| QA Engineer |
Validator agent |
2026-02-23 |
Approved (AI) |
| Product Owner |
John |
2026-02-23 |
Approved |
| AI Director (John) |
John |
2026-02-23 |
Approved |
| CEO (Alem) |
Alem Bašić |
TBD |
|