Coding Standards: Drop — Fintech Payment App

Coding Standards: 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 standards — TypeScript strict, Next.js App Router, fintech security rules

Purpose

These standards apply to all code in Drop. When automated tools enforce a rule, the tool wins. When in doubt, optimize for readability — the next AI agent reading your code is trying to understand intent, not guess it.

Non-negotiable: All new code must pass TypeScript strict check, ESLint, and Prettier formatting before merge. No exceptions.

Fintech non-negotiable: Parameterized SQL everywhere. No stored balances. No stored card numbers or CVV. These are encoded as automated tests — failing these means P0.


1. Language-Specific Conventions

1.1 Naming Conventions

TypeScript (Drop's primary language):

Type Convention Example
Variables camelCase userId, kycStatus
Functions camelCase hashPassword(), calculateFee()
Classes PascalCase TransactionService
Interfaces PascalCase (no I prefix) User, TransactionResult
Types PascalCase KycStatus, CurrencyCode
Enums PascalCase TransactionType, KycStatus
Constants UPPER_SNAKE_CASE REMITTANCE_FEE_RATE, MIN_AMOUNT_NOK
Files — components PascalCase.tsx SendMoneyForm.tsx
Files — utilities camelCase.ts calculateFee.ts, verifyJWT.ts
Files — API routes route.ts (Next.js convention) app/api/auth/login/route.ts
Test files {name}.test.ts auth.test.ts, db.test.ts
E2E test files {name}.spec.ts user-flows.spec.ts

Drop-specific constants:

// Fee rates (business rules — change requires CEO approval + ADR)
const REMITTANCE_FEE_RATE = 0.005;  // 0.5%
const QR_MERCHANT_FEE_RATE = 0.01;  // 1%
const MIN_REMITTANCE_NOK = 100;
const MAX_REMITTANCE_NOK = 50_000;

// Supported corridors
const SUPPORTED_CURRENCIES = ['RSD', 'BAM', 'PKR', 'TRY', 'PLN', 'EUR'] as const;
type CurrencyCode = typeof SUPPORTED_CURRENCIES[number];

1.2 File Organization

src/drop-app/src/
├── app/
│   ├── api/                  # Next.js API Routes
│   │   ├── auth/
│   │   │   ├── login/route.ts
│   │   │   ├── register/route.ts
│   │   │   └── me/route.ts
│   │   ├── transactions/
│   │   │   ├── remittance/route.ts
│   │   │   └── qr-payment/route.ts
│   │   └── rates/
│   │       ├── route.ts           # GET /api/rates
│   │       └── [currency]/route.ts # GET /api/rates/RSD
│   └── (app)/               # Protected pages (dashboard, send-money, etc.)
├── lib/
│   ├── auth.ts              # JWT, bcrypt, session helpers
│   ├── db.ts                # SQLite/PostgreSQL client
│   ├── validate.ts          # Zod schemas for input validation
│   └── fee.ts               # Fee calculation functions
└── components/
    ├── drop-logo.tsx        # Brand logo component
    └── ui/                  # Shared UI components

Rules:

1.3 Import Ordering

// 1. Node built-ins
import { cookies } from 'next/headers';

// 2. External dependencies (node_modules)
import { z } from 'zod';
import * as bcrypt from 'bcrypt';
import { SignJWT, jwtVerify } from 'jose';

// 3. Internal — absolute paths (@/ alias)
import { db } from '@/lib/db';
import { verifyJWT } from '@/lib/auth';
import type { User } from '@/lib/types';

// 4. Internal — relative paths
import { validateRemittanceBody } from './validate';

Enforced by: ESLint import/order rule

1.4 Error Handling Patterns

// PREFERRED — consistent HTTP error responses in Next.js API routes
export async function POST(request: Request): Promise<Response> {
  let body: unknown;
  try {
    body = await request.json();
  } catch {
    return Response.json({ error: 'Invalid JSON body' }, { status: 400 });
  }

  const validation = remittanceSchema.safeParse(body);
  if (!validation.success) {
    return Response.json(
      { error: 'Validation failed', details: validation.error.flatten() },
      { status: 422 }
    );
  }

  // Business logic in lib/ — throws typed errors
  try {
    const result = await processRemittance(validation.data);
    return Response.json(result, { status: 201 });
  } catch (error) {
    if (error instanceof InsufficientBalanceError) {
      return Response.json({ error: 'Insufficient balance' }, { status: 402 });
    }
    if (error instanceof KycRequiredError) {
      return Response.json({ error: 'KYC verification required' }, { status: 403 });
    }
    // Never expose internal errors
    console.error('Unhandled error in remittance:', error);
    return Response.json({ error: 'Internal server error' }, { status: 500 });
  }
}

Rules:


2. Code Formatting

2.1 Formatter

Language Tool Config File Enforced In
TypeScript / JavaScript Prettier .prettierrc CI + pre-commit hook
JSON / YAML Prettier .prettierrc CI + pre-commit hook

Key formatting rules:

Auto-format on save: Enabled via .vscode/settings.json (see local-development-setup.md)

2.2 Linter

Language Tool Config Rules Severity
TypeScript ESLint + TypeScript-ESLint .eslintrc.json Error = blocks CI

Linting rules that are errors (block CI):

Disable linting inline (sparingly):

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External library returns untyped response
const rawResponse: any = await externalLibrary.call();

Every inline disable must have a comment explaining why.


3. Git Conventions

3.1 Commit Message Format

Standard: Conventional Commits

Format:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Types:

Type When to Use
feat New feature
fix Bug fix
docs Documentation only
style Formatting changes (no logic change)
refactor Code change that neither fixes a bug nor adds a feature
test Adding or updating tests
chore Build system, dependency updates, CI changes
perf Performance improvements
security Security fix or hardening

Drop examples:

feat(auth): add bcrypt rounds configuration via env var
fix(transactions): prevent double-spend with per-user lock
test(db): add assertion that users table has no balance column
security(middleware): add CSRF protection to all mutating endpoints
chore: upgrade Next.js to 15.x patch

Breaking changes:

feat(api)!: rename /api/transactions/send to /api/transactions/remittance

BREAKING CHANGE: All clients must update API calls to new path

3.2 Branch Naming Convention

Type Pattern Example
Feature feature/MC-{task-id}-description feature/MC-042-rate-limiting
Bug fix fix/MC-{task-id}-description fix/MC-051-double-spend
Hotfix hotfix/MC-{task-id}-description hotfix/MC-060-jwt-bypass
Security security/MC-{task-id}-description security/MC-070-csrf-fix
Chore chore/MC-{task-id}-description chore/MC-080-upgrade-deps

Rules:

3.3 PR Title & Description Format

Title format: Same as commit message: type(scope): description

Required PR description sections:

  1. What: What does this PR do? (bullet points)
  2. Why: Which Mission Control task / acceptance criterion?
  3. Pass-through model check: Does this change touch DB schema? If yes, confirm no balance/CVV columns added
  4. Tests: What tests were added/modified?
  5. Security: Does this touch auth, payments, or input validation?

3.4 PR Size Guidelines

Size Lines Changed Status
Small < 200 Ideal — review same session
Medium 200-400 Acceptable
Large 400-800 Needs justification — split if possible
Extra Large > 800 Exceptional only — must be pre-approved by John

4. Code Review Guidelines

4.1 What to Look For (Validator Agent Checklist)

Reviewers must check:

Reviewers should NOT block on:

4.2 Review Turnaround

PR Type First review
Security / Critical fix Same session
Standard feature Within 1 session

4.3 Approval Requirements

Branch Required Approvals
main Validator agent approval + John (AI Director) sign-off
Feature branches Validator agent approval
Hotfix John (AI Director) or Alem Bašić (CEO) approval — async OK

4.4 Constructive Review Feedback


5. Testing Standards

5.1 Test Naming Conventions

// Vitest format: describe → it('should [behavior] when [condition]')
describe('hashPassword', () => {
  it('should return a bcrypt hash starting with $2', async () => { ... });
  it('should reject SHA-256 hashes at verify time', async () => { ... });
  it('should reject passwords longer than 1000 characters', async () => { ... });
});

describe('calculateRemittanceFee', () => {
  it('should return 5 NOK fee for 1000 NOK amount', () => { ... });
  it('should reject amounts below 100 NOK', () => { ... });
});

5.2 Test Organization

src/drop-app/__tests__/
├── auth.test.ts              # Unit: bcrypt, JWT, password validation
├── validation.test.ts        # Unit: input validation, XSS, injection
├── transactions.test.ts      # Unit: fee calculation, transaction logic
├── rates.test.ts             # Unit: exchange rates
├── api-endpoints.test.ts     # Integration: all 26 API routes
├── db.test.ts                # Integration: schema compliance, FK constraints
├── middleware.test.ts        # Integration: rate limiting, auth, CSRF
├── api-benchmarks.test.ts    # Performance: bcrypt timing, DB queries
├── feature-flags.test.ts     # Unit: feature flag behavior
├── regression-suite.test.ts  # Regression: critical path smoke
├── sumsub-integration.test.ts # Integration: KYC webhook mock
├── cards-integration.test.ts  # Integration: cards (feature-flagged)
└── e2e/
    ├── user-flows.spec.ts    # E2E: registration, login
    ├── full-flows.spec.ts    # E2E: remittance, QR payment
    └── input-chaos.spec.ts   # E2E: 20+ validation edge cases

5.3 Mocking Guidelines

// PREFERRED: Mock BaaS at the module level (NEXT_PUBLIC_SERVICE_MODE=mock)
// The mock mode is configured via env var — no vi.mock() needed for BaaS

// PREFERRED for unit tests: vi.mock for db queries
vi.mock('@/lib/db', () => ({
  db: {
    query: vi.fn(),
    run: vi.fn(),
  }
}));

// For integration tests: use real SQLite in-memory DB
// (configured in vitest.config.ts — no mock needed)

// NEVER mock the pass-through model assertions in db.test.ts
// NEVER mock the bcrypt rejection tests in auth.test.ts

5.4 Coverage Requirements

Layer Lines Branches Notes
Auth module 100% 100% Fintech security — strictly enforced
Transaction logic 100% 100% Fee calculation — strictly enforced
API handlers ≥ 80% ≥ 70%
Input validation ≥ 90% ≥ 85% Security-critical
DB layer ≥ 90% Compliance assertions required
Overall minimum ≥ 80% CI gate

6. Documentation Standards

6.1 When to Add Comments

// GOOD — explains WHY (not obvious from code)
// Bcrypt rounds set to 12 (not 10) per NFR-SEC02 fintech standard.
// Using 10 rounds would be faster but falls below security threshold.
const BCRYPT_ROUNDS = parseInt(process.env.BCRYPT_ROUNDS ?? '12', 10);

// GOOD — documents non-obvious business rule
// Fee is calculated on gross amount, not total debit (gross + fee)
// This is required by Drop's pass-through model (ADR-003)
const fee = amount * REMITTANCE_FEE_RATE;

// BAD — restates what code says
// Multiply amount by fee rate
const fee = amount * REMITTANCE_FEE_RATE;

Comment when:

6.2 ADR Format

Write an Architecture Decision Record when:

ADR location: comms/decisions/YYYY-MM-DD-decision-title.md

6.3 API Documentation

When adding a new API endpoint, update docs/backend/API-REFERENCE.md:


7. Security Coding Practices — Drop Fintech Standards

Practice Rule
SQL queries NEVER string interpolation. Always parameterized queries (better-sqlite3 ? placeholders)
Password hashing bcrypt ONLY with 12 rounds in production. SHA-256 hashes are REJECTED at login
JWT jose library only. Secret via JWT_SECRET env var. Fail fast if missing
Input validation Zod schemas on ALL API route request bodies. Server-side only — never trust client
Pass-through model NEVER add balance column to users. NEVER add card_number or cvv to cards
Rate limiting DB-backed rate limiter (not in-memory). Auth: 10/min; General: 60/min
CSRF CSRF token required on all POST/PATCH/DELETE endpoints
Cookie settings JWT: httpOnly, SameSite=Strict, Secure in production
Logging NEVER log passwords, JWT tokens, card numbers, or full phone numbers
Error messages NEVER expose DB errors, stack traces, or internal state to users
Dependencies Run npm audit before adding any dependency. No HIGH/CRITICAL CVEs

Security review trigger: Any PR touching auth, payments, DB schema, or input validation must be reviewed by Validator agent AND flagged to John (AI Director).


8. Performance Coding Practices

Practice Rule
Database queries One bounded query per API call where possible. Use JOIN instead of N+1
Pagination Never load all transactions in history — always paginate (default: 20 per page)
bcrypt Password max length = 1,000 chars (validation before bcrypt to prevent DoS)
SQLite Use WAL mode for concurrent reads. Serialize writes (one at a time)
Next.js Use Server Components for static/cached data; Client Components for interactive UI
Bundle size Run npm run build and check bundle analyzer before UI changes


Approval

Role Name Date Signature
Author John (AI Director) 2026-02-23 Approved (AI)
QA Lead Validator Agent 2026-02-23 Approved (AI)
Tech Lead John 2026-02-23 Approved
CEO (Alem) Alem Bašić TBD

Revision #5
Created 2026-02-23 12:06:33 UTC by John
Updated 2026-05-31 20:03:34 UTC by John