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
- Incorporate Drop Srbija d.o.o. in Serbia — min capital EUR 125,000
- Bank partner outreach — Priority: Raiffeisen Serbia, MTS Banka (Telekom Srbija subsidiary — phone data synergy)
- 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→+381641234567at route layer - Landline prefix (+38111, +38121 etc.) rejected for OTP
- Test fixtures documented in code
- Regex updated:
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:
-
SmsGatewayinterface withsendOtp(phone: String, otp: String): SmsResult -
TwilioSmsGatewayimplementation using Twilio REST API -
StubSmsGatewayfor dev/test (prints OTP to logs) -
PhoneOtpServiceinjectsSmsGateway(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_verificationstable 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
- OTP in
Phase 2: Architecture (CodeCraft)
Task 6: NBS IPS Bank Partner Adapter Pattern
- Owner: B1 (petter-graff)
- BlockedBy: Task 1
- Acceptance:
-
BankPartnerAdapterinterface withinitiateTransfer(),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 422AMOUNT_EXCEEDS_IPS_LIMIT -
NbsIpsLogs.request_bodystores ISO 20022 XML (not JSON stub) -
partner_bankcolumn added via Flyway V2 migration
-
Task 7: Phone-to-IBAN Resolution Layer
- Owner: B1 (petter-graff)
- BlockedBy: Task 6
- Acceptance:
-
linked_accountstable 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/initiatereturns 404RECIPIENT_NOT_REGISTEREDif phone not linked - Onboarding flow requires IBAN link before first payment
-
Task 8: Transaction Idempotency
- Owner: B1 (petter-graff)
- BlockedBy: Task 7
- Acceptance:
-
Idempotency-Keyheader onPOST /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.ktwithcreateKycSession(),handleKycWebhook(),updateKycStatus() -
kyc_sessionstable via Flyway V5 - JMBG field added to Users:
jmbg_encrypted,jmbg_hash(Flyway V6) -
KycRequiredPlugingates/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_flagstable 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: trueflag 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_acknowledgedto Transactions table
Task 13: Complaints Handling Module
- Owner: B1 (petter-graff) backend + B6 (vizu) frontend
- BlockedBy: Task 12
- Acceptance:
-
complaintstable (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
-
Task 14: Legal Documents Package (Lexicon)
- 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-apiwith non-root user (uid 1001) - Multi-stage build (builder + runtime)
-
docker-compose.ymlwith all 4 services: postgres:5434, redis:6380, api:3003, frontend:3000 -
docker-compose.production.ymlwith SEED_DEMO=false - Health checks on all containers
-
.env.examplewith 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 upstarts all 4 containers healthy -
GET http://localhost:3003/healthreturns 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.ktstest dependencies: kotest-runner-junit5, kotest-assertions-core, testcontainers-postgresql, wiremock-jre8, ktor-server-test-host -
AbstractIntegrationTestbase class: starts PG16 container, runs Flyway, truncates tables between tests -
FakeSmsGatewayimplementation -
docker-compose.test.yml(PG only) -
Makefiletargetmake 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.tswith 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)