-
Notifications
You must be signed in to change notification settings - Fork 0
/
monitor.js
386 lines (316 loc) · 14.9 KB
/
monitor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
const schedule = require('node-schedule');
const FirestoreService = require('./firestore-service')
const moment = require('moment');
const ethers = require('ethers');
const EVERY_THREE_SECONDS = "*/3 * * * * *";
const TRANSACTION_STATUSES = require('./constants.json').TRANSACTION_STATUSES;
const PAYMENT_STATUSES = require('./constants.json').PAYMENT_STATUSES;
const PAYMENT_TYPES = require('./constants.json').PAYMENT_TYPES;
class Monitor {
constructor(limepay) {
this.limepay = limepay;
}
/**
* @name monitor
* @summary Pings the LimePay api every 3 seconds for a payment and updates his status
* @description Uses node-scheduler to schedule jobs that execute every 3 seconds until a payment goes into `Successful` or `Failed` state
* @param {string} paymentId LimePay payment ID
*/
monitor(paymentId) {
console.log(`Started monitoring payment with id: ${paymentId}`);
// The function is being executed every 3 seconds until `successful` or `failed` status is retrieved
const scheduledJob = schedule.scheduleJob(paymentId, EVERY_THREE_SECONDS, async () => {
try {
// GET the payment from LimePay
const retrievedPayment = await this.limepay.payments.get(paymentId);
// Get the Payment from DB
const paymentInDB = await FirestoreService.getPayment(paymentId);
// Get the Job from DB
let job = await FirestoreService.getJob(paymentInDB.jobId);
if (isJobCreation(paymentInDB.type)) {
await updateJobCreationPayment(job, paymentInDB, retrievedPayment);
} else if (isJobCompletion(paymentInDB.type)) {
await updateJobCompletionPayment(job, paymentInDB, retrievedPayment);
} else {
throw 'Invalid payment type';
}
// Stop the monitoring of the status is Successful or Failed
if (isTerminalStatus(retrievedPayment.status)) {
// Stop the monitoring of the payment
console.log(`Stopping monitoring of payment with ID ${paymentId}, for Job with ID ${paymentInDB.jobId}. Status is ${retrievedPayment.status}`);
scheduledJob.cancel();
}
} catch (error) {
console.log(`An error occured while monitoring payment with ID ${paymentId}. Error: ${error}`);
}
});
}
}
module.exports = Monitor;
/**
* @name isJobCreation
* @param {String} type
* @description Returns true if the type of the payment is JOB_CREATION
*/
const isJobCreation = function (type) {
return type === PAYMENT_TYPES.JOB_CREATION;
}
/**
* @name isJobCompletion
* @param {String} type
* @description Returns true if the type of the payment is JOB_CREATION
*/
const isJobCompletion = function (type) {
return type === PAYMENT_TYPES.JOB_COMPLETION;
}
/**
* @name updateJobCreationPayment
* @param {Object} job
* @param {Object} paymentInDB
* @param {object} retrievedPayment
* @summary Updates the state of the job and Create Job payment in the DB depending on the progress
*/
const updateJobCreationPayment = async function (job, payment, retrievedPayment) {
const approveTx = retrievedPayment.genericTransactions[0];
const createJobTx = retrievedPayment.genericTransactions[1];
// 1. Update Approve Transction Data
updateApproveTransactionData(approveTx, payment, job);
// 2. Update CreateJob transaction Data
updateCreateJobTxData(createJobTx, payment, job);
// 3. Update Payment State
payment.status = retrievedPayment.status;
await FirestoreService.setPayment(payment);
// 4. Update Job State on success
if (payment.status == PAYMENT_STATUSES.SUCCESSFUL) {
job.state = 'Funds In Escrow';
}
await FirestoreService.setJob(job);
}
/**
* @name updateJobCompletionPayment
* @param {Object} job
* @param {Object} paymentInDB
* @summary Updates the state of the job and Complete Job payment in the DB depending on the progress
*/
const updateJobCompletionPayment = async function (job, payment, retrievedPayment) {
const completeJobTX = retrievedPayment.genericTransactions[0];
if (isTransactionBroadcasted(completeJobTX)) {
appendCompleteJobTX(payment, completeJobTX);
}
else if (isTransactionMined(completeJobTX)) {
// Safety check. If we missed an update and did not added the approve transaction.
safetyCheckCompleteTx(payment, completeJobTX);
// Update transaction success to true
updateTransactionByHash(payment, completeJobTX.transactionHash, true, false);
// // Adds new Job Action when Approve TX is mined
if (shouldAddCompleteJobActionLog(job, completeJobTX)) {
appendCompleteJobActionLog(job);
console.log(`Complete Job TX for payment [${payment.id}] was mined. TX hash: ${completeJobTX.transactionHash}`);
}
}
else if (isTransactionReverted(completeJobTX)) {
// Safety check. If we missed an update and did not added the approve transaction.
safetyCheckCompleteTx(payment, completeJobTX);
// Update transaction failure to true
updateTransactionByHash(payment, completeJobTX.transactionHash, false, true);
console.log(`Complete Job TX for payment [${payment.id}] has reverted. TX hash: ${completeJobTX.transactionHash}`);
}
// 3. Update Payment State
payment.status = retrievedPayment.status;
await FirestoreService.setPayment(payment);
// 4. Update Job State on success
if (payment.status == PAYMENT_STATUSES.SUCCESSFUL) {
job.state = 'Complete';
}
await FirestoreService.setJob(job);
}
// ------------- Main Functions -----------------
function updateCreateJobTxData(createJobTx, payment, job) {
if (isTransactionBroadcasted(createJobTx)) {
appendCreateJobTx(payment, createJobTx);
}
else if (isTransactionMined(createJobTx)) {
// Safety check. If we missed an update and did not added the createJob transaction.
safetyCheckCreateJobTx(payment, createJobTx);
//Update transaction success to true
updateTransactionByHash(payment, createJobTx.transactionHash, true, false);
// Add new Job Action when CreateJob TX is mined
if (shouldAddSendCANActionLog(job, createJobTx)) {
appendSendCANActionLog(job);
console.log(`Create Job TX for payment [${payment.id}] was mined. TX hash: ${createJobTx.transactionHash}`);
}
}
else if (isTransactionReverted(createJobTx)) {
// Safety check. If we missed an update and did not added the createJob transaction.
safetyCheckCreateJobTx(payment, createJobTx);
//Update transaction failure to true
updateTransactionByHash(payment, createJobTx.transactionHash, false, true);
console.log(`Create Job TX for payment [${payment.id}] rhas everted. TX hash: ${createJobTx.transactionHash}`);
}
}
function updateApproveTransactionData(approveTx, payment, job) {
if (isTransactionBroadcasted(approveTx)) {
appendApproveTransaction(payment, approveTx);
}
else if (isTransactionMined(approveTx)) {
// Safety check. If we missed an update and did not added the approve transaction.
safetyCheckApproveTx(payment, approveTx);
// Update transaction success to true
updateTransactionByHash(payment, approveTx.transactionHash, true, false);
// Adds new Job Action when Approve TX is mined
if (shouldAddEscrowActionLog(job, approveTx)) {
appendAuthoriseEscrowActionLog(job, approveTx);
console.log(`Approve TX for payment [${payment.id}] mined. TX hash: ${approveTx.transactionHash}`);
}
}
else if (isTransactionReverted(approveTx)) {
// Safety check. If we missed an update and did not added the approve transaction.
safetyCheckApproveTx(payment, approveTx);
// Update transaction failure to true
updateTransactionByHash(payment, approveTX.transactionHash, false, true);
console.log(`Approve TX for payment [${payment.id}] reverted. TX hash: ${approveTx.transactionHash}`);
}
}
// ------------- Main Functions -----------------
// ------------- Transaction Functions ----------
// Safety check. If we missed an update and did not added the approve transaction.
function safetyCheckApproveTx(payment, approveTx) {
if (payment.transactions == undefined || !findTransactionByHash(payment, approveTx.transactionHash)) {
appendApproveTransaction(payment, approveTx);
}
}
// Safety check. If we missed an update and did not added the create job transaction.
function safetyCheckCreateJobTx(payment, createJobTx) {
if (!findTransactionByHash(payment, createJobTx.transactionHash)) {
appendCreateJobTx(payment, createJobTx);
}
}
// Safety check. If we missed an update and did not added the complete job transaction.
function safetyCheckCompleteTx(payment, completeJobTX) {
if (payment.transactions == undefined || !findTransactionByHash(payment, completeJobTX.transactionHash)) {
appendCompleteJobTX(payment, completeJobTX);
}
}
// Adds the Approve transaction info into limepay-payment entry
function appendApproveTransaction(payment, approveTransaction) {
if (payment.transactions) {
const approveTX = findTransactionByHash(payment, approveTransaction.transactionHash);
// Transaction is already added in transactions array. Will skip adding it again
if (approveTX) return;
} else {
payment.transactions = [];
const transaction = createNewTransaction(approveTransaction.transactionHash, 'Authorise escrow');
payment.transactions.push(transaction);
console.log(`Approve TX for payment [${payment.id}] broadcasted. TX hash: ${approveTransaction.transactionHash}`);
}
}
// Adds the CreateJob transaction into limepay-payment entry
function appendCreateJobTx(payment, createJobTransactionFromLP) {
const createJobTx = findTransactionByHash(payment, createJobTransactionFromLP.transactionHash);
// Transaction is already added in transactions array. Will skip adding it again
if (createJobTx) return;
const transaction = createNewTransaction(createJobTransactionFromLP.transactionHash, 'Send CAN to escrow');
payment.transactions.push(transaction);
console.log(`Create Job TX for payment [${payment.id}] was broadcasted. TX hash: ${createJobTransactionFromLP.transactionHash}`);
}
// Adds the Complete Job transaciton info into limepay-payment entry
function appendCompleteJobTX(payment, completeJobTX) {
if (payment.transactions) {
const completeJob = findTransactionByHash(payment, completeJobTX.transactionHash);
// Transaction is already added in transactions array. Will skip adding it again
if (completeJob) return;
} else {
payment.transactions = [];
const transaction = createNewTransaction(completeJobTX.transactionHash, 'Complete job');
payment.transactions.push(transaction);
console.log(`Complete Job TX for payment [${payment.id}] was broadcasted. TX hash: ${completeJobTX.transactionHash}`);
}
}
// Finds a given transaciton by its transaction hash
function findTransactionByHash(payment, transactionHash) {
return payment.transactions.find(tx => { return tx.hash === transactionHash; });
}
function updateTransactionByHash(payment, transactionHash, success, failure) {
const tx = findTransactionByHash(payment, transactionHash);
tx.success = success;
tx.failure = failure;
}
function isTerminalStatus(status) {
return status === PAYMENT_STATUSES.SUCCESSFUL || status === PAYMENT_STATUSES.FAILED;
}
// Create new transaction object
function createNewTransaction(transactionHash, actionType) {
return {
hash: transactionHash,
actionType: actionType,
failure: false,
success: false,
timestamp: moment().format('x')
};
}
// ------------- Transaction Functions ----------
// -------------- Job Actions -------------------
const appendAuthoriseEscrowActionLog = function (job, transaction) {
let action = createNewAction('Authorise escrow', true);
action.amountCan = convertWithDecimals(transaction.functionParams[1].value);
console.log("actionLog:", action);
job.actionLog.push(action);
}
const appendSendCANActionLog = function (job) {
const action = createNewAction('Send CAN to escrow', false);
console.log('ActionLog:', action);
job.actionLog.push(action);
}
const appendCompleteJobActionLog = function (job) {
let action = createNewAction('Complete job', false);
console.log("actionLog:", action);
job.actionLog.push(action);
}
const createNewAction = function (type, private, executedBy = 'User') {
const action = {
type,
timestamp: moment().format('x'),
message: '',
private,
executedBy
}
return action;
}
// Updates the Job action log when the approve transaction is mined
function shouldAddEscrowActionLog(job, transaction) {
const lastActionLog = job.actionLog[job.actionLog.length - 1];
// Check whether we already added the escrow action log
return lastActionLog.type == 'Accept terms' && transaction.status === PAYMENT_STATUSES.SUCCESSFUL;
}
// Updates the Job action log when the approve transaction is mined
function shouldAddSendCANActionLog(job, transaction) {
const lastActionLog = job.actionLog[job.actionLog.length - 1];
// Check whether we already added the escrow action log
return lastActionLog.type == 'Authorise escrow' && transaction.status === PAYMENT_STATUSES.SUCCESSFUL;
}
// Updates the Job action log when the approve transaction is mined
function shouldAddCompleteJobActionLog(job, transaction) {
const lastActionLog = job.actionLog[job.actionLog.length - 1];
// Check whether we already added the escrow action log
return lastActionLog.type != 'Complete job' && transaction.status === PAYMENT_STATUSES.SUCCESSFUL;
}
// -------------- Job Actions --------------
// -------------- UTILS --------------------
// Returns true if the transaction is broadcasted
function isTransactionBroadcasted(transaction) {
return transaction.status === TRANSACTION_STATUSES.PROCESSING && transaction.transactionHash;
}
// Returns true if the transaction is mined
function isTransactionMined(transaction) {
return transaction.status === TRANSACTION_STATUSES.SUCCESSFUL && transaction.transactionHash;
}
// Returns true if the transaction has reverted
function isTransactionReverted(transaction) {
return transaction.status === TRANSACTION_STATUSES.FAILED && transaction.transactionHash;
}
// Converts the Amount of CAN tokens to their base. CAN token has 6 decimals.
const convertWithDecimals = function (amount) {
const amountInBN = ethers.utils.bigNumberify(amount);
return ethers.utils.formatUnits(amountInBN, 6).toString();
}
// -------------- UTILS --------------------