Merge remote-tracking branch 'origin/release-9.0' into chore/merge-9-into-10-20240206
This commit is contained in:
commit
35e40f4528
52 changed files with 4794 additions and 6007 deletions
|
|
@ -2419,158 +2419,5 @@
|
||||||
"Minor unit": 2,
|
"Minor unit": 2,
|
||||||
"Fund": "",
|
"Fund": "",
|
||||||
"": ""
|
"": ""
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ01_Bond Markets Unit European_EURCO",
|
|
||||||
"Currency": "Bond Markets Unit European Composite Unit (EURCO)",
|
|
||||||
"Alphabetic Code": "XBA",
|
|
||||||
"Numeric Code": 955,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ02_Bond Markets Unit European_EMU-6",
|
|
||||||
"Currency": "Bond Markets Unit European Monetary Unit (E.M.U.-6)",
|
|
||||||
"Alphabetic Code": "XBB",
|
|
||||||
"Numeric Code": 956,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ03_Bond Markets Unit European_EUA-9",
|
|
||||||
"Currency": "Bond Markets Unit European Unit of Account 9 (E.U.A.-9)",
|
|
||||||
"Alphabetic Code": "XBC",
|
|
||||||
"Numeric Code": 957,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ04_Bond Markets Unit European_EUA-17",
|
|
||||||
"Currency": "Bond Markets Unit European Unit of Account 17 (E.U.A.-17)",
|
|
||||||
"Alphabetic Code": "XBD",
|
|
||||||
"Numeric Code": 958,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ06_Testing_Code",
|
|
||||||
"Currency": "Codes specifically reserved for testing purposes",
|
|
||||||
"Alphabetic Code": "XTS",
|
|
||||||
"Numeric Code": 963,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ07_No_Currency",
|
|
||||||
"Currency": "The codes assigned for transactions where no currency is involved",
|
|
||||||
"Alphabetic Code": "XXX",
|
|
||||||
"Numeric Code": 999,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ08_Gold",
|
|
||||||
"Currency": "Gold",
|
|
||||||
"Alphabetic Code": "XAU",
|
|
||||||
"Numeric Code": 959,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ09_Palladium",
|
|
||||||
"Currency": "Palladium",
|
|
||||||
"Alphabetic Code": "XPD",
|
|
||||||
"Numeric Code": 964,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ10_Platinum",
|
|
||||||
"Currency": "Platinum",
|
|
||||||
"Alphabetic Code": "XPT",
|
|
||||||
"Numeric Code": 962,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "ZZ11_Silver",
|
|
||||||
"Currency": "Silver",
|
|
||||||
"Alphabetic Code": "XAG",
|
|
||||||
"Numeric Code": 961,
|
|
||||||
"Minor unit": "N.A.",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "",
|
|
||||||
"Currency": "",
|
|
||||||
"Alphabetic Code": "",
|
|
||||||
"Numeric Code": "",
|
|
||||||
"Minor unit": "",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "",
|
|
||||||
"Currency": "",
|
|
||||||
"Alphabetic Code": "",
|
|
||||||
"Numeric Code": "",
|
|
||||||
"Minor unit": "",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "",
|
|
||||||
"Currency": "",
|
|
||||||
"Alphabetic Code": "",
|
|
||||||
"Numeric Code": "",
|
|
||||||
"Minor unit": "",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "",
|
|
||||||
"Currency": "",
|
|
||||||
"Alphabetic Code": "",
|
|
||||||
"Numeric Code": "",
|
|
||||||
"Minor unit": "",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "",
|
|
||||||
"Currency": "",
|
|
||||||
"Alphabetic Code": "",
|
|
||||||
"Numeric Code": "",
|
|
||||||
"Minor unit": "",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "",
|
|
||||||
"Currency": "",
|
|
||||||
"Alphabetic Code": "",
|
|
||||||
"Numeric Code": "",
|
|
||||||
"Minor unit": "",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ENTITY": "",
|
|
||||||
"Currency": "",
|
|
||||||
"Alphabetic Code": "",
|
|
||||||
"Numeric Code": "",
|
|
||||||
"Minor unit": "",
|
|
||||||
"Fund": "",
|
|
||||||
"": ""
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -30,22 +30,22 @@ const BINARIES = {
|
||||||
BTC: {
|
BTC: {
|
||||||
defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
|
defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
|
||||||
defaultDir: 'bitcoin-0.20.1/bin',
|
defaultDir: 'bitcoin-0.20.1/bin',
|
||||||
url: 'https://bitcoincore.org/bin/bitcoin-core-25.0/bitcoin-25.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://bitcoincore.org/bin/bitcoin-core-26.0/bitcoin-26.0-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-25.0/bin'
|
dir: 'bitcoin-26.0/bin'
|
||||||
},
|
},
|
||||||
ETH: {
|
ETH: {
|
||||||
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.13.1-3f40e65c.tar.gz',
|
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.13.5-916d6a44.tar.gz',
|
||||||
dir: 'geth-linux-amd64-1.13.1-3f40e65c'
|
dir: 'geth-linux-amd64-1.13.5-916d6a44'
|
||||||
},
|
},
|
||||||
ZEC: {
|
ZEC: {
|
||||||
url: 'https://z.cash/downloads/zcash-5.6.1-linux64-debian-bullseye.tar.gz',
|
url: 'https://download.z.cash/downloads/zcash-5.7.0-linux64-debian-bullseye.tar.gz',
|
||||||
dir: 'zcash-5.6.1/bin'
|
dir: 'zcash-5.7.0/bin'
|
||||||
},
|
},
|
||||||
DASH: {
|
DASH: {
|
||||||
defaultUrl: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
|
defaultUrl: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
|
||||||
defaultDir: 'dashcore-18.1.0/bin',
|
defaultDir: 'dashcore-18.1.0/bin',
|
||||||
url: 'https://github.com/dashpay/dash/releases/download/v19.3.0/dashcore-19.3.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://github.com/dashpay/dash/releases/download/v20.0.2/dashcore-20.0.2-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'dashcore-19.3.0/bin'
|
dir: 'dashcore-20.0.2/bin'
|
||||||
},
|
},
|
||||||
LTC: {
|
LTC: {
|
||||||
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
||||||
|
|
@ -54,13 +54,13 @@ const BINARIES = {
|
||||||
dir: 'litecoin-0.21.2.2/bin'
|
dir: 'litecoin-0.21.2.2/bin'
|
||||||
},
|
},
|
||||||
BCH: {
|
BCH: {
|
||||||
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v26.1.0/bitcoin-cash-node-26.1.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v27.0.0/bitcoin-cash-node-27.0.0-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-cash-node-26.1.0/bin',
|
dir: 'bitcoin-cash-node-27.0.0/bin',
|
||||||
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
||||||
},
|
},
|
||||||
XMR: {
|
XMR: {
|
||||||
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.2.2.tar.bz2',
|
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.1.tar.bz2',
|
||||||
dir: 'monero-x86_64-linux-gnu-v0.18.2.2',
|
dir: 'monero-x86_64-linux-gnu-v0.18.3.1',
|
||||||
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']]
|
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
lib/blockexplorers/mempool.space.js
Normal file
8
lib/blockexplorers/mempool.space.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
const getSatBEstimateFee = () => {
|
||||||
|
return axios.get('https://mempool.space/api/v1/fees/recommended')
|
||||||
|
.then(r => r.data.hourFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getSatBEstimateFee }
|
||||||
|
|
@ -26,4 +26,9 @@ const hasPhone = hasRequirement('sms')
|
||||||
const hasFacephoto = hasRequirement('facephoto')
|
const hasFacephoto = hasRequirement('facephoto')
|
||||||
const hasIdScan = hasRequirement('idCardData')
|
const hasIdScan = hasRequirement('idCardData')
|
||||||
|
|
||||||
module.exports = { getBackwardsCompatibleTriggers, hasSanctions, maxDaysThreshold, getCashLimit, hasPhone, hasFacephoto, hasIdScan }
|
const AUTH_METHODS = {
|
||||||
|
SMS: 'SMS',
|
||||||
|
EMAIL: 'EMAIL'
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getBackwardsCompatibleTriggers, hasSanctions, maxDaysThreshold, getCashLimit, hasPhone, hasFacephoto, hasIdScan, AUTH_METHODS }
|
||||||
|
|
@ -6,13 +6,10 @@ const makeDir = require('make-dir')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const util = require('util')
|
const util = require('util')
|
||||||
const { sub, differenceInHours } = require('date-fns/fp')
|
|
||||||
|
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const BN = require('./bn')
|
|
||||||
const anonymous = require('../lib/constants').anonymousCustomer
|
const anonymous = require('../lib/constants').anonymousCustomer
|
||||||
const complianceOverrides = require('./compliance_overrides')
|
const complianceOverrides = require('./compliance_overrides')
|
||||||
const users = require('./users')
|
|
||||||
const writeFile = util.promisify(fs.writeFile)
|
const writeFile = util.promisify(fs.writeFile)
|
||||||
const notifierQueries = require('./notifier/queries')
|
const notifierQueries = require('./notifier/queries')
|
||||||
const notifierUtils = require('./notifier/utils')
|
const notifierUtils = require('./notifier/utils')
|
||||||
|
|
@ -43,6 +40,12 @@ function add (customer) {
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addWithEmail (customer) {
|
||||||
|
const sql = 'insert into customers (id, email, email_at) values ($1, $2, now()) returning *'
|
||||||
|
return db.one(sql, [uuid.v4(), customer.email])
|
||||||
|
.then(camelize)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get single customer by phone
|
* Get single customer by phone
|
||||||
* Phone numbers are unique per customer
|
* Phone numbers are unique per customer
|
||||||
|
|
@ -60,6 +63,12 @@ function get (phone) {
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWithEmail (email) {
|
||||||
|
const sql = 'select * from customers where email=$1'
|
||||||
|
return db.oneOrNone(sql, [email])
|
||||||
|
.then(camelize)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update customer record
|
* Update customer record
|
||||||
*
|
*
|
||||||
|
|
@ -308,7 +317,7 @@ const updateSubscriberData = (customerId, data, userToken) => {
|
||||||
*
|
*
|
||||||
* Used for the machine.
|
* Used for the machine.
|
||||||
*/
|
*/
|
||||||
function getById (id, userToken) {
|
function getById (id) {
|
||||||
const sql = 'select * from customers where id=$1'
|
const sql = 'select * from customers where id=$1'
|
||||||
return db.oneOrNone(sql, [id])
|
return db.oneOrNone(sql, [id])
|
||||||
.then(assignCustomerData)
|
.then(assignCustomerData)
|
||||||
|
|
@ -349,6 +358,7 @@ function camelizeDeep (customer) {
|
||||||
function getComplianceTypes () {
|
function getComplianceTypes () {
|
||||||
return [
|
return [
|
||||||
'sms',
|
'sms',
|
||||||
|
'email',
|
||||||
'id_card_data',
|
'id_card_data',
|
||||||
'id_card_photo',
|
'id_card_photo',
|
||||||
'front_camera',
|
'front_camera',
|
||||||
|
|
@ -478,11 +488,11 @@ function batch () {
|
||||||
* @returns {array} Array of customers with it's transactions aggregations
|
* @returns {array} Array of customers with it's transactions aggregations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getCustomersList (phone = null, name = null, address = null, id = null) {
|
function getCustomersList (phone = null, name = null, address = null, id = null, email = null) {
|
||||||
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
||||||
|
|
||||||
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_override,
|
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_override,
|
||||||
phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
|
phone, email, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||||
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||||
sanctions_override, total_txs, total_spent, GREATEST(created, last_transaction, last_data_provided) AS last_active, fiat AS last_tx_fiat,
|
sanctions_override, total_txs, total_spent, GREATEST(created, last_transaction, last_data_provided) AS last_active, fiat AS last_tx_fiat,
|
||||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, notes, is_test_customer
|
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, notes, is_test_customer
|
||||||
|
|
@ -491,9 +501,9 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
||||||
greatest(0, date_part('day', c.suspended_until - NOW())) AS days_suspended,
|
greatest(0, date_part('day', c.suspended_until - NOW())) AS days_suspended,
|
||||||
c.suspended_until > NOW() AS is_suspended,
|
c.suspended_until > NOW() AS is_suspended,
|
||||||
c.front_camera_path, c.front_camera_override,
|
c.front_camera_path, c.front_camera_override,
|
||||||
c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
|
c.phone, c.email, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
|
||||||
c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions,
|
c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions,
|
||||||
GREATEST(c.phone_at, c.id_card_data_at, c.front_camera_at, c.id_card_photo_at, c.us_ssn_at) AS last_data_provided,
|
GREATEST(c.phone_at, c.email_at, c.id_card_data_at, c.front_camera_at, c.id_card_photo_at, c.us_ssn_at) AS last_data_provided,
|
||||||
c.sanctions_at, c.sanctions_override, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
c.sanctions_at, c.sanctions_override, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
||||||
row_number() OVER (partition by c.id order by t.created desc) AS rn,
|
row_number() OVER (partition by c.id order by t.created desc) AS rn,
|
||||||
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs,
|
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs,
|
||||||
|
|
@ -519,8 +529,9 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
||||||
AND ($5 IS NULL OR CONCAT(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') = $5 OR id_card_data::json->>'firstName' = $5 OR id_card_data::json->>'lastName' = $5)
|
AND ($5 IS NULL OR CONCAT(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') = $5 OR id_card_data::json->>'firstName' = $5 OR id_card_data::json->>'lastName' = $5)
|
||||||
AND ($6 IS NULL OR id_card_data::json->>'address' = $6)
|
AND ($6 IS NULL OR id_card_data::json->>'address' = $6)
|
||||||
AND ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7)
|
AND ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7)
|
||||||
|
AND ($8 IS NULL OR email = $8)
|
||||||
limit $3`
|
limit $3`
|
||||||
return db.any(sql, [ passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ])
|
return db.any(sql, [ passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id, email ])
|
||||||
.then(customers => Promise.all(_.map(customer =>
|
.then(customers => Promise.all(_.map(customer =>
|
||||||
getCustomInfoRequestsData(customer)
|
getCustomInfoRequestsData(customer)
|
||||||
.then(camelizeDeep), customers)
|
.then(camelizeDeep), customers)
|
||||||
|
|
@ -540,7 +551,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
||||||
function getCustomerById (id) {
|
function getCustomerById (id) {
|
||||||
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
||||||
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
|
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
|
||||||
phone, phone_at, phone_override, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
|
phone, phone_at, email, email_at, phone_override, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||||
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||||
sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
|
sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
|
||||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, subscriber_info_at, custom_fields, notes, is_test_customer
|
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, subscriber_info_at, custom_fields, notes, is_test_customer
|
||||||
|
|
@ -549,7 +560,7 @@ function getCustomerById (id) {
|
||||||
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
||||||
c.suspended_until > now() AS is_suspended,
|
c.suspended_until > now() AS is_suspended,
|
||||||
c.front_camera_path, c.front_camera_override, c.front_camera_at,
|
c.front_camera_path, c.front_camera_override, c.front_camera_at,
|
||||||
c.phone, c.phone_at, c.phone_override, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
c.phone, c.phone_at, c.email, c.email_at, c.phone_override, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
||||||
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
|
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
|
||||||
c.sanctions_at, c.sanctions_override, c.subscriber_info, c.subscriber_info_at, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
c.sanctions_at, c.sanctions_override, c.subscriber_info, c.subscriber_info_at, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
||||||
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
|
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
|
||||||
|
|
@ -912,7 +923,9 @@ function disableTestCustomer (customerId) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
add,
|
add,
|
||||||
|
addWithEmail,
|
||||||
get,
|
get,
|
||||||
|
getWithEmail,
|
||||||
batch,
|
batch,
|
||||||
getCustomersList,
|
getCustomersList,
|
||||||
getCustomerById,
|
getCustomerById,
|
||||||
|
|
@ -930,7 +943,5 @@ module.exports = {
|
||||||
updateEditedPhoto,
|
updateEditedPhoto,
|
||||||
updateTxCustomerPhoto,
|
updateTxCustomerPhoto,
|
||||||
enableTestCustomer,
|
enableTestCustomer,
|
||||||
disableTestCustomer,
|
disableTestCustomer
|
||||||
selectLatestData,
|
|
||||||
getEditedData
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
lib/email.js
15
lib/email.js
|
|
@ -3,7 +3,7 @@ const ph = require('./plugin-helper')
|
||||||
function sendMessage (settings, rec) {
|
function sendMessage (settings, rec) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const pluginCode = 'mailgun'
|
const pluginCode = settings.config.notifications_thirdParty_email
|
||||||
const plugin = ph.load(ph.EMAIL, pluginCode)
|
const plugin = ph.load(ph.EMAIL, pluginCode)
|
||||||
const account = settings.accounts[pluginCode]
|
const account = settings.accounts[pluginCode]
|
||||||
|
|
||||||
|
|
@ -11,4 +11,15 @@ function sendMessage (settings, rec) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {sendMessage}
|
function sendCustomerMessage (settings, rec) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const pluginCode = settings.config.notifications_thirdParty_email
|
||||||
|
const plugin = ph.load(ph.EMAIL, pluginCode)
|
||||||
|
const account = settings.accounts[pluginCode]
|
||||||
|
|
||||||
|
return plugin.sendMessage(account, rec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {sendMessage, sendCustomerMessage}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
configManager.getReceipt(settings.config),
|
configManager.getReceipt(settings.config),
|
||||||
!!configManager.getCashOut(deviceId, settings.config).active,
|
!!configManager.getCashOut(deviceId, settings.config).active,
|
||||||
getMachine(deviceId, currentConfigVersion),
|
getMachine(deviceId, currentConfigVersion),
|
||||||
|
configManager.getCustomerAuthenticationMethod(settings.config)
|
||||||
])
|
])
|
||||||
.then(([
|
.then(([
|
||||||
enablePaperWalletOnly,
|
enablePaperWalletOnly,
|
||||||
|
|
@ -128,6 +129,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
receiptInfo,
|
receiptInfo,
|
||||||
twoWayMode,
|
twoWayMode,
|
||||||
{ numberOfCassettes, numberOfRecyclers },
|
{ numberOfCassettes, numberOfRecyclers },
|
||||||
|
customerAuthentication,
|
||||||
]) =>
|
]) =>
|
||||||
(currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ?
|
(currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ?
|
||||||
null :
|
null :
|
||||||
|
|
@ -144,6 +146,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
},
|
},
|
||||||
machineInfo: { deviceId, deviceName, numberOfCassettes, numberOfRecyclers },
|
machineInfo: { deviceId, deviceName, numberOfCassettes, numberOfRecyclers },
|
||||||
twoWayMode,
|
twoWayMode,
|
||||||
|
customerAuthentication,
|
||||||
speedtestFiles,
|
speedtestFiles,
|
||||||
urlsToPing,
|
urlsToPing,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,11 @@ type Terms {
|
||||||
details: TermsDetails
|
details: TermsDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CustomerAuthentication {
|
||||||
|
EMAIL
|
||||||
|
SMS
|
||||||
|
}
|
||||||
|
|
||||||
type StaticConfig {
|
type StaticConfig {
|
||||||
configVersion: Int!
|
configVersion: Int!
|
||||||
|
|
||||||
|
|
@ -134,6 +139,7 @@ type StaticConfig {
|
||||||
serverVersion: String!
|
serverVersion: String!
|
||||||
timezone: Int!
|
timezone: Int!
|
||||||
twoWayMode: Boolean!
|
twoWayMode: Boolean!
|
||||||
|
customerAuthentication: CustomerAuthentication!
|
||||||
|
|
||||||
localeInfo: LocaleInfo!
|
localeInfo: LocaleInfo!
|
||||||
operatorInfo: OperatorInfo
|
operatorInfo: OperatorInfo
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ const ALL_ACCOUNTS = [
|
||||||
{ code: 'telnyx', display: 'Telnyx', class: SMS },
|
{ code: 'telnyx', display: 'Telnyx', class: SMS },
|
||||||
{ code: 'vonage', display: 'Vonage', class: SMS },
|
{ code: 'vonage', display: 'Vonage', class: SMS },
|
||||||
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
||||||
|
{ code: 'mock-email', display: 'Mock Email', class: EMAIL, dev: true },
|
||||||
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: ALL_CRYPTOS },
|
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: ALL_CRYPTOS },
|
||||||
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
||||||
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true },
|
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true },
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ function massageCurrencies (currencies) {
|
||||||
const codeToRec = code => _.find(_.matchesProperty('code', code), mapped)
|
const codeToRec = code => _.find(_.matchesProperty('code', code), mapped)
|
||||||
const top5 = _.map(codeToRec, top5Codes)
|
const top5 = _.map(codeToRec, top5Codes)
|
||||||
const raw = _.uniqBy(_.get('code'), _.concat(top5, mapped))
|
const raw = _.uniqBy(_.get('code'), _.concat(top5, mapped))
|
||||||
return raw.filter(r => r.code !== '' && r.code[0] !== 'X' && r.display.indexOf('(') === -1)
|
return raw.filter(r => r.code !== '' && r.display.indexOf('(') === -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapLanguage = lang => {
|
const mapLanguage = lang => {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ function transaction () {
|
||||||
function customer () {
|
function customer () {
|
||||||
const sql = `SELECT DISTINCT * FROM (
|
const sql = `SELECT DISTINCT * FROM (
|
||||||
SELECT 'phone' AS type, phone AS value FROM customers WHERE phone IS NOT NULL UNION
|
SELECT 'phone' AS type, phone AS value FROM customers WHERE phone IS NOT NULL UNION
|
||||||
|
SELECT 'email' AS type, email AS value FROM customers WHERE email IS NOT NULL UNION
|
||||||
SELECT 'name' AS type, id_card_data::json->>'firstName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND id_card_data::json->>'lastName' IS NULL UNION
|
SELECT 'name' AS type, id_card_data::json->>'firstName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND id_card_data::json->>'lastName' IS NULL UNION
|
||||||
SELECT 'name' AS type, id_card_data::json->>'lastName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NULL AND id_card_data::json->>'lastName' IS NOT NULL UNION
|
SELECT 'name' AS type, id_card_data::json->>'lastName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NULL AND id_card_data::json->>'lastName' IS NOT NULL UNION
|
||||||
SELECT 'name' AS type, concat(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND id_card_data::json->>'lastName' IS NOT NULL UNION
|
SELECT 'name' AS type, concat(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND id_card_data::json->>'lastName' IS NOT NULL UNION
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const resolvers = {
|
||||||
isAnonymous: parent => (parent.customerId === anonymous.uuid)
|
isAnonymous: parent => (parent.customerId === anonymous.uuid)
|
||||||
},
|
},
|
||||||
Query: {
|
Query: {
|
||||||
customers: (...[, { phone, name, address, id }]) => customers.getCustomersList(phone, name, address, id),
|
customers: (...[, { phone, email, name, address, id }]) => customers.getCustomersList(phone, name, address, id, email),
|
||||||
customer: (...[, { customerId }]) => customers.getCustomerById(customerId),
|
customer: (...[, { customerId }]) => customers.getCustomerById(customerId),
|
||||||
customerFilters: () => filters.customer()
|
customerFilters: () => filters.customer()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const typeDef = gql`
|
||||||
frontCameraAt: Date
|
frontCameraAt: Date
|
||||||
frontCameraOverride: String
|
frontCameraOverride: String
|
||||||
phone: String
|
phone: String
|
||||||
|
email: String
|
||||||
isAnonymous: Boolean
|
isAnonymous: Boolean
|
||||||
smsOverride: String
|
smsOverride: String
|
||||||
idCardData: JSONObject
|
idCardData: JSONObject
|
||||||
|
|
@ -92,7 +93,7 @@ const typeDef = gql`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
customers(phone: String, name: String, address: String, id: String): [Customer] @auth
|
customers(phone: String, name: String, email: String, address: String, id: String): [Customer] @auth
|
||||||
customer(customerId: ID!): Customer @auth
|
customer(customerId: ID!): Customer @auth
|
||||||
customerFilters: [Filter] @auth
|
customerFilters: [Filter] @auth
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ const typeDef = gql`
|
||||||
rawTickerPrice: String
|
rawTickerPrice: String
|
||||||
isPaperWallet: Boolean
|
isPaperWallet: Boolean
|
||||||
customerPhone: String
|
customerPhone: String
|
||||||
|
customerEmail: String
|
||||||
customerIdCardDataNumber: String
|
customerIdCardDataNumber: String
|
||||||
customerIdCardDataExpiration: Date
|
customerIdCardDataExpiration: Date
|
||||||
customerIdCardData: JSONObject
|
customerIdCardData: JSONObject
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ function batch (
|
||||||
|
|
||||||
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
||||||
c.phone AS customer_phone,
|
c.phone AS customer_phone,
|
||||||
|
c.email AS customer_email,
|
||||||
c.id_card_data_number AS customer_id_card_data_number,
|
c.id_card_data_number AS customer_id_card_data_number,
|
||||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||||
c.id_card_data AS customer_id_card_data,
|
c.id_card_data AS customer_id_card_data,
|
||||||
|
|
@ -86,6 +87,7 @@ function batch (
|
||||||
txs.*,
|
txs.*,
|
||||||
actions.tx_hash,
|
actions.tx_hash,
|
||||||
c.phone AS customer_phone,
|
c.phone AS customer_phone,
|
||||||
|
c.email AS customer_email,
|
||||||
c.id_card_data_number AS customer_id_card_data_number,
|
c.id_card_data_number AS customer_id_card_data_number,
|
||||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||||
c.id_card_data AS customer_id_card_data,
|
c.id_card_data AS customer_id_card_data,
|
||||||
|
|
@ -159,7 +161,7 @@ function advancedBatch (data) {
|
||||||
'denominationRecycler1', 'denominationRecycler2', 'denominationRecycler3', 'denominationRecycler4', 'denominationRecycler5', 'denominationRecycler6',
|
'denominationRecycler1', 'denominationRecycler2', 'denominationRecycler3', 'denominationRecycler4', 'denominationRecycler5', 'denominationRecycler6',
|
||||||
'errorCode', 'customerId', 'txVersion', 'publishedAt', 'termsAccepted', 'layer2Address',
|
'errorCode', 'customerId', 'txVersion', 'publishedAt', 'termsAccepted', 'layer2Address',
|
||||||
'commissionPercentage', 'rawTickerPrice', 'receivedCryptoAtoms',
|
'commissionPercentage', 'rawTickerPrice', 'receivedCryptoAtoms',
|
||||||
'discount', 'txHash', 'customerPhone', 'customerIdCardDataNumber',
|
'discount', 'txHash', 'customerPhone', 'customerEmail', 'customerIdCardDataNumber',
|
||||||
'customerIdCardDataExpiration', 'customerIdCardData', 'customerName', 'sendTime',
|
'customerIdCardDataExpiration', 'customerIdCardData', 'customerName', 'sendTime',
|
||||||
'customerFrontCameraPath', 'customerIdCardPhotoPath', 'expired', 'machineName', 'walletScore']
|
'customerFrontCameraPath', 'customerIdCardPhotoPath', 'expired', 'machineName', 'walletScore']
|
||||||
|
|
||||||
|
|
@ -174,8 +176,8 @@ function advancedBatch (data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifiedBatch (data) {
|
function simplifiedBatch (data) {
|
||||||
const fields = ['txClass', 'id', 'created', 'machineName',
|
const fields = ['txClass', 'id', 'created', 'machineName', 'fee',
|
||||||
'cryptoCode', 'cryptoAtoms', 'fiat', 'fiatCode', 'phone', 'toAddress',
|
'cryptoCode', 'cryptoAtoms', 'fiat', 'fiatCode', 'phone', 'email', 'toAddress',
|
||||||
'txHash', 'dispense', 'error', 'status', 'fiatProfit', 'cryptoAmount']
|
'txHash', 'dispense', 'error', 'status', 'fiatProfit', 'cryptoAmount']
|
||||||
|
|
||||||
const addSimplifiedFields = _.map(it => ({
|
const addSimplifiedFields = _.map(it => ({
|
||||||
|
|
@ -236,6 +238,7 @@ function getCustomerTransactionsBatch (ids) {
|
||||||
|
|
||||||
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
||||||
c.phone AS customer_phone,
|
c.phone AS customer_phone,
|
||||||
|
c.email AS customer_email,
|
||||||
c.id_card_data_number AS customer_id_card_data_number,
|
c.id_card_data_number AS customer_id_card_data_number,
|
||||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||||
c.id_card_data AS customer_id_card_data,
|
c.id_card_data AS customer_id_card_data,
|
||||||
|
|
@ -254,6 +257,7 @@ function getCustomerTransactionsBatch (ids) {
|
||||||
txs.*,
|
txs.*,
|
||||||
actions.tx_hash,
|
actions.tx_hash,
|
||||||
c.phone AS customer_phone,
|
c.phone AS customer_phone,
|
||||||
|
c.email AS customer_email,
|
||||||
c.id_card_data_number AS customer_id_card_data_number,
|
c.id_card_data_number AS customer_id_card_data_number,
|
||||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||||
c.id_card_data AS customer_id_card_data,
|
c.id_card_data AS customer_id_card_data,
|
||||||
|
|
@ -282,6 +286,7 @@ function single (txId) {
|
||||||
|
|
||||||
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
||||||
c.phone AS customer_phone,
|
c.phone AS customer_phone,
|
||||||
|
c.email AS customer_email,
|
||||||
c.id_card_data_number AS customer_id_card_data_number,
|
c.id_card_data_number AS customer_id_card_data_number,
|
||||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||||
c.id_card_data AS customer_id_card_data,
|
c.id_card_data AS customer_id_card_data,
|
||||||
|
|
@ -299,6 +304,7 @@ function single (txId) {
|
||||||
txs.*,
|
txs.*,
|
||||||
actions.tx_hash,
|
actions.tx_hash,
|
||||||
c.phone AS customer_phone,
|
c.phone AS customer_phone,
|
||||||
|
c.email AS customer_email,
|
||||||
c.id_card_data_number AS customer_id_card_data_number,
|
c.id_card_data_number AS customer_id_card_data_number,
|
||||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||||
c.id_card_data AS customer_id_card_data,
|
c.id_card_data AS customer_id_card_data,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const {AUTH_METHODS} = require('./compliance-triggers')
|
||||||
|
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const { validate } = require('uuid')
|
const { validate } = require('uuid')
|
||||||
|
|
||||||
|
|
@ -120,6 +122,10 @@ const getGlobalNotifications = config => getNotifications(null, null, config)
|
||||||
|
|
||||||
const getTriggers = _.get('triggers')
|
const getTriggers = _.get('triggers')
|
||||||
|
|
||||||
|
function getCustomerAuthenticationMethod(config) {
|
||||||
|
return _.get('triggersConfig_customerAuthentication')(config)
|
||||||
|
}
|
||||||
|
|
||||||
/* `customInfoRequests` is the result of a call to `getCustomInfoRequests` */
|
/* `customInfoRequests` is the result of a call to `getCustomInfoRequests` */
|
||||||
const getTriggersAutomation = (customInfoRequests, config, oldFormat = false) => {
|
const getTriggersAutomation = (customInfoRequests, config, oldFormat = false) => {
|
||||||
return customInfoRequests
|
return customInfoRequests
|
||||||
|
|
@ -193,4 +199,5 @@ module.exports = {
|
||||||
getCryptosFromWalletNamespace,
|
getCryptosFromWalletNamespace,
|
||||||
getCryptoUnits,
|
getCryptoUnits,
|
||||||
setTermsConditions,
|
setTermsConditions,
|
||||||
|
getCustomerAuthenticationMethod,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ const SECRET_FIELDS = [
|
||||||
'twilio.authToken',
|
'twilio.authToken',
|
||||||
'telnyx.apiKey',
|
'telnyx.apiKey',
|
||||||
'vonage.apiSecret',
|
'vonage.apiSecret',
|
||||||
'galoy.walletId'
|
'galoy.walletId',
|
||||||
|
'galoy.apiSecret'
|
||||||
]
|
]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -933,6 +933,23 @@ function plugins (settings, deviceId) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEmailCode (toEmail) {
|
||||||
|
const code = settings.config.notifications_thirdParty_email === 'mock-email'
|
||||||
|
? '123'
|
||||||
|
: randomCode()
|
||||||
|
|
||||||
|
const rec = {
|
||||||
|
email: {
|
||||||
|
toEmail,
|
||||||
|
subject: 'Your cryptomat code',
|
||||||
|
body: `Your cryptomat code: ${code}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return email.sendCustomerMessage(settings, rec)
|
||||||
|
.then(() => code)
|
||||||
|
}
|
||||||
|
|
||||||
function sweepHdRow (row) {
|
function sweepHdRow (row) {
|
||||||
const txId = row.id
|
const txId = row.id
|
||||||
const cryptoCode = row.crypto_code
|
const cryptoCode = row.crypto_code
|
||||||
|
|
@ -999,6 +1016,10 @@ function plugins (settings, deviceId) {
|
||||||
return walletScoring.isWalletScoringEnabled(settings, tx.cryptoCode)
|
return walletScoring.isWalletScoringEnabled(settings, tx.cryptoCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function probeLN (cryptoCode, address) {
|
||||||
|
return wallet.probeLN(settings, cryptoCode, address)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getRates,
|
getRates,
|
||||||
recordPing,
|
recordPing,
|
||||||
|
|
@ -1012,6 +1033,7 @@ function plugins (settings, deviceId) {
|
||||||
isZeroConf,
|
isZeroConf,
|
||||||
getStatus,
|
getStatus,
|
||||||
getPhoneCode,
|
getPhoneCode,
|
||||||
|
getEmailCode,
|
||||||
executeTrades,
|
executeTrades,
|
||||||
pong,
|
pong,
|
||||||
clearOldLogs,
|
clearOldLogs,
|
||||||
|
|
@ -1031,6 +1053,7 @@ function plugins (settings, deviceId) {
|
||||||
getTransactionHash,
|
getTransactionHash,
|
||||||
getInputAddresses,
|
getInputAddresses,
|
||||||
isWalletScoringEnabled,
|
isWalletScoringEnabled,
|
||||||
|
probeLN,
|
||||||
buildAvailableUnits
|
buildAvailableUnits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,25 @@ const NAME = 'Mailgun'
|
||||||
|
|
||||||
function sendMessage ({apiKey, domain, fromEmail, toEmail}, rec) {
|
function sendMessage ({apiKey, domain, fromEmail, toEmail}, rec) {
|
||||||
const mailgun = Mailgun({apiKey, domain})
|
const mailgun = Mailgun({apiKey, domain})
|
||||||
|
const to = rec.email.toEmail ?? toEmail
|
||||||
|
|
||||||
const emailData = {
|
const emailData = {
|
||||||
from: `Lamassu Server ${fromEmail}`,
|
from: `Lamassu Server ${fromEmail}`,
|
||||||
to: toEmail,
|
to,
|
||||||
|
subject: rec.email.subject,
|
||||||
|
text: rec.email.body
|
||||||
|
}
|
||||||
|
|
||||||
|
return mailgun.messages().send(emailData)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendCustomerMessage ({apiKey, domain, fromEmail}, rec) {
|
||||||
|
const mailgun = Mailgun({apiKey, domain})
|
||||||
|
const to = rec.email.toEmail
|
||||||
|
|
||||||
|
const emailData = {
|
||||||
|
from: fromEmail,
|
||||||
|
to,
|
||||||
subject: rec.email.subject,
|
subject: rec.email.subject,
|
||||||
text: rec.email.body
|
text: rec.email.body
|
||||||
}
|
}
|
||||||
|
|
@ -17,5 +32,6 @@ function sendMessage ({apiKey, domain, fromEmail, toEmail}, rec) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NAME,
|
NAME,
|
||||||
sendMessage
|
sendMessage,
|
||||||
|
sendCustomerMessage
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
lib/plugins/email/mock-email/mock-email.js
Normal file
15
lib/plugins/email/mock-email/mock-email.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
const NAME = 'mock-email'
|
||||||
|
|
||||||
|
function sendMessage (settings, rec) {
|
||||||
|
console.log('sending email', rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendCustomerMessage(settings, rec) {
|
||||||
|
console.log('sending email', rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
NAME,
|
||||||
|
sendMessage,
|
||||||
|
sendCustomerMessage
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,14 @@ const RETRIES = 2
|
||||||
|
|
||||||
const tickerObjects = {}
|
const tickerObjects = {}
|
||||||
|
|
||||||
|
// This is probably fixed on upstream ccxt
|
||||||
|
// but we need to udpate node to get on the latest version
|
||||||
|
const sanityCheckRates = (ask, bid, tickerName) => {
|
||||||
|
if (new BN(0).eq(ask) || new BN(0).eq(bid)) {
|
||||||
|
throw new Error(`Failure fetching rates for ${tickerName}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ticker (fiatCode, cryptoCode, tickerName) {
|
function ticker (fiatCode, cryptoCode, tickerName) {
|
||||||
if (!tickerObjects[tickerName]) {
|
if (!tickerObjects[tickerName]) {
|
||||||
tickerObjects[tickerName] = new ccxt[tickerName]({
|
tickerObjects[tickerName] = new ccxt[tickerName]({
|
||||||
|
|
@ -45,12 +53,15 @@ function getCurrencyRates (ticker, fiatCode, cryptoCode) {
|
||||||
}
|
}
|
||||||
const symbol = buildMarket(fiatCode, cryptoCode, ticker.id)
|
const symbol = buildMarket(fiatCode, cryptoCode, ticker.id)
|
||||||
return ticker.fetchTicker(symbol)
|
return ticker.fetchTicker(symbol)
|
||||||
.then(res => ({
|
.then(res => {
|
||||||
|
sanityCheckRates(res.ask, res.bid, cryptoCode)
|
||||||
|
return {
|
||||||
rates: {
|
rates: {
|
||||||
ask: new BN(res.ask),
|
ask: new BN(res.ask),
|
||||||
bid: new BN(res.bid)
|
bid: new BN(res.bid)
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const jsonRpc = require('../../common/json-rpc')
|
const jsonRpc = require('../../common/json-rpc')
|
||||||
|
const { getSatBEstimateFee } = require('../../../blockexplorers/mempool.space')
|
||||||
|
|
||||||
const BN = require('../../../bn')
|
const BN = require('../../../bn')
|
||||||
const E = require('../../../error')
|
const E = require('../../../error')
|
||||||
|
|
@ -56,20 +57,28 @@ function balance (account, cryptoCode, settings, operatorId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function estimateFee () {
|
function estimateFee () {
|
||||||
return fetch('estimatesmartfee', [6, 'unset'])
|
return getSatBEstimateFee()
|
||||||
.then(result => BN(result.feerate))
|
.then(result => BN(result))
|
||||||
.catch(() => {})
|
.catch(err => {
|
||||||
|
logger.error('failure estimating fes', err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateFeeDiscount (feeMultiplier) {
|
function calculateFeeDiscount (feeMultiplier = 1, unitScale) {
|
||||||
// 0 makes bitcoind do automatic fee selection
|
// 0 makes bitcoind do automatic fee selection
|
||||||
const AUTOMATIC_FEE = isDevMode() ? 0.01 : 0
|
const AUTOMATIC_FEE = 0
|
||||||
if (!feeMultiplier || feeMultiplier.eq(1)) return AUTOMATIC_FEE
|
|
||||||
return estimateFee()
|
return estimateFee()
|
||||||
.then(estimatedFee => {
|
.then(estimatedFee => {
|
||||||
if (!estimatedFee) return AUTOMATIC_FEE
|
if (!estimatedFee) {
|
||||||
const newFee = estimatedFee.times(feeMultiplier)
|
logger.info('failure estimating fee, using bitcoind automatic fee selection')
|
||||||
if (newFee.lt(0.00001) || newFee.gt(0.1)) return AUTOMATIC_FEE
|
return AUTOMATIC_FEE
|
||||||
|
}
|
||||||
|
// transform from sat/vB to BTC/kvB and apply the multipler
|
||||||
|
const newFee = estimatedFee.shiftedBy(-unitScale+3).times(feeMultiplier)
|
||||||
|
if (newFee.lt(0.00001) || newFee.gt(0.1)) {
|
||||||
|
logger.info('fee outside safety parameters, defaulting to automatic fee selection')
|
||||||
|
return AUTOMATIC_FEE
|
||||||
|
}
|
||||||
return newFee.toFixed(8)
|
return newFee.toFixed(8)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +88,7 @@ function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
|
||||||
const coins = cryptoAtoms.shiftedBy(-unitScale).toFixed(8)
|
const coins = cryptoAtoms.shiftedBy(-unitScale).toFixed(8)
|
||||||
|
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => calculateFeeDiscount(feeMultiplier))
|
.then(() => calculateFeeDiscount(feeMultiplier, unitScale))
|
||||||
.then(newFee => fetch('settxfee', [newFee]))
|
.then(newFee => fetch('settxfee', [newFee]))
|
||||||
.then(() => fetch('sendtoaddress', [toAddress, coins]))
|
.then(() => fetch('sendtoaddress', [toAddress, coins]))
|
||||||
.then((txId) => fetch('gettransaction', [txId]))
|
.then((txId) => fetch('gettransaction', [txId]))
|
||||||
|
|
@ -95,7 +104,7 @@ function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
|
||||||
|
|
||||||
function sendCoinsBatch (account, txs, cryptoCode, feeMultiplier) {
|
function sendCoinsBatch (account, txs, cryptoCode, feeMultiplier) {
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => calculateFeeDiscount(feeMultiplier))
|
.then(() => calculateFeeDiscount(feeMultiplier, unitScale))
|
||||||
.then(newFee => fetch('settxfee', [newFee]))
|
.then(newFee => fetch('settxfee', [newFee]))
|
||||||
.then(() => _.reduce((acc, value) => ({
|
.then(() => _.reduce((acc, value) => ({
|
||||||
...acc,
|
...acc,
|
||||||
|
|
@ -207,7 +216,6 @@ module.exports = {
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
fetchRBF,
|
fetchRBF,
|
||||||
estimateFee,
|
|
||||||
sendCoinsBatch,
|
sendCoinsBatch,
|
||||||
checkBlockchainStatus,
|
checkBlockchainStatus,
|
||||||
getTxHashesByAddress,
|
getTxHashesByAddress,
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,20 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const invoice = require('@node-lightning/invoice')
|
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const { utils: coinUtils } = require('@lamassu/coins')
|
const { utils: coinUtils } = require('@lamassu/coins')
|
||||||
|
|
||||||
const NAME = 'LN'
|
const NAME = 'LN'
|
||||||
const SUPPORTED_COINS = ['LN', 'BTC']
|
const SUPPORTED_COINS = ['LN', 'BTC']
|
||||||
const TX_PENDING = 'PENDING'
|
|
||||||
const TX_SUCCESS = 'SUCCESS'
|
|
||||||
|
|
||||||
const URI = 'https://api.staging.galoy.io/graphql'
|
|
||||||
|
|
||||||
const BN = require('../../../bn')
|
const BN = require('../../../bn')
|
||||||
|
|
||||||
function request (graphqlQuery, token) {
|
function request (graphqlQuery, token, endpoint) {
|
||||||
const headers = {
|
const headers = {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
return axios({
|
return axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: URI,
|
url: endpoint,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
data: graphqlQuery
|
data: graphqlQuery
|
||||||
})
|
})
|
||||||
|
|
@ -27,6 +22,9 @@ function request (graphqlQuery, token) {
|
||||||
if (r.error) throw r.error
|
if (r.error) throw r.error
|
||||||
return r.data
|
return r.data
|
||||||
})
|
})
|
||||||
|
.catch(err => {
|
||||||
|
throw new Error(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCryptoCode (cryptoCode) {
|
function checkCryptoCode (cryptoCode) {
|
||||||
|
|
@ -37,62 +35,60 @@ function checkCryptoCode (cryptoCode) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGaloyAccount (token) {
|
function getTransactionsByAddress (token, endpoint, walletId, address) {
|
||||||
const accountInfo = {
|
const accountInfo = {
|
||||||
'operationName': 'me',
|
'operationName': 'me',
|
||||||
'query': `query me {
|
'query': `query me {
|
||||||
me {
|
me {
|
||||||
defaultAccount {
|
defaultAccount {
|
||||||
defaultWalletId
|
|
||||||
wallets {
|
wallets {
|
||||||
id
|
id
|
||||||
walletCurrency
|
transactionsByAddress (address: "${address}") {
|
||||||
balance
|
|
||||||
transactions {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
direction
|
direction
|
||||||
id
|
|
||||||
settlementAmount
|
settlementAmount
|
||||||
settlementFee
|
|
||||||
status
|
status
|
||||||
initiationVia {
|
|
||||||
... on InitiationViaIntraLedger {
|
|
||||||
counterPartyUsername
|
|
||||||
counterPartyWalletId
|
|
||||||
}
|
|
||||||
... on InitiationViaLn {
|
|
||||||
paymentHash
|
|
||||||
}
|
|
||||||
... on InitiationViaOnChain {
|
|
||||||
address
|
|
||||||
}
|
|
||||||
}
|
|
||||||
settlementVia {
|
|
||||||
... on SettlementViaIntraLedger {
|
|
||||||
counterPartyUsername
|
|
||||||
counterPartyWalletId
|
|
||||||
}
|
|
||||||
... on SettlementViaLn {
|
|
||||||
preImage
|
|
||||||
}
|
|
||||||
... on SettlementViaOnChain {
|
|
||||||
transactionHash
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}`,
|
}`,
|
||||||
'variables': {}
|
'variables': {}
|
||||||
}
|
}
|
||||||
return request(accountInfo, token)
|
return request(accountInfo, token, endpoint)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
return r.data.me.defaultAccount
|
return _.find(it => it.id === walletId, r.data.me.defaultAccount.wallets).transactionsByAddress
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
throw new Error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGaloyWallet (token, endpoint, walletId) {
|
||||||
|
const accountInfo = {
|
||||||
|
'operationName': 'me',
|
||||||
|
'query': `query me {
|
||||||
|
me {
|
||||||
|
defaultAccount {
|
||||||
|
wallets {
|
||||||
|
id
|
||||||
|
walletCurrency
|
||||||
|
balance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
'variables': {}
|
||||||
|
}
|
||||||
|
return request(accountInfo, token, endpoint)
|
||||||
|
.then(r => {
|
||||||
|
return _.find(it => it.id === walletId, r.data.me.defaultAccount.wallets)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
throw new Error(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +96,7 @@ function isLightning (address) {
|
||||||
return address.substr(0, 2) === 'ln'
|
return address.substr(0, 2) === 'ln'
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendFundsOnChain (walletId, address, cryptoAtoms, token) {
|
function sendFundsOnChain (walletId, address, cryptoAtoms, token, endpoint) {
|
||||||
const sendOnChain = {
|
const sendOnChain = {
|
||||||
'operationName': 'onChainPaymentSend',
|
'operationName': 'onChainPaymentSend',
|
||||||
'query': `mutation onChainPaymentSend($input: OnChainPaymentSendInput!) {
|
'query': `mutation onChainPaymentSend($input: OnChainPaymentSendInput!) {
|
||||||
|
|
@ -114,17 +110,17 @@ function sendFundsOnChain (walletId, address, cryptoAtoms, token) {
|
||||||
}`,
|
}`,
|
||||||
'variables': { 'input': { 'address': `${address}`, 'amount': `${cryptoAtoms}`, 'walletId': `${walletId}` } }
|
'variables': { 'input': { 'address': `${address}`, 'amount': `${cryptoAtoms}`, 'walletId': `${walletId}` } }
|
||||||
}
|
}
|
||||||
return request(sendOnChain, token)
|
return request(sendOnChain, token, endpoint)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
return result.data.onChainPaymentSend
|
return result.data.onChainPaymentSend
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendFundsLN (walletId, invoice, token) {
|
function sendFundsLN (walletId, invoice, cryptoAtoms, token, endpoint) {
|
||||||
const sendLN = {
|
const sendLnNoAmount = {
|
||||||
'operationName': 'lnInvoicePaymentSend',
|
'operationName': 'lnNoAmountInvoicePaymentSend',
|
||||||
'query': `mutation lnInvoicePaymentSend($input: LnInvoicePaymentInput!) {
|
'query': `mutation lnNoAmountInvoicePaymentSend($input: LnNoAmountInvoicePaymentInput!) {
|
||||||
lnInvoicePaymentSend(input: $input) {
|
lnNoAmountInvoicePaymentSend(input: $input) {
|
||||||
errors {
|
errors {
|
||||||
message
|
message
|
||||||
path
|
path
|
||||||
|
|
@ -132,29 +128,38 @@ function sendFundsLN (walletId, invoice, token) {
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
'variables': { 'input': { 'paymentRequest': `${invoice}`, 'walletId': `${walletId}` } }
|
'variables': { 'input': { 'paymentRequest': `${invoice}`, 'walletId': `${walletId}`, 'amount': `${cryptoAtoms}` } }
|
||||||
}
|
}
|
||||||
return request(sendLN, token)
|
return request(sendLnNoAmount, token, endpoint).then(result => result.data.lnNoAmountInvoicePaymentSend)
|
||||||
.then(result => {
|
}
|
||||||
return result.data.lnInvoicePaymentSend
|
|
||||||
})
|
function sendProbeRequest (walletId, invoice, cryptoAtoms, token, endpoint) {
|
||||||
|
const sendProbeNoAmount = {
|
||||||
|
'operationName': 'lnNoAmountInvoiceFeeProbe',
|
||||||
|
'query': `mutation lnNoAmountInvoiceFeeProbe($input: LnNoAmountInvoiceFeeProbeInput!) {
|
||||||
|
lnNoAmountInvoiceFeeProbe(input: $input) {
|
||||||
|
amount
|
||||||
|
errors {
|
||||||
|
message
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
'variables': { 'input': { 'paymentRequest': `${invoice}`, 'walletId': `${walletId}`, 'amount': `${cryptoAtoms}` } }
|
||||||
|
}
|
||||||
|
return request(sendProbeNoAmount, token, endpoint).then(result => result.data.lnNoAmountInvoiceFeeProbe)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendCoins (account, tx, settings, operatorId) {
|
function sendCoins (account, tx, settings, operatorId) {
|
||||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||||
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => getGaloyAccount(account.apiKey))
|
.then(() => getGaloyWallet(account.apiSecret, account.endpoint, account.walletId))
|
||||||
.then(galoyAccount => {
|
.then(wallet => {
|
||||||
const wallet = _.head(
|
|
||||||
_.filter(wallet => wallet.walletCurrency === externalCryptoCode &&
|
|
||||||
wallet.id === galoyAccount.defaultWalletId &&
|
|
||||||
wallet.id === account.walletId)(galoyAccount.wallets)
|
|
||||||
)
|
|
||||||
if (isLightning(toAddress)) {
|
if (isLightning(toAddress)) {
|
||||||
return sendFundsLN(wallet.id, toAddress, account.apiKey)
|
return sendFundsLN(wallet.id, toAddress, cryptoAtoms, account.apiSecret, account.endpoint)
|
||||||
}
|
}
|
||||||
return sendFundsOnChain(wallet.id, toAddress, cryptoAtoms, account.apiKey)
|
return sendFundsOnChain(wallet.id, toAddress, cryptoAtoms, account.apiSecret, account.endpoint)
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
switch (result.status) {
|
switch (result.status) {
|
||||||
|
|
@ -172,7 +177,17 @@ function sendCoins (account, tx, settings, operatorId) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function newOnChainAddress (walletId, token) {
|
function probeLN (account, cryptoCode, invoice) {
|
||||||
|
const probeHardLimits = [100, 500, 1000]
|
||||||
|
const promises = probeHardLimits.map(limit => {
|
||||||
|
return sendProbeRequest(account.walletId, invoice, limit, account.apiSecret, account.endpoint)
|
||||||
|
.then(r => _.isEmpty(r.errors))
|
||||||
|
})
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(results => _.zipObject(probeHardLimits, results))
|
||||||
|
}
|
||||||
|
|
||||||
|
function newOnChainAddress (walletId, token, endpoint) {
|
||||||
const createOnChainAddress = {
|
const createOnChainAddress = {
|
||||||
'operationName': 'onChainAddressCreate',
|
'operationName': 'onChainAddressCreate',
|
||||||
'query': `mutation onChainAddressCreate($input: OnChainAddressCreateInput!) {
|
'query': `mutation onChainAddressCreate($input: OnChainAddressCreateInput!) {
|
||||||
|
|
@ -186,13 +201,13 @@ function newOnChainAddress (walletId, token) {
|
||||||
}`,
|
}`,
|
||||||
'variables': { 'input': { 'walletId': `${walletId}` } }
|
'variables': { 'input': { 'walletId': `${walletId}` } }
|
||||||
}
|
}
|
||||||
return request(createOnChainAddress, token)
|
return request(createOnChainAddress, token, endpoint)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
return result.data.onChainAddressCreate.address
|
return result.data.onChainAddressCreate.address
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function newInvoice (walletId, cryptoAtoms, token) {
|
function newInvoice (walletId, cryptoAtoms, token, endpoint) {
|
||||||
const createInvoice = {
|
const createInvoice = {
|
||||||
'operationName': 'lnInvoiceCreate',
|
'operationName': 'lnInvoiceCreate',
|
||||||
'query': `mutation lnInvoiceCreate($input: LnInvoiceCreateInput!) {
|
'query': `mutation lnInvoiceCreate($input: LnInvoiceCreateInput!) {
|
||||||
|
|
@ -208,42 +223,28 @@ function newInvoice (walletId, cryptoAtoms, token) {
|
||||||
}`,
|
}`,
|
||||||
'variables': { 'input': { 'walletId': `${walletId}`, 'amount': `${cryptoAtoms}` } }
|
'variables': { 'input': { 'walletId': `${walletId}`, 'amount': `${cryptoAtoms}` } }
|
||||||
}
|
}
|
||||||
return request(createInvoice, token)
|
return request(createInvoice, token, endpoint)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
return result.data.lnInvoiceCreate.invoice.paymentRequest
|
return result.data.lnInvoiceCreate.invoice.paymentRequest
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function balance (account, cryptoCode, settings, operatorId) {
|
function balance (account, cryptoCode, settings, operatorId) {
|
||||||
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => getGaloyAccount(account.apiKey))
|
.then(() => getGaloyWallet(account.apiSecret, account.endpoint, account.walletId))
|
||||||
.then(galoyAccount => {
|
.then(wallet => {
|
||||||
// account has a list of wallets, should we consider the balance of each one?
|
|
||||||
// for now we'll get the first BTC wallet that matches the defaultWalletId
|
|
||||||
const wallet = _.head(
|
|
||||||
_.filter(
|
|
||||||
wallet => wallet.walletCurrency === externalCryptoCode &&
|
|
||||||
wallet.id === account.walletId)(galoyAccount.wallets)
|
|
||||||
)
|
|
||||||
return new BN(wallet.balance || 0)
|
return new BN(wallet.balance || 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function newAddress (account, info, tx, settings, operatorId) {
|
function newAddress (account, info, tx, settings, operatorId) {
|
||||||
const { cryptoAtoms, cryptoCode } = tx
|
const { cryptoAtoms, cryptoCode } = tx
|
||||||
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => getGaloyAccount(account.apiKey))
|
.then(() => getGaloyWallet(account.apiSecret, account.endpoint, account.walletId))
|
||||||
.then(galoyAccount => {
|
.then(wallet => {
|
||||||
const wallet = _.head(
|
|
||||||
_.filter(wallet => wallet.walletCurrency === externalCryptoCode &&
|
|
||||||
wallet.id === galoyAccount.defaultWalletId &&
|
|
||||||
wallet.id === account.walletId)(galoyAccount.wallets)
|
|
||||||
)
|
|
||||||
const promises = [
|
const promises = [
|
||||||
newOnChainAddress(wallet.id, account.apiKey),
|
newOnChainAddress(wallet.id, account.apiSecret, account.endpoint),
|
||||||
newInvoice(wallet.id, cryptoAtoms, account.apiKey)
|
newInvoice(wallet.id, cryptoAtoms, account.apiSecret, account.endpoint)
|
||||||
]
|
]
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
})
|
})
|
||||||
|
|
@ -254,31 +255,29 @@ function newAddress (account, info, tx, settings, operatorId) {
|
||||||
|
|
||||||
function getStatus (account, tx, requested, settings, operatorId) {
|
function getStatus (account, tx, requested, settings, operatorId) {
|
||||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||||
const mapStatus = tx => {
|
const getBalance = _.reduce((acc, value) => {
|
||||||
if (!tx) return 'notSeen'
|
acc[value.node.status] = acc[value.node.status].plus(new BN(value.node.settlementAmount))
|
||||||
if (tx.node.status === TX_PENDING) return 'authorized'
|
return acc
|
||||||
if (tx.node.status === TX_SUCCESS) return 'confirmed'
|
}, { SUCCESS: new BN(0), PENDING: new BN(0), FAILURE: new BN(0) })
|
||||||
return 'notSeen'
|
|
||||||
}
|
|
||||||
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
||||||
const address = coinUtils.parseUrl(toAddress)
|
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => getGaloyAccount(account.apiKey))
|
.then(() => {
|
||||||
.then(galoyAccount => {
|
const address = coinUtils.parseUrl(cryptoCode, account.environment, toAddress, false)
|
||||||
const wallet = _.head(
|
// Consider all LN transactions successful
|
||||||
_.filter(wallet => wallet.walletCurrency === externalCryptoCode &&
|
|
||||||
wallet.id === galoyAccount.defaultWalletId &&
|
|
||||||
wallet.id === account.walletId)(galoyAccount.wallets)
|
|
||||||
)
|
|
||||||
const transactions = wallet.transactions.edges
|
|
||||||
if (isLightning(address)) {
|
if (isLightning(address)) {
|
||||||
const paymentHash = invoice.decode(address).paymentHash.toString('hex')
|
return { receivedCryptoAtoms: cryptoAtoms, status: 'confirmed' }
|
||||||
const transaction = _.head(_.filter(tx => tx.node.initiationVia.paymentHash === paymentHash && tx.node.direction === 'RECEIVE')(transactions))
|
|
||||||
return { receivedCryptoAtoms: cryptoAtoms, status: mapStatus(transaction) }
|
|
||||||
}
|
}
|
||||||
// On-chain tx
|
// On-chain and intra-ledger transactions
|
||||||
const transaction = _.head(_.filter(tx => tx.node.initiationVia.address === address)(transactions))
|
return getTransactionsByAddress(account.apiSecret, account.endpoint, account.walletId, address)
|
||||||
return { receivedCryptoAtoms: cryptoAtoms, status: mapStatus(transaction) }
|
.then(transactions => {
|
||||||
|
const txEdges = transactions.edges
|
||||||
|
const { SUCCESS: confirmed, PENDING: pending } = getBalance(txEdges)
|
||||||
|
if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' }
|
||||||
|
if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' }
|
||||||
|
if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' }
|
||||||
|
return { receivedCryptoAtoms: pending, status: 'notSeen' }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,23 +285,15 @@ function newFunding (account, cryptoCode, settings, operatorId) {
|
||||||
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
const externalCryptoCode = coinUtils.getEquivalentCode(cryptoCode)
|
||||||
// Regular BTC address
|
// Regular BTC address
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => getGaloyAccount(account.apiKey))
|
.then(() => getGaloyWallet(account.apiSecret, account.endpoint, account.walletId))
|
||||||
.then(galoyAccount => {
|
.then(wallet => {
|
||||||
const wallet = _.head(
|
return newOnChainAddress(wallet.id, account.apiSecret, account.endpoint)
|
||||||
_.filter(wallet => wallet.walletCurrency === externalCryptoCode &&
|
.then(onChainAddress => [onChainAddress, wallet.balance])
|
||||||
wallet.id === galoyAccount.defaultWalletId &&
|
|
||||||
wallet.id === account.walletId)(galoyAccount.wallets)
|
|
||||||
)
|
|
||||||
const pendingBalance = _.sumBy(tx => {
|
|
||||||
if (tx.node.status === TX_PENDING) return tx.node.settlementAmount
|
|
||||||
return 0
|
|
||||||
})(wallet.transactions.edges)
|
|
||||||
return newOnChainAddress(wallet.id, account.apiKey)
|
|
||||||
.then(onChainAddress => [onChainAddress, wallet.balance, pendingBalance])
|
|
||||||
})
|
})
|
||||||
.then(([onChainAddress, balance, pendingBalance]) => {
|
.then(([onChainAddress, balance]) => {
|
||||||
return {
|
return {
|
||||||
fundingPendingBalance: new BN(pendingBalance),
|
// with the old api is not possible to get pending balance
|
||||||
|
fundingPendingBalance: new BN(0),
|
||||||
fundingConfirmedBalance: new BN(balance),
|
fundingConfirmedBalance: new BN(balance),
|
||||||
fundingAddress: onChainAddress
|
fundingAddress: onChainAddress
|
||||||
}
|
}
|
||||||
|
|
@ -327,5 +318,6 @@ module.exports = {
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
checkBlockchainStatus
|
checkBlockchainStatus,
|
||||||
|
probeLN
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,12 @@ function toCashOutTx (row) {
|
||||||
return _.set('direction', 'cashOut', newObj)
|
return _.set('direction', 'cashOut', newObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchPhoneTx (phone) {
|
function fetchEmailOrPhoneTx (data, type) {
|
||||||
const sql = `select * from cash_out_txs
|
const sql = `select * from cash_out_txs
|
||||||
where phone=$1 and dispense=$2
|
where ${type === 'email' ? 'email' : 'phone'}=$1 and dispense=$2
|
||||||
and (extract(epoch from (now() - created))) * 1000 < $3`
|
and (extract(epoch from (now() - created))) * 1000 < $3`
|
||||||
|
|
||||||
const values = [phone, false, TRANSACTION_EXPIRATION]
|
const values = [data, false, TRANSACTION_EXPIRATION]
|
||||||
|
|
||||||
return db.any(sql, values)
|
return db.any(sql, values)
|
||||||
.then(_.map(toCashOutTx))
|
.then(_.map(toCashOutTx))
|
||||||
|
|
@ -72,6 +72,13 @@ function fetchPhoneTx (phone) {
|
||||||
throw httpError('No transactions', 404)
|
throw httpError('No transactions', 404)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function fetchEmailTx (email) {
|
||||||
|
return fetchEmailOrPhoneTx(email, 'email')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchPhoneTx (phone) {
|
||||||
|
return fetchEmailOrPhoneTx(phone, 'phone')
|
||||||
|
}
|
||||||
|
|
||||||
function fetchStatusTx (txId, status) {
|
function fetchStatusTx (txId, status) {
|
||||||
const sql = 'select * from cash_out_txs where id=$1'
|
const sql = 'select * from cash_out_txs where id=$1'
|
||||||
|
|
@ -88,6 +95,7 @@ function fetchStatusTx (txId, status) {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
stateChange,
|
stateChange,
|
||||||
fetchPhoneTx,
|
fetchPhoneTx,
|
||||||
|
fetchEmailTx,
|
||||||
fetchStatusTx,
|
fetchStatusTx,
|
||||||
httpError
|
httpError
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ const { router: txRoutes } = require('./routes/txRoutes')
|
||||||
const verifyUserRoutes = require('./routes/verifyUserRoutes')
|
const verifyUserRoutes = require('./routes/verifyUserRoutes')
|
||||||
const verifyTxRoutes = require('./routes/verifyTxRoutes')
|
const verifyTxRoutes = require('./routes/verifyTxRoutes')
|
||||||
const verifyPromoCodeRoutes = require('./routes/verifyPromoCodeRoutes')
|
const verifyPromoCodeRoutes = require('./routes/verifyPromoCodeRoutes')
|
||||||
|
const probeRoutes = require('./routes/probeLnRoutes')
|
||||||
|
|
||||||
const graphQLServer = require('./graphql/server')
|
const graphQLServer = require('./graphql/server')
|
||||||
|
|
||||||
|
|
@ -77,7 +78,10 @@ app.use('/verify_user', verifyUserRoutes)
|
||||||
app.use('/verify_transaction', verifyTxRoutes)
|
app.use('/verify_transaction', verifyTxRoutes)
|
||||||
app.use('/verify_promo_code', verifyPromoCodeRoutes)
|
app.use('/verify_promo_code', verifyPromoCodeRoutes)
|
||||||
|
|
||||||
|
// BACKWARDS_COMPATIBILITY 9.0
|
||||||
|
// machines before 9.0 still use the phone_code route
|
||||||
app.use('/phone_code', phoneCodeRoutes)
|
app.use('/phone_code', phoneCodeRoutes)
|
||||||
|
|
||||||
app.use('/customer', customerRoutes)
|
app.use('/customer', customerRoutes)
|
||||||
|
|
||||||
app.use('/tx', txRoutes)
|
app.use('/tx', txRoutes)
|
||||||
|
|
@ -85,6 +89,8 @@ app.use('/tx', txRoutes)
|
||||||
app.use('/logs', logsRoutes)
|
app.use('/logs', logsRoutes)
|
||||||
app.use('/units', unitsRoutes)
|
app.use('/units', unitsRoutes)
|
||||||
|
|
||||||
|
app.use('/probe', probeRoutes)
|
||||||
|
|
||||||
graphQLServer.applyMiddleware({ app })
|
graphQLServer.applyMiddleware({ app })
|
||||||
|
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ const machineLoader = require('../machine-loader')
|
||||||
const { loadLatestConfig } = require('../new-settings-loader')
|
const { loadLatestConfig } = require('../new-settings-loader')
|
||||||
const customInfoRequestQueries = require('../new-admin/services/customInfoRequests')
|
const customInfoRequestQueries = require('../new-admin/services/customInfoRequests')
|
||||||
const T = require('../time')
|
const T = require('../time')
|
||||||
|
const plugins = require('../plugins')
|
||||||
|
const Tx = require('../tx')
|
||||||
|
const loyalty = require('../loyalty')
|
||||||
|
|
||||||
function updateCustomerCustomInfoRequest (customerId, patch, req, res) {
|
function updateCustomerCustomInfoRequest (customerId, patch, req, res) {
|
||||||
if (_.isNil(patch.data)) {
|
if (_.isNil(patch.data)) {
|
||||||
|
|
@ -185,6 +188,70 @@ function sendSmsReceipt (req, res, next) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addOrUpdateCustomer (customerData, config, isEmailAuth) {
|
||||||
|
const triggers = configManager.getTriggers(config)
|
||||||
|
const maxDaysThreshold = complianceTriggers.maxDaysThreshold(triggers)
|
||||||
|
|
||||||
|
const customerKey = isEmailAuth ? customerData.email : customerData.phone
|
||||||
|
const getFunc = isEmailAuth ? customers.getWithEmail : customers.get
|
||||||
|
const addFunction = isEmailAuth ? customers.addWithEmail : customers.add
|
||||||
|
|
||||||
|
return getFunc(customerKey)
|
||||||
|
.then(customer => {
|
||||||
|
if (customer) return customer
|
||||||
|
|
||||||
|
return addFunction(customerData)
|
||||||
|
})
|
||||||
|
.then(customer => customers.getById(customer.id))
|
||||||
|
.then(customer => {
|
||||||
|
return Tx.customerHistory(customer.id, maxDaysThreshold)
|
||||||
|
.then(result => {
|
||||||
|
customer.txHistory = result
|
||||||
|
return customer
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(customer => {
|
||||||
|
return loyalty.getCustomerActiveIndividualDiscount(customer.id)
|
||||||
|
.then(discount => ({ ...customer, discount }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrAddCustomerPhone (req, res, next) {
|
||||||
|
const customerData = req.body
|
||||||
|
|
||||||
|
const pi = plugins(req.settings, req.deviceId)
|
||||||
|
const phone = req.body.phone
|
||||||
|
|
||||||
|
return pi.getPhoneCode(phone)
|
||||||
|
.then(code => {
|
||||||
|
return addOrUpdateCustomer(customerData, req.settings.config, false)
|
||||||
|
.then(customer => respond(req, res, { code, customer }))
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrAddCustomerEmail (req, res, next) {
|
||||||
|
const customerData = req.body
|
||||||
|
|
||||||
|
const pi = plugins(req.settings, req.deviceId)
|
||||||
|
const email = req.body.email
|
||||||
|
|
||||||
|
return pi.getEmailCode(email)
|
||||||
|
.then(code => {
|
||||||
|
return addOrUpdateCustomer(customerData, req.settings.config, true)
|
||||||
|
.then(customer => respond(req, res, { code, customer }))
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
router.patch('/:id', updateCustomer)
|
router.patch('/:id', updateCustomer)
|
||||||
router.patch('/:id/sanctions', triggerSanctions)
|
router.patch('/:id/sanctions', triggerSanctions)
|
||||||
router.patch('/:id/block', triggerBlock)
|
router.patch('/:id/block', triggerBlock)
|
||||||
|
|
@ -192,5 +259,7 @@ router.patch('/:id/suspend', triggerSuspend)
|
||||||
router.patch('/:id/photos/idcarddata', updateIdCardData)
|
router.patch('/:id/photos/idcarddata', updateIdCardData)
|
||||||
router.patch('/:id/:txId/photos/customerphoto', updateTxCustomerPhoto)
|
router.patch('/:id/:txId/photos/customerphoto', updateTxCustomerPhoto)
|
||||||
router.post('/:id/smsreceipt', sendSmsReceipt)
|
router.post('/:id/smsreceipt', sendSmsReceipt)
|
||||||
|
router.post('/phone_code', getOrAddCustomerPhone)
|
||||||
|
router.post('/email_code', getOrAddCustomerEmail)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
||||||
20
lib/routes/probeLnRoutes.js
Normal file
20
lib/routes/probeLnRoutes.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const express = require('express')
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
const plugins = require('../plugins')
|
||||||
|
const settingsLoader = require('../new-settings-loader')
|
||||||
|
|
||||||
|
function probe (req, res, next) {
|
||||||
|
// TODO: why req.settings is undefined?
|
||||||
|
settingsLoader.loadLatest()
|
||||||
|
.then(settings => {
|
||||||
|
const pi = plugins(settings, req.deviceId)
|
||||||
|
return pi.probeLN('LN', req.body.address)
|
||||||
|
.then(r => res.status(200).send({ hardLimits: r }))
|
||||||
|
.catch(next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/', probe)
|
||||||
|
|
||||||
|
module.exports = router
|
||||||
|
|
@ -66,8 +66,19 @@ function getPhoneTx (req, res, next) {
|
||||||
return next(httpError('Not Found', 404))
|
return next(httpError('Not Found', 404))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEmailTx (req, res, next) {
|
||||||
|
if (req.query.email) {
|
||||||
|
return helpers.fetchEmailTx(req.query.email)
|
||||||
|
.then(r => res.json(r))
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(httpError('Not Found', 404))
|
||||||
|
}
|
||||||
|
|
||||||
router.post('/', postTx)
|
router.post('/', postTx)
|
||||||
router.get('/:id', getTx)
|
router.get('/:id', getTx)
|
||||||
router.get('/', getPhoneTx)
|
router.get('/', getPhoneTx)
|
||||||
|
router.get('/', getEmailTx)
|
||||||
|
|
||||||
module.exports = { postTx, getTx, getPhoneTx, router }
|
module.exports = { postTx, getTx, getPhoneTx, getEmailTx, router }
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,13 @@ function _balance (settings, cryptoCode) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function probeLN (settings, cryptoCode, address) {
|
||||||
|
return fetchWallet(settings, cryptoCode).then(r => {
|
||||||
|
if (!r.wallet.probeLN) return null
|
||||||
|
return r.wallet.probeLN(r.account, cryptoCode, address)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function sendCoins (settings, tx) {
|
function sendCoins (settings, tx) {
|
||||||
return fetchWallet(settings, tx.cryptoCode)
|
return fetchWallet(settings, tx.cryptoCode)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
|
|
@ -299,5 +306,6 @@ module.exports = {
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
supportsBatching,
|
supportsBatching,
|
||||||
checkBlockchainStatus
|
checkBlockchainStatus,
|
||||||
|
probeLN
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
migrations/1700123461281-customer-email.js
Normal file
14
migrations/1700123461281-customer-email.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
let sql = [
|
||||||
|
'ALTER TABLE customers ADD COLUMN email text unique',
|
||||||
|
'ALTER TABLE customers ADD COLUMN email_at timestamptz',
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
18
migrations/1700123461282-customer-auth-advanced-trigger.js
Normal file
18
migrations/1700123461282-customer-auth-advanced-trigger.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
const { migrationSaveConfig } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
const triggersDefault = {
|
||||||
|
triggersConfig_customerAuthentication: 'SMS',
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrationSaveConfig(triggersDefault)
|
||||||
|
.then(() => next())
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err.message)
|
||||||
|
return next(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
14
migrations/1700123461283-phone-on-tx.js
Normal file
14
migrations/1700123461283-phone-on-tx.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
let sql = [
|
||||||
|
'ALTER TABLE cash_in_txs ADD COLUMN email text',
|
||||||
|
'ALTER TABLE cash_out_txs ADD COLUMN email text',
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -101,6 +101,7 @@ const CustomerData = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
const phone = R.path(['phone'])(customer)
|
const phone = R.path(['phone'])(customer)
|
||||||
|
const email = R.path(['email'])(customer)
|
||||||
const smsData = R.path(['subscriberInfo'])(customer)
|
const smsData = R.path(['subscriberInfo'])(customer)
|
||||||
|
|
||||||
const isEven = elem => elem % 2 === 0
|
const isEven = elem => elem % 2 === 0
|
||||||
|
|
@ -134,6 +135,9 @@ const CustomerData = ({
|
||||||
idCardPhoto: {
|
idCardPhoto: {
|
||||||
idCardPhoto: null
|
idCardPhoto: null
|
||||||
},
|
},
|
||||||
|
email: {
|
||||||
|
email
|
||||||
|
},
|
||||||
smsData: {
|
smsData: {
|
||||||
phoneNumber: getFormattedPhone(phone, locale.country)
|
phoneNumber: getFormattedPhone(phone, locale.country)
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +205,19 @@ const CustomerData = ({
|
||||||
hasAdditionalData: !R.isNil(smsData) && !R.isEmpty(smsData),
|
hasAdditionalData: !R.isNil(smsData) && !R.isEmpty(smsData),
|
||||||
editable: false
|
editable: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Email',
|
||||||
|
fields: customerDataElements.email,
|
||||||
|
titleIcon: <CardIcon className={classes.cardIcon} />,
|
||||||
|
// state: R.path(['emailOverride'])(customer),
|
||||||
|
// authorize: () => updateCustomer({ emailOverride: OVERRIDE_AUTHORIZED }),
|
||||||
|
// reject: () => updateCustomer({ emailOverride: OVERRIDE_REJECTED }),
|
||||||
|
save: values => editCustomer(values),
|
||||||
|
deleteEditedData: () => deleteEditedData({ email: null }),
|
||||||
|
initialValues: initialValues.email,
|
||||||
|
isAvailable: !R.isNil(customer.email),
|
||||||
|
editable: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
titleIcon: <EditIcon className={classes.editIcon} />,
|
titleIcon: <EditIcon className={classes.editIcon} />,
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ const GET_CUSTOMER = gql`
|
||||||
frontCameraAt
|
frontCameraAt
|
||||||
frontCameraOverride
|
frontCameraOverride
|
||||||
phone
|
phone
|
||||||
|
email
|
||||||
isAnonymous
|
isAnonymous
|
||||||
smsOverride
|
smsOverride
|
||||||
idCardData
|
idCardData
|
||||||
|
|
@ -132,6 +133,7 @@ const SET_CUSTOMER = gql`
|
||||||
frontCameraPath
|
frontCameraPath
|
||||||
frontCameraOverride
|
frontCameraOverride
|
||||||
phone
|
phone
|
||||||
|
email
|
||||||
smsOverride
|
smsOverride
|
||||||
idCardData
|
idCardData
|
||||||
idCardDataOverride
|
idCardDataOverride
|
||||||
|
|
@ -516,6 +518,8 @@ const CustomerProfile = memo(() => {
|
||||||
})) ?? []
|
})) ?? []
|
||||||
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const email = R.path(['email'])(customerData)
|
||||||
|
const phone = R.path(['phone'])(customerData)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -532,10 +536,9 @@ const CustomerProfile = memo(() => {
|
||||||
<Label2 noMargin className={classes.labelLink}>
|
<Label2 noMargin className={classes.labelLink}>
|
||||||
{name.length
|
{name.length
|
||||||
? name
|
? name
|
||||||
: getFormattedPhone(
|
: email?.length
|
||||||
R.path(['phone'])(customerData),
|
? email
|
||||||
locale.country
|
: getFormattedPhone(phone, locale.country)}
|
||||||
)}
|
|
||||||
</Label2>
|
</Label2>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<div className={classes.panels}>
|
<div className={classes.panels}>
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,22 @@ const GET_CUSTOMERS = gql`
|
||||||
query configAndCustomers(
|
query configAndCustomers(
|
||||||
$phone: String
|
$phone: String
|
||||||
$name: String
|
$name: String
|
||||||
|
$email: String
|
||||||
$address: String
|
$address: String
|
||||||
$id: String
|
$id: String
|
||||||
) {
|
) {
|
||||||
config
|
config
|
||||||
customers(phone: $phone, name: $name, address: $address, id: $id) {
|
customers(
|
||||||
|
phone: $phone
|
||||||
|
email: $email
|
||||||
|
name: $name
|
||||||
|
address: $address
|
||||||
|
id: $id
|
||||||
|
) {
|
||||||
id
|
id
|
||||||
idCardData
|
idCardData
|
||||||
phone
|
phone
|
||||||
|
email
|
||||||
totalTxs
|
totalTxs
|
||||||
totalSpent
|
totalSpent
|
||||||
lastActive
|
lastActive
|
||||||
|
|
@ -154,6 +162,7 @@ const Customers = () => {
|
||||||
setVariables({
|
setVariables({
|
||||||
phone: filtersObject.phone,
|
phone: filtersObject.phone,
|
||||||
name: filtersObject.name,
|
name: filtersObject.name,
|
||||||
|
email: filtersObject.email,
|
||||||
address: filtersObject.address,
|
address: filtersObject.address,
|
||||||
id: filtersObject.id
|
id: filtersObject.id
|
||||||
})
|
})
|
||||||
|
|
@ -173,6 +182,7 @@ const Customers = () => {
|
||||||
setVariables({
|
setVariables({
|
||||||
phone: filtersObject.phone,
|
phone: filtersObject.phone,
|
||||||
name: filtersObject.name,
|
name: filtersObject.name,
|
||||||
|
email: filtersObject.email,
|
||||||
address: filtersObject.address,
|
address: filtersObject.address,
|
||||||
id: filtersObject.id
|
id: filtersObject.id
|
||||||
})
|
})
|
||||||
|
|
@ -187,6 +197,7 @@ const Customers = () => {
|
||||||
setVariables({
|
setVariables({
|
||||||
phone: filtersObject.phone,
|
phone: filtersObject.phone,
|
||||||
name: filtersObject.name,
|
name: filtersObject.name,
|
||||||
|
email: filtersObject.email,
|
||||||
address: filtersObject.address,
|
address: filtersObject.address,
|
||||||
id: filtersObject.id
|
id: filtersObject.id
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,10 @@ const CustomersList = ({
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
header: 'Phone',
|
header: 'Phone/email',
|
||||||
width: 199,
|
width: 199,
|
||||||
view: it => getFormattedPhone(it.phone, locale.country)
|
view: it => `${getFormattedPhone(it.phone, locale.country) || ''}
|
||||||
|
${it.email || ''}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ const CustomerDetails = memo(({ customer, photosData, locale, timezone }) => {
|
||||||
|
|
||||||
const idNumber = R.path(['idCardData', 'documentNumber'])(customer)
|
const idNumber = R.path(['idCardData', 'documentNumber'])(customer)
|
||||||
const usSsn = R.path(['usSsn'])(customer)
|
const usSsn = R.path(['usSsn'])(customer)
|
||||||
|
const name = getName(customer)
|
||||||
|
const email = R.path(['email'])(customer)
|
||||||
|
const phone = R.path(['phone'])(customer)
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +43,12 @@ const CustomerDetails = memo(({ customer, photosData, locale, timezone }) => {
|
||||||
value: usSsn
|
value: usSsn
|
||||||
})
|
})
|
||||||
|
|
||||||
const name = getName(customer)
|
if (email)
|
||||||
|
elements.push({
|
||||||
|
header: 'Email',
|
||||||
|
size: 190,
|
||||||
|
value: email
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
|
|
@ -51,7 +59,9 @@ const CustomerDetails = memo(({ customer, photosData, locale, timezone }) => {
|
||||||
<H2 noMargin>
|
<H2 noMargin>
|
||||||
{name.length
|
{name.length
|
||||||
? name
|
? name
|
||||||
: getFormattedPhone(R.path(['phone'])(customer), locale.country)}
|
: email?.length
|
||||||
|
? email
|
||||||
|
: getFormattedPhone(phone, locale.country)}
|
||||||
</H2>
|
</H2>
|
||||||
</div>
|
</div>
|
||||||
<Box display="flex" mt="auto">
|
<Box display="flex" mt="auto">
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ const ID_CARD_DATA = 'idCardData'
|
||||||
|
|
||||||
const getAuthorizedStatus = (it, triggers, customRequests) => {
|
const getAuthorizedStatus = (it, triggers, customRequests) => {
|
||||||
const fields = R.concat(
|
const fields = R.concat(
|
||||||
['frontCamera', 'idCardData', 'idCardPhoto', 'usSsn', 'sanctions'],
|
['frontCamera', 'idCardData', 'idCardPhoto', 'email', 'usSsn', 'sanctions'],
|
||||||
R.map(ite => ite.id, customRequests)
|
R.map(ite => ite.id, customRequests)
|
||||||
)
|
)
|
||||||
const fieldsWithPathSuffix = ['frontCamera', 'idCardPhoto']
|
const fieldsWithPathSuffix = ['frontCamera', 'idCardPhoto']
|
||||||
|
|
@ -165,6 +165,7 @@ const requirementOptions = [
|
||||||
{ display: 'ID card image', code: 'idCardPhoto' },
|
{ display: 'ID card image', code: 'idCardPhoto' },
|
||||||
{ display: 'ID data', code: 'idCardData' },
|
{ display: 'ID data', code: 'idCardData' },
|
||||||
{ display: 'US SSN', code: 'usSsn' },
|
{ display: 'US SSN', code: 'usSsn' },
|
||||||
|
{ display: 'Email', code: 'email' },
|
||||||
{ display: 'Customer camera', code: 'frontCamera' }
|
{ display: 'Customer camera', code: 'frontCamera' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -430,6 +431,15 @@ const customerDataElements = {
|
||||||
editable: true
|
editable: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
email: [
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
label: 'Email',
|
||||||
|
component: TextInput,
|
||||||
|
size: 190,
|
||||||
|
editable: false
|
||||||
|
}
|
||||||
|
],
|
||||||
idCardPhoto: [{ name: 'idCardPhoto' }],
|
idCardPhoto: [{ name: 'idCardPhoto' }],
|
||||||
frontCamera: [{ name: 'frontCamera' }]
|
frontCamera: [{ name: 'frontCamera' }]
|
||||||
}
|
}
|
||||||
|
|
@ -460,6 +470,9 @@ const customerDataSchemas = {
|
||||||
}),
|
}),
|
||||||
frontCamera: Yup.object().shape({
|
frontCamera: Yup.object().shape({
|
||||||
frontCamera: Yup.mixed().required()
|
frontCamera: Yup.mixed().required()
|
||||||
|
}),
|
||||||
|
email: Yup.object().shape({
|
||||||
|
email: Yup.string().required()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,6 +499,13 @@ const requirementElements = {
|
||||||
initialValues: { usSsn: '' },
|
initialValues: { usSsn: '' },
|
||||||
saveType: 'customerData'
|
saveType: 'customerData'
|
||||||
},
|
},
|
||||||
|
email: {
|
||||||
|
schema: customerDataSchemas.email,
|
||||||
|
options: customerDataElements.email,
|
||||||
|
Component: ManualDataEntry,
|
||||||
|
initialValues: { email: '' },
|
||||||
|
saveType: 'customerData'
|
||||||
|
},
|
||||||
idCardPhoto: {
|
idCardPhoto: {
|
||||||
schema: customerDataSchemas.idCardPhoto,
|
schema: customerDataSchemas.idCardPhoto,
|
||||||
options: customerDataElements.idCardPhoto,
|
options: customerDataElements.idCardPhoto,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ const ThirdPartyProvider = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThirdPartySchema = Yup.object().shape({
|
const ThirdPartySchema = Yup.object().shape({
|
||||||
sms: Yup.string('The sms must be a string').required('The sms is required')
|
sms: Yup.string('SMS must be a string').required('SMS is required'),
|
||||||
|
email: Yup.string('Email must be a string').required('Email is required')
|
||||||
})
|
})
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
|
|
@ -46,14 +47,30 @@ const ThirdPartyProvider = () => {
|
||||||
valueProp: 'code',
|
valueProp: 'code',
|
||||||
labelProp: 'display'
|
labelProp: 'display'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
size: 'sm',
|
||||||
|
view: getDisplayName('email'),
|
||||||
|
width: 175,
|
||||||
|
input: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: filterOptions('email'),
|
||||||
|
valueProp: 'code',
|
||||||
|
labelProp: 'display'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
const values = {
|
||||||
|
sms: data.sms ?? 'twilio',
|
||||||
|
email: data.email ?? 'mailgun'
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableTable
|
<EditableTable
|
||||||
name="thirdParty"
|
name="thirdParty"
|
||||||
initialValues={{ sms: data.sms ?? 'twilio' }}
|
initialValues={values}
|
||||||
data={R.of({ sms: data.sms ?? 'twilio' })}
|
data={R.of(values)}
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
enableEdit
|
enableEdit
|
||||||
editWidth={174}
|
editWidth={174}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { secretTest } from './helper'
|
||||||
|
|
||||||
|
|
@ -11,26 +14,49 @@ export default {
|
||||||
title: 'Galoy (Wallet)',
|
title: 'Galoy (Wallet)',
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
code: 'apiKey',
|
code: 'apiSecret',
|
||||||
display: 'API Key',
|
display: 'API Secret',
|
||||||
component: TextInputFormik,
|
component: SecretInput
|
||||||
face: true,
|
},
|
||||||
long: true
|
{
|
||||||
|
code: 'environment',
|
||||||
|
display: 'Environment',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: [
|
||||||
|
{ code: 'main', display: 'prod' },
|
||||||
|
{ code: 'test', display: 'test' }
|
||||||
|
],
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'endpoint',
|
||||||
|
display: 'Endpoint',
|
||||||
|
component: TextInput
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'walletId',
|
code: 'walletId',
|
||||||
display: 'Wallet ID',
|
display: 'Wallet ID',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
return Yup.object().shape({
|
return Yup.object().shape({
|
||||||
apiKey: Yup.string('The API key must be a string')
|
apiSecret: Yup.string('The API Secret must be a string')
|
||||||
.max(200, 'The API key is too long')
|
.max(200, 'The API Secret is too long')
|
||||||
.required('The API key is required'),
|
.test(secretTest(account?.apiSecret)),
|
||||||
walletId: Yup.string('The wallet id must be a string')
|
walletId: Yup.string('The wallet id must be a string')
|
||||||
.max(100, 'The wallet id is too long')
|
.max(100, 'The wallet id is too long')
|
||||||
.test(secretTest(account?.walletId))
|
.test(secretTest(account?.walletId)),
|
||||||
|
environment: Yup.string('The environment must be a string')
|
||||||
|
.matches(/(main|test)/)
|
||||||
|
.required('The environment is required'),
|
||||||
|
endpoint: Yup.string('The endpoint must be a string')
|
||||||
|
.max(100, 'The endpoint is too long')
|
||||||
|
.required('The endpoint is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,17 @@ const CANCEL_CASH_IN_TRANSACTION = gql`
|
||||||
const getCryptoAmount = tx =>
|
const getCryptoAmount = tx =>
|
||||||
coinUtils.toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode).toNumber()
|
coinUtils.toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode).toNumber()
|
||||||
|
|
||||||
|
const getCryptoFeeAmount = tx => {
|
||||||
|
const feeAmount = coinUtils
|
||||||
|
.toUnit(new BigNumber(tx.fee), tx.cryptoCode)
|
||||||
|
.toNumber()
|
||||||
|
|
||||||
|
return new BigNumber(feeAmount)
|
||||||
|
.times(tx.rawTickerPrice)
|
||||||
|
.toNumber()
|
||||||
|
.toFixed(2, 1)
|
||||||
|
}
|
||||||
|
|
||||||
const formatAddress = (cryptoCode = '', address = '') =>
|
const formatAddress = (cryptoCode = '', address = '') =>
|
||||||
coinUtils.formatCryptoAddress(cryptoCode, address).replace(/(.{5})/g, '$1 ')
|
coinUtils.formatCryptoAddress(cryptoCode, address).replace(/(.{5})/g, '$1 ')
|
||||||
|
|
||||||
|
|
@ -129,6 +140,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
||||||
.minus(cashInFee)
|
.minus(cashInFee)
|
||||||
.toFixed(2, 1) // ROUND_DOWN
|
.toFixed(2, 1) // ROUND_DOWN
|
||||||
const crypto = getCryptoAmount(tx)
|
const crypto = getCryptoAmount(tx)
|
||||||
|
const cryptoFee = getCryptoFeeAmount(tx)
|
||||||
const exchangeRate = BigNumber(fiat)
|
const exchangeRate = BigNumber(fiat)
|
||||||
.div(crypto)
|
.div(crypto)
|
||||||
.toFixed(2, 1) // ROUND_DOWN
|
.toFixed(2, 1) // ROUND_DOWN
|
||||||
|
|
@ -369,6 +381,12 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{tx.txClass === 'cashIn' && (
|
||||||
|
<div className={classes.blockFee}>
|
||||||
|
<Label>Network Fee</Label>
|
||||||
|
{cryptoFee} {tx.fiatCode}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className={classes.sessionId}>
|
<div className={classes.sessionId}>
|
||||||
<Label>Session ID</Label>
|
<Label>Session ID</Label>
|
||||||
<CopyToClipboard>{tx.id}</CopyToClipboard>
|
<CopyToClipboard>{tx.id}</CopyToClipboard>
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@ export default {
|
||||||
transactionId: {
|
transactionId: {
|
||||||
width: 280
|
width: 280
|
||||||
},
|
},
|
||||||
|
blockFee: {
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
sessionId: {
|
sessionId: {
|
||||||
width: 215
|
width: 215
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@ const GET_TRANSACTIONS = gql`
|
||||||
errorCode
|
errorCode
|
||||||
deviceId
|
deviceId
|
||||||
fiat
|
fiat
|
||||||
|
fee
|
||||||
cashInFee
|
cashInFee
|
||||||
fiatCode
|
fiatCode
|
||||||
cryptoAtoms
|
cryptoAtoms
|
||||||
|
|
@ -116,6 +117,7 @@ const GET_TRANSACTIONS = gql`
|
||||||
customerFrontCameraPath
|
customerFrontCameraPath
|
||||||
txCustomerPhotoPath
|
txCustomerPhotoPath
|
||||||
customerPhone
|
customerPhone
|
||||||
|
customerEmail
|
||||||
discount
|
discount
|
||||||
customerId
|
customerId
|
||||||
isAnonymous
|
isAnonymous
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ const TriggerView = ({
|
||||||
config,
|
config,
|
||||||
toggleWizard,
|
toggleWizard,
|
||||||
addNewTriger,
|
addNewTriger,
|
||||||
customInfoRequests
|
customInfoRequests,
|
||||||
|
emailAuth
|
||||||
}) => {
|
}) => {
|
||||||
const currency = R.path(['fiatCurrency'])(
|
const currency = R.path(['fiatCurrency'])(
|
||||||
fromNamespace(namespaces.LOCALE)(config)
|
fromNamespace(namespaces.LOCALE)(config)
|
||||||
|
|
@ -77,6 +78,7 @@ const TriggerView = ({
|
||||||
save={add}
|
save={add}
|
||||||
onClose={() => toggleWizard(true)}
|
onClose={() => toggleWizard(true)}
|
||||||
customInfoRequests={customInfoRequests}
|
customInfoRequests={customInfoRequests}
|
||||||
|
emailAuth={emailAuth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{R.isEmpty(triggers) && (
|
{R.isEmpty(triggers) && (
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const GET_CUSTOM_REQUESTS = gql`
|
||||||
const Triggers = () => {
|
const Triggers = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [wizardType, setWizard] = useState(false)
|
const [wizardType, setWizard] = useState(false)
|
||||||
const { data, loading: configLoading } = useQuery(GET_CONFIG)
|
const { data, loading: configLoading, refetch } = useQuery(GET_CONFIG)
|
||||||
const { data: customInfoReqData, loading: customInfoLoading } = useQuery(
|
const { data: customInfoReqData, loading: customInfoLoading } = useQuery(
|
||||||
GET_CUSTOM_REQUESTS
|
GET_CUSTOM_REQUESTS
|
||||||
)
|
)
|
||||||
|
|
@ -72,6 +72,8 @@ const Triggers = () => {
|
||||||
const enabledCustomInfoRequests = R.filter(R.propEq('enabled', true))(
|
const enabledCustomInfoRequests = R.filter(R.propEq('enabled', true))(
|
||||||
customInfoRequests
|
customInfoRequests
|
||||||
)
|
)
|
||||||
|
const emailAuth =
|
||||||
|
data?.config?.triggersConfig_customerAuthentication === 'EMAIL'
|
||||||
|
|
||||||
const triggers = fromServer(data?.config?.triggers ?? [])
|
const triggers = fromServer(data?.config?.triggers ?? [])
|
||||||
const complianceConfig =
|
const complianceConfig =
|
||||||
|
|
@ -141,6 +143,7 @@ const Triggers = () => {
|
||||||
inverseIcon: ReverseSettingsIcon,
|
inverseIcon: ReverseSettingsIcon,
|
||||||
forceDisable: !(subMenu === 'advancedSettings'),
|
forceDisable: !(subMenu === 'advancedSettings'),
|
||||||
toggle: show => {
|
toggle: show => {
|
||||||
|
refetch()
|
||||||
setSubMenu(show ? 'advancedSettings' : false)
|
setSubMenu(show ? 'advancedSettings' : false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -150,6 +153,7 @@ const Triggers = () => {
|
||||||
inverseIcon: ReverseCustomInfoIcon,
|
inverseIcon: ReverseCustomInfoIcon,
|
||||||
forceDisable: !(subMenu === 'customInfoRequests'),
|
forceDisable: !(subMenu === 'customInfoRequests'),
|
||||||
toggle: show => {
|
toggle: show => {
|
||||||
|
refetch()
|
||||||
setSubMenu(show ? 'customInfoRequests' : false)
|
setSubMenu(show ? 'customInfoRequests' : false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -216,6 +220,7 @@ const Triggers = () => {
|
||||||
toggleWizard={toggleWizard('newTrigger')}
|
toggleWizard={toggleWizard('newTrigger')}
|
||||||
addNewTriger={addNewTriger}
|
addNewTriger={addNewTriger}
|
||||||
customInfoRequests={enabledCustomInfoRequests}
|
customInfoRequests={enabledCustomInfoRequests}
|
||||||
|
emailAuth={emailAuth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!loading && subMenu === 'advancedSettings' && (
|
{!loading && subMenu === 'advancedSettings' && (
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,14 @@ const styles = {
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const getStep = (step, currency, customInfoRequests) => {
|
const getStep = (step, currency, customInfoRequests, emailAuth) => {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
// case 1:
|
// case 1:
|
||||||
// return txDirection
|
// return txDirection
|
||||||
case 1:
|
case 1:
|
||||||
return type(currency)
|
return type(currency)
|
||||||
case 2:
|
case 2:
|
||||||
return requirements(customInfoRequests)
|
return requirements(customInfoRequests, emailAuth)
|
||||||
default:
|
default:
|
||||||
return Fragment
|
return Fragment
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +138,8 @@ const getTypeText = (config, currency, classes) => {
|
||||||
|
|
||||||
const getRequirementText = (config, classes) => {
|
const getRequirementText = (config, classes) => {
|
||||||
switch (config.requirement?.requirement) {
|
switch (config.requirement?.requirement) {
|
||||||
|
case 'email':
|
||||||
|
return <>asked to enter code provided through email verification</>
|
||||||
case 'sms':
|
case 'sms':
|
||||||
return <>asked to enter code provided through SMS verification</>
|
return <>asked to enter code provided through SMS verification</>
|
||||||
case 'idCardPhoto':
|
case 'idCardPhoto':
|
||||||
|
|
@ -202,7 +204,14 @@ const GetValues = ({ setValues }) => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wizard = ({ onClose, save, error, currency, customInfoRequests }) => {
|
const Wizard = ({
|
||||||
|
onClose,
|
||||||
|
save,
|
||||||
|
error,
|
||||||
|
currency,
|
||||||
|
customInfoRequests,
|
||||||
|
emailAuth
|
||||||
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [liveValues, setLiveValues] = useState({})
|
const [liveValues, setLiveValues] = useState({})
|
||||||
|
|
@ -211,7 +220,7 @@ const Wizard = ({ onClose, save, error, currency, customInfoRequests }) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const isLastStep = step === LAST_STEP
|
const isLastStep = step === LAST_STEP
|
||||||
const stepOptions = getStep(step, currency, customInfoRequests)
|
const stepOptions = getStep(step, currency, customInfoRequests, emailAuth)
|
||||||
|
|
||||||
const onContinue = async it => {
|
const onContinue = async it => {
|
||||||
const newConfig = R.merge(config, stepOptions.schema.cast(it))
|
const newConfig = R.merge(config, stepOptions.schema.cast(it))
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,21 @@ const getDefaultSettings = () => {
|
||||||
labelProp: 'display',
|
labelProp: 'display',
|
||||||
valueProp: 'code'
|
valueProp: 'code'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customerAuthentication',
|
||||||
|
header: 'Customer Auth',
|
||||||
|
width: 196,
|
||||||
|
size: 'sm',
|
||||||
|
input: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: [
|
||||||
|
{ code: 'SMS', display: 'SMS' },
|
||||||
|
{ code: 'EMAIL', display: 'EMAIL' }
|
||||||
|
],
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +159,8 @@ const getOverrides = customInfoRequests => {
|
||||||
const defaults = [
|
const defaults = [
|
||||||
{
|
{
|
||||||
expirationTime: 'Forever',
|
expirationTime: 'Forever',
|
||||||
automation: 'Automatic'
|
automation: 'Automatic',
|
||||||
|
customerAuth: 'SMS'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,7 @@ const requirementSchema = Yup.object()
|
||||||
|
|
||||||
const requirementOptions = [
|
const requirementOptions = [
|
||||||
{ display: 'SMS verification', code: 'sms' },
|
{ display: 'SMS verification', code: 'sms' },
|
||||||
|
{ display: 'Email verification', code: 'email' },
|
||||||
{ display: 'ID card image', code: 'idCardPhoto' },
|
{ display: 'ID card image', code: 'idCardPhoto' },
|
||||||
{ display: 'ID data', code: 'idCardData' },
|
{ display: 'ID data', code: 'idCardData' },
|
||||||
{ display: 'Customer camera', code: 'facephoto' },
|
{ display: 'Customer camera', code: 'facephoto' },
|
||||||
|
|
@ -544,7 +545,7 @@ const hasCustomRequirementError = (errors, touched, values) =>
|
||||||
(!values.requirement?.customInfoRequestId ||
|
(!values.requirement?.customInfoRequestId ||
|
||||||
!R.isNil(values.requirement?.customInfoRequestId))
|
!R.isNil(values.requirement?.customInfoRequestId))
|
||||||
|
|
||||||
const Requirement = ({ customInfoRequests }) => {
|
const Requirement = ({ customInfoRequests, emailAuth }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const {
|
const {
|
||||||
touched,
|
touched,
|
||||||
|
|
@ -567,9 +568,11 @@ const Requirement = ({ customInfoRequests }) => {
|
||||||
display: 'Custom information requirement',
|
display: 'Custom information requirement',
|
||||||
code: 'custom'
|
code: 'custom'
|
||||||
}
|
}
|
||||||
|
const itemToRemove = emailAuth ? 'sms' : 'email'
|
||||||
|
const reqOptions = requirementOptions.filter(it => it.code !== itemToRemove)
|
||||||
const options = enableCustomRequirement
|
const options = enableCustomRequirement
|
||||||
? [...requirementOptions, customInfoOption]
|
? [...reqOptions, customInfoOption]
|
||||||
: [...requirementOptions]
|
: [...reqOptions]
|
||||||
const titleClass = {
|
const titleClass = {
|
||||||
[classes.error]:
|
[classes.error]:
|
||||||
(!!errors.requirement && !isSuspend && !isCustom) ||
|
(!!errors.requirement && !isSuspend && !isCustom) ||
|
||||||
|
|
@ -621,11 +624,11 @@ const Requirement = ({ customInfoRequests }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const requirements = customInfoRequests => ({
|
const requirements = (customInfoRequests, emailAuth) => ({
|
||||||
schema: requirementSchema,
|
schema: requirementSchema,
|
||||||
options: requirementOptions,
|
options: requirementOptions,
|
||||||
Component: Requirement,
|
Component: Requirement,
|
||||||
props: { customInfoRequests },
|
props: { customInfoRequests, emailAuth },
|
||||||
hasRequirementError: hasRequirementError,
|
hasRequirementError: hasRequirementError,
|
||||||
hasCustomRequirementError: hasCustomRequirementError,
|
hasCustomRequirementError: hasCustomRequirementError,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,17 @@ export default {
|
||||||
},
|
},
|
||||||
polymer: false
|
polymer: false
|
||||||
},
|
},
|
||||||
|
XCD: {
|
||||||
|
thickness: 0x0c,
|
||||||
|
lengths: {
|
||||||
|
5: [0x9b, 0x87],
|
||||||
|
10: [0x9b, 0x87],
|
||||||
|
20: [0x9b, 0x87],
|
||||||
|
50: [0x9b, 0x87],
|
||||||
|
100: [0x9b, 0x87]
|
||||||
|
},
|
||||||
|
polymer: true
|
||||||
|
},
|
||||||
ZAR: {
|
ZAR: {
|
||||||
thickness: 0x0c,
|
thickness: 0x0c,
|
||||||
lengths: {
|
lengths: {
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,13 @@ const displayName = ({
|
||||||
isAnonymous,
|
isAnonymous,
|
||||||
customerName,
|
customerName,
|
||||||
customerIdCardData,
|
customerIdCardData,
|
||||||
customerPhone
|
customerPhone,
|
||||||
|
customerEmail
|
||||||
}) =>
|
}) =>
|
||||||
isAnonymous
|
isAnonymous
|
||||||
? 'Anonymous'
|
? 'Anonymous'
|
||||||
: customerName || R.defaultTo(customerPhone, formatName(customerIdCardData))
|
: customerName ||
|
||||||
|
customerEmail ||
|
||||||
|
R.defaultTo(customerPhone, formatName(customerIdCardData))
|
||||||
|
|
||||||
export { displayName, formatFullName, formatName }
|
export { displayName, formatFullName, formatName }
|
||||||
|
|
|
||||||
9210
package-lock.json
generated
9210
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
|
@ -6,11 +6,17 @@
|
||||||
"license": "./LICENSE",
|
"license": "./LICENSE",
|
||||||
"author": "Lamassu (https://lamassu.is)",
|
"author": "Lamassu (https://lamassu.is)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@bitgo/sdk-api": "1.33.0",
|
||||||
|
"@bitgo/sdk-coin-bch": "1.5.22",
|
||||||
|
"@bitgo/sdk-coin-btc": "1.7.22",
|
||||||
|
"@bitgo/sdk-coin-dash": "1.5.22",
|
||||||
|
"@bitgo/sdk-coin-ltc": "2.2.22",
|
||||||
|
"@bitgo/sdk-coin-zec": "1.5.22",
|
||||||
"@ethereumjs/common": "^2.6.4",
|
"@ethereumjs/common": "^2.6.4",
|
||||||
"@ethereumjs/tx": "^3.5.1",
|
"@ethereumjs/tx": "^3.5.1",
|
||||||
"@graphql-tools/merge": "^6.2.5",
|
"@graphql-tools/merge": "^6.2.5",
|
||||||
"@haensl/subset-sum": "^3.0.5",
|
"@haensl/subset-sum": "^3.0.5",
|
||||||
"@lamassu/coins": "v1.4.0-beta.4",
|
"@lamassu/coins": "v1.4.3",
|
||||||
"@node-lightning/invoice": "0.28.0",
|
"@node-lightning/invoice": "0.28.0",
|
||||||
"@simplewebauthn/server": "^3.0.0",
|
"@simplewebauthn/server": "^3.0.0",
|
||||||
"@vonage/auth": "^1.5.0",
|
"@vonage/auth": "^1.5.0",
|
||||||
|
|
@ -24,13 +30,6 @@
|
||||||
"bchaddrjs": "^0.3.0",
|
"bchaddrjs": "^0.3.0",
|
||||||
"bignumber.js": "9.0.1",
|
"bignumber.js": "9.0.1",
|
||||||
"bip39": "^2.3.1",
|
"bip39": "^2.3.1",
|
||||||
"@bitgo/sdk-api": "1.21.0",
|
|
||||||
"@bitgo/sdk-coin-bch": "1.5.10",
|
|
||||||
"@bitgo/sdk-coin-btc": "1.7.10",
|
|
||||||
"@bitgo/sdk-coin-dash": "1.5.10",
|
|
||||||
"@bitgo/sdk-coin-ltc": "2.2.10",
|
|
||||||
"@bitgo/sdk-coin-zec": "1.5.10",
|
|
||||||
"@bitgo/utxo-lib": "9.13.0",
|
|
||||||
"ccxt": "2.9.16",
|
"ccxt": "2.9.16",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"connect-pg-simple": "^6.2.1",
|
"connect-pg-simple": "^6.2.1",
|
||||||
|
|
@ -40,6 +39,7 @@
|
||||||
"dataloader": "^2.0.0",
|
"dataloader": "^2.0.0",
|
||||||
"date-fns": "^2.26.0",
|
"date-fns": "^2.26.0",
|
||||||
"date-fns-tz": "^1.1.6",
|
"date-fns-tz": "^1.1.6",
|
||||||
|
"dateformat": "^3.0.3",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"ethereumjs-tx": "^1.3.3",
|
"ethereumjs-tx": "^1.3.3",
|
||||||
"ethereumjs-util": "^5.2.0",
|
"ethereumjs-util": "^5.2.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue