summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2013-04-25 13:35:58 -0700
committerMark Wells <mark@freeside.biz>2013-04-25 13:35:58 -0700
commite4d477537d9dcde911e39a96c035d16ecdb10e19 (patch)
tree55d0e1d7860378b70a8ec2a909134e5e3cdb9a61
start
-rw-r--r--.gitignore6
-rw-r--r--Changes4
-rw-r--r--MANIFEST5
-rw-r--r--Makefile.PL12
-rw-r--r--PayPal.pm189
-rw-r--r--README27
6 files changed, 243 insertions, 0 deletions
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 <mark@freeside.biz>',
+ '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 <mark@freeside.biz>
+
+=head1 SEE ALSO
+
+perl(1). L<Business::OnlineThirdPartyPayment>.
+
+=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.
+