Skip to main content

drop-srbija-plan

Plan: Drop Srbija — Phase 2 Build Plan

Created: 2026-04-16 Product: Drop Srbija — Serbian market phone-based payment app Scaffold: ~/ALAI/products/DropSrbija/ Status: Scaffold complete. Phase 2 build begins.


Research Summary (5-Expert Gap Analysis)

Critical Discoveries

# Finding Severity Expert
1 NBS IPS is ISO 20022 XML via mTLS — NOT REST. Blueprint's https://ips.nbs.rs/api/v1 endpoint DOES NOT EXIST P0 Markos Zachariadis
2 Drop cannot connect to NBS IPS directly — must go through a licensed bank partner P0 Markos Zachariadis
3 No Serbian d.o.o. legal entity → NBS PI license application impossible P0 Thaer Sabri
4 No NBS PI license application initiated — operating live IPS is criminal violation P0 Thaer Sabri
5 OTP stored as hash($otp) = plaintext string — NOT bcrypt. Security theater. P0 Petter Graff / Angie Jones
6 Phone-to-IBAN resolution missing — NBS IPS needs IBAN, not phone. No Serbian registry exists P0 Markos Zachariadis
7 Phase 3 (live IPS) BEFORE Phase 4 (KYC) = ZPNFTM AML violation — sequence is illegal P0 Thaer Sabri
8 69 test cases needed, 0 exist. No test infrastructure, no src/test directory P0 Angie Jones
9 NBS IPS P2P transfers are FREE by regulation — per-transaction fee model impossible for consumers P1 Markos Zachariadis + BA
10 IPS QR must use DinaCard standard — custom HMAC QR unreadable by all Serbian bank apps P1 Markos Zachariadis
11 No SMS provider integrated (Twilio referenced but not implemented) P1 Petter Graff
12 Sanctions screening references "NBS SDN list" — doesn't exist. Must use UN + EU + Serbian lists P1 Thaer Sabri
13 Pre-transaction disclosure screen missing — legally required by Law on Payment Services P1 Thaer Sabri
14 USPNFT AML reporting module entirely absent from architecture P1 Thaer Sabri
15 No CI/CD pipeline, no Dockerfile, no docker-compose.yml for DropSrbija P1 Petter Graff

Revenue Model Reality

  • Consumer P2P: Must be FREE (NBS regulation mandate)
  • Merchant QR: 0.5–1.2% per transaction (competitive vs. card at 1.5–1.8%)
  • B2B payroll: Flat fee per batch
  • NBS IPS cap: 300,000 RSD (~€2,550) per transaction hard limit
  • Bank partner timeline: 12–18 months to live IPS integration

CEO-Level Decisions Required

  1. Incorporate Drop Srbija d.o.o. in Serbia — min capital EUR 125,000
  2. Bank partner outreach — Priority: Raiffeisen Serbia, MTS Banka (Telekom Srbija subsidiary — phone data synergy)
  3. NBS PI license application — 12–14 months, requires Serbian legal counsel

Objective

Build Drop Srbija from scaffold to production-ready MVP: fix all P0 security/legal blockers, implement bank partner adapter architecture, build KYC/AML compliance layer, write 69+ test cases, set up CI/CD — in correct regulatory sequence (KYC → IPS, not IPS → KYC).


Team Orchestration

Team Members

ID Name Role Company Agent Type
B1 petter-graff Backend architecture + security fixes CodeCraft backend-builder
B2 finverge Payments compliance + AML architecture Finverge finverge
B3 lexicon Legal docs + ZZPL compliance Lexicon lexicon
B4 proveo QA — all test suites Proveo proveo
B5 flowforge DevOps — CI/CD, Docker FlowForge devops-dev
B6 vizu Frontend — disclosure screens, complaints UI Vizu vizu
V1 angie-jones Test validation — all builds Proveo angie-jones
V2 sentinel-validator Cross-reference final report SENTINEL sentinel-validator

Step-by-Step Tasks

Phase 0: CEO Decisions (Escalated — Not Delegated to Builders)

Task 0a: Incorporate Drop Srbija d.o.o. + initiate NBS PI license

  • Owner: Alem Basic (CEO)
  • Estimated cost: EUR 125,000 minimum capital + Serbian legal counsel fees
  • Lexicon can draft the application package; Serbian advocate must sign/submit
  • Timeline: 12–18 months to authorization

Task 0b: Bank partner outreach

  • Priority 1: Raiffeisen Bank Srbija (developer portal, fintech-friendly)
  • Priority 2: MTS Banka (Telekom Srbija — phone-to-IBAN synergy)
  • Documents prepared by: sentinel-ba (Task 10 below)

Phase 1: Security P0 Fixes (CodeCraft)

Task 1: Fix OTP Security (CRITICAL BLOCKER)

  • Owner: B1 (petter-graff)
  • BlockedBy: none
  • Acceptance:
    • PhoneOtpService.hashOtp() uses bcrypt (BCrypt.hashpw, cost factor 12)
    • PhoneOtpService.verifyOtpHash() uses BCrypt.checkpw
    • Stored OTP in DB is bcrypt hash, not "hash($otp)" string
    • Existing unit tests pass (or are updated to match)
    • No plaintext OTP ever written to logs

Task 2: Fix Phone Regex + Add Serbian Format Normalisation

  • Owner: B1 (petter-graff)
  • BlockedBy: none
  • Acceptance:
    • Regex updated: ^\+381[0-9]{8,9}$ (8–9 digits after country code)
    • Normalisation: 0641234567+381641234567 at route layer
    • Landline prefix (+38111, +38121 etc.) rejected for OTP
    • Test fixtures documented in code

Task 3: Per-Phone OTP Rate Limiting

  • Owner: B1 (petter-graff)
  • BlockedBy: none
  • Acceptance:
    • 3 OTP requests per phone per minute → 4th returns 429
    • 10 OTP requests per phone per hour → returns 429
    • Redis used as rate limit store (already in docker-compose)
    • Rate limit headers in response (X-RateLimit-Remaining)

Task 4: SMS Provider Integration (SmsGateway abstraction + Twilio)

  • Owner: B1 (petter-graff)
  • BlockedBy: Task 1
  • Acceptance:
    • SmsGateway interface with sendOtp(phone: String, otp: String): SmsResult
    • TwilioSmsGateway implementation using Twilio REST API
    • StubSmsGateway for dev/test (prints OTP to logs)
    • PhoneOtpService injects SmsGateway (DI via Koin/manual)
    • Twilio credentials from environment variables (not hardcoded)
    • ENV: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER

Task 5: Validate Task 1–4 (Security Fixes)

  • Owner: V1 (angie-jones)
  • BlockedBy: Tasks 1, 2, 3, 4
  • Acceptance:
    • OTP in phone_verifications table is bcrypt hash (verified via SELECT)
    • Attempting brute force OTP (6 wrong guesses) returns correct HTTP 400
    • 4th OTP request in 1 minute returns HTTP 429
    • Phone +381123456 (too short) rejected at route layer

Phase 2: Architecture (CodeCraft)

Task 6: NBS IPS Bank Partner Adapter Pattern

  • Owner: B1 (petter-graff)
  • BlockedBy: Task 1
  • Acceptance:
    • BankPartnerAdapter interface with initiateTransfer(), checkStatus(), resolvePhoneToAccount()
    • StubBankPartnerAdapter — realistic mock with ISO 20022 status codes (ACCP, ACSC, RJCT, PDNG)
    • RaiffeisenBankAdapter — skeleton with mTLS config hooks
    • Amount validation: amount > 300_000 RSD → HTTP 422 AMOUNT_EXCEEDS_IPS_LIMIT
    • NbsIpsLogs.request_body stores ISO 20022 XML (not JSON stub)
    • partner_bank column added via Flyway V2 migration

Task 7: Phone-to-IBAN Resolution Layer

  • Owner: B1 (petter-graff)
  • BlockedBy: Task 6
  • Acceptance:
    • linked_accounts table via Flyway V3 (id, user_id, iban, bank_name, is_primary, verified_at)
    • IBAN validation: Serbian RS + 20 digits with checksum
    • AccountLinkingService.resolvePhoneToIban(phone) returns primary IBAN or null
    • GET /v1/accounts, POST /v1/accounts/link, PATCH /v1/accounts/{id}/set-primary
    • POST /v1/ips/initiate returns 404 RECIPIENT_NOT_REGISTERED if phone not linked
    • Onboarding flow requires IBAN link before first payment

Task 8: Transaction Idempotency

  • Owner: B1 (petter-graff)
  • BlockedBy: Task 7
  • Acceptance:
    • Idempotency-Key header on POST /v1/ips/initiate
    • Duplicate request with same key returns original response (not double charge)
    • Idempotency key stored in Transactions table
    • 60-minute idempotency window

Task 9: Validate Task 6–8 (Architecture)

  • Owner: V1 (angie-jones)
  • BlockedBy: Tasks 6, 7, 8
  • Acceptance:
    • POST /v1/ips/initiate with amount 300,001 → HTTP 422
    • POST /v1/ips/initiate to unlinked phone → HTTP 404
    • Duplicate initiate with same Idempotency-Key → single transaction in DB

Phase 3: Compliance Architecture (Finverge + CodeCraft)

Task 10: KYC Service (Veriff/Sumsub + JMBG)

  • Owner: B2 (finverge)
  • BlockedBy: Task 6
  • Acceptance:
    • KycService.kt with createKycSession(), handleKycWebhook(), updateKycStatus()
    • kyc_sessions table via Flyway V5
    • JMBG field added to Users: jmbg_encrypted, jmbg_hash (Flyway V6)
    • KycRequiredPlugin gates /v1/ips/initiate → 403 if kyc_status ≠ VERIFIED
    • Human-in-the-loop review step before VERIFIED status
    • Phase sequence enforced: KYC BEFORE live IPS

Task 11: AML Monitoring + USPNFT Reporting

  • Owner: B2 (finverge)
  • BlockedBy: Task 10
  • Acceptance:
    • Velocity rules: flag user exceeding 120,000 RSD in 24h
    • Structuring detection: multiple sub-threshold transactions in pattern
    • STR workflow: alert → compliance review → USPNFT eUprava export (XML)
    • Sanctions screening: UN consolidated + EU restrictive + Serbian Government lists
    • All references to "NBS SDN list" removed/corrected in codebase + docs
    • aml_flags table with risk_level, flag_reason, reviewed_by, resolved_at

Task 12: Pre-Transaction Disclosure + Post-Settlement Receipt

  • Owner: B1 (petter-graff) backend + B6 (vizu) frontend
  • BlockedBy: Task 11
  • Acceptance:
    • Confirmation screen before POST /v1/ips/initiate: amount, fee, execution time, exchange rate
    • disclosure_acknowledged: true flag in initiation payload + stored in Transactions
    • NBS IPS settlement webhook handler → push notification/in-app receipt
    • Receipt contains: tx reference, amount, fee, value date, recipient ID
    • Flyway migration adds disclosure_acknowledged to Transactions table

Task 13: Complaints Handling Module

  • Owner: B1 (petter-graff) backend + B6 (vizu) frontend
  • BlockedBy: Task 12
  • Acceptance:
    • complaints table (id, user_id, transaction_id, category, status, submitted_at, resolved_at)
    • POST /v1/complaints (authenticated user)
    • GET /admin/complaints (compliance officer)
    • SLA alert: flag if complaint > 10 working days unresolved
    • Resolution letter template in Serbian with NBS escalation notice
  • Owner: B3 (lexicon)
  • BlockedBy: none (parallel)
  • Acceptance:
    • Privacy policy in Serbian (ZZPL Article 23 compliant)
    • DPIA for KYC biometric processing (ZZPL Article 54)
    • NBS PI license application package (business plan, org chart, AML programme)
    • Framework contract for payment service users (Serbian, Law on Payment Services Articles 60-70)
    • NBS PISP license requirements checklist
    • Bank partnership pitch document
    • Incident notification procedure (NBS 4h initial, NBS 72h detailed, Poverenik 72h)
    • All saved to: ~/ALAI/products/DropSrbija/comms/decisions/

Task 15: Validate Task 10–13 (Compliance)

  • Owner: V1 (angie-jones)
  • BlockedBy: Tasks 10, 11, 12, 13
  • Acceptance:
    • User with kyc_status = PENDING cannot initiate payment → 403
    • Payment attempt with sanctioned phone → 403 + audit log entry
    • Complaint submission → DB record + SLA timer started
    • Disclosure screen appears before payment confirmation

Phase 4: DevOps (FlowForge)

Task 16: Dockerfile + Docker Compose for DropSrbija

  • Owner: B5 (flowforge)
  • BlockedBy: Task 4 (SMS env vars needed)
  • Acceptance:
    • Dockerfile.drop-srbija-api with non-root user (uid 1001)
    • Multi-stage build (builder + runtime)
    • docker-compose.yml with all 4 services: postgres:5434, redis:6380, api:3003, frontend:3000
    • docker-compose.production.yml with SEED_DEMO=false
    • Health checks on all containers
    • .env.example with all required variables documented

Task 17: CI/CD Pipeline

  • Owner: B5 (flowforge)
  • BlockedBy: Task 16
  • Acceptance:
    • .github/workflows/test.yml — run Kotest on every PR
    • .github/workflows/build.yml — docker build on push to develop
    • .github/workflows/deploy-staging.yml — deploy to staging on merge to develop
    • Quality gate: fails if test coverage < 60%
    • Sonar integration (reuse pattern from Drop Norway)

Task 18: Validate Task 16–17 (DevOps)

  • Owner: B5 (flowforge) + V1 (angie-jones)
  • BlockedBy: Tasks 16, 17
  • Acceptance:
    • docker-compose up starts all 4 containers healthy
    • GET http://localhost:3003/health returns 200
    • CI pipeline runs on test PR without errors

Phase 5: Test Suites (Proveo + CodeCraft)

Task 19: Kotest + Testcontainers + WireMock Infrastructure

  • Owner: B1 (petter-graff)
  • BlockedBy: Task 16
  • Acceptance:
    • build.gradle.kts test dependencies: kotest-runner-junit5, kotest-assertions-core, testcontainers-postgresql, wiremock-jre8, ktor-server-test-host
    • AbstractIntegrationTest base class: starts PG16 container, runs Flyway, truncates tables between tests
    • FakeSmsGateway implementation
    • docker-compose.test.yml (PG only)
    • Makefile target make test

Task 20: PhoneOtpService Tests (10 cases)

  • Owner: B4 (proveo)
  • BlockedBy: Task 19
  • Acceptance:
    • PhoneOtpServiceTest.kt — 10 test cases per Angie Jones spec
    • OTP bcrypt storage verified
    • Expiry, attempts lock, re-request all tested
    • All 10 pass

Task 21: OTP Rate Limiting Tests (5 cases)

  • Owner: B4 (proveo)
  • BlockedBy: Tasks 3, 19
  • Acceptance:
    • 5 rate limiting scenarios tested
    • Per-phone independence verified
    • All 5 pass

Task 22: NBS IPS WireMock Tests (9 cases)

  • Owner: B4 (proveo)
  • BlockedBy: Tasks 6, 19
  • Acceptance:
    • WireMock stubs for ACCP, RJCT, 500, timeout, 429 scenarios
    • NbsIpsLogs verified after each scenario
    • All 9 pass

Task 23: Amount Validation + AML Threshold Tests (19 cases)

  • Owner: B4 (proveo)
  • BlockedBy: Tasks 8, 11, 19
  • Acceptance:
    • Amount edge cases: 0, -1, MAX_INT, decimal, >300k RSD
    • AML threshold: >120k RSD flags correctly
    • Sanctioned phone → 403
    • All 19 pass

Task 24: JWT Security Tests (10 cases)

  • Owner: B4 (proveo)
  • BlockedBy: Task 19
  • Acceptance:
    • Wrong issuer, expired, tampered, wrong audience all → 401
    • 401 responses don't leak internal info
    • All 10 pass

Task 25: Playwright E2E Tests (6 journeys, Serbian locale)

  • Owner: B4 (proveo)
  • BlockedBy: Task 16 (needs running stack)
  • Acceptance:
    • playwright.config.ts with sr-RS locale, Belgrade timezone, iPhone 14 viewport
    • 6 user journeys: happy login, invalid phone, wrong OTP, network error, session persist, logout
    • All 6 pass against running docker-compose stack

Task 26: Validate All Test Suites

  • Owner: V1 (angie-jones)
  • BlockedBy: Tasks 20, 21, 22, 23, 24, 25
  • Acceptance:
    • ./gradlew test — all test suites pass
    • npx playwright test — all E2E journeys pass
    • Total test count ≥ 69
    • No test suite has 0 tests
    • Coverage report shows ≥ 60% on modules with tests

Phase 6: Business Development (Skybound/BA)

Task 27: Bank Partnership Outreach Package

  • Owner: B2 (finverge) + Skybound BA
  • BlockedBy: none (parallel)
  • Acceptance:
    • serbian-banks-api-landscape.md — Raiffeisen, MTS Banka, ProCredit, NLB with API capabilities
    • serbian-bank-partnership-pitch.md — one-page pitch deck content
    • nbs-pisp-license-requirements.md — full checklist, capital requirements, timeline
    • Recommendation: start as bank agent → own license Year 2
    • Saved to ~/ALAI/products/DropSrbija/comms/decisions/

Phase 7: Validation (End-to-End)

Task 28: Full E2E Scaffold + Feature Validation

  • Owner: V1 (angie-jones) + V2 (sentinel-validator)
  • BlockedBy: All Phase 1–6 tasks
  • Acceptance:
    • docker-compose up — all 4 containers healthy
    • OTP flow end-to-end (request → verify → JWT)
    • IBAN link → IPS initiate → stub PENDING response
    • KYC gate enforced (unverified user blocked)
    • AML flag triggered on large transaction
    • All DB tables exist (8 tables including new ones)
    • Frontend compiles (next build)
    • Evidence: screenshots, curl outputs, DB query results

Phase 8: Documentation (Skillforge)

Task 29: BookStack Documentation

  • Owner: Skillforge
  • BlockedBy: Task 28
  • Acceptance:
    • BookStack page: Drop Srbija architecture overview
    • Regulatory compliance notes (NBS, ZPNFTM, ZZPL)
    • Developer onboarding guide
    • Runbook: what to do when NBS IPS goes down
    • Decision log: bank adapter pattern rationale

Validation Commands

# Backend tests
cd ~/ALAI/products/DropSrbija/backend
./gradlew test --info

# E2E
cd ~/ALAI/products/DropSrbija/frontend
npx playwright test --reporter=html

# Docker stack
cd ~/ALAI/products/DropSrbija
docker-compose up -d
curl http://localhost:3003/health

# DB check
docker exec -it dropsrbija-postgres psql -U dropsrbija -d dropsrbija_dev -c '\dt'

Priority Matrix

Priority Tasks Rationale
P0 — Build Now 1, 2, 3, 4, 6, 7, 14, 16, 19 Security, architecture, legal, DevOps foundations
P1 — Build Next 5, 8, 10, 11, 17, 20–25, 27 Compliance, CI, test suites
P2 — Build After 12, 13, 15, 18, 26, 28, 29 UX, validation, docs
CEO Decision 0a, 0b Capital commitment, bank outreach

Last Updated: 2026-04-16 Experts consulted: Markos Zachariadis (Finverge), Thaer Sabri (Lexicon), Angie Jones (Proveo), Petter Graff (CodeCraft), Sentinel BA (Skybound)