SL-374/SL-375 Card flow rework: inline Fields, Payment Page routing, remove Transaction Interface#336
Conversation
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).
There was a problem hiding this comment.
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.
| success: function (response) { | ||
| try { | ||
| window.location = JSON.parse(response).url; | ||
| } catch (e) { | ||
| $('#' + SLOT_ID + ' .internal-error').show(); | ||
| } | ||
| } |
There was a problem hiding this comment.
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();
}
}| $('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(); | ||
| } | ||
| }); |
There was a problem hiding this comment.
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');| (function () { | ||
| if (typeof saferpay_field_access_token === 'undefined' || !saferpay_field_access_token) { | ||
| return; | ||
| } | ||
|
|
||
| var SLOT_ID = 'saferpay-inline-fields'; | ||
| var initialised = false; |
There was a problem hiding this comment.
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.
| (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'; |
| ' <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 + '">' + |
There was a problem hiding this comment.
Use the safe fallback variable safeHolderName to prevent potential ReferenceError exceptions if holder_name is not defined globally.
| ' <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 + '">' + |
| accessToken: saferpay_field_access_token, | ||
| url: saferpay_field_url, | ||
| placeholders: { | ||
| holdername: holder_name, |
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
af8c039 to
9b4953a
Compare
8915c31
into
feature/react-admin-settings
Part of SL-350 settings redesign. Phase 4 — card flow rework. Verified live on a TEST account (Business + Fields) incl. 3-D Secure.
Tickets
What changed
submitHostedFieldsAJAX flow. No-JS fallback is the Payment Page.iframe/successIFrame/failIFrame/hostedIframecontrollers,saferpay_iframe+ hosted-template tpl/js/css,example-cardassets, and the deadPaymentType::IFRAME/ControllerNameIFRAME branches. Kept the checkouthosted_fields.js(saved-card handling).Live verification (test store)
Known follow-ups
hosted_fields.jspath — recommend a smoke test.views/js/admin/dist) is gitignored / built in CI.