From e4d477537d9dcde911e39a96c035d16ecdb10e19 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 25 Apr 2013 13:35:58 -0700 Subject: [PATCH] start --- .gitignore | 6 ++ Changes | 4 ++ MANIFEST | 5 ++ Makefile.PL | 12 ++++ PayPal.pm | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README | 27 +++++++++ 6 files changed, 243 insertions(+) create mode 100644 .gitignore create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 Makefile.PL create mode 100644 PayPal.pm create mode 100644 README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9788afa --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +blib/ +*.sw? +Makefile +Makefile.old +MYMETA.yml +pm_to_blib diff --git a/Changes b/Changes new file mode 100644 index 0000000..e3d8e51 --- /dev/null +++ b/Changes @@ -0,0 +1,4 @@ +Revision history for Business-OnlineThirdPartyPayment-PayPal + +0.01 Thu Apr 25 13:06:59 PDT 2013 + Initial release diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..2776586 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,5 @@ +Makefile.PL +MANIFEST +README +PayPal.pm +Changes diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..d8acdce --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,12 @@ +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. +WriteMakefile( + 'NAME' => 'Business::OnlineThirdPartyPayment::PayPal', + 'VERSION_FROM' => 'PayPal.pm', # finds $VERSION + 'AUTHOR' => 'Mark Wells ', + 'PREREQ_PM' => { + 'Business::OnlineThirdPartyPayment' => 3, + 'Net::PayPal' => 0, + }, +); 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 + diff --git a/README b/README new file mode 100644 index 0000000..e355197 --- /dev/null +++ b/README @@ -0,0 +1,27 @@ +Business-OnlineThirdPartyPayment-PayPal is a +Business::OnlineThirdPartyPayment module for interfacing with PayPal. + +INSTALLATION + +To install this module, run the following commands: + + perl Makefile.PL + make + make test + make install + +SUPPORT AND DOCUMENTATION + +After installing, you can find documentation for this module with the +perldoc command. + + perldoc Business::OnlineThirdPartyPayment::PayPal + +COPYRIGHT AND LICENCE + +Copyright (C) 2013 Mark Wells +Copyright (C) 2013 Freeside Internet Services, Inc. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + -- 2.11.0