From 23320ff13caaad0350bd790cd6adb1e2545ff1fc Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Tue, 14 Apr 2026 16:18:07 +0100 Subject: [PATCH] Bug 41705: Use PRG pattern for POS receipt printing to avoid popup blockers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous approach triggered popup blockers because the receipt window was opened via JavaScript after a page reload — browsers treat this as a non-user-initiated action. This patch reverses the flow: when FinePaymentAutoPopup is enabled, the payment form submits directly to a new tab via target="_blank" (a user-initiated action that browsers allow), the server processes the payment and redirects (303) to the receipt page, which auto-prints and closes itself, returning focus to the POS tab. When FinePaymentAutoPopup is disabled, the server redirects back to the POS page with payment details in the URL (standard PRG), displaying the existing success banner with print/email receipt buttons. Changes: - pos/pay.pl: Implement PRG redirect for both popup and non-popup flows, URI-encode redirect parameters, remove session-based state management - pos/pay.tt: Add target="_blank" when FinePaymentAutoPopup is enabled, reset form state after submission via same-scope setTimeout - pos/printreceipt.pl: Add error handling for missing receipts, pass autoprint parameter to template - pos/printreceipt.tt: Add conditional auto-print, manual print/close buttons, error display, and IntranetSlipPrinterJS preference support - slip-print.inc: Modernize jQuery to vanilla JS, remove obsolete Chrome setTimeout hack Test plan: 1. Enable FinePaymentAutoPopup system preference 2. Go to Point of Sale, add items, and complete a sale 3. Verify receipt opens in a new tab without popup blocker warnings 4. Verify receipt auto-prints and tab closes after printing 5. Verify original POS tab form is reset for next transaction 6. Disable FinePaymentAutoPopup 7. Complete another sale 8. Verify POS page shows success with Print/Email receipt buttons 9. Test receipt printing from other pages (circulation, fees) to confirm slip-print.inc change causes no regressions Sponsored-by: OpenFifth Signed-off-by: David Nind Signed-off-by: Andrew Fuerste Henry Signed-off-by: Pedro Amorim --- .../prog/en/includes/slip-print.inc | 3 +- .../intranet-tmpl/prog/en/modules/pos/pay.tt | 28 ++++++------- .../prog/en/modules/pos/printreceipt.tt | 35 +++++++++++++++- pos/pay.pl | 41 ++++++++++++++----- pos/printreceipt.pl | 17 ++++++-- 5 files changed, 93 insertions(+), 31 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc index ea785090e0..098aeb1bb8 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc @@ -6,12 +6,11 @@ [% Koha.Preference('IntranetSlipPrinterJS') | $raw %] [% ELSE %] let autoprint = true; - $( window ).load(function() { + window.addEventListener('load', function() { window.print(); window.onafterprint = function () { window.close(); } - setTimeout('window.close()', 1000); //Hack from Chrome < 63 }); [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt index d884d0ce12..129d877582 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt @@ -59,7 +59,7 @@ [% END %] -
+ [% INCLUDE 'csrf-token.inc' %]
@@ -240,11 +240,6 @@
-[% IF payment_id && Koha.Preference('FinePaymentAutoPopup') %] - - -[% END %] - [% MACRO jsinclude BLOCK %] [% INCLUDE 'format_price.inc' %] [% INCLUDE 'datatables.inc' %] @@ -522,17 +517,22 @@ }) .appendTo("#payForm"); }); + + [% IF Koha.Preference('FinePaymentAutoPopup') %] + // Form submits to new tab; reset this page for the next transaction + setTimeout(function() { + sale_kohaTable.api().clear().draw(); + $('#tendered').val(''); + $('#paid').val(zero_formatted); + $('#change').html(zero_formatted); + $(':input[name="change"]').val(zero_formatted); + checked = false; + }, 0); + [% END %] + return true; } }); - - if (payment_id && Koha.prefs.FinePaymentAutoPopup) { - $("#printReceipt").click(function () { - var win = window.open(`/cgi-bin/koha/pos/printreceipt.pl?action=print&accountlines_id=${payment_id}&tendered=${tendered}&change=${change}`, "_blank"); - win.focus(); - }); - $("#printReceipt").click(); - } }); [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt index c131bee6d1..6fdbbcfb95 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt @@ -38,8 +38,41 @@ [% END %] +[% IF error_msg %] +
+

[% error_msg | html %]

+ +
+[% ELSE %] +
+ + +
+[% END %] + [% MACRO jsinclude BLOCK %] - [% INCLUDE 'slip-print.inc' #printThenClose %] + [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/pos/pay.pl b/pos/pay.pl index 73070c9d44..8dbe65516e 100755 --- a/pos/pay.pl +++ b/pos/pay.pl @@ -20,9 +20,10 @@ use Modern::Perl; use CGI; -use JSON qw( from_json ); +use JSON qw( from_json ); +use URI::Escape qw( uri_escape_utf8 ); -use C4::Auth qw( get_session get_template_and_user ); +use C4::Auth qw( get_template_and_user ); use C4::Context; use C4::Letters qw( GetPreparedLetter EnqueueLetter SendQueuedMessages ); use C4::Output qw( output_html_with_http_headers ); @@ -34,9 +35,7 @@ use Koha::Charges::Sales; use Koha::Database; use Koha::Libraries; -my $input = CGI->new(); -my $sessionID = $input->cookie('CGISESSID'); -my $session = get_session($sessionID); +my $input = CGI->new(); my ( $template, $loggedinuser, $cookie, $user_flags ) = get_template_and_user( { @@ -52,6 +51,15 @@ my $library_id = C4::Context->userenv->{'branch'}; my $registerid = $input->param('registerid'); my $op = $input->param('op') || ''; +# PRG flow: pick up payment details from redirect URL params +if ( !$op && $input->param('payment_id') ) { + $template->param( + payment_id => scalar $input->param('payment_id'), + tendered => scalar $input->param('tendered'), + change => scalar $input->param('change'), + ); +} + my $invoice_types = Koha::Account::DebitTypes->search_with_library_limits( { can_be_sold => 1, archived => 0 }, {}, $library_id @@ -78,11 +86,24 @@ if ( $op eq 'cud-pay' ) { my $payment = $sale->purchase( { payment_type => $payment_type } ); - $template->param( - payment_id => $payment->accountlines_id, - tendered => scalar $input->param('tendered'), - change => scalar $input->param('change') - ); + my $payment_id = $payment->accountlines_id; + my $tendered = uri_escape_utf8( scalar $input->param('tendered') ); + my $change = uri_escape_utf8( scalar $input->param('change') ); + + my $redirect_url; + if ( C4::Context->preference('FinePaymentAutoPopup') ) { + $redirect_url = + "/cgi-bin/koha/pos/printreceipt.pl" + . "?accountlines_id=$payment_id" + . "&tendered=$tendered" + . "&change=$change" + . "&autoprint=1"; + } else { + $redirect_url = + "/cgi-bin/koha/pos/pay.pl" . "?payment_id=$payment_id" . "&tendered=$tendered" . "&change=$change"; + } + print $input->redirect( -uri => $redirect_url, -status => 303 ); + exit; } } diff --git a/pos/printreceipt.pl b/pos/printreceipt.pl index b5f64eb9de..978bbe3ea2 100755 --- a/pos/printreceipt.pl +++ b/pos/printreceipt.pl @@ -37,7 +37,15 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( my $payment_id = $input->param('accountlines_id'); my $accountline = Koha::Account::Lines->find($payment_id); -my $patron = $accountline->patron; +if ( !$accountline ) { + $template->param( + error_msg => 'Receipt not found or access denied.', + ); + output_html_with_http_headers $input, $cookie, $template->output; + exit; +} +my $patron = $accountline->patron; +my $autoprint = $input->param('autoprint') // 0; my $logged_in_user = Koha::Patrons->find($loggedinuser) or die "Not logged in"; output_and_exit_if_error( @@ -83,9 +91,10 @@ my $letter = C4::Letters::GetPreparedLetter( ); $template->param( - slip => $letter->{content}, - plain => !$letter->{is_html}, - style => $letter->{style}, + slip => $letter->{content}, + plain => !$letter->{is_html}, + style => $letter->{style}, + autoprint => $autoprint, ); output_html_with_http_headers $input, $cookie, $template->output;