1 package Business::OnlineThirdPartyPayment::PayPal;
4 use base 'Business::OnlineThirdPartyPayment';
5 use vars qw($VERSION $DEBUG);
10 use Net::PayPal; # for authentication, mostly
12 use Cache::FileCache; # for ID strings
20 $self->build_subs(qw(order_number result_code error_message error_object
26 my %content = $self->content;
28 Net::PayPal->new($content{'login'}, $content{'password'});
34 Cache::FileCache->new(
35 { namespace => 'PayPal',
36 default_expires_in => 3600,
37 cache_root => $self->cache_root
43 my %content = $self->content;
44 my $action = lc($content{'action'});
45 if ( $action eq 'authorization only' ) {
46 $self->create_payment;
47 } elsif ( $action eq 'post authorization' ) {
48 $self->execute_payment;
53 # a wrapper for the one in Net::PayPal, with better error handling
54 my ($self, $path, $request) = @_;
55 my $json_request = encode_json($request);
56 warn "REQUEST:\n$json_request\n\n" if $DEBUG >= 2;
58 $self->client->rest('POST', $path, $json_request, 1);
59 # last argument is "dump_responce" [sic]--tells Net::PayPal to dump the
60 # HTTP::Response object instead of returning (part of) the error status
62 # deal with certain ambiguities from Data::Dumper
66 if ( !defined($res) || !ref($res) || !$res->isa('HTTP::Response') ) {
67 die "Nonsense output from Net::PayPal REST call:\n$raw_res\n\n";
69 warn "RESPONSE:" . $res->status_line . "\n" . $res->content . "\n\n"
72 if ( $res->is_success ) {
74 return decode_json($res->content);
77 if ( $res->content ) {
78 my $response = decode_json($res->content);
79 $self->error_object($response);
80 my $error = sprintf("%s: %s",
81 $response->{'name'}, $response->{'message'});
82 if ( $response->{'details'} ) {
83 foreach (@{ $response->{'details'} }) {
84 $error .= sprintf("\n%s:\t%s", $_->{'field'}, $_->{'issue'});
87 $self->error_message($error);
90 $self->error_object({});
91 $self->error_message($res->status_line);
99 my %content = $self->content;
100 my $ref = $content{'reference'}
101 or die "reference required";
102 my $return_url = URI->new($content{'callback_url'})
103 or die "callback_url required";
104 $return_url->query_form( $return_url->query_form(), 'ref' => $ref );
105 my $cancel_url = URI->new($content{'cancel_url'})
106 or die "cancel_url required";
107 $cancel_url->query_form( $cancel_url->query_form(), 'ref' => $ref );
113 payment_method => 'paypal',
118 total => $content{'amount'},
119 currency => ($content{'currency'} || 'USD'),
121 description => $content{'description'},
125 return_url => $return_url->as_string,
126 cancel_url => $cancel_url->as_string,
130 my $response = $self->rest('/v1/payments/payment', $request);
132 if ( $self->is_success ) {
133 $self->order_number($response->{'id'});
134 $self->cache->set( "REF-$ref" => $response->{'id'} );
136 my %links = map { $_->{rel} => $_->{href} } @{ $response->{'links'} };
137 $self->popup_url($links{'approval_url'});
138 # other links are "self", which is where we just posted,
139 # and "execute_url", which we can determine from the payment id
143 sub execute_payment {
145 my %content = $self->content;
146 # at this point the transaction is already set up
147 # (right? the workflow in this is horribly confusing...)
148 if ( !$self->authorization ) {
149 die "No authorization was received for this payment.\n";
151 my $request = { 'payer_id' => $self->authorization };
152 my $execute_path = '/v1/payments/payment/' . $self->order_number . '/execute';
153 $self->rest($execute_path, $request);
158 my $data = shift; # hashref of query params included in the callback URL
160 $self->authorization($data->{'PayerID'});
161 my $ref = $data->{'ref'};
162 my $id = $self->cache->get("REF-$ref");
164 $self->error_message("Payment reference '$ref' not found.");
165 $self->is_success(0);
167 $self->order_number($id);
176 Business::OnlineThirdPartyPayment::PayPal
182 Mark Wells <mark@freeside.biz>
186 perl(1). L<Business::OnlineThirdPartyPayment>.