diff --git a/lib/compliance-triggers.js b/lib/compliance-triggers.js index da4191a3..dcbe6ab2 100644 --- a/lib/compliance-triggers.js +++ b/lib/compliance-triggers.js @@ -26,4 +26,9 @@ const hasPhone = hasRequirement('sms') const hasFacephoto = hasRequirement('facephoto') const hasIdScan = hasRequirement('idCardData') -module.exports = { getBackwardsCompatibleTriggers, hasSanctions, maxDaysThreshold, getCashLimit, hasPhone, hasFacephoto, hasIdScan } \ No newline at end of file +const AUTH_METHODS = { + SMS: 'SMS', + EMAIL: 'EMAIL' +} + +module.exports = { getBackwardsCompatibleTriggers, hasSanctions, maxDaysThreshold, getCashLimit, hasPhone, hasFacephoto, hasIdScan, AUTH_METHODS } \ No newline at end of file diff --git a/lib/customers.js b/lib/customers.js index e855070d..012c042e 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -6,13 +6,10 @@ const makeDir = require('make-dir') const path = require('path') const fs = require('fs') const util = require('util') -const { sub, differenceInHours } = require('date-fns/fp') const db = require('./db') -const BN = require('./bn') const anonymous = require('../lib/constants').anonymousCustomer const complianceOverrides = require('./compliance_overrides') -const users = require('./users') const writeFile = util.promisify(fs.writeFile) const notifierQueries = require('./notifier/queries') const notifierUtils = require('./notifier/utils') @@ -43,6 +40,12 @@ function add (customer) { .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 * Phone numbers are unique per customer @@ -60,6 +63,12 @@ function get (phone) { .then(camelize) } +function getWithEmail (email) { + const sql = 'select * from customers where email=$1' + return db.oneOrNone(sql, [email]) + .then(camelize) +} + /** * Update customer record * @@ -308,7 +317,7 @@ const updateSubscriberData = (customerId, data, userToken) => { * * Used for the machine. */ -function getById (id, userToken) { +function getById (id) { const sql = 'select * from customers where id=$1' return db.oneOrNone(sql, [id]) .then(assignCustomerData) @@ -349,6 +358,7 @@ function camelizeDeep (customer) { function getComplianceTypes () { return [ 'sms', + 'email', 'id_card_data', 'id_card_photo', 'front_camera', @@ -482,7 +492,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null) 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, - 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, 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 @@ -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, c.suspended_until > NOW() AS is_suspended, 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, - 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, 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, @@ -540,7 +550,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null) function getCustomerById (id) { 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, - 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, 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 @@ -549,7 +559,7 @@ function getCustomerById (id) { greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended, c.suspended_until > now() AS is_suspended, 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.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, @@ -912,7 +922,9 @@ function disableTestCustomer (customerId) { module.exports = { add, + addWithEmail, get, + getWithEmail, batch, getCustomersList, getCustomerById, @@ -930,7 +942,5 @@ module.exports = { updateEditedPhoto, updateTxCustomerPhoto, enableTestCustomer, - disableTestCustomer, - selectLatestData, - getEditedData + disableTestCustomer } diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js index c442cc95..66b60061 100644 --- a/lib/graphql/resolvers.js +++ b/lib/graphql/resolvers.js @@ -115,6 +115,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings configManager.getOperatorInfo(settings.config), configManager.getReceipt(settings.config), !!configManager.getCashOut(deviceId, settings.config).active, + configManager.getCustomerAuthenticationMethod(settings.config) ]) .then(([ enablePaperWalletOnly, @@ -125,6 +126,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings operatorInfo, receiptInfo, twoWayMode, + customerAuthentication, ]) => (currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ? null : @@ -141,6 +143,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings }, machineInfo: { deviceId, deviceName }, twoWayMode, + customerAuthentication, speedtestFiles, urlsToPing, }), diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 3b4655ea..f2c4e743 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -123,6 +123,11 @@ type Terms { details: TermsDetails } +enum CustomerAuthentication { + EMAIL + SMS +} + type StaticConfig { configVersion: Int! @@ -132,6 +137,7 @@ type StaticConfig { serverVersion: String! timezone: Int! twoWayMode: Boolean! + customerAuthentication: CustomerAuthentication! localeInfo: LocaleInfo! operatorInfo: OperatorInfo diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index 5af0c458..278491ad 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -12,6 +12,7 @@ const typeDef = gql` frontCameraAt: Date frontCameraOverride: String phone: String + email: String isAnonymous: Boolean smsOverride: String idCardData: JSONObject diff --git a/lib/new-config-manager.js b/lib/new-config-manager.js index 65c7966d..efb84450 100644 --- a/lib/new-config-manager.js +++ b/lib/new-config-manager.js @@ -1,3 +1,5 @@ +const {AUTH_METHODS} = require('./compliance-triggers') + const _ = require('lodash/fp') const { validate } = require('uuid') @@ -120,6 +122,10 @@ const getGlobalNotifications = config => getNotifications(null, null, config) const getTriggers = _.get('triggers') +function getCustomerAuthenticationMethod(config) { + return _.get('triggersConfig_customerAuthentication')(config) +} + /* `customInfoRequests` is the result of a call to `getCustomInfoRequests` */ const getTriggersAutomation = (customInfoRequests, config, oldFormat = false) => { return customInfoRequests @@ -193,4 +199,5 @@ module.exports = { getCryptosFromWalletNamespace, getCryptoUnits, setTermsConditions, + getCustomerAuthenticationMethod, } diff --git a/lib/plugins.js b/lib/plugins.js index 6961a6d4..b7d05d2d 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -779,6 +779,27 @@ function plugins (settings, deviceId) { }) } + function getEmailCode (toEmail) { + const notifications = configManager.getNotifications(settings.config) + + const code = notifications.thirdParty_email === 'mock-email' + ? '123' + : randomCode() + + const req = { + email: { + toEmail, + subject: 'Your cryptomat code', + text: `Your cryptomat code: ${code}` + } + } + + console.log(code) + return Promise.resolve(code) + // return sms.sendMessage(settings, rec) + // .then(() => code) + } + function sweepHdRow (row) { const txId = row.id const cryptoCode = row.crypto_code @@ -862,6 +883,7 @@ function plugins (settings, deviceId) { isZeroConf, getStatus, getPhoneCode, + getEmailCode, executeTrades, pong, clearOldLogs, diff --git a/lib/plugins/email/mailgun/mailgun.js b/lib/plugins/email/mailgun/mailgun.js index 357baa20..e4f602a3 100644 --- a/lib/plugins/email/mailgun/mailgun.js +++ b/lib/plugins/email/mailgun/mailgun.js @@ -4,10 +4,11 @@ const NAME = 'Mailgun' function sendMessage ({apiKey, domain, fromEmail, toEmail}, rec) { const mailgun = Mailgun({apiKey, domain}) + const to = req.email.toEmail ?? toEmail const emailData = { from: `Lamassu Server ${fromEmail}`, - to: toEmail, + to, subject: rec.email.subject, text: rec.email.body } diff --git a/lib/route-helpers.js b/lib/route-helpers.js index b382b056..91cf5d5f 100644 --- a/lib/route-helpers.js +++ b/lib/route-helpers.js @@ -45,12 +45,12 @@ function toCashOutTx (row) { return _.set('direction', 'cashOut', newObj) } -function fetchPhoneTx (phone) { +function fetchEmailOrPhoneTx (data, type) { 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` - const values = [phone, false, TRANSACTION_EXPIRATION] + const values = [data, false, TRANSACTION_EXPIRATION] return db.any(sql, values) .then(_.map(toCashOutTx)) @@ -72,6 +72,13 @@ function fetchPhoneTx (phone) { throw httpError('No transactions', 404) }) } +function fetchEmailTx (email) { + return fetchEmailOrPhoneTx(email, 'email') +} + +function fetchPhoneTx (phone) { + return fetchEmailOrPhoneTx(phone, 'phone') +} function fetchStatusTx (txId, status) { const sql = 'select * from cash_out_txs where id=$1' @@ -88,6 +95,7 @@ function fetchStatusTx (txId, status) { module.exports = { stateChange, fetchPhoneTx, + fetchEmailTx, fetchStatusTx, httpError } diff --git a/lib/routes.js b/lib/routes.js index 8d248413..a28ce062 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -77,7 +77,10 @@ app.use('/verify_user', verifyUserRoutes) app.use('/verify_transaction', verifyTxRoutes) 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('/customer', customerRoutes) app.use('/tx', txRoutes) diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 0f880a10..3e7c4a98 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -20,6 +20,9 @@ const machineLoader = require('../machine-loader') const { loadLatestConfig } = require('../new-settings-loader') const customInfoRequestQueries = require('../new-admin/services/customInfoRequests') const T = require('../time') +const plugins = require('../plugins') +const Tx = require('../tx') +const loyalty = require('../loyalty') function updateCustomerCustomInfoRequest (customerId, patch, req, res) { 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/sanctions', triggerSanctions) router.patch('/:id/block', triggerBlock) @@ -192,5 +259,7 @@ router.patch('/:id/suspend', triggerSuspend) router.patch('/:id/photos/idcarddata', updateIdCardData) router.patch('/:id/:txId/photos/customerphoto', updateTxCustomerPhoto) router.post('/:id/smsreceipt', sendSmsReceipt) +router.post('/phone_code', getOrAddCustomerPhone) +router.post('/email_code', getOrAddCustomerEmail) module.exports = router diff --git a/lib/routes/txRoutes.js b/lib/routes/txRoutes.js index cfda6428..a3261147 100644 --- a/lib/routes/txRoutes.js +++ b/lib/routes/txRoutes.js @@ -66,8 +66,19 @@ function getPhoneTx (req, res, next) { 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.get('/:id', getTx) router.get('/', getPhoneTx) +router.get('/', getEmailTx) -module.exports = { postTx, getTx, getPhoneTx, router } +module.exports = { postTx, getTx, getPhoneTx, getEmailTx, router } diff --git a/migrations/1700123461281-customer-email.js b/migrations/1700123461281-customer-email.js new file mode 100644 index 00000000..58cd358d --- /dev/null +++ b/migrations/1700123461281-customer-email.js @@ -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() +} diff --git a/migrations/1700123461282-customer-auth-advanced-trigger.js b/migrations/1700123461282-customer-auth-advanced-trigger.js new file mode 100644 index 00000000..02e61f3f --- /dev/null +++ b/migrations/1700123461282-customer-auth-advanced-trigger.js @@ -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() +} diff --git a/migrations/1700123461283-phone-on-tx.js b/migrations/1700123461283-phone-on-tx.js new file mode 100644 index 00000000..1e8833f1 --- /dev/null +++ b/migrations/1700123461283-phone-on-tx.js @@ -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() +} diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index cbc79296..0ee9f7ae 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -57,6 +57,7 @@ const GET_CUSTOMER = gql` frontCameraAt frontCameraOverride phone + email isAnonymous smsOverride idCardData @@ -132,6 +133,7 @@ const SET_CUSTOMER = gql` frontCameraPath frontCameraOverride phone + email smsOverride idCardData idCardDataOverride @@ -516,6 +518,8 @@ const CustomerProfile = memo(() => { })) ?? [] const classes = useStyles() + const email = R.path(['email'])(customerData) + const phone = R.path(['phone'])(customerData) return ( <> @@ -532,10 +536,9 @@ const CustomerProfile = memo(() => { {name.length ? name - : getFormattedPhone( - R.path(['phone'])(customerData), - locale.country - )} + : email?.length + ? email + : getFormattedPhone(phone, locale.country)}
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js index ed8c8ae6..007cd01e 100644 --- a/new-lamassu-admin/src/pages/Customers/Customers.js +++ b/new-lamassu-admin/src/pages/Customers/Customers.js @@ -39,6 +39,7 @@ const GET_CUSTOMERS = gql` id idCardData phone + email totalTxs totalSpent lastActive diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.js b/new-lamassu-admin/src/pages/Customers/CustomersList.js index 9e01fd0a..e9832f89 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomersList.js +++ b/new-lamassu-admin/src/pages/Customers/CustomersList.js @@ -25,9 +25,9 @@ const CustomersList = ({ const elements = [ { - header: 'Phone', + header: 'Phone/email', width: 199, - view: it => getFormattedPhone(it.phone, locale.country) + view: it => `${getFormattedPhone(it.phone, locale.country)} ${it.email}` }, { header: 'Name', diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js b/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js index a03728f4..738ebd3b 100644 --- a/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js +++ b/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js @@ -17,6 +17,9 @@ const CustomerDetails = memo(({ customer, photosData, locale, timezone }) => { const idNumber = R.path(['idCardData', 'documentNumber'])(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 = [ { @@ -40,7 +43,12 @@ const CustomerDetails = memo(({ customer, photosData, locale, timezone }) => { value: usSsn }) - const name = getName(customer) + if (email) + elements.push({ + header: 'Email', + size: 190, + value: email + }) return ( @@ -51,7 +59,9 @@ const CustomerDetails = memo(({ customer, photosData, locale, timezone }) => {

{name.length ? name - : getFormattedPhone(R.path(['phone'])(customer), locale.country)} + : email?.length + ? email + : getFormattedPhone(phone, locale.country)}

diff --git a/new-lamassu-admin/src/pages/Triggers/Wizard.js b/new-lamassu-admin/src/pages/Triggers/Wizard.js index b78fc5fa..2dd80749 100644 --- a/new-lamassu-admin/src/pages/Triggers/Wizard.js +++ b/new-lamassu-admin/src/pages/Triggers/Wizard.js @@ -138,6 +138,8 @@ const getTypeText = (config, currency, classes) => { const getRequirementText = (config, classes) => { switch (config.requirement?.requirement) { + case 'email': + return <>asked to enter code provided through email verification case 'sms': return <>asked to enter code provided through SMS verification case 'idCardPhoto': diff --git a/new-lamassu-admin/src/pages/Triggers/components/helper.js b/new-lamassu-admin/src/pages/Triggers/components/helper.js index 4fb30f35..1ab870b8 100644 --- a/new-lamassu-admin/src/pages/Triggers/components/helper.js +++ b/new-lamassu-admin/src/pages/Triggers/components/helper.js @@ -94,6 +94,21 @@ const getDefaultSettings = () => { labelProp: 'display', 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 = [ { expirationTime: 'Forever', - automation: 'Automatic' + automation: 'Automatic', + customerAuth: 'SMS' } ] diff --git a/new-lamassu-admin/src/pages/Triggers/helper.js b/new-lamassu-admin/src/pages/Triggers/helper.js index 1e57e2b5..02b386e1 100644 --- a/new-lamassu-admin/src/pages/Triggers/helper.js +++ b/new-lamassu-admin/src/pages/Triggers/helper.js @@ -522,6 +522,7 @@ const requirementSchema = Yup.object() const requirementOptions = [ { display: 'SMS verification', code: 'sms' }, + { display: 'Email verification', code: 'email' }, { display: 'ID card image', code: 'idCardPhoto' }, { display: 'ID data', code: 'idCardData' }, { display: 'Customer camera', code: 'facephoto' },