From e4d477537d9dcde911e39a96c035d16ecdb10e19 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 25 Apr 2013 13:35:58 -0700 Subject: start --- PayPal.pm | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 PayPal.pm (limited to 'PayPal.pm') diff --git a/PayPal.pm b/PayPal.pm new file mode 100644 index 0000000..538e3ff --- /dev/null +++ b/PayPal.pm @@ -0,0 +1,189 @@ +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 + -- cgit v1.2.1