File Coverage

blib/lib/WebService/PayPal/NVP.pm
Criterion Covered Total %
statement 32 111 28.8
branch 3 16 18.7
condition 0 7 0.0
subroutine 10 26 38.4
pod 12 13 92.3
total 57 173 32.9


line stmt bran cond sub pod time code
1             package WebService::PayPal::NVP;
2              
3 5     5   450821 use Moo;
  5         50658  
  5         20  
4 5     5   10100 use DateTime;
  5         1616670  
  5         223  
5 5     5   2966 use Encode qw( decode );
  5         35801  
  5         287  
6 5     5   2581 use LWP::UserAgent ();
  5         136462  
  5         127  
7 5     5   2308 use MooX::Types::MooseLike::Base qw( InstanceOf );
  5         22844  
  5         390  
8 5     5   29 use URI::Escape qw/uri_escape uri_escape_utf8 uri_unescape/;
  5         7  
  5         227  
9 5     5   1837 use WebService::PayPal::NVP::Response;
  5         12  
  5         2247  
10              
11             our $VERSION = '0.006';
12             $VERSION = eval $VERSION;
13              
14             has 'errors' => (
15             is => 'rw',
16             isa => sub {
17             die "errors expects an array reference!\n"
18             unless ref $_[0] eq 'ARRAY';
19             },
20             default => sub { [] },
21             );
22              
23             has 'ua' => (
24             is => 'ro',
25             isa => InstanceOf ['LWP::UserAgent'],
26             builder => '_build_ua'
27             );
28              
29             has 'user' => ( is => 'rw', required => 1 );
30             has 'pwd' => ( is => 'rw', required => 1 );
31             has 'sig' => ( is => 'rw', required => 1 );
32             has 'url' => ( is => 'rw' );
33             has 'branch' => ( is => 'rw', default => sub { 'sandbox' } );
34             has 'api_ver' => ( is => 'rw', default => sub { 51.0 } );
35              
36             sub BUILDARGS {
37 1     1 0 4041 my ( $class, %args ) = @_;
38              
39             # detect URL if it's missing
40 1 50       5 if ( not $args{url} ) {
41             $args{url} = "https://api-3t.sandbox.paypal.com/nvp"
42 1 50       4 if $args{branch} eq 'sandbox';
43              
44             $args{url} = "https://api-3t.paypal.com/nvp"
45 1 50       6 if $args{branch} eq 'live';
46             }
47              
48 1         15 return \%args;
49             }
50              
51             sub _build_ua {
52 0     0     my $self = shift;
53              
54 0           my $lwp = LWP::UserAgent->new;
55 0           $lwp->agent("p-Webservice-PayPal-NVP/${VERSION}");
56 0           return $lwp;
57             }
58              
59             sub _do_request {
60 0     0     my ( $self, $args ) = @_;
61              
62             my $authargs = {
63             user => $self->user,
64             pwd => $self->pwd,
65             signature => $self->sig,
66             version => $args->{version} || $self->api_ver,
67 0   0       subject => $args->{subject} || '',
      0        
68             };
69              
70 0           my $allargs = { %$authargs, %$args };
71 0           my $content = $self->_build_content($allargs);
72 0           my $res = $self->ua->post(
73             $self->url,
74             'Content-Type' => 'application/x-www-form-urlencoded',
75             Content => $content,
76             );
77              
78 0 0         unless ( $res->code == 200 ) {
79 0           $self->errors( [ "Failure: " . $res->code . ": " . $res->message ] );
80 0           return;
81             }
82              
83             my $resp = {
84 0           map { decode( 'UTF-8', uri_unescape($_) ) }
85 0           map { split '=', $_, 2 }
  0            
86             split '&', $res->content
87             };
88              
89 0           my $res_object = WebService::PayPal::NVP::Response->new(
90             branch => $self->branch,
91             raw => $resp
92             );
93 0 0         if ( $resp->{ACK} ne 'Success' ) {
94 0           $res_object->errors( [] );
95 0           my $i = 0;
96 0           while ( my $err = $resp->{"L_LONGMESSAGE${i}"} ) {
97 0           push @{ $res_object->errors },
98 0           $resp->{"L_LONGMESSAGE${i}"};
99 0           $i += 1;
100             }
101              
102 0           $res_object->success(0);
103             }
104             else {
105 0           $res_object->success(1);
106             }
107              
108             {
109 5     5   30 no strict 'refs';
  5         5  
  5         141  
  0            
110 5     5   34 no warnings 'redefine';
  5         6  
  5         2875  
111 0           foreach my $key ( keys %$resp ) {
112 0           my $val = $resp->{$key};
113 0           my $lc_key = lc $key;
114 0 0         if ( $lc_key eq 'timestamp' ) {
115 0 0         if ( $val =~ /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)Z/ ) {
116 0           my ( $day, $month, $year, $hour, $min, $sec )
117             = ( $3, $2, $1, $4, $5, $6 );
118              
119 0           $val = DateTime->new(
120             year => $year,
121             month => $month,
122             day => $day,
123             hour => $hour,
124             minute => $min,
125             second => $sec,
126             );
127             }
128             }
129 0           *{"WebService::PayPal::NVP::Response::$lc_key"} = sub {
130 0     0     return $val;
131 0           };
132             }
133             }
134 0           return $res_object;
135             }
136              
137             sub _build_content {
138 0     0     my ( $self, $args ) = @_;
139 0           my @args;
140 0           for my $key ( keys %$args ) {
141 0 0         $args->{$key} = defined $args->{$key} ? $args->{$key} : '';
142             push @args,
143 0           uc( uri_escape($key) ) . '=' . uri_escape_utf8( $args->{$key} );
144             }
145              
146 0   0       return ( join '&', @args ) || '';
147             }
148              
149             sub has_errors {
150 0     0 1   my $self = shift;
151 0           return scalar @{ $self->errors } > 0;
  0            
152             }
153              
154             sub set_express_checkout {
155 0     0 1   my ( $self, $args ) = @_;
156 0           $args->{method} = 'SetExpressCheckout';
157 0           $self->_do_request($args);
158             }
159              
160             sub do_express_checkout_payment {
161 0     0 1   my ( $self, $args ) = @_;
162 0           $args->{method} = 'DoExpressCheckoutPayment';
163 0           $self->_do_request($args);
164             }
165              
166             sub get_express_checkout_details {
167 0     0 1   my ( $self, $args ) = @_;
168 0           $args->{method} = 'GetExpressCheckoutDetails';
169 0           $self->_do_request($args);
170             }
171              
172             sub do_direct_payment {
173 0     0 1   my ( $self, $args ) = @_;
174 0           $args->{method} = 'DoDirectPayment';
175 0           $self->_do_request($args);
176             }
177              
178             sub create_recurring_payments_profile {
179 0     0 1   my ( $self, $args ) = @_;
180 0           $args->{method} = 'CreateRecurringPaymentsProfile';
181 0           $self->_do_request($args);
182             }
183              
184             sub get_recurring_payments_profile_details {
185 0     0 1   my ( $self, $args ) = @_;
186 0           $args->{method} = 'GetRecurringPaymentsProfileDetails';
187 0           $self->_do_request($args);
188             }
189              
190             sub get_transaction_details {
191 0     0 1   my ( $self, $args ) = @_;
192 0           $args->{method} = 'GetTransactionDetails';
193 0           $self->_do_request($args);
194             }
195              
196             sub manage_recurring_payments_profile_status {
197 0     0 1   my ( $self, $args ) = @_;
198 0           $args->{method} = 'ManageRecurringPaymentsProfileStatus';
199 0           $self->_do_request($args);
200             }
201              
202             sub mass_pay {
203 0     0 1   my ( $self, $args ) = @_;
204 0           $args->{method} = 'MassPay';
205 0           $self->_do_request($args);
206             }
207              
208             sub refund_transaction {
209 0     0 1   my ( $self, $args ) = @_;
210 0           $args->{method} = 'RefundTransaction';
211 0           $self->_do_request($args);
212             }
213              
214             sub transaction_search {
215 0     0 1   my ( $self, $args ) = @_;
216 0           $args->{method} = 'TransactionSearch';
217 0           $self->_do_request($args);
218             }
219              
220             1;
221              
222             =pod
223              
224             =encoding UTF-8
225              
226             =head1 NAME
227              
228             WebService::PayPal::NVP - PayPal NVP API
229              
230             =head1 VERSION
231              
232             version 0.006
233              
234             =head1 SYNOPSIS
235              
236             use feature qw( say );
237              
238             my $nvp = WebService::PayPal::NVP->new(
239             user => 'user.tld',
240             pwd => 'xxx',
241             sig => 'xxxxxxx',
242             branch => 'sandbox',
243             );
244              
245             my $res = $nvp->set_express_checkout({
246             DESC => 'Payment for something cool',
247             AMT => 25.00,
248             CURRENCYCODE => 'GBP',
249             PAYMENTACTION => 'Sale',
250             RETURNURL => "http://returnurl.tld",
251             CANCELURL => "http//cancelurl.tld",
252             LANDINGPAGE => 'Login',
253             ADDOVERRIDE => 1,
254             SHIPTONAME => "Customer Name",
255             SHIPTOSTREET => "7 Customer Street",
256             SHIPTOSTREET2 => "",
257             SHIPTOCITY => "Town",
258             SHIPTOZIP => "Postcode",
259             SHIPTOEMAIL => "customer\@example.com",
260             SHIPTOCOUNTRYCODE => 'GB',
261             });
262              
263             if ($res->success) {
264             # timestamps turned into DateTime objects
265             say "Response received at "
266             . $res->timestamp->dmy . " "
267             . $res->timestamp->hms(':');
268              
269             say $res->token;
270              
271             for my $arg ($res->args) {
272             if ($res->has_arg($arg)) {
273             say "$arg => " . $res->$arg;
274             }
275             }
276              
277             # get a redirect uri to paypal express checkout
278             # the Response object will automatically detect if you have
279             # live or sandbox and return the appropriate url for you
280             if (my $redirect_user_to = $res->express_checkout_uri) {
281             ...;
282             }
283             }
284             else {
285             say $_
286             for @{$res->errors};
287             }
288              
289             =head1 DESCRIPTION
290              
291             A pure object oriented interface to PayPal's NVP API (Name-Value Pair). A lot of the logic in this module was taken from L<Business::PayPal::NVP>. I re-wrote it because it wasn't working with Catalyst adaptors and I couldn't save instances of it in Moose-type accessors. Otherwise it worked fine. So if you don't need that kind of support you should visit L<Business::PayPal::NVP>!.
292             Another difference with this module compared to Business::PayPal::NVP is that the keys may be passed as lowercase. Also, a response will return a WebService::PayPal::NVP::Response object where the response values are methods. Timestamps will automatically be converted to DateTime objects for your convenience.
293              
294             =head1 METHODS
295              
296             =head2 api_ver
297              
298             The version of PayPal's NVP API which you would like to use. Defaults to 51.
299              
300             =head2 errors
301              
302             Returns an C<ArrayRef> of errors. The ArrayRef is empty when there are no
303             errors.
304              
305             =head2 has_errors
306              
307             Returns true if C<errors()> is non-empty.
308              
309             =head2 create_recurring_payments_profile( $HashRef )
310              
311             =head2 do_direct_payment( $HashRef )
312              
313             =head2 do_express_checkout_payment( $HashRef )
314              
315             =head2 get_express_checkout_details( $HashRef )
316              
317             =head2 get_recurring_payments_profile_details( $HashRef )
318              
319             =head2 get_transaction_details( $HashRef )
320              
321             =head2 manage_recurring_payments_profile_status( $HashRef )
322              
323             =head2 mass_pay( $HashRef )
324              
325             =head2 refund_transaction( $HashRef )
326              
327             =head2 set_express_checkout( $HashRef )
328              
329             =head2 transaction_search( $HashRef )
330              
331             =head2 ua( LWP::UserAgent->new( ... ) )
332              
333             This method allows you to provide your own UserAgent. This object must be of
334             the L<LWP::UserAgent> family, so L<WWW::Mechanize> modules will also work.
335              
336             =head2 url
337              
338             The PayPal URL to use for requests. This can be helpful when mocking requests.
339             Defaults to PayPals production or sandbox URL as appropriate.
340              
341             =head1 TESTING
342              
343             The main test will not work out of the box, because obviously it needs some sandbox/live api details before it can proceed. Simply create an C<auth.yml> file in the distribution directory with the following details:
344              
345             ---
346             user: 'api_user'
347             pass: 'api password'
348             sig: 'api signature'
349             branch: 'sandbox or live'
350              
351             If it detects the file missing completely it will just skip every test. Otherwise, it will only fail if any of the required information is missing.
352              
353             =head1 AUTHOR
354              
355             Brad Haywood <brad@geeksware.com>
356              
357             =head1 CREDITS
358              
359             A lot of this module was taken from L<Business::PayPal::NVP> by Scott Wiersdorf.
360             It was only rewritten in order to work properly in L<Catalyst::Model::Adaptor>.
361              
362             =head2 THANKS
363              
364             A huge thanks to Olaf Alders (OALDERS) for all of his useful pull requests!
365              
366             =head1 AUTHOR
367              
368             Brad Haywood <brad@perlpowered.com>
369              
370             =head1 COPYRIGHT AND LICENSE
371              
372             This software is copyright (c) 2013-2017 by Brad Haywood.
373              
374             This is free software; you can redistribute it and/or modify it under
375             the same terms as the Perl 5 programming language system itself.
376              
377             =cut
378              
379             __END__
380              
381              
382             # ABSTRACT: PayPal NVP API