File Coverage

blib/lib/WWW/PayPal/Role/HTTP.pm
Criterion Covered Total %
statement 24 69 34.7
branch 0 22 0.0
condition 0 13 0.0
subroutine 8 12 66.6
pod 2 2 100.0
total 34 118 28.8


line stmt bran cond sub pod time code
1             package WWW::PayPal::Role::HTTP;
2              
3             # ABSTRACT: HTTP + OAuth2 role for the PayPal REST API
4              
5 2     2   1432 use Moo::Role;
  2         4  
  2         17  
6 2     2   1178 use Carp qw(croak);
  2         4  
  2         161  
7 2     2   1034 use JSON::MaybeXS qw(decode_json encode_json);
  2         23259  
  2         173  
8 2     2   1211 use HTTP::Request;
  2         50819  
  2         92  
9 2     2   1854 use LWP::UserAgent;
  2         67627  
  2         98  
10 2     2   1155 use MIME::Base64 qw(encode_base64);
  2         1706  
  2         183  
11 2     2   14 use URI;
  2         4  
  2         58  
12 2     2   9 use Log::Any qw($log);
  2         5  
  2         28  
13              
14             our $VERSION = '0.002';
15              
16              
17             requires 'client_id';
18             requires 'secret';
19             requires 'base_url';
20              
21             has ua => (
22             is => 'lazy',
23             builder => sub {
24 0     0     LWP::UserAgent->new(
25             agent => 'WWW-PayPal/' . $WWW::PayPal::Role::HTTP::VERSION,
26             timeout => 30,
27             );
28             },
29             );
30              
31              
32             has _access_token => ( is => 'rw' );
33             has _token_expires_at => ( is => 'rw', default => sub { 0 } );
34              
35             sub _fetch_token {
36 0     0     my ($self) = @_;
37 0 0         croak "client_id required" unless $self->client_id;
38 0 0         croak "secret required" unless $self->secret;
39              
40 0           my $uri = URI->new($self->base_url . '/v1/oauth2/token');
41 0           my $req = HTTP::Request->new(POST => $uri);
42 0           $req->header(
43             Authorization => 'Basic ' . encode_base64($self->client_id . ':' . $self->secret, ''),
44             'Content-Type' => 'application/x-www-form-urlencoded',
45             Accept => 'application/json',
46             );
47 0           $req->content('grant_type=client_credentials');
48              
49 0           $log->debugf('PayPal OAuth2 token request to %s', $uri);
50 0           my $res = $self->ua->request($req);
51 0 0         unless ($res->is_success) {
52 0           $log->errorf('PayPal OAuth2 failed: %s', $res->status_line);
53 0           croak 'PayPal OAuth2 failed: ' . $res->status_line . ' ' . $res->decoded_content;
54             }
55 0           my $data = decode_json($res->decoded_content);
56 0           $self->_access_token($data->{access_token});
57             # refresh 60s before expiry to be safe
58 0   0       $self->_token_expires_at(time + ($data->{expires_in} // 0) - 60);
59 0           return $data->{access_token};
60             }
61              
62             sub access_token {
63 0     0 1   my ($self) = @_;
64 0 0 0       return $self->_access_token
65             if $self->_access_token && time < $self->_token_expires_at;
66 0           return $self->_fetch_token;
67             }
68              
69              
70             sub request {
71 0     0 1   my ($self, $method, $path, %args) = @_;
72              
73 0           my $uri = URI->new($self->base_url . $path);
74 0 0         $uri->query_form($args{query}) if $args{query};
75              
76 0           my $req = HTTP::Request->new($method => $uri);
77 0           $req->header(
78             Authorization => 'Bearer ' . $self->access_token,
79             Accept => 'application/json',
80             );
81 0 0         for my $k (keys %{$args{headers} || {}}) {
  0            
82 0           $req->header($k => $args{headers}{$k});
83             }
84              
85 0 0         if (defined $args{body}) {
86 0   0       my $ct = $args{content_type} || 'application/json';
87 0           $req->header('Content-Type' => $ct);
88 0 0         if (ref $args{body}) {
89 0           $req->content(encode_json($args{body}));
90             } else {
91 0           $req->content($args{body});
92             }
93             }
94              
95 0           $log->debugf('PayPal %s %s', $method, $uri);
96 0           my $res = $self->ua->request($req);
97              
98 0           my $body = $res->decoded_content;
99 0           my $data;
100 0 0 0       if (length $body && $body =~ /\A\s*[\{\[]/) {
101 0           $data = decode_json($body);
102             }
103              
104 0 0         unless ($res->is_success) {
105             my $msg = ref $data eq 'HASH'
106 0 0 0       ? ($data->{message} || $data->{error_description} || $data->{error} || $res->status_line)
107             : $res->status_line;
108 0           $log->errorf('PayPal API error: %s', $msg);
109 0           croak "PayPal API error ($method $path): $msg";
110             }
111              
112 0           return $data;
113             }
114              
115              
116              
117             1;
118              
119             __END__