File Coverage

blib/lib/Business/GoCardless/Pro.pm
Criterion Covered Total %
statement 99 104 95.1
branch 22 40 55.0
condition 5 14 35.7
subroutine 27 28 96.4
pod 15 16 93.7
total 168 202 83.1


line stmt bran cond sub pod time code
1             package Business::GoCardless::Pro;
2              
3             =head1 NAME
4              
5             Business::GoCardless::Pro - Perl library for interacting with the GoCardless Pro v2 API
6             (https://gocardless.com)
7              
8             =head1 DESCRIPTION
9              
10             Module for interacting with the GoCardless Pro (v2) API.
11              
12             B
13             B, as the official API documentation explains in more depth
14             some of the functionality including required / optional parameters for certain
15             methods. L, specifically the docs for the
16             v2 GoCardless API at L.
17              
18             Note that this module is currently incomplete and limited to being a back
19             compatiblity wrapper to allow migration from the v1 (Basic) API. The complete
20             API methods will be added at a later stage (also: patches welcome).
21              
22             Also note this class also currently inherits from L
23             so has all attributes and methods available on that class (some of which may not
24             make sense from the context of the Pro API).
25              
26             =head1 SYNOPSIS
27              
28             The following examples show instantiating the object and getting a resource
29             (Payment in this case) to manipulate. For more examples see the t/004_end_to_end_pro.t
30             script, which can be run against the gocardless sandbox (or even live) endpoint
31             when given the necessary ENV variables.
32              
33             my $GoCardless = Business::GoCardless::Pro->new(
34             token => $your_gocardless_token
35             client_details => {
36             base_url => $gocardless_url, # defaults to https://api.gocardless.com
37             webhook_secret => $secret,
38             },
39             );
40              
41             # create URL for a one off payment
42             my $new_bill_url = $GoCardless->new_bill_url(
43             session_token => 'foo',
44             description => "Test Payment",
45             success_redirect_url => "http://example.com/rflow/confirm?jwt=$jwt",
46             );
47              
48             # having sent the user to the $new_bill_url and them having complete it,
49             # we need to confirm the resource using the details sent by gocardless to
50             # the redirect_uri (https://developer.gocardless.com/api-reference/#redirect-flows-complete-a-redirect-flow)
51             my $Payment = $GoCardless->confirm_resource(
52             redirect_flow_id => $id,
53             type => 'payment', # bill / payment / pre_auth / subscription
54             amount => 0,
55             currency => 'GBP',
56             );
57              
58             # get a specfic Payment
59             $Payment = $GoCardless->payment( $id );
60              
61             # cancel the Payment
62             $Payment->cancel;
63              
64             # too late? maybe we should refund instead (note: needs to be enabled on GoCardless end)
65             $Payment->refund;
66              
67             # or maybe it failed?
68             $Payment->retry if $Payment->failed;
69              
70             # get a list of Payment objects (filter optional: https://developer.gocardless.com/#filtering)
71             my @payments = $GoCardless->payments( %filter );
72              
73             # on any resource object:
74             my %data = $Payment->to_hash;
75             my $json = $Payment->to_json;
76              
77             =head1 PAGINATION
78              
79             Any methods marked as B have a dual interface, when called in list context
80             they will return the first 100 resource objects, when called in scalar context they
81             will return a L object allowing you to iterate
82             through all the objects:
83              
84             # get a list of L objects
85             # (filter optional: https://developer.gocardless.com/#filtering)
86             my @payments = $GoCardless->payments( %filter );
87              
88             # or using the Business::GoCardless::Paginator object:
89             my $Pager = $GoCardless->payments;
90              
91             while( my @payments = $Pager->next ) {
92             foreach my $Payment ( @payments ) {
93             ...
94             }
95             }
96              
97             =cut
98              
99 2     2   215143 use strict;
  2         12  
  2         58  
100 2     2   11 use warnings;
  2         4  
  2         55  
101              
102 2     2   9 use Carp qw/ confess /;
  2         4  
  2         91  
103 2     2   1138 use Moo;
  2         22321  
  2         9  
104             extends 'Business::GoCardless';
105              
106 2     2   3915 use Business::GoCardless::Payment;
  2         6  
  2         80  
107 2     2   918 use Business::GoCardless::RedirectFlow;
  2         7  
  2         86  
108 2     2   937 use Business::GoCardless::Subscription;
  2         8  
  2         81  
109 2     2   1053 use Business::GoCardless::Customer;
  2         7  
  2         68  
110 2     2   986 use Business::GoCardless::Webhook::V2;
  2         8  
  2         79  
111 2     2   14 use Business::GoCardless::Exception;
  2         4  
  2         3346  
112              
113             =head1 ATTRIBUTES
114              
115             All attributes are inherited from L.
116              
117             =cut
118              
119             has api_version => (
120             is => 'ro',
121             required => 0,
122             default => sub { 2 },
123             );
124              
125             =head1 Payment Methods
126              
127             =head2 payment
128              
129             Get an individual payment, returns a L object:
130              
131             my $Payment = $GoCardless->payment( $id );
132              
133             =head2 payments (B)
134              
135             Get a list of Payment objects (%filter is optional)
136              
137             my @payments = $GoCardless->payments( %filter );
138              
139             =head2 create_payment
140              
141             Create a payment with the passed params
142              
143             my $Payment = $GoCardless->create_payment(
144             "amount" => 100,
145             "currency" => "GBP",
146             "charge_date" => "2014-05-19",
147             "reference" => "WINEBOX001",
148             "metadata" => {
149             "order_dispatch_date" => "2014-05-22"
150             },
151             "links" => {
152             "mandate" => "MD123"
153             }
154             );
155              
156             =cut
157              
158             sub payment {
159 1     1 1 3 my ( $self,$id ) = @_;
160 1         9 return $self->_generic_find_obj( $id,'Payment','payments' );
161             }
162              
163             sub payments {
164 2     2 1 6 my ( $self,%filters ) = @_;
165 2         6 return $self->_list( 'payments',\%filters );
166             }
167              
168             sub create_payment {
169 1     1 1 546 my ( $self,%params ) = @_;
170 1         24 my $data = $self->client->api_post( '/payments',{ payments => { %params } } );
171              
172             return Business::GoCardless::Payment->new(
173             client => $self->client,
174 1         27 %{ $data->{payments} }
  1         18  
175             );
176             }
177              
178             =head1 Subscription Methods
179              
180             =head2 subscription
181              
182             Get an individual subscription, returns a L object:
183              
184             my $Subscription = $GoCardless->subscription( $id );
185              
186             =head2 subscriptions (B)
187              
188             Get a list of Subscription objects (%filter is optional)
189              
190             my @subscriptions = $GoCardless->subscriptions( %filter );
191              
192             =cut
193              
194             sub subscription {
195 1     1 1 33308 my ( $self,$id ) = @_;
196 1         6 return $self->_generic_find_obj( $id,'Subscription','subscriptions' );
197             }
198              
199             sub subscriptions {
200 1     1 1 16721 my ( $self,%filters ) = @_;
201 1         5 return $self->_list( 'subscriptions',\%filters );
202             }
203              
204             =head1 RedirectFlow Methods
205              
206             See L for more information on RedirectFlow operations.
207              
208             =head2 pre_authorization
209              
210             Get an individual redirect flow, returns a L object:
211              
212             my $RedirectFlow = $GoCardless->pre_authorization( $id );
213              
214             =head2 pre_authorizations (B)
215              
216             This is meaningless in the v2 API so will throw an exception if called.
217              
218             =cut
219              
220             sub pre_authorization {
221 1     1 1 3942 my ( $self,$id ) = @_;
222 1         5 return $self->_generic_find_obj( $id,'RedirectFlow','redirect_flows' );
223             };
224              
225             sub pre_authorizations {
226 1     1 1 5570 Business::GoCardless::Exception->throw({
227             message => "->pre_authorizations is no longer meaningful in the Pro API",
228             });
229             };
230              
231             =head1 Mandate Methods
232              
233             See L for more information on Mandate operations.
234              
235             =head2 mandate
236              
237             Get an individual mandate, returns a L object:
238              
239             my $Mandate = $GoCardless->mandate( $id );
240              
241             =cut
242              
243             sub mandate {
244 0     0 1 0 my ( $self,$id ) = @_;
245 0         0 return $self->_generic_find_obj( $id,'Mandate','mandates' );
246             }
247              
248             =head1 Customer Methods
249              
250             See L for more information on Customer operations.
251              
252             =head2 customer
253              
254             Get an individual customer, returns a L.
255              
256             my $Customer = $GoCardless->customer;
257              
258             =head2 customers (B)
259              
260             Get a list of L objects.
261              
262             my @customers = $GoCardless->customers;
263              
264             =cut
265              
266             sub customer {
267 1     1 1 14327 my ( $self,$id ) = @_;
268 1         5 return $self->_generic_find_obj( $id,'Customer','customer' );
269             }
270              
271             sub customers {
272 2     2 1 705 my ( $self,%filters ) = @_;
273 2         5 return $self->_list( 'customers',\%filters );
274             }
275              
276             =head1 Webhook Methods
277              
278             See L for more information on Webhook operations.
279              
280             =head2 webhook
281              
282             Get a L object from the data sent to you via a
283             GoCardless webhook:
284              
285             my $Webhook = $GoCardless->webhook( $json_data,$signature );
286              
287             Note that GoCardless may continue to send old style webhooks even after you have
288             migrated from the Basic to the Pro API, so to handle this the logic in this method
289             will check the payload and if it is a legacy webhook will return a legacy Webhook
290             object. You can check this like so:
291              
292             if ( $Webhook->is_legacy ) {
293             # process webhook using older legacy code
294             ...
295             } else {
296             # process webhook using new style code
297             ...
298             }
299              
300             =cut
301              
302             sub webhook {
303 2     2 1 1455 my ( $self,$data,$signature ) = @_;
304              
305 2         50 my $webhook = Business::GoCardless::Webhook::V2->new(
306             client => $self->client,
307             json => $data,
308             # load ordering handled by setting _signature rather than signature
309             # signature will be set in the json trigger
310             _signature => $signature,
311             );
312              
313 1 50       20 return $webhook->has_legacy_data
314             ? $webhook->legacy_webhook
315             : $webhook;
316             }
317              
318             sub _list {
319 5     5   13 my ( $self,$endpoint,$filters ) = @_;
320              
321             my $class = {
322             payments => 'Payment',
323             redirect_flows => 'RedirectFlow',
324             customers => 'Customer',
325             subscriptions => 'Subscription',
326 5         19 }->{ $endpoint };
327              
328 5   50     20 $filters //= {};
329              
330 5         13 my $uri = "/$endpoint";
331              
332 5 100       8 if ( keys( %{ $filters } ) ) {
  5         33  
333 1         19 $uri .= '?' . $self->client->normalize_params( $filters );
334             }
335              
336 5         121 my ( $data,$links,$info ) = $self->client->api_get( $uri );
337              
338 5         16 $class = "Business::GoCardless::$class";
339 9         226 my @objects = map { $class->new( client => $self->client,%{ $_ } ) }
  9         220  
340 5         9 @{ $data->{$endpoint} };
  5         12  
341              
342 5 0       114 return wantarray ? ( @objects ) : Business::GoCardless::Paginator->new(
    50          
343             class => $class,
344             client => $self->client,
345             links => $links,
346             info => $info ? JSON->new->decode( $info ) : {},
347             objects => \@objects,
348             );
349             }
350              
351             ################################################################
352             #
353             # BACK COMPATIBILITY WITH V1 (BASIC) API SECTION FOLLOWS
354             # the Pro version of the API is built on "redirect flows" when
355             # using their hosted pages, so we can make it back compatible
356             #
357             ################################################################
358              
359             =head1 BACK COMPATIBILITY METHODS
360              
361             These methods are provided for those who want to move from the v1 (Basic)
362             API with minimal changes in your application code.
363              
364             =head2 new_bill_url
365              
366             =head2 new_pre_authorization_url
367              
368             =head2 new_subscription_url
369              
370             Return a URL for redirecting the user to to complete a direct debit mandate that
371             will allow you to setup payments.
372              
373             See L
374             for more information.
375              
376             my $url = $GoCardless->new_bill_url(
377              
378             # required
379             session_token => $session_token,
380             success_redirect_url => $success_callback_url,
381              
382             # optional
383             scheme => $direct_debit_scheme
384             description => $description,
385             prefilled_customer => { ... }, # see documentation above
386             links => { ... }, # see documentation above
387             );
388              
389             =cut
390              
391             sub new_bill_url {
392 1     1 1 838 my ( $self,%params ) = @_;
393 1         6 return $self->_redirect_flow_from_legacy_params( 'bill',%params );
394             }
395              
396             sub new_pre_authorization_url {
397 1     1 1 2033 my ( $self,%params ) = @_;
398 1         6 return $self->_redirect_flow_from_legacy_params( 'pre_authorization',%params );
399             }
400              
401             sub new_subscription_url {
402 1     1 1 703 my ( $self,%params ) = @_;
403 1         5 return $self->_redirect_flow_from_legacy_params( 'subscription',%params );
404             }
405              
406             sub _redirect_flow_from_legacy_params {
407 3     3   11 my ( $self,$type,%params ) = @_;
408              
409 3         8 for ( qw/ session_token success_redirect_url / ) {
410 6   33     20 $params{$_} // confess( "$_ is required for new_${type}_url (v2)" );
411             }
412              
413             # we can't just pass through %params as GoCardless will throw an error
414             # if it receives any unknown parameters
415             return $self->client->_new_redirect_flow_url({
416             ( $params{scheme} ? ( scheme => $params{scheme} ) : () ),
417             ( $params{description} // $params{name} ? ( description => $params{description} // $params{name} ) : () ),
418             session_token => $params{session_token},
419             success_redirect_url => $params{success_redirect_url},
420              
421             ( $params{prefilled_customer}
422             ? (
423 0         0 prefilled_customer => { %{ $params{prefilled_customer} } }
424             )
425             : (
426             prefilled_customer => {
427              
428             # only include fields that are defined in the prefilled_customer, otherwise
429             # gocardless will try to assume the user's location and thus the scheme
430             ( $params{user}{billing_address1} ? ( address_line1 => $params{user}{billing_address1} ) : () ),
431             ( $params{user}{billing_address2} ? ( address_line2 => $params{user}{billing_address2} ) : () ),
432             ( $params{user}{billing_address3} ? ( address_line3 => $params{user}{billing_address3} ) : () ),
433             ( $params{user}{billing_town} ? ( city => $params{user}{billing_town} ) : () ),
434             ( $params{user}{billing_postcode} ? ( postal_code => $params{user}{billing_postcode} ) : () ),
435              
436             ( $params{user}{last_name} ? ( family_name => $params{user}{last_name} ) : () ),
437             ( $params{user}{first_name} ? ( given_name => $params{user}{first_name} ) : () ),
438              
439             (
440 27 50       149 map { ( $params{user}{$_} ? ( $_ => $params{user}{$_} ) : () ) }
441             qw/ city company_name country_code email given_name language region swedish_identity_number postal_code /
442             ),
443             },
444             )
445             ),
446              
447             (
448             $params{links}{creditor}
449             ? ( links => { creditor => $params{links}{creditor} } )
450 3 50 33     76 : ()
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
451             ),
452             });
453             }
454              
455             =head2 confirm_resource
456              
457             After a user completes the form in the redirect flow (using a URL generated from
458             one of the new_.*?url methods above) GoCardless will redirect them back to the
459             success_redirect_url with a redirect_flow_id, at which point you can call this
460             method to confirm the mandate and set up a one off payment, subscription, etc
461              
462             The object returned will depend on the C parameter passed to the method
463              
464             my $Payment = $GoCardless->confirm_resource(
465              
466             # required
467             redirect_flow_id => $redirect_flow_id,
468             type => 'payment', # one of bill, payment, pre_auth, subscription
469             amount => 0,
470             currency => 'GBP',
471              
472             # required in the case of type being "subscription"
473             interval_unit =>
474             interval =>
475             start_at =>
476             );
477              
478             =cut
479              
480             # BACK COMPATIBILITY method, in which we (try to) return the correct object for
481             # the required type as this is how the v1 API works
482             sub confirm_resource {
483 3     3 1 15 my ( $self,%params ) = @_;
484              
485 3         7 for ( qw/ redirect_flow_id type amount currency / ) {
486 12   33     32 $params{$_} // confess( "$_ is required for confirm_resource (v2)" );
487             }
488              
489 3         5 my $r_flow_id = $params{redirect_flow_id};
490 3         6 my $type = $params{type};
491 3         6 my $amount = $params{amount};
492 3         4 my $currency = $params{currency};
493 3         6 my $int_unit = $params{interval_unit};
494 3         5 my $interval = $params{interval};
495 3         5 my $start_at = $params{start_at};
496              
497 3 50       72 if ( my $RedirectFlow = $self->client->_confirm_redirect_flow( $r_flow_id ) ) {
498              
499             # now we have a confirmed redirect flow object we can create the
500             # payment, subscription, whatever
501 3 100       37 if ( $type =~ /bill|payment/i ) {
    100          
    50          
502              
503             # Bill -> Payment
504             my $post_data = {
505             payments => {
506             amount => $amount,
507             currency => $currency,
508             links => {
509             mandate => $RedirectFlow->links->{mandate},
510             },
511             },
512 1         7 };
513              
514 1         19 my $data = $self->client->api_post( "/payments",$post_data );
515              
516             return Business::GoCardless::Payment->new(
517             client => $self->client,
518 1         24 %{ $data->{payments} },
  1         32  
519             );
520              
521             } elsif ( $type =~ /pre_auth/i ) {
522              
523             # a pre authorization is, effectively, a redirect flow
524 1         10 return $RedirectFlow;
525              
526             } elsif ( $type =~ /subscription/i ) {
527              
528             my $post_data = {
529             subscriptions => {
530             amount => $amount,
531             currency => $currency,
532             interval_unit => $int_unit,
533             interval => $interval,
534             start_date => $start_at,
535             links => {
536             mandate => $RedirectFlow->links->{mandate},
537             },
538             },
539 1         11 };
540              
541 1         20 my $data = $self->client->api_post( "/subscriptions",$post_data );
542              
543             return Business::GoCardless::Subscription->new(
544             client => $self->client,
545 1         26 %{ $data->{subscriptions} },
  1         32  
546             );
547             }
548              
549             # don't know what to do, complain
550             Business::GoCardless::Exception->throw({
551 0         0 message => "Unknown type ($type) in ->confirm_resource",
552             });
553             }
554              
555             Business::GoCardless::Exception->throw({
556 0         0 message => "Failed to get RedirectFlow for $r_flow_id",
557             });
558             }
559              
560 1     1 0 12 sub users { shift->customers( @_ ); }
561              
562             =head1 SEE ALSO
563              
564             L
565              
566             L
567              
568             L
569              
570             L
571              
572             L
573              
574             L
575              
576             L
577              
578             =head1 AUTHOR
579              
580             Lee Johnson - C
581              
582             =head1 LICENSE
583              
584             This library is free software; you can redistribute it and/or modify it under
585             the same terms as Perl itself. If you would like to contribute documentation,
586             features, bug fixes, or anything else then please raise an issue / pull request:
587              
588             https://github.com/Humanstate/business-gocardless
589              
590             =cut
591              
592             1;
593              
594             # vim: ts=4:sw=4:et