diff --git a/lib/emailservice.js b/lib/emailservice.js index ac4ef26..9d10c68 100644 --- a/lib/emailservice.js +++ b/lib/emailservice.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var $ = require('preconditions').singleton(); var async = require('async'); +var Mustache = require('mustache'); var log = require('npmlog'); log.debug = log.verbose; var fs = require('fs'); @@ -98,8 +99,8 @@ EmailService.prototype._readTemplate = function(filename, cb) { } var lines = template.split('\n'); return cb(null, { - subject: _.template(lines[0]), - body: _.template(_.rest(lines).join('\n')), + subject: lines[0], + body: _.rest(lines).join('\n'), }); }); }; @@ -107,7 +108,7 @@ EmailService.prototype._readTemplate = function(filename, cb) { EmailService.prototype._applyTemplate = function(template, data, cb) { var result = _.mapValues(template, function(t) { try { - return t(data); + return Mustache.render(t, data); } catch (e) { log.error('Could not apply data to template', e); return cb(e); @@ -154,8 +155,17 @@ EmailService.prototype._getDataForTemplate = function(notification, cb) { id: notification.creatorId }); if (copayer) { - data.creatorId = copayer.id; - data.creatorName = copayer.name; + data.copayerId = copayer.id; + data.copayerName = copayer.name; + } + + if (notification.type == 'TxProposalFinallyRejected' && data.rejectedBy) { + var rejectors = _.map(data.rejectedBy, function(copayerId) { + return _.find(wallet.copayers, { + id: copayerId + }).name + }); + data.rejectorsNames = rejectors.join(', '); } return cb(null, data); }); diff --git a/lib/server.js b/lib/server.js index bfa38a2..a0a3a0d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1072,8 +1072,13 @@ WalletService.prototype.rejectTx = function(opts, cb) { }, function(next) { if (txp.status == 'rejected') { + var rejectedBy = _.pluck(_.filter(txp.actions, { + type: 'reject' + }), 'copayerId'); + self._notify('TxProposalFinallyRejected', { txProposalId: opts.txProposalId, + rejectedBy: rejectedBy, }, next); } else { next(); diff --git a/lib/templates/new_copayer.plain b/lib/templates/new_copayer.plain index 2cc1e53..0a9cc69 100644 --- a/lib/templates/new_copayer.plain +++ b/lib/templates/new_copayer.plain @@ -1,2 +1,2 @@ -<%= subjectPrefix %>New copayer -A new copayer just joined your wallet <%= walletName %>. +{{subjectPrefix}}New copayer +A new copayer just joined your wallet {{walletName}}. diff --git a/lib/templates/new_incoming_tx.plain b/lib/templates/new_incoming_tx.plain index 498b75b..4acc0c1 100644 --- a/lib/templates/new_incoming_tx.plain +++ b/lib/templates/new_incoming_tx.plain @@ -1,2 +1,2 @@ -<%= subjectPrefix %>New payment received -A payment of <%= amount %> has been received into your wallet <%= walletName %>. +{{subjectPrefix}}New payment received +A payment of {{amount}} has been received into your wallet {{walletName}}. diff --git a/lib/templates/new_outgoing_tx.plain b/lib/templates/new_outgoing_tx.plain index 38ece4f..c8f92cb 100644 --- a/lib/templates/new_outgoing_tx.plain +++ b/lib/templates/new_outgoing_tx.plain @@ -1,2 +1,2 @@ -<%= subjectPrefix %>Payment sent -A Payment of <%= amount %> has been sent from your wallet <%= walletName %>. +{{subjectPrefix}}Payment sent +A Payment of {{amount}} has been sent from your wallet {{walletName}}. diff --git a/lib/templates/new_tx_proposal.plain b/lib/templates/new_tx_proposal.plain index e4a9fc9..56f2313 100644 --- a/lib/templates/new_tx_proposal.plain +++ b/lib/templates/new_tx_proposal.plain @@ -1,2 +1,2 @@ -<%= subjectPrefix %>New payment proposal -A new payment proposal has been created in your wallet <%= walletName %> by <%= creatorName %>. +{{subjectPrefix}}New payment proposal +A new payment proposal has been created in your wallet {{walletName}} by {{copayerName}}. diff --git a/lib/templates/txp_finally_rejected.plain b/lib/templates/txp_finally_rejected.plain index 80a3fb1..72db5e9 100644 --- a/lib/templates/txp_finally_rejected.plain +++ b/lib/templates/txp_finally_rejected.plain @@ -1,2 +1,2 @@ -<%= subjectPrefix %>Payment proposal rejected -A payment proposal in your wallet <%= walletName %> has been rejected by <%= creatorName %>. +{{subjectPrefix}}Payment proposal rejected +A payment proposal in your wallet {{walletName}} has been rejected by {{rejectorsNames}}. diff --git a/lib/templates/wallet_complete.plain b/lib/templates/wallet_complete.plain index ba52e3e..e18902a 100644 --- a/lib/templates/wallet_complete.plain +++ b/lib/templates/wallet_complete.plain @@ -1,2 +1,2 @@ -<%= subjectPrefix %>Wallet complete -Your wallet <%= walletName %> is complete. +{{subjectPrefix}}Wallet complete +Your wallet {{walletName}} is complete. diff --git a/package.json b/package.json index 7c09fc0..160d4d1 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "moment": "^2.10.3", "mongodb": "^2.0.27", "morgan": "*", + "mustache": "^2.1.0", "nodemailer": "^1.3.4", "npmlog": "^0.1.1", "preconditions": "^1.0.7", @@ -61,11 +62,14 @@ "test": "./node_modules/.bin/mocha", "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" }, - "contributors": [{ - "name": "Ivan Socolsky", - "email": "ivan@bitpay.com" - }, { - "name": "Matias Alejo Garcia", - "email": "ematiu@gmail.com" - }] + "contributors": [ + { + "name": "Ivan Socolsky", + "email": "ivan@bitpay.com" + }, + { + "name": "Matias Alejo Garcia", + "email": "ematiu@gmail.com" + } + ] } diff --git a/test/integration/server.js b/test/integration/server.js index d8924cc..7fb3695 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -274,7 +274,7 @@ describe('Wallet service', function() { async.eachSeries(w.copayers, function(copayer, next) { helpers.getAuthServer(copayer.id, function(server) { server.savePreferences({ - email: 'copayer' + (i++) + '@domain.com', + email: 'copayer' + (++i) + '@domain.com', }, next); }); }, function(err) { @@ -313,7 +313,7 @@ describe('Wallet service', function() { var emails = _.map(calls, function(c) { return c.args[0]; }); - _.difference(['copayer1@domain.com', 'copayer2@domain.com'], _.pluck(emails, 'to')).should.be.empty; + _.difference(['copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('New payment proposal'); @@ -366,7 +366,7 @@ describe('Wallet service', function() { var emails = _.map(_.takeRight(calls, 3), function(c) { return c.args[0]; }); - _.difference(['copayer0@domain.com', 'copayer1@domain.com', 'copayer2@domain.com'], _.pluck(emails, 'to')).should.be.empty; + _.difference(['copayer1@domain.com', 'copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('Payment sent'); @@ -382,6 +382,52 @@ describe('Wallet service', function() { }); }); + it('should notify copayers a tx has been finally rejected', function(done) { + helpers.stubUtxos(server, wallet, 1, function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); + + var txpId; + async.waterfall([ + + function(next) { + server.createTx(txOpts, next); + }, + function(txp, next) { + txpId = txp.id; + async.eachSeries(_.range(1, 3), function(i, next) { + var copayer = TestData.copayers[i]; + helpers.getAuthServer(copayer.id, function(server) { + server.rejectTx({ + txProposalId: txp.id, + }, next); + }); + }, next); + }, + ], function(err) { + should.not.exist(err); + + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + var emails = _.map(_.takeRight(calls, 2), function(c) { + return c.args[0]; + }); + _.difference(['copayer1@domain.com', 'copayer2@domain.com'], _.pluck(emails, 'to')).should.be.empty; + var one = emails[0]; + one.from.should.equal('bws@dummy.net'); + one.subject.should.contain('Payment proposal rejected'); + one.text.should.contain(wallet.name); + one.text.should.contain('copayer 2, copayer 3'); + one.text.should.not.contain('copayer 1'); + server.storage.fetchUnsentEmails(function(err, unsent) { + should.not.exist(err); + unsent.should.be.empty; + done(); + }); + }, 100); + }); + }); + }); + it('should notify copayers of incoming txs', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); @@ -398,7 +444,7 @@ describe('Wallet service', function() { var emails = _.map(calls, function(c) { return c.args[0]; }); - _.difference(['copayer0@domain.com', 'copayer1@domain.com', 'copayer2@domain.com'], _.pluck(emails, 'to')).should.be.empty; + _.difference(['copayer1@domain.com', 'copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('New payment received');