LokiCheckout_Opayo
Opayo Pi card payments for the Loki Checkout on Magento 2.4.x.
This module integrates the Opayo (Elavon) Pi REST API as a Magento Payment Provider Gateway. Card details are tokenised directly in the browser against Opayo; Magento only ever receives a single-use Merchant Session Key and card identifier. Raw card numbers, expiry dates and CVV values are never sent to, logged by, or stored in Magento.
Frontend note (Hyvä / Loki Checkout): This store uses the Loki Checkout (Alpine.js + Loki Components), not the Luma/Knockout checkout. The payment form is implemented as a Loki Component (Context / ViewModel / Repository + PHTML + Alpine), not a
Magento_Checkoutknockoutmethod-renderer. Styling uses Tailwind utility classes; there is intentionally no_module.less(inert under Hyvä).
Requirements
- Magento Open Source / Adobe Commerce 2.4.x
- PHP 8.2+
loki-checkout/magento2-core^2.0 (the Loki Checkout)- An Opayo/Elavon Pi merchant account (vendor name, integration key, integration password)
- Optional:
magento/module-cspfor Content-Security-Policy whitelisting
Installation
Composer (packaged separately):
composer require loki-checkout/magento2-opayo
Enable and deploy:
bin/magento module:enable LokiCheckout_Opayo bin/magento setup:upgrade bin/magento setup:di:compile bin/magento setup:static-content:deploy -f bin/magento cache:clean
If you develop against a Loki Checkout Hyvä theme, rebuild the Tailwind CSS for the active checkout theme after enabling the module.
Configuration
Stores → Configuration → Sales → Payment Methods → Opayo (Loki Checkout)
| Field | Notes |
|-------|-------|
| Enabled | Turns the method on. |
| Title | Shown in the checkout. |
| Environment | Sandbox or Production — selects the API base URL. |
| Vendor Name | Your Opayo vendor name. |
| Integration Key | Stored encrypted, masked in Admin. |
| Integration Password | Stored encrypted, masked in Admin. |
| Payment Action | Authorize Only (Deferred) or Authorize and Capture (Payment). |
| 3-D Secure Enabled | Requests 3DS on the initial transaction. |
| Debug Logging | Writes masked diagnostics to var/log/lokicheckout_opayo.log. |
| Transaction Status Reconciliation | Enables the reconciliation cron. |
| Reconciliation Min Order Age / Batch Size | Cron tuning. |
| API Connection Timeout / Request Timeout | Explicit HTTP timeouts (seconds). |
| New Order Status | Initial order status. |
| Sort Order | Position in the payment list. |
| Applicable / Specific Countries | Country restrictions. |
| Minimum / Maximum Order Total | Order-total restrictions. |
All values are read by store id and support website/store scope. Credentials use Magento encrypted configuration fields and are never exposed to the browser.
Sandbox setup
- Set Environment = Sandbox.
- Enter your sandbox vendor name / integration key / password.
- Use Opayo test cards for card entry and 3-D Secure testing.
Production setup
- Set Environment = Production.
- Enter your live credentials.
- Ensure HTTPS is enforced and the CSP hosts (below) are whitelisted.
Checkout flow
- The shopper selects Opayo in the Loki Checkout.
- The Alpine component requests a Merchant Session Key from Magento
(
POST lokicheckout_opayo/session/create, CSRF form-key + rate-limited). Magento calls Opayo server-side with the (encrypted) credentials and returns only{ success, merchantSessionKey, expiresIn }. - The browser loads Opayo's
sagepay.jsand sends the raw card details directly to Opayo, receiving acardIdentifier. Raw card data never touches Magento. - The component stores only
merchant_session_key+card_identifieron the quote payment (via the componentRepository::saveValue()). - Magento places the order. The gateway
authorize(Deferred) orsale(Payment) command builds the transaction from the server-side order (amount, currency, billing address, names) and sends it to Opayo. - Response handlers persist the Opayo transaction id and safe metadata; the single-use session key + card identifier are removed.
3-D Secure flow
- Opayo returns
3DAuth→ the order stays inpending_payment; minimal challenge data (ACS URL,cReq) is persisted; the payment is not marked successful. - The shopper is redirected to a secure challenge page
(
lokicheckout_opayo/threedsecure/challenge, authorised via the checkout session order match + a per-order access token) which posts to the issuer's ACS. - The ACS returns to
lokicheckout_opayo/threedsecure/callback, which submits the continuation to Opayo server-side, validates the final state, and only then marks the payment authorised/captured. - Failed, abandoned, timed-out or cancelled challenges leave the order pending for reconciliation. Repeated callbacks are made idempotent by a per-order lock.
Capture flow (Admin)
Creating an invoice on a Deferred authorization issues an Opayo release
instruction against the original transaction id. Partial capture is supported only
where the Opayo account allows it (see API details to verify).
Refund flow (Admin)
A credit memo issues an Opayo Refund transaction that references the settled
original transaction id (distinct from a void). Full and partial refunds are supported
where the account allows; duplicate refunds are guarded by a per-order lock and the
unique vendorTxCode.
Void and cancel behaviour
These are deliberately not mapped to a single API call:
| Magento action | Opayo operation |
|----------------|-----------------|
| Void an authorization (same-day, pre-settlement) | void instruction |
| Cancel an incomplete / 3DS-pending transaction | abort instruction |
| Cancel a pre-authorization Magento order | Magento order cancel |
| Refund a settled payment | Refund transaction |
Cron and reconciliation behaviour
When enabled, lokicheckout_opayo_reconcile_pending (every 15 min) processes a
limited, locked batch of stale pending_payment Opayo orders older than the
configured age and reconciles them against the authoritative Opayo status via a
status lookup (OrderReconciler). It never creates a second transaction and logs
only masked data. Authoritative outcomes are applied to Magento's own order state:
Ok→ the order is invoiced/captured and moved out ofpending_payment;Rejected/NotAuthed/Error→ the order is cancelled;- any in-flight/unknown status or a pending 3-D Secure challenge → left untouched for the next run.
Content Security Policy
The module ships etc/csp_whitelist.xml allowing the minimum required hosts:
script-src:https://pi-test.sagepay.com,https://pi-live.sagepay.com(SDK)connect-src:https://pi-test.sagepay.com,https://pi-live.sagepay.com(browser → Opayo tokenisation)frame-src: the same hosts (3-D Secure challenge). Issuer ACS domains vary; tighten per acquirer where possible.
No broad wildcards are used. TODO-VERIFY the exact SDK host/paths and any
additional ACS frame-src hosts against current Opayo documentation.
Security considerations (PCI boundaries)
- Raw card data (PAN / expiry / CVV) is sent only from the browser to Opayo — never to Magento, never to logs, never to quote/order tables, never to storage/cookies.
- CVV is never stored under any circumstance.
- Integration credentials are encrypted, read server-side only, and never sent to the browser or written unmasked to logs.
- All trusted values (amount, currency, increment id, billing/shipping address) are built from the server-side quote/order, never from browser input.
- HTTPS is required. CSRF is enforced on the session-key endpoint (form key). The 3-D Secure callback authorises via the checkout-session order match + a per-order access token (not the predictable increment id alone).
- The session-key endpoint is rate-limited for basic abuse protection.
- Uncertain results (timeouts / ambiguous responses) are not auto-retried and do not auto-cancel the order — they are left for status reconciliation to avoid a double charge.
- This module implements PCI-friendly boundaries (card data isolated to the browser ↔ Opayo). It does not claim any specific PCI compliance level; assess your own scope.
Logging behaviour
Dedicated log file var/log/lokicheckout_opayo.log, gated on the Debug flag for
debug/info; errors are always logged. A recursive DataMasker masks integration
password, Authorization headers, Merchant Session Key, card identifier, PAN, CVV,
expiry and unnecessary PII in every logged payload.
Idempotency & duplicate protection
- Frontend disables the place-order button during async operations.
- A deterministic
vendorTxCode(derived from the order increment id) lets Opayo reject blind resubmissions. - Initial transaction creation, capture, refund, void and 3-D Secure completion run
under a Magento
LockManagerlock keyed per order. - After an uncertain result the module performs a status lookup rather than retrying.
- Repeated 3-D Secure callbacks are safe (idempotent completion).
Testing
Unit tests (mock all external API calls; no real credentials required):
# Docker Compose docker-compose exec -T php-test bash -c 'cd /var/www/html && \ vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist \ vendor/loki-checkout/magento2-opayo/Test/Unit'
Coverage includes: config + URL selection, amount conversion (0/2/3-decimal currencies), address mapping (incl. virtual orders without a shipping address), Merchant Session Key handling, authorize/sale/capture/refund request building, transaction-status validation, 3-D Secure-required responses, success/declined/ malformed responses, HTTP timeout behaviour, sensitive-data log masking and duplicate-transaction prevention.
Troubleshooting
- "Unable to initialize card payment." — check credentials/vendor name and
environment; inspect
var/log/lokicheckout_opayo.log(Debug on). - Order stuck in
pending_payment— a 3-D Secure challenge was not completed; enable reconciliation or check the transaction in the Opayo dashboard. - SDK/tokenisation blocked — verify the CSP hosts are whitelisted and HTTPS is on.
Known limitations
- Exact Opayo Pi endpoint paths, field names and enum values are isolated behind
Api/EndpointsandApi/FieldMapand markedTODO-VERIFY— confirm against current Opayo documentation before production use. - No Vault (stored-card) support in this release.
Upgrade considerations
- API endpoint/field changes should only require edits to
Api/Endpoints/Api/FieldMap. - Re-run
setup:upgrade,setup:di:compileand static-content deploy after upgrades.
API details to verify against current Opayo Pi documentation
The following are marked TODO-VERIFY in the code and must be confirmed:
- API version segment and base hosts (
Api/Endpoints). - Endpoint paths:
merchant-session-keys,transactions,transactions/{id}/instruction,transactions/{id}/3d-secure,transactions/{id}(status). transactionTypevalues (Payment/Deferred/Refund) and instruction types (release/void/abort), including whether partial release is supported.- The
apply3DSecureenum values and the 3DS continuation field names (cReqfor 3DS2 vspaResfor legacy) and the ACS post fields. - Address field names and maximum field lengths (
Service/AddressMapper). vendorTxCodemaximum length.- The exact
sagepay.jsSDK URL/version and its browser API (sagepayOwnForm(...).tokeniseCardDetails(...), response shape). - The refund payload shape (
referenceTransactionId).
@dev
to the composer command.
Support
For getting support, create an Issue under the following project URL:
https://gitlab.yireo.com/loki-checkout/LokiCheckout_Opayo.git
Configuration Options
The following options of this module can be configured via the Store Configuration:
Loki Components
The following LokiComponents are created or references in this module:
loki-checkout.payment.methods.lokicheckout_opayo.form
Module Dependencies
The following dependencies are declared in the module its `etc/module.xml` file:
LokiCheckout_Core
Loki_Base
Loki_Components
Loki_CssUtils
Loki_FieldComponents
Loki_MapComponents
Magento_Backend
Magento_Catalog
Magento_Checkout
Magento_CheckoutAgreements
Magento_Config
Magento_Customer
Magento_Directory
Magento_Eav
Magento_Newsletter
Magento_Payment
Magento_Quote
Magento_Sales
Magento_Shipping
Magento_Store
Magento_Tax
Magento_Vault
Yireo_CspUtilities
Composer details
LokiCheckout_Opayoloki-checkout/magento2-opayo
php: >=8.2
ext-json: *
ext-pcre: *
magento/framework: ^103.0
magento/module-payment: *
magento/module-sales: *
magento/module-quote: *
magento/module-checkout: *
magento/module-store: *
magento/module-directory: *
magento/module-webapi: *
loki-checkout/magento2-core: ^2.0