[MIG] pos_pax: Rewrite for Odoo 14.0 + Owl

This commit is contained in:
Jared Kipe
2020-11-15 15:07:02 -08:00
parent 99edb6916f
commit 9d08173221
18 changed files with 1179 additions and 0 deletions

3
pos_pax/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import models

45
pos_pax/__manifest__.py Normal file
View File

@@ -0,0 +1,45 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
{
'name': 'POS PAX Terminal Credit Card',
'author': 'Hibou Corp. <hello@hibou.io>',
'version': '14.0.1.0.0',
'category': 'Point of Sale',
'sequence': 6,
'summary': 'PAX Terminal Credit card support for Point Of Sale',
'description': """
Allow credit card POS payments
==============================
This module allows customers to pay for their orders with credit cards.
The transactions are processed on the PAX Terminal (no credit credit card
information through Odoo itself).
Depending on Device and processor support, this integration can handle:
* Magnetic swiped cards
* EMV chip cards
* Contactless (including Apple Pay, Samsung Pay, Google Pay)
""",
'depends': [
'web',
'pos_sale',
'hibou_professional',
],
'website': 'https://hibou.io',
'data': [
'views/pos_config_setting_views.xml',
'views/pos_pax_templates.xml',
'views/pos_pax_views.xml',
],
'demo': [
],
'qweb': [
'static/src/xml/OrderReceipt.xml',
'static/src/xml/PAXPaymentTransactionPopup.xml',
'static/src/xml/PAXPaymentScreenPaymentLines.xml',
],
'installable': True,
'auto_install': False,
}

View File

@@ -0,0 +1,4 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import pos_pax
from . import update

38
pos_pax/models/pos_pax.py Normal file
View File

@@ -0,0 +1,38 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import models, fields, api, _
class PoSPayment(models.Model):
_inherit = 'pos.payment'
pax_card_number = fields.Char(string='Card Number', help='Masked credit card.')
pax_txn_id = fields.Char(string='PAX Transaction ID')
class PoSPaymentMethod(models.Model):
_inherit = 'pos.payment.method'
def _get_payment_terminal_selection(self):
return super(PoSPaymentMethod, self)._get_payment_terminal_selection() + [('pax', 'PAX')]
class PosConfig(models.Model):
_inherit = 'pos.config'
pax_endpoint = fields.Char(string='PAX Endpoint',
help='Endpoint for PAX device (include protocol (http or https) and port). '
'e.g. http://192.168.1.101:10009')
class PosOrder(models.Model):
_inherit = "pos.order"
@api.model
def _payment_fields(self, order, ui_paymentline):
fields = super(PosOrder, self)._payment_fields(order, ui_paymentline)
fields.update({
'pax_card_number': ui_paymentline.get('pax_card_number'),
'pax_txn_id': ui_paymentline.get('pax_txn_id'),
})
return fields

20
pos_pax/models/update.py Normal file
View File

@@ -0,0 +1,20 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import models
class PublisherWarrantyContract(models.AbstractModel):
_inherit = 'publisher_warranty.contract'
def _get_hibou_modules(self):
modules = super(PublisherWarrantyContract, self)._get_hibou_modules()
try:
self.env.cr.execute(
'SELECT COUNT(*) FROM pos_config WHERE pax_endpoint != \'\' AND pax_endpoint IS NOT NULL')
pax_count = self.env.cr.fetchone()[0] or 0
modules.update({
'pos_pax': pax_count,
})
except:
pass
return modules

View File

@@ -0,0 +1,17 @@
.pos .paymentline.selected.o_pos_pax_txn_pending, .pos .paymentline.o_pos_pax_txn_pending {
background: rgb(239, 153, 65);
}
.pos .col-tendered.edit.o_pos_pax_txn_pending {
color: rgb(239, 153, 65);
box-shadow: 0px 0px 0px 3px rgb(239, 153, 65);
}
.pos .paymentline .pax-send-transaction-button {
cursor: pointer !important;
border: 1px solid rgba(255, 255, 255, 0.5);
background-color: rgba(255,255,255, 0.2);
display: block;
text-align: center;
margin: 6px 0 0 0;
padding: 2px 5px;
}

View File

@@ -0,0 +1,183 @@
odoo.define('pos_pax.PAXPaymentScreen', function (require) {
'use strict';
// Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
const { _t } = require('web.core');
const PaymentScreen = require('point_of_sale.PaymentScreen');
const Registries = require('point_of_sale.Registries');
const { useListener } = require('web.custom_hooks');
const PAX = require('pos_pax.pax_device');
PAX.mDestinationIP = '';
const PAXPaymentScreen = (PaymentScreen) =>
class extends PaymentScreen {
constructor() {
super(...arguments);
// tempting to use 'send-payment-request',
// but method implements things that don't seem to exist yet (payment_method.payment_terminal)
useListener('pax-send-payment-request', this._sendPAXPaymentRequest);
}
async _sendPAXPaymentRequest({ detail: line }) {
this.pax_credit_transaction(line);
}
pax_credit_transaction(line) {
var order = this.env.pos.get_order();
if(this.env.pos.getPAXOnlinePaymentJournals().length === 0) {
return;
}
var self = this;
var transaction = {};
var purchase_amount = line.get_amount();
var transactionType = '01'; // SALE
if (purchase_amount < 0.0) {
purchase_amount = -purchase_amount;
transactionType = '02'; // RETURN
}
transaction = {
command: 'T00',
version: '1.28',
transactionType: transactionType,
amountInformation: {
TransactionAmount: (purchase_amount * 100) | 0, // cast to integer
},
cashierInformation: {
ClerkID: this.env.pos.user.name,
},
traceInformation: {
ReferenceNumber: self.env.pos.get_order().uid,
// InvoiceNumber: self.env.pos.get_order().uid,
},
}
var def = new $.Deferred();
// show the transaction popup.
// the transaction deferred is used to update transaction status
self.showPopup('PAXPaymentTransactionPopup', {
transaction: def
});
def.notify({
message: _t('Handling transaction...'),
});
PAX.mDestinationIP = self.env.pos.config.pax_endpoint;
PAX.DoCredit(transaction, function (response) {
console.log(response);
var parsed_response = self.env.pos.decodePAXResponse(response);
if (parsed_response.fail) {
def.resolve({message: parsed_response.fail})
return;
}
if (parsed_response.success) {
line.paid = true;
line.pax_card_number = parsed_response.card_num;
line.pax_txn_id = parsed_response.txn_id;
line.pax_approval = parsed_response.approval;
line.pax_txn_pending = false;
line.set_credit_card_name();
order.trigger('change', order);
self.render();
def.resolve({message: 'Approval ' + parsed_response.approval, auto_close: true})
}
def.resolve({message: _t('Unknown response.')})
}, function (fail){
def.resolve({message: _t('Communication Failure: ') + fail});
});
}
pax_do_reversal(line) {
var def = new $.Deferred();
var self = this;
var transaction = {};
// show the transaction popup.
// the transaction deferred is used to update transaction status
this.showPopup('PAXPaymentTransactionPopup', {
transaction: def
});
def.notify({
message: 'Sending reversal...',
});
transaction = {
command: 'T00',
version: '1.28',
transactionType: (line.get_amount() > 0) ? '17' : '18', // V/SALE, V/RETURN
cashierInformation: {
ClerkID: this.env.pos.user.name,
},
traceInformation: {
ReferenceNumber: this.env.pos.get_order().uid,
InvoiceNumber: '',
AuthCode: line.pax_approval,
TransactionNumber: line.pax_txn_id,
},
}
PAX.mDestinationIP = self.env.pos.config.pax_endpoint;
PAX.DoCredit(transaction, function (response) {
var parsed_response = self.env.pos.decodePAXResponse(response);
if (parsed_response.fail) {
def.resolve({message: parsed_response.fail})
return;
}
if (parsed_response.success) {
def.resolve({
message: _t('Reversal succeeded.'),
auto_close: true,
});
self.remove_paymentline_by_ref(line);
return;
}
def.resolve({message: _t('Unknown response.')})
}, function (fail){
def.resolve({message: _t('Communication Failure: ') + fail});
});
}
remove_paymentline_by_ref(line) {
this.env.pos.get_order().remove_paymentline(line);
this.render();
}
/**
* @override
*/
deletePaymentLine(event) {
const { cid } = event.detail;
const line = this.paymentLines.find((line) => line.cid === cid);
if (line.pax_txn_id) {
this.pax_do_reversal(line);
} else {
super.deletePaymentLine(event);
}
}
/**
* @override
*/
addNewPaymentLine({ detail: paymentMethod }) {
const order = this.env.pos.get_order();
const res = super.addNewPaymentLine(...arguments);
if (res && paymentMethod.use_payment_terminal == 'pax') {
order.selected_paymentline.pax_txn_pending = true;
order.trigger('change', order);
this.render();
}
}
};
Registries.Component.extend(PaymentScreen, PAXPaymentScreen);
return PAXPaymentScreen;
});

View File

@@ -0,0 +1,32 @@
odoo.define('pos_pax.PAXPaymentScreenPaymentLines', function (require) {
'use strict';
// Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
const PaymentScreenPaymentLines = require('point_of_sale.PaymentScreenPaymentLines');
const Registries = require('point_of_sale.Registries');
const PAXPaymentLines = (PaymentScreenPaymentLines) =>
class extends PaymentScreenPaymentLines {
/**
* @override
*/
selectedLineClass(line) {
return Object.assign({}, super.selectedLineClass(line), {
o_pos_pax_txn_pending: line.pax_txn_pending,
});
}
/**
* @override
*/
unselectedLineClass(line) {
return Object.assign({}, super.unselectedLineClass(line), {
o_pos_pax_txn_pending: line.pax_txn_pending,
});
}
};
Registries.Component.extend(PaymentScreenPaymentLines, PAXPaymentLines);
return PAXPaymentLines;
});

View File

@@ -0,0 +1,39 @@
odoo.define('pos_pax.PAXPaymentTransactionPopup', function(require) {
'use strict';
// Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
const { useState } = owl.hooks;
const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
const Registries = require('point_of_sale.Registries');
class PAXPaymentTransactionPopup 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;
})
}
}
PAXPaymentTransactionPopup.template = 'PAXPaymentTransactionPopup';
PAXPaymentTransactionPopup.defaultProps = {
confirmText: 'Ok',
cancelText: 'Cancel',
title: 'PAX Online Payment',
body: '',
};
Registries.Component.add(PAXPaymentTransactionPopup);
return PAXPaymentTransactionPopup;
});

133
pos_pax/static/src/js/jquery_base64.js vendored Normal file
View File

@@ -0,0 +1,133 @@
odoo.define('pos_pax.jquery_base64', function (require) {
"use strict";
// Hibou Corp. © 2020 - Wrapped library for PAX Device
var jQuery = window.jQuery;
/*!
* jquery.base64.js 0.1 - https://github.com/yckart/jquery.base64.js
* Makes Base64 en & -decoding simpler as it is.
*
* Based upon: https://gist.github.com/Yaffle/1284012
*
* Copyright (c) 2012 Yannick Albert (http://yckart.com)
* Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php).
* 2013/02/10
**/
;(function ($) {
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
a256 = '',
r64 = [256],
r256 = [256],
i = 0;
var UTF8 = {
/**
* Encode multi-byte Unicode string into utf-8 multiple single-byte characters
* (BMP / basic multilingual plane only)
*
* Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
*
* @param {String} strUni Unicode string to be encoded as UTF-8
* @returns {String} encoded string
*/
encode: function (strUni) {
// use regular expressions & String.replace callback function for better efficiency
// than procedural approaches
var strUtf = strUni.replace(/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
function (c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);
})
.replace(/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
function (c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3F, 0x80 | cc & 0x3f);
});
return strUtf;
},
/**
* Decode utf-8 encoded string back into multi-byte Unicode characters
*
* @param {String} strUtf UTF-8 string to be decoded back to Unicode
* @returns {String} decoded string
*/
decode: function (strUtf) {
// note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars
function (c) { // (note parentheses for precence)
var cc = ((c.charCodeAt(0) & 0x0f) << 12) | ((c.charCodeAt(1) & 0x3f) << 6) | (c.charCodeAt(2) & 0x3f);
return String.fromCharCode(cc);
})
.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars
function (c) { // (note parentheses for precence)
var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f;
return String.fromCharCode(cc);
});
return strUni;
}
};
while (i < 256) {
var c = String.fromCharCode(i);
a256 += c;
r256[i] = i;
r64[i] = b64.indexOf(c);
++i;
}
function code(s, discard, alpha, beta, w1, w2) {
s = String(s);
var buffer = 0,
i = 0,
length = s.length,
result = '',
bitsInBuffer = 0;
while (i < length) {
var c = s.charCodeAt(i);
c = c < 256 ? alpha[c] : -1;
buffer = (buffer << w1) + c;
bitsInBuffer += w1;
while (bitsInBuffer >= w2) {
bitsInBuffer -= w2;
var tmp = buffer >> bitsInBuffer;
result += beta.charAt(tmp);
buffer ^= tmp << bitsInBuffer;
}
++i;
}
if (!discard && bitsInBuffer > 0) result += beta.charAt(buffer << (w2 - bitsInBuffer));
return result;
}
var Plugin = $.base64 = function (dir, input, encode) {
return input ? Plugin[dir](input, encode) : dir ? null : this;
};
Plugin.btoa = Plugin.encode = function (plain, utf8encode) {
plain = Plugin.raw === false || Plugin.utf8encode || utf8encode ? UTF8.encode(plain) : plain;
plain = code(plain, false, r256, b64, 8, 6);
return plain + '===='.slice((plain.length % 4) || 4);
};
Plugin.atob = Plugin.decode = function (coded, utf8decode) {
coded = String(coded).split('=');
var i = coded.length;
do {
--i;
coded[i] = code(coded[i], true, r64, a256, 6, 8);
} while (i > 0);
coded = coded.join('');
return Plugin.raw === false || Plugin.utf8decode || utf8decode ? UTF8.decode(coded) : coded;
};
}(jQuery));
return jQuery;
});

View File

@@ -0,0 +1,479 @@
odoo.define('pos_pax.pax_device', function (require) {
"use strict";
var $ = require('pos_pax.jquery_base64');
// Hibou Corp. © 2020 - Wrapped library for PAX Device
// Additions and Fixes:
// 2020-11-12 Fixed variable i and ii in getLRC
// 2020-11-12 Fixed/added 'fail' mechanisms for XHR
//HEX TO BASE64
function hexToBase64(str) {
return $.base64.btoa(String.fromCharCode.apply(null,
str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))
);
}
//BASE64 TO HEX
function base64ToHex(str) {
for (var i = 0, bin = $.base64.atob(str), hex = []; i < bin.length; ++i) {
var tmp = bin.charCodeAt(i).toString(16);
if (tmp.length === 1) tmp = "0" + tmp;
hex[hex.length] = tmp;
}
return hex.join(" ");
}
function StringToHex(response){
var responseHex = "";
for(var i=0; i<response.length; i++){
if(responseHex == "")
responseHex = response.charCodeAt(i).toString(16).length<2?'0'+response.charCodeAt(i).toString(16):response.charCodeAt(i).toString(16);
else
responseHex += response.charCodeAt(i).toString(16).length<2?" " + '0'+response.charCodeAt(i).toString(16):" " + response.charCodeAt(i).toString(16);
}
return responseHex;
}
function HexToString(response){
var responseHex = "";
var arr = response.split(" ");
for(var i=0; i<arr.length; i++){
if(arr[i] == "")
continue;
responseHex += String.fromCharCode(parseInt(arr[i],16));
}
return responseHex;
}
var PAX = {
//IP of the POS
mDestinationIP : "http://127.0.0.1:10009", // - OLD "http://192.167.2.100:10009"; //http://112.199.49.146:8181
mStx : {
hex: 0x02,
code: "02"
},
mFS : {
hex : 0x1c,
code : "1c"
},
mEtx : {
hex : 0x03,
code : "03"
},
mUS : {
hex : 0x1F,
code : "1F"
},
//var _this : this;
customData : '',
timeout : {
"Initialize":120*1000,
"GetSignature":120*1000,
"DoSignature":120*1000,
"DoCredit":120*1000
},
//Set ip and port
Settings : function(ip,port){
this.mDestinationIP = "http://" + ip + ":" + port;
console.log("New service address: "+this.mDestinationIP);
},
AjaxTimeOut : function(command,timeout){
this.timeout[command]= timeout;
},
SetCustomData : function(custom_data){
this.customData = custom_data;
console.log(custom_data);
},
//Get LRC
getLRC : function(params){
var lrc = 0;
for(var i=1; i< params.length; i++){
var type_of = typeof(params[i]);
if(type_of == "string"){
var element = params[i].split("");
for(var ii=0; ii<element.length; ii++){
lrc ^= element[ii].charCodeAt(0);
}
}else{
lrc ^= params[i];
}
}
return (lrc>0)?String.fromCharCode(lrc):0;
},
//Connect to the server
HttpCommunication : function(commandType,url,callback,timeout,fail){
var xhr = null;
if(window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
try{
xhr = new ActiveXObject('Microsoft.XMLHttp');
}catch(e){
xhr = new ActiveXObject('msxml2.xmlhttp');
}
}
//get
xhr.open("GET", url,true);
xhr.onreadystatechange=function(){
if(xhr.readyState==4) {
//alert(xhr.status);
if(xhr.status==200) {
var response = xhr.responseText;
console.log("Raw response: "+response);
var checkParams = StringToHex(response).split(" ").pop();
var RedundancyCheck = StringToHex(response).split(" ").pop().substring(1);
var check = PAX.getLRC(checkParams);
if(check == RedundancyCheck){
//get package detail info
var packetInfo = [];
var len = StringToHex(response).indexOf("03");
var hex = StringToHex(response).slice(0,len).split(/02|1c/);
console.log(hex);
if(commandType == "DoCredit"){
var subHex=[], subPacketInfo=[];
for(var i=0; i<hex.length; i++){
if(hex[i] != ""){
if(hex[i].indexOf("1f")>0){
subHex = hex[i].split("1f");
console.log(subHex);
subPacketInfo = [];
for(var j=0; j<subHex.length; j++){
if(subHex[j]!=''){
subPacketInfo.push(HexToString(subHex[j]));
}
}
console.log(subPacketInfo);
packetInfo.push(subPacketInfo);
}else{
packetInfo[i] = HexToString(hex[i]);
}
}
}
}else{
for(var i=0; i<hex.length; i++){
if(hex[i] != ""){
packetInfo[i] = HexToString(hex[i]);
}
}
}
console.log("Separate package info: ");
console.log(packetInfo);
callback(packetInfo);
}
} else {
if(fail) {
fail(xhr.status);
}
}
}
};
xhr.send(null);
},
Initialize : function(initialInfo,callback,fail){
var params = [this.mStx.hex,initialInfo.command,this.mFS.hex, initialInfo.version, this.mEtx.hex];
//[02]A08[1c]1.28[1c]0[1c]90000[03]
//var params = [0x02,"A08",0x1c,"1.28",0x1c, "0", 0x1c,"90000",0x03];
var lrc = this.getLRC(params);
var command_hex = base64ToHex($.base64.btoa(initialInfo.command));
var version_hex = base64ToHex($.base64.btoa(initialInfo.version));
//var elements = [this.mStx, command_hex, this.mFS, version_hex, this.mEtx, base64ToHex($.base64.btoa(lrc))];
var elements = [this.mStx.code, command_hex, this.mFS.code, version_hex, this.mEtx.code, base64ToHex($.base64.btoa(lrc))];
var final_string = elements.join(" ");
//console.log("final_string: " + final_string);
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('Initialize',url,function(response){
callback(response);
},PAX.timeout.Initialize,fail);
},
//GET SIGNATURE
GetSignature : function(getSignatureInfo,callback,fail){
var params = [this.mStx.hex,getSignatureInfo.command,this.mFS.hex,getSignatureInfo.version,this.mFS.hex, getSignatureInfo.offset, this.mFS.hex,getSignatureInfo.requestlength,this.mEtx.hex];
var lrc = this.getLRC(params);
//prepare for base64 encoding.
var command_hex = base64ToHex($.base64.btoa(getSignatureInfo.command));
var version_hex = base64ToHex($.base64.btoa(getSignatureInfo.version));
var offset_hex = base64ToHex($.base64.btoa(getSignatureInfo.offset));
var requestlength_hex = base64ToHex($.base64.btoa(getSignatureInfo.requestlength));
//var elements = [this.mStx.code, command_hex, this.mFS.code, version_hex, this.mFS.code, offset_hex, this.mFS.code, requestlength_hex, this.mEtx.code, base64ToHex($.base64.btoa(lrc))];
var elements = [this.mStx.code];
elements.push(command_hex);
elements.push(this.mFS.code);
elements.push(version_hex);
elements.push(this.mFS.code);
if(offset_hex != ''){
elements.push(offset_hex);
}
elements.push(this.mFS.code);
if(requestlength_hex != ''){
elements.push(requestlength_hex);
}
elements.push(this.mEtx.code);
elements.push(base64ToHex($.base64.btoa(lrc)));
var final_string = elements.join(" ");
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('GetSignature',url,function(response){
callback(response);
},PAX.timeout.GetSignature,fail);
},
//DO SIGNATURE
DoSignature : function(doSignatureInfo,callback,fail){
var params = [this.mStx.hex,doSignatureInfo.command, this.mFS.hex, doSignatureInfo.version, this.mFS.hex, doSignatureInfo.uploadFlag, this.mFS.hex,doSignatureInfo.hostReferenceNumber, this.mFS.hex, doSignatureInfo.edcType, this.mFS.hex, doSignatureInfo.timeout, this.mEtx.hex];
var lrc = this.getLRC(params);
//prepare for base64 encoding.
var command_hex = base64ToHex($.base64.btoa(doSignatureInfo.command));
var version_hex = base64ToHex($.base64.btoa(doSignatureInfo.version));
var uploadFlag_hex = base64ToHex($.base64.btoa(doSignatureInfo.uploadFlag));
var hostReferenceNumber_hex = base64ToHex($.base64.btoa(doSignatureInfo.hostReferenceNumber));
var edcType_hex = base64ToHex($.base64.btoa(doSignatureInfo.edcType));
var timeout_hex = base64ToHex($.base64.btoa(doSignatureInfo.timeout));
var elements = [this.mStx.code];
elements.push(command_hex);
elements.push(this.mFS.code);
elements.push(version_hex);
elements.push(this.mFS.code);
if(uploadFlag_hex != ''){
elements.push(uploadFlag_hex);
}
elements.push(this.mFS.code);
if(hostReferenceNumber_hex != ''){
elements.push(hostReferenceNumber_hex);
}
elements.push(this.mFS.code);
if(edcType_hex != ''){
elements.push(edcType_hex);
}
elements.push(this.mFS.code);
if(timeout_hex != ''){
elements.push(timeout_hex);
}
elements.push(this.mEtx.code);
elements.push(base64ToHex($.base64.btoa(lrc)));
var final_string = elements.join(" ");
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('DoSignature',url,function(response){
callback(response);
},PAX.timeout.DoSignature,fail);
},
PushParams : function(params,type,objectInfo){
var empty = 0;
var arr = [];
arr = arr.concat(params);
for(name in objectInfo){
if(objectInfo[name] == '' && type!="additionalInformation")
{
arr.push(this.mUS.hex);
continue;
}
if(type == "additionalInformation"){
if(objectInfo[name] == ''){
continue;
}
empty++;
arr.push(name+"="+objectInfo[name].toString());
}else{
empty++;
arr.push(objectInfo[name].toString());
}
arr.push(this.mUS.hex);
}
arr.pop();
if(empty==0 && type!="additionalInformation"){
arr = params;
}
if(empty==0 && type=="additionalInformation"){
arr.push(this.mFS.hex);
}
//console.log(params);
return arr;
},
AddBase64 : function(elements,type,objectInfo){
//console.log(objectInfo);
var empty = 0;
var arr = [];
arr = arr.concat(elements);
for(name in objectInfo){
if(objectInfo[name] == '' && type!="additionalInformation")
{
arr.push(this.mUS.code);
continue;
}
if(type == "additionalInformation"){
if(objectInfo[name] == '')
continue;
empty++;
arr.push(base64ToHex($.base64.btoa(name+"="+objectInfo[name].toString())));
}else{
empty++;
arr.push(base64ToHex($.base64.btoa(objectInfo[name].toString())));
}
arr.push(this.mUS.code);
}
arr.pop();
if(empty==0 && type!="additionalInformation"){
arr = elements;
}
if(empty==0 && type=="additionalInformation"){
arr.push(this.mFS.code);
}
//console.log(arr);
return arr;
},
//DO Credit
DoCredit : function(doCreditInfo,callback,fail){
var amountInformation,accountInformation,traceInformation,avsInformation,cashierInformation,commercialInformation,motoEcommerce,additionalInformation;
var params = [this.mStx.hex,doCreditInfo.command, this.mFS.hex, doCreditInfo.version];
params.push(this.mFS.hex);
if(doCreditInfo.transactionType != ''){
params.push(doCreditInfo.transactionType);
}
params.push(this.mFS.hex);
params = this.PushParams(params,"amountInformation",doCreditInfo.amountInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"accountInformation",doCreditInfo.accountInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"traceInformation",doCreditInfo.traceInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"avsInformation",doCreditInfo.avsInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"cashierInformation",doCreditInfo.cashierInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"commercialInformation",doCreditInfo.commercialInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"motoEcommerce",doCreditInfo.motoEcommerce);
params.push(this.mFS.hex);
params = this.PushParams(params,"additionalInformation",doCreditInfo.additionalInformation);
params.push(this.mEtx.hex);
var lrc = this.getLRC(params);
console.log(params);
//prepare for base64 encoding.
var command_hex = base64ToHex($.base64.btoa(doCreditInfo.command));
var version_hex = base64ToHex($.base64.btoa(doCreditInfo.version));
var transactionType_hex = base64ToHex($.base64.btoa(doCreditInfo.transactionType));
var amountInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.amountInformation));
var accountInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.accountInformation));
var traceInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.traceInformation));
var avsInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.avsInformation));
var cashierInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.cashierInformation));
var commercialInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.commercialInformation));
var motoEcommerce_hex = base64ToHex($.base64.btoa(doCreditInfo.motoEcommerce));
var additionalInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.additionalInformation));
//var elements = [this.mStx.code, command_hex, this.mFS.code, version_hex, this.mFS.code, uploadFlag_hex, this.mFS.code, timeout, this.mEtx.code, base64ToHex($.base64.btoa(lrc))];
var elements = [this.mStx.code];
elements.push(command_hex);
elements.push(this.mFS.code);
elements.push(version_hex);
elements.push(this.mFS.code);
if(transactionType_hex != ''){
elements.push(transactionType_hex);
}
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"amountInformation",doCreditInfo.amountInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"accountInformation",doCreditInfo.accountInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"traceInformation",doCreditInfo.traceInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"avsInformation",doCreditInfo.avsInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"cashierInformation",doCreditInfo.cashierInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"commercialInformation",doCreditInfo.commercialInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"motoEcommerce",doCreditInfo.motoEcommerce);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"additionalInformation",doCreditInfo.additionalInformation);
elements.push(this.mEtx.code);
elements.push(base64ToHex($.base64.btoa(lrc)));
console.log("elements");
console.log(elements);
var final_string = elements.join(" ");
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
// if(customData != ''){
// final_b64 = hexToBase64(final_string+"&custom_data=<PAX>"+customData+"</PAX>");
// }
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('DoCredit',url,function(response){
callback(response);
},PAX.timeout.DoCredit,fail);
}
};
return PAX;
});

View File

@@ -0,0 +1,71 @@
odoo.define('pos_pax.pos_pax', function (require) {
"use strict";
// Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
const pos_model = require('point_of_sale.models');
pos_model.PosModel = pos_model.PosModel.extend({
getPAXOnlinePaymentJournals: function () {
var online_payment_methods = [];
$.each(this.payment_methods, function (i, payment_method) {
if (payment_method.use_payment_terminal == 'pax') {
online_payment_methods.push({label: payment_method.name, item: payment_method.id});
}
});
return online_payment_methods;
},
decodePAXResponse: function (data) {
if (data[5] == 'ABORTED') {
return {fail: 'Transaction Aborted'};
} else if (data[5] == 'OK') {
return {
success: true,
approval: data[6][2],
txn_id: data[10][0],
card_num: '***' + data[9][0],
}
}
return {fail: 'Unknown Response. ' + data};
},
});
var _paylineproto = pos_model.Paymentline.prototype;
pos_model.Paymentline = pos_model.Paymentline.extend({
init_from_JSON: function (json) {
_paylineproto.init_from_JSON.apply(this, arguments);
this.paid = json.paid;
this.pax_txn_pending = json.pax_txn_pending;
this.pax_card_number = json.pax_card_number;
this.pax_approval = json.pax_approval;
this.pax_txn_id = json.pax_txn_id;
this.set_credit_card_name();
},
export_as_JSON: function () {
return _.extend(_paylineproto.export_as_JSON.apply(this, arguments), {
paid: this.paid,
pax_txn_pending: this.pax_txn_pending,
pax_card_number: this.pax_card_number,
pax_approval: this.pax_approval,
pax_txn_id: this.pax_txn_id,
});
},
set_credit_card_name: function () {
if (this.pax_card_number) {
this.name = this.pax_card_number;
}
},
});
var _order_super = pos_model.Order.prototype;
pos_model.Order = pos_model.Order.extend({
electronic_payment_in_progress: function() {
var res = _order_super.electronic_payment_in_progress.apply(this, arguments);
return res || this.get_paymentlines().some(line => line.pax_txn_pending);
},
});
});

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_pax.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.pax_approval">
<div class="pos-receipt-left-padding">
<span>APPROVAL CODE: </span>
<span>
<t t-esc="line.pax_approval" />
</span>
</div>
</t>
</xpath>
</t>
</templates>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_pax.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.pax_txn_pending">
<span>WAITING FOR TXN</span>
<div class="pax-send-transaction-button"
t-on-click="trigger('pax-send-payment-request', line)"
aria-label="Send Transaction" title="Send Transaction">
<span>Send</span>
</div>
</t>
<t t-else="">
<t t-esc="line.payment_method.name" />
<t t-esc="line.name" />
</t>
</xpath>
</t>
</templates>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="PAXPaymentTransactionPopup" 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>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="pos_config_view_form_inherit_pos_pax" model="ir.ui.view">
<field name="name">pos.config.form.inherit.pax</field>
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.pos_config_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='payment_methods_new']" position="after">
<div class="col-12 col-lg-6 o_setting_box" id="pos_pax">
<div class="o_setting_right_pane">
<strong class="o_form_label">PAX</strong>
<div>
<p>Your PAX Device Endpoint</p>
<field name="pax_endpoint"/>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<script type="text/javascript" src="/pos_pax/static/src/js/jquery_base64.js"></script>
<script type="text/javascript" src="/pos_pax/static/src/js/pax_device.js"></script>
<script type="text/javascript" src="/pos_pax/static/src/js/pos_pax.js"></script>
<script type="text/javascript" src="/pos_pax/static/src/js/PAXPaymentTransactionPopup.js"></script>
<script type="text/javascript" src="/pos_pax/static/src/js/PAXPaymentScreen.js"></script>
<script type="text/javascript" src="/pos_pax/static/src/js/PAXPaymentScreenPaymentLines.js"></script>
<link rel="stylesheet" href="/pos_pax/static/src/css/pos_pax.css" />
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<odoo>
<record id="view_pos_order" model="ir.ui.view">
<field name="name">POS orders</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='payment_ids']//field[@name='amount']" position="before">
<field name="pax_card_number"/>
<field name="pax_txn_id"/>
</xpath>
</field>
</record>
</odoo>