Bugzilla – Attachment 64528 Details for
Bug 17705
Payments with cards through payment terminal
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 17705: payments using payment terminal
Bug-17705-payments-using-payment-terminal.patch (text/plain), 43.85 KB, created by
Radek Šiman (R-Bit Technology, s.r.o.)
on 2017-06-22 08:24:14 UTC
(
hide
)
Description:
Bug 17705: payments using payment terminal
Filename:
MIME Type:
Creator:
Radek Šiman (R-Bit Technology, s.r.o.)
Created:
2017-06-22 08:24:14 UTC
Size:
43.85 KB
patch
obsolete
>From 76c04a1435b312d5af29dae6e70c132e81dad7cb Mon Sep 17 00:00:00 2001 >From: =?UTF-8?q?Radek=20=C5=A0iman?= <rbit@rbit.cz> >Date: Thu, 22 Jun 2017 09:37:15 +0200 >Subject: [PATCH] Bug 17705: payments using payment terminal > >This patch expects already installed and configured payment terminal, >e.g Ingenico, provided by GPE (Global Payments Europe). This piece of >code already acquired an official certification in GPE Headquarter, >Prague, Czech Republic. Bank accounts connected to banks KB or CSOB >should work perfectly. Other banks will probably require additional >certifications and changes in the code. > >Test plan >1) Appy this patch >2) Connect and/or start terminal device >3) Go to administration setting and look for "PosTerminal" items >4) Setup desired values (IP and port) >5) Make patron to pay a single fine (eg. lost book) >6) Tick the box "Pay by card" and confirm the payment >7) Popup window will raise displaying transaction status >8) Let the patron pay his amount using the terminal >9) When transaction finishes, close popup window >10) Optional: try to refund an amount by clicking "Refund to card" >button within fines overview (members/boraccount.pl). Steps 7-9 will >repeat accordingly. >--- > Koha/PosTerminal/Client.pm | 101 +++++++++ > Koha/PosTerminal/Message.pm | 232 +++++++++++++++++++ > Koha/PosTerminal/Message/Field.pm | 26 +++ > Koha/PosTerminal/Message/Header.pm | 126 +++++++++++ > Koha/PosTerminal/Transaction.pm | 50 +++++ > Koha/PosTerminal/Transactions.pm | 56 +++++ > installer/data/mysql/sysprefs.sql | 3 + > .../en/includes/members-pos-terminal-dialog.inc | 16 ++ > .../en/includes/members-pos-terminal-messages.inc | 21 ++ > .../en/modules/admin/preferences/circulation.pref | 12 + > .../prog/en/modules/members/boraccount.tt | 6 + > .../prog/en/modules/members/paycollect.tt | 12 +- > koha-tmpl/intranet-tmpl/prog/js/payments.js | 205 +++++++++++++++++ > members/paycollect.pl | 1 + > svc/pos_terminal | 250 +++++++++++++++++++++ > 15 files changed, 1115 insertions(+), 2 deletions(-) > create mode 100644 Koha/PosTerminal/Client.pm > create mode 100644 Koha/PosTerminal/Message.pm > create mode 100644 Koha/PosTerminal/Message/Field.pm > create mode 100644 Koha/PosTerminal/Message/Header.pm > create mode 100644 Koha/PosTerminal/Transaction.pm > create mode 100644 Koha/PosTerminal/Transactions.pm > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-dialog.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-messages.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/payments.js > create mode 100755 svc/pos_terminal > >diff --git a/Koha/PosTerminal/Client.pm b/Koha/PosTerminal/Client.pm >new file mode 100644 >index 0000000..2974cac >--- /dev/null >+++ b/Koha/PosTerminal/Client.pm >@@ -0,0 +1,101 @@ >+package Koha::PosTerminal::Client; >+ >+# This file is part of Koha. >+# >+# Copyright 2014 BibLibre >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use IO::Socket::INET; >+use IO::Socket::Timeout; >+use Koha::PosTerminal::Message; >+use Errno qw(ETIMEDOUT EWOULDBLOCK); >+ >+use constant { >+ ERR_CONNECTION_FAILED => -1, >+ ERR_NO_RESPONSE => -2 >+}; >+ >+# auto-flush on socket >+$| = 1; >+ >+sub new { >+ my $class = shift; >+ my $self = { >+ _ip => shift, >+ _port => shift, >+ _socket => 0, >+ }; >+ >+ bless $self, $class; >+ return $self; >+} >+ >+sub connect { >+ my ( $self ) = @_; >+ >+ $self->{_socket} = new IO::Socket::INET ( >+ PeerHost => $self->{_ip}, >+ PeerPort => $self->{_port}, >+ Proto => 'tcp', >+ Timeout => 5 >+ ); >+ >+ if ($self->{_socket}) { >+ IO::Socket::Timeout->enable_timeouts_on($self->{_socket}); >+ $self->{_socket}->read_timeout(60); >+ $self->{_socket}->write_timeout(60); >+ } >+ >+ return !!$self->{_socket}; >+} >+ >+sub disconnect { >+ my ( $self ) = @_; >+ >+ $self->{_socket}->close(); >+} >+ >+sub send { >+ my ( $self, $message ) = @_; >+ >+ # data to send to a server >+ my $req = $message->getContent(); >+ my $size = $self->{_socket}->send($req); >+} >+ >+sub receive { >+ my ( $self ) = @_; >+ >+ my $socket = $self->{_socket}; >+ >+# my $response = <$socket>; >+ my $response; >+ $self->{_socket}->recv($response, 1024); >+ if (!$response) { >+ if (( 0+$! == ETIMEDOUT) || (0+$! == EWOULDBLOCK )) { >+ return ERR_CONNECTION_FAILED; >+ } >+ else { >+ return 0+$!; #ERR_NO_RESPONSE; >+ } >+ } >+ >+ my $msg = new Koha::PosTerminal::Message(Koha::PosTerminal::Message::DIR_RECEIVED); >+ $msg->parse($response); >+ >+ return $msg; >+} >+ >+1; >\ No newline at end of file >diff --git a/Koha/PosTerminal/Message.pm b/Koha/PosTerminal/Message.pm >new file mode 100644 >index 0000000..1206337 >--- /dev/null >+++ b/Koha/PosTerminal/Message.pm >@@ -0,0 +1,232 @@ >+package Koha::PosTerminal::Message; >+ >+# Copyright 2017 R-Bit Technology, s.r.o. >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use strict; >+use warnings; >+ >+use Digest::CRC; >+use Koha::PosTerminal::Message::Header; >+use Koha::PosTerminal::Message::Field; >+use Data::Dumper qw( Dumper ); >+ >+use constant { >+ STX => "\x02", >+ ETX => "\x03", >+ FS => "\x1C", >+ GS => "\x1D", >+ CR => "\x0D", >+ LF => "\x0A" >+}; >+ >+use constant { >+ F_PAID_AMOUNT => "B", >+ F_CURRENCY_CODE => "I", >+ F_TRANSACTION_TYPE => "T", >+ F_RESPONSE_CODE => "R", >+ F_CARD_NUMBER => "P", >+ F_CARD_PRODUCT => "J", >+ F_INVOICE_NUMBER => "S", >+ F_CODE_PAGE => "f", >+ F_RECEIPT => "t", >+ F_TRANSACTION_ID => "n", >+ F_APPLICATION_ID => "a" >+}; >+ >+use constant { >+ TTYPE_SALE => "00", >+ TTYPE_PREAUTH => "01", >+ TTYPE_PREAUTH_COMPLETION => "02", >+ TTYPE_REVERSAL => "10", >+ TTYPE_REFUND => "04", >+ TTYPE_ABORT => "12", >+ TTYPE_POST_DATA_PRINTING => "16", >+ TTYPE_REPEAT_LAST_TRANSACTION => "17" >+}; >+ >+use constant { >+ DIR_SENT => "SENT", >+ DIR_RECEIVED => "RCVD" >+}; >+ >+sub new { >+ my $class = shift; >+ >+ my $self = {}; >+ $self->{_header} = Koha::PosTerminal::Message::Header->new(); >+ $self->{_fields} = (); >+ $self->{_isValid} = 0; >+ $self->{_direction} = shift; >+ >+ bless $self, $class; >+ return $self; >+} >+ >+sub getDirection { >+ my( $self ) = @_; >+ return $self->{_direction}; >+} >+ >+sub getHeader { >+ my( $self ) = @_; >+ return $self->{_header}; >+} >+ >+sub getContent { >+ my( $self ) = @_; >+ >+ my $msg = $self->getHeader()->getContent(); >+ foreach my $field (@{$self->{_fields}}) { >+ $msg .= FS.$field->name.$field->value; >+ } >+ >+ return STX.$msg.ETX; >+} >+ >+sub addField { >+ my ( $self, $fieldName, $value ) = @_; >+ my $field = Koha::PosTerminal::Message::Field->new({ name => $fieldName, value => $value }); >+ push(@{$self->{_fields}}, $field); >+ $self->updateHeader(); >+} >+ >+sub getField { >+ my ( $self, $fieldName ) = @_; >+ foreach my $field (@{$self->{_fields}}) { >+ if ( $field->name eq $fieldName ) { >+ return $field; >+ } >+ } >+ return 0; >+} >+ >+sub fieldCount { >+ my( $self ) = @_; >+ >+ return $self->{_fields} ? scalar @{$self->{_fields}} : 0; >+} >+ >+sub updateHeader { >+ my( $self ) = @_; >+ >+ my $dataPart = ""; >+ foreach my $field (@{$self->{_fields}}) { >+ $dataPart .= FS.$field->name.$field->value; >+ } >+ $self->getHeader()->crc($self->getCrcHex($dataPart)); >+ $self->getHeader()->length(sprintf("%04X", length($dataPart))); >+} >+ >+sub getCrcHex { >+ my( $self, $data ) = @_; >+ >+ my $crc = Digest::CRC->new(width=>16, init => 0x0000, xorout => 0x0000, >+ refout => 0, poly => 0x11021, refin => 0, cont => 0); >+ $crc->add($data); >+ my $crcBin = $crc->digest; >+ return sprintf("%04X",$crcBin); >+} >+ >+sub isValid { >+ my( $self ) = @_; >+ >+ return $self->{_isValid}; >+} >+ >+sub setValid { >+ my ( $self, $valid ) = @_; >+ $self->{_isValid} = $valid; >+} >+ >+sub parse { >+ my ( $self, $response ) = @_; >+ >+ my $hdr = $self->getHeader(); >+ >+ my $first = substr $response, 0, 1; >+ my $last = substr $response, -1; >+ >+ if (($first eq STX) && ($last eq ETX)) { >+ $hdr->protocolType(substr $response, 1, 2); >+ $hdr->protocolVersion(substr $response, 3, 2); >+ $hdr->terminalID(substr $response, 5, 8); >+ $hdr->dateTime(substr $response, 13, 12); >+ $hdr->tags(substr $response, 25, 4); >+ $hdr->length(substr $response, 29, 4); >+ $hdr->crc(substr $response, 33, 4); >+ $self->{_fields} = (); >+ my $dataPart = substr $response, 37, -1; >+ if ($hdr->crc eq $self->getCrcHex($dataPart)) { >+ $self->parseFields($dataPart); >+ $self->setValid(1); >+ } >+ else { >+ $self->setValid(0); >+ } >+ } >+# print Dumper($self); >+ >+} >+ >+sub parseFields { >+ my ( $self, $dataPart ) = @_; >+ my $fs = FS; >+ my @fields = split /$fs/, substr $dataPart, 1; >+ >+ foreach my $field (@fields) { >+ my $fname = substr $field, 0, 1; >+ my $fvalue = substr $field, 1; >+ $self->addField($fname, $fvalue); >+ } >+ return 0; >+} >+ >+sub decodeControlCharacters { >+ my( $self, $msg ) = @_; >+ >+ $msg =~ s/[\x02]/\<STX\>/g; >+ $msg =~ s/[\x03]/\<ETX\>/g; >+ $msg =~ s/[\x1C]/\<FS\>/g; >+ $msg =~ s/[\x1D]/\<GS\>/g; >+ $msg =~ s/[\x0D]/\<CR\>/g; >+ $msg =~ s/[\x0A]/\<LF\>/g; >+ $msg =~ s/[\xFF]/\<0xFF\>/g; >+ $msg =~ s/ /\<SPC\>/g; >+ >+ return $msg; >+} >+ >+sub dumpString { >+ my( $self ) = @_; >+ return $self->decodeControlCharacters($self->getContent()); >+} >+ >+sub dumpObject { >+ my( $self ) = @_; >+ my $msg = $self->getHeader()->dumpObject(); >+ >+ $msg .= "data:\n"; >+# print Dumper($self); >+#die(); >+ foreach my $field (@{$self->{_fields}}) { >+ $msg .= " ".$field->name.": '".$field->value."'\n"; >+ } >+ return $msg; >+} >+ >+1; >\ No newline at end of file >diff --git a/Koha/PosTerminal/Message/Field.pm b/Koha/PosTerminal/Message/Field.pm >new file mode 100644 >index 0000000..04547b8 >--- /dev/null >+++ b/Koha/PosTerminal/Message/Field.pm >@@ -0,0 +1,26 @@ >+package Koha::PosTerminal::Message::Field; >+ >+# Copyright 2017 R-Bit Technology, s.r.o. >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use strict; >+use warnings; >+ >+use base qw(Class::Accessor); >+Koha::PosTerminal::Message::Field->mk_accessors(qw(name value)); >+ >+1; >\ No newline at end of file >diff --git a/Koha/PosTerminal/Message/Header.pm b/Koha/PosTerminal/Message/Header.pm >new file mode 100644 >index 0000000..bc5be26 >--- /dev/null >+++ b/Koha/PosTerminal/Message/Header.pm >@@ -0,0 +1,126 @@ >+package Koha::PosTerminal::Message::Header; >+ >+# Copyright 2017 R-Bit Technology, s.r.o. >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use strict; >+use warnings; >+ >+use DateTime qw(); >+ >+use constant SPC => ' '; >+use constant PROTOCOL_TYPE => "B1"; >+use constant PROTOCOL_VERSION => "01"; >+use constant TIMEZONE => "Europe/Prague"; >+use constant CRC_NO_DATA => "A5A5"; >+use constant NO_DATA_LENGTH => "0000"; >+use constant TAGS_EMPTY => "0000"; >+use constant TAGS_SIGNATURE_CHECK => 0x0001; >+ >+use base qw(Class::Accessor); >+Koha::PosTerminal::Message::Header->mk_accessors(qw(protocolType protocolVersion terminalID dateTime tags length crc)); >+ >+sub new { >+ my $class = shift @_; >+ >+ my $self = $class->SUPER::new(@_); >+ $self->protocolType(PROTOCOL_TYPE); >+ $self->protocolVersion(PROTOCOL_VERSION); >+ $self->terminalID(0); >+ $self->dateTime(0); >+ $self->tags(TAGS_EMPTY); >+ $self->length(NO_DATA_LENGTH); >+ $self->crc(CRC_NO_DATA); >+ >+ return $self; >+} >+ >+sub terminalID { >+ my($self) = shift; >+ >+ if( @_ ) { # Setting >+ my($terminalID) = @_; >+ >+ if (!$terminalID) { >+ $terminalID = " " x 8; >+ } >+ return $self->set('terminalID', $terminalID); >+ } >+ else { >+ return $self->get('terminalID'); >+ } >+} >+ >+sub dateTime { >+ my($self) = shift; >+ >+ if( @_ ) { # Setting >+ my($dateTime) = @_; >+ >+ if (!$dateTime) { >+ my $dt = DateTime->now(time_zone => TIMEZONE); >+ $dateTime = $dt->strftime('%y%m%d%H%M%S'); >+ } >+ return $self->set('dateTime', $dateTime); >+ } >+ else { >+ return $self->get('dateTime'); >+ } >+} >+ >+sub isSignatureCheckRequired { >+ my( $self ) = @_; >+ return hex("0x" . $self->tags) & TAGS_SIGNATURE_CHECK; >+} >+ >+sub getContent { >+ my( $self ) = @_; >+ my $content = >+ $self->protocolType >+ . $self->protocolVersion >+ . $self->terminalID >+ . $self->dateTime >+ . $self->tags >+ . $self->length >+ . $self->crc; >+ return $content; >+} >+ >+sub dumpObject { >+ my( $self ) = @_; >+ my @dt = ( $self->dateTime =~ m/../g ); >+ my $obj = >+ "protocol:\n" >+ . " type: '".$self->protocolType."'\n" >+ . " version: '".$self->protocolVersion."'\n" >+ . "terminal ID: '".$self->terminalID."'\n" >+ . "date:\n" >+ . " year: '".$dt[0]."'\n" >+ . " month: '".$dt[1]."'\n" >+ . " day: '".$dt[2]."'\n" >+ . "time:\n" >+ . " hours: '".$dt[3]."'\n" >+ . " minutes: '".$dt[4]."'\n" >+ . " seconds: '".$dt[5]."'\n" >+ . "tags: '".$self->tags."'\n" >+ . "length: '".$self->length."'\n" >+ . "crc: '".$self->crc."'\n"; >+ >+ return $obj; >+} >+ >+1; >\ No newline at end of file >diff --git a/Koha/PosTerminal/Transaction.pm b/Koha/PosTerminal/Transaction.pm >new file mode 100644 >index 0000000..8167fac >--- /dev/null >+++ b/Koha/PosTerminal/Transaction.pm >@@ -0,0 +1,50 @@ >+package Koha::PosTerminal::Transaction; >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 3 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# with Koha; if not, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Carp; >+ >+use Koha::Database; >+ >+use base qw(Koha::Object); >+ >+=head1 NAME >+ >+Koha::PosTerminal::Transactions - Koha pos_terminal_transaction Object class >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub _type { >+ return 'PosTerminalTransaction'; >+} >+ >+1; >+ >+=head1 AUTHOR >+ >+Radek Å iman <rbit@rbit.cz> >+ >+=cut >diff --git a/Koha/PosTerminal/Transactions.pm b/Koha/PosTerminal/Transactions.pm >new file mode 100644 >index 0000000..5bf0b2c >--- /dev/null >+++ b/Koha/PosTerminal/Transactions.pm >@@ -0,0 +1,56 @@ >+package Koha::PosTerminal::Transactions; >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 3 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# with Koha; if not, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Carp; >+ >+use Koha::Database; >+ >+use Koha::PosTerminal::Transaction; >+ >+use base qw(Koha::Objects); >+ >+=head1 NAME >+ >+Koha::PosTerminal::Transactions - Koha PosTerminal Transaction Object set class >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub _type { >+ return 'PosTerminalTransaction'; >+} >+ >+sub object_class { >+ return 'Koha::PosTerminal::Transaction'; >+} >+ >+1; >+ >+=head1 AUTHOR >+ >+Radek Å iman <rbit@rbit.cz> >+ >+=cut >diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql >index ef83c3d..794a3d0 100644 >--- a/installer/data/mysql/sysprefs.sql >+++ b/installer/data/mysql/sysprefs.sql >@@ -410,6 +410,9 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('PayPalSignature', '', NULL , 'Your PayPal API signature', 'Free'), > ('PayPalUser', '', NULL , 'Your PayPal API username ( email address )', 'Free'), > ('Persona','0','','Use Mozilla Persona for login','YesNo'), >+('PosTerminalCurrencyCode', '', NULL , 'POS Terminal currency code number', 'Integer') >+('PosTerminalIP', '', NULL , 'POS Terminal IP address', 'Free'), >+('PosTerminalPort', '', NULL , 'POS Terminal port', 'Integer'), > ('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'), > ('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'), > ('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'), >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-dialog.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-dialog.inc >new file mode 100644 >index 0000000..f7820f4 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-dialog.inc >@@ -0,0 +1,16 @@ >+<!-- Modal --> >+<div id="card_payment_dialog" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="cardPaymentModalLabel"> >+ <div class="modal-dialog" role="document"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h4 class="modal-title" id="cardPaymentModalLabel">Card payment</h4> >+ </div> >+ <div class="modal-body"> >+ <p class="transaction-message"></p> >+ </div> >+ <div class="modal-footer"> >+ <button id="transaction-close" type="button" class="btn btn-default" data-dismiss="modal">Close</button> >+ </div> >+ </div><!-- /.modal-content --> >+ </div><!-- /.modal-dialog --> >+</div><!-- /.modal --> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-messages.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-messages.inc >new file mode 100644 >index 0000000..686c934 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/members-pos-terminal-messages.inc >@@ -0,0 +1,21 @@ >+<script type="text/javascript"> >+ //<![CDATA[ >+ var MSG_POS_IN_PROGRESS = _("Transaction is in progress..."); >+ var MSG_POS_SUCESS = _("Transaction successfully finished, thank you."); >+ var MSG_POS_INIT = _("Initializing connection..."); >+ var MSG_POS_REQUEST_PAYMENT = _("Requesting payment transaction..."); >+ var MSG_POS_REQUEST_REFUND = _("Requesting refund transaction..."); >+ var MSG_POS_SENT_REQUEST = _("Request sent."); >+ var MSG_POS_RECEIVED_MESSAGE = _("Request confirmed."); >+ var MSG_POS_RECEIVED_RESPONSE = _("Response received."); >+ var MSG_POS_SENT_CONFIRMATION = _("Transaction confirmed."); >+ var MSG_POS_DISCONNECTED = _("Device disconnected."); >+ var MSG_POS_ERR_TRANSACTION_REJECTED = _("Transaction rejected."); >+ var MSG_POS_ERR_REQUEST_REJECTED = _("Connection request rejected."); >+ var MSG_POS_ERR_CONNECTION_FAILED = _("Conection interrupted."); >+ var MSG_POS_ERR = _("Error:"); >+ var MSG_POS_ERR_CONNECTION_ABORTED = _("Connection aborted."); >+ var MSG_POS_ERR_EXPIRED = _("Session timed out. Please log in again."); >+ var MSG_POS_ACTIVITY_MESSAGE = _("Activity message received."); >+ //]]> >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >index e69e37f..350f2a9 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >@@ -136,6 +136,18 @@ Circulation: > yes: Show > no: "Do not show" > - all items in the "Checked-in items" list, even items that were not checked out. >+ - >+ - Payment terminal communicates at IP address >+ - pref: PosTerminalIP >+ - and port >+ - pref: PosTerminalPort >+ class: integer >+ - . Leave the IP address blank to disable this payment option. >+ - >+ - Payment terminal uses >+ - pref: PosTerminalCurrencyCode >+ class: integer >+ - as currency code number. Please see <a href="http://www.iso.org/iso/home/standards/currency_codes.htm" target="_blank">ISO 4217</a> for a full list of assigned numbers. > > Checkout Policy: > - >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/boraccount.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/boraccount.tt >index ca0a804..dd8216c 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/boraccount.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/boraccount.tt >@@ -3,6 +3,8 @@ > [% INCLUDE 'doc-head-open.inc' %] > <title>Koha › Patrons › Account for [% INCLUDE 'patron-title.inc' %]</title> > [% INCLUDE 'doc-head-close.inc' %] >+[% INCLUDE 'members-pos-terminal-messages.inc' %] >+<script type="text/javascript" src="[% interface %]/[% theme %]/js/payments.js"></script> > <link rel="stylesheet" type="text/css" href="[% interface %]/[% theme %]/css/datatables.css" /> > [% INCLUDE 'datatables.inc' %] > <script type="text/javascript"> >@@ -116,6 +118,9 @@ $(document).ready(function() { > [% IF ( reverse_col) %] > [% IF ( account.payment ) %] > <a href="boraccount.pl?action=reverse&accountlines_id=[% account.accountlines_id %]&borrowernumber=[% account.borrowernumber %]" class="btn btn-mini"><i class="fa fa-undo"></i> Reverse</a> >+ [%IF (account.amountoutstanding+0 + account.amount+0 != 0 ) %] >+ <a href="boraccount.pl?action=reverse&accountlines_id=[% account.accountlines_id %]&borrowernumber=[% account.borrowernumber %]" class="btn btn-mini" onclick="refundPayment(this.href, [% account.accountlines_id %], [% -1*account.amount %]);return false;"><i class="fa fa-undo"></i> Refund to card</a> >+ [% END %] > [% ELSE %] > > [% END %] >@@ -145,4 +150,5 @@ $(document).ready(function() { > [% INCLUDE 'circ-menu.inc' %] > </div> > </div> >+[% INCLUDE 'members-pos-terminal-dialog.inc' %] > [% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/paycollect.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/paycollect.tt >index 069ad69..19e9ff4 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/paycollect.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/paycollect.tt >@@ -3,6 +3,8 @@ > [% INCLUDE 'doc-head-open.inc' %] > <title>Koha › Patrons › Collect fine payment for [% borrower.firstname %] [% borrower.surname %]</title> > [% INCLUDE 'doc-head-close.inc' %] >+[% INCLUDE 'members-pos-terminal-messages.inc' %] >+<script type="text/javascript" src="[% interface %]/[% theme %]/js/payments.js"></script> > <script type= "text/javascript"> > //<![CDATA[ > $(document).ready(function() { >@@ -100,7 +102,7 @@ function moneyFormat(textObj) { > [% END %] > > [% IF ( pay_individual ) %] >- <form name="payindivfine" id="payindivfine" onsubmit="return validatePayment(this);" method="post" action="/cgi-bin/koha/members/paycollect.pl"> >+ <form name="payindivfine" id="payindivfine" onsubmit="return makePayment(this);" method="post" action="/cgi-bin/koha/members/paycollect.pl"> > <input type="hidden" name="borrowernumber" id="borrowernumber" value="[% borrower.borrowernumber %]" /> > <input type="hidden" name="pay_individual" id="pay_individual" value="[% pay_individual %]" /> > <input type="hidden" name="itemnumber" id="itemnumber" value="[% itemnumber %]" /> >@@ -148,6 +150,12 @@ function moneyFormat(textObj) { > <!-- default to paying all --> > <input name="paid" id="paid" value="[% amountoutstanding | format('%.2f') %]" onchange="moneyFormat(document.payindivfine.paid)"/> > </li> >+[% IF Koha.Preference('PosTerminalIP') %] >+ <li> >+ <label for="bycard">Pay by card: </label> >+ <input type="checkbox" name="bycard" id="bycard" value="1"/> >+ </li> >+[% END %] > </ol> > </fieldset> > >@@ -231,5 +239,5 @@ function moneyFormat(textObj) { > [% INCLUDE 'circ-menu.inc' %] > </div> > </div> >+[% INCLUDE 'members-pos-terminal-dialog.inc' %] > [% INCLUDE 'intranet-bottom.inc' %] >- >diff --git a/koha-tmpl/intranet-tmpl/prog/js/payments.js b/koha-tmpl/intranet-tmpl/prog/js/payments.js >new file mode 100644 >index 0000000..ac7766c >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/payments.js >@@ -0,0 +1,205 @@ >+var posTransactionTimer = 0; >+var formPayment = 0; >+var posTransactionSucceeded = 0; >+ >+function callSvcApi(data, callbacks) { >+ $.post('/cgi-bin/koha/svc/pos_terminal', data, function( response ) { >+ if (callbacks.success) { >+ callbacks.success(response); >+ } >+ }) >+ .fail(function(response) { >+ if (callbacks.fail) { >+ callbacks.fail(response); >+ } >+ }) >+ .always(function(response) { >+ if (callbacks.always) { >+ callbacks.always(response); >+ } >+ }); >+} >+ >+function showMessage(status) { >+ var msg = ""; >+ >+ if (status == "new") { >+ msg = MSG_POS_IN_PROGRESS; >+ } >+ else if (status == "success") { >+ msg = MSG_POS_SUCESS; >+ } >+ else if (status == "init") { >+ msg = MSG_POS_INIT; >+ } >+ else if (status == "request-payment") { >+ msg = MSG_POS_REQUEST_PAYMENT; >+ } >+ else if (status == "request-refund") { >+ msg = MSG_POS_REQUEST_REFUND; >+ } >+ else if (status == "sent-request") { >+ msg = MSG_POS_SENT_REQUEST; >+ } >+ else if (status == "received-message") { >+ msg = MSG_POS_RECEIVED_MESSAGE; >+ } >+ else if (status == "received-response") { >+ msg = MSG_POS_RECEIVED_RESPONSE; >+ } >+ else if (status == "sent-confirmation") { >+ msg = MSG_POS_SENT_CONFIRMATION; >+ } >+ else if (status == "activity-message") { >+ msg = MSG_POS_ACTIVITY_MESSAGE; >+ } >+ else if (status == "disconnected") { >+ msg = MSG_POS_DISCONNECTED; >+ } >+ else if (status == "connection-error") { >+ msg = "Connection error."; >+ } >+ else if (status == "ERR_TRANSACTION_REJECTED") { >+ msg = MSG_POS_ERR_TRANSACTION_REJECTED; >+ } >+ else if (status == "ERR_REQUEST_REJECTED") { >+ msg = MSG_POS_ERR_REQUEST_REJECTED; >+ } >+ else if (status == "ERR_CONNECTION_FAILED") { >+ msg = MSG_POS_ERR_CONNECTION_FAILED; >+ } >+ else if (status.lastIndexOf("ERR_") === 0) { >+ msg = MSG_POS_ERR + " " + status + ". " + MSG_POS_ERR_CONNECTION_ABORTED; >+ } >+ else if (status == "expired") { >+ msg = MSG_POS_ERR_EXPIRED >+ } >+ else { >+ msg = status; >+ } >+ >+ $("#card_payment_dialog .transaction-message").text(msg); >+} >+ >+function checkStatus(transaction_id) { >+ callSvcApi( >+ {transaction_id: transaction_id, action: "status"}, >+ { >+ success: function(xml) { >+ var status = $(xml).find('status').first().text(); >+ showMessage(status); >+ if ((status.lastIndexOf("ERR_") === 0) || (status == "success") || (status == "expired")) { >+ formPayment = (status == "success") ? formPayment : 0; >+ posTransactionSucceeded = (status == "success"); >+ $("#card_payment_dialog button").prop("disabled", false); >+ } >+ else { >+ posTransactionTimer = setTimeout(function() { checkStatus(transaction_id); }, 5000); >+ } >+ } >+ } >+ ); >+} >+ >+// ---------------- payments >+function requestPayment(transaction_id) { >+ showMessage("request-payment"); >+ callSvcApi( >+ { >+ transaction_id: transaction_id, >+ action: "request-payment", >+ paid: parseInt($('#paid').val()) >+ }, >+ { } >+ ); >+} >+ >+function startPaymentTransaction(accountlines_id) { >+ showMessage('init'); >+ $("#card_payment_dialog button").click(closePaymentTransaction); >+ $("#card_payment_dialog button").prop("disabled", true); >+ $("#card_payment_dialog").modal({ >+ backdrop: 'static', >+ keyboard: false >+ }); >+ callSvcApi( >+ {accountlines_id: accountlines_id}, >+ { >+ success: function(xml) { >+ var transaction_id = $(xml).find('transaction_id').first().text(); >+ posTransactionTimer = setTimeout(function() { checkStatus(transaction_id); }, 5000); >+ requestPayment(transaction_id); >+ }, >+ fail: function(xml) { alert(MSG_POS_ERR + " " + $(xml).find('status').first().text()); }, >+ } >+ ); >+} >+ >+function closePaymentTransaction() { >+ clearTimeout(posTransactionTimer); >+ if (formPayment && posTransactionSucceeded) { >+ formPayment.submit(); >+ } >+ else { >+ $("body, form input[type='submit'], form button[type='submit'], form a").removeClass('waiting'); >+ } >+} >+ >+function makePayment(form) { >+ if ($("#bycard").is(':checked')) { >+ formPayment = form; >+ startPaymentTransaction($("#accountlines_id").val()) >+ } >+ else { >+ formPayment = 0; >+ form.submit(); >+ } >+ >+ return false; // always return false not to submit the form automatically >+} >+ >+// ---------------- refund >+function refundPayment(href, accountlines_id, amount) { >+ showMessage('init'); >+ $("#card_payment_dialog button").click((function() { closeRefundTransaction(href); })); >+ $("#card_payment_dialog button").prop("disabled", true); >+ $("#card_payment_dialog").modal({ >+ backdrop: 'static', >+ keyboard: false >+ }); >+ callSvcApi( >+ {accountlines_id: accountlines_id}, >+ { >+ success: function(xml) { >+ var transaction_id = $(xml).find('transaction_id').first().text(); >+ $("#card_payment_dialog button").click((function() { closeRefundTransaction(href, transaction_id); })); >+ posTransactionTimer = setTimeout(function() { checkStatus(transaction_id); }, 5000); >+ requestRefund(transaction_id, amount); >+ }, >+ fail: function(xml) { alert(MSG_POS_ERR + " " + $(xml).find('status').first().text()); }, >+ } >+ ); >+} >+ >+function requestRefund(transaction_id, amount) { >+ showMessage("request-refund"); >+ callSvcApi( >+ { >+ transaction_id: transaction_id, >+ action: "request-refund", >+ paid: amount >+ }, >+ { } >+ ); >+} >+ >+function closeRefundTransaction(href, transaction_id) { >+ clearTimeout(posTransactionTimer); >+ if (posTransactionSucceeded) { >+ window.location.href = href; >+ } >+ else { >+ $("body, form input[type='submit'], form button[type='submit'], form a").removeClass('waiting'); >+ } >+ posTransactionSucceeded = 0; >+} >diff --git a/members/paycollect.pl b/members/paycollect.pl >index b34dba6..ebc59ad 100755 >--- a/members/paycollect.pl >+++ b/members/paycollect.pl >@@ -55,6 +55,7 @@ my $branch = C4::Context->userenv->{'branch'}; > > my ( $total_due, $accts, $numaccts ) = GetMemberAccountRecords($borrowernumber); > my $total_paid = $input->param('paid'); >+my $by_card = $input->param('bycard'); > > my $individual = $input->param('pay_individual'); > my $writeoff = $input->param('writeoff_individual'); >diff --git a/svc/pos_terminal b/svc/pos_terminal >new file mode 100755 >index 0000000..593b59f >--- /dev/null >+++ b/svc/pos_terminal >@@ -0,0 +1,250 @@ >+#!/usr/bin/perl >+ >+# Copyright 2017 R-Bit technology, s.r.o. >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+# >+ >+use Modern::Perl; >+ >+use CGI qw ( -utf8 ); >+use C4::Auth qw( check_api_auth ); >+use C4::Context; >+use JSON qw( to_json ); >+use IO::Socket::INET; >+use Koha::PosTerminal::Message; >+use Koha::PosTerminal::Client; >+use Koha::PosTerminal::Transaction; >+use Koha::PosTerminal::Transactions; >+use XML::Simple; >+use Scalar::Util qw( looks_like_number ); >+use DateTime; >+use Data::Dumper; >+ >+my $query = new CGI; >+my ($status, $cookie, $sessionID) = check_api_auth($query, { updatecharges => 1 } ); >+my $transactionId = $query->param('transaction_id'); >+my $accountlinesId = $query->param('accountlines_id'); >+my $action = $query->param('action') || q{}; >+my $result = { >+ status => $status, >+ transaction_id => defined($transactionId) ? $transactionId : -1 >+}; >+ >+binmode STDOUT, ":encoding(UTF-8)"; >+print $query->header( >+ -type => 'text/xml', >+ -charset => 'UTF-8' >+); >+ >+if ($status eq 'ok') { >+ >+ if (!$action) { >+ start_transaction($result, $accountlinesId); >+ } >+ >+ elsif ($action eq 'status') { >+ get_transaction_status($result, $transactionId); >+ } >+ >+ elsif (($action eq 'request-payment') || ($action eq 'request-refund') || ($action eq 'abort')) { >+ my $transaction = Koha::PosTerminal::Transactions->find($transactionId); >+ my $client = new Koha::PosTerminal::Client( >+ C4::Context->preference('PosTerminalIP'), >+ C4::Context->preference('PosTerminalPort'), >+ ); >+ >+ if ($client->connect()) { >+ $transaction->set({status => 'connected'})->store(); >+ >+ if ($action eq 'abort') { >+ my $abort = abort_transaction($client, $transaction); >+ my $field = $abort->getField($abort->F_RESPONSE_CODE); >+ $result->{response_code} = $field ? $field->value : 0; >+ $result->{status} = "abort"; >+ $transaction->set({status => $result->{status}, response_code => $result->{response_code}})->store(); >+ print XMLout($result, NoAttr => 1, RootName => 'response', XMLDecl => 1); >+ exit 0; >+ } >+ >+ my $transactionRequest = send_transaction_request($client, $transaction, $action eq 'request-payment' ? Koha::PosTerminal::Message->TTYPE_SALE : Koha::PosTerminal::Message->TTYPE_REFUND, scalar $query->param('paid')); >+ >+ my $transactionMessage = receive_transaction_message($client, $transaction); >+ >+ my $field = $transactionMessage->getField($transactionMessage->F_RESPONSE_CODE); >+ $result->{response_code} = $field ? $field->value : 0; >+ if ($result->{response_code} <= 10) { >+ >+ my $transactionResponse = receive_transaction_response($client, $transaction); >+ if (!looks_like_number($transactionResponse)) { >+ send_confirmation_message($client, $transaction, $transactionResponse); >+ >+ $client->disconnect(); >+ $transaction->set({status => 'disconnected'})->store(); >+ >+ $field = $transactionResponse->getField($transactionResponse->F_RESPONSE_CODE); >+ $result->{response_code} = $field ? $field->value : 0; >+ >+ if ($result->{response_code} <= 10) { >+ if ( $field = $transactionResponse->getField($transactionResponse->F_CARD_NUMBER) ) { >+ $result->{cardnumber} = $field->value; >+ $result->{status} = "success"; >+ } >+ else { >+ $result->{status} = "ERR_NO_CARD_NUMBER"; >+ } >+ } >+ else { >+ $result->{status} = "ERR_TRANSACTION_REJECTED"; >+ } >+ } >+ else { >+ $result->{status} = "ERR_CONNECTION_FAILED"; >+ } >+ } >+ else { >+ $result->{status} = "ERR_REQUEST_REJECTED"; >+ } >+ } >+ else { >+ $result->{status} = "ERR_NO_CONNECTION"; >+ } >+ $transaction->set({status => $result->{status}, response_code => $result->{response_code}})->store(); >+ } >+ elsif ($action eq 'request-refund') { >+ } >+} >+ >+print XMLout($result, NoAttr => 1, RootName => 'response', XMLDecl => 1); >+ >+exit 0; >+ >+sub log_communication { >+ my ( $terminalMsg, $transactionId ) = @_; >+ >+ my $transaction = Koha::PosTerminal::Transactions->find($transactionId); >+ my $now = DateTime->now(); >+ my $message = "[" . $now->ymd . " " . $now->hms ."] data: "; >+ if (looks_like_number($terminalMsg)) { >+ $message .= "error " . $terminalMsg . "\n"; >+ } >+ else { >+ $message .= $terminalMsg->fieldCount() . ", " . $terminalMsg->getDirection() . ": " . $terminalMsg->decodeControlCharacters($terminalMsg->getContent()) . "\n"; >+ } >+ $transaction->set({message_log => (defined $transaction->message_log ? $transaction->message_log : "") . $message})->store(); >+} >+ >+sub start_transaction { >+ my ( $result, $accountlinesId ) = @_; >+ >+ my $transaction = Koha::PosTerminal::Transaction->new({accountlines_id => $accountlinesId, status => 'new' })->store(); >+ $result->{status} = $transaction->status; >+ $result->{transaction_id} = $transaction->id; >+} >+ >+sub abort_transaction { >+ my ( $client, $transaction ) = @_; >+ >+ # send abort message >+ my $abort = new Koha::PosTerminal::Message(Koha::PosTerminal::Message::DIR_SENT); >+ my $hdrAbort = $abort->getHeader(); >+die(Dumper($transaction ? $transaction->getHeader() : "BUBU")); >+ my $hdrTransaction = $transaction->getHeader(); >+ >+ $hdrAbort->dateTime($hdrTransaction->dateTime()); >+ $hdrAbort->terminalID($hdrTransaction->terminalID()); >+ $hdrAbort->protocolType($hdrTransaction->protocolType()); >+ $hdrAbort->protocolVersion($hdrTransaction->protocolVersion()); >+ $abort->addField($abort->F_TRANSACTION_TYPE, $abort->TTYPE_ABORT); >+ $client->send($abort); >+ >+ $abort->set({status => 'abort'})->store(); >+ log_communication($abort, $transaction->id); >+ >+ return $abort; >+} >+ >+sub get_transaction_status { >+ my ( $result, $transactionId ) = @_; >+ >+ my $transaction = Koha::PosTerminal::Transactions->find($transactionId); >+ $result->{status} = $transaction->status; >+} >+ >+sub send_transaction_request { >+ my ( $client, $transaction, $type, $paid ) = @_; >+ >+ # send transaction request >+ my $transactionRequest = new Koha::PosTerminal::Message(Koha::PosTerminal::Message::DIR_SENT); >+ $transactionRequest->getHeader()->dateTime(0); >+ $transactionRequest->addField($transactionRequest->F_TRANSACTION_TYPE, $type); >+ $transactionRequest->addField($transactionRequest->F_PAID_AMOUNT, $paid * 100); >+ $transactionRequest->addField($transactionRequest->F_INVOICE_NUMBER, $transaction->accountlines_id); >+ $transactionRequest->addField($transactionRequest->F_CURRENCY_CODE, C4::Context->preference('PosTerminalCurrencyCode')); >+ $client->send($transactionRequest); >+ $transaction->set({status => 'sent-request'})->store(); >+ log_communication($transactionRequest, $transaction->id); >+ >+ return $transactionRequest; >+} >+ >+sub receive_transaction_message { >+ my ( $client, $transaction ) = @_; >+ >+ # receive transaction message >+ my $transactionMessage = $client->receive(); >+ >+ $transaction->set({status => 'received-message'})->store(); >+ log_communication($transactionMessage, $transaction->id); >+ >+ return $transactionMessage; >+} >+ >+sub receive_transaction_response { >+ my ( $client, $transaction ) = @_; >+ my $transactionResponse; >+ my $status; >+ >+ # receive transaction response >+ for (;;) { >+ $transactionResponse = $client->receive(); >+ $status = looks_like_number($transactionResponse) ? 'ERR_CONNECTION_FAILED' >+ : ($transactionResponse->fieldCount() ? 'received-response' : 'activity-message'); >+ $transaction->set({status => $status})->store(); >+ log_communication($transactionResponse, $transaction->id); >+ >+ last if (looks_like_number($transactionResponse) || $transactionResponse->fieldCount()); >+ } >+ >+ return $transactionResponse; >+} >+ >+sub send_confirmation_message { >+ my ( $client, $transaction, $transactionResponse ) = @_; >+ >+ # send confirmation message >+ my $confirmation = new Koha::PosTerminal::Message(Koha::PosTerminal::Message::DIR_SENT); >+ my $hdrConfirm = $confirmation->getHeader(); >+ my $hdrResponse = $transactionResponse->getHeader(); >+ $hdrConfirm->dateTime($hdrResponse->dateTime()); >+ $hdrConfirm->terminalID($hdrResponse->terminalID()); >+ $hdrConfirm->protocolType($hdrResponse->protocolType()); >+ $hdrConfirm->protocolVersion($hdrResponse->protocolVersion()); >+ $client->send($confirmation); >+ >+ $transaction->set({status => 'sent-confirmation'})->store(); >+ log_communication($confirmation, $transaction->id); >+} >-- >2.1.4
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 17705
:
63499
|
63500
|
64526
|
64527
|
64528
|
64529
|
64613
|
76366
|
76367
|
76368