Skip to content

SL-374/SL-375 Card flow rework: inline Fields, Payment Page routing, remove Transaction Interface#336

Merged
justelis22 merged 8 commits into
feature/react-admin-settingsfrom
SL-350/settings-card-flow
Jul 10, 2026
Merged

SL-374/SL-375 Card flow rework: inline Fields, Payment Page routing, remove Transaction Interface#336
justelis22 merged 8 commits into
feature/react-admin-settingsfrom
SL-350/settings-card-flow

Conversation

@justelis22

Copy link
Copy Markdown
Collaborator

Part of SL-350 settings redesign. Phase 4 — card flow rework. Verified live on a TEST account (Business + Fields) incl. 3-D Secure.

Tickets

  • SL-374 — route cards to the Saferpay Payment Page when Custom Form is OFF; remove the legacy Transaction Interface entirely.
  • SL-375 — render Saferpay Fields inline in the default checkout; grouped single "Cards" form; remove the "Inline Layout with Card" hosted-field style; retire the hosted Fields page.

What changed

  • Routing: Custom Form ON -> inline Fields; Custom Form OFF / non-Business -> Payment Page. Transaction Interface no longer selectable.
  • Inline Fields: a single shared Saferpay Fields form (the SDK binds to fixed element IDs, has no re-init) placed once and shown under the selected card. Submit is intercepted and routed through the existing submitHostedFields AJAX flow. No-JS fallback is the Payment Page.
  • Grouped cards: the "Cards" option renders one inline Fields form accepting all brands.
  • Admin: removed hosted-field style option 3; clamped stored value to 1-2 (legacy 3 degrades to default); repositioned + reworded the info box.
  • Removals: iframe/successIFrame/failIFrame/hostedIframe controllers, saferpay_iframe + hosted-template tpl/js/css, example-card assets, and the dead PaymentType::IFRAME / ControllerName IFRAME branches. Kept the checkout hosted_fields.js (saved-card handling).

Live verification (test store)

  • Ungrouped Visa, new card + 3DS -> order confirmed.
  • Regression after the file removals -> order confirmed.
  • Grouped "Cards", Mastercard + 3DS -> order confirmed.
  • Switching between card options keeps the fields working (no iframe reload).

Known follow-ups

  • The "Hosted field style" (Classic/Labeled) selector no longer affects the inline form (inline uses theme styling); consider wiring or removing it.
  • Saved-card inline recharge uses the unchanged hosted_fields.js path — recommend a smoke test.
  • Compiled bundle (views/js/admin/dist) is gitignored / built in CI.

Justas added 5 commits July 10, 2026 09:57
Custom Form OFF (or non-Business) cards now fall through to the Saferpay
Payment Page; Custom Form ON keeps Saferpay Fields. The Transaction
Interface (iframe) is no longer selectable at checkout.

Routing-only step. Removal of the now-dead Transaction Interface
controllers/templates/return-flow branches and SL-375 (inline Fields)
follow after live verification on a TEST account.
…word hosted-field info box

- Remove hosted-field style option 3 (Inline Layout with Card) from the admin selector and preview
- Clamp stored SAFERPAY_HOSTED_FIELDS_TEMPLATE to 1-2 on save and on display (legacy value 3 degrades to default)
- Reposition the info box next to the Hosted field style selector and apply final wording (verified live)

Verified on the test store: General Settings renders, selector shows Classic/Labeled only, info box repositioned. Inline Fields runtime conversion + hostedIframe retirement tracked separately.
Add a single shared Saferpay Fields form (the SDK binds to fixed element IDs and has
no re-init, so only one form may exist) that is placed once below the payment options
and shown under whichever Custom-Form card is selected. Place-once + show/hide avoids
reparenting the SDK iframes (which would reload them). Submit is intercepted and routed
through the existing submitHostedFields AJAX flow.

Verified live on the test store: Fields render inline under Visa/Mastercard, SDK
initialises, new-card payment + 3-D Secure completes (order confirmed), and switching
between card options keeps the fields working.

The hosted iframe page remains as a no-JS fallback for now; its retirement + the
Transaction Interface file removal follow.
…ields page

The card fields now render inline in the checkout, so the redirect targets are gone:
- Delete the Transaction Interface flow: iframe controller, saferpay_iframe template/CSS/JS,
  successIFrame/failIFrame controllers, and the PaymentType::IFRAME / ControllerName IFRAME
  success/fail branches in return.php, ajax.php and failValidation.php.
- Retire the hosted Fields page: hostedIframe controller + template1/2/3 (tpl/js/css) +
  example-card assets + template_submit.js. The shared checkout hosted_fields.js (saved-card
  handling) is kept.
- The no-JS redirect fallback for every method is now the Saferpay Payment Page.

Verified live: inline Fields new-card + 3-D Secure still completes end to end (order confirmed)
after the removals.
When card grouping is on (Business licence), the single 'Cards' option now renders the
inline Saferpay Fields form and accepts all card brands, instead of redirecting to the
Payment Page. Individual cards continue to use Fields per their Custom Form toggle.

Verified live: grouped 'Cards' checkout with a Mastercard test card + 3-D Secure completed
(order confirmed).

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request removes the legacy iframe and hosted iframe payment flows, deleting several obsolete controllers, templates, and assets, and introduces inline rendering of the Saferpay Fields card form directly in the default PrestaShop checkout. Feedback on the new inline-fields.js file highlights a critical bug where calling JSON.parse on an already-parsed AJAX response will throw a SyntaxError, and an issue where the inline form is not displayed on page load if a card option is pre-selected. Additionally, it is recommended to add a safe fallback for the global holder_name variable to prevent potential ReferenceError exceptions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +146 to +152
success: function (response) {
try {
window.location = JSON.parse(response).url;
} catch (e) {
$('#' + SLOT_ID + ' .internal-error').show();
}
}

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.

critical

Critical Bug: Since the server response is rendered with ajaxRender (which sets the application/json header), jQuery automatically parses the response into a JavaScript object. Calling JSON.parse() on an already-parsed object will convert it to the string "[object Object]" and throw a SyntaxError, causing the checkout to always fail with an internal error.

We should check if the response is a string before parsing it, or directly access the url property if it is already an object.

                    success: function (response) {
                        try {
                            var data = typeof response === 'string' ? JSON.parse(response) : response;
                            if (data && data.url) {
                                window.location = data.url;
                            } else {
                                $('#' + SLOT_ID + ' .internal-error').show();
                            }
                        } catch (e) {
                            $('#' + SLOT_ID + ' .internal-error').show();
                        }
                    }

Comment on lines +170 to +179
$('body').on('change', 'input[name="payment-option"]', function () {
var $option = $('#pay-with-' + $(this).attr('id') + '-form');
var $form = $option.find('form').first();

if ($form.length && isInlineFieldsOption($form) && selectedCardValue($form) <= 0) {
revealUnder();
} else {
hide();
}
});

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.

high

Issue: If a Saferpay card option is pre-selected on page load (which is common in PrestaShop checkout or custom themes), the inline fields form will not be displayed because the change event listener has not been triggered yet.

To fix this, we should trigger the change event on the currently checked payment option immediately after registering the event listener.

        // Reveal / hide the shared form as payment options are selected.
        $('body').on('change', 'input[name="payment-option"]', function () {
            var $option = $('#pay-with-' + $(this).attr('id') + '-form');
            var $form = $option.find('form').first();

            if ($form.length && isInlineFieldsOption($form) && selectedCardValue($form) <= 0) {
                revealUnder();
            } else {
                hide();
            }
        });

        // Trigger the change event on load to handle pre-selected payment options.
        $('input[name="payment-option"]:checked').trigger('change');

Comment thread views/js/front/inline-fields.js Outdated
Comment on lines +33 to +39
(function () {
if (typeof saferpay_field_access_token === 'undefined' || !saferpay_field_access_token) {
return;
}

var SLOT_ID = 'saferpay-inline-fields';
var initialised = false;

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.

medium

Improvement: If holder_name is not defined globally (e.g., if the asset loader fails or under certain checkout configurations), referencing it directly will throw a ReferenceError and halt script execution.

It is safer to define a local fallback variable at the top of the IIFE, similar to how saferpay_internal_error is handled.

Suggested change
(function () {
if (typeof saferpay_field_access_token === 'undefined' || !saferpay_field_access_token) {
return;
}
var SLOT_ID = 'saferpay-inline-fields';
var initialised = false;
(function () {
if (typeof saferpay_field_access_token === 'undefined' || !saferpay_field_access_token) {
return;
}
var SLOT_ID = 'saferpay-inline-fields';
var initialised = false;
var safeHolderName = typeof holder_name !== 'undefined' ? holder_name : 'Holder name';

Comment thread views/js/front/inline-fields.js Outdated
Comment on lines +54 to +55
' <label for="fields-holder-name" class="sr-only">' + holder_name + '</label>' +
' <input class="form-control" id="fields-holder-name" readonly placeholder="' + holder_name + '" aria-label="' + holder_name + '">' +

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.

medium

Use the safe fallback variable safeHolderName to prevent potential ReferenceError exceptions if holder_name is not defined globally.

Suggested change
' <label for="fields-holder-name" class="sr-only">' + holder_name + '</label>' +
' <input class="form-control" id="fields-holder-name" readonly placeholder="' + holder_name + '" aria-label="' + holder_name + '">' +
' <label for="fields-holder-name" class="sr-only">' + safeHolderName + '</label>' +
' <input class="form-control" id="fields-holder-name" readonly placeholder="' + safeHolderName + '" aria-label="' + safeHolderName + '">' +

Comment thread views/js/front/inline-fields.js Outdated
accessToken: saferpay_field_access_token,
url: saferpay_field_url,
placeholders: {
holdername: holder_name,

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.

medium

Use the safe fallback variable safeHolderName to prevent potential ReferenceError exceptions if holder_name is not defined globally.

Suggested change
holdername: holder_name,
holdername: safeHolderName,

Justas added 2 commits July 10, 2026 11:42
Address review feedback:
- Handle an already-parsed AJAX response defensively (don't blindly JSON.parse an object).
- Reveal the shared Fields form for a payment option that is already selected on page load
  by triggering change on the checked option after binding.
- Fall back to a safe holder-name label if the global is undefined.

Verified live: new-card Visa + 3-D Secure completes end to end (order confirmed).
- Block place-order submit until all card fields (incl. holder name) are
  valid and show a clear, field-specific message instead of the raw SDK
  'cannot store data' error
- Render the Fields form under the selected card option, rebuilding and
  re-initialising when switching options
- Re-enable the place-order button after a blocked/failed submit
- Normalise field height, vertically centre the input text and add
  focus/invalid highlighting on the field iframes
- Add localized validation strings and version-tag the checkout assets
@justelis22 justelis22 force-pushed the SL-350/settings-card-flow branch from af8c039 to 9b4953a Compare July 10, 2026 12:22
@justelis22 justelis22 merged commit 8915c31 into feature/react-admin-settings Jul 10, 2026
1 of 3 checks passed
@justelis22 justelis22 deleted the SL-350/settings-card-flow branch July 10, 2026 14:10
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