8 use List::Util qw(first sum);
9 use List::MoreUtils qw(any);
10 use POSIX qw(strftime);
14 use SL::SEPA::XML::Transaction;
31 $self->{transactions} = [];
32 $self->{src_charset} = 'UTF-8';
35 map { $self->{$_} = $params{$_} if (exists $params{$_}) } qw(src_charset company message_id grouped);
37 $self->{iconv} = SL::Iconv->new($self->{src_charset}, "UTF-8") || croak "Unsupported source charset $self->{src_charset}.";
39 my $missing_parameter = first { !$self->{$_} } qw(company message_id);
40 croak "Missing parameter: $missing_parameter" if ($missing_parameter);
42 map { $self->{$_} = $self->_replace_special_chars($self->{iconv}->convert($self->{$_})) } qw(company message_id);
48 foreach my $transaction (@_) {
49 croak "Expecting hash reference." if (ref $transaction ne 'HASH');
50 push @{ $self->{transactions} }, SL::SEPA::XML::Transaction->new(%{ $transaction }, 'sepa' => $self);
56 sub _replace_special_chars {
71 map { $text =~ s/$_/$special_chars{$_}/g; } keys %special_chars;
80 return sprintf '%d.%02d', int($amount), int($amount * 100) % 100;
83 sub _group_transactions {
91 foreach my $transaction (@{ $self->{transactions} }) {
92 my $key = $self->{grouped} ? join("\t", map { $transaction->get($_) } qw(src_bic src_iban execution_date)) : 'all';
93 $grouped->{groups}->{$key} ||= {
95 'transactions' => [ ],
98 push @{ $grouped->{groups}->{$key}->{transactions} }, $transaction;
100 $grouped->{groups}->{$key}->{sum_amount} += $transaction->{amount};
101 $grouped->{sum_amount} += $transaction->{amount};
110 croak "No transactions added yet." if (!@{ $self->{transactions} });
114 my $xml = XML::Writer->new(OUTPUT => \$output,
117 ENCODING => 'utf-8');
120 my $time_zone = strftime "%z", @now;
121 my $now_str = strftime('%Y-%m-%dT%H:%M:%S', @now) . substr($time_zone, 0, 3) . ':' . substr($time_zone, 3, 2);
123 my $grouped_transactions = $self->_group_transactions();
126 $xml->startTag('Document',
127 'xmlns' => 'urn:sepade:xsd:pain.001.001.02.grp',
128 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
129 'xsi:schemaLocation' => 'urn:sepade:xsd:pain.001.001.02.grp pain.001.001.02.grp.xsd');
131 $xml->startTag('pain.001.001.02');
133 $xml->startTag('GrpHdr');
134 $xml->dataElement('MsgId', encode('UTF-8', substr($self->{message_id}, 0, 35)));
135 $xml->dataElement('CreDtTm', $now_str);
136 $xml->dataElement('NbOfTxs', scalar @{ $self->{transactions} });
137 $xml->dataElement('CtrlSum', $self->_format_amount($grouped_transactions->{sum_amount}));
138 $xml->dataElement('Grpg', 'MIXD');
140 $xml->startTag('InitgPty');
141 $xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70)));
142 $xml->endTag('InitgPty');
144 $xml->endTag('GrpHdr');
146 foreach my $key (keys %{ $grouped_transactions->{groups} }) {
147 my $transaction_group = $grouped_transactions->{groups}->{$key};
148 my $master_transaction = $transaction_group->{transactions}->[0];
150 $xml->startTag('PmtInf');
151 $xml->dataElement('PmtMtd', 'TRF');
153 $xml->startTag('PmtTpInf');
154 $xml->startTag('SvcLvl');
155 $xml->dataElement('Cd', 'SEPA');
156 $xml->endTag('SvcLvl');
157 $xml->endTag('PmtTpInf');
159 $xml->dataElement('ReqdExctnDt', $master_transaction->get('execution_date'));
160 $xml->startTag('Dbtr');
161 $xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70)));
162 $xml->endTag('Dbtr');
164 $xml->startTag('DbtrAcct');
165 $xml->startTag('Id');
166 $xml->dataElement('IBAN', $master_transaction->get('src_iban', 34));
168 $xml->endTag('DbtrAcct');
170 $xml->startTag('DbtrAgt');
171 $xml->startTag('FinInstnId');
172 $xml->dataElement('BIC', $master_transaction->get('src_bic', 20));
173 $xml->endTag('FinInstnId');
174 $xml->endTag('DbtrAgt');
176 $xml->dataElement('ChrgBr', 'SLEV');
178 foreach my $transaction (@{ $transaction_group->{transactions} }) {
179 $xml->startTag('CdtTrfTxInf');
181 $xml->startTag('PmtId');
182 $xml->dataElement('EndToEndId', $transaction->get('end_to_end_id', 35));
183 $xml->endTag('PmtId');
185 $xml->startTag('Amt');
186 $xml->startTag('InstdAmt', 'Ccy' => 'EUR');
187 $xml->characters($self->_format_amount($transaction->{amount}));
188 $xml->endTag('InstdAmt');
191 $xml->startTag('CdtrAgt');
192 $xml->startTag('FinInstnId');
193 $xml->dataElement('BIC', $transaction->get('dst_bic', 20));
194 $xml->endTag('FinInstnId');
195 $xml->endTag('CdtrAgt');
197 $xml->startTag('Cdtr');
198 $xml->dataElement('Nm', $transaction->get('recipient', 70));
199 $xml->endTag('Cdtr');
201 $xml->startTag('CdtrAcct');
202 $xml->startTag('Id');
203 $xml->dataElement('IBAN', $transaction->get('dst_iban', 34));
205 $xml->endTag('CdtrAcct');
207 $xml->startTag('RmtInf');
208 $xml->dataElement('Ustrd', $transaction->get('reference', 140));
209 $xml->endTag('RmtInf');
211 $xml->endTag('CdtTrfTxInf');
214 $xml->endTag('PmtInf');
217 $xml->endTag('pain.001.001.02');
218 $xml->endTag('Document');