Features, Merchants & Rates
Drop — Merchant, Recipients & Rates
Note (2026-02-14): This document predates the current architecture. Drop now uses a pass-through PSD2 model (PISP/AISP) — Drop NEVER holds customer money. Some sections below reference wallet/balance concepts from the earlier BaaS design. The current architecture is defined in architecture-document.md and Drop CLAUDE.md.
1. Recipients
Data Model
interface Contact {
id: string;
name: string;
iban: string;
avatar: string; // 2-char initials
}
Current Implementation
Sample contacts (hardcoded in src/app/send/page.tsx):
| Name | IBAN | Country |
|---|---|---|
| Sara M. | DE89370400440532013000 | Germany |
| Amir K. | FR7630006000011234567890189 | France |
| Lejla H. | AT611904300234573201 | Austria |
Mock contacts (src/lib/mockData.ts):
| Name | IBAN | Country |
|---|---|---|
| John Doe | DE89 3704 0044 0532 0130 00 | Germany |
| Jane Smith | FR76 3000 6000 0112 3456 7890 189 | France |
| Mike Wilson | GB29 NWBK 6016 1331 9268 19 | UK |
RecipientStep UI (src/app/send/page.tsx)
- Search bar (filters by name or IBAN)
- Contact list with avatar, name, truncated IBAN
- "Add new recipient" button (placeholder — not wired)
- Click to select → moves to AmountStep
Send Money Flow (6 steps)
RecipientStep → AmountStep → ConfirmStep → Processing → Success/Error
- Recipient — select from contacts or search
- Amount — input + quick buttons (€10, €50, €100, €200) + optional note
- Confirm — review: amount, recipient, IBAN, note, fee (Free)
- Processing — loading animation
- Success — confirmation with amount and name
- Error — message + retry
API: Send Money
POST /api/transactions
Authorization: Bearer <jwt>
Body: { toIban: string, amount: number, reference?: string }
Validations:
- Required:
toIban,amount amount > 0- Balance sufficient
- Creates
SepaCredittransaction with directionDebit - Updates account balance atomically
Future (not yet implemented)
- Add/edit/delete saved recipients
- Favorite/frequent contacts
- Import from phone contacts (mobile app)
- Recipient groups
2. Merchant Payments
Current State: Demo Only
Merchant payments exist as:
CardTransactiontype in schemasimulatePurchase()method in AppContext (no-op, logs to console)- Demo buttons on dashboard: "Netflix €9.99", "Groceries €45"
Transaction Types
| Type | Direction | Use Case |
|---|---|---|
SepaCredit |
Debit | Outgoing SEPA transfer |
SepaDebit |
Credit | Incoming SEPA transfer |
CardTransaction |
Debit | Card purchase at merchant |
AppContext Method
const simulatePurchase = async (amount: number, merchant: string) => {
// No-op — no API route for card purchases yet
console.log("[AppContext] simulatePurchase not implemented:", { amount, merchant });
return { id: 'demo_auth', amount, merchant };
};
Stripe Issuing Mock (src/lib/services/mock-stripe.ts)
- Checks card status (active)
- Checks spending limit (
spending_limitvsspent_this_month + amount) - Returns approved/declined
- Physical card ordering supported
Transaction Display (src/components/TransactionItem.tsx)
Shows for each transaction:
- Icon (emoji for type)
- Description (merchant name or counterparty)
- Date (formatted)
- Amount (green for incoming, gray for outgoing)
Future (post-MVP)
- Merchant directory/discovery
- Bill pay integration
- Recurring payments to merchants
- Purchase categorization (AI)
- Merchant notifications
3. Rates, Fees & Limits
Currency
MVP: EUR only (single account)
Formatting (src/lib/mockData.ts):
formatCurrency(amount, currency = "EUR")
// Uses Intl.NumberFormat("de-DE") → "€1.234,56"
Fees
| Transaction Type | Fee |
|---|---|
| SEPA transfer | Free |
| Card top-up | Free |
| Card payment | Free (interchange 0.2-0.3% from merchant) |
All transfers show "Free" in the UI confirmation step.
Transfer Limits (from MVP spec)
| Type | Daily | Monthly |
|---|---|---|
| Internal P2P | €5,000 | €20,000 |
| SEPA | €2,000 | €10,000 |
Top-up Limits
| Parameter | Value |
|---|---|
| Minimum | €5 |
| Maximum | €10,000 |
| Preset options | €20, €50, €100, €200, €500 |
Card Spending
| Parameter | Value |
|---|---|
| Monthly limit (default) | €5,000 |
| Tracked via | spent_this_month column |
Revenue Model (post-MVP)
| Stream | Rate |
|---|---|
| Interchange fees | 0.2-0.3% of card transactions |
| FX markup | 0.5-2% on currency conversion |
| Premium subscription | €5-15/month |
| Interest income | On deposits |
| Lending | Personal loans, BNPL, overdrafts |
Multi-Currency (Future)
- Additional currency accounts (GBP, USD, etc.)
- Real-time FX rates display
- FX conversion with 0.5-2% markup
- Currency selection at transfer time
4. Database Schema Reference
transactions
CREATE TABLE transactions (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL REFERENCES accounts(id),
type TEXT NOT NULL, -- SepaCredit | SepaDebit | CardTransaction
amount REAL NOT NULL,
currency TEXT DEFAULT 'EUR',
direction TEXT NOT NULL, -- Credit | Debit
status TEXT DEFAULT 'Pending', -- Pending | Booked | Rejected
counterparty TEXT, -- Recipient IBAN or merchant name
reference TEXT, -- Payment note
created_at TEXT DEFAULT (datetime('now'))
);
accounts
CREATE TABLE accounts (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
iban TEXT UNIQUE NOT NULL, -- Format: BA393912XXXXXXXX
bic TEXT DEFAULT 'FONLBA22',
currency TEXT DEFAULT 'EUR',
balance REAL DEFAULT 0,
status TEXT DEFAULT 'Opened',
created_at TEXT DEFAULT (datetime('now'))
);
cards
CREATE TABLE cards (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
type TEXT NOT NULL DEFAULT 'virtual',
brand TEXT DEFAULT 'Visa',
last4 TEXT NOT NULL,
exp_month INTEGER NOT NULL,
exp_year INTEGER NOT NULL,
status TEXT DEFAULT 'active',
spending_limit REAL DEFAULT 5000,
spent_this_month REAL DEFAULT 0,
cardholder_name TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
5. Open Tasks (Related)
| Task | Priority | Description |
|---|---|---|
| #191 | HIGH | Wire /send page to /api/transactions/remittance |
| #192 | HIGH | Wire /scan page to /api/transactions/qr-payment |
| #193 | HIGH | Wire /merchant page to real APIs |
| #198 | LOW | Delete mock-data.ts and orphaned components |