Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet/backend): add script for managed user and customer creation #1675

Merged
merged 13 commits into from
Oct 10, 2024
4 changes: 4 additions & 0 deletions docker/dev/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ GATEHUB_VAULT_UUID_EUR=
GATEHUB_VAULT_UUID_USD=
GATEHUB_SETTLEMENT_WALLET_ADDRESS=
GATEHUB_CARD_APP_ID=
GATEHUB_ACCOUNT_PRODUCT_CODE=
GATEHUB_CARD_PRODUCT_CODE=
GATEHUB_NAME_ON_CARD=
GATEHUB_CARD_PP_PREFIX=

# commerce env variables
# encoded base 64 private key
Expand Down
4 changes: 4 additions & 0 deletions docker/dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ services:
GATEHUB_VAULT_UUID_USD: ${GATEHUB_VAULT_UUID_USD}
GATEHUB_SETTLEMENT_WALLET_ADDRESS: ${GATEHUB_SETTLEMENT_WALLET_ADDRESS}
GATEHUB_CARD_APP_ID: ${GATEHUB_CARD_APP_ID}
GATEHUB_ACCOUNT_PRODUCT_CODE: ${GATEHUB_ACCOUNT_PRODUCT_CODE}
GATEHUB_CARD_PRODUCT_CODE: ${GATEHUB_CARD_PRODUCT_CODE}
GATEHUB_NAME_ON_CARD: ${GATEHUB_NAME_ON_CARD}
GATEHUB_CARD_PP_PREFIX: ${GATEHUB_CARD_PP_PREFIX}
restart: always
networks:
- testnet
Expand Down
4 changes: 4 additions & 0 deletions docker/prod/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ WALLET_BACKEND_GATEHUB_VAULT_UUID_EUR=
WALLET_BACKEND_GATEHUB_VAULT_UUID_USD=
WALLET_BACKEND_GATEHUB_SETTLEMENT_WALLET_ADDRESS=
WALLET_BACKEND_GATEHUB_CARD_APP_ID=
WALLET_BACKEND_GATEHUB_ACCOUNT_PRODUCT_CODE=
WALLET_BACKEND_GATEHUB_CARD_PRODUCT_CODE=
WALLET_BACKEND_GATEHUB_NAME_ON_CARD=
WALLET_BACKEND_GATEHUB_CARD_PP_PREFIX=

# BOUTIQUE
BOUTIQUE_BACKEND_PORT=
Expand Down
4 changes: 4 additions & 0 deletions docker/prod/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ services:
GATEHUB_VAULT_UUID_USD: ${WALLET_BACKEND_GATEHUB_VAULT_UUID_USD}
GATEHUB_SETTLEMENT_WALLET_ADDRESS: ${WALLET_BACKEND_GATEHUB_SETTLEMENT_WALLET_ADDRESS}
GATEHUB_CARD_APP_ID: ${WALLET_BACKEND_GATEHUB_CARD_APP_ID}
GATEHUB_ACCOUNT_PRODUCT_CODE: ${WALLET_BACKEND_GATEHUB_ACCOUNT_PRODUCT_CODE}
GATEHUB_CARD_PRODUCT_CODE: ${WALLET_BACKEND_GATEHUB_CARD_PRODUCT_CODE}
GATEHUB_NAME_ON_CARD: ${WALLET_BACKEND_GATEHUB_NAME_ON_CARD}
GATEHUB_CARD_PP_PREFIX: ${WALLET_BACKEND_GATEHUB_CARD_PP_PREFIX}
networks:
- testnet
ports:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function (knex) {
return knex.schema.table('users', function (table) {
table.string('customerId').unique().nullable()
raducristianpopa marked this conversation as resolved.
Show resolved Hide resolved
})
}

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function (knex) {
return knex.schema.table('users', function (table) {
table.dropColumn('customerId')
})
}
159 changes: 133 additions & 26 deletions packages/wallet/backend/src/card/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CardLimitType } from '@wallet/shared/src'

export type GateHubCardCurrency = 'EUR'

export interface ICardDetailsRequest {
cardId: string
publicKeyBase64: string
Expand All @@ -19,39 +21,125 @@ export interface ILinksResponse {
}

export interface ICreateCustomerRequest {
emailAddress: string
walletAddress: string
account: {
productCode: string
currency: GateHubCardCurrency
card: {
productCode: string
}
}
card: {
productCode: string
}
user: {
firstName: string
lastName: string
mobileNumber?: string
nationalIdentifier?: string
}
identification: {
documents: Array<{
type: string
file: string // Base64-encoded file content
}>
}
address: {
addressLine1: string
addressLine2?: string
city: string
region?: string
postalCode: string
countryCode: string
nameOnCard: string
citizen: {
name: string
surname: string
birthPlace?: string | null
}
}

export interface ICitizen {
name: string
surname: string
birthDate?: string | null
birthPlace?: string | null
gender?: 'Female' | 'Male' | 'Unspecified' | 'Unknown' | null
title?: string | null
language?: string | null
}

export interface ILegalEntity {
longName: string
shortName: string
sector?:
| 'Public'
| 'Private'
| 'Corporate'
| 'Others'
| 'NoInformation'
| 'UnrelatedPersonsLegalEntities'
| null
industrialClassificationProvider?: string | null
industrialClassificationValue?: string | null
type?: string | null
vat?: string | null
hqCustomerId?: number | null
contactPerson?: string | null
agentCode?: string | null
agentName?: string | null
}

export interface IAddress {
sourceId?: string | null
type: 'PermanentResidence' | 'Work' | 'Other' | 'TemporaryResidence'
countryCode: string
line1: string
line2?: string | null
line3?: string | null
city: string
postOffice?: string | null
zipCode: string
status?: 'Inactive' | 'Active' | null
id?: string | null
customerId?: string | null
customerSourceId?: string | null
}

export interface ICommunication {
sourceId?: string | null
type: 'Email' | 'Mobile'
value?: string | null
id?: string | null
status?: 'Inactive' | 'Active' | null
customerId?: string | null
customerSourceId?: string | null
}

export interface IAccount {
sourceId?: string | null
type?: 'CHARGE' | 'LOAN' | 'DEBIT' | 'PREPAID' | null
productCode?: string | null
accountNumber?: string | null
feeProfile?: string | null
accountProfile?: string | null
id?: string | null
customerId?: string | null
customerSourceId?: string | null
status?: 'ACTIVE' | 'LOCKED' | 'BLOCKED' | null
statusReasonCode?:
| 'TemporaryBlockForDelinquency'
| 'TemporaryBlockOnIssuerRequest'
| 'TemporaryBlockForDepo'
| 'TemporaryBlockForAmlKyc'
| 'IssuerRequestGeneral'
| 'UserRequest'
| 'PremanentBlockChargeOff'
| 'IssuerRequestBureauInquiry'
| 'IssuerRequestCustomerDeceased'
| 'IssuerRequestStornoFromCollectionStraight'
| 'IssuerRequestStornoFromCollectionDepo'
| 'IssuerRequestStornoFromCollectionDepoPaid'
| 'IssuerRequestHandoverToAttorney'
| 'IssuerRequestLegalAction'
| 'IssuerRequestAmlKyc'
| null
currency?: string | null
cards?: ICardResponse[] | null
}

export interface ICreateCustomerResponse {
customerId: string
accountId: string
cardId: string
walletAddress: string
customers: {
sourceId?: string | null
taxNumber?: string | null
code: string
type: 'Citizen' | 'LegalEntity'
citizen?: ICitizen | null
legalEntity?: ILegalEntity | null
id?: string | null
addresses?: IAddress[] | null
communications?: ICommunication[] | null
accounts?: IAccount[] | null
}
}

export interface ICardResponse {
Expand Down Expand Up @@ -108,3 +196,22 @@ export interface ICardLimitResponse {
currency: string
isDisabled: boolean
}

export type CloseCardReason =
| 'IssuerRequestGeneral'
| 'IssuerRequestFraud'
| 'IssuerRequestLegal'
| 'IssuerRequestIncorrectOpening'
| 'UserRequest'
| 'IssuerRequestCustomerDeceased'

export interface ICreateCardRequest {
nameOnCard: string
deliveryAddressId: string
walletAddress: string
currency: GateHubCardCurrency
productCode: string
card: {
productCode: string
}
}
111 changes: 111 additions & 0 deletions packages/wallet/backend/src/cardUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { createContainer } from '@/createContainer'
import { env } from '@/config/env'
import { ICreateCustomerRequest } from '@/card/types'

interface UserData {
email: string
firstName: string
lastName: string
ppNumber: string
}

const entries = `John;Doe;john@doe.com;8888
Alice;Smith;alice@smith.com;9999`

function processEntries() {
const users: Array<UserData> = []
const lines = entries.split('\n')
for (const line of lines) {
const [firstName, lastName, email, ppNumber] = line.split(';')
users.push({
email,
firstName,
lastName,
ppNumber
})
}

return users
}

async function cardManagement() {
const container = await createContainer(env)
const logger = container.resolve('logger')
const knex = container.resolve('knex')
const gateHubClient = container.resolve('gateHubClient')
const accountProductCode = env.GATEHUB_ACCOUNT_PRODUCT_CODE
const cardProductCode = env.GATEHUB_CARD_PRODUCT_CODE
const nameOnCard = env.GATEHUB_NAME_ON_CARD
const ppPrefix = env.GATEHUB_CARD_PP_PREFIX
const GATEWAY_UUID = env.GATEHUB_GATEWAY_UUID

try {
const usersData = processEntries()

for (const userData of usersData) {
const { email, firstName, lastName, ppNumber } = userData

const managedUser = await gateHubClient.createManagedUser(email)

logger.info(`Created managed user for ${email}: ${managedUser.id}`)

await gateHubClient.connectUserToGateway(managedUser.id, GATEWAY_UUID)

logger.info(
`Connected user ${managedUser.id} - ${managedUser.email} to gateway`
)

const user = await gateHubClient.getWalletForUser(managedUser.id)
const walletAddress = user.wallets[0].address
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use the default wallet created instead of creating on for each account?

Copy link
Member

@raducristianpopa raducristianpopa Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getWalletForUser retrieves the already created wallet - when the managed user is created. We are not creating another wallet here.


logger.info(`Retrieved user ${managedUser.id} wallet - ${walletAddress}`)

// Create customer using product codes as env vars
const createCustomerRequestBody: ICreateCustomerRequest = {
walletAddress,
account: {
productCode: accountProductCode,
currency: 'EUR',
card: {
productCode: cardProductCode
}
},
nameOnCard,
citizen: {
name: firstName,
surname: lastName
}
}

const customer = await gateHubClient.createCustomer(
managedUser.id,
createCustomerRequestBody
)

logger.info(`Created customer for ${email}: ${customer.customers.id}`)

const pp = ppPrefix + ppNumber
await gateHubClient.updateMetaForManagedUser(managedUser.id, {
paymentPointer: pp.toLowerCase(),
customerId: customer.customers.id!
})

logger.info(`Updated meta object for user ${email}`)

const accounts = customer.customers.accounts![0]
const card = accounts.cards![0]

await gateHubClient.orderPlasticForCard(managedUser.id, card.id)
}
} catch (error: unknown) {
console.log(`An error occurred: ${(error as Error).message}`)
} finally {
await knex.destroy()
await container.dispose()
}
}

cardManagement().catch((error) => {
console.error(`Script failed: ${error.message}`)
process.exit(1)
})
9 changes: 9 additions & 0 deletions packages/wallet/backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ const envSchema = z.object({
.string()
.default('GATEHUB_SETTLEMENT_WALLET_ADDRESS'),
GATEHUB_CARD_APP_ID: z.string().default('GATEHUB_CARD_APP_ID'),
GATEHUB_ACCOUNT_PRODUCT_CODE: z
.string()
.default('GATEHUB_ACCOUNT_PRODUCT_CODE'),
GATEHUB_CARD_PRODUCT_CODE: z.string().default('GATEHUB_CARD_PRODUCT_CODE'),
GATEHUB_NAME_ON_CARD: z
.string()
.regex(/^[a-zA-Z0-9]*$/, 'Only alphanumeric characters are allowed')
.default('INTERLEDGER'),
GATEHUB_CARD_PP_PREFIX: z.string().default('GATEHUB_GATEHUB_CARD_PP_PREFIX'),
GRAPHQL_ENDPOINT: z.string().url().default('http://localhost:3011/graphql'),
AUTH_GRAPHQL_ENDPOINT: z
.string()
Expand Down
Loading
Loading