start
authorMark Wells <mark@freeside.biz>
Thu, 25 Apr 2013 20:35:58 +0000 (13:35 -0700)
committerMark Wells <mark@freeside.biz>
Thu, 25 Apr 2013 20:35:58 +0000 (13:35 -0700)
.gitignore [new file with mode: 0644]
Changes [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
PayPal.pm [new file with mode: 0644]
README [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..9788afa
--- /dev/null
@@ -0,0 +1,6 @@
+blib/
+*.sw?
+Makefile
+Makefile.old
+MYMETA.yml
+pm_to_blib
diff --git a/Changes b/Changes
new file mode 100644 (file)
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 (file)
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 (file)
index 0000000..d8acdce
--- /dev/null
@@ -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 (file)
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 (file)
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.
+