Skip to content

Commit

Permalink
handle legacy webhooks in the ::Pro ->webhook code
Browse files Browse the repository at this point in the history
GoCardless will continue to return legacy style webhooks for Basic API
transactions made before migration to the Pro API, so we need to be able
to handle both in the ::Pro code

add some checks and if we get a legacy webhook return that from the Pro
->webhook method and add some methods on the Webhook objects to allow
users to easily figure out if the Webhook is a legacy webhook or not

add tests to cover above changes, bump VERSION and Changes for CPAN
  • Loading branch information
leejo committed Sep 12, 2017
1 parent 78e0866 commit ae9da8e
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 22 deletions.
3 changes: 3 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Revision history for Business-GoCardless

0.26 2017-09-12
- Automatically handle legacy webhooks in the ::Pro ->webhook code

0.25 2017-09-05
- Add missing attributes to Payout class for Pro API (GH #11)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ set of modules

# VERSION

0.24
0.26

# DESCRIPTION

Expand Down
4 changes: 2 additions & 2 deletions lib/Business/GoCardless.pm
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ set of modules
=head1 VERSION
0.25
0.26
=head1 DESCRIPTION
Expand Down Expand Up @@ -53,7 +53,7 @@ use Carp qw/ confess /;
use Business::GoCardless::Client;
use Business::GoCardless::Webhook;

$Business::GoCardless::VERSION = '0.25';
$Business::GoCardless::VERSION = '0.26';

has api_version => (
is => 'ro',
Expand Down
19 changes: 18 additions & 1 deletion lib/Business/GoCardless/Pro.pm
Original file line number Diff line number Diff line change
Expand Up @@ -267,18 +267,35 @@ GoCardless webhook:
my $Webhook = $GoCardless->webhook( $json_data,$signature );
Note that GoCardless may continue to send old style webhooks even after you have
migrated from the Basic to the Pro API, so to handle this the logic in this method
will check the payload and if it is a legacy webhook will return a legacy Webhook
object. You can check this like so:
if ( $Webhook->is_legacy ) {
# process webhook using older legacy code
...
} else {
# process webhook using new style code
...
}
=cut

sub webhook {
my ( $self,$data,$signature ) = @_;

return Business::GoCardless::Webhook::V2->new(
my $webhook = Business::GoCardless::Webhook::V2->new(
client => $self->client,
json => $data,
# load ordering handled by setting _signature rather than signature
# signature will be set in the json trigger
_signature => $signature,
);

return $webhook->has_legacy_data
? $webhook->legacy_webhook
: $webhook;
}

sub _list {
Expand Down
21 changes: 21 additions & 0 deletions lib/Business/GoCardless/Upgrading.pod
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ information. Also see the C<Mandate restrictions> section in the GoCardless upgr
(L<https://support.gocardless.com/hc/en-us/articles/115000451505-Guide-to-upgrading>) as
there are some considerations depending on the mandate type.

=head1 What about legacy webhooks

Note that GoCardless may continue to send old style webhooks even after you have
migrated from the Basic to the Pro API, so to handle this the V2 Webhook object has
logic to check this:

my $Webhook = $GoCardless->webhook( $json_data,$signature );

if ( $Webhook->is_legacy ) {
# process webhook using older legacy code
...
} else {
# process webhook using new style code
...
}

See the docs for L<Business::GoCardless::Webhook::V2> and
L<Business::GoCardless::Webhook>. Note that to support this you will also need to
make sure either you set the value of $ENV{'GOCARDLESS_APP_SECRET'} to your previous
Basic API app_secret or your new webhook_secret is the same as your old app_secret.

=head1 AUTHOR

Lee Johnson - C<leejo@cpan.org>
Expand Down
12 changes: 12 additions & 0 deletions lib/Business/GoCardless/Webhook.pm
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ sub is_bill { return shift->resource_type eq 'bill' }
sub is_pre_authorization { return shift->resource_type eq 'pre_authorization' }
sub is_subscription { return shift->resource_type eq 'subscription' }

=head2 is_legacy
See if the webhook is a legacy (Basic API) webhook
if ( $Webhook->is_legacy ) {
...
}
=cut

sub is_legacy { 1 }

=head1 CONFIRMING WEBHOOKS
According to the gocardless API docs you should respond once the signature of the
Expand Down
71 changes: 53 additions & 18 deletions lib/Business/GoCardless/Webhook/V2.pm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extends 'Business::GoCardless::Webhook';
with 'Business::GoCardless::Utils';

use JSON ();
use Business::GoCardless::Client;
use Business::GoCardless::Exception;
use Business::GoCardless::Webhook::Event;

Expand All @@ -30,13 +31,17 @@ use Business::GoCardless::Webhook::Event;
json
events
signature
has_legacy_data
=cut

has [ qw/
events
signature
_signature
has_legacy_data
legacy_webhook
/ ] => (
is => 'rw',
clearer => 1,
Expand Down Expand Up @@ -73,32 +78,62 @@ has json => (
});
};

# coerce the events into objects
$self->events([
map { Business::GoCardless::Webhook::Event->new(
client => $self->client,
%{ $_ }
) }
@{ $params->{events} }
]);
if ( $params->{payload} ) {

$self->signature( $self->_signature ) if ! $self->signature;
# this is a legacy API webhook
$self->has_legacy_data( 1 );

if ( ! $self->signature_valid(
$json,$self->client->webhook_secret,$self->signature
) ) {
$self->clear_events;
$self->clear_signature;
my $LegacyWebhook = Business::GoCardless::Webhook->new(
json => $json,
client => Business::GoCardless::Client->new(
app_secret => $self->client->webhook_secret, # bad assumption?
%{ $self->client },
),
);

Business::GoCardless::Exception->throw({
message => "Invalid signature for webhook",
});
}
$self->legacy_webhook( $LegacyWebhook );

} else {

# coerce the events into objects
$self->events([
map { Business::GoCardless::Webhook::Event->new(
client => $self->client,
%{ $_ }
) }
@{ $params->{events} }
]);

$self->signature( $self->_signature ) if ! $self->signature;

if ( ! $self->signature_valid(
$json,$self->client->webhook_secret,$self->signature
) ) {
$self->clear_events;
$self->clear_signature;

Business::GoCardless::Exception->throw({
message => "Invalid signature for webhook",
});
}
}

return $json;
}
);

=head2 is_legacy
See if the webhook is a legacy (Basic API) webhook
if ( $Webhook->is_legacy ) {
...
}
=cut

sub is_legacy { 0 }

=head1 CONFIRMING WEBHOOKS
According to the gocardless API docs you should respond once the signature of the
Expand Down
1 change: 1 addition & 0 deletions t/business/gocardless/webhook.t
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ok( $Webhook->is_bill,'is_bill' );
ok( !$Webhook->is_subscription,'! is_subscription' );
ok( !$Webhook->is_pre_authorization,'! is_pre_authorization' );
is( $Webhook->action,'paid','action' );
ok( $Webhook->is_legacy,'->is_legacy' );

cmp_deeply(
[ $Webhook->resources ],
Expand Down
76 changes: 76 additions & 0 deletions t/business/gocardless/webhook/v2.t
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ can_ok(
/,
);

ok( !$Webhook->is_legacy,'! ->is_legacy' );
ok( my $events = $Webhook->events,'->events' );
cmp_deeply(
$events->[2],
Expand Down Expand Up @@ -72,6 +73,44 @@ throws_ok(

ok( ! $Webhook->resources,' ... and clears resources if bad' );

isa_ok(
$Webhook = Business::GoCardless::Webhook::V2->new(
client => Business::GoCardless::Client->new(
token => 'foo',
webhook_secret => 'baz',
),
json => _json_payload_legacy(),
),
'Business::GoCardless::Webhook::V2'
);

ok( $Webhook->has_legacy_data,'->has_legacy_data' );
isa_ok( $Webhook = $Webhook->legacy_webhook,'Business::GoCardless::Webhook' );
is( $Webhook->resource_type,'bill','resource_type' );
ok( $Webhook->is_bill,'is_bill' );
ok( !$Webhook->is_subscription,'! is_subscription' );
ok( !$Webhook->is_pre_authorization,'! is_pre_authorization' );
is( $Webhook->action,'paid','action' );
ok( $Webhook->is_legacy,'->is_legacy' );

cmp_deeply(
[ $Webhook->resources ],
[ ( bless( {
'amount' => '20.0',
'amount_minus_fees' => '19.8',
'client' => ignore(),
'endpoint' => '/bills/%s',
'id' => ignore(),
'paid_at' => ignore(),
'source_id' => ignore(),
'source_type' => 'subscription',
'status' => 'paid',
'uri' => ignore(),
},'Business::GoCardless::Bill' ) ) x 2
],
'resources'
);

done_testing();

sub _json_payload {
Expand Down Expand Up @@ -135,4 +174,41 @@ sub _json_payload {
}!;
}

sub _json_payload_legacy {

my ( $signature ) = @_;

$signature //= 'ae05e1ab577c728593d2670aa40560e62817e0fa482ff748c27bcad7846eace0';

return qq{{
"payload": {
"resource_type": "bill",
"action": "paid",
"bills": [
{
"id": "AKJ398H8KA",
"status": "paid",
"source_type": "subscription",
"source_id": "KKJ398H8K8",
"amount": "20.0",
"amount_minus_fees": "19.8",
"paid_at": "2011-12-01T12:00:00Z",
"uri": "https://gocardless.com/api/v1/bills/AKJ398H8KA"
},
{
"id": "AKJ398H8KB",
"status": "paid",
"source_type": "subscription",
"source_id": "8AKJ398H78",
"amount": "20.0",
"amount_minus_fees": "19.8",
"paid_at": "2011-12-09T12:00:00Z",
"uri": "https://gocardless.com/api/v1/bills/AKJ398H8KB"
}
],
"signature": "$signature"
}
}};
}

# vim: ts=4:sw=4:et

0 comments on commit ae9da8e

Please sign in to comment.