package Business::PxPay;

BEGIN {
    $Business::PxPay::VERSION = '0.04';
}

# ABSTRACT: PX Pay Interface for www.paymentexpress.com

use warnings;
use strict;
use Carp qw/croak/;
use URI::Escape qw/uri_escape/;
use LWP::UserAgent;
use XML::Simple qw/XMLin XMLout/;
use vars qw/%TRANSACTIONS/;

%TRANSACTIONS = (
    purchase      => 'Purchase',
    credit        => 'Refund',
    authorization => 'Auth',
    capture       => 'Complete',
    validate      => 'Validate'
);

sub new {
    my $class = shift;
    my $args = scalar @_ % 2 ? shift : {@_};

    # validate
    $args->{userid} or croak 'userid is required';
    $args->{key}    or croak 'key is required';

    $args->{url} ||= 'https://sec.paymentexpress.com/pxpay/pxaccess.aspx';

    unless ( $args->{ua} ) {
        my $ua_args = delete $args->{ua_args} || {};
        $args->{ua} = LWP::UserAgent->new(%$ua_args);
    }

    bless $args, $class;
}

sub request {
    my $self = shift;
    my $args = scalar @_ % 2 ? shift : {@_};

    my $xml = $self->request_xml($args);
    my $resp = $self->{ua}->post( $self->{url}, Content => $xml );
    unless ( $resp->is_success ) {
        croak $resp->status_line;
    }
    my $rtn = XMLin( $resp->content, SuppressEmpty => undef );
    return $rtn;
}

sub request_xml {
    my $self = shift;
    my $args = scalar @_ % 2 ? shift : {@_};

    # validate
    my $TxnType = $args->{TxnType} || croak 'TxnType is required';
    my $Amount =
         $args->{Amount}
      || $self->{Amount}
      || croak 'Amount is required';
    $Amount = sprintf( "%.2f", $Amount );    # .XX format
    my $Currency =
         $args->{Currency}
      || $self->{Currency}
      || croak 'Currency is required';
    my $UrlFail =
         $args->{UrlFail}
      || $self->{UrlFail}
      || croak 'UrlFail is required';
    my $UrlSuccess =
         $args->{UrlSuccess}
      || $self->{UrlSuccess}
      || croak 'UrlSuccess is required';
    my $MerchantReference = $args->{MerchantReference}
      || croak 'MerchantReference is required';

    # UrlFail can't contain '?' or '&'
    if ( $UrlFail =~ /\?/ or $UrlFail =~ /\&/ ) {
        croak
"UrlFail can't contain '?' or '&', please use TxnData1, TxnData2, TxnData3 or Opt\n";
    }
    if ( $UrlSuccess =~ /\?/ or $UrlSuccess =~ /\&/ ) {
        croak
"UrlSuccess can't contain '?' or '&', please use TxnData1, TxnData2, TxnData3 or Opt\n";
    }

    my $request = {
        GenerateRequest => {
            PxPayUserId       => [ $self->{userid} ],
            PxPayKey          => [ $self->{key} ],
            AmountInput       => [$Amount],
            CurrencyInput     => [$Currency],
            MerchantReference => [$MerchantReference],
            EmailAddress      => [ $args->{EmailAddress} ],
            TxnData1          => [ $args->{TxnData1} ],
            TxnData2          => [ $args->{TxnData2} ],
            TxnData3          => [ $args->{TxnData3} ],
            TxnType           => [$TxnType],
            TxnId             => [ $args->{TxnId} ],
            BillingId         => [ $args->{BillingId} ],
            EnableAddBillCard => [ $args->{EnableAddBillCard} ],
            UrlSuccess        => [$UrlSuccess],
            UrlFail           => [$UrlFail],
            Opt               => [ $args->{Opt} ],
        },
    };
    return XMLout( $request, KeepRoot => 1 );
}

sub result {
    my ( $self, $ResponseCode ) = @_;

    my $xml = $self->result_xml($ResponseCode);
    my $resp = $self->{ua}->post( $self->{url}, Content => $xml );
    unless ( $resp->is_success ) {
        croak $resp->status_line;
    }
    my $rtn = XMLin( $resp->content, SuppressEmpty => undef );
    return $rtn;
}

sub result_xml {
    my ( $self, $ResponseCode ) = @_;

    my $request = {
        ProcessResponse => {
            PxPayUserId => [ $self->{userid} ],
            PxPayKey    => [ $self->{key} ],
            Response    => [$ResponseCode],
        },
    };
    return XMLout( $request, KeepRoot => 1 );
}

1;

=pod

=head1 NAME

Business::PxPay - PX Pay Interface for www.paymentexpress.com

=head1 VERSION

version 0.04

=head1 SYNOPSIS

    use Business::PxPay;

    my $pxpay = Business::PxPay->new(
        userid => 'TestAccount',
        key    => 'c9fff215b9e2add78d252b78e214880b46a906e73190a380483c1c29acab4157'
    );
    
    # when submit the cart order
    if ( $submit_order ) {
        my $rtn = $pxpay->request($args); # $args from CGI params
        if ( exists $rtn->{valid} and $rtn->{valid} == 1 ) {
            print $q->redirect( $rtn->{URI} );
        } else {
            die Dumper(\$rtn);
        }
    }
    # when user returns back from pxpal
    elsif ( $in_return_or_cancel_page or $params->{result} ) {
        my $rtn = $pxpay->result($params->{result});
        if ( exists $rtn->{valid} and $rtn->{valid} == 1 ) {
            print "Transaction Success!\n";
        } else {
            print "Transaction Failed!\n";
        }
    }

=head1 DESCRIPTION

PX Pay - Payment Express L<http://www.paymentexpress.com/>

=head2 new

    my $pxpay = Business::PxPay->new(
        userid => $user,
        key    => $key
    );

=over 4

=item * C<userid> (required)

=item * C<key> (required)

PxPayUserId & PxPayKey

=item * C<ua>

=item * C<ua_args>

By default, we use LWP::UserAgent->new as the UserAgent. you can pass C<ua> or C<ua_args> to use a different one.

=item * C<url>

    my $pxpay = Business::PxPay->new(
        userid => $user,
        key    => $key,
        url    => 'https://sec2.paymentexpress.com/pxpay/pxaccess.aspx', # to test?
    );

The URL is 'https://www.paymentexpress.com/pxpay/pxaccess.aspx' by default.

=back

=head2 Arguments

All those arguments can be passed into Business::PxPay->new() or pass into $pxpay->request later

    {
        TxnType => 'Purchase',
        Amount  => 10.9,
        UrlFail => 'http://test.com',
        UrlSuccess => 'http://example.com',
        MerchantReference => 'Test Transaction',
        EmailAddress => 'test@example.com',
        TxnData1 => 'test=A',
        TxnData2 => 'data2=B',
        TxnData3 => 'data3=C',
    };

=over 4

=item * C<Amount> (required)

Amount value in d.cc format

=item * C<Currency> (required)

L<http://www.paymentexpress.com/technical_resources/ecommerce_hosted/pxpay.html#currencyinput>

=item * C<UrlFail> (required)

URL of the merchant transaction failure page. No parameters ("&" or "?") are permitted.

=item * C<UrlSuccess> (required)

URL of the merchant transaction success page. No parameters ("&" or "?") are permitted.

=item * C<TxnType> (required)

"Auth" or "Purchase"

=item * C<MerchantReference> (required)

Reference field to appear on transaction reports

=item * C<BillingId>

Optional identifier when adding a card for recurring billing

=item * C<EmailAddress>

Optional email address

=item * C<EnableAddBillCard>

Required when adding a card to the DPS system for recurring billing. Set element to "1" for true

=item * C<TxnId>

A value that uniquely identifies the transaction 

=item * C<TxnData1>

=item * C<TxnData2>

=item * C<TxnData3>

Optional free text

=item * C<Opt>

Optional additional parameter

=back

=head2 request

    my $xml = $pxpay->request_xml($args);
    my $rtn = $pxpay->request($args);

request and parse the response XML into HASHREF. sample:

    $VAR1 = \{
        'URI' => 'https://sec2.paymentexpress.com/pxpay/pxpay.aspx?userid=
TestAccount&request=v51flwn7rvSNcbY86uRMdJ74XB2gHd8ZY-WHqyEYoPm9xd1ROXX00pXYkkuk
dleLlS402E65EjOSCkrqvmAsZUWRCck8RkmIJcRLvG0KZLi7PQRBfpIQ0wzKwdHGKvBCpqhRH6Tx-w93
MRYsP0ThOK4btgTneR_hGEk0supyLeE1taNWCkyFj8KX7rzZ9ncdWRlmciNBsiV4zX4DQ_7Poi9qiblI
5o0Gm49yb90kUlUtH1hrV3ulzidQbn0CcQKhHFKGX8IVMXiAtVN29r_Cgdzc7dOrwOxY-LBY2h4Or5GQ
hJHB96kjBziu3GyGBvaGfsosNodT3-wyM29A5M-Z62ITkno6JUA6H4',
        'valid' => '1'
    };

Usually you need redirect to the $rtn->{URI} when valid is 1

=head2 result

    my $xml = $pxpay->result_xml($ResponseCode);
    my $rtn = $pxpay->result($ResponseCode);
    if ( exists $rtn->{valid} and $rtn->{valid} == 1 ) {
        print "Transaction Success!\n";
    } else {
        print "Transaction Failed!\n";
    }

PxPay will POST to your C<UrlSuccess> (or C<UrlFail>) when you finish the transaction (or click Cancel button). the POST would contain a param B<result> which you can request to get the transaction status.

=head2 TIPS

=head3 I need params in C<UrlSuccess>

For example, you want your UrlSuccess to be 'http://mysite.com/cgi-bin/cart.cgi?cart_id=ABC'.

you need write the request like:

    my $rtn = $pxpay->request(
        # others
        UrlSuccess => 'http://mysite.com/cgi-bin/cart.cgi',
        TxnData1 => 'ABC',
    );

and you can get the C<TxnData1> in

    my $rtn = $pxpay->result($ResponseCode);
    my $cart_id = $rtn->{TxnData1}

=head1 AUTHOR

Fayland Lam <fayland@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2011 by Fayland Lam.

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

__END__
