diff --git a/fn_pay.js b/fn_pay.js new file mode 100644 index 0000000..c331b25 --- /dev/null +++ b/fn_pay.js @@ -0,0 +1,225 @@ +const TYPE_MONEY_REQUEST = "MoneyRequests", + TYPE_CASHIER_REQUEST = "CashierRequests"; + +const cashierUPI = {}; + +//https://ranchimallflo.duckdns.org/api/v1.0/getFloAddressTransactions?token=rupee&floAddress=FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf +//For regular users +const User = {}; +const cashierStatus = {}; + +User.init = function() { + return new Promise((resolve, reject) => { + let promises; + //Request cashier for token-cash exchange + promises = floGlobals.subAdmins.map(cashierID => floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, { + senderID: myFloID, + receiverID: cashierID, + group: "Cashiers", + callback: userUI.renderCashierRequests //UI_fn + })); + //Request received from other Users for token + promises.push(floCloudAPI.requestGeneralData(TYPE_MONEY_REQUEST, { + receiverID: myFloID, + callback: userUI.renderMoneyRequests //UI_fn + })); + //Check online status of cashiers + promises.push(floCloudAPI.requestStatus(Array.from(floGlobals.subAdmins), { + callback: (d, e) => { + if (e) return console.error(e); + for (let i in d) + cashierStatus[i] = d[i]; + //Add any UI_fn if any + } + })) + /* + promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed? + callback: UI_RENDER_FN + })); + */ + Promise.all(promises) + .then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +Object.defineProperty(Cashier, 'cashierRequests', { + get: function() { + let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, { + senderID: myFloID, + receiverID: cashierID, + group: "Cashiers", + }); + return floGlobals.generalData[fk]; + } +}); + +Object.defineProperty(Cashier, 'moneyRequests', { + get: function() { + let fk = floCloudAPI.util.filterKey(TYPE_MONEY_REQUEST, { + receiverID: myFloID, + }); + return floGlobals.generalData[fk]; + } +}); + +User.findCashier = function() { + let online = []; + for (let c in cashierStatus) + if (cashierStatus[c]) + online.push(c); + if (!online.length) + return null; + else + return online[floCrypto.randInt(0, online.length)]; +} + +User.cashToToken = function(cashier, amount, upiTxID) { + return new Promise((resolve, reject) => { + if (!floCloudAPI.subAdmins.includes(cashier)) + return reject("Invalid cashier"); + floCloudAPI.sendGeneralData({ + mode: "cash-to-token", + amount: amount, + upi_txid: upiTxID + }, TYPE_CASHIER_REQUEST, { + receiverID: cashier + }).then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +User.tokenToCash = function(cashier, amount, blkTxID, upiID) { + return new Promise((resolve, reject) => { + if (!floCloudAPI.subAdmins.includes(cashier)) + return reject("Invalid cashier"); + floCloudAPI.sendGeneralData({ + mode: "token-to-cash", + amount: amount, + token_txid: blkTxID, + upi_id: upiID + }, TYPE_CASHIER_REQUEST, { + receiverID: cashier + }).then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +User.sendToken = function(receiverID, amount, remark = '') { + return new Promise((resolve, reject) => { + tokenAPI.sendToken(myPrivKey, amount, receiverID, remark) + .then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +User.requestToken = function(floID, amount, remark = '') { + return new Promise((resolve, reject) => { + floCloudAPI.sendGeneralData({ + amount: amount, + remark: remark + }, TYPE_MONEY_REQUEST, { + receiverID: floID + }).then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +User.decideRequest = function(request, note) { + return new Promise((resolve, reject) => { + floCloudAPI.noteApplicationData(request.vectorClock, note, { + receiverID: myFloID + }).then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +const Cashier = {}; + +Cashier.init = function() { + return new Promise((resolve, reject) => { + let promises; + //Requests from user to cashier(self) for token-cash exchange + promises.push(floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, { + receiverID: myFloID, + callback: cashierUI.renderRequests //UI_fn + })); + //Set online status of cashier(self) + promises.push(floCloudAPI.setStatus()); + /* + promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed? + callback: UI_RENDER_FN + })); + */ + Promise.all(promises) + .then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +Object.defineProperty(Cashier, 'Requests', { + get: function() { + let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, { + receiver: myFloID + }); + return floGlobals.generalData[fk]; + } +}); + +Cashier.finishRequest = function(request, txid) { + return new Promise((resolve, reject) => { + floCloudAPI.tagApplicationData(request.vectorClock, 'COMPLETED', { + receiverID: myFloID + }).then(result => { + floCloudAPI.noteApplicationData(request.vectorClock, txid, { + receiverID: myFloID + }).then(result => resolve(result)) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }) +} + +Cashier.rejectRequest = function(request, reason) { + return new Promise((resolve, reject) => { + floCloudAPI.noteApplicationData(request.vectorClock, "REJECTED:" + reason, { + receiverID: myFloID + }).then(result => resolve(result)) + .catch(error => reject(error)) + }) +} + +Cashier.checkIfUpiTxIsValid = function(upiTxID) { + return new Promise((resolve, reject) => { + let requests = Cashier.Requests; + for (let r in requests) + if (requests[r].message.mode === "cash-to-token") + if (requests[r].note === upiTxID) + return reject([true, "UPI transaction is already used for another request"]); + return resolve(true); + }) +} + +Cashier.checkIfTokenTxIsValid = function(tokenTxID, sender, amount) { + return new Promise((resolve, reject) => { + let requests = Cashier.Requests; + for (let r in requests) + if (requests[r].message.mode === "token-to-cash") + if (requests[r].note === tokenTxID) + return reject([true, "Token transaction is already used for another request"]); + tokenAPI.getTx(tokenTxID).then(tx => { + let parsedTxData = tokenAPI.util.parseTxData(tx); + console.debug(parsedTxData); + if (parsedTxData.type !== "transfer" || parsedTxData.transferType !== "token") + reject([true, "Invalid token transfer type"]); + else if (parsedTxData.tokenAmount !== amount) + reject([true, "Incorrect token amount: " + parsedTxData.tokenAmount]); + else if (parsedTxData.tokenIdentification !== floGlobals.currency) + reject([true, "Incorrect token: " + parsedTxData.tokenIdentification]); + else if (parsedTxData.sender !== sender) + reject([true, "Incorrect senderID: " + parsedTxData.sender]); + else if (parsedTxData.receiver !== myFloID) + reject([true, "Incorrect receiverID: " + parsedTxData.receive]) + else resolve(true); + }).catch(error => reject([null, error])) + }) +} \ No newline at end of file diff --git a/fn_ui.js b/fn_ui.js new file mode 100644 index 0000000..90238fb --- /dev/null +++ b/fn_ui.js @@ -0,0 +1,218 @@ +const userUI = {}; + +userUI.requestTokenFromCashier = function() { + let cashier = User.findCashier(); + if (!cashier) + return alert("No cashier online"); + let amount = parseFloat(document.forms['request-cashier'][amount]); + //get UPI txid from user + let upiTxID = prompt(`Send Rs. ${amount} to ${cashierUPI[cashier]} and enter UPI txid`); + if (!upiTxID) + return alert("Cancelled"); + User.cashToToken(cashier, amount, upiTxID).then(result => { + console.log(result); + alert("Requested cashier. please wait!"); + }).catch(error => console.error(error)) +} + +userUI.withdrawCashFromCashier = function() { + let cashier = User.findCashier(); + if (!cashier) + return alert("No cashier online"); + let amount = parseFloat(document.forms['request-cashier'][amount]); + //get confirmation from user + let upiID = prompt(`${amount} ${floGlobals.currency}# will be sent to ${cashier}. Enter UPI ID`); + if (!upiID) + return alert("Cancelled"); + User.sendToken(cashier, amount, 'for token-to-cash').then(txid => { + console.log("txid", txid); + User.tokenToCash(cashier, amount, txid, upiID).then(result => { + console.log(result); + alert("Requested cashier. please wait!"); + }).catch(error => console.error(error)) + }).catch(error => console.error(error)) +} + +userUI.sendMoneyToUser = function() { + let form = document.forms['user-money']; + let floID = form['flo-id'], + amount = parseFloat(form['amount']), + remark = form['remark']; + let confirmation = prompt(`Do you want to SEND ${amount} to ${floID}?`); + if (!confirmation) + return alert("Cancelled"); + User.sendToken(floID, amount, remark).then(txid => { + console.info(`Sent ${amount} to ${floID}`, txid); + alert(`Sent ${amount} to ${floID}. It may take a few mins to reflect in their wallet`); + }).catch(error => console.error(error)); +} + +userUI.requestMoneyFromUser = function() { + let form = document.forms['user-money']; + let floID = form['flo-id'], + amount = parseFloat(form['amount']), + remark = form['remark']; + let confirmation = prompt(`Do you want to REQUEST ${amount} from ${floID}?`); + if (!confirmation) + return alert("Cancelled"); + User.requestToken(floID, amount, remark).then(result => { + console.log(`Requested ${amount} from ${floID}`, result); + alert(`Requested ${amount} from ${floID}`); + }).catch(error => console.error(error)); +} + +userUI.renderCashierRequests = function(requests, error = null) { + if (error) + return console.error(error); + else if (typeof requests !== "object" || request === null) + return; + let table = document.getElementById('user-cashier-requests').getElementsByTagName('tbody')[0]; + for (let r in requests) { + let oldCard = document.getElementById(r); + if (oldCard) oldCard.remove(); + let row = table.insertRow(); + renderUser_cashierRequestCard(request[r], row); + } +} + +function renderUser_cashierRequestCard(request, row) { + row.id = request.vectorClock; + row.insertCell().textContent = request.time; + row.insertCell().textContent = request.receiverID; + row.insertCell().textContent = request.message.mode; + let status = request.tag ? (status = request.tag + ":" + request.note) : (request.note || "PENDING"); + row.insertCell().textContent = status; //Status +} + +userUI.renderMoneyRequests = function(requests, error = null) { + if (error) + return console.error(error); + else if (typeof requests !== "object" || request === null) + return; + let table = document.getElementById('user-money-requests').getElementsByTagName('tbody')[0]; + for (let r in requests) { + let oldCard = document.getElementById(r); + if (oldCard) oldCard.remove(); + let row = table.insertRow(); + renderUser_moneyRequestCard(request[r], row); + } +} + +function renderUser_moneyRequestCard(request, row) { + row.id = request.vectorClock; + row.insertCell().textContent = request.time; + row.insertCell().textContent = request.senderID; + row.insertCell().textContent = request.message.amount; + row.insertCell().textContent = request.message.remark; + let status = request.note; + if (status) + row.insertCell().textContent = request.note; + else + row.insertCell().innerHTML = + `` + + ``; +} + +userUI.payRequest = function(reqID) { + let request = User.moneyRequests[reqID]; + let confirmation = prompt(`Do you want to SEND ${request.message. amount} to ${request.senderID}?`); + if (!confirmation) + return alert("Cancelled"); + User.sendToken(request.senderID, request.message.amount, request.message.remark).then(txid => { + console.info(`Sent ${request.message.amount} to ${request.senderID}`, txid); + alert(`Sent ${request.message.amount} to ${request.senderID}. It may take a few mins to reflect in their wallet`); + User.decideRequest(request, 'PAID: ' + txid) + .then(result => console.log(result)) + .catch(error => console.error(error)) + }).catch(error => console.error(error)); +} + +userUI.declineRequest = function(reqID) { + let request = User.moneyRequests[reqID]; + User.decideRequest(request, "DECLINED").then(result => { + console.log(result); + alert("Declined request"); + }).catch(error => console.error(error)) +} + +//Cashier +const cashierUI = {}; + +cashierUI.renderRequests = function(requests, error = null) { + if (error) + return console.error(error); + else if (typeof requests !== "object" || request === null) + return; + let table = document.getElementById('cashier-request-list').getElementsByTagName('tbody')[0]; + for (let r in requests) { + let oldCard = document.getElementById(r); + if (oldCard) oldCard.remove(); + let row = table.insertRow(); + renderRequestCard(request[r], row); + } +} + +function renderCashier_requestCard(request, row) { + row.id = request.vectorClock; + row.insertCell().textContent = request.senderID; + row.insertCell().textContent = request.time; + row.insertCell().textContent = request.message.mode; + let status = request.tag || request.note; //status tag for completed, note for rejected + if (status) + row.insertCell().textContent = status; + else + row.insertCell().innerHTML = `` +} + +cashierUI.completeRequest = function(reqID) { + let request = Cashier.Requests[reqID]; + if (request.message.mode === "cash-to-token") + completeCashToTokenRequest(request); + else if (request.message.mode === "token-to-cash") + completeTokenToCashRequest(request); +} + +function completeCashToTokenRequest(request) { + Cashier.checkIfUpiTxIsValid(request.message.upiTxID).then(_ => { + let confirmation = prompt(`Check if you have received UPI transfer\ntxid:${request.message.upi_txid}\namount:${request.message.amount}`); + if (!confirmation) + return alert("Cancelled"); + User.sendToken(request.senderID, request.message.amount, 'for cash-to-token').then(txid => { + console.log("txid", txid); + Cashier.finishRequest(request, txid).then(result => { + console.log(result); + console.info('Completed cash-to-token request:', request.vectorClock); + alert("Completed request"); + }).catch(error => console.error(error)) + }).catch(error => console.error(error)) + }).catch(error => { + console.error(error); + alert(error); + if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string') + Cashier.rejectRequest(request, error[1]).then(result => { + console.log(result); + console.info('Rejected cash-to-token request:', request.vectorClock); + }).catch(error => console.error(error)) + }) +} + +function completeTokenToCashRequest(request) { + Cashier.checkIfTokenTxIsValid(request.message.token_txid, request.senderID, request.message.amount).then(result => { + let upiTxID = prompt(`Token transfer is verified!\n Send ${request.message.amount} to ${request.message.upi_id} and Enter UPI txid`); + if (!upiTxID) + return alert("Cancelled"); + Cashier.finishRequest(request, upiTxID).then(result => { + console.log(result); + console.info('Completed token-to-cash request:', request.vectorClock); + alert("Completed request"); + }).catch(error => console.error(error)) + }).catch(error => { + console.error(error); + alert(error); + if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string') + Cashier.rejectRequest(request, error[1]).then(result => { + console.log(result); + console.info('Rejected token-to-cash request:', request.vectorClock); + }).catch(error => console.error(error)) + }) +} \ No newline at end of file diff --git a/new.html b/new.html new file mode 100644 index 0000000..62bc740 --- /dev/null +++ b/new.html @@ -0,0 +1,133 @@ + + + + + RanchiMall Pay + + + + + + + + + + + + + + + \ No newline at end of file