NAME

Business::Payr::Webhook - class for verifying and parsing Payr webhook notifications.

SYNOPSIS

use Business::Payr::Webhook;

# In your webhook endpoint handler:
my $Webhook = Business::Payr::Webhook->new(
    body      => $raw_request_body,        # raw POST body string
    signature => $x_payr_signature_header, # X-Payr-Signature header value
    secret    => $webhook_secret,          # your Payr webhook secret
);

# Signature is verified during construction - an exception is thrown
# if the signature does not match. Always wrap in eval / try:

my $Payment = eval { $Webhook->resource };
if ( $@ ) {
    warn "Webhook verification failed: $@";
    return http_response( 400 );
}

if ( $Payment->is_payment_success ) {
    my $amount_gbp = $Payment->amount / 100;
    printf "Received %.2f %s for student %s\n",
        $amount_gbp, $Payment->currency, $Payment->student_ref;

    if ( $Payment->schedule_activated ) {
        printf "Schedule %s activated; next instalment: %s\n",
            $Payment->schedule_id, $Payment->next_installment_date // 'N/A';
    }
}
elsif ( $Payment->is_payment_failed ) {
    warn sprintf "Payment %s failed (%s): %s\n",
        $Payment->payment_id,
        $Payment->error_code    // 'unknown',
        $Payment->error_message // 'no detail';
}
elsif ( $Payment->is_payment_pending ) {
    warn "Payment " . $Payment->payment_id
        . " is pending: " . ( $Payment->pending_reason // 'unknown reason' );
}

DESCRIPTION

Business::Payr::Webhook handles the receipt, cryptographic verification, and parsing of webhook notifications sent by the Payr platform.

All webhooks are signed with HMAC-SHA256 using a secret shared between Payr and your platform. The signature is carried in the X-Payr-Signature HTTP header. Verification is performed automatically during object construction; an exception is thrown if the signature is missing, incorrect, or the payload cannot be parsed.

Important: You should always pass the raw request body string as the body argument, before any deserialisation. Re-serialising a parsed structure may produce different byte sequences and will cause verification to fail.

Contact support@mypayr.co.uk to configure your webhook endpoint URL and receive your webhook secret.

SIGNATURE VERIFICATION DETAILS

Payr signs webhook payloads as follows:

  1. The JSON payload is serialised with compact separators (no spaces) and lexicographically sorted keys.

  2. An HMAC-SHA256 digest of the serialised payload is computed using the shared webhook secret.

  3. The hex-encoded digest is placed in the X-Payr-Signature HTTP header.

This module computes the same digest over the raw request body and compares it against the header value using a constant-time comparison to prevent timing attacks.

DEBUGGING

Set MOJO_CLIENT_DEBUG=1 for user agent and transport debug output when fetching JWKS or other remote resources.

ATTRIBUTES

body (Str, required)

The raw (undecoded) HTTP request body string exactly as received from Payr. Do not deserialise and re-serialise this value before passing it in; the signature is computed over the original byte sequence.

signature (Str, required)

The value of the X-Payr-Signature HTTP header included in the webhook request. This is a hex-encoded HMAC-SHA256 digest.

secret (Str, required)

Your Payr webhook signing secret. Keep this value secure and never expose it in client-side code or version control. Contact support@mypayr.co.uk to obtain or rotate your secret.

METHODS

BUILD

Called automatically by Moose after construction. Verifies the HMAC-SHA256 signature and decodes the JSON payload. Throws an exception if either step fails.

You do not need to call this method directly.

event_type

Returns the event field from the decoded webhook payload.

my $type = $Webhook->event_type;
# "payment_success", "payment_failed", or "payment_pending"

is_payment_success

Returns 1 if this webhook represents a payment_success event, 0 otherwise.

if ( $Webhook->is_payment_success ) { ... }

is_payment_failed

Returns 1 if this webhook represents a payment_failed event, 0 otherwise.

if ( $Webhook->is_payment_failed ) { ... }

is_payment_pending

Returns 1 if this webhook represents a payment_pending event, 0 otherwise.

if ( $Webhook->is_payment_pending ) { ... }

resource

Parses the verified webhook payload and returns a Business::Payr::Webhook::Payment object populated with all fields from the payload.

my $Payment = $Webhook->resource;

Throws an exception if the payload has already been cleared or if required fields are missing.

SEE ALSO

Business::Payr::Webhook::Payment

Business::Payr

AUTHORS

Lee Johnson - leejo@cpan.org

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. If you would like to contribute documentation, features, bug fixes, or anything else then please raise an issue / pull request:

https://github.com/payprop/business-payr