1 package SL::Helper::QrBillParser;
6 use SL::Helper::QrBillFunctions qw(
7 get_street_name_from_address_line
8 get_building_number_from_address_line
9 get_postal_code_from_address_line
10 get_town_name_from_address_line
13 use Rose::Object::MakeMethods::Generic(
14 scalar => [ qw(is_valid error raw_data) ],
15 'scalar --get_set_init' => [ qw(spec) ],
18 our $VERSION = '0.01';
21 REGEX_QRTYPE => qr{^SPC$},
22 REGEX_VERSION => qr{^0200$},
23 REGEX_CODING => qr{^1$},
24 REGEX_IBAN => qr{^(?:CH|LI)[0-9a-zA-Z]{19}$},
25 REGEX_ADDRESS_TYPE => qr{^[KS]$},
26 REGEX_NAME => qr{^.{1,70}$},
27 REGEX_ADDRESS_LINE => qr{^.{0,70}$},
28 REGEX_POSTAL_CODE => qr{^.{0,16}$},
29 REGEX_TOWN => qr{^.{0,35}$},
30 REGEX_COUNTRY => qr{^[A-Za-z]{2}$},
31 REGEX_AMOUNT => qr{^(?:(?:0|[1-9][0-9]{0,8})\.[0-9]{2})?$},
32 REGEX_CURRENCY => qr{^(?:CHF|EUR)$},
33 REGEX_REFERENCE_TYPE => qr{^(?:QRR|SCOR|NON)$},
34 REGEX_REFERENCE => qr{^.{0,27}$},
35 REGEX_UNSTRUCTURED_MESSAGE => qr{^.{0,140}$},
36 REGEX_TRAILER => qr{^EPD$},
37 REGEX_BILL_INFORMATION => qr{^.{0,140}$},
38 REGEX_ALTERNATIVE_SCHEME_PARAMETER => qr{^.{0,100}$},
44 my $self = bless {}, $class;
55 my @lines = split /(?:\n|\r\n)/, $qrtext;
59 $self->raw_data($qrtext);
61 for my $section ( @{$self->spec} ) {
62 for my $field ( @{$section->{fields}} ) {
63 my $value = $lines[$field->{line_number}];
65 if (!test_value($value, $field->{test}, $field->{status})) {
66 $self->error("Test failed: Section: '$section->{section}' Field: '$field->{name}' Value: '$value'");
71 $self->{$section->{section}} = {} if (!$self->{$section->{section}});
72 $self->{$section->{section}}->{$field->{name}} = $value;
78 sub get_creditor_field {
80 my ($structured_field, $extract_field, $extract_fn) = @_;
82 if ($self->{creditor}->{address_type} eq 'S') {
83 return $self->{creditor}->{$structured_field};
86 my $r = $extract_fn->($self->{creditor}->{$extract_field});
91 sub get_creditor_street_name {
92 return shift->get_creditor_field(
93 'street_or_address_line_1',
94 'street_or_address_line_1',
95 \&get_street_name_from_address_line
99 sub get_creditor_building_number {
100 return shift->get_creditor_field(
101 'building_number_or_address_line_2',
102 'street_or_address_line_1',
103 \&get_building_number_from_address_line
107 sub get_creditor_post_code {
108 return shift->get_creditor_field(
110 'building_number_or_address_line_2',
111 \&get_postal_code_from_address_line
115 sub get_creditor_town_name {
116 return shift->get_creditor_field(
118 'building_number_or_address_line_2',
119 \&get_town_name_from_address_line
131 test => REGEX_QRTYPE,
137 test => REGEX_VERSION,
143 test => REGEX_CODING,
149 section => 'creditor_information',
160 section => 'creditor',
163 name => 'address_type',
165 test => REGEX_ADDRESS_TYPE,
175 name => 'street_or_address_line_1',
177 test => REGEX_ADDRESS_LINE,
181 name => 'building_number_or_address_line_2',
183 test => REGEX_ADDRESS_LINE,
187 name => 'postal_code',
189 test => REGEX_POSTAL_CODE,
201 test => REGEX_COUNTRY,
207 section => 'ultimate_creditor',
210 name => 'address_type',
212 test => REGEX_ADDRESS_TYPE,
222 name => 'street_or_address_line_1',
224 test => REGEX_ADDRESS_LINE,
228 name => 'building_number_or_address_line_2',
230 test => REGEX_ADDRESS_LINE,
234 name => 'postal_code',
236 test => REGEX_POSTAL_CODE,
248 test => REGEX_COUNTRY,
254 section => 'payment_amount_information',
259 test => REGEX_AMOUNT,
265 test => REGEX_CURRENCY,
271 section => 'ultimate_debtor',
274 name => 'address_type',
276 test => REGEX_ADDRESS_TYPE,
286 name => 'street_or_address_line_1',
288 test => REGEX_ADDRESS_LINE,
292 name => 'building_number_or_address_line_2',
294 test => REGEX_ADDRESS_LINE,
298 name => 'postal_code',
300 test => REGEX_POSTAL_CODE,
312 test => REGEX_COUNTRY,
318 section => 'payment_reference',
321 name => 'reference_type',
323 test => REGEX_REFERENCE_TYPE,
329 test => REGEX_REFERENCE,
335 section => 'additional_information',
338 name => 'unstructured_message',
340 test => REGEX_UNSTRUCTURED_MESSAGE,
346 test => REGEX_TRAILER,
350 name => 'bill_information',
352 test => REGEX_BILL_INFORMATION,
358 section => 'alternative_scheme',
361 name => 'alternative_scheme_parameter1',
363 test => REGEX_ALTERNATIVE_SCHEME_PARAMETER,
367 name => 'alternative_scheme_parameter2',
369 test => REGEX_ALTERNATIVE_SCHEME_PARAMETER,
380 my ($value, $test, $status) = @_;
382 # mandatory fields must have a content
383 return 0 if $status eq 'M' && length $value <= 0;
385 # optional fields can be empty
386 return 1 if $status eq 'O' && length $value == 0;
388 # dependent fields can be empty
389 return 1 if $status eq 'D' && length $value == 0;
391 # "do not fill" fields cannot have a content
392 if ($status eq 'X') {
393 return 1 if ($value eq '');
397 # additional fields can be undefined
398 if ($status eq 'A') {
399 return 1 if !defined($value);
400 return 0 if $value !~ $test;
404 return 0 if !defined($value);
405 return 0 if $value !~ $test;
419 SL::Helper::QrBillParser - Helper for parsing QR bill data
423 use SL::Helper::QrBillParser;
425 my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data});
427 my $valid = $qr_obj->is_valid;
428 my $error_message = $qr_obj->error;
429 my $qrtext = $qr_obj->raw_data;
431 # data for remittance information
432 my $reference = $qr_obj->{payment_reference}->{reference};
433 my $unstructured_message = $qr_obj->{additional_information}->{unstructured_message}
435 # set currency and amount
436 my $currency = $qr_obj->{payment_amount_information}->{currency};
437 my $amount = $qr_obj->{payment_amount_information}->{amount}
439 # set creditor name and address from qr data
440 my $creditor_name = $qr_obj->{creditor}->{name};
441 my $creditor_street_name = $qr_obj->get_creditor_street_name;
442 my $creditor_building_number = $qr_obj->get_creditor_building_number;
443 my $creditor_postal_code = $qr_obj->get_creditor_post_code;
444 my $creditor_town_name = $qr_obj->get_creditor_town_name;
445 my $creditor_country = $qr_obj->{creditor}->{country}
448 my $creditor_iban = $qr_obj->{creditor_information}->{iban};
452 This is simple helper to parse swiss qr bill data from a string into an object.
454 Some methods are provided to easily retrieve the creditor address data.
462 my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data});
464 Creates a new object from the qr bill data string.
468 my $valid = $qr_obj->is_valid;
470 Returns true if the qr bill data is valid.
474 my $error_message = $qr_obj->error;
476 Returns the error message if the qr bill data is invalid.
480 my $qrtext = $qr_obj->raw_data;
482 Returns the raw qr bill data string.
484 =item C<get_creditor_street_name>
486 my $creditor_street_name = $qr_obj->get_creditor_street_name;
488 Returns the creditor street name.
490 =item C<get_creditor_building_number>
492 my $creditor_building_number = $qr_obj->get_creditor_building_number;
494 Returns the creditor building number.
496 =item C<get_creditor_post_code>
498 my $creditor_postal_code = $qr_obj->get_creditor_post_code;
500 Returns the creditor postal code.
502 =item C<get_creditor_town_name>
504 my $creditor_town_name = $qr_obj->get_creditor_town_name;
506 Returns the creditor town name.
512 Tests for functions see t/helper/qrbill_parser.t.
514 Run: C<t/test.pl t/helper/qrbill_parser.t>
518 Basic validation is performed based on the status code and regular expressions.
519 However complete checks of dependent fields would require more elaborate logic.
527 Cem Aydin E<lt>cem.aydin@revamp-it.chE<gt>
528 Steven Schubiger E<lt>stsc@refcnt.orgE<gt>