diff options
| author | Joseph Caburnay <jcb@odoo.com> | 2020-05-05 07:55:55 +0000 |
|---|---|---|
| committer | Pierre Masereel <pim@odoo.com> | 2020-05-12 12:48:58 +0000 |
| commit | ee593a916773e1e18d29dabac1a0e90d32aceaa0 (patch) | |
| tree | 874cda40148e96f904751c12e86be0efc57d2272 | |
| parent | 42b85f11154d12e6d040225e5e04aaedaac44b56 (diff) | |
[REF] pos_mercury: migration to OWL framework
| -rw-r--r-- | addons/pos_mercury/__manifest__.py | 4 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/js/OrderReceipt.js | 24 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/js/PaymentScreen.js | 569 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/js/PaymentScreenPaymentLines.js | 30 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/js/PaymentTransactionPopup.js | 37 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/js/ProductScreen.js | 26 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/js/pos_mercury.js | 514 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/xml/OrderReceipt.xml | 28 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/xml/PaymentScreenPaymentLines.xml | 15 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml | 22 | ||||
| -rw-r--r-- | addons/pos_mercury/static/src/xml/pos_mercury.xml | 72 | ||||
| -rw-r--r-- | addons/pos_mercury/views/pos_mercury_templates.xml | 5 |
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 && ! 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> |
