Skip to content

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 ​

bash
composer require webx-ui/module-payments
php artisan migrate

It 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.php has to print the block prompt: $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.php has to handle the state declined. 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:

php
'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.
  • title is a key of a dictionary or the words themselves.
  • pictogram is one of card, cash, document, bank, wallet, truck, store, qr.
  • The site's groups replaces 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:doctor names 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:

php
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:
bash
php artisan webx:payments:exchange-prune --days=365 --dry-run
php artisan webx:payments:exchange-prune --days=365

webx: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 ​

PermissionOpens
payments.viewmethods without keys, attempts, the exchange without bodies, the tab «Payment» of an order
payments.managethe methods: on and off, order, words, rules, limits, settings, keys and their test
payments.exchangeraw 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 ​

bash
php artisan webx:demo --module=payments

Both 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.

ToolWhat it does
payments_methods_listmethods in the checkout's order: type, titles, rules, limits, keys only as «set»
payments_methods_savecreate a method of a type, or change one, its group too; never its keys; warnings
payments_methods_reorderthe order of methods inside their groups, or moved as in the panel (groups)
payments_groups_listthe groups in their order: words, pictogram, methods, marks
payments_groups_savea group's titles, descriptions and pictogram; never creates or removes one
payments_groups_reorderthe order of the groups
payments_availablethe methods of a cart or an amount and a delivery, each with why it is not offered
payments_listattempts by order, method, status, problem
payments_getan attempt with its events
payments_checkask the gateway about an attempt in progress or held
payments_capturecapture a hold, all or a part; a dry run says whether it pays the order
payments_voidrelease a hold
payments_mark_paidmark an order paid by transfer: document, day, note
payments_switch_methodanother method for an order awaiting payment
payments_pay_linkthe signed link to the order's page of payment
payments_problemsattempts with problems, open or resolved
payments_resolve_problemmark a problem settled, with a note
payments_exchange_listevents of the journal by method, kind, decision, order, payment — no bodies
payments_exchange_getone 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 ​

Released under the MIT License.