diff --git a/package.json b/package.json index 20af8aac..eeec2a6a 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "basic-auth": "~1.0.3", "bip39": "~2.3.1", "bitcoinjs-lib": "~2.2.0", - "blockchain-wallet-client": "3.32.4", + "blockchain-wallet-client": "blockchain/my-wallet-v3#new-mdid", "blockchain.info": "~2.2.2", "body-parser": "~1.14.1", "colors": "~1.1.2", diff --git a/src/api.js b/src/api.js index 357b0e75..a4ffc1e2 100644 --- a/src/api.js +++ b/src/api.js @@ -262,6 +262,63 @@ MerchantAPI.prototype.unarchiveAccount = function (guid, options) { }) } +// 📔 Contacts +MerchantAPI.prototype.getWalletContacts = function (guid, options) { + return this.getWalletHD(guid, options).then(function (wallet) { + return wallet.loadContacts() + .then(function () { return wallet.contacts.digestNewMessages() }) + .then(function () { return wallet.contacts }) + }) +} + +MerchantAPI.prototype.createInvitation = function (guid, options) { + return this.getWalletContacts(guid, options).then(function (contacts) { + var customerInfo = { name: options.name } + var businessInfo = { name: options.companyName } + return contacts.createInvitation(businessInfo, customerInfo).then(function (invitation) { + return contacts.save().then(function () { + var contactId = Object.keys(contacts.list) + .find(id => contacts.get(id).invitationSent === invitation.invitationReceived) + return Object.assign({ contactId }, invitation) + }) + }) + }) +} + +MerchantAPI.prototype.listContacts = function (guid, options) { + return this.getWalletContacts(guid, options).then(function (contacts) { + return Object.values(contacts.list).map(formatContact) + }) +} + +MerchantAPI.prototype.getContact = function (guid, options) { + return this.getWalletContacts(guid, options).then(function (contacts) { + var contact = contacts.list[options.contact] + return contact ? formatContact(contact) : Promise.reject('Contact with that id not found') + }) +} + +MerchantAPI.prototype.requestPayment = function (guid, options) { + return this.getWalletContacts(guid, options).then(function (contacts) { + var { id, amount, message } = options + var contact = contacts.list[id] + + if (contact == null) { + return Promise.reject('Contact with that id not found') + } + + var complete = contact.trusted + ? Promise.resolve() + : contacts.completeRelation(id) + + return complete.then(function () { + return contacts.sendPR(id, amount, void 0, message).then(function () { + return Object.values(contacts.list[id].facilitatedTxList) + }) + }) + }) +} + module.exports = new MerchantAPI() // Helper functions @@ -305,6 +362,12 @@ function formatAcct (a) { } } +function formatContact (contact) { + return Object.assign({}, contact, { + facilitatedTxList: Object.values(contact.facilitatedTxList) + }) +} + function add (total, next) { return total + next } diff --git a/src/server.js b/src/server.js index fc6b417b..c119944f 100644 --- a/src/server.js +++ b/src/server.js @@ -21,15 +21,18 @@ var v2API = express() var merchantAPI = express() var legacyAPI = express() var accountsAPI = express() +var contactsAPI = express() // Configuration app.use('/merchant/:guid', merchantAPI) app.use('/api/v2', v2API) merchantAPI.use('/', legacyAPI) merchantAPI.use('/accounts', accountsAPI) +merchantAPI.use('/contacts', contactsAPI) app.param('guid', setParam('guid')) accountsAPI.param('account', setParam('account')) +contactsAPI.param('contact', setParam('contact')) app.use(function (req, res) { res.status(404).json({ error: 'Not found' }) @@ -52,6 +55,25 @@ legacyAPI.use(parseOptions({ unsafe: Boolean })) +contactsAPI.use(parseOptions({ + password: String, + api_code: String, + id: String, + name: String, + companyName: String, + amount: Number, + message: String +})) + +contactsAPI.use(function (req, res, next) { + winston.info('Setting Access-Control headers') + res.header('Access-Control-Allow-Origin', '*') + res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE') + res.header('Access-Control-Allow-Headers', 'Content-Type') + if (req.method === 'OPTIONS') res.status(200).end() + else next() +}) + merchantAPI.all( '/login', required(['password']), @@ -161,6 +183,31 @@ accountsAPI.all( callApi('unarchiveAccount') ) +// Routing: Contacts +contactsAPI.all( + '/', + required(['password']), + callApi('listContacts') +) + +contactsAPI.all( + '/create_invitation', + required(['password', 'name', 'companyName']), + callApi('createInvitation') +) + +contactsAPI.all( + '/request_payment', + required(['password', 'id', 'amount', 'message']), + callApi('requestPayment') +) + +contactsAPI.all( + '/:contact', + required(['password']), + callApi('getContact') +) + // v2 API v2API.use(bodyParser.json()) v2API.use(bodyParser.urlencoded({ extended: true })) @@ -251,7 +298,7 @@ function handleResponse (apiAction, res, errCode) { res.status(errCode || 500).json(addWarning(e)) } else { winston.error(e) - var err = ecodes[e] || ecodes['ERR_UNEXPECT'] + var err = ecodes[e] || e if ( stringContains(e, 'Missing query parameter') || stringContains(e, 'Error Decrypting Wallet') diff --git a/yarn.lock b/yarn.lock index a4c698dc..1e7b629b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -69,8 +69,8 @@ are-we-there-yet@~1.1.2: readable-stream "^2.0.6" argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" dependencies: sprintf-js "~1.0.2" @@ -107,8 +107,10 @@ arrify@^1.0.0: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + dependencies: + safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" @@ -568,8 +570,8 @@ basic-auth@~1.0.3: resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.0.4.tgz#030935b01de7c9b94a824b29f3fccb750d3a5290" bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" dependencies: tweetnacl "^0.14.3" @@ -680,9 +682,9 @@ block-stream@*: dependencies: inherits "~2.0.0" -blockchain-wallet-client@3.32.4: - version "3.32.4" - resolved "https://registry.yarnpkg.com/blockchain-wallet-client/-/blockchain-wallet-client-3.32.4.tgz#1d485ebb4631b4523261d0373153bf012851baee" +blockchain-wallet-client@blockchain/my-wallet-v3#new-mdid: + version "3.32.5" + resolved "https://codeload.github.com/blockchain/my-wallet-v3/tar.gz/119d9d81348165eeceda85006ff46027f3612867" dependencies: bigi "1.4.*" bip39 "2.1.*" @@ -691,12 +693,15 @@ blockchain-wallet-client@3.32.4: bitcoin-sfox-client "^0.1.11" bitcoinjs-lib "2.1.*" bs58 "2.0.*" + core-js "^2.4.1" es6-promise "^3.0.2" isomorphic-fetch "^2.2.1" + jwt-decode "^2.1.0" pbkdf2 "^3.0.12" ramda "^0.22.1" randombytes "^2.0.1" unorm "^1.4.1" + uuid "^3.0.1" ws "2.0.*" blockchain.info@~2.2.2: @@ -951,7 +956,7 @@ cookie@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.5.tgz#6ab9948a4b1ae21952cd2588530a4722d4044d7c" -core-js@^2.4.0: +core-js@^2.4.0, core-js@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" @@ -1095,10 +1100,11 @@ duplexify@^3.2.0: stream-shift "^1.0.0" ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" dependencies: jsbn "~0.1.0" + safer-buffer "^2.1.0" ecurve@^1.0.0: version "1.0.5" @@ -1274,9 +1280,9 @@ espree@^3.3.1: acorn "^5.0.1" acorn-jsx "^3.0.0" -esprima@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" esrecurse@^4.1.0: version "4.1.0" @@ -1899,22 +1905,16 @@ jade@0.26.3: commander "0.6.1" mkdirp "0.3.0" -jodid25519@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" - dependencies: - jsbn "~0.1.0" - js-tokens@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" js-yaml@^3.5.1: - version "3.8.4" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6" + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" dependencies: argparse "^1.0.7" - esprima "^3.1.1" + esprima "^4.0.0" jsbn@~0.1.0: version "0.1.1" @@ -1983,6 +1983,10 @@ jsx-ast-utils@^1.3.3: version "1.4.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" +jwt-decode@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" + kind-of@^3.0.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -2512,9 +2516,9 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -querystringify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" ramda@^0.22.1: version "0.22.1" @@ -2726,7 +2730,7 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" -requires-port@1.0.x: +requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -2782,6 +2786,10 @@ safe-buffer@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + samsam@1.1.2, samsam@~1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" @@ -2920,18 +2928,17 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.0.tgz#ff2a3e4fd04497555fed97b39a0fd82fafb3a33c" + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" ecc-jsbn "~0.1.1" - jodid25519 "^1.0.0" + getpass "^0.1.1" jsbn "~0.1.0" + safer-buffer "^2.0.2" tweetnacl "~0.14.0" stack-trace@0.0.x: @@ -3197,11 +3204,11 @@ url-join@0.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8" url-parse@^1.0.5: - version "1.1.9" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19" + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" dependencies: - querystringify "~1.0.0" - requires-port "1.0.x" + querystringify "^2.1.1" + requires-port "^1.0.0" url-pattern@^0.10.2: version "0.10.2" @@ -3238,7 +3245,7 @@ uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0: +uuid@^3.0.0, uuid@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"