This library implements parts of FinTS V3.0, which is specified here. Before version 3, the standard was called HBCI. Today, HBCI still refers to the security part of the specification. The specification is split into several documents:
- Hauptdokument Unimportant index document that gives an overview of the other documents.
- Formals Wire format and basic protocol (dialog, BPD, UPD).
- Rückmeldungen Directory of response codes that the bank can send.
- Security HBCI Protocol for encryption and cryptographic signatures.
- Security PIN/TAN Protocol for PIN/TAN authentication, based on the HBCI security protocol, but without actual encryption/signatures.
- Messages Geschäftsvorfälle Various business transactions (account statements, wire transfers, etc.).
- Messages Finanzdatenformate Wire formats other than the FinTS format itself (e.g. DTAUS and MT 940) that are used for certain transactions.
Note that there is also an archive with older versions of the HBCI specification.
A message (implemented in Fhp\Protocol\Message
) is a series of segments (implemented in Fhp\Segment\...
),
which themselves consist of data elements (DEs) and data element groups (DEGs).
Each segment starts with an identifier to declare its type. Example:
HNHBK:1:3+000000000079+300+dialogID+2'HKEND:2:1+dialogID'HNHBS:3:1+2'
This message contains three segments.
The middle segment is HKEND
version 1
at position 2
. It only contains one data element with value dialogId
.
The first segment is HNHBK
. It contains four data elements 000000000079
, ..., 2
.
The syntactical representation (wire format) (see Formals) used when transmitting/storing messages and segments is
described below and implemented in Fhp\Syntax
.
The protocol specifies which messages a client can send (requests) and how the server can respond to them (responses).
There is exactly one response per request, the client needs to wait for the response before sending another request.
While FinTs is specified independently of the underlying transport layer, all banks use TLS 3.0 in practice, so it's
very similar to HTTPS and the server addresses are given as https://
URIs as well.
The most basic protocol is the dialog (see Formals, implemented in Fhp\Protocol\DialogInitialization
and FinTs
),
which combines elements of a handshake (as in TCP/TLS) and a session (i.e. authentication through cookies/tokens).
Before sending any other messages, the client must initialize a dialog.
Most dialogs require authentication, so the initialization is comparable to a login.
While all of the above is part of the FinTS infrastructure, the business transactions (the part of the protocol that is
ultimately useful for users) are specified separately in Messages Geschäftsvorfälle (implemented in Fhp\Action\...
).
In addition to these, bank can specify their own actions (would be implemented in separate dependent libraries, but
currently there are none).
Separately from the FinTS specification, this library implements a user-facing API in Fhp\Model
(in addition to
the FinTs
class and the Fhp\Action
namespace) to provide a simpler and more stable interface over time.
NOTE: The PHP namespaces DataElementGroups
, Dialog
and Response
(and their contents) are deprecated.
In this library, segments are implemented in classes named like HKTANv6
that inherit from BaseSegment
.
Each of these classes is mapped 1:1 from the corresponding chapter of the specification document.
The class name consists of the segment identifier (e.g. HKTAN
) and the version (just an integer prefixed by v
).
The segment identifier itself has two parts:
In HXyyy
, the H
is constant (for "HBCI"), yyy
is an abbreviation of the name of the respective functionality that
the segment provides, and X
is one of K
, I
or N
depending on whether it is a request segment sent by the client
("Kunde" = customer), a response segment sent by the server ("(Kredit-)Institut" = bank) or both ("Nachricht" = message).
The HIyyyS
segments suffixed by S
contain meta information (called "parameters") that describe what constitues a
valid HKyyy
request.
The class HXyyyvN
that implements version N
of a particular segment type yyy
can either be placed in the PHP
namespace Fhp\Segment\HXyyy
(to group versions together, used especially when X
=N
) or Fhp\Segment\yyy
(to group
corresponding request and reponse segments together).
Analogously to segments, data element groups (DEGs) are mapped to classes that inherit from BaseDeg
.
The class name is the title of the sub-section that specifies the DEG structure in the specification document.
The version suffix (VN
with capital V
) is optional for DEGs.
The naming does not have to be as strictly deterministic as with segments because DEGs are referenced explicitly in
code from the respective segments that contain them.
Especially when there are multiple supported versions of the same segment/DEG type (e.g. HKKAZv6
and HKKAZv7
), it
makes sense to also have a common interface HKKAZ
implemented by all versions that defines getters for the common
fields, so that business logic code can access all versions transparently.
As an example, consider the HIUPDv6
class that implements the "Kontoinformation" segment from page 88 (PDF page 96)
from the Formals document.
Within each segment/DEG class, the elements defined in the specification are translated one by one to class fields. It is important that the fields occur in the same order as in the specification and that no fields are left out, because the absolute index/position of each field in the segment class must map to its index in HBCI's wire format. Segment classes may inherit from other segment classes, in which case the fields of the parent class come first. The name of the field/element is taken directly from the specification, so it is usually in German. Special characters like Umlauts are replaced with their expanded versions and the whole name is transformed to camel case.
The element types (specified in Formals section B.4) are mapped to PHP types as follows:
jn
(yes/no) becomesbool
.num
(numerical) anddig
(single digit) becomeint
.float
andwrt
(amounts) becomefloat
.an
(alpha-numerical),txt
(text),id
(identifiers),ctr
(country codes, despite being numerical) andcur
(currency codes) becomestring
and the maximum length is documented in phpDoc.code
(enum) is resolved to the type of the underlying value type (usuallystring
orint
) and the allowed values are documented in phpDoc.bin
uses theFhp\Syntax\Bin
class and the maximum length is documented in phpDoc.dat
andtim
are currently mapped tostring
, but could get their own class inFhp\DataTypes
in future.- Any data element group is implemented as a separate DEG class (see above), so that the PHP field can reference that class name as its type.
The "Status" of each element is mapped as follows:
- Mandatory fields ("M" in the specification) use the plain type as described above.
- Optional fields ("O") append
|null
. - Conditional fields ("C") are resolved as much as possible given the context of the segment and this library in general
(often only one of the conditions is always satisfied, so it is clear whether the field is mandatory, optional or
disallowed whenever it is used in this library), and otherwise mapped to
|null
as well.
The "Anzahl" (cardinality) is mapped as follows:
- If only
1
is allowed, use the plain (possibly nullable) type as described above. - Otherwise append
[]
to the type and add a@Max(N)
annotation for the maximum number. E.g. ajn
field that can be repeated at most 20 times becomes@var int[] @Max(20)
. - If
0
is within the allowed range, add|null
, e.g.@var int[]|null @Max(20)
.
Any other information that is given in the specification like maximum length, restrictions on when the field can be used or not, and the guidelines ("Belegungsrichtlinien"), are added to the phpDoc in English (translated) if useful.
Segments are terminated (and thus also delimited) by '
.
They contain a series of DEs and DEGs, delimited by :
and +
.
Note that these delimiters aren't very consistent (see below).
The values essentially form an ordered tree, where the leaves are data elements (similar to scalar values), inner nodes
are data element groups (similar to compound values) and the roots are segments.
The entries of a segment (i.e. the second level of the tree), which can be a mix of DEs and DEGs, are delimited by +
.
The entries of any lower-level inner node are delimited by :
.
Such a tree can be arbitrarily deep, which leads to ambiguities.
For instance, the wire format of a DEG X
that contains nothing but a nested DEG Y
is indistinguishable from the wire
format of Y
alone.
With repeated and optional elements, there are even more ambiguities, but the specification requires "empty" elements to
clarify in such situations.
While the wire format's syntax can be recognized and parsed without knowing what the contents mean (similar to JSON or
XML), the FinTs wire format is not self-describing on the semantic level, so the segment definitions are needed to
understand the contents (similar to protocol buffers and other binary serialization formats).
However, each segment begins with a Segmentkopf
as its first entry, which declares the type of the segment and allows
picking the right segment definition/schema for parsing.
For debugging purposes, it can be useful to render serialized messages/segments in a more readable (self-describing)
format, specifically with the field names inlined.
The prettify_message.php
and prettify_segment.php
scripts do exactly that, given the wire format on stdin.
The PIN/TAN specification piggy-backs on the HBCI encryption/signature specification that was originally intended for
cryptographically secure communication based on private keys contained in physical tokens like HBCI chip cards.
Nowadays, the surrounding transport layer (TLS) already provides the cryptographic security based on the public-key
infrastructure of the WWW, so that proper encryption and signatures are not necessary anymore on the FinTS level.
Nevertheless, all messages need to be wrapped in the respective envelope (implemented in Fhp\Protocol\Message
).
This envelope also carries PIN and TAN whenever they need to be sent.
In addition to the envelope, the HKTAN
family of segments are used to communicate whether a TAN is needed, what it
needs to look like, what it is for when it is being sent, and whether it was accepted on the bank side.
HKTAN
version 6 is equivalent to PSD2.
In practice, with PSD2 for the first time, several German banks started asking for a TAN even upon login, which can
sometimes lead to problems depending on how they implement the protocol, given that HKTAN
was designed to authenticate
business transactions within a dialog, but is now used to initialize a dialog as well.
Very few banks provide a sandbox environment with fake accounts for testing purposes. So most developers manual tests against their real bank accounts. To minify the impact on these accounts (undesired transactions, account locks due to failed login attempts), automated integration testing (against fake backends) has proven very useful (in addition to the usual regression-catching benefits of those tests).
The CLILogger
class can be used to record requests/responses during a (manually scripted) dialog with the bank.
The recorded messages can then be filled into a PhpUnit
test.
When used through FinTs::setLogger()
, the logger already replaces the most important sensitive values
(username, PIN, ...) with placeholders.
But the recorded segments regularly still contain personal information (e.g. IBANs and names of other parties involved
in transactions, descriptions of transactions, etc.).
In order to remove this information, it is advisable not to change the overall length (i.e. using the Ins
mode in the
code editor to overwrite it with some dummy data), because the wire format hard-codes the length of subsequent data in
various places and simply removing it would result in parsing failures.
For examples, see Tests\Fhp\Integration\...
.