File Coverage

blib/lib/Business/GoCardless/Pro.pm
Criterion Covered Total %
statement 101 106 95.2
branch 24 42 57.1
condition 7 18 38.8
subroutine 28 29 96.5
pod 16 17 94.1
total 176 212 83.0


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   638996 use strict;
  2         3  
  2         65  
100 2     2   8 use warnings;
  2         8  
  2         103  
101              
102 2     2   8 use Carp qw/ confess /;
  2         2  
  2         104  
103 2     2   1169 use Moo;
  2         14199  
  2         12  
104             extends 'Business::GoCardless';
105              
106 2     2   4048 use Business::GoCardless::Payment;
  2         19  
  2         113  
107 2     2   1474 use Business::GoCardless::RedirectFlow;
  2         14  
  2         169  
108 2     2   1777 use Business::GoCardless::Subscription;
  2         11  
  2         161  
109 2     2   1260 use Business::GoCardless::Customer;
  2         11  
  2         130  
110 2     2   1375 use Business::GoCardless::Webhook::V2;
  2         7  
  2         77  
111 2     2   14 use Business::GoCardless::Exception;
  2         4  
  2         2784  
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         26 return $self->_generic_find_obj( $id,'Payment','payments' );
161             }
162              
163             sub payments {
164 2     2 1 7 my ( $self,%filters ) = @_;
165 2         11 return $self->_list( 'payments',\%filters );
166             }
167              
168             sub create_payment {
169 1     1 1 603 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         30 %{ $data->{payments} }
  1         25  
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 25491 my ( $self,$id ) = @_;
196 1         29 return $self->_generic_find_obj( $id,'Subscription','subscriptions' );
197             }
198              
199             sub subscriptions {
200 1     1 1 12772 my ( $self,%filters ) = @_;
201 1         7 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 3295 my ( $self,$id ) = @_;
222 1         10 return $self->_generic_find_obj( $id,'RedirectFlow','redirect_flows' );
223             };
224              
225             sub pre_authorizations {
226 1     1 1 5182 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 10571 my ( $self,$id ) = @_;
268 1         8 return $self->_generic_find_obj( $id,'Customer','customer' );
269             }
270              
271             sub customers {
272 2     2 1 877 my ( $self,%filters ) = @_;
273 2         9 return $self->_list( 'customers',\%filters );
274             }
275              
276             =head1 Webhook Methods
277              
278             See L for more information on Webhook operations.
279              
280             =head2 webhooks (B)
281              
282             Get a list of L objects.
283              
284             my @webhooks = $GoCardless->webhooks
285              
286             =cut
287              
288             sub webhooks {
289 1     1 1 905 my ( $self,%filters ) = @_;
290 1         5 return $self->_list( 'webhooks',\%filters );
291             }
292              
293             =head2 webhook
294              
295             Get a L object from the data sent to you via a
296             GoCardless webhook:
297              
298             my $Webhook = $GoCardless->webhook( $json_data,$signature );
299              
300             Note that GoCardless may continue to send old style webhooks even after you have
301             migrated from the Basic to the Pro API, so to handle this the logic in this method
302             will check the payload and if it is a legacy webhook will return a legacy Webhook
303             object. You can check this like so:
304              
305             if ( $Webhook->is_legacy ) {
306             # process webhook using older legacy code
307             ...
308             } else {
309             # process webhook using new style code
310             ...
311             }
312              
313             =cut
314              
315             sub webhook {
316 2     2 1 2691 my ( $self,$data,$signature ) = @_;
317              
318 2         51 my $webhook = Business::GoCardless::Webhook::V2->new(
319             client => $self->client,
320             json => $data,
321             # load ordering handled by setting _signature rather than signature
322             # signature will be set in the json trigger
323             _signature => $signature,
324             );
325              
326 1 50       10 return $webhook->has_legacy_data
327             ? $webhook->legacy_webhook
328             : $webhook;
329             }
330              
331             # payout methods are in the SUPER class
332             =head1 Payout Methods
333              
334             See L for more information on Payout operations.
335              
336             =head2 payouts (B)
337              
338             Get a list of L objects.
339              
340             my @payouts = $GoCardless->payouts
341              
342             =head2 payout
343              
344             Get an individual payout, returns a L object:
345              
346             my $Payout = $GoCardless->payout( $id );
347              
348             =cut
349              
350             sub _list {
351 6     6   21 my ( $self,$endpoint,$filters ) = @_;
352              
353             my $class = {
354             payments => 'Payment',
355             redirect_flows => 'RedirectFlow',
356             customers => 'Customer',
357             subscriptions => 'Subscription',
358             webhooks => 'Webhook::V2',
359             payouts => 'Payout',
360 6         50 }->{ $endpoint };
361              
362 6   50     44 $filters //= {};
363              
364 6         15 my $uri = "/$endpoint";
365              
366 6 100       9 if ( keys( %{ $filters } ) ) {
  6         20  
367 1         13 $uri .= '?' . $self->client->normalize_params( $filters );
368             }
369              
370 6         183 my ( $data,$links,$info ) = $self->client->api_get( $uri );
371              
372 6         16 $class = "Business::GoCardless::$class";
373             my @objects = map { $class->new(
374             client => $self->client,
375              
376             # webhooks come back with stringified JSON
377             # so we need to further decode that
378             $endpoint eq 'webhooks'
379             ? (
380             json => $_->{request_body},
381             # load ordering handled by setting _signature rather than signature
382             # signature will be set in the json trigger
383             _signature => $_->{request_headers}{'Webhook-Signature'}
384             )
385 10 100       230 : ( %{ $_ } )
  9         201  
386 6         11 ); } @{ $data->{$endpoint} };
  6         20  
387              
388 6 0       122 return wantarray ? ( @objects ) : Business::GoCardless::Paginator->new(
    50          
389             class => $class,
390             client => $self->client,
391             links => $links,
392             info => $info ? JSON->new->decode( $info ) : {},
393             objects => \@objects,
394             );
395             }
396              
397             ################################################################
398             #
399             # BACK COMPATIBILITY WITH V1 (BASIC) API SECTION FOLLOWS
400             # the Pro version of the API is built on "redirect flows" when
401             # using their hosted pages, so we can make it back compatible
402             #
403             ################################################################
404              
405             =head1 BACK COMPATIBILITY METHODS
406              
407             These methods are provided for those who want to move from the v1 (Basic)
408             API with minimal changes in your application code.
409              
410             =head2 new_bill_url
411              
412             =head2 new_pre_authorization_url
413              
414             =head2 new_subscription_url
415              
416             Return a URL for redirecting the user to to complete a direct debit mandate that
417             will allow you to setup payments.
418              
419             See L
420             for more information.
421              
422             my $url = $GoCardless->new_bill_url(
423              
424             # required
425             session_token => $session_token,
426             success_redirect_url => $success_callback_url,
427              
428             # optional
429             scheme => $direct_debit_scheme
430             description => $description,
431             prefilled_customer => { ... }, # see documentation above
432             links => { ... }, # see documentation above
433             );
434              
435             =cut
436              
437             sub new_bill_url {
438 1     1 1 1209 my ( $self,%params ) = @_;
439 1         8 return $self->_redirect_flow_from_legacy_params( 'bill',%params );
440             }
441              
442             sub new_pre_authorization_url {
443 1     1 1 5157 my ( $self,%params ) = @_;
444 1         6 return $self->_redirect_flow_from_legacy_params( 'pre_authorization',%params );
445             }
446              
447             sub new_subscription_url {
448 1     1 1 998 my ( $self,%params ) = @_;
449 1         7 return $self->_redirect_flow_from_legacy_params( 'subscription',%params );
450             }
451              
452             sub _redirect_flow_from_legacy_params {
453 3     3   14 my ( $self,$type,%params ) = @_;
454              
455 3         23 for ( qw/ session_token success_redirect_url / ) {
456 6   33     23 $params{$_} // confess( "$_ is required for new_${type}_url (v2)" );
457             }
458              
459             # we can't just pass through %params as GoCardless will throw an error
460             # if it receives any unknown parameters
461             return $self->client->_new_redirect_flow_url({
462             ( $params{scheme} ? ( scheme => $params{scheme} ) : () ),
463             ( $params{description} // $params{name} ? ( description => $params{description} // $params{name} ) : () ),
464             session_token => $params{session_token},
465             success_redirect_url => $params{success_redirect_url},
466              
467             ( $params{prefilled_customer}
468             ? (
469 0         0 prefilled_customer => { %{ $params{prefilled_customer} } }
470             )
471             : (
472             prefilled_customer => {
473              
474             # only include fields that are defined in the prefilled_customer, otherwise
475             # gocardless will try to assume the user's location and thus the scheme
476             ( $params{user}{billing_address1} ? ( address_line1 => $params{user}{billing_address1} ) : () ),
477             ( $params{user}{billing_address2} ? ( address_line2 => $params{user}{billing_address2} ) : () ),
478             ( $params{user}{billing_address3} ? ( address_line3 => $params{user}{billing_address3} ) : () ),
479             ( $params{user}{billing_town} ? ( city => $params{user}{billing_town} ) : () ),
480             ( $params{user}{billing_postcode} ? ( postal_code => $params{user}{billing_postcode} ) : () ),
481              
482             ( $params{user}{last_name} ? ( family_name => $params{user}{last_name} ) : () ),
483             ( $params{user}{first_name} ? ( given_name => $params{user}{first_name} ) : () ),
484              
485             (
486 27 50       154 map { ( $params{user}{$_} ? ( $_ => $params{user}{$_} ) : () ) }
487             qw/ city company_name country_code email given_name language region swedish_identity_number postal_code /
488             ),
489             },
490             )
491             ),
492              
493             (
494             $params{links}{creditor}
495             ? ( links => { creditor => $params{links}{creditor} } )
496 3 50 33     92 : ()
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
497             ),
498             });
499             }
500              
501             =head2 confirm_resource
502              
503             After a user completes the form in the redirect flow (using a URL generated from
504             one of the new_.*?url methods above) GoCardless will redirect them back to the
505             success_redirect_url with a redirect_flow_id, at which point you can call this
506             method to confirm the mandate and set up a one off payment, subscription, etc
507              
508             The object returned will depend on the C parameter passed to the method
509              
510             my $Payment = $GoCardless->confirm_resource(
511              
512             # required
513             redirect_flow_id => $redirect_flow_id,
514             type => 'payment', # one of bill, payment, pre_auth, subscription
515             amount => 0,
516             currency => 'GBP',
517              
518             # required in the case of type being "subscription"
519             interval_unit =>
520             interval =>
521             start_at =>
522             );
523              
524             =cut
525              
526             # BACK COMPATIBILITY method, in which we (try to) return the correct object for
527             # the required type as this is how the v1 API works
528             sub confirm_resource {
529 3     3 1 24 my ( $self,%params ) = @_;
530              
531 3         9 for ( qw/ redirect_flow_id type amount currency / ) {
532 12   33     30 $params{$_} // confess( "$_ is required for confirm_resource (v2)" );
533             }
534              
535 3         7 my $r_flow_id = $params{redirect_flow_id};
536 3         7 my $type = $params{type};
537 3         7 my $amount = $params{amount};
538 3         5 my $currency = $params{currency};
539 3         5 my $int_unit = $params{interval_unit};
540 3         6 my $interval = $params{interval};
541 3         5 my $start_at = $params{start_at};
542              
543 3 50       74 if ( my $RedirectFlow = $self->client->_confirm_redirect_flow( $r_flow_id ) ) {
544              
545             # now we have a confirmed redirect flow object we can create the
546             # payment, subscription, whatever
547 3 100       35 if ( $type =~ /bill|payment/i ) {
    100          
    50          
548              
549             # Bill -> Payment
550 1   50     23 my $post_data = {
551             payments => {
552             amount => $amount,
553             currency => $currency,
554             links => $RedirectFlow->links // {},
555             },
556             };
557              
558 1         18 my $data = $self->client->api_post( "/payments",$post_data );
559              
560             return Business::GoCardless::Payment->new(
561             client => $self->client,
562 1         25 %{ $data->{payments} },
  1         24  
563             );
564              
565             } elsif ( $type =~ /pre_auth/i ) {
566              
567             # a pre authorization is, effectively, a redirect flow
568 1         8 return $RedirectFlow;
569              
570             } elsif ( $type =~ /subscription/i ) {
571              
572 1   50     12 my $post_data = {
573             subscriptions => {
574             amount => $amount,
575             currency => $currency,
576             interval_unit => $int_unit,
577             interval => $interval,
578             start_date => $start_at,
579             links => $RedirectFlow->links // {},
580             },
581             };
582              
583 1         15 my $data = $self->client->api_post( "/subscriptions",$post_data );
584              
585             return Business::GoCardless::Subscription->new(
586             client => $self->client,
587 1         23 %{ $data->{subscriptions} },
  1         19  
588             );
589             }
590              
591             # don't know what to do, complain
592             Business::GoCardless::Exception->throw({
593 0         0 message => "Unknown type ($type) in ->confirm_resource",
594             });
595             }
596              
597             Business::GoCardless::Exception->throw({
598 0         0 message => "Failed to get RedirectFlow for $r_flow_id",
599             });
600             }
601              
602 1     1 0 8 sub users { shift->customers( @_ ); }
603              
604             =head1 SEE ALSO
605              
606             L
607              
608             L
609              
610             L
611              
612             L
613              
614             L
615              
616             L
617              
618             L
619              
620             L
621              
622             =head1 AUTHOR
623              
624             Lee Johnson - C
625              
626             =head1 LICENSE
627              
628             This library is free software; you can redistribute it and/or modify it under
629             the same terms as Perl itself. If you would like to contribute documentation,
630             features, bug fixes, or anything else then please raise an issue / pull request:
631              
632             https://github.com/Humanstate/business-gocardless
633              
634             =cut
635              
636             1;
637              
638             # vim: ts=4:sw=4:et