mirror of
https://github.com/bringout/oca-ocb-pos.git
synced 2026-04-23 05:42:09 +02:00
Initial commit: Pos packages
This commit is contained in:
commit
95dfb9edb0
1301 changed files with 264148 additions and 0 deletions
|
|
@ -0,0 +1,3 @@
|
|||
.pos .paymentline.selected.o_pos_mercury_swipe_pending, .pos .paymentline.o_pos_mercury_swipe_pending {
|
||||
background: rgb(239, 153, 65);
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -0,0 +1,597 @@
|
|||
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 {
|
||||
setup() {
|
||||
super.setup();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The card reader acts as a barcode scanner. This sets up
|
||||
* the NumberBuffer to not immediately act on keyboard
|
||||
* input.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
get _getNumberBufferConfig() {
|
||||
const res = super._getNumberBufferConfig;
|
||||
res['useWithBarcode'] = true;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish any pending input before trying to validate.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async validateOrder(isForceValidate) {
|
||||
NumberBuffer.capture();
|
||||
return super.validateOrder(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish any pending input before sending a request to a terminal.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async _sendPaymentRequest({ detail: line }) {
|
||||
NumberBuffer.capture();
|
||||
return super._sendPaymentRequest(...arguments);
|
||||
}
|
||||
|
||||
_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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(PaymentScreen, PosMercuryPaymentScreen);
|
||||
|
||||
return PaymentScreen;
|
||||
});
|
||||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
odoo.define('pos_mercury.PaymentTransactionPopup', function(require) {
|
||||
'use strict';
|
||||
|
||||
const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { _lt } = require('@web/core/l10n/translation');
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
class PaymentTransactionPopup extends AbstractAwaitablePopup {
|
||||
setup() {
|
||||
super.setup();
|
||||
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: _lt('Ok'),
|
||||
title: _lt('Online Payment'),
|
||||
body: '',
|
||||
cancelKey: false,
|
||||
};
|
||||
|
||||
Registries.Component.add(PaymentTransactionPopup);
|
||||
|
||||
return PaymentTransactionPopup;
|
||||
});
|
||||
|
|
@ -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 {
|
||||
setup() {
|
||||
super.setup();
|
||||
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;
|
||||
});
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
odoo.define('pos_mercury.pos_mercury', function (require) {
|
||||
"use strict";
|
||||
|
||||
var { PosGlobalState, Order, Payment } = require('point_of_sale.models');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
|
||||
const PosMercuryPosGlobalState = (PosGlobalState) => class PosMercuryPosGlobalState extends PosGlobalState {
|
||||
getOnlinePaymentMethods() {
|
||||
var online_payment_methods = [];
|
||||
|
||||
$.each(this.payment_methods, function (i, payment_method) {
|
||||
if (payment_method.pos_mercury_config_id) {
|
||||
online_payment_methods.push({label: payment_method.name, item: payment_method.id});
|
||||
}
|
||||
});
|
||||
|
||||
return online_payment_methods;
|
||||
}
|
||||
decodeMagtek(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(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()),
|
||||
};
|
||||
}
|
||||
}
|
||||
Registries.Model.extend(PosGlobalState, PosMercuryPosGlobalState);
|
||||
|
||||
|
||||
const PosMercuryPayment = (Payment) => class PosMercuryPayment extends Payment {
|
||||
init_from_JSON(json) {
|
||||
super.init_from_JSON(...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() {
|
||||
return _.extend(super.export_as_JSON(...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() {
|
||||
if (this.mercury_card_number) {
|
||||
this.name = this.mercury_card_brand + " (****" + this.mercury_card_number + ")";
|
||||
}
|
||||
}
|
||||
is_done() {
|
||||
var res = super.is_done(...arguments);
|
||||
return res && !this.mercury_swipe_pending;
|
||||
}
|
||||
export_for_printing() {
|
||||
const result = super.export_for_printing(...arguments);
|
||||
result.mercury_data = this.mercury_data;
|
||||
result.mercury_auth_code = this.mercury_auth_code;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Registries.Model.extend(Payment, PosMercuryPayment);
|
||||
|
||||
|
||||
const PosMercuryOrder = (Order) => class PosMercuryOrder extends Order {
|
||||
electronic_payment_in_progress() {
|
||||
var res = super.electronic_payment_in_progress(...arguments);
|
||||
return res || this.get_paymentlines().some(line => line.mercury_swipe_pending);
|
||||
}
|
||||
}
|
||||
Registries.Model.extend(Order, PosMercuryOrder);
|
||||
});
|
||||
|
|
@ -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='receipt.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>
|
||||
|
|
@ -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('paymentline')]//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>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="PaymentTransactionPopup" owl="1">
|
||||
<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>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Loading…
Add table
Add a link
Reference in a new issue