From 3f5ede883295ed9649d7f9e7821e43ee9243fe9e 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: Lucas Gass (cherry picked from commit a5a1eeaa5b8539d94a4df2efdcde4da58973400d) Signed-off-by: Baptiste Wojtkowski --- .../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 4a39a40a1d..c6e354db11 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 f5cb9b2dd0..685171a35b 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' %] @@ -515,17 +510,22 @@ value: JSON.stringify(sale) }).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.Preference('FinePaymentAutoPopup') %] - $("#printReceipt").click(function() { - var win = window.open('/cgi-bin/koha/pos/printreceipt.pl?action=print&accountlines_id=[% payment_id | uri %]&tendered=[% tendered | uri %]&change=[% change | uri %]', '_blank'); - win.focus(); - }); - $("#printReceipt").click(); - [% END %] }); [% 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 53c8b56a85..58c89448f4 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;