Skip to content

Bank Adapter Development Checklist ​

Plane task: MHPAY-27

Practical runbook for adding a new Indonesian bank statement adapter to MyHoreca Pay.

Related documents:

1. Purpose ​

Use this checklist whenever you add support for a new bank or a new export format from an existing bank.

Goals:

  • every adapter implements IBankAdapter explicitly;
  • detection is deterministic and scored;
  • parsing produces explainable warnings/errors for operators;
  • raw source data is preserved for audit and support;
  • tests use sanitized fixtures only;
  • real bank exports never enter the repository;
  • supported banks metadata stays in sync with registered adapters.

_TemplateBankAdapter.example.cs is the learning template. Production adapters must be stricter and more explicit than a catch-all synthetic format.

2. Minimal workflow ​

StepAction
1Obtain a real sample export from the operator or bank portal. Store it locally only — never commit.
2Create a sanitized copy: replace names, account numbers, references, balances, and descriptions with fictional values. Keep structure realistic.
3Identify the format family: CSV, fixed-width text, MT940-like tags, multi-section corporate summary, etc.
4Copy _TemplateBankAdapter.example.cs → {Bank}{Format}Adapter.cs and rename the class.
5Fill BankAdapterDescriptor (DescriptorValue).
6Implement ScoreDetection with explicit, additive signals.
7Implement ParseAsync: dates, amounts, direction, references, raw fields, warnings/errors.
8Register in BankAdapterRegistry and DependencyInjection.cs.
9Add sanitized fixtures under backend/tests/MyHoreca.Pay.UnitTests/Fixtures/BankStatements/.
10Add mandatory unit tests in BankStatementAdapterTests.cs (or a dedicated test class).
11Update docs and verify supported banks API exposes the new descriptor.
12Run verification commands (see section 14).
13Update docs/docs/ai-agents/current-implementation-state.md on handoff.

3. Adapter contract ​

Every production adapter must implement IBankAdapter (usually via BankAdapterBase).

MemberResponsibility
DescriptorStable metadata for API, UI, and support.
BankCode / FormatCodeShort codes from descriptor; used in normalized transactions.
ScoreDetectionReturns 0.0–1.0 confidence based on file name + header/preview bytes.
CanParseInherited from BankAdapterBase: ScoreDetection >= 0.70. Override only with a documented reason.
ParseAsyncReads stream, returns BankStatementAdapterParseResult.

BankStatementAdapterParseResult ​

Must populate at minimum:

  • Transactions — list of NormalizedBankTransaction;
  • BankCode, FormatCode, AdapterName;
  • ParseConfidence via BankQualityScorer.ComputeParseConfidence;
  • TotalRows, ParsedTransactions, SkippedRows;
  • Warnings and Errors as BankStatementQualityIssue lists;
  • optional OpeningBalance, ClosingBalance, BalanceMatches, Metadata.

Shared helpers (reuse before writing custom logic):

HelperUse for
BankAmountParserAmount parsing, Indonesian decimal conventions
BankDateParserExplicit date format list
BankCounterpartyExtractorHeuristic counterparty from description. For BILL PAYMENT … strips TOTAL amount and trailing invoice/document tokens (0030, INV-…, SO-…) so names like DRINK SUPPLIER PT are not reduced to PT 0030 0031 TOTAL 1.
BankCsvLineReaderQuoted CSV splitting
BankBalanceValidatorOpening/closing balance check
BankQualityScorerParse confidence and quality status

Reference implementations:

  • CorporateExplicitDebitCreditCsvAdapter — corporate CSV with debit/credit columns (CIMB).
  • MultiAccountCrDbCsvAdapter — multi-account CR/DB CSV (BCA).
  • PermataCsvAdapter, PermataTextAdapter, PermataMt940LikeAdapter — format variants.
  • MandiriAccountStatementCsvAdapter — Mandiri semicolon CSV.
  • _TemplateBankAdapter.example.cs — copy-paste starting point for new adapters.

4. BankAdapterDescriptor checklist ​

Fill every field deliberately:

FieldGuidance
BankCodeStable uppercase code, e.g. PERMATA, CIMB, BCA.
BankDisplayNameOperator-facing bank name.
FormatCodeUnique per format variant, e.g. permata-csv-v1, cimb-account-statement-summary-csv-v1.
FormatDisplayNameHuman-readable format label for UI.
AdapterNameC# class name (nameof(YourAdapter)).
AdapterVersionSemver for detection/descriptor/capability changes; bump when ScoreDetection or descriptor changes.
ParserVersionSemver for parser logic; required on any parse change.
NormalizerVersionSemver for counterparty extraction / normalization behavior.
SupportedExtensionse.g. [".csv"], [".txt"], [".sta"].
StatusExperimental, Beta, ProductionReady, or Disabled (BankAdapterStatuses).
ConfidenceLevelOperator hint about adapter maturity; usually matches Status initially.
ExampleFileNamesTypical export file names (sanitized examples only).
CapabilitiesTags: csv, debit_credit_columns, mt940, balance_validation, etc.
NotesLimitations, encoding assumptions, known gaps.

All three version fields are mandatory — the compiler enforces them on BankAdapterDescriptor. After any bump, add an entry to bank-adapter-changelog.md.

5. Detection scoring ​

Build score from additive signals. Return 0 immediately when a hard requirement fails (e.g. wrong extension).

Signals to use:

  • file extension match;
  • header row text or column names;
  • mandatory columns present in preview;
  • bank-specific markers (company block, account line, statement period);
  • MT940 tags (:20:, :25:, :61:, :86:) where applicable;
  • file name patterns when stable (low weight only).

Confidence thresholds ​

ScoreMeaningAction
>= 0.90Strong matchSafe for automatic selection when no more specific adapter scores higher.
0.70 – 0.89Usable automatic matchCanParse returns true; monitor ambiguity in tests.
0.40 – 0.69Weak matchManual review; must not auto-select (CanParse false).
< 0.40No matchMust not auto-select.

Rules:

  • prefer the more specific adapter over a generic one when scores are close;
  • return 0 for formats owned by another adapter (e.g. demo header → corporate adapter returns 0);
  • use ClampScore from BankAdapterBase to cap at 1.0;
  • document scoring weights in adapter Notes or tests.

6. Date parsing ​

  • List accepted formats explicitly in adapter code or comments.
  • Use BankDateParser.TryParseDate with supported formats; extend BankDateParser only when a format is shared across adapters.
  • Do not guess ambiguous dd/MM vs MM/dd without bank evidence.
  • Indonesian exports may use dd/MM/yyyy, dd-MM-yyyy, dd MMM yyyy, localized month names, or year-less dates with period context.
  • Invalid date → row-level error with BankQualityWarningCodes.InvalidDate or BankImportErrorCodes.InvalidDate, including LineNumber and source value in Message.

7. Amount parsing ​

  • Normalized Amount is always positive.
  • Use BankAmountParser.TryParseAmount or TryParseIndonesianDecimal for thousand separators (. thousands, , decimal in ID locale).
  • Strip Rp prefix when present.
  • If currency is assumed (not in source), add a warning — do not silently change currency without documenting in Notes.
  • Zero or empty amount rows: skip or error based on bank rules; document the choice.
  • Invalid amount → row-level error with BankQualityWarningCodes.InvalidAmount, LineNumber, and raw value.

8. Debit/credit direction ​

Canonical rules (all adapters):

DirectionMeaning
CreditMoney enters the bank account (incoming).
DebitMoney leaves the bank account (outgoing).

Implementation rules:

  • source negative amounts → positive Amount + correct Direction;
  • separate debit/credit columns → populate one direction only; both populated → BankQualityWarningCodes.AmbiguousDebitCredit and skip/error the row;
  • single signed amount column → map sign to direction, store positive amount;
  • explicit DR/CR text columns → parse to TransactionDirection;
  • ambiguous direction → warning or error using AmbiguousDebitCredit; never invent direction silently.

9. Raw fields preservation ​

Every NormalizedBankTransaction must include RawFields for audit and support.

Recommended keys:

KeyContent
LineNumber1-based source line or row index
RawLineFull source line when practical
{ColumnName}Original column header → cell value
ParserAssumptione.g. assumed year, inferred currency
MT940 tagse.g. :61:, :86: segment text

Raw fields support duplicate investigation, operator explanations, and future adapter fixes without re-importing production files.

10. Warnings and errors ​

Use existing codes from BankQualityWarningCodes, BankImportErrorCodes, and severities from BankQualityIssueSeverities.

Populate issues with operator-facing detail:

csharp
new BankStatementQualityIssue
{
    Code = BankQualityWarningCodes.InvalidDate,
    Severity = BankQualityIssueSeverities.Blocking, // or Warning
    Message = $"Строка {lineNumber}: некорректная дата '{rawDate}'.",
    OperatorTitle = "Некорректная дата",
    WhatToCheck = "Формат даты в исходном файле",
    NextAction = "Проверьте экспорт банка или обратитесь в поддержку",
    LineNumber = lineNumber,
    ColumnName = "Post Date",
    FieldName = "TransactionDate"
}

Common codes:

SituationCode
Missing counterpartycounterparty_not_extracted
Invalid dateinvalid_date
Invalid amountinvalid_amount
Both debit and creditambiguous_debit_credit
Missing columnsmissing_required_columns / rows_skipped
Low detectionlow_detection_confidence
Low parse confidencelow_parse_confidence

Prefer row-level errors over throwing for recoverable bad rows; reserve throw for unrecoverable structure (missing header, wrong file type after detection).

11. Sanitized fixtures policy ​

Never commit real bank exports.

RuleDetail
Storagebackend/tests/MyHoreca.Pay.UnitTests/Fixtures/BankStatements/
NamingUse .sanitized.csv, .sanitized.txt, or clearly synthetic names (sample-bank-statement.csv).
ReplaceReal names, account numbers, bank refs, customer refs, descriptions, balances.
KeepColumn order, headers, quoting, encoding quirks, row structure.
GitVerify .gitignore does not accidentally exclude fixtures; real files must stay untracked.
TestsOptionally assert fixtures contain no known production markers (account numbers, company legal names from samples).

Workflow: real sample → local sanitization script or manual edit → commit sanitized fixture only.

12. Mandatory unit tests ​

Add tests in BankStatementAdapterTests.cs (or {Bank}AdapterTests.cs). Fixture root: Fixtures/BankStatements.

TestAssert
Positive detectionScoreDetection >= 0.70 on sanitized fixture
Negative detectionScore < 0.70 on unrelated format (e.g. demo CSV, wrong bank)
Successful parsingExpected transaction count, amounts, directions
Invalid dateError issue with invalid_date, row skipped or not parsed
Invalid amountError issue with invalid_amount
Ambiguous debit/creditWarning/error ambiguous_debit_credit
Missing mandatory columnsHeader/structure failure or rows_skipped
Optional counterparty/reference warningcounterparty_not_extracted when expected
Raw fields preservationLineNumber, RawLine, column keys present
Sanitized fixture parseEnd-to-end parse of committed fixture
Parse confidence / qualityParseConfidence and quality status thresholds
Registry inclusionDescriptor appears in BankAdapterRegistry.AllDescriptors
Version metadataBankAdapterRegistry_AllDescriptors_ShouldExposeRequiredVersionMetadata — all version fields non-empty
Parse metadataBankStatementParser adds AdapterVersion, ParserVersion, NormalizerVersion to Metadata

Use DeterministicUuidV7TestGenerator in tests. Import IDs in tests may use Guid.NewGuid() for test isolation; adapter code must use IUuidGenerator.NewUuidV7() for transaction IDs.

13. Supported banks update ​

After implementing a production adapter:

  1. Add YourAdapter.DescriptorValue to BankAdapterRegistry.AllDescriptors.
  2. Register IBankAdapter implementation in DependencyInjection.cs.
  3. Confirm IBankStatementSupportedBanksService returns the new descriptor via API GET /api/bank-statements/supported-banks.
  4. Update docs/docs/backend/api.md if response shape or documented banks change.
  5. Frontend supported-banks UI picks up API data automatically; update UI copy only if operator messaging is required.

Do not register template or example adapters.

14. Verification commands ​

Run from repository root unless noted.

bash
# Guard: no real bank files in repo (run before handoff)
python tools/security/guard-bank-files.py --self-test
python tools/security/guard-bank-files.py .

# Narrow: bank statement unit tests
dotnet test backend/tests/MyHoreca.Pay.UnitTests/MyHoreca.Pay.UnitTests.csproj --filter BankStatement

# After csproj / adapter compile changes
dotnet build

# Docs site (from docs/)
cd docs && npm install && npm run build

Prefer narrow tests first, then broader dotnet test when time allows.

15. Final handoff requirements ​

Before marking the Plane task done:

  • [ ] Checklist items above completed for the new adapter.
  • [ ] docs/docs/ai-agents/current-implementation-state.md updated with changed files, commands, results, gaps.
  • [ ] codegraph/manifest.yaml updated if new adapter or architecture doc added.
  • [ ] No real bank files in the commit (python tools/security/guard-bank-files.py .).
  • [ ] Template file remains excluded from compile and registry.

Acceptance criteria (MHPAY-27) ​

This document and _TemplateBankAdapter.example.cs satisfy the developer-experience baseline for future bank adapter work. Each new bank still requires its own adapter implementation, fixtures, and tests following this checklist.