Skip to content

SL-388 Read checkout payment methods from storage instead of the API - #346

Open
TLabutis wants to merge 2 commits into
feature/react-admin-settingsfrom
SL-388-stop-calling-management-api-on-checkout
Open

SL-388 Read checkout payment methods from storage instead of the API#346
TLabutis wants to merge 2 commits into
feature/react-admin-settingsfrom
SL-388-stop-calling-management-api-on-checkout

Conversation

@TLabutis

Copy link
Copy Markdown
Contributor

Self-Checks

  • I have performed a self-review of my code.
  • I have updated/added necessary technical documentation in the README file.

JIRA task link

SL-388

Summary

The checkout called the Saferpay Management API on every payment step render to get the payment method list. The logo and the supported currencies are now stored alongside the method, so the list is built locally and the account is only asked when nothing is stored yet.

QA Checklist Labels

  • Bug fix?
  • New feature?
  • Improvement?
  • Technical debt?
  • Reusable?
  • Covered by tests?

QA Checklist

Part of the same working tree as the SL-386 card brand grouping work, which is deliberately not in this PR and ships on its own branch. Nothing here depends on it and nothing here reads SAFERPAY_GROUP_CARDS.

Additional Context

Cause

hookPaymentOptions resolved SaferPayObtainPaymentMethods and called obtainPaymentMethods() on every render. PrestaShop re-renders the payment step over AJAX on every address and carrier change, so a single order produced several Management API round trips, each one blocking the payment step behind a remote call, and each one able to empty the payment step if the account was briefly unreachable.

Everything the checkout actually needed from that response, beyond the method name it already had in saferpay_payment, was two fields: logoUrl and currencies.

Changes

  • saferpay_payment gains logo_url VARCHAR(255) and currencies VARCHAR(1024), in Installer for fresh installs and in upgrade/install-2.1.0.php for existing shops.
  • SaferPayRefreshPaymentsService now calls obtainPaymentMethods() instead of obtainPaymentMethodsNamesAsArray() and persists both fields. The currency list is comma joined; pSQL() is applied per value because SaferPayPaymentRepository::insertPayment() goes through Db::insert(), which does not escape.
  • New SaferPayStoredPaymentMethods::getPaymentMethods() reads the stored rows and returns the same array shape obtainPaymentMethods() returned, so nothing downstream in the hook changed. If no row carries a logo it triggers one refresh and re-reads, which is how a shop that has never opened the settings page still recovers instead of showing an empty payment step.
  • New SaferPayPaymentRepository::getAllPaymentMethods().
  • SaferPayRefreshPaymentsService also reads every stored row rather than only the active ones when it snapshots the current flags. Before this, a refresh dropped the state of any method the merchant had switched off, so it came back enabled. That is a behaviour fix, not a side effect of the caching.

Why the migration is in install-2.1.0.php and not a new 2.1.1 script

2.1.0 is still unreleased (last tag is v2.0.3), and PrestaShop only runs an upgrade file when file_version <= module_version (classes/module/Module.php). A install-2.1.1.php at module version 2.1.0 would be skipped entirely, which is the same trap #340 fixed on this branch. The module version is therefore left at 2.1.0 and the changelog line goes into the 2.1.0 section.

The backfill runs one refreshPayments() inside a try/catch and logs on failure rather than failing the upgrade. An unreachable account at upgrade time must not roll the version back, and the lazy refresh in SaferPayStoredPaymentMethods covers that shop on its first checkout.

Testing

PS 1.7.6.1 / PHP 7.2.34, live Saferpay test account, 24 payment methods on the account.

Real upgrade path, not a simulated one. Dropped both columns, set ps_module.version back to 2.0.3, then ran php bin/console prestashop:module upgrade saferpayofficial:

Upgrade action on module saferpayofficial succeeded.
registered version: 2.1.0
columns: id_saferpay_payment, name, active, logo_url, currencies
rows: 24   with logo: 24   with currencies: 21

The three rows without currencies are APPLEPAY, GOOGLEPAY and CLICKTOPAY, exactly SaferPayConfig::WALLET_PAYMENT_METHODS, which the hook overrides with every shop currency. No method is dropped from the checkout because of an empty list. No 2.1.0 upgrade entry in ps_log, so the backfill did not fall back.

Checkout, both grouping states. Called hookPaymentOptions against a real cart in EUR:

SAFERPAY_GROUP_CARDS=0 -> 19 options, every one with a logo (per brand: Visa, Mastercard, Amex, Diners, Jcb, Maestro, ...)
SAFERPAY_GROUP_CARDS=1 -> 13 options, brands collapsed into Cards

The ungrouped path is the one this branch runs on its own, since the grouping default stays at 0 here. Both were checked because the stored rows now feed both.

Lazy refresh. Blanked logo_url on every row and rendered the payment step: isPopulated() returned false, one refresh ran, the methods came back. That is the recovery path for a shop whose upgrade backfill could not reach the account.

php -l clean on all changed files. php-cs-fixer --dry-run clean, run inside the PHP 7.2 container because the host PHP 8.4 is above the fixer's supported range.

Frontend Changes

No visual change. The payment step renders the same options from the same logos; only the source of the data changed.

Tadas Labutis added 2 commits August 27, 2026 12:36
PrestaShop re-renders the payment step over AJAX on every address and
carrier change, and hookPaymentOptions called SaferPayObtainPaymentMethods
on each render, so one order produced several Management API round trips.

The only things the checkout needed from that response beyond the method
name were the logo and the supported currencies. Both are now stored on
saferpay_payment as logo_url and currencies, written by
SaferPayRefreshPaymentsService, and read by the new
SaferPayStoredPaymentMethods, which falls back to one account call only
when nothing is stored yet. SaferPayRefreshPaymentsService now also reads
every stored row rather than the active ones alone, so a method the
merchant switched off keeps its flags across a refresh instead of
reappearing enabled.

The column migration and its backfill go into upgrade/install-2.1.0.php
rather than a new 2.1.1 script, because 2.1.0 is still unreleased and
PrestaShop skips an upgrade file whose version is above the module
version. A backfill failure is logged rather than fatal, since an
unreachable account must not break the upgrade and the checkout
repopulates the columns by itself.

The card brand grouping changes sitting on the same working tree are
deliberately left out and ship on their own branch.
A method that disappears from the Saferpay account is dropped from storage by
refreshPayments() without leaving any trace, so the merchant finds it gone from
both the settings page and the checkout with nothing to explain why, and support
cannot tell whether Saferpay stopped offering it or the module lost it.

refreshPayments() now diffs the stored names against the account list and logs
each removal through logRemovedPayments() before the tables are rebuilt, so a
failure part way through the inserts still leaves a record. A removal that was
enabled is a warning because it cost the shop a live checkout option, one that
was already disabled is a notice. Logger::warning() and Logger::notice() are not
gated by SaferPayConfig::isDebugMode(), so the rows reach Advanced Parameters >
Logs on a default install. getPaymentName() is extracted so the payment name
normalisation is shared by the comparison and the insert.

Left out: the reactive refresh on a rejected Initialize, which needs the
Saferpay ErrorName for an unavailable method confirmed against the sandbox
first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant