Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into 1629-cards-wallet-backend-…
Browse files Browse the repository at this point in the history
…view-card-transactions
  • Loading branch information
sanducb committed Oct 2, 2024
2 parents c33837c + 237b91a commit 782fc4f
Show file tree
Hide file tree
Showing 13 changed files with 586 additions and 274 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"eslint-plugin-react-hooks": "^4.6.2",
"only-allow": "^1.2.1",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.6",
"prettier-plugin-tailwindcss": "^0.6.8",
"typescript": "^5.6.2"
},
"engines": {
Expand Down
4 changes: 2 additions & 2 deletions packages/boutique/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/react": "18.3.8",
"@types/react": "18.3.10",
"@types/react-dom": "18.3.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"typescript": "^5.6.2",
"vite": "^5.4.7"
"vite": "^5.4.8"
}
}
12 changes: 7 additions & 5 deletions packages/wallet/backend/src/account/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ export class AccountService implements IAccountService {
const accounts = await query

if (!includeWalletAddress) {
accounts.forEach(async (acc) => {
const balance = await this.getAccountBalance(acc)
acc.balance = transformBalance(balance, acc.assetScale)
})
await Promise.all(
accounts.map(async (acc) => {
const balance = await this.getAccountBalance(acc)
acc.balance = transformBalance(balance, acc.assetScale)
})
)
}

return accounts
Expand Down Expand Up @@ -164,7 +166,7 @@ export class AccountService implements IAccountService {
account.gateHubWalletId
)
return Number(
balances.find((balance) => balance.vault.assetCode === account.assetCode)
balances.find((balance) => balance.vault.asset_code === account.assetCode)
?.total ?? 0
)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/wallet/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ export class App {
isAuth,
cardController.getCardTransactions
)
router.get('/cards/:cardId/pin', isAuth, cardController.getPin)
router.post('/cards/:cardId/change-pin', isAuth, cardController.changePin)

// Return an error for invalid routes
router.use('*', (req: Request, res: CustomResponse) => {
Expand Down
42 changes: 39 additions & 3 deletions packages/wallet/backend/src/card/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
getCardDetailsSchema,
lockCardSchema,
unlockCardSchema,
getCardTransactionsSchema
getCardTransactionsSchema,
changePinSchema
} from './validation'

export interface ICardController {
Expand All @@ -25,6 +26,8 @@ export interface ICardController {
lock: Controller<ICardResponse>
unlock: Controller<ICardResponse>
getCardTransactions: Controller<IGetTransactionsResponse>
getPin: Controller<ICardResponse>
changePin: Controller<void>
}

export class CardController implements ICardController {
Expand Down Expand Up @@ -53,9 +56,9 @@ export class CardController implements ICardController {
) => {
try {
const userId = req.session.user.id
const { params, body } = await validate(getCardDetailsSchema, req)
const { params, query } = await validate(getCardDetailsSchema, req)
const { cardId } = params
const { publicKeyBase64 } = body
const { publicKeyBase64 } = query

const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 }
const cardDetails = await this.cardService.getCardDetails(
Expand Down Expand Up @@ -124,4 +127,37 @@ export class CardController implements ICardController {
next(error)
}
}

public getPin = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.session.user.id
const { params, query } = await validate(getCardDetailsSchema, req)
const { cardId } = params
const { publicKeyBase64 } = query

const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 }
const cardPin = await this.cardService.getPin(userId, requestBody)
res.status(200).json(toSuccessResponse(cardPin))
} catch (error) {
next(error)
}
}

public changePin = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const userId = req.session.user.id
const { params, body } = await validate(changePinSchema, req)
const { cardId } = params
const { cypher } = body

const result = await this.cardService.changePin(userId, cardId, cypher)
res.status(201).json(toSuccessResponse(result))
} catch (error) {
next(error)
}
}
}
21 changes: 21 additions & 0 deletions packages/wallet/backend/src/card/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class CardService {
): Promise<ICardDetailsResponse> {
const { cardId } = requestBody
await this.ensureWalletAddressExists(userId, cardId)
await this.ensureWalletAddressExists(userId, cardId)

return this.gateHubClient.getCardDetails(requestBody)
}
Expand All @@ -42,6 +43,26 @@ export class CardService {
return this.gateHubClient.getCardTransactions(cardId, pageSize, pageNumber)
}

async getPin(
userId: string,
requestBody: ICardDetailsRequest
): Promise<ICardDetailsResponse> {
const { cardId } = requestBody
await this.ensureWalletAddressExists(userId, cardId)

return this.gateHubClient.getPin(requestBody)
}

async changePin(
userId: string,
cardId: string,
cypher: string
): Promise<void> {
await this.ensureWalletAddressExists(userId, cardId)

await this.gateHubClient.changePin(cardId, cypher)
}

async lock(
cardId: string,
reasonCode: LockReasonCode,
Expand Down
11 changes: 10 additions & 1 deletion packages/wallet/backend/src/card/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const getCardDetailsSchema = z.object({
params: z.object({
cardId: z.string()
}),
body: z.object({
query: z.object({
publicKeyBase64: z.string()
})
})
Expand Down Expand Up @@ -52,3 +52,12 @@ export const getCardTransactionsSchema = z.object({
pageNumber: z.coerce.number().int().nonnegative().optional()
})
})

export const changePinSchema = z.object({
params: z.object({
cardId: z.string()
}),
body: z.object({
cypher: z.string()
})
})
72 changes: 70 additions & 2 deletions packages/wallet/backend/src/gatehub/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,18 @@ export class GateHubClient {
}

async createTransaction(
body: ICreateTransactionRequest
body: ICreateTransactionRequest,
managedUserUuid?: string
): Promise<ICreateTransactionResponse> {
const url = `${this.apiUrl}/core/v1/transactions`

const response = await this.request<ICreateTransactionResponse>(
'POST',
url,
JSON.stringify(body)
JSON.stringify(body),
{
managedUserUuid
}
)

return response
Expand Down Expand Up @@ -431,6 +435,70 @@ export class GateHubClient {
)
}

async getPin(
requestBody: ICardDetailsRequest
): Promise<ICardDetailsResponse> {
const url = `${this.apiUrl}/token/pin`

const response = await this.request<ILinksResponse>(
'POST',
url,
JSON.stringify(requestBody),
{
cardAppId: this.env.GATEHUB_CARD_APP_ID
}
)

const token = response.token
if (!token) {
throw new Error('Failed to obtain token for card pin retrieval')
}

// TODO change this to direct call to card managing entity
// Will get this from the GateHub proxy for now
const cardPinUrl = `${this.apiUrl}/v1/proxy/client-device/pin`
const cardPinResponse = await this.request<ICardDetailsResponse>(
'GET',
cardPinUrl,
undefined,
{
token
}
)

return cardPinResponse
}

async changePin(cardId: string, cypher: string): Promise<void> {
const url = `${this.apiUrl}/token/pin-change`

const response = await this.request<ILinksResponse>(
'POST',
url,
JSON.stringify({ cardId: cardId }),
{
cardAppId: this.env.GATEHUB_CARD_APP_ID
}
)

const token = response.token
if (!token) {
throw new Error('Failed to obtain token for card pin retrieval')
}

// TODO change this to direct call to card managing entity
// Will get this from the GateHub proxy for now
const cardPinUrl = `${this.apiUrl}/v1/proxy/client-device/pin`
await this.request<void>(
'POST',
cardPinUrl,
JSON.stringify({ cypher: cypher }),
{
token
}
)
}

private async request<T>(
method: HTTP_METHODS,
url: string,
Expand Down
6 changes: 3 additions & 3 deletions packages/wallet/backend/src/gatehub/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ export interface IWalletBalance {
interface IVault {
uuid: string
name: string
assetCode: string
createdAt: string
updatedAt: string
asset_code: string
created_at: string
updated_at: string
}

export interface IConnectUserToGatewayResponse {}
Expand Down
35 changes: 22 additions & 13 deletions packages/wallet/backend/src/rafiki/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,21 +247,27 @@ export class RafikiService implements IRafikiService {
const walletAddress = await this.getWalletAddress(wh)
const debitAmount = this.getAmountFromWebHook(wh)

const { gateHubWalletId: sendingWallet, userId } =
await this.getGateHubWalletAddress(walletAddress)
const {
gateHubWalletId: sendingWallet,
userId,
gateHubUserId
} = await this.getGateHubWalletAddress(walletAddress)

if (!this.validateAmount(debitAmount, wh.type)) {
return
}

await this.gateHubClient.createTransaction({
amount: this.amountToNumber(debitAmount),
vault_uuid: this.getVaultUuid(debitAmount.assetCode),
sending_address: sendingWallet,
receiving_address: this.env.GATEHUB_SETTLEMENT_WALLET_ADDRESS,
type: HOSTED_TRANSACTION_TYPE,
message: 'Transfer'
})
await this.gateHubClient.createTransaction(
{
amount: this.amountToNumber(debitAmount),
vault_uuid: this.getVaultUuid(debitAmount.assetCode),
sending_address: sendingWallet,
receiving_address: this.env.GATEHUB_SETTLEMENT_WALLET_ADDRESS,
type: HOSTED_TRANSACTION_TYPE,
message: 'Transfer'
},
gateHubUserId
)

if (wh.data.balance !== '0') {
await this.rafikiClient.withdrawLiqudity(wh.id)
Expand Down Expand Up @@ -365,17 +371,20 @@ export class RafikiService implements IRafikiService {
}

private async getGateHubWalletAddress(walletAddress: WalletAddress) {
const account = await Account.query().findById(walletAddress.accountId)
const account = await Account.query()
.findById(walletAddress.accountId)
.withGraphFetched('user')

if (!account || !account.gateHubWalletId) {
if (!account?.gateHubWalletId || !account.user?.gateHubUserId) {
throw new BadRequest(
'No account associated to the provided payment pointer'
)
}

return {
userId: account.userId,
gateHubWalletId: account.gateHubWalletId
gateHubWalletId: account.gateHubWalletId,
gateHubUserId: account.user.gateHubUserId
}
}
}
Loading

0 comments on commit 782fc4f

Please sign in to comment.