diff options
| author | Joren Van Onder <jov@odoo.com> | 2015-04-17 17:53:13 +0200 |
|---|---|---|
| committer | Frédéric van der Essen <fva@odoo.com> | 2015-07-02 16:42:36 +0200 |
| commit | 04fb3dfcfffe5d2abfcf6f3ebe9d693101d5aa20 (patch) | |
| tree | 4cfe24f22bfefa25903ff6106842ad36410a515e /addons/pos_mercury/static | |
| parent | 3a26daeb296a1efdae9c5514c0ef142d82fb8702 (diff) | |
[IMP] pos_mercury: pos extension for electronic card payment.
This new module integrate the 'Mercury Pay' electronic payment
provider with the point of sale.
This commit is the end result of a large branch whose main
author is Joren Van Onder <jov@odoo.com>, with additional
contributions from Martin Donies <mdo@odoo.com> and Frederic van
der Essen <fva@odoo.com>
Diffstat (limited to 'addons/pos_mercury/static')
| -rw-r--r-- | addons/pos_mercury/static/src/css/pos_mercury.css | 7 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/js/pos_mercury.js | 675 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/xml/pos_mercury.xml | 88 |
3 files changed, 770 insertions, 0 deletions
diff --git a/addons/pos_mercury/static/src/css/pos_mercury.css b/addons/pos_mercury/static/src/css/pos_mercury.css new file mode 100644 index 00000000000..6d48663fc19 --- /dev/null +++ b/addons/pos_mercury/static/src/css/pos_mercury.css @@ -0,0 +1,7 @@ +.pos .paymentline.selected.o_pos_mercury_swipe_pending, .pos .paymentline.o_pos_mercury_swipe_pending { + background: rgb(239, 153, 65); +} +.pos .col-tendered.edit.o_pos_mercury_swipe_pending { + color: rgb(239, 153, 65); + box-shadow: 0px 0px 0px 3px rgb(239, 153, 65); +}
\ No newline at end of file diff --git a/addons/pos_mercury/static/src/js/pos_mercury.js b/addons/pos_mercury/static/src/js/pos_mercury.js new file mode 100644 index 00000000000..89af16b52d9 --- /dev/null +++ b/addons/pos_mercury/static/src/js/pos_mercury.js @@ -0,0 +1,675 @@ +odoo.define('pos_mercury.pos_mercury', function (require) { +"use strict"; + +var Class = require('web.Class'); +var Model = require('web.Model'); +var session = require('web.session'); +var core = require('web.core'); +var screens = require('point_of_sale.screens'); +var gui = require('point_of_sale.gui'); +var pos_model = require('point_of_sale.models'); +var utils = require('web.utils'); + +var _t = core._t; + +var BarcodeParser = require('barcodes.BarcodeParser'); +var PopupWidget = require('point_of_sale.popups'); +var ScreenWidget = screens.ScreenWidget; +var PaymentScreenWidget = screens.PaymentScreenWidget; +var round_pr = utils.round_precision; + +var onlinePaymentJournal = []; + +var _modelproto = pos_model.PosModel.prototype; +pos_model.PosModel = pos_model.PosModel.extend({ + allowOnlinePayment: function () { + if (onlinePaymentJournal.length) { + return true; + } + $.each(this.journals, function (i, val) { + if (val.pos_mercury_config_id) { + onlinePaymentJournal.push({label:val.display_name, item:val.id}); + } + }); + return onlinePaymentJournal.length; + }, + getCashRegisterByJournalID: function (journal_id) { + var cashregister_return; + + $.each(this.cashregisters, function (index, cashregister) { + if (cashregister.journal_id[0] === journal_id) { + cashregister_return = cashregister; + } + }); + + return cashregister_return; + }, + decodeMagtek: function (magtekInput) { + // Regular expression to identify and extract data from the track 1 & 2 of the magnetic code + var _track1_regex = /%B?([0-9]*)\^([A-Z\/ -_]*)\^([0-9]{4})(.{3})([^?]+)\?/; + + var track1 = magtekInput.match(_track1_regex); + var magtek_generated = magtekInput.split('|'); + + var to_return = {}; + try { + track1.shift(); // get rid of complete match + to_return['number'] = track1.shift().substr(-4); + to_return['name'] = track1.shift(); + track1.shift(); // expiration date + track1.shift(); // service code + track1.shift(); // discretionary data + track1.shift(); // zero pad + + magtek_generated.shift(); // track1 and track2 + magtek_generated.shift(); // clear text crc + magtek_generated.shift(); // encryption counter + to_return['encrypted_block'] = magtek_generated.shift(); + magtek_generated.shift(); // enc session id + magtek_generated.shift(); // device serial + magtek_generated.shift(); // magneprint data + magtek_generated.shift(); // magneprint status + magtek_generated.shift(); // enc track3 + to_return['encrypted_key'] = magtek_generated.shift(); + magtek_generated.shift(); // enc track1 + magtek_generated.shift(); // reader enc status + + return to_return; + } catch (e) { + return 0; + } + }, + decodeMercuryResponse: function (data) { + // get rid of xml version declaration and just keep the RStream + // from the response because the xml contains two version + // declarations. One for the SOAP, and one for the content. Maybe + // we should unpack the SOAP layer in python? + data = data.replace(/.*<\?xml version="1.0"\?>/, ""); + data = data.replace(/<\/CreditTransactionResult>.*/, ""); + + var xml = $($.parseXML(data)); + var cmd_response = xml.find("CmdResponse"); + var tran_response = xml.find("TranResponse"); + + return { + status: cmd_response.find("CmdStatus").text(), + message: cmd_response.find("TextResponse").text(), + error: cmd_response.find("DSIXReturnCode").text(), + card_type: tran_response.find("CardType").text(), + auth_code: tran_response.find("AuthCode").text(), + acq_ref_data: tran_response.find("AcqRefData").text(), + process_data: tran_response.find("ProcessData").text(), + invoice_no: tran_response.find("InvoiceNo").text(), + ref_no: tran_response.find("RefNo").text(), + record_no: tran_response.find("RecordNo").text(), + purchase: parseFloat(tran_response.find("Purchase").text()), + authorize: parseFloat(tran_response.find("Authorize").text()), + }; + } +}); + +var _paylineproto = pos_model.Paymentline.prototype; +pos_model.Paymentline = pos_model.Paymentline.extend({ + init_from_JSON: function (json) { + _paylineproto.init_from_JSON.apply(this, arguments); + + this.paid = json.paid; + this.mercury_card_number = json.mercury_card_number; + this.mercury_card_brand = json.mercury_card_brand; + this.mercury_card_owner_name = json.mercury_card_owner_name; + this.mercury_ref_no = json.mercury_ref_no; + this.mercury_record_no = json.mercury_record_no; + this.mercury_invoice_no = json.mercury_invoice_no; + this.mercury_auth_code = json.mercury_auth_code; + this.mercury_data = json.mercury_data; + this.mercury_swipe_pending = json.mercury_swipe_pending; + + this.set_credit_card_name(); + }, + export_as_JSON: function () { + return _.extend(_paylineproto.export_as_JSON.apply(this, arguments), {paid: this.paid, + mercury_card_number: this.mercury_card_number, + mercury_card_brand: this.mercury_card_brand, + mercury_card_owner_name: this.mercury_card_owner_name, + mercury_ref_no: this.mercury_ref_no, + mercury_record_no: this.mercury_record_no, + mercury_invoice_no: this.mercury_invoice_no, + mercury_auth_code: this.mercury_auth_code, + mercury_data: this.mercury_data, + mercury_swipe_pending: this.mercury_swipe_pending}); + }, + set_credit_card_name: function () { + if (this.mercury_card_number) { + this.name = this.mercury_card_brand + " (****" + this.mercury_card_number + ")"; + } + } +}); + +// Lookup table to store status and error messages +var lookUpCodeTransaction = { + 'Approved': { + '000000': _t('Transaction approved'), + }, + 'TimeoutError': { + '001006': _t('Global API Not Initialized'), + '001007': _t('Timeout on Response'), + '003003': _t('Socket Error sending request'), + '003004': _t('Socket already open or in use'), + '003005': _t('Socket Creation Failed'), + '003006': _t('Socket Connection Failed'), + '003007': _t('Connection Lost'), + '003008': _t('TCP/IP Failed to Initialize'), + '003010': _t('Time Out waiting for server response'), + '003011': _t('Connect Canceled'), + '003053': _t('Initialize Failed'), + '009999': _t('Unknown Error'), + }, + 'FatalError': { + '-1': _t('Timeout error'), + '001001': _t('General Failure'), + '001003': _t('Invalid Command Format'), + '001004': _t('Insufficient Fields'), + '001011': _t('Empty Command String'), + '002000': _t('Password Verified'), + '002001': _t('Queue Full'), + '002002': _t('Password Failed – Disconnecting'), + '002003': _t('System Going Offline'), + '002004': _t('Disconnecting Socket'), + '002006': _t('Refused ‘Max Connections’'), + '002008': _t('Duplicate Serial Number Detected'), + '002009': _t('Password Failed (Client / Server)'), + '002010': _t('Password failed (Challenge / Response)'), + '002011': _t('Internal Server Error – Call Provider'), + '003002': _t('In Process with server'), + '003009': _t('Control failed to find branded serial (password lookup failed)'), + '003012': _t('128 bit CryptoAPI failed'), + '003014': _t('Threaded Auth Started Expect Response'), + '003017': _t('Failed to start Event Thread.'), + '003050': _t('XML Parse Error'), + '003051': _t('All Connections Failed'), + '003052': _t('Server Login Failed'), + '004001': _t('Global Response Length Error (Too Short)'), + '004002': _t('Unable to Parse Response from Global (Indistinguishable)'), + '004003': _t('Global String Error'), + '004004': _t('Weak Encryption Request Not Supported'), + '004005': _t('Clear Text Request Not Supported'), + '004010': _t('Unrecognized Request Format'), + '004011': _t('Error Occurred While Decrypting Request'), + '004017': _t('Invalid Check Digit'), + '004018': _t('Merchant ID Missing'), + '004019': _t('TStream Type Missing'), + '004020': _t('Could Not Encrypt Response- Call Provider'), + '100201': _t('Invalid Transaction Type'), + '100202': _t('Invalid Operator ID'), + '100203': _t('Invalid Memo'), + '100204': _t('Invalid Account Number'), + '100205': _t('Invalid Expiration Date'), + '100206': _t('Invalid Authorization Code'), + '100207': _t('Invalid Authorization Code'), + '100208': _t('Invalid Authorization Amount'), + '100209': _t('Invalid Cash Back Amount'), + '100210': _t('Invalid Gratuity Amount'), + '100211': _t('Invalid Purchase Amount'), + '100212': _t('Invalid Magnetic Stripe Data'), + '100213': _t('Invalid PIN Block Data'), + '100214': _t('Invalid Derived Key Data'), + '100215': _t('Invalid State Code'), + '100216': _t('Invalid Date of Birth'), + '100217': _t('Invalid Check Type'), + '100218': _t('Invalid Routing Number'), + '100219': _t('Invalid TranCode'), + '100220': _t('Invalid Merchant ID'), + '100221': _t('Invalid TStream Type'), + '100222': _t('Invalid Batch Number'), + '100223': _t('Invalid Batch Item Count'), + '100224': _t('Invalid MICR Input Type'), + '100225': _t('Invalid Driver’s License'), + '100226': _t('Invalid Sequence Number'), + '100227': _t('Invalid Pass Data'), + '100228': _t('Invalid Card Type'), + }, +}; +// Popup to show all transaction state for the payment. + +var PaymentTransactionPopupWidget = PopupWidget.extend({ + template: 'PaymentTransactionPopupWidget', + show: function (options) { + var self = this; + this._super(options); + options.transaction.then(function (data) { + if (data.auto_close) { + setTimeout(function () { + self.gui.close_popup(); + }, 2000); + } else { + self.close(); + self.$el.find('.popup').append('<div class="footer"><div class="button cancel">Ok</div></div>'); + } + + self.$el.find('p.body').html(data.message); + }).progress(function (data) { + self.$el.find('p.body').html(data.message); + }); + } +}); + +gui.define_popup({name:'payment-transaction', widget: PaymentTransactionPopupWidget}); + +// On all screens, if a card is swipped, return a popup error. +ScreenWidget.include({ + credit_error_action: function () { + this.gui.show_popup('error-barcode',_t('Go to payment screen to use cards')); + }, + + show: function () { + this._super(); + if(this.pos.allowOnlinePayment()) { + this.pos.barcode_reader.set_action_callback('credit', _.bind(this.credit_error_action, this)); + } + } +}); + +// On Payment screen, allow electronic payments +PaymentScreenWidget.include({ + // How long we wait for the odoo server to deliver the response of + // a Mercury transaction + server_timeout_in_ms: 95000, + + // How many Mercury transactions we send without receiving a + // response + server_retries: 3, + + _get_swipe_pending_line: function () { + var i = 0; + var lines = this.pos.get_order().get_paymentlines(); + + for (i = 0; i < lines.length; i++) { + if (lines[i].mercury_swipe_pending) { + return lines[i]; + } + } + + return 0; + }, + + _does_credit_payment_line_exist: function (amount, card_number, card_brand, card_owner_name) { + var i = 0; + var lines = this.pos.get_order().get_paymentlines(); + + for (i = 0; i < lines.length; i++) { + if (lines[i].mercury_amount === amount && + lines[i].mercury_card_number === card_number && + lines[i].mercury_card_brand === card_brand && + lines[i].mercury_card_owner_name === card_owner_name) { + return true; + } + } + + return false; + }, + + retry_mercury_transaction: function (def, response, retry_nr, can_connect_to_server, callback, args) { + var self = this; + var message = ""; + + if (retry_nr < self.server_retries) { + if (response) { + message = "Retry #" + (retry_nr + 1) + "...<br/><br/>" + response.message; + } else { + message = "Retry #" + (retry_nr + 1) + "..."; + } + def.notify({ + message: message + }); + + setTimeout(function () { + callback.apply(self, args); + }, 1000); + } else { + if (response) { + message = "Error " + response.error + ": " + lookUpCodeTransaction["TimeoutError"][response.error] + "<br/>" + response.message; + } else { + if (can_connect_to_server) { + message = _t("No response from Mercury (Mercury down?)"); + } else { + message = _t("No response from server (connected to network?)"); + } + } + def.resolve({ + message: message, + auto_close: false + }); + } + }, + + // Handler to manage the card reader string + credit_code_transaction: function (parsed_result, old_deferred, retry_nr) { + if(!this.pos.allowOnlinePayment()) { + return; + } + + var self = this; + var decodedMagtek = self.pos.decodeMagtek(parsed_result.code); + + if (! decodedMagtek) { + this.gui.show_popup('error',{ + 'title': 'Could not read card', + 'body': _t('This can be caused by a badly executed swipe or by not having your keyboard layout set to US QWERTY (not US International).'), + }); + return; + } + + var swipe_pending_line = self._get_swipe_pending_line(); + var purchase_amount = 0; + + if (swipe_pending_line) { + purchase_amount = swipe_pending_line.get_amount(); + } else { + purchase_amount = self.pos.get_order().get_due(); + } + + var transaction = { + 'encrypted_key' : decodedMagtek['encrypted_key'], + 'encrypted_block' : decodedMagtek['encrypted_block'], + 'transaction_type' : 'Credit', + 'transaction_code' : 'Sale', + 'invoice_no' : self.pos.get_order().uid.replace(/-/g,''), + 'purchase' : purchase_amount, + 'journal_id' : parsed_result.journal_id, + }; + + var def = old_deferred || new $.Deferred(); + retry_nr = retry_nr || 0; + + // show the transaction popup. + // the transaction deferred is used to update transaction status + // if we have a previous deferred it indicates that this is a retry + if (! old_deferred) { + self.gui.show_popup('payment-transaction', { + transaction: def + }); + def.notify({ + message: _t('Handling transaction...'), + }); + } + + var mercury_transaction = new Model('pos_mercury.mercury_transaction'); + mercury_transaction.call('do_payment', [transaction], undefined, {timeout: self.server_timeout_in_ms}).then(function (data) { + // if not receiving a response from Mercury, we should retry + if (data === "timeout") { + self.retry_mercury_transaction(def, null, retry_nr, true, self.credit_code_transaction, [parsed_result, def, retry_nr + 1]); + return; + } + + if (data === "not setup") { + def.resolve({ + message: _t("Please setup your Mercury merchant account.") + }); + return; + } + + if (data === "internal error") { + def.resolve({ + message: _t("Odoo error while processing transaction.") + }); + return; + } + + var response = self.pos.decodeMercuryResponse(data); + response.journal_id = parsed_result.journal_id; + + if (response.status === 'Approved') { + // AP* indicates a duplicate request, so don't add anything for those + if (response.message === "AP*" && self._does_credit_payment_line_exist(response.authorize, decodedMagtek['number'], + response.card_type, decodedMagtek['name'])) { + def.resolve({ + message: lookUpCodeTransaction["Approved"][response.error], + auto_close: true, + }); + } else { + // If the payment is approved, add a payment line + var order = self.pos.get_order(); + + if (swipe_pending_line) { + order.select_paymentline(swipe_pending_line); + } else { + order.add_paymentline(self.pos.getCashRegisterByJournalID(parsed_result.journal_id)); + } + + order.selected_paymentline.paid = true; + order.selected_paymentline.mercury_swipe_pending = false; + order.selected_paymentline.mercury_amount = response.authorize; + order.selected_paymentline.mercury_card_number = decodedMagtek['number']; + order.selected_paymentline.mercury_card_brand = response.card_type; + order.selected_paymentline.mercury_card_owner_name = decodedMagtek['name']; + order.selected_paymentline.mercury_ref_no = response.ref_no; + order.selected_paymentline.mercury_record_no = response.record_no; + order.selected_paymentline.mercury_invoice_no = response.invoice_no; + order.selected_paymentline.mercury_auth_code = response.auth_code; + order.selected_paymentline.mercury_data = response; // used to reverse transactions + order.selected_paymentline.set_credit_card_name(); + + self.order_changes(); + self.reset_input(); + self.render_paymentlines(); + order.trigger('change', order); // needed so that export_to_JSON gets triggered + + if (response.message === "PARTIAL AP") { + def.resolve({ + message: _t("Partially approved"), + auto_close: false, + }); + } else { + def.resolve({ + message: lookUpCodeTransaction["Approved"][response.error], + auto_close: true, + }); + } + } + } + + // if an error related to timeout or connectivity issues arised, then retry the same transaction + else { + if (lookUpCodeTransaction["TimeoutError"][response.error]) { // recoverable error + self.retry_mercury_transaction(def, response, retry_nr, true, self.credit_code_transaction, [parsed_result, def, retry_nr + 1]); + } else { // not recoverable + def.resolve({ + message: "Error " + response.error + ":<br/>" + response.message, + auto_close: false + }); + } + } + + }).fail(function (error, event) { + event.preventDefault(); + self.retry_mercury_transaction(def, null, retry_nr, false, self.credit_code_transaction, [parsed_result, def, retry_nr + 1]); + }); + }, + + credit_code_cancel: function () { + return; + }, + + credit_code_action: function (parsed_result) { + self = this; + + if (onlinePaymentJournal.length === 1) { + parsed_result.journal_id = onlinePaymentJournal[0].item; + self.credit_code_transaction(parsed_result); + } else { // this is for supporting another payment system like mercury + this.gui.show_popup('selection',{ + title: 'Pay ' + this.pos.get_order().get_due().toFixed(2) + ' with : ', + list: onlinePaymentJournal, + confirm: function (item) { + parsed_result.journal_id = item; + self.credit_code_transaction(parsed_result); + }, + cancel: self.credit_code_cancel, + }); + } + }, + + remove_paymentline_by_ref: function (line) { + this.pos.get_order().remove_paymentline(line); + this.reset_input(); + this.render_paymentlines(); + }, + + do_reversal: function (line, is_voidsale, old_deferred, retry_nr) { + var def = old_deferred || new $.Deferred(); + var self = this; + retry_nr = retry_nr || 0; + + // show the transaction popup. + // the transaction deferred is used to update transaction status + this.gui.show_popup('payment-transaction', { + transaction: def + }); + + var request_data = _.extend({ + 'transaction_type': 'Credit', + 'transaction_code': 'VoidSaleByRecordNo', + }, line.mercury_data); + + var message = ""; + var rpc_method = ""; + + if (is_voidsale) { + message = _t("Reversal failed, sending VoidSale..."); + rpc_method = "do_voidsale"; + } else { + message = _t("Sending reversal..."); + rpc_method = "do_reversal"; + } + + if (! old_deferred) { + def.notify({ + message: message, + }); + } + + var mercury_transaction = new Model('pos_mercury.mercury_transaction'); + mercury_transaction.call(rpc_method, [request_data], undefined, {timeout: self.server_timeout_in_ms}).then(function (data) { + if (data === "timeout") { + self.retry_mercury_transaction(def, null, retry_nr, true, self.do_reversal, [line, is_voidsale, def, retry_nr + 1]); + return; + } + + if (data === "internal error") { + def.resolve({ + message: _t("Odoo error while processing transaction.") + }); + return; + } + + var response = self.pos.decodeMercuryResponse(data); + + if (! is_voidsale) { + if (response.status != 'Approved' || response.message != 'REVERSED') { + // reversal was not successful, send voidsale + self.do_reversal(line, true); + } else { + // reversal was successful + def.resolve({ + message: _t("Reversal succeeded"), + }); + + self.remove_paymentline_by_ref(line); + } + } else { // voidsale ended, nothing more we can do + if (response.status === 'Approved') { + def.resolve({ + message: _t("VoidSale succeeded"), + }); + + self.remove_paymentline_by_ref(line); + } else { + def.resolve({ + message: "Error " + response.error + ":<br/>" + response.message, + }); + } + } + }).fail(function (error, event) { + event.preventDefault(); + self.retry_mercury_transaction(def, null, retry_nr, false, self.do_reversal, [line, is_voidsale, def, retry_nr + 1]); + }); + }, + + click_delete_paymentline: function (cid) { + var self = this; + var lines = this.pos.get_order().get_paymentlines(); + + for (var i = 0; i < lines.length; i++) { + if (lines[i].cid === cid && lines[i].mercury_data) { + this.do_reversal(lines[i], false); + return; + } + } + + this._super(cid); + }, + + // make sure there is only one paymentline waiting for a swipe + click_paymentmethods: function (id) { + var i; + var order = this.pos.get_order(); + var cashregister = null; + for (i = 0; i < this.pos.cashregisters.length; i++) { + if (this.pos.cashregisters[i].journal_id[0] === id){ + cashregister = this.pos.cashregisters[i]; + break; + } + } + + if (cashregister.journal.type === 'bank') { + var already_swipe_pending = false; + var lines = order.get_paymentlines(); + + for (i = 0; i < lines.length; i++) { + if (lines[i].cashregister.journal.type === 'bank' && lines[i].mercury_swipe_pending) { + already_swipe_pending = true; + } + } + + if (already_swipe_pending) { + this.gui.show_popup('error',{ + 'title': 'Error', + 'body': _t('One credit card swipe already pending.'), + }); + } else { + this._super(id); + order.selected_paymentline.mercury_swipe_pending = true; + this.render_paymentlines(); + order.trigger('change', order); // needed so that export_to_JSON gets triggered + } + } else { + this._super(id); + } + }, + + show: function () { + this._super(); + if (this.pos.allowOnlinePayment()) { + this.pos.barcode_reader.set_action_callback('credit', _.bind(this.credit_code_action, this)); + } + }, + + // before validating, get rid of any paymentlines that are waiting + // on a swipe. + validate_order: function(force_validation) { + if (this.pos.get_order().is_paid() && ! this.invoicing) { + var lines = this.pos.get_order().get_paymentlines(); + + for (var i = 0; i < lines.length; i++) { + if (lines[i].mercury_swipe_pending) { + this.pos.get_order().remove_paymentline(lines[i]); + this.render_paymentlines(); + } + } + } + + this._super(); + } +}); + +}); diff --git a/addons/pos_mercury/static/src/xml/pos_mercury.xml b/addons/pos_mercury/static/src/xml/pos_mercury.xml new file mode 100644 index 00000000000..bb8071a5a59 --- /dev/null +++ b/addons/pos_mercury/static/src/xml/pos_mercury.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> + +<templates id="template" inherit_id="point_of_sale.template"> + <t t-name="PaymentTransactionPopupWidget"> + <div class="modal-dialog"> + <div class="popup"> + <p class="title">Electronic Payment</p> + <p class="body"></p> + </div> + </div> + </t> + + <t t-extend="PaymentScreen-Paymentlines"> + <t t-jquery=".col-name" t-operation="inner"> + <t t-if="line.cashregister.journal.type === 'bank'"> + <t t-if="line.mercury_swipe_pending"> + <div>WAITING FOR SWIPE</div> + </t> + <t t-if="! line.mercury_swipe_pending"> + <t t-esc='line.name' /> + </t> + </t> + <t t-if="line.cashregister.journal.type !== 'bank'"> + <t t-esc='line.name' /> + </t> + </t> + <t t-jquery="tbody tr.paymentline.selected"> + this.removeAttr("class"); + this.attr('t-attf-class', 'paymentline selected #{line.mercury_swipe_pending ? "o_pos_mercury_swipe_pending" : ""}'); + </t> + <t t-jquery="tbody tr.paymentline[t-att-data-cid*='line.cid']"> + this.removeAttr("class"); + this.attr('t-attf-class', 'paymentline #{line.mercury_swipe_pending ? "o_pos_mercury_swipe_pending" : ""}'); + </t> + <t t-jquery="tbody tr td.col-tendered.edit"> + this.removeAttr("class"); + this.attr('t-attf-class', 'col-tendered edit #{line.mercury_swipe_pending ? "o_pos_mercury_swipe_pending" : ""}'); + </t> + </t> + + <t t-name="PosMercurySignature"> + <t t-foreach="paymentlines" t-as="paymentline"> + <t t-if="paymentline.mercury_data && ! printed_signature"> + <br /> + <div>CARDHOLDER WILL PAY CARD ISSUER</div> + <div>ABOVE AMOUNT PURSUANT</div> + <div>TO CARDHOLDER AGREEMENT</div> + <br /> + <br /> + <div>X______________________________</div> + <t t-set="printed_signature" t-value="true"/> + </t> + </t> + </t> + + <t t-extend="XmlReceipt"> + <t t-jquery="t[t-foreach*='paymentlines'][t-as*='line']" t-operation="append"> + <t t-debug="true"/> + <t t-if="line.mercury_data"> + <line line-ratio="1"> + <left><pre> APPROVAL CODE:</pre><t t-esc="line.mercury_auth_code"/></left> + </line> + </t> + </t> + <t t-jquery="receipt" t-operation="append"> + <div> + <t t-call="PosMercurySignature"/> + </div> + </t> + </t> + + <t t-extend="PosTicket"> + <t t-jquery="t[t-foreach*='paymentlines'][t-as*='line']" t-operation="append"> + <tr> + <td colspan="2"> + <t t-if="line.mercury_data"> + &nbsp;&nbsp;APPROVAL CODE: <t t-esc="line.mercury_auth_code"/> + </t> + </td> + </tr> + </t> + <t t-jquery="t[t-if*='receipt.footer']" t-operation="after"> + <div class="pos-center-align"> + <t t-call="PosMercurySignature"/> + </div> + </t> + </t> +</templates> |
