Appearance
Payments
Online and offline payment methods for orders and anything else payable.
kitloom/payments · admins: kitloom/payments-filament, kitloom/payments-nova
Payments for any model. An order, an invoice or a booking implements Payable, takes the HasPayments trait, and $order->pay('stripe') sends the payer to the gateway; the webhook calls $order->paid(). The engine is fomvasss/laravel-billing: its billing_payments table, its gateways (Monobank, LiqPay, WayForPay, Hutko, Stripe, Paddle) and its webhook pipeline. This package adds the model side, offline methods, the gateway keys in the site's settings, its own events and the landing page.
The admin is kitloom/payments-filament or kitloom/payments-nova.
A payable model
php
use Fomvasss\Billing\Models\Payment;
use Fomvasss\Billing\Support\Money;
use Kitloom\Payments\Concerns\HasPayments;
use Kitloom\Payments\Contracts\Payable;
class Order extends Model implements Payable
{
use HasPayments;
public function payableAmount(): Money
{
return Money::fromDecimal($this->total, $this->currency);
}
public function payableReturnUrl(Payment $payment): string
{
return route('orders.received', $this);
}
public function paid(Payment $payment): void
{
$this->markProcessing();
}
}payableDescription(), payablePayer() and tenantId() have defaults in the trait: "Order #12", the model itself as the payer, no tenant. Override payablePayer() to return the user when there is one.
php
$checkout = $order->pay($request->input('payment_method')); // a Checkout
return $checkout->redirect(); // the gateway, or the order page for an offline method
$order->isPaid();
$order->latestPayment();
$order->payments; // charges and refunds, newest firstMethods
Payments::methods() lists what the payer may choose: the gateways switched on in the settings and the offline methods from kitloom-payments.offline (bacs, cod) that nobody switched off. <x-payments::methods /> renders them as radio buttons.
A shop brought over from WooCommerce keeps the offline methods it had. bacs and cod are WooCommerce's own ids, so what the site's settings do not say is read from WooCommerce's woocommerce_bacs_settings / woocommerce_cod_settings: whether the method is on, its title, its description and its instructions. A method WooCommerce kept no settings for is off, as in WooCommerce (a database with WooCommerce's woocommerce_db_version is taken for its shop). Saving the method here writes the site's own option, which counts from then on; WooCommerce's is left as it is.
An offline method has instructions too — what the payer is told once the order is placed, where the description is what the checkout says before: the method's instructions (Method::$instructions; the settings page's Instructions, else WooCommerce's, else the defaults in kitloom-payments.offline). A bank transfer also has the accounts to pay into, Method::$bankAccounts, read from WooCommerce's woocommerce_bacs_accounts (account name, number, bank, sort code, IBAN, BIC; they are not edited here yet). BankDetails::of($method, $country) lists them as WooCommerce's "Our bank details" do: the fields filled in, the sort code by the name the payer's country gives it (BSB, routing number…). The shop shows both on the "order received" page and in its emails.
An offline payment stays pending until the admin marks it paid: Payments::markPaid($payment) runs paid() and fires the event, as a webhook would. Payments::cancel() and Payments::refund() do the rest; a refund of an offline payment is a record of money given back by hand, a refund of an online one goes through the gateway.
Settings
One option per method, kitloom_payments_{id}: enabled, title, description and credentials. Credentials are encrypted with the application key; what is saved wins over config/billing.php and the environment. Methods\MethodSettings reads and writes them, the admin packages edit them, and the gateway's credentialFields() says which keys it needs.
AI agents
With kitloom/ai installed, an agent whose user has manage_payment_settings reads the ways to pay (payments_methods): each gateway and offline method, whether it is on, the currencies it takes, whether it refunds, which credentials a gateway needs and which are still empty, and whether its webhook needs setting up in the gateway's dashboard — so it can say why a checkout fails. Never a credential's value, never a bank account's details; it changes nothing (the option is protected). kitloom-payments.ai.register false — no such tool.
Events
Kitloom\Payments\Events: PaymentStarted, PaymentPaid, PaymentFailed, PaymentCanceled, PaymentRefunded and CheckoutReturned, each with the Payment and payable(). PaymentPaid fires after the payable's paid(). CheckoutReturned is the payer's browser coming back, unverified — fulfil on PaymentPaid only.
Routes
POST billing/webhooks/{gateway}— laravel-billing's webhook, no CSRF, signature-checked, handled on the queue.GET|POST billing/return/{payment}/{outcome}→GET payments/landing/{outcome}?payment=→ the payable'spayableReturnUrl().GET billing/pay/{payment}— the permanent pay link for emails and invoices;<x-payments::pay-button :payment="$payment" />.
Personal data
With kitloom/privacy installed, Privacy → Export Personal Data lists a person's payments — those their account made, and any whose stored data carries their email address: number, method, amount, status, dates and the gateway's reference — and the gateways' notifications that named them. Erase Personal Data does not delete payments: the accounts need the amount, currency, dates, method and the gateway's reference, and these stay. What the gateway answered (raw_response) is cleared, the address is replaced with [email protected] in the payment's meta, and the body of each notification that carried it goes; the notification's row stays, as it is what keeps a gateway's repeated delivery from being handled twice. A payment still pending and a notification not handled yet are kept and reported: erase again once they are settled. The payment services keep their own records — the person asks them. kitloom-payments.privacy.register false leaves the payments out of both.
Installing
The package's migrations run with the application's. php artisan payments:install checks the queue the webhooks run on, lists the methods and prints the webhook addresses that must be registered in a gateway's dashboard. Pending payments past their link's lifetime are reconciled every fifteen minutes when the scheduler runs, on one server of several, by a lock in the cache, so a site on several servers needs a shared cache store (Redis, the database) — with each server's own cache the lock is local and the task runs on every one.
Tests
FakePayments::enable() makes laravel-billing's fake gateway the default method; FakePayments::pay($payment), fail() and cancel() answer as the gateway would. The tests boot the application of the skeleton they run from.