package Business::OnlineThirdPartyPayment::PayPal; use strict; use base 'Business::OnlineThirdPartyPayment'; use vars qw($VERSION $DEBUG); use strict; use LWP; use JSON; use Net::PayPal; # for authentication, mostly use URI; use Cache::FileCache; # for ID strings $VERSION = '0.01'; $DEBUG = 2; sub set_defaults { my $self = shift; $self->build_subs(qw(order_number result_code error_message error_object cache_root)); } sub client { my $self = shift; my %content = $self->content; $self->{'client'} ||= Net::PayPal->new($content{'login'}, $content{'password'}); } sub cache { my $self = shift; $self->{'cache'} ||= Cache::FileCache->new( { namespace => 'PayPal', default_expires_in => 3600, cache_root => $self->cache_root } ); } sub submit { my $self = shift; my %content = $self->content; my $action = lc($content{'action'}); if ( $action eq 'authorization only' ) { $self->create_payment; } elsif ( $action eq 'post authorization' ) { $self->execute_payment; } } sub rest { # a wrapper for the one in Net::PayPal, with better error handling my ($self, $path, $request) = @_; my $json_request = encode_json($request); warn "REQUEST:\n$json_request\n\n" if $DEBUG >= 2; my $raw_res = $self->client->rest('POST', $path, $json_request, 1); # last argument is "dump_responce" [sic]--tells Net::PayPal to dump the # HTTP::Response object instead of returning (part of) the error status my $res; # deal with certain ambiguities from Data::Dumper { my $VAR1; eval "$raw_res"; $res = $VAR1; } if ( !defined($res) || !ref($res) || !$res->isa('HTTP::Response') ) { die "Nonsense output from Net::PayPal REST call:\n$raw_res\n\n"; } warn "RESPONSE:" . $res->status_line . "\n" . $res->content . "\n\n" if $DEBUG >= 2; if ( $res->is_success ) { $self->is_success(1); return decode_json($res->content); } else { $self->is_success(0); if ( $res->content ) { my $response = decode_json($res->content); $self->error_object($response); my $error = sprintf("%s: %s", $response->{'name'}, $response->{'message'}); if ( $response->{'details'} ) { foreach (@{ $response->{'details'} }) { $error .= sprintf("\n%s:\t%s", $_->{'field'}, $_->{'issue'}); } } $self->error_message($error); return $response; } else { $self->error_object({}); $self->error_message($res->status_line); return {}; } } } sub create_payment { my $self = shift; my %content = $self->content; my $ref = $content{'reference'} or die "reference required"; my $return_url = URI->new($content{'callback_url'}) or die "callback_url required"; $return_url->query_form( $return_url->query_form(), 'ref' => $ref ); my $cancel_url = URI->new($content{'cancel_url'}) or die "cancel_url required"; $cancel_url->query_form( $cancel_url->query_form(), 'ref' => $ref ); my $request = { intent => 'sale', payer => { payment_method => 'paypal', }, transactions => [ { amount => { total => $content{'amount'}, currency => ($content{'currency'} || 'USD'), }, description => $content{'description'}, }, ], redirect_urls => { return_url => $return_url->as_string, cancel_url => $cancel_url->as_string, }, }; my $response = $self->rest('/v1/payments/payment', $request); if ( $self->is_success ) { $self->order_number($response->{'id'}); $self->cache->set( "REF-$ref" => $response->{'id'} ); my %links = map { $_->{rel} => $_->{href} } @{ $response->{'links'} }; $self->popup_url($links{'approval_url'}); # other links are "self", which is where we just posted, # and "execute_url", which we can determine from the payment id } } sub execute_payment { my $self = shift; my %content = $self->content; # at this point the transaction is already set up # (right? the workflow in this is horribly confusing...) if ( !$self->authorization ) { die "No authorization was received for this payment.\n"; } my $request = { 'payer_id' => $self->authorization }; my $execute_path = '/v1/payments/payment/' . $self->order_number . '/execute'; $self->rest($execute_path, $request); } sub reference { my $self = shift; my $data = shift; # hashref of query params included in the callback URL $self->authorization($data->{'PayerID'}); my $ref = $data->{'ref'}; my $id = $self->cache->get("REF-$ref"); if (!$id) { $self->error_message("Payment reference '$ref' not found."); $self->is_success(0); } $self->order_number($id); $ref; } 1; __END__ =head1 NAME Business::OnlineThirdPartyPayment::PayPal =head1 DESCRIPTION =head1 AUTHOR Mark Wells =head1 SEE ALSO perl(1). L. =cut