Skip to content

Expose Servicing Institution BIC on Statement / Record #182

@dereuromark

Description

@dereuromark

Summary

Record::getAccount() returns an account DTO with only the account identification (for IBAN-backed accounts, an IbanAccount containing the IBAN and optional account name). The CAMT.052, CAMT.053, and CAMT.054 schemas also carry the servicing institution (the bank running the account) via <Acct><Svcr><FinInstnId>..., but this element is not surfaced through any public API on the record / statement / report / notification DTOs.

Would you accept a PR that exposes the account servicer on the public DTOs?

Why it matters

Reconciliation-layer tools built on top of genkgo/camt often want to persist a bank_statements row alongside every imported file so operators can answer questions like "show me all statements from my Deutsche Bank account". The IBAN is enough to identify the account, but the BIC is what disambiguates it for users who operate across multiple banks. More practically, IBAN+BIC is still a common pair for downstream payment and reconciliation workflows.

Concrete downstream use case: My app has a bank_statements table with iban and bic columns. The IBAN is populated today via $record->getAccount()->getIdentification(). The bic column stays NULL because there is no equivalent getter, even when the source XML contains the servicing institution.

What the library does today

The omission appears to be current behavior, not just missing docs:

  • src/Camt052/Decoder/Message.php
  • src/Camt053/Decoder/Message.php
  • src/Camt054/Decoder/Message.php

All three currently decode <Acct><Id>... into an Account DTO, but do not read <Acct><Svcr><FinInstnId>.

The DTO surface also has no place for the servicer today:

  • Record::getAccount() returns an Account
  • IbanAccount currently stores only the IBAN and optional account name

Where the servicer lives in CAMT

Depending on message version, the financial institution identifier may be represented as either BIC or BICFI under <FinInstnId>.

Example using BIC

<Acct>
  <Id>
    <IBAN>DE89370400440532013000</IBAN>
  </Id>
  <Svcr>
    <FinInstnId>
      <BIC>COBADEFFXXX</BIC>
      <Nm>Commerzbank AG</Nm>
    </FinInstnId>
  </Svcr>
</Acct>

Example using BICFI

<Acct>
  <Id>
    <IBAN>CH2801234000123456789</IBAN>
  </Id>
  <Svcr>
    <FinInstnId>
      <BICFI>UBSWCHZH80A</BICFI>
      <Nm>UBS SWITZERLAND AG</Nm>
    </FinInstnId>
  </Svcr>
</Acct>

The repo’s own fixtures already contain this structure for supported versions, so this is not hypothetical.

Proposed API

Three options, in order of non-invasiveness:

Option A — new getters on Record

public function getAccountServicerBic(): ?string
public function getAccountServicerName(): ?string

Backward compatible. Existing getAccount() callers keep working. This is the smallest public change.

Option B — extend IbanAccount / Account with optional servicer info

public function getServicingBic(): ?string
public function getServicingName(): ?string

This is conceptually closer to the XML shape, since the servicer belongs to the account. But it widens the DTO contract more than Option A.

Option C — add a dedicated account-servicer DTO

Something like:

final class FinancialInstitution
{
    public function getBic(): ?string
    public function getName(): ?string
}

and then either Record::getAccountServicer(): ?FinancialInstitution or Account::getServicer(): ?FinancialInstitution.

Probably the cleanest model long-term, but likely too large a delta for the immediate need.

I’d propose Option A as the smallest compatible change.

Decoder path

This looks like a sibling-of-account-identification change, not a new schema branch:

  • extract <Acct><Svcr><FinInstnId><BIC> or <BICFI>
  • optionally also expose <Nm>
  • wire the result onto the chosen DTO/API for all supported CAMT.052 / .053 / .054 record decoders

Happy to send a PR for whichever API shape you prefer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions