Skip to content

Commit

Permalink
feat(wallet): freeze card; pin; card details (#1679)
Browse files Browse the repository at this point in the history
  • Loading branch information
raducristianpopa authored Oct 24, 2024
1 parent a8bf42d commit 9fb3720
Show file tree
Hide file tree
Showing 38 changed files with 1,228 additions and 458 deletions.
2 changes: 2 additions & 0 deletions docker/dev/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ GATEHUB_ACCOUNT_PRODUCT_CODE=
GATEHUB_CARD_PRODUCT_CODE=
GATEHUB_NAME_ON_CARD=
GATEHUB_CARD_PP_PREFIX=
CARD_PIN_HREF=
CARD_DATA_HREF=

# commerce env variables
# encoded base 64 private key
Expand Down
2 changes: 2 additions & 0 deletions docker/dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ services:
GATEHUB_CARD_PRODUCT_CODE: ${GATEHUB_CARD_PRODUCT_CODE}
GATEHUB_NAME_ON_CARD: ${GATEHUB_NAME_ON_CARD}
GATEHUB_CARD_PP_PREFIX: ${GATEHUB_CARD_PP_PREFIX}
CARD_DATA_HREF: ${CARD_DATA_HREF}
CARD_PIN_HREF: ${CARD_PIN_HREF}
restart: always
networks:
- testnet
Expand Down
12 changes: 8 additions & 4 deletions packages/wallet/backend/src/account/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type CreateAccountArgs = {
name: string
assetId: string
isDefaultCardsAccount?: boolean
cardId?: string
}

interface IAccountService {
Expand Down Expand Up @@ -93,7 +94,8 @@ export class AccountService implements IAccountService {
assetCode: asset.code,
assetId: args.assetId,
assetScale: asset.scale,
gateHubWalletId
gateHubWalletId,
cardId: args.cardId
})

// On creation account will have balance 0
Expand Down Expand Up @@ -208,7 +210,7 @@ export class AccountService implements IAccountService {

return Number(
balances.find((balance) => balance.vault.asset_code === account.assetCode)
?.total ?? 0
?.available ?? 0
)
}

Expand All @@ -230,7 +232,8 @@ export class AccountService implements IAccountService {
public async createDefaultAccount(
userId: string,
name = 'USD Account',
isDefaultCardsAccount = false
isDefaultCardsAccount = false,
cardId?: string
): Promise<Account | undefined> {
const asset = (await this.rafikiClient.listAssets({ first: 100 })).find(
(asset) => asset.code === 'EUR' && asset.scale === DEFAULT_ASSET_SCALE
Expand All @@ -242,7 +245,8 @@ export class AccountService implements IAccountService {
name,
userId,
assetId: asset.id,
isDefaultCardsAccount
isDefaultCardsAccount,
cardId
})

return account
Expand Down
15 changes: 6 additions & 9 deletions packages/wallet/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ export class App {
cors({
origin: [
'http://localhost:4003',
`https://${env.RAFIKI_MONEY_FRONTEND_HOST}`
`https://${env.RAFIKI_MONEY_FRONTEND_HOST}`,
`https://wallet.${env.RAFIKI_MONEY_FRONTEND_HOST}`
],
credentials: true
})
Expand Down Expand Up @@ -327,11 +328,7 @@ export class App {
)

// Cards
router.get(
'/customers/:customerId/cards',
isAuth,
cardController.getCardsByCustomer
)
router.get('/customers/cards', isAuth, cardController.getCardsByCustomer)
router.get('/cards/:cardId/details', isAuth, cardController.getCardDetails)
router.get(
'/cards/:cardId/transactions',
Expand All @@ -358,7 +355,7 @@ export class App {
cardController.getPin
)
router.get(
'/cards/:cardId/change-pin',
'/cards/:cardId/change-pin-token',
this.ensureGateHubProductionEnv,
isAuth,
cardController.getTokenForPinChange
Expand All @@ -381,11 +378,11 @@ export class App {
isAuth,
cardController.unlock
)
router.put(
router.delete(
'/cards/:cardId/block',
this.ensureGateHubProductionEnv,
isAuth,
cardController.permanentlyBlockCard
cardController.closeCard
)

// Return an error for invalid routes
Expand Down
5 changes: 3 additions & 2 deletions packages/wallet/backend/src/auth/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ export class AuthController implements IAuthController {
signUp = async (req: Request, res: CustomResponse, next: NextFunction) => {
try {
const {
body: { email, password }
body: { email, password, acceptedCardTerms }
} = await validate(signUpBodySchema, req)

await this.authService.signUp({ email, password })
await this.authService.signUp({ email, password, acceptedCardTerms })

res
.status(201)
Expand All @@ -54,6 +54,7 @@ export class AuthController implements IAuthController {
req.session.user = {
id: user.id,
email: user.email,
// TODO: REMOVE NEEDSWALLET
needsWallet: !user.gateHubUserId,
needsIDProof: !user.kycVerified,
customerId: user.customerId
Expand Down
85 changes: 55 additions & 30 deletions packages/wallet/backend/src/card/controller.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { Request, Response, NextFunction } from 'express'
import { Controller } from '@shared/backend'
import {
BadRequest,
Controller,
InternalServerError,
NotFound
} from '@shared/backend'
import { CardService } from '@/card/service'
import { toSuccessResponse } from '@shared/backend'
import {
ICardDetailsRequest,
ICardDetailsResponse,
ICardDetailsWithPinStatusResponse,
ICardLimitRequest,
ICardLimitResponse,
ICardLockRequest,
ICardResponse,
ICardUnlockRequest
} from './types'
import { IGetTransactionsResponse } from '@wallet/shared/src'
import { ICardResponse, IGetTransactionsResponse } from '@wallet/shared'
import { validate } from '@/shared/validate'
import {
getCardsByCustomerSchema,
getCardDetailsSchema,
lockCardSchema,
unlockCardSchema,
Expand All @@ -26,33 +28,48 @@ import {
permanentlyBlockCardSchema,
getTokenForPinChange
} from './validation'
import { Logger } from 'winston'
import { UserService } from '@/user/service'

export interface ICardController {
getCardsByCustomer: Controller<ICardDetailsResponse[]>
getCardDetails: Controller<ICardDetailsWithPinStatusResponse>
getCardsByCustomer: Controller<ICardResponse[]>
getCardDetails: Controller<ICardDetailsResponse>
getCardLimits: Controller<ICardLimitResponse[]>
createOrOverrideCardLimits: Controller<ICardLimitResponse[]>
getCardTransactions: Controller<IGetTransactionsResponse>
getPin: Controller<ICardResponse>
changePin: Controller<void>
lock: Controller<ICardResponse>
unlock: Controller<ICardResponse>
permanentlyBlockCard: Controller<ICardResponse>
closeCard: Controller<void>
}

export class CardController implements ICardController {
constructor(private cardService: CardService) {}
constructor(
private cardService: CardService,
private userService: UserService,
private logger: Logger
) {}

public getCardsByCustomer = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const { params } = await validate(getCardsByCustomerSchema, req)
const { customerId } = params
const customerId = req.session.user.customerId

if (!customerId) {
this.logger.error(
`Customer id was not found on session object for user ${req.session.user.id}`
)
throw new InternalServerError()
}

const cards = await this.cardService.getCardsByCustomer(customerId)
const cards = await this.cardService.getCardsByCustomer(
req.session.user.id,
customerId
)
res.status(200).json(toSuccessResponse(cards))
} catch (error) {
next(error)
Expand All @@ -70,7 +87,10 @@ export class CardController implements ICardController {
const { cardId } = params
const { publicKeyBase64 } = query

const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 }
const requestBody: ICardDetailsRequest = {
cardId,
publicKey: publicKeyBase64
}
const cardDetails = await this.cardService.getCardDetails(
userId,
requestBody
Expand Down Expand Up @@ -155,7 +175,10 @@ export class CardController implements ICardController {
const { cardId } = params
const { publicKeyBase64 } = query

const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 }
const requestBody: ICardDetailsRequest = {
cardId,
publicKey: publicKeyBase64
}
const cardPin = await this.cardService.getPin(userId, requestBody)
res.status(200).json(toSuccessResponse(cardPin))
} catch (error) {
Expand Down Expand Up @@ -191,13 +214,8 @@ export class CardController implements ICardController {
const { cardId } = params
const { token, cypher } = body

const result = await this.cardService.changePin(
userId,
cardId,
token,
cypher
)
res.status(201).json(toSuccessResponse(result))
await this.cardService.changePin(userId, cardId, token, cypher)
res.status(201).json(toSuccessResponse())
} catch (error) {
next(error)
}
Expand Down Expand Up @@ -239,23 +257,30 @@ export class CardController implements ICardController {
}
}

public permanentlyBlockCard = async (
public closeCard = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const userId = req.session.user.id
const { params, query } = await validate(permanentlyBlockCardSchema, req)
const { params, body } = await validate(permanentlyBlockCardSchema, req)
const { cardId } = params
const { reasonCode } = query
const { reasonCode, password } = body

const result = await this.cardService.permanentlyBlockCard(
userId,
cardId,
reasonCode
)
res.status(200).json(toSuccessResponse(result))
const user = await this.userService.getById(userId)

if (!user) {
throw new NotFound()
}

const passwordIsValid = await user?.verifyPassword(password)
if (!passwordIsValid) {
throw new BadRequest('Password is not valid')
}

await this.cardService.closeCard(userId, cardId, reasonCode)
res.status(200).json(toSuccessResponse())
} catch (error) {
next(error)
}
Expand Down
Loading

0 comments on commit 9fb3720

Please sign in to comment.