bitstamped.getTicker(paidTime, function(err, docs) { if (!err && docs.rows && docs.rows.length > 0) { var tickerData = docs.rows[0].value; var rate = new BigNumber(tickerData.vwap); var totalPaid = new BigNumber(invoiceHelper.getTotalPaid(invoice, paymentsArr)); var remainingBalance = new BigNumber(invoice.balance_due).minus(totalPaid); var isUSD = invoice.currency.toUpperCase() === 'USD'; if (isUSD) { var actualPaid = helper.roundToDecimal(rate.times(transaction.amount).valueOf(), 2); var closeEnough = new BigNumber(actualPaid).equals(helper.roundToDecimal(remainingBalance, 2)); if (closeEnough) { remainingBalance = transaction.amount; } else { remainingBalance = Number(remainingBalance.dividedBy(rate).valueOf()); } } remainingBalance = helper.roundToDecimal(remainingBalance, 8); var payment = { _id: invoiceId + '_' + transaction.txid, invoice_id: invoiceId, address: transaction.address, amount_paid: Number(transaction.amount), expected_amount: Number(remainingBalance), block_hash: transaction.blockhash ? transaction.blockhash : null, spot_rate: Number(rate.valueOf()), // Exchange rate at time of payment created: new Date().getTime(), paid_timestamp: paidTime, txid: transaction.txid, // Bitcoind txid for transaction watched: true, type: 'payment' }; payment.status = helper.getPaymentStatus(payment, transaction.confirmations, invoice); // New transaction to known address has wallet conflicts. This indicates that // this transaction is a mutated tx of a known payment. if (transaction.walletconflicts.length > 0) { payment.double_spent_history = transaction.walletconflicts; var latestConflictingTx = transaction.walletconflicts[transaction.walletconflicts.length - 1]; // Need to grab spot rate and expected_amount from conflicting payment paymentsArr.forEach(function(curPayment) { if (curPayment.txid === latestConflictingTx) { payment.expected_amount = curPayment.expected_amount; payment.spot_rate = curPayment.spot_rate; } }); } db.insert(payment, function(err) { if (err && err.error === 'conflict' ) { console.log('createNewPaymentWithTransaction: Document update conflict: ' + require('util').inspect(err.request.body)); return cb(); } }); } else { return cb(err); } });
db.findInvoiceAndPayments(invoiceId, function(err, invoice, paymentsArr) { if (err) { return cb(err, null); } var origInvoice = _.cloneDeep(invoice); var paymentHistory = invoicesLib.getPaymentHistory(paymentsArr); invoice.payment_history = paymentHistory; var isUSD = invoice.currency.toUpperCase() === 'USD'; invoicesLib.calculateLineTotals(invoice); invoicesLib.calculateDiscountTotals(invoice); if (!invoice.discounts) { invoice.discounts = []; } invoice.amount_paid = invoicesLib.getTotalPaid(invoice, paymentsArr); invoice.balance_due = invoicesLib.getAmountDue(invoice.invoice_total, invoice.amount_paid, invoice.currency); invoice.is_expired = false; invoice.is_void = invoice.is_void ? invoice.is_void : false; invoice.expiration = invoice.expiration ? invoice.expiration : null; invoice.text = invoice.text ? invoice.text : null; var invoiceExpired = validate.invoiceExpired(invoice); invoice.is_expired = invoiceExpired && invoice.balance_due > 0; invoice.expiration_time = null; if (invoice.expiration && !invoiceExpired && invoice.balance_due > 0) { invoice.expiration_time = helper.getExpirationCountDown(invoice.expiration); } // Is the invoice paid in full? var hasPending = false; paymentHistory.forEach(function(payment) { if(isUSD) { var amountUSD = new BigNumber(payment.amount_paid).times(payment.spot_rate); amountUSD = helper.roundToDecimal(amountUSD, 2); payment.amount_usd = amountUSD; } payment.url = config.publicURL + '/redirect/txid/' + payment.txid; // populate chain explorer url hasPending = payment.status.toLowerCase() === 'pending'; delete payment._id; delete payment._rev; delete payment.spot_rate; }); invoice.payment_history = _.sortBy(paymentHistory, function(payment) { return payment.created; }); invoice.is_paid = !hasPending && invoice.balance_due <= 0; invoice.is_overpaid = !hasPending && invoice.balance_due < 0; delete invoice.webhooks; delete invoice.metadata; delete invoice._rev; invoice.demo_mode = config.demoMode || false; return cb(null, invoice, origInvoice); });
it('should calculate the total amount paid', function() { var curTime = new Date().getTime(); var paymentA = { status: 'partial', amount_paid: 0.4, created: (curTime - 10000) }; var paymentB = { status: 'invalid', amount_paid: 1.5535, created: (curTime - 5000) }; var paymentC = { status: 'paid', amount_paid: 0.2433, created: curTime }; var paymentD = { status: 'overpaid', amount_paid: 5.123456789, spot_rate: 400, created: (curTime + 10000) }; var invoice = { currency: 'BTC' }; var payments = [ paymentA, paymentB, paymentC, paymentD ]; assert.equal('5.76675679', invoiceHelper.getTotalPaid(invoice, payments).toString()); paymentA = { status: 'partial', amount_paid: 0.4, spot_rate: 421, created: (curTime - 10000) }; paymentB = { status: 'invalid', amount_paid: 1.5535, spot_rate: 411.23, created: (curTime - 5000) }; paymentC = { status: 'paid', amount_paid: 0.2433, spot_rate: 415.26, created: curTime }; paymentD = { status: 'overpaid', amount_paid: 5.123456789, spot_rate: 456.43, created: (curTime + 10000) }; invoice = { currency: 'USD' }; payments = [ paymentA, paymentB, paymentC, paymentD ]; assert.equal('2607.93', invoiceHelper.getTotalPaid(invoice, payments).toString()); });
db.findInvoiceAndPayments(invoiceId, function(err, invoice, paymentsArr) { if (err) { return cb(err, null); } var paymentHistory = invoiceHelper.getPaymentHistory(paymentsArr); invoice.payment_history = paymentHistory; var isUSD = invoice.currency.toUpperCase() === 'USD'; invoiceHelper.calculateLineTotals(invoice); invoice.total_paid = invoiceHelper.getTotalPaid(invoice, paymentsArr); invoice.balance_due = isUSD ? helper.roundToDecimal(invoice.balance_due, 2) : Number(invoice.balance_due); invoice.remaining_balance = invoiceHelper.getAmountDue(invoice.balance_due, invoice.total_paid, invoice.currency); var invoiceExpired = validate.invoiceExpired(invoice); if (invoiceExpired && invoice.remaining_balance > 0) { var expiredErr = new Error('Error: Invoice is expired.'); return cb(expiredErr, null); } else if (invoice.expiration && !invoiceExpired && invoice.remaining_balance > 0) { invoice.expiration_msg = 'Expires: ' + helper.getExpirationCountDown(invoice.expiration); } // Is the invoice paid in full? var hasPending = false; paymentHistory.forEach(function(payment) { payment.url = config.chainExplorerUrl + '/' + payment.tx_id; // populate chain explorer url if(isUSD) { var amountUSD = new BigNumber(payment.amount_paid).times(payment.spot_rate); amountUSD = helper.roundToDecimal(amountUSD, 2); payment.amount_usd = amountUSD; } if (payment.status.toLowerCase() === 'pending') { hasPending = true; } }); invoice.is_paid = !hasPending && invoice.remaining_balance <= 0; invoice.is_overpaid = !hasPending && invoice.remaining_balance < 0; return cb(null, invoice); });