summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/pos_mercury/__manifest__.py4
-rw-r--r--addons/pos_mercury/static/src/js/OrderReceipt.js24
-rw-r--r--addons/pos_mercury/static/src/js/PaymentScreen.js569
-rw-r--r--addons/pos_mercury/static/src/js/PaymentScreenPaymentLines.js30
-rw-r--r--addons/pos_mercury/static/src/js/PaymentTransactionPopup.js37
-rw-r--r--addons/pos_mercury/static/src/js/ProductScreen.js26
-rw-r--r--addons/pos_mercury/static/src/js/pos_mercury.js514
-rw-r--r--addons/pos_mercury/static/src/xml/OrderReceipt.xml28
-rw-r--r--addons/pos_mercury/static/src/xml/PaymentScreenPaymentLines.xml15
-rw-r--r--addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml22
-rw-r--r--addons/pos_mercury/static/src/xml/pos_mercury.xml72
-rw-r--r--addons/pos_mercury/views/pos_mercury_templates.xml5
12 files changed, 759 insertions, 587 deletions
diff --git a/addons/pos_mercury/__manifest__.py b/addons/pos_mercury/__manifest__.py
index 29cfc076421..397b15316ed 100644
--- a/addons/pos_mercury/__manifest__.py
+++ b/addons/pos_mercury/__manifest__.py
@@ -34,7 +34,9 @@ following:
'data/pos_mercury_demo.xml',
],
'qweb': [
- 'static/src/xml/pos_mercury.xml',
+ 'static/src/xml/OrderReceipt.xml',
+ 'static/src/xml/PaymentScreenPaymentLines.xml',
+ 'static/src/xml/PaymentTransactionPopup.xml',
],
'installable': True,
'auto_install': False,
diff --git a/addons/pos_mercury/static/src/js/OrderReceipt.js b/addons/pos_mercury/static/src/js/OrderReceipt.js
new file mode 100644
index 00000000000..35e771696f6
--- /dev/null
+++ b/addons/pos_mercury/static/src/js/OrderReceipt.js
@@ -0,0 +1,24 @@
+odoo.define('pos_mercury.OrderReceipt', function(require) {
+ 'use strict';
+
+ const OrderReceipt = require('point_of_sale.OrderReceipt');
+ const Registries = require('point_of_sale.Registries');
+
+ const PosMercuryOrderReceipt = OrderReceipt =>
+ class extends OrderReceipt {
+ /**
+ * The receipt has signature if one of the paymentlines
+ * is paid with mercury.
+ */
+ get hasPosMercurySignature() {
+ for (let line of this.paymentlines) {
+ if (line.mercury_data) return true;
+ }
+ return false;
+ }
+ };
+
+ Registries.Component.extend(OrderReceipt, PosMercuryOrderReceipt);
+
+ return OrderReceipt;
+});
diff --git a/addons/pos_mercury/static/src/js/PaymentScreen.js b/addons/pos_mercury/static/src/js/PaymentScreen.js
new file mode 100644
index 00000000000..d803fff1258
--- /dev/null
+++ b/addons/pos_mercury/static/src/js/PaymentScreen.js
@@ -0,0 +1,569 @@
+odoo.define('pos_mercury.PaymentScreen', function (require) {
+ 'use strict';
+
+ const { _t } = require('web.core');
+ const PaymentScreen = require('point_of_sale.PaymentScreen');
+ const Registries = require('point_of_sale.Registries');
+ const NumberBuffer = require('point_of_sale.NumberBuffer');
+ const { useBarcodeReader } = require('point_of_sale.custom_hooks');
+
+ // Lookup table to store status and error messages
+ const lookUpCodeTransaction = {
+ Approved: {
+ '000000': _t('Transaction approved'),
+ },
+ TimeoutError: {
+ '001006': 'Global API Not Initialized',
+ '001007': 'Timeout on Response',
+ '003003': 'Socket Error sending request',
+ '003004': 'Socket already open or in use',
+ '003005': 'Socket Creation Failed',
+ '003006': 'Socket Connection Failed',
+ '003007': 'Connection Lost',
+ '003008': 'TCP/IP Failed to Initialize',
+ '003010': 'Time Out waiting for server response',
+ '003011': 'Connect Canceled',
+ '003053': 'Initialize Failed',
+ '009999': 'Unknown Error',
+ },
+ FatalError: {
+ '-1': 'Timeout error',
+ '001001': 'General Failure',
+ '001003': 'Invalid Command Format',
+ '001004': 'Insufficient Fields',
+ '001011': 'Empty Command String',
+ '002000': 'Password Verified',
+ '002001': 'Queue Full',
+ '002002': 'Password Failed – Disconnecting',
+ '002003': 'System Going Offline',
+ '002004': 'Disconnecting Socket',
+ '002006': 'Refused ‘Max Connections’',
+ '002008': 'Duplicate Serial Number Detected',
+ '002009': 'Password Failed (Client / Server)',
+ '002010': 'Password failed (Challenge / Response)',
+ '002011': 'Internal Server Error – Call Provider',
+ '003002': 'In Process with server',
+ '003009': 'Control failed to find branded serial (password lookup failed)',
+ '003012': '128 bit CryptoAPI failed',
+ '003014': 'Threaded Auth Started Expect Response',
+ '003017': 'Failed to start Event Thread.',
+ '003050': 'XML Parse Error',
+ '003051': 'All Connections Failed',
+ '003052': 'Server Login Failed',
+ '004001': 'Global Response Length Error (Too Short)',
+ '004002': 'Unable to Parse Response from Global (Indistinguishable)',
+ '004003': 'Global String Error',
+ '004004': 'Weak Encryption Request Not Supported',
+ '004005': 'Clear Text Request Not Supported',
+ '004010': 'Unrecognized Request Format',
+ '004011': 'Error Occurred While Decrypting Request',
+ '004017': 'Invalid Check Digit',
+ '004018': 'Merchant ID Missing',
+ '004019': 'TStream Type Missing',
+ '004020': 'Could Not Encrypt Response- Call Provider',
+ '100201': 'Invalid Transaction Type',
+ '100202': 'Invalid Operator ID',
+ '100203': 'Invalid Memo',
+ '100204': 'Invalid Account Number',
+ '100205': 'Invalid Expiration Date',
+ '100206': 'Invalid Authorization Code',
+ '100207': 'Invalid Authorization Code',
+ '100208': 'Invalid Authorization Amount',
+ '100209': 'Invalid Cash Back Amount',
+ '100210': 'Invalid Gratuity Amount',
+ '100211': 'Invalid Purchase Amount',
+ '100212': 'Invalid Magnetic Stripe Data',
+ '100213': 'Invalid PIN Block Data',
+ '100214': 'Invalid Derived Key Data',
+ '100215': 'Invalid State Code',
+ '100216': 'Invalid Date of Birth',
+ '100217': 'Invalid Check Type',
+ '100218': 'Invalid Routing Number',
+ '100219': 'Invalid TranCode',
+ '100220': 'Invalid Merchant ID',
+ '100221': 'Invalid TStream Type',
+ '100222': 'Invalid Batch Number',
+ '100223': 'Invalid Batch Item Count',
+ '100224': 'Invalid MICR Input Type',
+ '100225': 'Invalid Driver’s License',
+ '100226': 'Invalid Sequence Number',
+ '100227': 'Invalid Pass Data',
+ '100228': 'Invalid Card Type',
+ },
+ };
+
+ const PosMercuryPaymentScreen = (PaymentScreen) =>
+ class extends PaymentScreen {
+ constructor() {
+ super(...arguments);
+ if (this.env.pos.getOnlinePaymentMethods().length !== 0) {
+ useBarcodeReader({
+ credit: this.credit_code_action,
+ });
+ }
+ // How long we wait for the odoo server to deliver the response of
+ // a Vantiv transaction
+ this.server_timeout_in_ms = 95000;
+
+ // How many Vantiv transactions we send without receiving a
+ // response
+ this.server_retries = 3;
+ }
+
+ _get_swipe_pending_line() {
+ var i = 0;
+ var lines = this.env.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(amount, card_number, card_brand, card_owner_name) {
+ var i = 0;
+ var lines = this.env.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(
+ 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 = self.env._t('No response from Vantiv (Vantiv down?)');
+ } else {
+ message = self.env._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(parsed_result, old_deferred, retry_nr) {
+ var order = this.env.pos.get_order();
+ if (order.get_due(order.selected_paymentline) < 0) {
+ this.showPopup('ErrorPopup', {
+ title: this.env._t('Refunds not supported'),
+ body: this.env._t(
+ "Credit card refunds are not supported. Instead select your credit card payment method, click 'Validate' and refund the original charge manually through the Vantiv backend."
+ ),
+ });
+ return;
+ }
+
+ if (this.env.pos.getOnlinePaymentMethods().length === 0) {
+ return;
+ }
+
+ var self = this;
+ var decodedMagtek = self.env.pos.decodeMagtek(parsed_result.code);
+
+ if (!decodedMagtek) {
+ this.showPopup('ErrorPopup', {
+ title: this.env._t('Could not read card'),
+ body: this.env._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.env.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.env.pos.get_order().uid.replace(/-/g, ''),
+ purchase: purchase_amount,
+ payment_method_id: parsed_result.payment_method_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.showPopup('PaymentTransactionPopup', {
+ transaction: def,
+ });
+ def.notify({
+ message: this.env._t('Handling transaction...'),
+ });
+ }
+
+ this.rpc(
+ {
+ model: 'pos_mercury.mercury_transaction',
+ method: 'do_payment',
+ args: [transaction],
+ },
+ {
+ timeout: self.server_timeout_in_ms,
+ }
+ )
+ .then(function (data) {
+ // if not receiving a response from Vantiv, 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: self.env._t('Please setup your Vantiv merchant account.'),
+ });
+ return;
+ }
+
+ if (data === 'internal error') {
+ def.resolve({
+ message: self.env._t('Odoo error while processing transaction.'),
+ });
+ return;
+ }
+
+ var response = self.env.pos.decodeMercuryResponse(data);
+ response.payment_method_id = parsed_result.payment_method_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.env.pos.get_order();
+
+ if (swipe_pending_line) {
+ order.select_paymentline(swipe_pending_line);
+ } else {
+ order.add_paymentline(
+ self.payment_methods_by_id[parsed_result.payment_method_id]
+ );
+ }
+
+ order.selected_paymentline.paid = true;
+ order.selected_paymentline.mercury_swipe_pending = false;
+ order.selected_paymentline.mercury_amount = response.authorize;
+ order.selected_paymentline.set_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();
+
+ NumberBuffer.reset();
+ order.trigger('change', order); // needed so that export_to_JSON gets triggered
+ self.render();
+
+ if (response.message === 'PARTIAL AP') {
+ def.resolve({
+ message: self.env._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,
+ });
+ }
+ }
+ })
+ .catch(function () {
+ self.retry_mercury_transaction(
+ def,
+ null,
+ retry_nr,
+ false,
+ self.credit_code_transaction,
+ [parsed_result, def, retry_nr + 1]
+ );
+ });
+ }
+
+ credit_code_cancel() {
+ return;
+ }
+
+ credit_code_action(parsed_result) {
+ var online_payment_methods = this.env.pos.getOnlinePaymentMethods();
+
+ if (online_payment_methods.length === 1) {
+ parsed_result.payment_method_id = online_payment_methods[0].item;
+ this.credit_code_transaction(parsed_result);
+ } else {
+ // this is for supporting another payment system like mercury
+ const selectionList = online_payment_methods.map((paymentMethod) => ({
+ id: paymentMethod.item,
+ label: paymentMethod.label,
+ isSelected: false,
+ item: paymentMethod.item,
+ }));
+ this.showPopup('SelectionPopup', {
+ title: this.env._t('Pay with: '),
+ list: selectionList,
+ }).then(({ confirmed, payload: selectedPaymentMethod }) => {
+ if (confirmed) {
+ parsed_result.payment_method_id = selectedPaymentMethod;
+ this.credit_code_transaction(parsed_result);
+ } else {
+ this.credit_code_cancel();
+ }
+ });
+ }
+ }
+
+ remove_paymentline_by_ref(line) {
+ this.env.pos.get_order().remove_paymentline(line);
+ NumberBuffer.reset();
+ this.render();
+ }
+
+ do_reversal(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.showPopup('PaymentTransactionPopup', {
+ transaction: def,
+ });
+
+ var request_data = _.extend(
+ {
+ transaction_type: 'Credit',
+ transaction_code: 'VoidSaleByRecordNo',
+ },
+ line.mercury_data
+ );
+
+ var message = '';
+ var rpc_method = '';
+
+ if (is_voidsale) {
+ message = this.env._t('Reversal failed, sending VoidSale...');
+ rpc_method = 'do_voidsale';
+ } else {
+ message = this.env._t('Sending reversal...');
+ rpc_method = 'do_reversal';
+ }
+
+ if (!old_deferred) {
+ def.notify({
+ message: message,
+ });
+ }
+
+ this.rpc(
+ {
+ model: 'pos_mercury.mercury_transaction',
+ method: rpc_method,
+ args: [request_data],
+ },
+ {
+ 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: self.env._t('Odoo error while processing transaction.'),
+ });
+ return;
+ }
+
+ var response = self.env.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: self.env._t('Reversal succeeded'),
+ });
+
+ self.remove_paymentline_by_ref(line);
+ }
+ } else {
+ // voidsale ended, nothing more we can do
+ if (response.status === 'Approved') {
+ def.resolve({
+ message: self.env._t('VoidSale succeeded'),
+ });
+
+ self.remove_paymentline_by_ref(line);
+ } else {
+ def.resolve({
+ message:
+ 'Error ' + response.error + ':<br/>' + response.message,
+ });
+ }
+ }
+ })
+ .catch(function () {
+ self.retry_mercury_transaction(
+ def,
+ null,
+ retry_nr,
+ false,
+ self.do_reversal,
+ [line, is_voidsale, def, retry_nr + 1]
+ );
+ });
+ }
+
+ /**
+ * @override
+ */
+ deletePaymentLine(event) {
+ const { cid } = event.detail;
+ const line = this.paymentLines.find((line) => line.cid === cid);
+ if (line.mercury_data) {
+ this.do_reversal(line, false);
+ } else {
+ super.deletePaymentLine(event);
+ }
+ }
+
+ /**
+ * @override
+ */
+ addNewPaymentLine({ detail: paymentMethod }) {
+ const order = this.env.pos.get_order();
+ const res = super.addNewPaymentLine(...arguments);
+ if (res && paymentMethod.pos_mercury_config_id) {
+ order.selected_paymentline.mercury_swipe_pending = true;
+ order.trigger('change', order);
+ this.render();
+ }
+ }
+ };
+
+ Registries.Component.extend(PaymentScreen, PosMercuryPaymentScreen);
+
+ return PaymentScreen;
+});
diff --git a/addons/pos_mercury/static/src/js/PaymentScreenPaymentLines.js b/addons/pos_mercury/static/src/js/PaymentScreenPaymentLines.js
new file mode 100644
index 00000000000..64228d79800
--- /dev/null
+++ b/addons/pos_mercury/static/src/js/PaymentScreenPaymentLines.js
@@ -0,0 +1,30 @@
+odoo.define('pos_mercury.PaymentScreenPaymentLines', function (require) {
+ 'use strict';
+
+ const PaymentScreenPaymentLines = require('point_of_sale.PaymentScreenPaymentLines');
+ const Registries = require('point_of_sale.Registries');
+
+ const PosMercuryPaymentLines = (PaymentScreenPaymentLines) =>
+ class extends PaymentScreenPaymentLines {
+ /**
+ * @override
+ */
+ selectedLineClass(line) {
+ return Object.assign({}, super.selectedLineClass(line), {
+ o_pos_mercury_swipe_pending: line.mercury_swipe_pending,
+ });
+ }
+ /**
+ * @override
+ */
+ unselectedLineClass(line) {
+ return Object.assign({}, super.unselectedLineClass(line), {
+ o_pos_mercury_swipe_pending: line.mercury_swipe_pending,
+ });
+ }
+ };
+
+ Registries.Component.extend(PaymentScreenPaymentLines, PosMercuryPaymentLines);
+
+ return PaymentScreenPaymentLines;
+});
diff --git a/addons/pos_mercury/static/src/js/PaymentTransactionPopup.js b/addons/pos_mercury/static/src/js/PaymentTransactionPopup.js
new file mode 100644
index 00000000000..9b47da70993
--- /dev/null
+++ b/addons/pos_mercury/static/src/js/PaymentTransactionPopup.js
@@ -0,0 +1,37 @@
+odoo.define('pos_mercury.PaymentTransactionPopup', function(require) {
+ 'use strict';
+
+ const { useState } = owl.hooks;
+ const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
+ const Registries = require('point_of_sale.Registries');
+
+ class PaymentTransactionPopup extends AbstractAwaitablePopup {
+ constructor() {
+ super(...arguments);
+ this.state = useState({ message: '', confirmButtonIsShown: false });
+ this.props.transaction.then(data => {
+ if (data.auto_close) {
+ setTimeout(() => {
+ this.confirm();
+ }, 2000)
+ } else {
+ this.state.confirmButtonIsShown = true;
+ }
+ this.state.message = data.message;
+ }).progress(data => {
+ this.state.message = data.message;
+ })
+ }
+ }
+ PaymentTransactionPopup.template = 'PaymentTransactionPopup';
+ PaymentTransactionPopup.defaultProps = {
+ confirmText: 'Ok',
+ cancelText: 'Cancel',
+ title: 'Online Payment',
+ body: '',
+ };
+
+ Registries.Component.add(PaymentTransactionPopup);
+
+ return PaymentTransactionPopup;
+});
diff --git a/addons/pos_mercury/static/src/js/ProductScreen.js b/addons/pos_mercury/static/src/js/ProductScreen.js
new file mode 100644
index 00000000000..64d53048e63
--- /dev/null
+++ b/addons/pos_mercury/static/src/js/ProductScreen.js
@@ -0,0 +1,26 @@
+odoo.define('pos_mercury.ProductScreen', function (require) {
+ 'use strict';
+
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const Registries = require('point_of_sale.Registries');
+ const { useBarcodeReader } = require('point_of_sale.custom_hooks');
+
+ const PosMercuryProductScreen = (ProductScreen) =>
+ class extends ProductScreen {
+ constructor() {
+ super(...arguments);
+ useBarcodeReader({
+ credit: this.credit_error_action,
+ });
+ }
+ credit_error_action() {
+ this.showPopup('ErrorPopup', {
+ body: this.env._t('Go to payment screen to use cards'),
+ });
+ }
+ };
+
+ Registries.Component.extend(ProductScreen, PosMercuryProductScreen);
+
+ return ProductScreen;
+});
diff --git a/addons/pos_mercury/static/src/js/pos_mercury.js b/addons/pos_mercury/static/src/js/pos_mercury.js
index d5663b38d57..93687a68b6d 100644
--- a/addons/pos_mercury/static/src/js/pos_mercury.js
+++ b/addons/pos_mercury/static/src/js/pos_mercury.js
@@ -1,18 +1,8 @@
odoo.define('pos_mercury.pos_mercury', function (require) {
"use strict";
-var core = require('web.core');
-var rpc = require('web.rpc');
-var screens = require('point_of_sale.screens');
-var gui = require('point_of_sale.gui');
var pos_model = require('point_of_sale.models');
-var _t = core._t;
-
-var PopupWidget = require('point_of_sale.popups');
-var ScreenWidget = screens.ScreenWidget;
-var PaymentScreenWidget = screens.PaymentScreenWidget;
-
pos_model.load_fields('pos.payment.method', 'pos_mercury_config_id');
pos_model.PosModel = pos_model.PosModel.extend({
@@ -140,508 +130,4 @@ pos_model.Order = pos_model.Order.extend({
},
});
-// Lookup table to store status and error messages
-var lookUpCodeTransaction = {
- 'Approved': {
- '000000': _t('Transaction approved'),
- },
- 'TimeoutError': {
- '001006': 'Global API Not Initialized',
- '001007': 'Timeout on Response',
- '003003': 'Socket Error sending request',
- '003004': 'Socket already open or in use',
- '003005': 'Socket Creation Failed',
- '003006': 'Socket Connection Failed',
- '003007': 'Connection Lost',
- '003008': 'TCP/IP Failed to Initialize',
- '003010': 'Time Out waiting for server response',
- '003011': 'Connect Canceled',
- '003053': 'Initialize Failed',
- '009999': 'Unknown Error',
- },
- 'FatalError': {
- '-1': 'Timeout error',
- '001001': 'General Failure',
- '001003': 'Invalid Command Format',
- '001004': 'Insufficient Fields',
- '001011': 'Empty Command String',
- '002000': 'Password Verified',
- '002001': 'Queue Full',
- '002002': 'Password Failed – Disconnecting',
- '002003': 'System Going Offline',
- '002004': 'Disconnecting Socket',
- '002006': 'Refused ‘Max Connections’',
- '002008': 'Duplicate Serial Number Detected',
- '002009': 'Password Failed (Client / Server)',
- '002010': 'Password failed (Challenge / Response)',
- '002011': 'Internal Server Error – Call Provider',
- '003002': 'In Process with server',
- '003009': 'Control failed to find branded serial (password lookup failed)',
- '003012': '128 bit CryptoAPI failed',
- '003014': 'Threaded Auth Started Expect Response',
- '003017': 'Failed to start Event Thread.',
- '003050': 'XML Parse Error',
- '003051': 'All Connections Failed',
- '003052': 'Server Login Failed',
- '004001': 'Global Response Length Error (Too Short)',
- '004002': 'Unable to Parse Response from Global (Indistinguishable)',
- '004003': 'Global String Error',
- '004004': 'Weak Encryption Request Not Supported',
- '004005': 'Clear Text Request Not Supported',
- '004010': 'Unrecognized Request Format',
- '004011': 'Error Occurred While Decrypting Request',
- '004017': 'Invalid Check Digit',
- '004018': 'Merchant ID Missing',
- '004019': 'TStream Type Missing',
- '004020': 'Could Not Encrypt Response- Call Provider',
- '100201': 'Invalid Transaction Type',
- '100202': 'Invalid Operator ID',
- '100203': 'Invalid Memo',
- '100204': 'Invalid Account Number',
- '100205': 'Invalid Expiration Date',
- '100206': 'Invalid Authorization Code',
- '100207': 'Invalid Authorization Code',
- '100208': 'Invalid Authorization Amount',
- '100209': 'Invalid Cash Back Amount',
- '100210': 'Invalid Gratuity Amount',
- '100211': 'Invalid Purchase Amount',
- '100212': 'Invalid Magnetic Stripe Data',
- '100213': 'Invalid PIN Block Data',
- '100214': 'Invalid Derived Key Data',
- '100215': 'Invalid State Code',
- '100216': 'Invalid Date of Birth',
- '100217': 'Invalid Check Type',
- '100218': 'Invalid Routing Number',
- '100219': 'Invalid TranCode',
- '100220': 'Invalid Merchant ID',
- '100221': 'Invalid TStream Type',
- '100222': 'Invalid Batch Number',
- '100223': 'Invalid Batch Item Count',
- '100224': 'Invalid MICR Input Type',
- '100225': 'Invalid Driver’s License',
- '100226': 'Invalid Sequence Number',
- '100227': 'Invalid Pass Data',
- '100228': '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.getOnlinePaymentMethods().length !== 0) {
- this.pos.barcode_reader.set_action_callback('credit', _.bind(this.credit_error_action, this));
- }
- }
-});
-
-// On Payment screen, allow online payments
-PaymentScreenWidget.include({
- // How long we wait for the odoo server to deliver the response of
- // a Vantiv transaction
- server_timeout_in_ms: 95000,
-
- // How many Vantiv 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 Vantiv (Vantiv 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) {
- var order = this.pos.get_order();
- if (order.get_due(order.selected_paymentline) < 0) {
- this.gui.show_popup('error',{
- 'title': _t('Refunds not supported'),
- 'body': _t('Credit card refunds are not supported. Instead select your credit card payment method, click \'Validate\' and refund the original charge manually through the Vantiv backend.'),
- });
- return;
- }
-
- if(this.pos.getOnlinePaymentMethods().length === 0) {
- return;
- }
-
- var self = this;
- var decodedMagtek = self.pos.decodeMagtek(parsed_result.code);
-
- if (! decodedMagtek) {
- this.gui.show_popup('error',{
- 'title': _t('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,
- 'payment_method_id' : parsed_result.payment_method_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...'),
- });
- }
-
- rpc.query({
- model: 'pos_mercury.mercury_transaction',
- method: 'do_payment',
- args: [transaction],
- }, {
- timeout: self.server_timeout_in_ms,
- })
- .then(function (data) {
- // if not receiving a response from Vantiv, 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 Vantiv merchant account.")
- });
- return;
- }
-
- if (data === "internal error") {
- def.resolve({
- message: _t("Odoo error while processing transaction.")
- });
- return;
- }
-
- var response = self.pos.decodeMercuryResponse(data);
- response.payment_method_id = parsed_result.payment_method_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.payment_methods_by_id[parsed_result.payment_method_id]);
- }
-
- order.selected_paymentline.paid = true;
- order.selected_paymentline.mercury_swipe_pending = false;
- order.selected_paymentline.mercury_amount = response.authorize;
- order.selected_paymentline.set_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
- });
- }
- }
-
- }).catch(function () {
- 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) {
- var self = this;
- var online_payment_methods = this.pos.getOnlinePaymentMethods();
-
- if (online_payment_methods.length === 1) {
- parsed_result.payment_method_id = online_payment_methods[0].item;
- self.credit_code_transaction(parsed_result);
- } else { // this is for supporting another payment system like mercury
- this.gui.show_popup('selection',{
- title: _t('Pay with: '),
- list: online_payment_methods,
- confirm: function (item) {
- parsed_result.payment_method_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,
- });
- }
-
- rpc.query({
- model: 'pos_mercury.mercury_transaction',
- method: rpc_method,
- args: [request_data],
- }, {
- 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,
- });
- }
- }
- }).catch(function () {
- 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 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 order = this.pos.get_order();
- var payment_method = this.pos.payment_methods_by_id[id];
- var res = this._super(id);
- if (res && payment_method.pos_mercury_config_id) {
- order.selected_paymentline.mercury_swipe_pending = true;
- this.render_paymentlines();
- order.trigger('change', order); // needed so that export_to_JSON gets triggered
- }
- },
-
- show: function () {
- this._super();
- if (this.pos.getOnlinePaymentMethods().length !== 0) {
- this.pos.barcode_reader.set_action_callback('credit', _.bind(this.credit_code_action, this));
- }
- },
-});
-
});
diff --git a/addons/pos_mercury/static/src/xml/OrderReceipt.xml b/addons/pos_mercury/static/src/xml/OrderReceipt.xml
new file mode 100644
index 00000000000..b05ebcada28
--- /dev/null
+++ b/addons/pos_mercury/static/src/xml/OrderReceipt.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="pos_mercury.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension" owl="1">
+ <xpath expr="//t[@t-foreach='paymentlines']" position="inside">
+ <t t-if="line.mercury_data">
+ <div class="pos-receipt-left-padding">
+ <span>APPROVAL CODE: </span>
+ <span>
+ <t t-esc="line.mercury_auth_code" />
+ </span>
+ </div>
+ </t>
+ </xpath>
+ <xpath expr="//div[hasclass('pos-receipt-order-data')]" position="after">
+ <div t-if="hasPosMercurySignature">
+ <br />
+ <div>CARDHOLDER WILL PAY CARD ISSUER</div>
+ <div>ABOVE AMOUNT PURSUANT</div>
+ <div>TO CARDHOLDER AGREEMENT</div>
+ <br />
+ <br />
+ <div>X______________________________</div>
+ </div>
+ </xpath>
+ </t>
+
+</templates>
diff --git a/addons/pos_mercury/static/src/xml/PaymentScreenPaymentLines.xml b/addons/pos_mercury/static/src/xml/PaymentScreenPaymentLines.xml
new file mode 100644
index 00000000000..c44071847de
--- /dev/null
+++ b/addons/pos_mercury/static/src/xml/PaymentScreenPaymentLines.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="pos_mercury.PaymentScreenPaymentLines" t-inherit="point_of_sale.PaymentScreenPaymentLines" t-inherit-mode="extension" owl="1">
+ <xpath expr="//div[hasclass('col-name')]//t[@t-esc='line.payment_method.name']" position="replace">
+ <t t-if="!line.payment_method.is_cash_count and line.mercury_swipe_pending">
+ <span>WAITING FOR SWIPE</span>
+ </t>
+ <t t-else="">
+ <t t-esc="line.payment_method.name" />
+ </t>
+ </xpath>
+ </t>
+
+</templates>
diff --git a/addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml b/addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml
new file mode 100644
index 00000000000..39c05a8fce0
--- /dev/null
+++ b/addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="PaymentTransactionPopup" owl="1">
+ <div role="dialog" class="modal-dialog">
+ <div class="popup">
+ <p class="title">
+ <t t-esc="props.title"></t>
+ </p>
+ <p class="body">
+ <t t-esc="state.message"></t>
+ </p>
+ <div t-if="state.confirmButtonIsShown" class="footer">
+ <div class="button cancel" t-on-click="confirm">
+ <t t-esc="props.confirmText"></t>
+ </div>
+ </div>
+ </div>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_mercury/static/src/xml/pos_mercury.xml b/addons/pos_mercury/static/src/xml/pos_mercury.xml
deleted file mode 100644
index d0af7b8b55d..00000000000
--- a/addons/pos_mercury/static/src/xml/pos_mercury.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<templates id="template" inherit_id="point_of_sale.template">
- <t t-name="PaymentTransactionPopupWidget">
- <div role="dialog" class="modal-dialog">
- <div class="popup">
- <p class="title">Online 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.payment_method.is_cash_count">
- <t t-if="line.mercury_swipe_pending">
- <div>WAITING FOR SWIPE</div>
- </t>
- <t t-if="! line.mercury_swipe_pending">
- <t t-esc='line.payment_method.name' />
- </t>
- </t>
- <t t-if="line.payment_method.is_cash_count">
- <t t-esc='line.payment_method.name' />
- </t>
- </t>
- <!-- if there is payment status, then it is terminal paymentline, so payment-terminal class should be added. -->
- <t t-jquery=".body div[t-attf-class*='paymentline selected']">
- this.removeAttr('class');
- this.attr('t-attf-class', 'paymentline selected#{(line.get_payment_status() ? \' payment-terminal\' : \'\') + (line.mercury_swipe_pending ? \' o_pos_mercury_swipe_pending\' : \'\')}');
- </t>
- <t t-jquery=".body .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=".body .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 &amp;&amp; ! 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="OrderReceipt">
- <t t-jquery="t[t-foreach*='paymentlines'][t-as*='line']" t-operation="append">
- <t t-if="line.mercury_data">
- <div class="pos-receipt-left-padding">
- APPROVAL CODE: <t t-esc="line.mercury_auth_code"/>
- </div>
- </t>
- </t>
- <t t-jquery="receipt" t-operation="append">
- <div>
- <t t-call="PosMercurySignature"/>
- </div>
- </t>
- </t>
-
-</templates>
diff --git a/addons/pos_mercury/views/pos_mercury_templates.xml b/addons/pos_mercury/views/pos_mercury_templates.xml
index 60352a5e051..4233cda51ee 100644
--- a/addons/pos_mercury/views/pos_mercury_templates.xml
+++ b/addons/pos_mercury/views/pos_mercury_templates.xml
@@ -5,6 +5,11 @@
<template id="assets" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<script type="text/javascript" src="/pos_mercury/static/src/js/pos_mercury.js"></script>
+ <script type="text/javascript" src="/pos_mercury/static/src/js/OrderReceipt.js"></script>
+ <script type="text/javascript" src="/pos_mercury/static/src/js/PaymentScreen.js"></script>
+ <script type="text/javascript" src="/pos_mercury/static/src/js/PaymentScreenPaymentLines.js"></script>
+ <script type="text/javascript" src="/pos_mercury/static/src/js/PaymentTransactionPopup.js"></script>
+ <script type="text/javascript" src="/pos_mercury/static/src/js/ProductScreen.js"></script>
<link rel="stylesheet" href="/pos_mercury/static/src/css/pos_mercury.css"/>
</xpath>
</template>