LokiCheckout_Opayo

PHP 8.1 - 8.4
Magento 2.4.7 | 2.4.8
CSP Yes
Integration Tests 11
Functional Tests 5
PHPStan 1
Magento PHPCS 9

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_Checkout knockout method-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-csp for 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

  1. Set Environment = Sandbox.
  2. Enter your sandbox vendor name / integration key / password.
  3. Use Opayo test cards for card entry and 3-D Secure testing.

Production setup

  1. Set Environment = Production.
  2. Enter your live credentials.
  3. Ensure HTTPS is enforced and the CSP hosts (below) are whitelisted.

Checkout flow

  1. The shopper selects Opayo in the Loki Checkout.
  2. 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 }.
  3. The browser loads Opayo's sagepay.js and sends the raw card details directly to Opayo, receiving a cardIdentifier. Raw card data never touches Magento.
  4. The component stores only merchant_session_key + card_identifier on the quote payment (via the component Repository::saveValue()).
  5. Magento places the order. The gateway authorize (Deferred) or sale (Payment) command builds the transaction from the server-side order (amount, currency, billing address, names) and sends it to Opayo.
  6. Response handlers persist the Opayo transaction id and safe metadata; the single-use session key + card identifier are removed.

3-D Secure flow

  1. Opayo returns 3DAuth → the order stays in pending_payment; minimal challenge data (ACS URL, cReq) is persisted; the payment is not marked successful.
  2. 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.
  3. 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.
  4. 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 of pending_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 LockManager lock 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/Endpoints and Api/FieldMap and marked TODO-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:compile and 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:

  1. API version segment and base hosts (Api/Endpoints).
  2. Endpoint paths: merchant-session-keys, transactions, transactions/{id}/instruction, transactions/{id}/3d-secure, transactions/{id} (status).
  3. transactionType values (Payment / Deferred / Refund) and instruction types (release / void / abort), including whether partial release is supported.
  4. The apply3DSecure enum values and the 3DS continuation field names (cReq for 3DS2 vs paRes for legacy) and the ACS post fields.
  5. Address field names and maximum field lengths (Service/AddressMapper).
  6. vendorTxCode maximum length.
  7. The exact sagepay.js SDK URL/version and its browser API (sagepayOwnForm(...).tokeniseCardDetails(...), response shape).
  8. The refund payload shape (referenceTransactionId).



    
WARNING: This module has no releases yet. If you want to use it, make sure to add the version @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
Magento module name
LokiCheckout_Opayo
Composer name
loki-checkout/magento2-opayo
Composer version
No release yet
Default branch
main
Requirements
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
Releases
Changelog

Last modified: July 28, 2026