The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Business::CPI::Gateway::PagSeguro;
# ABSTRACT: Business::CPI's PagSeguro driver

use Moo;
use XML::LibXML;
use Carp;
use LWP::Simple ();
use URI;
use URI::QueryParam;
use DateTime;

extends 'Business::CPI::Gateway::Base';

has '+checkout_url' => (
    default => sub { 'https://pagseguro.uol.com.br/v2/checkout/payment.html' },
);

has '+currency' => (
    default => sub { 'BRL' },
);

has base_url => (
    is => 'ro',
    default => sub { 'https://ws.pagseguro.uol.com.br/v2' },
);

has token => (
    is  => 'ro',
);

sub get_notifications_url {
    my ($self, $code) = @_;

    return $self->_build_uri("/transactions/notifications/$code");
}

sub get_transaction_details_url {
    my ($self, $code) = @_;

    return $self->_build_uri("/transactions/$code");
}

sub get_transaction_query_url {
    my ($self, $info) = @_;

    $info ||= {};

    my $final_date   = $info->{final_date}   || DateTime->now(time_zone => 'local'); # XXX: really local?
    my $initial_date = $info->{initial_date} || $final_date->clone->subtract(days => 30);

    my $new_info = {
        initialDate    => $initial_date->strftime('%Y-%m-%dT%H:%M'),
        finalDate      => $final_date->strftime('%Y-%m-%dT%H:%M'),
        page           => $info->{page} || 1,
        maxPageResults => $info->{rows} || 1000,
    };

    return $self->_build_uri('/transactions', $new_info);
}

sub query_transactions { goto \&get_and_parse_transactions }

sub get_and_parse_notification {
    my ($self, $code) = @_;

    my $xml = $self->_load_xml_from_url(
        $self->get_notifications_url($code)
    );

    return $self->_parse_transaction($xml);
}

sub notify {
    my ($self, $req) = @_;

    if ($req->params->{notificationType} eq 'transaction') {
        return $self->get_and_parse_notification(
            $req->params->{notificationCode}
        );
    }
}

sub get_and_parse_transactions {
    my ($self, $info) = @_;

    my $xml = $self->_load_xml_from_url(
        $self->get_transaction_query_url( $info )
    );

    my @transactions = $xml->getChildrenByTagName('transactions')->get_node(1)->getChildrenByTagName('transaction');

    return {
        current_page         => $xml->getChildrenByTagName('currentPage')->string_value,
        results_in_this_page => $xml->getChildrenByTagName('resultsInThisPage')->string_value,
        total_pages          => $xml->getChildrenByTagName('totalPages')->string_value,
        transactions         => [
            map { $self->get_transaction_details( $_ ) }
            map { $_->getChildrenByTagName('code')->string_value } @transactions
        ],
    };
}

sub get_transaction_details {
    my ($self, $code) = @_;

    my $xml = $self->_load_xml_from_url(
        $self->get_transaction_details_url( $code )
    );

    my $result = $self->_parse_transaction($xml);
    $result->{buyer_email} = $xml->getChildrenByTagName('sender')->get_node(1)->getChildrenByTagName('email')->string_value;

    return $result;
}

sub _parse_transaction {
    my ($self, $xml) = @_;

    my $date   = $xml->getChildrenByTagName('date')->string_value;
    my $ref    = $xml->getChildrenByTagName('reference')->string_value;
    my $status = $xml->getChildrenByTagName('status')->string_value;
    my $amount = $xml->getChildrenByTagName('grossAmount')->string_value;

    return {
        payment_id => $ref,
        status     => $self->_status_code_map($status),
        amount     => $amount,
        date       => $date,
    };
}

sub _load_xml_from_url {
    my ($self, $url) = @_;

    return XML::LibXML->load_xml(
        string => LWP::Simple::get( $url )
    )->firstChild();
}

sub _build_uri {
    my ($self, $path, $info) = @_;

    $info ||= {};

    $info->{email} = $self->receiver_email;
    $info->{token} = $self->token;

    my $uri = URI->new($self->base_url . $path);

    while (my ($k, $v) = each %$info) {
        $uri->query_param($k, $v);
    }

    return $uri->as_string;
}

sub _status_code_map {
    my ($self, $status) = @_;

    croak qq/No status provided/
        unless $status;

    $status = int($status);

    my @status_codes;
    @status_codes[1,2,5] = ('processing') x 3;
    @status_codes[6,7]   = ('failed') x 2;
    @status_codes[3,4]   = ('completed') x 2;

    croak qq/Can't understand status code $status/
        if ($status > 7 || $status < 1);

    return $status_codes[$status];
}

sub get_hidden_inputs {
    my ($self, $info) = @_;

    my @hidden_inputs = (
        receiverEmail => $self->receiver_email,
        currency      => $self->currency,
        encoding      => $self->form_encoding,
        reference     => $info->{payment_id},
        senderName    => $info->{buyer}->name,
        senderEmail   => $info->{buyer}->email,
    );

    my $i = 1;

    foreach my $item (@{ $info->{items} }) {
        push @hidden_inputs,
          (
            "itemId$i"          => $item->id,
            "itemDescription$i" => $item->description,
            "itemAmount$i"      => $item->price,
            "itemQuantity$i"    => $item->quantity,
          );
        $i++;
    }

    return @hidden_inputs;
}

1;

__END__

=pod

=encoding utf-8

=head1 NAME

Business::CPI::Gateway::PagSeguro - Business::CPI's PagSeguro driver

=head1 VERSION

version 0.1

=head1 ATTRIBUTES

=head2 token

The token provided by PagSeguro

=head2 base_url

The url for PagSeguro API. Not to be confused with the checkout url, this is
just for the API.

=head1 METHODS

=head2 get_notifications_url

Reader for the notifications URL in PagSeguro's API. This uses the base_url
attribute.

=head2 get_transaction_details_url

Reader for the transaction details URL in PagSeguro's API. This uses the
base_url attribute.

=head2 get_transaction_query_url

Reader for the transaction query URL in PagSeguro's API. This uses the base_url
attribute.

=head2 get_and_parse_notification

Gets the url from L</get_notifications_url>, and loads the XML from there.
Returns a parsed standard Business::CPI hash.

=head2 get_and_parse_transactions

=head2 get_transaction_details

=head2 query_transactions

Alias for L</get_and_parse_transactions> to maintain compatibility with other
Business::CPI modules.

=head2 notify

=head2 get_hidden_inputs

=head1 SEE ALSO

L<Business::CPI::Gateway::Base>

=head1 AUTHOR

André Walker <andre@andrewalker.net>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by André Walker.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut