Payments
webx-ui/module-payments is how a shop on WebX UI takes the money for an order: which payment methods a buyer is offered for this cart and this delivery, the step «Payment» of the checkout, the details of a bank transfer on «Thank you», «Mark as paid» for a manager, and online payment through a gateway. A gateway comes as a satellite package; a shop without one works with the two built-in methods — on receipt and by bank transfer.
Everything the shop sends a gateway and everything a gateway sends back is kept whole in the exchange journal: the raw body, the headers, the status, the time, the signature check and what the shop decided — the evidence of any dispute about a payment.
Install
composer require webx-ui/module-payments
php artisan migrateIt requires webx-ui/module-orders (and through it webx-ui/module-cart). The panel gets the section «Payments» — methods, attempts and the gateway exchange — and the order's card gets the tab «Payment». The checkout gets its step «Payment» by itself.
Online payment needs the scheduler: webx:payments:poll runs every five minutes and asks the gateway about payments nobody told the shop about.
A shop without a gateway
The migration adds two methods, both switched off. Switch them on in Payments → Methods:
- On receipt — the buyer pays at the shop's counter or the carrier's courier. The order does not await payment. By default it is offered only with a delivery where money changes hands — a pick-up at the shop's point or cash on delivery; at the shop's point the buyer reads «Pay at the counter».
- Bank transfer — the order awaits payment until a manager marks it paid. Fill in the recipient, the account and the bank (one field: the format is the country's), the purpose —
{number}stands for the order's number — and a note, such as how many days the buyer has. Each in every language of the site. The buyer sees the details on «Thank you», on the order's page and in the letter.
Each method also has its rules: the amount it is offered from and up to, and the deliveries it goes with — any, only where money changes hands, only the methods listed, or all but those. Why the checkout does not offer a method to a cart, the agent's payments_available says.
Mark as paid
The money came by transfer: on the order, Mark as paid in the header — the document's number, the day the money came, a note. The order gets the milestone «paid», leaves «Awaiting payment», and the tab «Payment» shows what paid it and who marked it. The sum is the order's: there is no partial payment by transfer — a buyer who sent less is a phone call.
Online payment
A gateway is a satellite package, payment-<gateway>: install it, open its method in Payments → Methods, type the keys of the gateway's account and press Test the connection. Keys are stored encrypted, shown as «set» and never sent back — not to the panel, not to an agent. Copy the notification address the form shows into the gateway's account if the gateway asks for one; the return address goes with every payment by itself.
What the buyer sees: the order is placed, the browser goes to the gateway's page, the buyer pays and comes back to «Thank you». The shop believes nothing the browser brings back — the status comes from the gateway's signed notification or from its API.
- Notifications. The gateway posts to
/payments/notify/{method}. The raw request is in the journal before anything reads it; a bad signature changes nothing. A gateway whose notifications are not signed is never believed: its notification is only a reason for the shop to ask the gateway's API itself. - Pay again. A payment that did not go through leaves the order awaiting payment. The buyer's page of the order says «Awaiting payment» with «Pay» and «Choose another method», and the letter carries a link. The list holds every method the order may take by its sum and delivery, on receipt and bank transfer too; the buyer changes the method, never the items.
- Problems. Money for a cancelled order, a second payment for a paid one, a sum that does not match — the attempt is marked a problem in Payments → Attempts → Problems; a manager settles it with the gateway and presses «Resolved».
Hold and capture
A gateway that can hold money (a card blocked, not charged) offers it as the method's switch «Hold the money, capture later». The order then shows «Money held» until a manager decides, on the tab «Payment»:
- Capture — all of the money or a part. A capture that covers the order pays it. Less than the order — the items were lowered first, or not — is money in without the milestone «paid»: the order owes the rest, its attempt reads «Captured 50,00 € of 92,90 €».
- Release the hold — the money goes back to the buyer; the order awaits payment as before.
Both show what they will do before they do it. A hold the gateway lets expire is a failed payment; the poll warns before. A capture the gateway refused leaves a note on the order; it goes from the notes when the hold is over — captured, released or expired — and stays in the history.
A theme with its own templates
The buyer's pages say «Awaiting payment», «Pay» and «Choose another method», and tell a card the bank declined. A theme that keeps its own copy of these templates has to carry them over:
webx-orders/order/body.blade.phphas to print the blockprompt:$order['prompt']['title']over the status and$order['prompt']['html']under the scale. Without it the buyer reads the public status and gets no button to pay. It also has to print$order['offer']under the scale: without it the buyer of an order on receipt gets no «Pay online now».webx-payments/pay/page.blade.phphas to handle the statedeclined. Without it the buyer the bank turned down gets an empty place instead of the reason and «Pay again».
php artisan webx:doctor names a copy that lacks any of them. «My orders» needs nothing: the words come in $row['status'], with the code awaiting-payment.
Groups and marks
The step «Payment» reads in two levels: a few groups, and the methods inside the one the buyer opens. Opening a group picks its first method. The lists on the page of payment are the same component, with Continue under them, because a choice there leaves for the gateway.
Three groups come with the package: on_receipt, online and transfer. A method stands in the group of its type by itself. In Payments → Methods a manager drags groups and methods, renames a group, gives it a description and a pictogram, and moves a method to another group, by dragging or by its field Group. Nothing in the panel or the agent's tools creates or removes a group: that is the site's config. Two enabled methods of one group with the same marks are warned of, never refused.
The buyer does not see a group with nothing to offer. A group with one method is one line, and a single group is a flat list.
The groups of a site
Which groups there are is webx-payments.groups. Publish the config with php artisan vendor:publish --tag=webx-payments-config:
'groups' => [
'on_receipt' => ['title' => 'webx-payments::groups.on_receipt', 'pictogram' => 'cash'],
'online' => ['title' => 'webx-payments::groups.online', 'pictogram' => 'card'],
'instalments' => ['title' => 'Pay in parts', 'pictogram' => 'wallet'],
],- The key is the group's for good: moves of methods and a manager's edits are stored under it.
titleis a key of a dictionary or the words themselves.pictogramis one ofcard,cash,document,bank,wallet,truck,store,qr.- The site's
groupsreplaces the list whole: what is not there, the site does not have. An empty array is the flat list without groups. - A key taken out keeps its edits and the moves into it: they come back with the key. Until then its methods stand in the group of their type, and
php artisan webx:doctornames the key.
The panel's edits stay in the table payment_groups, over the config, so a deploy does not undo them.
Official files of the marks
The marks are files of resources/marks/ of the package, based on Simple Icons. Where a payment system or your acquirer asks for its official file, give the mark another file from a service provider of the site. A key registered again keeps its place on the page:
use WebxUi\Payments\PaymentMarks;
$this->callAfterResolving(PaymentMarks::class, static function (PaymentMarks $marks): void {
$marks->register('visa', 'Visa', resource_path('payment-marks/visa.svg'));
});The file has to be an SVG. No code depends on what it draws: the width comes from its viewBox, or from width and height without one, every mark stands 24 pixels high, and a new file gets a new address, so no cache holds the old one. A key without a file is drawn as a plate with its words, as Приват24, ПриватБанк and monobank are. In the package itself a file is replaced by putting another under the same name. The trademarks belong to their owners and are not under the package's MIT licence: see resources/marks/NOTICE.md.
Which marks a method shows is decided by its type, not by a manager. A method whose only mark is Apple Pay is hidden by the buyer's browser where Apple Pay does not work; Google Pay is always shown.
The exchange journal
Payments → Gateway exchange lists every event: what went out to the gateway, what came in, what the buyer's browser brought, and the shop's own decisions. Filters by gateway, method, kind, direction, the order's number or the attempt; «Signature errors» and «Errors only» at a click. An event opens with its request, its answer and the whole trace it belongs to.
- The headers and bodies are behind their own permission,
payments.exchange— by default only the super-admin has it. Without it the list and the decisions are visible, the bodies are not. - The keys of the gateway are masked wherever they stand — as they are, url-encoded, in JSON, in base64 — and so are
Authorization, cookies and card numbers. A signature is kept: it is the evidence. - A body is kept whole up to 1 MB; a longer one is cut, with its full size and sha256.
- Nothing removes an event by itself, and nobody edits one. A journal older than a year may be pruned by hand:
php artisan webx:payments:exchange-prune --days=365 --dry-run
php artisan webx:payments:exchange-prune --days=365webx:doctor notes a journal over 1 GB. WEBX_PAYMENTS_EXCHANGE_CONNECTION moves it to another database; empty, it writes through a copy of the default connection, so that a rolled-back status change never takes its event with it.
Permissions
| Permission | Opens |
|---|---|
payments.view | methods without keys, attempts, the exchange without bodies, the tab «Payment» of an order |
payments.manage | the methods: on and off, order, words, rules, limits, settings, keys and their test |
payments.exchange | raw headers and bodies of the exchange; only the super-admin by default |
What is done to an order — mark paid, check with the gateway, capture or release, change the method, the payment link, a problem resolved — needs orders.manage.
Try it with the demo
php artisan webx:demo --module=paymentsBoth built-in methods switched on, the transfer with fake details («Shop A Ltd», an account of zeros at «Bank A»). webx:demo --remove --module=payments switches them off again.
On a development site, WEBX_PAYMENTS_FAKE_GATEWAY=true in .env adds «gateway A» — the fake gateway of the tests — and the demo switches its three methods on with its test key: «Card (gateway A)», which can hold money, «Gateway A page», which approves first, and «Gateway A (unsigned)», whose notifications are not believed. Its «page» at /pay/fake/{uuid} has a button for each thing a gateway does: pay, decline, cancel, pay with the notification later, the notification again, a bad signature, a payment of nobody's, hold, approve. Each lands in the journal as a real gateway's would. Never in production: off there whatever .env says, and its payments move no money.
Agents
With the panel and webx-ui/mcp installed, an agent works through the same doors as the panel. Reading tools are behind payments:read and payments.view, writing ones behind payments:write; what is done to an order needs orders.manage. Every write takes dry_run.
| Tool | What it does |
|---|---|
payments_methods_list | methods in the checkout's order: type, titles, rules, limits, keys only as «set» |
payments_methods_save | create a method of a type, or change one, its group too; never its keys; warnings |
payments_methods_reorder | the order of methods inside their groups, or moved as in the panel (groups) |
payments_groups_list | the groups in their order: words, pictogram, methods, marks |
payments_groups_save | a group's titles, descriptions and pictogram; never creates or removes one |
payments_groups_reorder | the order of the groups |
payments_available | the methods of a cart or an amount and a delivery, each with why it is not offered |
payments_list | attempts by order, method, status, problem |
payments_get | an attempt with its events |
payments_check | ask the gateway about an attempt in progress or held |
payments_capture | capture a hold, all or a part; a dry run says whether it pays the order |
payments_void | release a hold |
payments_mark_paid | mark an order paid by transfer: document, day, note |
payments_switch_method | another method for an order awaiting payment |
payments_pay_link | the signed link to the order's page of payment |
payments_problems | attempts with problems, open or resolved |
payments_resolve_problem | mark a problem settled, with a note |
payments_exchange_list | events of the journal by method, kind, decision, order, payment — no bodies |
payments_exchange_get | one event or a trace with its headers and bodies (up to 64 KB), payments.exchange |
The resource payments://catalog lists the types of methods and what each can do, the gateways installed, the groups, the pictograms, the marks and the delivery rules. A gateway's key never passes through an agent: no answer carries it and no tool takes it.
Letters
With webx-ui/module-notifications, the letters of orders get payment_url (the link «Pay»), payment_instructions (the transfer's details) and payment_method. The event payments.problem tells the managers about a problem; its channels are the setting «Payments» of the site's settings.
Read more
- Payment gateways — writing a satellite of a gateway and its contract test.
- LiqPay — the first gateway: keys, the switches of the page, the hold.
- Orders — milestones, the balance, top-ups.
- Checkout — the steps and the slots.
- The payments spec —
WEBX_UI_MODULE_PAYMENTS.md. - AI agents — connecting an agent, scopes and permissions.