The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Net::API::Stripe::WebHook::Apache - An Apache handler for Stripe Web Hook

SYNOPSIS

package My::Module::WebHook;
BEGIN
{
    use strict;
    use curry;
    use parent qw( Net::API::Stripe::WebHook::Apache );
};

sub init
{
    my $self = shift( @_ );
    $self->{event_handlers} =
    {
    account => { updated => $self->curry::account_updated,
    # A fallback method for all other event types not purposely defined here
    fallback => $self->curry::fallback,
    # etc..
    # See here for a list of Stripe events:
    # https://stripe.com/docs/api/events/types
    };
    # See https://stripe.com/docs/webhooks#signatures
    # For example:
    $self->{signing_secret} = 'whsec_fake1234567890mnbvcxz';
    # Set up Net::API::Stripe object
    my $stripe = Net::API::Stripe->new( $hash_ref_of_params ) || 
        return( $self->error( "Unable to create a Net::API::Stripe object: ", Net::API::Stripe->error ) );
    # Set the Stripe object that is needed later in the handler and validate_webhook methods
    $self->stripe( $stripe );
    # Set this to true if you want to test your application and not check the webhook caller's IP, 
    # which should normally be Stripe's ip
    $self->{ignore_ip} = 0;
    return( $self );
}

VERSION

v0.100.2

DESCRIPTION

This is the module to handle Stripe Web Hooks using Apache/mod_perl configuration

The way this works is you create your own module which inherits from this one. You override the init method in which you create the object property event_handler with an hash value with keys corresponding to the types of Stripe events. A dot in the Stripe event type corresponds to a sub hash in our event_handler definition.

You can set up your endpoint on Stripe dashboard at: https://dashboard.stripe.com/webhooks or do it via the api with "webhook" in Net::API::Stripe.

See also the list of all possible Stripe endpoints

For example:

sub init
{
    my $self = shift( @_ );
    $self->SUPER::init( @_ );
    $self->{event_handler} = 
    {
    account =>
        {
            updated => $self->curry::account_updated,
            application => 
            {
                authorized => $self->curry:account_application_authorised,
            },
        },
    charge => 
        {
            captured => $self->curry::charge_captured,
            dispute =>
            {
                created => $self->curry::charge_dispute_created,
            }
        },
    customer =>
        {
            created => $self->curry::customer_created,
        },
    # A fallback method for all other event types not purposely defined here
    fallback => $self->curry::fallback,
    ## And so on....
    };
}

Nota bene: here in this example above, I use curry which is a very handy module.

In a nutshell: when an http query is made by Stripe on your webhook, Apache will trigger the method handler, which will check and create the object environment, and call the method event_handler provided by this package to find out the sub in charge of this Stripe event type, as defined in your map event_handlers. Your own method is then called and you can do whatever you want with Stripe data.

It is also worth mentioning that Stripe requires ssl to be enabled to perform webhook queries.

CONFIGURATION

Your Apache VirtualHost configuration would look something like this, assuming your module package is My::Module::WebHook

<VirtualHost *:443>
    ServerName example.com:443
    ServerAdmin www@example.com
    DocumentRoot /home/john/example.com
    DirectoryIndex "index.html" "index.php"
    CustomLog "${APACHE_LOG_DIR}/example.com-access.log" combined
    ErrorLog "${APACHE_LOG_DIR}/example.com-error.log"
    LogLevel warn
    <Directory "/home/john/example.com">
        Options All +MultiViews -ExecCGI -Indexes +Includes +FollowSymLinks
        AllowOverride All
    </Directory>
    ScriptAlias "/cgi-bin/"     "/home/john/example.com/cgi-bin/"
    <Directory "/home/john/example.com/cgi-bin/">
        Options All +Includes +ExecCGI -Indexes -MultiViews
        AllowOverride All
        SetHandler cgi-script
        AcceptPathInfo On
        Require all granted
    </Directory>
    <IfModule mod_perl.c>
        PerlOptions     +GlobalRequest
        PerlPassEnv     MOD_PERL
        PerlPassEnv     PATH_INFO
        PerlModule      Apache2::Request
        <Perl>
        unshift( @INC, "/home/john/lib" );
        </Perl>
        <Location /hook>
            SetHandler      perl-script
            ## Switch it back to modperl once the soft is stable
            # SetHandler        modperl
            PerlSendHeader      On
            PerlSetupEnv        On
            PerlOptions         +GlobalRequest
            # PerlResponseHandler   Net::API::Stripe::WebHook::Apache
            PerlResponseHandler My::Module::WebHook
            Options             +ExecCGI
            Order allow,deny
            Allow from all
        </Location>
    </IfModule>

    SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
</Virtualhost>

The key part is the line with PerlResponseHandler and value Net::API::Stripe::WebHook::Apache. This will tell Apache/mod_perl that our module will handle all http request for this particular location.

So, if we get an incoming event from Stripe at https://example.com/hook/d18bbab7-e537-4dba-9a1f-dd6cc70ea6c1, we receive d18bbab7-e537-4dba-9a1f-dd6cc70ea6c1 as part of the path info, and we call validate_webhook() to validate it before processing the event incoming packet.

Apache will call our special method handler(), which will invoque validate_webhook() that should be implemented in your module if you want to do further checks, and which must return either a hash reference containing the payload or false. For example:

sub validate_webhook
{
    my $self = shift( @_ );
    # Receive the hash reference containing the properties: payload (hash ref), headers (hash ref), remote_addr and remote_host
    my $hash = $self->SUPER::validate_webhook || return;
    # Do further check and make sure to return an Exception object that has the code and message method
    # Example:
    my $payload = $hash->{payload};
    return( $self->error({ code => Apache2::Const::HTTP_BAD_REQUEST, "Something is off with the payload received" }) ) if( !$payload->{object} );
    # Make sure to return the hash reference
    return( $hash );
}

Upon successful return from validate_webhook(), handler will create a new object from your class such as $class->new()

It will then call methods request providing it with the Net::API::REST::Request object and call the method response providing it with the Net::API::REST::Response object.

It will then collect the Stripe event data and create a Net::API::Stripe::Event object with it.

It will then call "event_handler" with the Stripe event type as the sole argument (See https://stripe.com/docs/api/events/types for a list of all possible Stripe events), and will get in return either a code reference to the handler for this event type, or an empty string if no event handler was set for this event type or undef() in scalar context or an empty list in list context if there was an error.

Finally it will call the referenced subroutine returned by "event_handler" passing it the Net::API::Stripe::Event object.

If your event handler returns undef, Net::API::Stripe::WebHook::Apache will return a server error. If your event handler returns either 1 or Apache2::Const::HTTP_OK, Net::API::Stripe::WebHook::Apache will return an OK code, and for anything else, Net::API::Stripe::WebHook::Apache will return the code as returned by your handler.

This means you can use Apache2::Const values as return code of your event handler.

CONSTRUCTOR

new

Takes an hash or hash reference.

Creates a new Net::API::Stripe::WebHook::Apache object. This should be overriden by your own package.

Here are the object properties recognised and used in this module:

debug

Integer. When set to a true value, this will produce debugging output on STDERR or http server log.

event_handlers

An hash reference of event type to subroutine reference. See example above

ignore_ip

When set to true, "webhook_validate_caller_ip" in Net::API::Stripe will not check fo the validity of the webhook caller's ip.

signing_secret

String. This is the secret key used by Stripe to sign the webhook payload and used by us to check the payload received is authentic. See your Stripe dashboard

stripe

The Net::API::Stripe object instantiated. This is used in "handler" and "validate_webhook" methods

handler

This is called by Apache/mod_perl upon incoming http request.

It takes your module class and the Apache2::Request object as arguments

Your module class is the one defined in the Apache Virtual Hsot configuration with PerlResponseHandler

METHODS

handler

This is called by Apache with an Apache2::Request object and returns an Apache2::Constant code such as 200

event_handler

Provided with a Stripe event type such as customer.subscription.updated, this checks for a suitable handler (set up in your init method), then return the handler code reference.

event_handlers

Set/get an hash reference of Stripe event type to handling methods.

Returns an hash reference.

stripe

Set/get a Net::API::Stripe object. It returns the current value.

validate_webhook

This checks the webhook call is valid and returns true upon success or false upon failure.

You want to override this like this:

sub validate_webhook
{
    my $self = shift( @_ );
    ## Get the basic checks done by our default validate_webhook method
    $self->SUPER::validate_webhook || return;
    # Add checks of your own
    # And if all is ok, return true, or false otherwise
    return( 1 );
}

HISTORY

v0.1

Initial version

AUTHOR

Jacques Deguest <jack@deguest.jp>

SEE ALSO

Stripe API documentation: https://stripe.com/docs/api/events/types

Net::API::REST, Apache2

ModPerl::Registry, ModPerl::PerlRun, http://perl.apache.org/

COPYRIGHT & LICENSE

Copyright (c) 2019-2020 DEGUEST Pte. Ltd.

You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.