File Coverage

blib/lib/Business/GoCardless/Client.pm
Criterion Covered Total %
statement 124 126 98.4
branch 26 32 81.2
condition 2 6 33.3
subroutine 26 26 100.0
pod 0 3 0.0
total 178 193 92.2


line stmt bran cond sub pod time code
1             package Business::GoCardless::Client;
2              
3             =head1 NAME
4              
5             Business::GoCardless::Client
6              
7             =head1 DESCRIPTION
8              
9             This is a class for the lower level requests to the gocardless API. generally
10             there is nothing you should be doing with this.
11              
12             =cut
13              
14 19     19   1923831 use strict;
  19         183  
  19         584  
15 19     19   116 use warnings;
  19         40  
  19         586  
16 19     19   120 use feature qw/ say /;
  19         43  
  19         2622  
17              
18 19     19   8286 use Moo;
  19         172172  
  19         107  
19             with 'Business::GoCardless::Utils';
20              
21 19     19   31276 use Business::GoCardless::Exception;
  19         85  
  19         710  
22 19     19   7913 use Business::GoCardless::Bill;
  19         64  
  19         733  
23 19     19   8793 use Business::GoCardless::Merchant;
  19         73  
  19         664  
24 19     19   150 use Business::GoCardless::Payout;
  19         42  
  19         453  
25 19     19   8236 use Business::GoCardless::RedirectFlow;
  19         55  
  19         653  
26 19     19   7519 use Business::GoCardless::Subscription;
  19         68  
  19         742  
27              
28 19     19   160 use Carp qw/ confess /;
  19         48  
  19         1198  
29 19     19   10578 use POSIX qw/ strftime /;
  19         130994  
  19         118  
30 19     19   38450 use MIME::Base64 qw/ encode_base64 /;
  19         13718  
  19         1224  
31 19     19   13493 use LWP::UserAgent;
  19         926150  
  19         847  
32 19     19   187 use JSON ();
  19         46  
  19         39869  
33              
34             =head1 ATTRIBUTES
35              
36             =head2 token
37              
38             Your gocardless API token, this attribute is required.
39              
40             =head2 base_url
41              
42             The gocardless API URL, defaults to $ENV{GOCARDLESS_URL} or
43             https://gocardless.com.
44              
45             =head2 api_path
46              
47             The gocardless API path, defaults to /api/v1.
48              
49             =head2 app_id
50              
51             Your gocardless app identifier, defaults to $ENV{GOCARDLESS_APP_ID} or will
52             exit if not set.
53              
54             =head2 app_secret
55              
56             Your gocardless app secret, defaults to $ENV{GOCARDLESS_APP_SECRET} or will
57             exit if not set.
58              
59             =head2 webhook_secret
60              
61             Your gocardless webhook secret, defaults to $ENV{GOCARDLESS_WEBHOOK_SECRET} or will
62             exit if not set.
63              
64             =head2 merchant_id
65              
66             Your gocardless merchant identifier, defaults to $ENV{GOCARDLESS_MERCHANT_ID}
67             or will exit if not set.
68              
69             =head2 user_agent
70              
71             The user agent string used in requests to the gocardless API, defaults to
72             business-gocardless/perl/v . $version_of_this_library . - . $api_version
73              
74             =cut
75              
76             has api_version => (
77             is => 'ro',
78             required => 0,
79             lazy => 1,
80             default => sub { $ENV{GOCARDLESS_API_VERSION} // 1 },
81             );
82              
83             has token => (
84             is => 'ro',
85             required => 1,
86             );
87              
88             has base_url => (
89             is => 'ro',
90             required => 0,
91             default => sub {
92             my ( $self ) = @_;
93              
94             if ( my $url = $ENV{GOCARDLESS_URL} ) {
95             return $url;
96             } else {
97             return $self->api_version == 1
98             ? 'https://gocardless.com'
99             : 'https://api.gocardless.com';
100             }
101             },
102             );
103              
104             has api_path => (
105             is => 'ro',
106             required => 0,
107             lazy => 1,
108             default => sub {
109             my ( $self ) = @_;
110              
111             if ( $self->api_version == 1 ) {
112             return "/api/v" . $self->api_version;
113             } else {
114             return '';
115             }
116             },
117             );
118              
119             has app_id => (
120             is => 'ro',
121             required => 0,
122             lazy => 1,
123             default => sub {
124             return undef if shift->api_version > 1;
125             $ENV{'GOCARDLESS_APP_ID'}
126             or confess( "Missing required argument: app_id" );
127             }
128             );
129              
130             has app_secret => (
131             is => 'ro',
132             required => 0,
133             lazy => 1,
134             default => sub {
135             return undef if shift->api_version > 1;
136             $ENV{'GOCARDLESS_APP_SECRET'}
137             or confess( "Missing required argument: app_secret" );
138             }
139             );
140              
141             has webhook_secret => (
142             is => 'ro',
143             required => 0,
144             lazy => 1,
145             default => sub {
146             $ENV{'GOCARDLESS_WEBHOOK_SECRET'}
147             or confess( "Missing required argument: webhook_secret" );
148             }
149             );
150              
151             has merchant_id => (
152             is => 'ro',
153             required => 0,
154             lazy => 1,
155             default => sub {
156             my ( $self ) = @_;
157             return undef if $self->api_version > 1;
158             $ENV{'GOCARDLESS_MERCHANT_ID'}
159             or confess( "Missing required argument: merchant_id" );
160             }
161             );
162              
163             has user_agent => (
164             is => 'ro',
165             default => sub {
166             my ( $self ) = @_;
167             # maybe want more info in here, version of perl, platform, and such
168             require Business::GoCardless;
169             return "business-gocardless/perl/v"
170             . $Business::GoCardless::VERSION
171             . "-" . $self->api_version
172             ;
173             }
174             );
175              
176             # making these methods "private" to prevent confusion with the
177             # public methods of the same name in Business::GoCardless
178              
179             sub _new_bill_url {
180 1     1   11 my ( $self,$params ) = @_;
181 1         4 return $self->_new_limit_url( 'bill',$params );
182             }
183              
184             sub _new_pre_authorization_url {
185 1     1   18 my ( $self,$params ) = @_;
186 1         8 return $self->_new_limit_url( 'pre_authorization',$params );
187             }
188              
189             sub _new_subscription_url {
190 1     1   14 my ( $self,$params ) = @_;
191 1         5 return $self->_new_limit_url( 'subscription',$params );
192             }
193              
194             sub _new_limit_url {
195 3     3   40 my ( $self,$type,$limit_params ) = @_;
196              
197 3         89 $limit_params->{merchant_id} = $self->merchant_id;
198              
199             my $params = {
200             nonce => $self->generate_nonce,
201             timestamp => strftime( "%Y-%m-%dT%H:%M:%SZ",gmtime ),
202             client_id => $self->app_id,
203 3         40 ( map { ( $limit_params->{$_}
204 12 100       91 ? ( $_ => delete( $limit_params->{$_} ) ) : ()
205             ) } qw/ redirect_uri cancel_uri cancel_uri state / ),
206             $type => $limit_params,
207             };
208              
209 3         64 $params->{signature} = $self->sign_params( $params,$self->app_secret );
210              
211 3         19 return sprintf(
212             "%s/connect/%ss/new?%s",
213             $self->base_url,
214             $type,
215             $self->normalize_params( $params )
216             );
217             }
218              
219             sub _new_redirect_flow_url {
220 3     3   12 my ( $self,$params ) = @_;
221              
222             my $data = $self->api_post(
223             '/redirect_flows',
224 3         7 { redirect_flows => { %{ $params } } },
  3         16  
225             );
226              
227             my $RedirectFlow = Business::GoCardless::RedirectFlow->new(
228             client => $self,
229 3         13 %{ $data->{redirect_flows} }
  3         68  
230             );
231              
232 3         119 return $RedirectFlow->redirect_url;
233             }
234              
235             sub _confirm_redirect_flow {
236 3     3   33 my ( $self,$redirect_flow_id ) = @_;
237              
238             # first find the original session token
239 3         56 my $RedirectFlow = Business::GoCardless::RedirectFlow->new(
240             client => $self,
241             id => $redirect_flow_id,
242             );
243              
244 3         48 $RedirectFlow->find_with_client( 'redirect_flows' );
245              
246             # now confirm the redirect flow
247 3         19 my $data = $self->api_post(
248             "/redirect_flows/$redirect_flow_id/actions/complete",
249             { data => { session_token => $RedirectFlow->session_token } },
250             );
251              
252             $RedirectFlow = Business::GoCardless::RedirectFlow->new(
253             client => $self,
254 3         10 %{ $data->{redirect_flows} }
  3         73  
255             );
256              
257 3         59 return $RedirectFlow;
258             }
259              
260             sub _confirm_resource {
261 4     4   49 my ( $self,$params ) = @_;
262              
263 4 100       66 if ( ! $self->signature_valid( $params,$self->app_secret ) ) {
264 1         11 Business::GoCardless::Exception->throw({
265             message => "Invalid signature for confirm_resource"
266             });
267             }
268              
269             my $data = {
270             resource_id => $params->{resource_id},
271             resource_type => $params->{resource_type},
272 3         18 };
273              
274 3         52 my $credentials = encode_base64( $self->app_id . ':' . $self->app_secret );
275 3         98 $credentials =~ s/\s//g;
276              
277 3         19 my $ua = LWP::UserAgent->new;
278 3         3643 $ua->agent( $self->user_agent );
279              
280 3         262 my $req = HTTP::Request->new(
281             POST => join( '/',$self->base_url . $self->api_path,'confirm' )
282             );
283              
284 3         9474 $req->header( 'Authorization' => "Basic $credentials" );
285 3         243 $req->header( 'Accept' => 'application/json' );
286              
287 3         154 $req->content_type( 'application/x-www-form-urlencoded' );
288 3         105 $req->content( $self->normalize_params( $data ) );
289              
290 3         88 my $res = $ua->request( $req );
291              
292 3 50       30 if ( $res->is_success ) {
293            
294 3         243 my $class_suffix = ucfirst( $params->{resource_type} );
295 3         14 $class_suffix =~ s/_([A-z])/uc($1)/ge;
  1         6  
296 3         11 my $class = "Business::GoCardless::$class_suffix";
297             my $obj = $class->new(
298             client => $self,
299             id => $params->{resource_id}
300 3         33 );
301 3         109 return $obj->find_with_client;
302             }
303             else {
304 0         0 Business::GoCardless::Exception->throw({
305             message => $res->content,
306             code => $res->code,
307             response => $res->status_line,
308             });
309             }
310             }
311              
312             =head1 METHODS
313              
314             api_get
315             api_post
316             api_put
317              
318             Make a request to the gocardless API:
319              
320             my $data = $Client->api_get( '/merchants/123ABCD/bills',\%params );
321              
322             In list context returns the links and pagination headers:
323              
324             my ( $data,$links,$info ) = $Client->api_get( ... );
325              
326             =cut
327              
328             sub api_get {
329 41     41 0 156 my ( $self,$path,$params ) = @_;
330 41         150 return $self->_api_request( 'GET',$path,$params );
331             }
332              
333             sub api_post {
334 16     16 0 77 my ( $self,$path,$params ) = @_;
335 16         42 return $self->_api_request( 'POST',$path,$params );
336             }
337              
338             sub api_put {
339 3     3 0 11 my ( $self,$path,$params ) = @_;
340 3         12 return $self->_api_request( 'PUT',$path,$params );
341             }
342              
343             sub _api_request {
344 60     60   138 my ( $self,$method,$path,$params ) = @_;
345              
346 60         260 my $ua = LWP::UserAgent->new;
347 60         23844 $ua->agent( $self->user_agent );
348              
349 60 100       5024 my $req = HTTP::Request->new(
350             # passing through the absolute URL means we don't build it
351             $method => my $uri = $path =~ /^http/
352             ? $path : join( '/',$self->base_url . $self->api_path . $path ),
353             );
354              
355 60 50       31801 say STDERR "GOCARDLESS -> $uri" if $ENV{GOCARDLESS_DEBUG};
356              
357 60         384 $req->header( 'Authorization' => "Bearer " . $self->token );
358 60         3606 $req->header( 'Accept' => 'application/json' );
359              
360 60 100       3971 if ( $self->api_version > 1 ) {
361             # pegged to a specific version for this library and not user controlled
362             # https://developer.gocardless.com/api-reference/#making-requests-versions
363 27         266 $req->header( 'GoCardless-Version' => '2015-07-06' );
364             }
365              
366 60 100       2043 if ( $method =~ /POST|PUT/ ) {
367 19 100       351 if ( $self->api_version > 1 ) {
368 13         130 $req->content_type( 'application/json' );
369 13         267 my $json;
370 13 100       177 $json = JSON->new->utf8->canonical->encode( $params ) if $params;
371 13 100       87 $req->content( $json ) if $json;
372 13 100       229 $req->header( 'Content-Length' => 0 ) if ! $json; # always have a content length
373 13 50 33     178 say STDERR "GOCARDLESS -> $json" if $ENV{GOCARDLESS_DEBUG} && $json;
374             } else {
375 6         64 $req->content_type( 'application/x-www-form-urlencoded' );
376 6         129 $req->content( my $normalize_params = $self->normalize_params( $params ) );
377 6 50 33     180 say STDERR "GOCARDLESS -> $normalize_params" if $ENV{GOCARDLESS_DEBUG} && $normalize_params;
378             }
379             }
380              
381 60         214 my $res = $ua->request( $req );
382              
383 60 50       460 if ( $res->is_success ) {
384 60 50       4307 say STDERR "GOCARDLESS <- " . $res->content if $ENV{GOCARDLESS_DEBUG};
385 60         583 my $data = JSON->new->canonical->decode( $res->content );
386 60         5540 my $links = $res->header( 'link' );
387 60         3968 my $info = $res->header( 'x-pagination' );
388 60 100       4418 return wantarray ? ( $data,$links,$info ) : $data;
389             }
390             else {
391 0           Business::GoCardless::Exception->throw({
392             message => $res->content,
393             code => $res->code,
394             response => $res->status_line,
395             });
396             }
397             }
398              
399             =head1 AUTHOR
400              
401             Lee Johnson - C
402              
403             This library is free software; you can redistribute it and/or modify it under
404             the same terms as Perl itself. If you would like to contribute documentation,
405             features, bug fixes, or anything else then please raise an issue / pull request:
406              
407             https://github.com/Humanstate/business-gocardless
408              
409             =cut
410              
411             1;
412              
413             # vim: ts=4:sw=4:et