Skip to content

PRE-3601: Let merchants narrow the Scalapay amount range at checkout - #119

Merged
adumont-payplug merged 1 commit into
developfrom
feature/PRE-3601_overload_min_max_scalapay
Sep 14, 2026
Merged

adumont-payplug merged 1 commit into
developfrom
feature/PRE-3601_overload_min_max_scalapay

Conversation

@adumont-payplug

@adumont-payplug adumont-payplug commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Description

Scalapay was offered for any cart amount within the bounds the Payplug account authorizes. Merchants can now choose their own min/max inside those bounds, and Scalapay is hidden at checkout for carts outside them — same UX and same back-office placement as the existing Oney thresholds.

Every bound is sourced from GET /account (amounts.scalapay, falling back to the account default): there is no hardcoded range anywhere. The merchant can only narrow the authorized range, never widen it, so an out-of-range custom limit is ignored in favour of the account's own.

Changes:

  • Checkout gating (Scalapay only). The amount lookup was duplicated in PaymentMethod::getPaymentTab() and ::getPaymentOption(); extracted it into PaymentMethod::getPriceLimit() (pure, behaviour-preserving) and overrode it in ScalapayPaymentMethod. One override now gates both the payment-option list (hides the method) and payment-tab creation (server-side guard), with no behaviour change for any other gateway.
  • Config. The merchant's choice lives in scalapay_custom_{min,max}_amounts, stored as EUR:<cents> like the Oney equivalents. Empty means "never customised", so the account range applies as-is. Registered for existing installs by upgrade-5.2.0.php.
  • Back office. getOption() now returns a top-level advanced_options carrying the thresholds block. value is the merchant's effective limit; default is always the account bound, which is what the widget validates against client-side — the same ceiling as the save-time check, so display and enforcement can't drift.
  • Save. Validates before writing anything and returns an error modal when min > max or either bound escapes the account range. Translations added in all 7 language files.
  • Dependency. payplug-ui-plugins-bo ^2.1.0^2.3.0. The locked 2.2.1 does not render advanced_options for a generic gateway, so the fields would simply not appear without this bump (UI side was done in payplug/payplug-ui-plugins#36).

Also fixed in passing: upgrade/upgrade-5.1.0.php shipped unlisted in module_files.csv (PRE-3456), and FilesHelper::clean() deletes any on-disk file absent from that list. It self-skips on localhost/ngrok, so this only bites real shops. Added both it and the new upgrade-5.2.0.php.

Review feedback addressed

The automated review raised three findings; all three are fixed in this branch.

  • [HIGH] src/actions/ConfigurationAction.php — a partial payload bypassed validation entirely. The upfront range check only ran when enable_scalapay, scalapay_min_amounts and scalapay_max_amounts were all present, while the switch-case below persists each amount on its own gate. A payload carrying a single amount was therefore saved unchecked — and since getScalapayPriceLimit() clamps each bound independently against the account range, a minimum saved above the maximum in force was never caught, silently hiding Scalapay for every cart with no error to the merchant. The validation gate is now as wide as the persistence one, and an amount the payload omits is resolved from the limits currently in force, so the two bounds are still compared against each other. A bound the account does not expose comes back as false, hence 0 once cast, which isAmount() rejects — an unusable range fails the save rather than passing it.
  • [LOW] src/models/classes/paymentMethod/PaymentMethod.php — docblock splice. getPriceLimit() had landed between set()'s docblock and set() itself, leaving set() undocumented and its docblock attached to the wrong method. getPriceLimit() now sits above, and set() keeps its own docblock.
  • ⚠️ [LOW] src/actions/ConfigurationAction.php — pre-existing Oney no-op, fixed here. This one is outside the original scope of this PR. The Oney branch tested isAmount()'s return array rather than its ['result'] flag; the array is always truthy, so out-of-range Oney custom amounts were persisted regardless of validity. The flag is now read out, so an invalid Oney amount is no longer written. The reviewer suggested deferring this to its own ticket — flagging the scope change explicitly in case you would rather it were split back out. Note there is no modal.oney.thresholds.* translation key, so an invalid Oney amount is silently skipped rather than raising an error modal the way Scalapay does; adding that modal would mean new keys in every language file and is deliberately left out.

Related Issue

Ticket: PRE-3601

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 📦 Dependency update

✅ Quality Checklist

Local Environment & Hooks

  • Local Git hooks (CaptainHook) are installed and executed cleanly.
  • Commit messages strictly follow the (PRE|SMP)-XXXX: description pattern.
  • Core configuration files (phpstan.neon / .php-cs-fixer.php) were generated successfully from .dist templates.

Testing & Code Quality

  • Coding style rules have been applied locally (composer cs:fix).
  • Static analysis checks pass with no new regressions (vendor/bin/phpstan).
  • I have added/updated unit or integration tests if applicable.
  • I have verified these changes locally on a native PrestaShop environment.

CI/CD Deployment Context

  • The CI pipeline passes fully on GitHub.

Screenshots (if applicable)

Notes for Reviewer

Test results: 2712 tests / 4076 assertions, 0 failures (baseline 2691, so +21 new). PHPStan [OK] No errors. php-cs-fixer --dry-run clean. The 2 PHPUnit warnings ("No tests found in class … LinkHelper") are pre-existing — verified against a stashed baseline.

New coverage: PaymentMethod/getPriceLimitTest, and ScalapayPaymentMethod/ with getScalapayPriceLimitTest, getPriceLimitTest, getOptionTest, setCustomScalapayLimitTest, plus 8 cases in saveActionTest — the 5 added for the review covering a Scalapay minimum submitted alone above the ceiling in force, a maximum submitted alone below the floor in force, a valid single-amount payload, and the Oney branch persisting (resp. not persisting) an in-range (resp. out-of-range) amount. All 5 were written first and confirmed failing against the previous code. A scalapay entry was added to the shared amounts fixture in BasePaymentMethod — the full suite is green with it.

Two boxes left unticked deliberately:

  1. Native PrestaShop environment — the BO rendering and a real checkout were not verified in a running shop. The contract against plugins-ui 2.3.0 is covered by unit tests and by reading the components, not by a browser. Worth a manual pass on a real install before merge, especially the field display and the save-error modal.
  2. CaptainHook — the hooks abort with a PHP parse error in vendor/captainhook/captainhook/src/Console/Command.php under the PHP 7.2 CLI on my machine (unrelated to this PR; composer test/stan/cs-fixer all run fine).

Changelog: the Oney isAmount() fix has no changelog.md entry yet — it is a merchant-visible behaviour change (out-of-range Oney custom amounts stop being saved), so it likely deserves its own bugfix line under 5.2.0.

Worth a specific look: the module_files.csv fix touches a file the feature also needs, so the two changes overlap there. Happy to split the upgrade-5.1.0.php line into its own PR if you'd rather keep this one single-purpose.

Reused asset: the thresholds illustration reuses views/img/oney/payplug-thresholds.jpg. It's a generic amount-range slider with no Oney branding — flagging in case design wants a Scalapay-specific one.

@jhoaraupp jhoaraupp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review (3 findings below, one thread each).

Comment thread src/actions/ConfigurationAction.php Outdated
Comment thread src/models/classes/paymentMethod/PaymentMethod.php Outdated
Comment thread src/actions/ConfigurationAction.php
@adumont-payplug
adumont-payplug force-pushed the feature/PRE-3601_overload_min_max_scalapay branch from 9acac64 to ce65dfb Compare September 14, 2026 09:58
@adumont-payplug
adumont-payplug merged commit 904fe3c into develop Sep 14, 2026
13 checks passed
@adumont-payplug
adumont-payplug deleted the feature/PRE-3601_overload_min_max_scalapay branch September 14, 2026 12:44
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.

2 participants