diff --git a/lib/customers.js b/lib/customers.js
index 0f68f1ed1..fdd5c6105 100644
--- a/lib/customers.js
+++ b/lib/customers.js
@@ -55,7 +55,7 @@ function add (customer) {
* @returns {object} Customer
*/
function get (phone) {
- const sql = 'select * from customers where phone=$1'
+ const sql = 'select * from customers where phone=$1 and enabled'
return db.oneOrNone(sql, [phone])
.then(camelize)
}
@@ -459,7 +459,7 @@ function addComplianceOverrides (id, customer, userToken) {
*/
function batch () {
const sql = `select * from customers
- where id != $1
+ where id != $1 and enabled
order by created desc limit $2`
return db.any(sql, [ anonymous.uuid, NUM_RESULTS ])
.then(customers => Promise.all(_.map(customer => {
@@ -493,7 +493,8 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
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.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions,
- 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,
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs,
coalesce(sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (partition by c.id), 0) AS total_spent, ccf.custom_fields
@@ -513,11 +514,12 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
GROUP BY customer_notes.customer_id
) cn ON c.id = cn.customer_id
WHERE c.id != $2
+ AND c.enabled
) AS cl WHERE rn = 1
AND ($4 IS NULL OR phone = $4)
- 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 ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7)
+ 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 ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7)
limit $3`
return db.any(sql, [ passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ])
.then(customers => Promise.all(_.map(customer =>
@@ -550,7 +552,8 @@ function getCustomerById (id) {
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.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, c.enabled,
+ 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,
sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields
@@ -570,6 +573,7 @@ function getCustomerById (id) {
GROUP BY customer_notes.customer_id
) cn ON c.id = cn.customer_id
WHERE c.id = $2
+ AND c.enabled
) AS cl WHERE rn = 1`
return db.oneOrNone(sql, [passableErrorCodes, id])
.then(assignCustomerData)
@@ -909,6 +913,11 @@ function disableTestCustomer (customerId) {
return db.none(sql, [customerId])
}
+function deleteCustomer (customerId) {
+ const sql = `UPDATE customers SET enabled=false WHERE id=$1 AND is_test_customer`
+ return db.none(sql, [customerId])
+}
+
module.exports = {
add,
get,
@@ -931,5 +940,6 @@ module.exports = {
enableTestCustomer,
disableTestCustomer,
selectLatestData,
- getEditedData
+ getEditedData,
+ deleteCustomer
}
diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js
index 19b54f790..9d8dcdbcc 100644
--- a/lib/new-admin/graphql/resolvers/customer.resolver.js
+++ b/lib/new-admin/graphql/resolvers/customer.resolver.js
@@ -54,7 +54,9 @@ const resolvers = {
enableTestCustomer: (...[, { customerId }]) =>
customers.enableTestCustomer(customerId),
disableTestCustomer: (...[, { customerId }]) =>
- customers.disableTestCustomer(customerId)
+ customers.disableTestCustomer(customerId),
+ deleteCustomer: (...[, { customerId }]) =>
+ customers.deleteCustomer(customerId)
}
}
diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js
index 5af0c4582..4a8e6aeaf 100644
--- a/lib/new-admin/graphql/types/customer.type.js
+++ b/lib/new-admin/graphql/types/customer.type.js
@@ -111,6 +111,7 @@ const typeDef = gql`
createCustomer(phoneNumber: String): Customer @auth
enableTestCustomer(customerId: ID!): Boolean @auth
disableTestCustomer(customerId: ID!): Boolean @auth
+ deleteCustomer(customerId: ID!): Boolean @auth(requires: [SUPERUSER])
}
`
diff --git a/migrations/1649353351891-delete-test-customers.js b/migrations/1649353351891-delete-test-customers.js
new file mode 100644
index 000000000..e01f516be
--- /dev/null
+++ b/migrations/1649353351891-delete-test-customers.js
@@ -0,0 +1,15 @@
+var db = require('./db')
+
+exports.up = function (next) {
+ var sql = [
+ `ALTER TABLE customers ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT true`,
+ `ALTER TABLE customers DROP CONSTRAINT customers_phone_key`,
+ `CREATE UNIQUE INDEX customers_phone_key ON customers (phone) WHERE enabled`
+ ]
+
+ 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 cbc792968..b1795d379 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -10,9 +10,11 @@ import {
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import gql from 'graphql-tag'
import * as R from 'ramda'
-import React, { memo, useState } from 'react'
+import React, { memo, useState, useContext } from 'react'
import { useHistory, useParams } from 'react-router-dom'
+import AppContext from 'src/AppContext'
+import { ConfirmDialog } from 'src/components/ConfirmDialog'
import ErrorMessage from 'src/components/ErrorMessage'
import { Button, IconButton, ActionButton } from 'src/components/buttons'
import { Switch } from 'src/components/inputs'
@@ -22,6 +24,8 @@ import {
OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard'
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
+import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
+import { ReactComponent as DeleteIconReversedIcon } from 'src/styling/icons/action/delete/white.svg'
import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/button/authorize/white.svg'
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg'
import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/block/white.svg'
@@ -260,6 +264,12 @@ const DISABLE_TEST_CUSTOMER = gql`
}
`
+const DELETE_CUSTOMER = gql`
+ mutation deleteCustomer($customerId: ID!) {
+ deleteCustomer(customerId: $customerId)
+ }
+`
+
const GET_DATA = gql`
query getData {
config
@@ -289,10 +299,12 @@ const GET_ACTIVE_CUSTOM_REQUESTS = gql`
const CustomerProfile = memo(() => {
const history = useHistory()
+ const { userData } = useContext(AppContext)
const [retrieve, setRetrieve] = useState(false)
const [showCompliance, setShowCompliance] = useState(false)
const [wizard, setWizard] = useState(false)
+ const [toDelete, setToDelete] = useState(false)
const [error, setError] = useState(null)
const [clickedItem, setClickedItem] = useState('overview')
const { id: customerId } = useParams()
@@ -395,6 +407,11 @@ const CustomerProfile = memo(() => {
onCompleted: () => getCustomer()
})
+ const [deleteCustomer] = useMutation(DELETE_CUSTOMER, {
+ variables: { customerId },
+ onCompleted: () => history.push('/compliance/customers')
+ })
+
const updateCustomer = it =>
setCustomer({
variables: {
@@ -595,6 +612,17 @@ const CustomerProfile = memo(() => {
}>
{`${blocked ? 'Authorize' : 'Block'} customer`}
+ {Boolean(R.path(['isTestCustomer'])(customerData)) &&
+ userData?.role === 'superuser' && (
+