Bug 41705: Use PRG pattern for POS receipt printing to avoid popup blockers
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 <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit a5a1eeaa5b)
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
This commit is contained in:
parent
b378ee94cb
commit
3f5ede8832
|
|
@ -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 %]
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
</div>
|
||||
[% END %]
|
||||
|
||||
<form name="payForm" id="payForm" method="post" action="/cgi-bin/koha/pos/pay.pl">
|
||||
<form name="payForm" id="payForm" method="post" action="/cgi-bin/koha/pos/pay.pl" [% IF Koha.Preference('FinePaymentAutoPopup') %]target="_blank"[% END %]>
|
||||
[% INCLUDE 'csrf-token.inc' %]
|
||||
<input type="hidden" name="op" value="cud-pay" />
|
||||
<div class="row">
|
||||
|
|
@ -240,11 +240,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
[% IF payment_id && Koha.Preference('FinePaymentAutoPopup') %]
|
||||
<!-- Automatic Print Receipt -->
|
||||
<a id="printReceipt" style="display: none" href="#"></a>
|
||||
[% 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 %]
|
||||
});
|
||||
</script>
|
||||
[% END %]
|
||||
|
|
|
|||
|
|
@ -38,8 +38,41 @@
|
|||
[% END %]
|
||||
</div>
|
||||
|
||||
[% IF error_msg %]
|
||||
<div class="alert alert-danger">
|
||||
<p>[% error_msg | html %]</p>
|
||||
<button id="close-window" class="btn btn-secondary" onclick="window.close();"> [% t("Close") | html %] </button>
|
||||
</div>
|
||||
[% ELSE %]
|
||||
<div class="no-print" style="margin-top: 20px; text-align: center;">
|
||||
<button id="print-receipt" class="btn btn-primary"> <i class="fa fa-print"></i> [% t("Print receipt") | html %] </button>
|
||||
<button id="close-window" class="btn btn-secondary" onclick="window.close();"> [% t("Close") | html %] </button>
|
||||
</div>
|
||||
[% END %]
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% INCLUDE 'slip-print.inc' #printThenClose %]
|
||||
<script nonce="[% Koha.CSPNonce | $raw %]">
|
||||
[% IF ( Koha.Preference('IntranetSlipPrinterJS') ) %]
|
||||
[% Koha.Preference('IntranetSlipPrinterJS') | $raw %]
|
||||
[% ELSE %]
|
||||
function doPrint() {
|
||||
window.print();
|
||||
}
|
||||
|
||||
window.onafterprint = () => {
|
||||
window.close();
|
||||
};
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
if ( [% autoprint | html %] == 1 ) {
|
||||
doPrint();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('print-receipt')
|
||||
.addEventListener('click', doPrint);
|
||||
[% END %]
|
||||
</script>
|
||||
[% END %]
|
||||
|
||||
[% INCLUDE 'intranet-bottom.inc' %]
|
||||
|
|
|
|||
41
pos/pay.pl
41
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue