Projekt

Allgemein

Profil

Herunterladen (12,6 KB) Statistiken
| Zweig: | Markierung: | Revision:
package SL::Helper::QrBillParser;

use strict;
use warnings;

use SL::Helper::QrBillFunctions qw(
get_street_name_from_address_line
get_building_number_from_address_line
get_postal_code_from_address_line
get_town_name_from_address_line
);

use Rose::Object::MakeMethods::Generic(
scalar => [ qw(is_valid error raw_data) ],
'scalar --get_set_init' => [ qw(spec) ],
);

our $VERSION = '0.01';

use constant {
REGEX_QRTYPE => qr{^SPC$},
REGEX_VERSION => qr{^0200$},
REGEX_CODING => qr{^1$},
REGEX_IBAN => qr{^(?:CH|LI)[0-9a-zA-Z]{19}$},
REGEX_ADDRESS_TYPE => qr{^[KS]$},
REGEX_NAME => qr{^.{1,70}$},
REGEX_ADDRESS_LINE => qr{^.{0,70}$},
REGEX_POSTAL_CODE => qr{^.{0,16}$},
REGEX_TOWN => qr{^.{0,35}$},
REGEX_COUNTRY => qr{^[A-Za-z]{2}$},
REGEX_AMOUNT => qr{^(?:(?:0|[1-9][0-9]{0,8})\.[0-9]{2})?$},
REGEX_CURRENCY => qr{^(?:CHF|EUR)$},
REGEX_REFERENCE_TYPE => qr{^(?:QRR|SCOR|NON)$},
REGEX_REFERENCE => qr{^.{0,27}$},
REGEX_UNSTRUCTURED_MESSAGE => qr{^.{0,140}$},
REGEX_TRAILER => qr{^EPD$},
REGEX_BILL_INFORMATION => qr{^.{0,140}$},
REGEX_ALTERNATIVE_SCHEME_PARAMETER => qr{^.{0,100}$},
};

sub new {
my $class = shift;

my $self = bless {}, $class;

$self->init(@_);

return $self;
}

sub init {
my $self = shift;
my ($qrtext) = @_;

my @lines = split /(?:\n|\r\n)/, $qrtext;

$self->is_valid(1);
$self->error('');
$self->raw_data($qrtext);

for my $section ( @{$self->spec} ) {
for my $field ( @{$section->{fields}} ) {
my $value = $lines[$field->{line_number}];

if (!test_value($value, $field->{test}, $field->{status})) {
$self->error("Test failed: Section: '$section->{section}' Field: '$field->{name}' Value: '$value'");
$self->is_valid(0);
last;
}

$self->{$section->{section}} = {} if (!$self->{$section->{section}});
$self->{$section->{section}}->{$field->{name}} = $value;
}
last if $self->error;
}
}

sub get_creditor_field {
my $self = shift;
my ($structured_field, $extract_field, $extract_fn) = @_;

if ($self->{creditor}->{address_type} eq 'S') {
return $self->{creditor}->{$structured_field};
}
# extract
my $r = $extract_fn->($self->{creditor}->{$extract_field});

return $r // '';
}

sub get_creditor_street_name {
return shift->get_creditor_field(
'street_or_address_line_1',
'street_or_address_line_1',
\&get_street_name_from_address_line
);
}

sub get_creditor_building_number {
return shift->get_creditor_field(
'building_number_or_address_line_2',
'street_or_address_line_1',
\&get_building_number_from_address_line
);
}

sub get_creditor_post_code {
return shift->get_creditor_field(
'postal_code',
'building_number_or_address_line_2',
\&get_postal_code_from_address_line
);
}

sub get_creditor_town_name {
return shift->get_creditor_field(
'town',
'building_number_or_address_line_2',
\&get_town_name_from_address_line
);
}

sub init_spec {
[
{
section => 'header',
fields => [
{
name => 'qrtype',
line_number => 0,
test => REGEX_QRTYPE,
status => 'M'
},
{
name => 'version',
line_number => 1,
test => REGEX_VERSION,
status => 'M'
},
{
name => 'coding',
line_number => 2,
test => REGEX_CODING,
status => 'M'
}
]
},
{
section => 'creditor_information',
fields => [
{
name => 'iban',
line_number => 3,
test => REGEX_IBAN,
status => 'M'
}
]
},
{
section => 'creditor',
fields => [
{
name => 'address_type',
line_number => 4,
test => REGEX_ADDRESS_TYPE,
status => 'M',
},
{
name => 'name',
line_number => 5,
test => REGEX_NAME,
status => 'M',
},
{
name => 'street_or_address_line_1',
line_number => 6,
test => REGEX_ADDRESS_LINE,
status => 'O'
},
{
name => 'building_number_or_address_line_2',
line_number => 7,
test => REGEX_ADDRESS_LINE,
status => 'O'
},
{
name => 'postal_code',
line_number => 8,
test => REGEX_POSTAL_CODE,
status => 'D'
},
{
name => 'town',
line_number => 9,
test => REGEX_TOWN,
status => 'D'
},
{
name => 'country',
line_number => 10,
test => REGEX_COUNTRY,
status => 'M'
}
]
},
{
section => 'ultimate_creditor',
fields => [
{
name => 'address_type',
line_number => 11,
test => REGEX_ADDRESS_TYPE,
status => 'X'
},
{
name => 'name',
line_number => 12,
test => REGEX_NAME,
status => 'X'
},
{
name => 'street_or_address_line_1',
line_number => 13,
test => REGEX_ADDRESS_LINE,
status => 'X'
},
{
name => 'building_number_or_address_line_2',
line_number => 14,
test => REGEX_ADDRESS_LINE,
status => 'X'
},
{
name => 'postal_code',
line_number => 15,
test => REGEX_POSTAL_CODE,
status => 'X'
},
{
name => 'town',
line_number => 16,
test => REGEX_TOWN,
status => 'X'
},
{
name => 'country',
line_number => 17,
test => REGEX_COUNTRY,
status => 'X'
}
]
},
{
section => 'payment_amount_information',
fields => [
{
name => 'amount',
line_number => 18,
test => REGEX_AMOUNT,
status => 'O'
},
{
name => 'currency',
line_number => 19,
test => REGEX_CURRENCY,
status => 'M'
}
]
},
{
section => 'ultimate_debtor',
fields => [
{
name => 'address_type',
line_number => 20,
test => REGEX_ADDRESS_TYPE,
status => 'D'
},
{
name => 'name',
line_number => 21,
test => REGEX_NAME,
status => 'D'
},
{
name => 'street_or_address_line_1',
line_number => 22,
test => REGEX_ADDRESS_LINE,
status => 'O'
},
{
name => 'building_number_or_address_line_2',
line_number => 23,
test => REGEX_ADDRESS_LINE,
status => 'O'
},
{
name => 'postal_code',
line_number => 24,
test => REGEX_POSTAL_CODE,
status => 'D'
},
{
name => 'town',
line_number => 25,
test => REGEX_TOWN,
status => 'D'
},
{
name => 'country',
line_number => 26,
test => REGEX_COUNTRY,
status => 'D'
}
]
},
{
section => 'payment_reference',
fields => [
{
name => 'reference_type',
line_number => 27,
test => REGEX_REFERENCE_TYPE,
status => 'M'
},
{
name => 'reference',
line_number => 28,
test => REGEX_REFERENCE,
status => 'D'
}
]
},
{
section => 'additional_information',
fields => [
{
name => 'unstructured_message',
line_number => 29,
test => REGEX_UNSTRUCTURED_MESSAGE,
status => 'O'
},
{
name => 'trailer',
line_number => 30,
test => REGEX_TRAILER,
status => 'M'
},
{
name => 'bill_information',
line_number => 31,
test => REGEX_BILL_INFORMATION,
status => 'A'
}
]
},
{
section => 'alternative_scheme',
fields => [
{
name => 'alternative_scheme_parameter1',
line_number => 32,
test => REGEX_ALTERNATIVE_SCHEME_PARAMETER,
status => 'A'
},
{
name => 'alternative_scheme_parameter2',
line_number => 33,
test => REGEX_ALTERNATIVE_SCHEME_PARAMETER,
status => 'A'
}
]
}
];
}

### helper

sub test_value {
my ($value, $test, $status) = @_;

# mandatory fields must have a content
return 0 if $status eq 'M' && length $value <= 0;

# optional fields can be empty
return 1 if $status eq 'O' && length $value == 0;

# dependent fields can be empty
return 1 if $status eq 'D' && length $value == 0;

# "do not fill" fields cannot have a content
if ($status eq 'X') {
return 1 if ($value eq '');
return 0;
}

# additional fields can be undefined
if ($status eq 'A') {
return 1 if !defined($value);
return 0 if $value !~ $test;
return 1;
}

return 0 if !defined($value);
return 0 if $value !~ $test;
return 1;
}

1;

__END__

=pod

=encoding utf8

=head1 NAME

SL::Helper::QrBillParser - Helper for parsing QR bill data

=head1 SYNOPSIS

use SL::Helper::QrBillParser;

my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data});

my $valid = $qr_obj->is_valid;
my $error_message = $qr_obj->error;
my $qrtext = $qr_obj->raw_data;

# data for remittance information
my $reference = $qr_obj->{payment_reference}->{reference};
my $unstructured_message = $qr_obj->{additional_information}->{unstructured_message}

# set currency and amount
my $currency = $qr_obj->{payment_amount_information}->{currency};
my $amount = $qr_obj->{payment_amount_information}->{amount}

# set creditor name and address from qr data
my $creditor_name = $qr_obj->{creditor}->{name};
my $creditor_street_name = $qr_obj->get_creditor_street_name;
my $creditor_building_number = $qr_obj->get_creditor_building_number;
my $creditor_postal_code = $qr_obj->get_creditor_post_code;
my $creditor_town_name = $qr_obj->get_creditor_town_name;
my $creditor_country = $qr_obj->{creditor}->{country}

# set creditor iban
my $creditor_iban = $qr_obj->{creditor_information}->{iban};

=head1 DESCRIPTION

This is simple helper to parse swiss qr bill data from a string into an object.

Some methods are provided to easily retrieve the creditor address data.

=head1 FUNCTIONS

=over 4

=item C<new>

my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data});

Creates a new object from the qr bill data string.

=item C<is_valid>

my $valid = $qr_obj->is_valid;

Returns true if the qr bill data is valid.

=item C<error>

my $error_message = $qr_obj->error;

Returns the error message if the qr bill data is invalid.

=item C<raw_data>

my $qrtext = $qr_obj->raw_data;

Returns the raw qr bill data string.

=item C<get_creditor_street_name>

my $creditor_street_name = $qr_obj->get_creditor_street_name;

Returns the creditor street name.

=item C<get_creditor_building_number>

my $creditor_building_number = $qr_obj->get_creditor_building_number;

Returns the creditor building number.

=item C<get_creditor_post_code>

my $creditor_postal_code = $qr_obj->get_creditor_post_code;

Returns the creditor postal code.

=item C<get_creditor_town_name>

my $creditor_town_name = $qr_obj->get_creditor_town_name;

Returns the creditor town name.

=back

=head1 TESTS

Tests for functions see t/helper/qrbill_parser.t.

Run: C<t/test.pl t/helper/qrbill_parser.t>

=head1 LIMITATIONS

Basic validation is performed based on the status code and regular expressions.
However complete checks of dependent fields would require more elaborate logic.

=head1 BUGS

Nothing here yet.

=head1 AUTHOR

Cem Aydin E<lt>cem.aydin@revamp-it.chE<gt>
Steven Schubiger E<lt>stsc@refcnt.orgE<gt>

=cut
(17-17/20)