Parse-PayPal-TxDetailReport

 view release on metacpan or  search on metacpan

lib/Parse/PayPal/TxDetailReport.pm  view on Meta::CPAN


sub _parse_date {
    DateTime::Format::Flexible->parse_datetime(shift)->epoch;
}

$SPEC{parse_paypal_txdetail_report} = {
    v => 1.1,
    summary => 'Parse PayPal transaction detail report into data structure',
    description => <<'_',

The result will be a hashref. The main key is `transactions` which will be an
arrayref of hashrefs.

Dates will be converted into Unix timestamps.

_
    args => {
        files => {
            schema => ['array*', of=>'filename*', min_len=>1],
            description => <<'_',

Files can all be in tab-separated or comma-separated (CSV) format but cannot be
mixed. If there are multiple files, they must be ordered.

_
            pos => 0,
            greedy => 1,
        },
        strings => {
            schema => ['array*', of=>'str*', min_len=>1],
            description => <<'_',

Instead of `files`, you can alternatively provide the file contents in
`strings`.

_
        },
        format => {
            schema => ['str*', in=>[qw/tsv csv/]],
            description => <<'_',

If unspecified, will be deduced from the first filename's extension (/csv/ for
CSV, or /txt|tsv/ for tab-separated).

_
        },
    },
    args_rels => {
        req_one => ['files', 'strings'],
    },
};
sub parse_paypal_txdetail_report {
    my %args = @_;

    my $format = $args{format};

    my @handles;
    my @files;
    if (my $strings = $args{strings}) {
        require IO::Scalar;
        require String::BOM;

        if (!$format) {
            $format = $strings->[0] =~ /\t/ ? 'tsv' : 'csv';
        }
        for my $str0 (@{ $strings }) {
            my $str = String::BOM::strip_bom_from_string($str0);
            my $fh = IO::Scalar->new(\$str);
            push @handles, $fh;
            push @files, "string";
        }
    } elsif (my $files = $args{files}) {
        require File::BOM;

        if (!$format) {
            $format = $files->[0] =~ /\.(csv)\z/i ? 'csv' : 'tsv';
        }
        for my $file (@{ $files }) {
            open my($fh), "<:encoding(utf8):via(File::BOM)", $file
                or return [500, "Can't open file '$file': $!"];

            push @handles, $fh;
            push @files, $file;
        }
    } else {
        return [400, "Please specify files (or strings)"];
    }

    my $res = [200, "OK", {
        format => "txdetail_v11",
        transactions => [],
    }];

    my $code_parse_row = sub {
        my $row = shift;

        if ($row->[0] eq 'RH') { # row header
            $res->[2]{RH_seen}++ and do {
                $res = [400, "RH row seen twice in a file"];
                goto RETURN_RES;
            };
            $res->[2]{report_generation_date} //= _parse_date($row->[1]);
            $res->[2]{reporting_window} //= $row->[2];
            $res->[2]{account_id} //= $row->[3];
            $res->[2]{report_version} //= $row->[4];
            $row->[4] == 11 or do {
                $res = [400, "Version ($row->[4]) not supported, only version 11 is supported"];
                goto RETURN_RES;
            };
        } elsif ($row->[0] eq 'FH') { # file header
            $res->[2]{FH_seen}++ and do {
                $res = [400, "FH row seen twice in a file"];
                goto RETURN_RES;
            };
            $res->[2]{cur_file_seq} == $row->[1] or do {
                $res = [400, "Unexpected file sequence, expected sequence ".
                            "$res->[2]{cur_file_seq} for file ".
                            "$res->[2]{cur_file}"];
                goto RETURN_RES;
            };
        } elsif ($row->[0] eq 'SH') { # section header
            $res->[2]{SH_seen}++ and do {
                $res = [400, "SH row seen twice in a file"];
                goto RETURN_RES;
            };
        } elsif ($row->[0] eq 'CH') { # column header
            $res->[2]{transaction_columns} //= [@{$row}[1..$#{$row}]];
        } elsif ($row->[0] eq 'SB') { # section body
            my $tx = {};
            my $txcols = $res->[2]{transaction_columns};
            for (1..$#{$row}) {
                my $header = $txcols->[$_-1];
                if ($header =~ /Date$/ && $row->[$_]) {
                    $tx->{$header} = _parse_date($row->[$_]);
                } else {
                    $tx->{$header} = $row->[$_];
                }
            }
            push @{ $res->[2]{transactions} }, $tx;



( run in 0.633 second using v1.01-cache-2.11-cpan-9581c071862 )