File Coverage

blib/lib/Net/PayPal.pm
Criterion Covered Total %
statement 21 127 16.5
branch 0 38 0.0
condition 0 22 0.0
subroutine 7 20 35.0
pod 8 11 72.7
total 36 218 16.5


line stmt bran cond sub pod time code
1             package Net::PayPal;
2            
3             # $Id$
4            
5 1     1   40100 use 5.005;
  1         4  
  1         37  
6 1     1   6 use strict;
  1         2  
  1         39  
7 1     1   1288 use JSON;
  1         15853  
  1         7  
8 1     1   1092 use LWP;
  1         50983  
  1         35  
9 1     1   989 use Crypt::CBC;
  1         4795  
  1         51  
10 1     1   13 use Carp ("croak");
  1         2  
  1         81  
11 1     1   1091 use Cache::FileCache;
  1         44019  
  1         1759  
12            
13             our $VERSION = '0.02';
14            
15             our $ENDPOINT_SANDBOX = "https://api.sandbox.paypal.com";
16             our $ENDPOINT_LIVE = "https://api.paypal.com";
17            
18             my $live = 0;
19            
20             sub live {
21 0     0 0   my $class = shift;
22 0           my ($value) = @_;
23 0           return $live = $value;
24             }
25            
26             sub endpoint {
27 0     0 0   my $class = shift;
28            
29 0 0         if ( $live == 1 ) {
30 0           return $ENDPOINT_LIVE;
31             }
32 0           return $ENDPOINT_SANDBOX;
33             }
34            
35             sub new {
36 0     0 1   my $class = shift;
37            
38 0           my %args = (
39             client_id => $_[0],
40             secret => $_[1],
41             user_agent => LWP::UserAgent->new,
42             app_id => undef,
43             access_token => undef,
44             @_
45             );
46            
47 0 0 0       unless ( $args{client_id} && $args{secret} ) {
48 0           croak " new() : client_id and secret are missing ";
49             }
50            
51             #
52             # checking if access_token is available from previous requests
53             #
54 0           my $cache = Cache::FileCache->new( { cache_root => File::Spec->tmpdir, namespace => 'NetPayPal' } );
55            
56 0           my $cipher = Crypt::CBC->new( -key => $args{"secret"}, -cipher => 'Blowfish' );
57            
58 0 0         if ( my $e_token = $cache->get( $args{"client_id"} ) ) {
59 0           $args{access_token} = $cipher->decrypt($e_token);
60             }
61            
62             else {
63            
64             # if access_token cannot be found in the cache we need to authenticate ourselves to get one
65 0           my $ua = $args{user_agent};
66            
67 0           my $h = HTTP::Headers->new(
68             Accept => "application/json",
69             'Accept-Language' => 'en_US'
70             );
71            
72 0           $h->authorization_basic( $args{client_id}, $args{secret} );
73            
74 0           my $endpoint = $class->endpoint;
75            
76 0           my $req = HTTP::Request->new( "POST", $endpoint . '/v1/oauth2/token', $h );
77 0           $req->content("grant_type=client_credentials");
78            
79 0           my $res = $ua->request($req);
80 0 0         unless ( $res->is_success ) {
81 0           croak "Authorization failed : " . $res->status_line . ', ' . $res->content;
82             }
83            
84 0           my $res_hash = _json_decode( $res->content );
85            
86 0           $args{access_token} = $res_hash->{access_token};
87 0           $args{app_id} = $res_hash->{app_id};
88            
89 0           $cache->set( $args{"client_id"}, $cipher->encrypt( $args{access_token} ), $res_hash->{expires_in} - 5 );
90             }
91 0           return bless( \%args, $class );
92             }
93            
94             my $json = JSON->new->allow_nonref;
95            
96             sub _json_decode {
97 0     0     my $text = shift;
98 0           my $hashref;
99 0           eval { $hashref = $json->decode($text); };
  0            
100            
101 0 0         if ( my $error = $@ ) {
102 0           croak "_json_decode(): cannot decode $text: $error";
103             }
104 0           return $hashref;
105             }
106            
107             sub _json_encode {
108 0     0     my $hashref = shift;
109 0           return $json->encode($hashref);
110             }
111            
112             sub rest {
113 0     0 1   my $self = shift;
114 0           my ( $method, $path, $json, $dump_responce ) = @_;
115            
116 0 0         unless ( $path =~ /\/$/ ) {
117 0           $path = $path . '/';
118             }
119            
120 0           my $endpoint = $self->endpoint;
121 0           $endpoint = sprintf( " % s%s", $endpoint, $path );
122 0           my $a_token = $self->{access_token};
123 0           my $req = HTTP::Request->new( $method, $endpoint, [ 'Content-Type', 'application/json', 'Authorization', "Bearer $a_token" ] );
124            
125 0 0         if ($json) {
126 0           $req->content($json);
127             }
128            
129 0           my $ua = $self->{user_agent};
130 0           my $res = $ua->request($req);
131            
132 0 0         if ($dump_responce) {
133 0           require Data::Dumper;
134 0           return Data::Dumper::Dumper($res);
135             }
136            
137 0 0         unless ( $res->is_success ) {
138 0 0         if ( my $content = $res->content ) {
139 0           my $error = _json_decode( $res->content );
140 0           $self->error( sprintf( "%s: %s. See: %s", $error->{name}, $error->{message}, $error->{information_link} ) );
141 0           return undef;
142             }
143 0           $self->error( $res->status_line );
144 0           return undef;
145             }
146 0           return _json_decode( $res->content );
147             }
148            
149             sub cc_payment {
150 0     0 1   my $self = shift;
151 0           my ($data) = @_;
152            
153 0           foreach my $field (qw/cc_number cc_type cc_expire_month cc_expire_year/) {
154 0 0         unless ( $data->{$field} ) {
155 0           croak "payment(): $field is a required field";
156             }
157             }
158            
159 0           my %credit_card = (
160             number => $data->{cc_number},
161             type => $data->{cc_type},
162             expire_month => $data->{cc_expire_month},
163             expire_year => $data->{cc_expire_year}
164             );
165            
166 0           foreach my $field (qw/first_name last_name billing_address/) {
167 0 0         if ( $data->{$field} ) {
168 0           $credit_card{$field} = $data->{$field};
169             }
170             }
171            
172 0   0       my $request_hash = {
173             intent => 'sale',
174             payer => {
175             payment_method => "credit_card",
176             funding_instruments => [ { credit_card => \%credit_card } ]
177             },
178             transactions => [
179             {
180             amount => {
181             total => $data->{amount},
182             currency => $data->{currency} || "USD"
183             },
184             }
185             ]
186             };
187            
188 0 0         if ( $data->{redirect_urls} ) {
189 0           $request_hash->{redirect_urls} = $data->{redirect_urls};
190             }
191            
192 0           return $self->rest( 'POST', "/v1/payments/payment", _json_encode($request_hash) );
193             }
194            
195             sub stored_cc_payment {
196 0     0 1   my $self = shift;
197 0           my ($data) = @_;
198            
199 0 0         unless ( $data->{id} ) {
200 0           croak "stored_cc_payment(): 'id' is missing";
201             }
202            
203 0   0       my $request_hash = {
204             intent => 'sale',
205             payer => {
206             payment_method => "credit_card",
207             funding_instruments => [ { credit_card_token => { credit_card_id => $data->{id} } } ]
208             },
209             transactions => [
210             {
211             amount => {
212             total => $data->{amount},
213             currency => $data->{currency} || "USD"
214             },
215             }
216             ]
217             };
218            
219 0 0         if ( $data->{redirect_urls} ) {
220 0           $request_hash->{redirect_urls} = $data->{redirect_urls};
221             }
222            
223 0           return $self->rest( 'POST', "/v1/payments/payment", _json_encode($request_hash) );
224             }
225            
226             sub get_payment {
227 0     0 1   my $self = shift;
228 0           my ($id) = @_;
229            
230 0 0         unless ($id) {
231 0           croak "get_payment(): Invalid Payment ID";
232             }
233            
234 0           return $self->rest( "GET", "/v1/payments/payment/$id" );
235             }
236            
237             sub get_payments {
238 0     0 1   my $self = shift;
239            
240 0           return $self->rest( "GET", "/v1/payments/payment" );
241             }
242            
243             sub store_cc {
244 0     0 1   my $self = shift;
245 0           my ($data) = @_;
246            
247 0   0       my %credit_card = (
      0        
      0        
      0        
248             number => $data->{number} || $data->{cc_number},
249             type => $data->{type} || $data->{cc_type},
250             expire_month => $data->{expire_month} || $data->{cc_expire_month},
251             expire_year => $data->{expire_year} || $data->{cc_expire_year}
252             );
253            
254 0 0 0       if ( my $cvv2 = $data->{cvv2} || $data->{cc_cvv2} ) {
255 0           $credit_card{cvv2} = $cvv2;
256             }
257            
258 0           foreach my $field (qw/first_name last_name billing_address/) {
259 0 0         if ( $data->{$field} ) {
260 0           $credit_card{$field} = $data->{$field};
261             }
262             }
263 0           return $self->rest( 'POST', "/v1/vault/credit-card", _json_encode( \%credit_card ) );
264             }
265            
266             sub get_cc {
267 0     0 1   my $self = shift;
268 0           my ($id) = @_;
269 0           return $self->rest( "GET", "/v1/vault/credit-card/$id" );
270             }
271            
272             my $last_error;
273            
274             sub error {
275 0     0 0   my $self = shift;
276 0           my ($new_message) = @_;
277            
278 0 0         unless ($new_message) {
279 0           return $last_error;
280             }
281            
282 0           $last_error = $new_message;
283             }
284            
285             1;
286             __END__