File Coverage

blib/lib/Net/PayPal/Lite.pm
Criterion Covered Total %
statement 21 131 16.0
branch 0 58 0.0
condition 0 40 0.0
subroutine 7 27 25.9
pod 9 13 69.2
total 37 269 13.7


line stmt bran cond sub pod time code
1             package Net::PayPal::Lite;
2 2     2   158418 use strict;
  2         3  
  2         56  
3 2     2   12 use warnings;
  2         4  
  2         90  
4            
5 2     2   1435 use JSON;
  2         20569  
  2         9  
6 2     2   308 use Carp 'croak';
  2         4  
  2         78  
7 2     2   1255 use LWP::UserAgent;
  2         95471  
  2         66  
8 2     2   12 use HTTP::Headers;
  2         3  
  2         32  
9 2     2   4 use HTTP::Request;
  2         2  
  2         3122  
10            
11             our $VERSION = '0.01';
12            
13             my $json = JSON->new->allow_nonref;
14            
15             sub live {
16 0     0 0   my $self = shift;
17 0 0         return ! $self->sandbox( scalar @_ ? !$_[0] : () );
18             }
19            
20             sub sandbox {
21 0     0 1   my $self = shift;
22 0 0         if (scalar @_) {
23 0           $self->{sandbox} = !! $_[0];
24             }
25 0           return $self->{sandbox};
26             }
27            
28             sub endpoint {
29 0     0 0   my ($self) = @_;
30 0 0         $self->sandbox ? 'https://api.sandbox.paypal.com'
31             : 'https://api.paypal.com';
32             }
33            
34             sub new {
35 0     0 1   my $class = shift;
36 0 0         my $args = (ref $_[0] ? $_[0] : {@_});
37            
38             croak 'please provide a client_id and a secret'
39 0 0 0       unless $args->{client_id} and $args->{secret};
40            
41             croak 'passing both sandbox and live attributes is not allowed'
42 0 0 0       if exists $args->{sandbox} and exists $args->{live};
43            
44             croak '"cache_transform" hashref must have "in" and "out" subrefs'
45             if exists $args->{cache_transform}
46             && (ref $args->{cache_transform} ne 'HASH'
47             || !exists $args->{cache_transform}{in}
48             || !exists $args->{cache_transform}{out}
49             || ref $args->{cache_transform}{in} ne 'CODE'
50 0 0 0       || ref $args->{cache_transform}{out} ne 'CODE'
      0        
51             );
52            
53             return bless {
54             client_id => $args->{client_id},
55             secret => $args->{secret},
56             user_agent => $args->{user_agent} || _create_ua(),
57             cache_transform => $args->{cache_transform},
58             cache => $args->{cache}
59             || _create_cache($args->{cache_dir}),
60             sandbox => ( exists $args->{sandbox} ? $args->{sandbox}
61             : exists $args->{live} ? !$args->{live}
62 0 0 0       : 1
    0 0        
63             ),
64             }, $class;
65             }
66            
67             sub access_token {
68 0     0 0   my $self = shift;
69            
70 0 0         if (@_) {
    0          
71 0           my ($token, $expiration) = @_;
72 0   0       $self->_set_cached_access_token($token, $expiration || () );
73 0           return $token;
74             }
75             elsif ( my $token = $self->_get_cached_access_token ) {
76 0           return $token;
77             }
78             else {
79 0           my ($token ,$expiration) = $self->_get_access_token_from_paypal;
80 0           $self->_set_cached_access_token($token, $expiration);
81 0           return $token;
82             }
83             }
84            
85            
86             sub _get_access_token_from_paypal {
87 0     0     my ($self) = @_;
88            
89 0           my $header = HTTP::Headers->new(
90             'Content-Type' => 'application/x-www-form-urlencoded',
91             'Accept' => 'application/json',
92             'Accept-Language' => 'en_US'
93             );
94            
95 0           $header->authorization_basic( $self->{client_id}, $self->{secret} );
96            
97 0           my $req = HTTP::Request->new(
98             'POST' => $self->endpoint . '/v1/oauth2/token',
99             $header,
100             'grant_type=client_credentials'
101             );
102            
103 0           my $res = $self->{user_agent}->request($req);
104            
105 0 0         croak 'Authorization failed: ' . $res->status_line . ', ' . $res->content
106             unless $res->is_success;
107            
108 0           my $res_hash = _json_decode( $res->content );
109            
110 0           return ($res_hash->{access_token}, $res_hash->{expires_in});
111             }
112            
113             sub _set_cached_access_token {
114 0     0     my ($self, $token, $expiration) = @_;
115            
116             $token = $self->{cache_transform}{in}->($token)
117 0 0         if $self->{cache_transform};
118            
119 0           $self->{cache}->set( $self->{client_id}, $token, $expiration );
120             }
121            
122             sub _get_cached_access_token {
123 0     0     my ($self) = @_;
124            
125 0           my $token = $self->{cache}->get( $self->{client_id} );
126            
127             $token = $self->{cache_transform}{out}->($token)
128 0 0         if $self->{cache_transform};
129            
130 0           return $token;
131             }
132            
133             sub _create_cache {
134 0     0     my ($cache_root) = @_;
135 0           require Cache::FileCache;
136 0 0         return Cache::FileCache->new({
137             namespace => 'PayPalAPI',
138             ($cache_root ? (cache_root => $cache_root) : ()),
139             });
140             }
141            
142             sub _create_ua {
143 0     0     require LWP::UserAgent;
144 0           return LWP::UserAgent->new( agent => "Net-PayPal-Lite/$VERSION" );
145             }
146            
147             sub _json_decode {
148 0     0     my $text = shift;
149 0           my $hashref;
150 0           eval { $hashref = $json->decode($text); };
  0            
151            
152 0 0         if ( my $error = $@ ) {
153 0           croak "_json_decode(): cannot decode $text: $error";
154             }
155 0           return $hashref;
156             }
157            
158             sub _json_encode {
159 0     0     my $hashref = shift;
160 0           return $json->encode($hashref);
161             }
162            
163             sub rest {
164 0     0 1   my ($self, $method, $path, $json) = @_;
165            
166 0           my $target_uri = $self->endpoint . $path;
167 0           my $token = $self->access_token;
168            
169 0           my $req = HTTP::Request->new(
170             $method => $target_uri,
171             [
172             'Content-Type' => 'application/json',
173             'Authorization' => "Bearer $token"
174             ]
175             );
176            
177 0 0         if ($json) {
178 0 0         $json = _json_encode($json) if ref $json;
179 0           $req->content($json);
180             }
181            
182 0           my $res = $self->{user_agent}->request($req);
183            
184 0 0         if ($res->is_success) {
185 0           return _json_decode( $res->content );
186             }
187             else {
188 0 0         if ( my $content = $res->content ) {
189 0           my $error = _json_decode( $res->content );
190 0           $self->error( $res->content );
191             }
192             else {
193 0           $self->error( $res->status_line );
194             }
195 0           return undef;
196             }
197             }
198            
199             sub cc_payment {
200 0     0 1   my $self = shift;
201 0           my ($data) = @_;
202            
203 0           foreach my $field (qw/cc_number cc_type cc_expire_month cc_expire_year/) {
204 0 0         unless ( $data->{$field} ) {
205 0           croak "payment(): $field is a required field";
206             }
207             }
208            
209             my %credit_card = (
210             number => $data->{cc_number},
211             type => $data->{cc_type},
212             expire_month => $data->{cc_expire_month},
213             expire_year => $data->{cc_expire_year}
214 0           );
215            
216 0           foreach my $field (qw/first_name last_name billing_address/) {
217 0 0         if ( $data->{$field} ) {
218 0           $credit_card{$field} = $data->{$field};
219             }
220             }
221            
222             my $request_hash = {
223             intent => 'sale',
224             payer => {
225             payment_method => 'credit_card',
226             funding_instruments => [ { credit_card => \%credit_card } ]
227             },
228             transactions => [
229             {
230             amount => {
231             total => $data->{amount},
232 0   0       currency => $data->{currency} || 'USD'
233             },
234             }
235             ]
236             };
237            
238 0 0         if ( $data->{redirect_urls} ) {
239 0           $request_hash->{redirect_urls} = $data->{redirect_urls};
240             }
241            
242 0           return $self->rest( 'POST', '/v1/payments/payment', _json_encode($request_hash) );
243             }
244            
245             sub stored_cc_payment {
246 0     0 1   my $self = shift;
247 0           my ($data) = @_;
248            
249 0 0         unless ( $data->{id} ) {
250 0           croak 'stored_cc_payment(): "id" is missing';
251             }
252            
253             my $request_hash = {
254             intent => 'sale',
255             payer => {
256             payment_method => 'credit_card',
257             funding_instruments => [ { credit_card_token => { credit_card_id => $data->{id} } } ]
258             },
259             transactions => [
260             {
261             amount => {
262             total => $data->{amount},
263 0   0       currency => $data->{currency} || 'USD'
264             },
265             }
266             ]
267             };
268            
269 0 0         if ( $data->{redirect_urls} ) {
270 0           $request_hash->{redirect_urls} = $data->{redirect_urls};
271             }
272            
273 0           return $self->rest( 'POST', '/v1/payments/payment', _json_encode($request_hash) );
274             }
275            
276             sub get_payment {
277 0     0 1   my $self = shift;
278 0           my ($id) = @_;
279            
280 0 0         unless ($id) {
281 0           croak 'get_payment(): Invalid Payment ID';
282             }
283            
284 0           return $self->rest( 'GET', "/v1/payments/payment/$id" );
285             }
286            
287             sub get_payments {
288 0     0 1   my $self = shift;
289            
290 0           return $self->rest( 'GET', '/v1/payments/payment' );
291             }
292            
293             sub store_cc {
294 0     0 1   my $self = shift;
295 0           my ($data) = @_;
296            
297             my %credit_card = (
298             number => $data->{number} || $data->{cc_number},
299             type => $data->{type} || $data->{cc_type},
300             expire_month => $data->{expire_month} || $data->{cc_expire_month},
301             expire_year => $data->{expire_year} || $data->{cc_expire_year}
302 0   0       );
      0        
      0        
      0        
303            
304 0 0 0       if ( my $cvv2 = $data->{cvv2} || $data->{cc_cvv2} ) {
305 0           $credit_card{cvv2} = $cvv2;
306             }
307            
308 0           foreach my $field (qw/first_name last_name billing_address/) {
309 0 0         if ( $data->{$field} ) {
310 0           $credit_card{$field} = $data->{$field};
311             }
312             }
313 0           return $self->rest( 'POST', '/v1/vault/credit-card', _json_encode( \%credit_card ) );
314             }
315            
316             sub get_cc {
317 0     0 1   my $self = shift;
318 0           my ($id) = @_;
319 0           return $self->rest( 'GET', "/v1/vault/credit-card/$id" );
320             }
321            
322             sub error {
323 0     0 0   my ($self, $message) = @_;
324 0 0         return $self->{last_error} unless $message;
325 0           return $self->{last_error} = $message
326             }
327            
328             1;
329             __END__