=encoding utf8

=head1 NAME

Business::CAMT - ISO20022 Cash Management (CAMT) messages

=head1 SYNOPSIS

  my $camt = Business::CAMT->new;
  my $msg  = $camt->read($file|$xml);
  print $msg->toPerl;
  print $msg->toJSON;

  my $message = $camt->create('053.001.02', $data);
  $camt->write('out.xml', $message);
  $message->write('out.xml');   # same

=head1 DESCRIPTION

Use this module to manage B<any type of CAMT messages>, which are
ISO20022 standard "Cash Management" messages as produced in banking.
For instance, CAMT.053 is produced by banks and consumed by accountancies,
showing transactions in bank-accounts.  See L<https://www.iso20022.org>.

At the moment, this module can be used to read and write the XML message
files, perfectly validated and predictable.  Not all versions of all
schemas are included (yet) because there is no public historical archive
for them (AFAIK).

It is intended to also support abstraction in interpreting and
constructing the content.  However, B<I need a sponsor> to make that
happen.  Contact the author for support.  Please.  I would also like to
include a CAMT.053 to CSV and MT-940 converter, v.v.  Please hire me.

=head1 METHODS

=head2 Constructors

=over 4

=item $class-E<gt>B<new>(%options)

Create a new CAMT processing object.

Reuse this object to avoid recompilation of the message processor, which
is pretty expensive.

 -Option       --Default
  big_numbers    false
  long_tagnames  false
  match_schema   'NEWER'

=over 2

=item big_numbers => BOOLEAN

Set to a C<true> value when your accounts run into the billions.  This will
enable Math::BigFloat to be used, which is slower and memory hungry.

=item long_tagnames => BOOLEAN

The schemas are derived from an UML specifications which uses clear and
readible long names for relations and attributes.  But, someone with a
poor sense of optimization removed most of the vowels from these tags
while translating the UML into an XSD.  When set to C<true>, this option
will give you the nice long names in Perl.

=item match_schema => $rule

Sets the default C<$rule> how to handle messages which have namespaces
which do not match the schemas contained in the module release.

See L<matchSchema()|Business::CAMT/"Helper methods"> for the available C<$rule> values.  See the L<DETAILS>
section about the namespace versioning horrors.

=back

=back

=head2 Attributes

=over 4

=item $obj-E<gt>B<schemas>()

Returns the XML::Compile::Schema object, which collects the
compiled message XSDs.  The XSDs get automatically loaded when
messages are encountered which need them.

=back

=head2 Read and Write messages

=over 4

=item $obj-E<gt>B<create>($type, $data, %options)

Create a new message, to be written later.  The C<$data> is the content of
the message, in a structure as can be found in the example templates.

The C<$type> is either in the form C<camt.053.001.02> or C<053.001.02>.

» example: of create

  my $camt = Business::CAMT->new(...);
  my $msg  = $camt->create('053.001.02', \%data);
  $msg->write('file.xml');

=item $obj-E<gt>B<fromHASH>(\%data, %options)

Create a L<Business::CAMT::Message|Business::CAMT::Message> object of the given C<type>.  It is not
checked whether the type schema exists until an attempt is made to write the
message.

 -Option--Default
  type    <required>

=over 2

=item type => VERSION

Something like C<camt.053.001.02> or C<053.001.02>.

=back

=item $obj-E<gt>B<read>($file|$xml, %options)

Pass a C<$file> name, an C<$xml> document or an C<$xml> element.  Returned is
a HASH blessed in class 'C<Business::CAMT::CAMT[nr]>', for instance
C<Business::CAMT::CAMT053>.

 -Option      --Default
  match_schema  new(match_schema)

=over 2

=item match_schema => $rule

=back

=item $obj-E<gt>B<write>($file, $message, %options)

Write a constructed C<$message> (an extension of L<Business::CAMT::Message|Business::CAMT::Message>)
to a file in XML format.  The message can also be written as JSON or Perl
data-structure, via the message itself.

» example: writing a message

  my $message = $camt->create('053.001.02', $data);
  $camt->write('out.xml', $message);
  $message->write('out.xml');   # same

=back

=head2 Helper methods

You would rarely (or never) need to use these methods in your programs: they support
the reader and writer function.

=over 4

=item $obj-E<gt>B<fullname2tagTable>()

Translates long and understandable names into (silly) abbreviated tags.

=item $obj-E<gt>B<knownVersions>( [$set] )

Returns a sorted LIST with all schema versions which are included in this
distribution.  When the C<$set> is specified (like C<053.001>), then only
those are reported.

=item $obj-E<gt>B<matchSchema>($set, $version, %options)

Find the available schema version for the C<$set> (like '053.001') to interpret
a message with C<$version> (like '02').

 -Option--Default
  rule    new(match_schema)

=over 2

=item rule => 'EXACT'|'NEWER'|'NEWEST'|'ANY'|CODE

When C<EXACT>, only the precise version is acceptable.  C<NEWER> will
fall back to the closest newer version.  When no exact match, C<NEWEST>
returns the highest available version, but must be newer.  Most generous
is C<ANY>, which falls back to the newest available version even when
it is older than the message version.

You may also pass a CODE reference, which is called with the C<$set>, the requested
schema, and a sorted ARRAY of available versions.  It must return one of the
available versions or C<undef> (no compatible version).

=back

=item $obj-E<gt>B<schemaReader>($set, $version, $ns)

Produces a CODE which can be called with an XML message, to get it transformed
into a Perl data-structure.  In this case, the C<$set> and C<$version> have to be
known already; method L<read()|Business::CAMT/"Read and Write messages"> figures that out by itself.

=item $obj-E<gt>B<schemaWriter>($set, $version, $ns)

Z<>

=item $obj-E<gt>B<tag2fullnameTable>()

Returns a table which translates the (silly) abbreviations used in the
XML tags into readable names.  Good names make it easier to understand
the handling code and is less error-prone.

=back

=head1 DETAILS

In this chapter, you find some background information and implementation tips.

=head2 Examples

The release contains a C<examples/> directory.  In that directory, you with find
a C<show> script and some xml files.  Run the script with a file, to see what
this module has to offer.  For example:

  cd Business-CAMT-0.01/
  examples/show examples/danskeci.com/camt053_dk_example.xml /tmp/a.xml

The script (this module) auto-detects the CAMT type which is found in the XML
message.  Play with the script to see how changes affect the output.

=head2 Templates

In our GitHub repository, you find a C<templates/> directory which
contains a structural dump of each of the Perl data structure which is
produced (for L<read()|Business::CAMT/"Read and Write messages">) or consumed (for C<write>, to be implemented)
by this module.

Be sure you understand anonymous HASHes and ARRAYs in Perl well, when you
start playing.  Do not forget that code gets more readible when you use
practical reference variables.

On GitHub, you will also find a C<templates-long/> directory full of
examples.  This demonstrates what option L<new(long_tagnames)|Business::CAMT/"Constructors"> does: it
will make the Perl datastructures readible.

=head2 Implementation issues

=head3 XML namespaces

The idea behind XML namespaces to include schema versioning is
fundamentally flawed.  The CAMT files form no exception in this broken
XML concept.  Of course, schema versions are mainly backwards compatible,
so why not design your versioning with that in mind?

This module bends the namespace use to hide these design mistakes
into flexible code.  Without full knowledge about existing and future
versions of the schemas, there is a powerfull configuration setting
with L<matchSchema()|Business::CAMT/"Helper methods">.

Please consider to contribute discovered incompatibility issues to this
module, to hide them where possible.

=head3 Tag abbreviations

XML is very verbose anyway, so it really does not help to abbreviate tags
leaving some vowels out.  This makes it harder to read messages and code.
It increases the chance on stupid typos in the code.

When you set L<new(long_tagnames)|Business::CAMT/"Constructors">, then your Perl structure will use
longer, understandable names: it gets easy to understand the message
without reading the documentation.  This improves maintenance on the
long run.

This option will be applied both on L<read()|Business::CAMT/"Read and Write messages"> and L<write()|Business::CAMT/"Read and Write messages">.  Of course,
the templates will show you how it works: see the C<templates-long/>
directory in the github repository.

=head3 No common types

Each schema is separate, although their type definitions are overlapping.
It is not guaranteed that equal types will stay that way over time.
This may cause instable code.

Probably, these issues will not emerge because the schema files are
generated from a central UML model.  However: small changes in the data
structure will cause multiple schemas to change to a new version.

A better setup would be:

=over 4

=item * a schema for base types, like "Amount"

=item * a schema for more complex (reused) structures

=item * a schema per message, which composes the complex structures

=back

=head3 Missed chances on XML

The way these schema's got generated, make them very low in using more
powerful XML schema features.  Those features would have helped the
stability of the "interface" which these messages implement a lot.
Done this way, XML is not much better than JSON.  To be honest, the
schemas are littered by missed chances.

The messages are designed with an UML tool, which means: limited to the
features of that tool and hindering the view on the quality of the schema.
This leads to structures like:

  <Bal>
    <Tp>
      <CdOrPrtry>
         <Cd>OPBD</Cd>
      </CdOrPrtry>
    </Tp>
    <Amt Ccy="SEK">500000</Amt>
    <CdtDbtInd>CRDT</CdtDbtInd>
    <Dt>
      <Dt>2010-10-15</Dt>
    </Dt>
  </Bal>

In Perl, this leads to (C<long_tagnames> on)

  Balance => {
    Type => {
      CodeOrProperty => {
        Code => 'OPBD',
      }
    },
    Amount => {
      _ => '500000',
      Currency => 'SEK',
    },
    CreditDebitInd => 'CRDT',
    Date => {
      Date => '2010-10-15',
    },
  }

The XML schema, when B<designed> as XML schema, could have looked like

  <OpeningBook Date="2010-10-15CEST">
    <Credit Currency="SEK">500000.00</Credit>
  </OpeningBook>

The use of C<group>'ed elements and C<substitutionGroup>'s would have
made messages so much clearer and easier.  Even simple constructs
as C<extension> and C<restriction> of C<complexType>'s are not used.
It would have reduced the message size much further than by leaving
out the vowels from tags, as the example shows.

But more importantly: this hinders backward compatibility in the message
evaluation a lot!  Using XML features better would result in better
maintainable applications.  Much better.

=head1 DIAGNOSTICS

=over 4

=item Error: Message set '$set' is unsupported.

Cast by C<write()>

=item Error: No compatible schema version available.

Cast by C<read()>

=item Error: Not a CAMT file.

Cast by C<read()>

=item Error: Not a supported CAMT message type.

Cast by C<read()>

=item Error: Schema version $version is not available, pick from $versions.

Cast by C<write()>

=item Error: Unknown message type '$type'

Cast by C<fromHASH()>

=item Error: Unknown message type '$type'

Cast by C<create()>

=item Error: Unknown schema match rule '$rule'.

Cast by C<matchSchema()>

=item Error: Unrecognized input

Cast by C<read()>

=back

=head1 SEE ALSO

This module is part of Business-CAMT version 0.15,
built on April 16, 2026. Website: F<http://perl.overmeer.net/CPAN/>

=head1 LICENSE

For contributors see file ChangeLog.

This software is copyright (c) 2024-2026 by Mark Overmeer.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

