ifthenpay / laravel
Laravel package for the ifthenpay Payment Gateway (Multibanco, MB WAY, Payshop, Credit Card, Pix, Pay by Link).
Requires
- php: ^8.3
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/http: ^13.0
- illuminate/support: ^13.0
README
Laravel package for ifthenpay payment gateway that facilitates payment generation with Portuguese payment methods and others.
Table of Contents
Requirements
(working on backwards compatibility for Laravel 11 and 12)
- PHP ^8.3
- Laravel ^13.0
Installation
composer require ifthenpay/laravel
Publish the config file:
php artisan vendor:publish --tag=ifthenpay-config
Run the migrations (creates the ifthenpay_payments table used for optional persistence, loaded automatically by the package):
php artisan migrate
Configure the package by adding these keys to your .env file.
The following is an example of how you would fully configure the package.
IFTHENPAY_BACKOFFICE_KEY=0000-0000-0000-0000 IFTHENPAY_MULTIBANCO_KEY=ITP-000000 IFTHENPAY_MBWAY_KEY=ITP-000000 IFTHENPAY_PAYSHOP_KEY=ITP-000000 IFTHENPAY_CREDITCARD_KEY=ITP-000000 IFTHENPAY_PIX_KEY=ITP-000000 IFTHENPAY_PAYBYLINK_KEY=ITPG-000000 IFTHENPAY_PAYBYLINK_METHODS=MULTIBANCO|ITP-000000;MBWAY|ITP-000000;PAYSHOP|ITP-000000;CREDITCARD|ITP-000000;PIX|ITP-000000;GOOGLE|ITP-000000;APPLE|ITP-000000 IFTHENPAY_PAYBYLINK_DEFAULT_METHOD=GOOGLE IFTHENPAY_ANTIPHISHING_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa IFTHENPAY_WEBHOOK_PATH=ifthenpay/webhook IFTHENPAY_WEBHOOK_RATE_LIMIT_PER_MINUTE=60
Configure only what you need, use the table below for reference.
| configuration | purpose | example | required for |
|---|---|---|---|
IFTHENPAY_BACKOFFICE_KEY |
Authenticates the webhook registration call | 0000-0000-0000-0000 |
Registering webhooks |
IFTHENPAY_MULTIBANCO_KEY |
Multibanco key — dynamic (online) or entity-subentity (offline) | (online) ITP-000000 or (offline) 00000-000 |
Generating Multibanco payments |
IFTHENPAY_MBWAY_KEY |
MB WAY key | ITP-000000 |
Generating MB WAY payments |
IFTHENPAY_PAYSHOP_KEY |
Payshop key | ITP-000000 |
Generating Payshop payments |
IFTHENPAY_CREDITCARD_KEY |
Credit Card key | ITP-000000 |
Generating Credit Card payments |
IFTHENPAY_PIX_KEY |
Pix key | ITP-000000 |
Generating Pix payments |
IFTHENPAY_PAYBYLINK_KEY |
Pay-by-link key | ITPG-000000 |
Generating Pay by Link payments |
IFTHENPAY_PAYBYLINK_METHODS |
Method|key pairs, separated by ;, used to define what methods to display in the gateway page |
GOOGLE|ITP-000000;APPLE|ITP-000000 |
Generating Pay by Link payments |
IFTHENPAY_PAYBYLINK_DEFAULT_METHOD |
Sets preselected method on the gateway page | GOOGLE |
Generating Pay by Link payments |
IFTHENPAY_ANTIPHISHING_KEY |
Validated against incoming webhook requests | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
Registering webhooks; updating payment status through webhook |
IFTHENPAY_WEBHOOK_PATH |
Relative path used to register the webhook URL (defaults to ifthenpay/webhook) |
ifthenpay/webhook |
Receiving webhooks |
IFTHENPAY_WEBHOOK_RATE_LIMIT_PER_MINUTE |
Requests per minute allowed on the webhook route middleware (defaults to 60) |
60 |
Receiving webhooks |
Usage
Generating Payments
There are two ways to generate payments:
Using the facade This is the recommended approach, but keep in mind it requires you to configure the payment method keys you'll be using.
use Ifthenpay\Laravel\Facades\Ifthenpay; $paymentResponse = Ifthenpay::multibanco()->generatePayment( orderId: 'order-1887', amount: '10.99', description: 'Order #1887', // optional expiryDays: 3, // optional );
Direct instantiation In this approach you are responsible for configuring and instantiating the gateway object
use Ifthenpay\Laravel\Gateways\MultibancoGateway; $multibancoGateway = new MultibancoGateway(['multibanco_key' => 'ITP-000000']); $multibancoGateway->generatePayment( orderId: 'order-1887', amount: '10.99', description: 'Order #1887', // optional expiryDays: 3, // optional );
Given the payment method type, the generatePayment() method will expect different parameters.
// ifthenpay/laravel-ifthenpay/src/Gateways/MultibancoGateway.php public function generatePayment(string $orderId, string $amount, ?string $description = null, ?int $expiryDays = null): MultibancoResponse // ifthenpay/laravel-ifthenpay/src/Gateways/MbwayGateway.php public function generatePayment(string $orderId, string $amount, string $mobileNumber, ?string $description = null, ?string $email = null, ?int $expiryMinutes = null): MbwayResponse // ifthenpay/laravel-ifthenpay/src/Gateways/PayshopGateway.php public function generatePayment(string $orderId, string $amount, ?int $expiryDays = null): PayshopResponse // ifthenpay/laravel-ifthenpay/src/Gateways/CreditcardGateway.php public function generatePayment(string $orderId, string $amount, string $successUrl, string $errorUrl, string $cancelUrl, string $language = 'en', ?int $expiryMinutes = null): CreditcardResponse // ifthenpay/laravel-ifthenpay/src/Gateways/PixGateway.php public function generatePayment(string $orderId, string $amount, string $customerCpf, string $customerName, string $customerEmail, string $customerPhone, string $redirectUrl, ?string $description = null, ?string $customerAddress = null, ?string $customerStreetNumber = null, ?string $customerCity = null, ?string $customerZipCode = null, ?string $customerState = null, ?int $expiryMinutes = null): PixResponse // ifthenpay/laravel-ifthenpay/src/Gateways/PaybylinkGateway.php public function generatePayment(string $orderId, string $amount, string $successUrl, string $errorUrl, string $cancelUrl, ?string $description = null, ?int $expiryDays = null, bool $otp = false, string $lang = 'pt', ?string $btnCloseUrl = null, ?string $btnCloseLabel = null): PaybylinkResponse
But their return will always be an implementation of IfthenpayResponse interface.
// ifthenpay/laravel-ifthenpay/src/Contracts/IfthenpayResponse.php public function isSuccessful(): bool; // result of payment generation public function getMessage(): ?string; // error message, in case of failure public function toArray(): array; // response payload with keys prepared for mass-assignment to ifthenpay_payments model
Multibanco
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $multibancoResponse = Ifthenpay::multibanco()->generatePayment( orderId: '1000', amount: '10.99', description: 'Order #1887', // optional expiryDays: 3, // optional ); if(!$multibancoResponse->isSuccessful()) { throw new Exception("Error Generating Payment", 1); } // store payment or another action Payment::create($response->toArray());
MB WAY
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $mbwayResponse = Ifthenpay::mbway()->generatePayment( orderId: '1000', amount: '10.99', mobileNumber: '351#912345678', description: 'Order #1887', // optional email: 'jane@example.com', // optional expiryMinutes: 10, // optional ); if(!$mbwayResponse->isSuccessful()) { throw new Exception("Error Generating Payment", 1); } // store payment or another action Payment::create($mbwayResponse->toArray());
Payshop
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $payshopResponse = Ifthenpay::payshop()->generatePayment( orderId: '1000', amount: '10.99', expiryDays: 5, // optional ); if(!$payshopResponse->isSuccessful()) { throw new Exception("Error Generating Payment", 1); } // store payment or another action Payment::create($payshopResponse->toArray());
Credit Card
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $creditcardResponse = Ifthenpay::creditcard()->generatePayment( orderId: '1000', amount: '10.99', successUrl: 'https://example.com/success', errorUrl: 'https://example.com/error', cancelUrl: 'https://example.com/cancel', language: 'en', // optional expiryMinutes: 15, // optional ); if(!$creditcardResponse->isSuccessful()) { throw new Exception("Error Generating Payment", 1); } // store payment or another action Payment::create($creditcardResponse->toArray());
Pix
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $pixResponse = Ifthenpay::pix()->generatePayment( orderId: '1000', amount: '10.99', customerCpf: '123.456.789-00', customerName: 'John Doe', customerEmail: 'john@example.com', customerPhone: '351912345678', redirectUrl: 'https://example.com/redirect', description: 'Order #1887', // optional customerAddress: 'Rua Exemplo', // optional customerStreetNumber: '10', // optional customerCity: 'Lisboa', // optional customerZipCode: '1000-001', // optional customerState: 'Lisboa', // optional expiryMinutes: 15, // optional ); if(!$pixResponse->isSuccessful()) { throw new Exception("Error Generating Payment", 1); } // store payment or another action Payment::create($pixResponse->toArray());
Pay by Link
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $paybylinkResponse = Ifthenpay::paybylink()->generatePayment( orderId: '1000', amount: '10.99', successUrl: 'https://example.com/success', errorUrl: 'https://example.com/error', cancelUrl: 'https://example.com/cancel', description: 'Order #1887', // optional expiryDays: 5, // optional otp: false, // optional lang: 'pt', // optional btnCloseUrl: 'https://example.com/close', // optional btnCloseLabel: 'Close', // optional ); if(!$paybylinkResponse->isSuccessful()) { throw new Exception("Error Generating Payment", 1); } // store payment or another action Payment::create($paybylinkResponse->toArray());
Persisting payments
If you published and ran the migration for this package, you can use the Payment model Ifthenpay\Laravel\Models\Payment to persist the payments in your database
You can store a payment separately right after generating it:
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $multibancoResponse = Ifthenpay::multibanco()->generatePayment(orderId: 'order-1887', amount: '10.99', expiryDays: 3); Payment::create($multibancoResponse->toArray());
Or add the HasIfthenpayPayments trait to any Eloquent model (e.g. your Order) to generate and persist a payment linked to it in one line.
// app/Models/Order.php use Ifthenpay\Laravel\Traits\HasIfthenpayPayments; class Order extends Model { use HasIfthenpayPayments; }
// app/Http/Controllers/YourController.php use App\Models\Order; $order = Order::find(1); $payment = $order->createMbwayPayment(orderId: 'order-1887', amount: '10.99', mobileNumber: '351#912345678'); $orderPayments = $order->payments; // all payments for this order (MorphMany) $orderMbwayPayments = $order->mbwayPayments; // only its MB WAY payments
Equivalent methods to createMbwayPayment() and mbwayPayments() exist for every payment method.
| Payment method | Method | Eloquent relation |
|---|---|---|
| Multibanco | createMultibancoPayment() | multibancoPayments() |
| MB WAY | createMbwayPayment() | mbwayPayments() |
| Payshop | createPayshopPayment() | payshopPayments() |
| Pix | createPixPayment() | pixPayments() |
| Credit Card | createCreditcardPayment() | creditcardPayments() |
| Pay by Link | createPaybylinkPayment() | paybylinkPayments() |
The payment model also exposes the following methods
| Method | Description |
|---|---|
| isPaid() | check if payment record has paid status, returns true if payment status is paid (Enum of PaymentStatus) |
| secondsToExpire() | returns seconds remaining until expires_at, or 0 if already past/unset |
| totalSecondsToExpire() | returns total seconds between created_at and expires_at, or 0 if either is unset, useful if you want to create a progress bar like blade component of the remaining time a user has to complete the payment |
| markAsPaid() | sets status to paid and stamps paid_at (if not already set) |
| markAsCanceled() | sets status to canceled and stamps canceled_at |
| markAsExpired() | sets status to expired |
| scopePending() | query scope for payments with status pending |
| scopePaid() | query scope for payments with status paid |
| scopeCanceled() | query scope for payments with status canceled |
| scopeExpired() | query scope for pending payments whose expires_at has passed |
| scopeByMultibanco() | query scope for payments generated via Multibanco |
| scopeByMbway() | query scope for payments generated via MB WAY |
| scopeByPayshop() | query scope for payments generated via Payshop |
| scopeByPix() | query scope for payments generated via Pix |
| scopeByCreditcard() | query scope for payments generated via Credit Card |
| scopeByPaybylink() | query scope for payments generated via Pay by Link |
Registering webhooks
Register your callback URL with ifthenpay via the ifthenpay:register-webhook command.
Note: If IFTHENPAY_ANTIPHISHING_KEY isn't set yet, the command offers to generate and save one for you.
With no options (recommended), it registers a webhook for every payment method you have a key configured for (including every method bundled into paybylink_methods):
php artisan ifthenpay:register-webhook
Or register a single method explicitly, if you are not using the config to load the method keys and are instantiating the gateway classes directly:
php artisan ifthenpay:register-webhook --method=mbway --key=ITP-000000
By default this command registers the named route ifthenpay.webhook as the url, but you can use --url to override it and set your own route, just be sure to pass an absolute path without any querystrings.
php artisan ifthenpay:register-webhook --method=mbway --key=ITP-000000 --url="https://yoururl.com"
If you need to add more parameters in the webhook, you must add them with --params
php artisan ifthenpay:register-webhook --method=mbway --key=ITP-000000 --url="https://yoururl.com" --params="scope=01"
Warning: If the same key is used both standalone and inside
paybylink_methods(e.g.IFTHENPAY_MULTIBANCO_KEY=ITP-000000andIFTHENPAY_PAYBYLINK_METHODS=MULTIBANCO|ITP-000000;...), running the command with no options registers a webhook for that key twice — once for the standalone method, once forpaybylink— and whichever registration ifthenpay processes last wins, silently overriding the other's callback URL. This is easy to hit by accident sincepaybylink_methodscommonly reuses the same keys configured for the individual methods. Avoid reusing a key across a standalone method andpaybylink_methodsif they need different callback URLs, or re-run the command for the affected method/key after registering all methods to make sure the URL you want ends up as the last one registered.
Receiving webhooks
By default the package registers a route at the path configured by ifthenpay.webhook_path (default ifthenpay/webhook), protected by the api middleware group and a named rate limiter (ifthenpay-webhook, default 60/minute — configurable via IFTHENPAY_WEBHOOK_RATE_LIMIT_PER_MINUTE).
To fully customize or disable this route (e.g. to point it at your own controller), publish the routes file into your app:
php artisan vendor:publish --tag=ifthenpay-routes
Once routes/ifthenpay.php exists in your application, the package loads that file instead of its own — edit, replace, or empty it as needed.
Incoming requests are validated (required pm/apk/val/oid/ref/req query params, apk checked against IFTHENPAY_ANTIPHISHING_KEY), matched to a stored Payment by method-specific fields, and amount-checked in cents to avoid float issues. On success the matching payment is marked paid; a negative amount is treated as a refund and leaves the payment's status untouched. Every outcome fires an event you can listen for — see Payment Webhook Confirmed, Refunded, and Rejected below.
Expiring payments
All payment methods can be attributed an expiration, but only Multibanco(online), Payshop, Pay-by-link can use it to block access to payment after that time expires, meaning after that payment has expired, the user will not be able to pay (Multibanco and Payshop) or access the gateway page (Pay-by-link).
This functionality can still be used to manage abandoned payments for all payment methods, so that you can update the payments that are pending to expired. To use this functionality you need to pass the expiration (varies by payment method: days or minutes) when generating the payment.
The command ifthenpay:payments:expire, marks pending payments as expired once expires_at has passed and fires an event Ifthenpay\Laravel\Events\PaymentExpired for each (--dry-run lists them without changing anything).
Schedule it yourself in routes/console.php, and set the frequency you like.
// /routes/console.php use Illuminate\Support\Facades\Schedule; Schedule::command('ifthenpay:payments:expire')->hourly();
If you want to add expiration to payments you may use this table as suggestion
| Payment method | Suggested expiration |
|---|---|
| Multibanco | 3 days |
| MB WAY | 4 minutes |
| Payshop | 3 days |
| Credit Card | 15 minutes |
| Pix | 5 minutes |
| Pay by Link | 1 day |
Method Specific Utilities
You can make use of these utilities if you want to change/improve the user experience, but are optional and you can still implement the methods without relying on them.
MB WAY check status
MB WAY payments can be actively polled for status via checkStatus(), in addition to being updated by webhook. It requires the Payment's request_id (returned by ifthenpay when the payment was generated) to be set.
This functionality is useful when implementing a countdown timer that shows feedback about the status to the user.
use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $payment = Payment::find(1); $statusResponse = Ifthenpay::mbway()->checkStatus($payment); if ($statusResponse->isSuccessful()) { $payment->markAsPaid(); }
checkStatus() returns a MbwayStatusResponse with a typed status (Ifthenpay\Laravel\Enums\MbwayStatus: PENDING, PAID, EXPIRED, REFUSED_BY_USER, REJECTED_BY_MBWAY) and a message. isSuccessful() only returns true when status is MbwayStatus::PAID.
// ifthenpay/laravel-ifthenpay/src/Responses/MbwayStatusResponse.php public function __construct( public ?string $message, public MbwayStatus $status, ) {}
Credit Card verify return
When generating a credit card payment, you'll get a Credit Card gateway page url in the response, to which you will redirect the user, upon being redirected back (to successUrl, errorUrl, or cancelUrl), ifthenpay appends a sk (secret key) query parameter.
Use verifyPayment() to confirm the redirect is authentic before trusting it — e.g. before showing a success page.
You can also ignore this verification system and rely solely on the webhook.
use Ifthenpay\Laravel\Exceptions\IfthenpayException; use Ifthenpay\Laravel\Facades\Ifthenpay; use Ifthenpay\Laravel\Models\Payment; $payment = Payment::where('order_id', request('orderId'))->firstOrFail(); try { Ifthenpay::creditcard()->verifyPayment(secretKey: request('sk'), payment: $payment); } catch (IfthenpayException $e) { abort(403, 'Invalid payment return.'); } // verification passed, safe to treat as returning from a genuine ifthenpay redirect
verifyPayment() returns void on success and throws IfthenpayException (secret key mismatch) on failure — it does not return a response object like the other gateway methods.
Blade Components
The package registers its views under the ifthenpay namespace, so its Blade components are available anywhere in your app as <x-ifthenpay::*>, no publishing required.
All of them are styled with Tailwind CSS v4 utility classes (including dark: variants for dark mode) and ship no CSS of their own — they render correctly out of the box only in an app that already has Tailwind set up. See Customizing the components below if your app doesn't use Tailwind, or if you just want a different look.
<x-ifthenpay::payment-details>
Renders a card summarizing a Payment model, dispatching to the right method-specific partial (below) based on $payment->method.
<x-ifthenpay::payment-details :payment="$payment" /> {{-- show a status badge (pending/paid/canceled/expired/failed) alongside the amount --}} <x-ifthenpay::payment-details :payment="$payment" :show-status="true" />
| Prop | Type | Default | Purpose |
|---|---|---|---|
payment |
Ifthenpay\Laravel\Models\Payment |
— | The payment to render. |
showStatus |
bool |
false |
Show a colored status badge next to the amount. |
Any attributes you pass through (e.g. class="...") are merged onto the root <div>.
Method-specific partials
payment-details renders one of these internally based on $payment->method, but each can also be used standalone if you're building your own layout — they all just take a :payment prop:
| Component | Method | Renders |
|---|---|---|
<x-ifthenpay::payment-details-multibanco> |
Multibanco | Entity/reference, and expiry date if expires_at is set. |
<x-ifthenpay::payment-details-mbway> |
MB WAY | Instructions to confirm in-app, plus a live countdown bar to expires_at (vanilla JS, no Alpine/Livewire dependency). |
<x-ifthenpay::payment-details-payshop> |
Payshop | Reference to pay at a Payshop agent/CTT store, and expiry date if set. |
<x-ifthenpay::payment-details-credit-card> |
Credit Card | A link to payment_url (same tab, so the gateway's redirect back to successUrl/errorUrl/cancelUrl lands where the user started). |
<x-ifthenpay::payment-details-pix> |
Pix | A link to payment_url (same tab, so the gateway's redirect back to redirectUrl lands where the user started). |
<x-ifthenpay::payment-details-pay-by-link> |
Pay by Link | A link to payment_url (same tab). |
Each partial no-ops (renders nothing) if the field it needs (entity/reference/payment_url) isn't set on the payment yet.
Customizing the components
These components are bland and generic, because they are meant as a base for what you may want to display to your user. Publish the views to get an editable copy in your own app and customize it to your needs:
php artisan vendor:publish --tag=ifthenpay-views
This copies every .blade.php file (payment-details and all six method-specific partials) into resources/views/vendor/ifthenpay/components/. Laravel prefers published views over the package's own, so once they exist there, editing them (markup, copy, or swapping the Tailwind classes for your own CSS/framework) is picked up automatically by every <x-ifthenpay::*> tag already in use — no need to change any of your existing usages.
If you keep the Tailwind classes, make sure your resources/views/vendor/ifthenpay/** directory is covered by Tailwind's @source/content scanning (Tailwind v4's automatic content detection already covers resources/views by default, so this is usually a non-issue unless you publish elsewhere or use a custom Tailwind config).
Events
The package fires typed events for webhook outcomes and payment expiry. Every event implements Ifthenpay\Laravel\Contracts\LoggableEvent (logLevel(), logMessage(), logContext()), which is what the bundled LogIfthenpayEvent listener relies on.
Payment Expired
Fired by the ifthenpay:payments:expire command for each payment it marks as expired (see Expiring payments).
use Ifthenpay\Laravel\Events\PaymentExpired; use Illuminate\Support\Facades\Event; Event::listen(function (PaymentExpired $event) { // $event->payment — the Payment model instance that was just marked as expired });
Payment Webhook Confirmed
Fired when an incoming webhook passes validation with a positive amount matching the stored payment. The payment has already been marked as paid by the time this fires.
use Ifthenpay\Laravel\Events\PaymentWebhookConfirmed; use Illuminate\Support\Facades\Event; Event::listen(function (PaymentWebhookConfirmed $event) { // $event->payload — Ifthenpay\Laravel\DTO\WebhookPayload: method, orderId, reference, requestId, amount });
Payment Webhook Refunded
Fired when an incoming webhook carries a negative amount. The matching payment's status is left untouched. This packaged does not have a refund feature, but you can make use of this event to implement it yourself. Check the official documentation on how to implement the refund endpoint LINK
use Ifthenpay\Laravel\Events\PaymentWebhookRefunded; use Illuminate\Support\Facades\Event; Event::listen(function (PaymentWebhookRefunded $event) { // $event->payload — Ifthenpay\Laravel\DTO\WebhookPayload: method, orderId, reference, requestId, amount });
Payment Webhook Rejected
Fired when an incoming webhook fails validation — missing/invalid query params, an anti-phishing key mismatch, no matching payment, or an amount mismatch. The HTTP response is always a generic 400 regardless of reason, so a caller can't learn which check failed; the reason is only available to your listener.
use Ifthenpay\Laravel\Events\PaymentWebhookRejected; use Illuminate\Support\Facades\Event; Event::listen(function (PaymentWebhookRejected $event) { // $event->payload — Ifthenpay\Laravel\DTO\WebhookPayload, built defensively from the raw (possibly invalid) query // $event->reason — internal string explaining why the webhook was rejected });
Log
Optionaly, you can log the webhook events by just using the event listener LogIfthenpayEvent included in this package to enable logging for the four events mentioned above.
To enable it, just add this line in the your AppServiceProvider.
// app/Providers/AppServiceProvider.php use Ifthenpay\Laravel\Contracts\LoggableEvent; use Ifthenpay\Laravel\Listeners\LogIfthenpayEvent; use Illuminate\Support\Facades\Event; Event::listen(LoggableEvent::class, LogIfthenpayEvent::class);
This log never includes secrets (e.g. the webhook's anti-phishing key is stripped before logging). It reports
method, order_id, reference, request_id, amount for webhook events, or id, method, order_id,
expires_at, status for PaymentExpired.
License
MIT.