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 creditor_id message_id grouped collection);
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);
41 croak "Missing parameter: creditor_id" if !$self->{creditor_id} && $self->{collection};
43 map { $self->{$_} = $self->_replace_special_chars($self->{iconv}->convert($self->{$_})) } qw(company message_id creditor_id);
49 foreach my $transaction (@_) {
50 croak "Expecting hash reference." if (ref $transaction ne 'HASH');
51 push @{ $self->{transactions} }, SL::SEPA::XML::Transaction->new(%{ $transaction }, 'sepa' => $self);
57 sub _replace_special_chars {
72 map { $text =~ s/$_/$special_chars{$_}/g; } keys %special_chars;
81 return sprintf '%.02f', $amount;
84 sub _group_transactions {
92 foreach my $transaction (@{ $self->{transactions} }) {
93 my $key = $self->{grouped} ? join("\t", map { $transaction->get($_) } qw(src_bic src_iban execution_date)) : 'all';
94 $grouped->{groups}->{$key} ||= {
96 'transactions' => [ ],
99 push @{ $grouped->{groups}->{$key}->{transactions} }, $transaction;
101 $grouped->{groups}->{$key}->{sum_amount} += $transaction->{amount};
102 $grouped->{sum_amount} += $transaction->{amount};
111 croak "No transactions added yet." if (!@{ $self->{transactions} });
115 my $xml = XML::Writer->new(OUTPUT => \$output,
118 ENCODING => 'utf-8');
121 my $time_zone = strftime "%z", @now;
122 my $now_str = strftime('%Y-%m-%dT%H:%M:%S', @now) . substr($time_zone, 0, 3) . ':' . substr($time_zone, 3, 2);
124 my $is_coll = $self->{collection};
125 my $cd_src = $is_coll ? 'Cdtr' : 'Dbtr';
126 my $cd_dst = $is_coll ? 'Dbtr' : 'Cdtr';
127 my $pain_id = $is_coll ? 'pain.008.002.01' : 'pain.001.001.02';
128 my $pain_elmt = $is_coll ? 'pain.008.001.01' : 'pain.001.001.02';
129 my @pii_base = (strftime('PII%Y%m%d%H%M%S', @now), rand(1000000000));
131 my $grouped_transactions = $self->_group_transactions();
135 $xml->startTag('Document',
136 'xmlns' => "urn:swift:xsd:\$${pain_id}",
137 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
138 'xsi:schemaLocation' => "urn:swift:xsd:\$${pain_id} ${pain_id}.xsd");
140 $xml->startTag($pain_elmt);
142 $xml->startTag('GrpHdr');
143 $xml->dataElement('MsgId', encode('UTF-8', substr($self->{message_id}, 0, 35)));
144 $xml->dataElement('CreDtTm', $now_str);
145 $xml->dataElement('NbOfTxs', scalar @{ $self->{transactions} });
146 $xml->dataElement('CtrlSum', $self->_format_amount($grouped_transactions->{sum_amount}));
147 $xml->dataElement('Grpg', 'MIXD');
149 $xml->startTag('InitgPty');
150 $xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70)));
151 $xml->endTag('InitgPty');
153 $xml->endTag('GrpHdr');
155 foreach my $key (keys %{ $grouped_transactions->{groups} }) {
156 my $transaction_group = $grouped_transactions->{groups}->{$key};
157 my $master_transaction = $transaction_group->{transactions}->[0];
159 $xml->startTag('PmtInf');
161 $xml->dataElement('PmtInfId', sprintf('%s%010d', @pii_base));
164 $xml->dataElement('PmtMtd', $is_coll ? 'DD' : 'TRF');
166 $xml->startTag('PmtTpInf');
167 $xml->startTag('SvcLvl');
168 $xml->dataElement('Cd', 'SEPA');
169 $xml->endTag('SvcLvl');
172 $xml->startTag('LclInstrm');
173 $xml->dataElement('Cd', 'CORE');
174 $xml->endTag('LclInstrm');
175 $xml->dataElement('SeqTp', 'OOFF');
177 $xml->endTag('PmtTpInf');
179 $xml->dataElement($is_coll ? 'ReqdColltnDt' : 'ReqdExctnDt', $master_transaction->get('execution_date'));
180 $xml->startTag($cd_src);
181 $xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70)));
182 $xml->endTag($cd_src);
184 $xml->startTag($cd_src . 'Acct');
185 $xml->startTag('Id');
186 $xml->dataElement('IBAN', $master_transaction->get('src_iban', 34));
188 $xml->endTag($cd_src . 'Acct');
190 $xml->startTag($cd_src . 'Agt');
191 $xml->startTag('FinInstnId');
192 $xml->dataElement('BIC', $master_transaction->get('src_bic', 20));
193 $xml->endTag('FinInstnId');
194 $xml->endTag($cd_src . 'Agt');
196 $xml->dataElement('ChrgBr', 'SLEV');
198 foreach my $transaction (@{ $transaction_group->{transactions} }) {
199 $xml->startTag($is_coll ? 'DrctDbtTxInf' : 'CdtTrfTxInf');
201 $xml->startTag('PmtId');
202 $xml->dataElement('EndToEndId', $transaction->get('end_to_end_id', 35));
203 $xml->endTag('PmtId');
206 $xml->startTag('InstdAmt', 'Ccy' => 'EUR');
207 $xml->characters($self->_format_amount($transaction->{amount}));
208 $xml->endTag('InstdAmt');
210 $xml->startTag('DrctDbtTx');
212 $xml->startTag('MndtRltdInf');
213 $xml->dataElement('MndtId', $transaction->get('reference', 35));
214 $xml->dataElement('DtOfSgntr', $transaction->get('reference_date', 2010-12-02));
215 $xml->endTag('MndtRltdInf');
217 $xml->startTag('CdtrSchmeId');
218 $xml->startTag('Id');
219 $xml->startTag('PrvtId');
220 $xml->startTag('OthrId');
221 $xml->dataElement('Id', encode('UTF-8', substr($self->{creditor_id}, 0, 35)));
222 $xml->dataElement('IdTp', 'SEPA');
223 $xml->endTag('OthrId');
224 $xml->endTag('PrvtId');
226 $xml->endTag('CdtrSchmeId');
228 $xml->endTag('DrctDbtTx');
231 $xml->startTag('Amt');
232 $xml->startTag('InstdAmt', 'Ccy' => 'EUR');
233 $xml->characters($self->_format_amount($transaction->{amount}));
234 $xml->endTag('InstdAmt');
238 $xml->startTag("${cd_dst}Agt");
239 $xml->startTag('FinInstnId');
240 $xml->dataElement('BIC', $transaction->get('dst_bic', 20));
241 $xml->endTag('FinInstnId');
242 $xml->endTag("${cd_dst}Agt");
244 $xml->startTag("${cd_dst}");
245 $xml->dataElement('Nm', $transaction->get('company', 70));
246 $xml->endTag("${cd_dst}");
248 $xml->startTag("${cd_dst}Acct");
249 $xml->startTag('Id');
250 $xml->dataElement('IBAN', $transaction->get('dst_iban', 34));
252 $xml->endTag("${cd_dst}Acct");
254 $xml->startTag('RmtInf');
255 $xml->dataElement('Ustrd', $transaction->get('reference', 140));
256 $xml->endTag('RmtInf');
258 $xml->endTag($is_coll ? 'DrctDbtTxInf' : 'CdtTrfTxInf');
261 $xml->endTag('PmtInf');
264 $xml->endTag($pain_elmt);
265 $xml->endTag('Document');