diff --git a/packages/wallet/backend/src/app.ts b/packages/wallet/backend/src/app.ts index 4298d9250..8aaf62825 100644 --- a/packages/wallet/backend/src/app.ts +++ b/packages/wallet/backend/src/app.ts @@ -307,7 +307,11 @@ export class App { ) // Cards - router.get('/:customerId/cards', isAuth, cardController.getCardsByCustomer) + router.get( + '/customers/:customerId/cards', + isAuth, + cardController.getCardsByCustomer + ) router.get('/cards/:cardId/details', isAuth, cardController.getCardDetails) // Return an error for invalid routes diff --git a/packages/wallet/backend/src/card/controller.ts b/packages/wallet/backend/src/card/controller.ts index 559fdc394..581c3f469 100644 --- a/packages/wallet/backend/src/card/controller.ts +++ b/packages/wallet/backend/src/card/controller.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from 'express' -import { Controller, NotFound } from '@shared/backend' +import { Controller } from '@shared/backend' import { CardService } from '@/card/service' import { toSuccessResponse } from '@shared/backend' import { @@ -7,7 +7,6 @@ import { ICardDetailsResponse, ICardResponse } from './types' -import { WalletAddressService } from '@/walletAddress/service' import { validate } from '@/shared/validate' import { getCardsByCustomerSchema, getCardDetailsSchema } from './validation' @@ -17,10 +16,7 @@ export interface ICardController { } export class CardController implements ICardController { - constructor( - private cardService: CardService, - private walletAddressService: WalletAddressService - ) {} + constructor(private cardService: CardService) {} public getCardsByCustomer = async ( req: Request, @@ -49,17 +45,11 @@ export class CardController implements ICardController { const { cardId } = params const { publicKeyBase64 } = body - const walletAddress = await this.walletAddressService.getByCardId( + const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 } + const cardDetails = await this.cardService.getCardDetails( userId, - cardId + requestBody ) - - if (!walletAddress) { - throw new NotFound('Card not found or not associated with the user.') - } - - const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 } - const cardDetails = await this.cardService.getCardDetails(requestBody) res.status(200).json(toSuccessResponse(cardDetails)) } catch (error) { next(error) diff --git a/packages/wallet/backend/src/card/service.ts b/packages/wallet/backend/src/card/service.ts index 025945d20..bd2a023b2 100644 --- a/packages/wallet/backend/src/card/service.ts +++ b/packages/wallet/backend/src/card/service.ts @@ -1,20 +1,36 @@ +import { WalletAddressService } from '@/walletAddress/service' import { GateHubClient } from '../gatehub/client' import { ICardDetailsRequest, ICardDetailsResponse, ICardResponse } from './types' +import { NotFound } from '@shared/backend' export class CardService { - constructor(private gateHubClient: GateHubClient) {} + constructor( + private gateHubClient: GateHubClient, + private walletAddressService: WalletAddressService + ) {} async getCardsByCustomer(customerId: string): Promise { return this.gateHubClient.getCardsByCustomer(customerId) } async getCardDetails( + userId: string, requestBody: ICardDetailsRequest ): Promise { + const { cardId } = requestBody + + const walletAddress = await this.walletAddressService.getByCardId( + userId, + cardId + ) + if (!walletAddress) { + throw new NotFound('Card not found or not associated with the user.') + } + return this.gateHubClient.getCardDetails(requestBody) } } diff --git a/packages/wallet/backend/src/gatehub/client.ts b/packages/wallet/backend/src/gatehub/client.ts index 36b155488..b458870c3 100644 --- a/packages/wallet/backend/src/gatehub/client.ts +++ b/packages/wallet/backend/src/gatehub/client.ts @@ -48,7 +48,7 @@ export class GateHubClient { private iframeMappings: Record< IFRAME_TYPE, - (managedUserId: string) => Promise + (managedUserUuid: string) => Promise > = { deposit: this.getDepositUrl.bind(this), withdrawal: this.getWithdrawalUrl.bind(this), @@ -85,41 +85,41 @@ export class GateHubClient { return `https://onboarding.${this.mainUrl}` } - async getWithdrawalUrl(managedUserId: string): Promise { + async getWithdrawalUrl(managedUserUuid: string): Promise { const token = await this.getIframeAuthorizationToken( this.clientIds.onOffRamp, DEFAULT_APP_SCOPE, - managedUserId + managedUserUuid ) return `${this.rampUrl}/?paymentType=${PAYMENT_TYPE.withdrawal}&bearer=${token}` } - async getDepositUrl(managedUserId: string): Promise { + async getDepositUrl(managedUserUuid: string): Promise { const token = await this.getIframeAuthorizationToken( this.clientIds.onOffRamp, DEFAULT_APP_SCOPE, - managedUserId + managedUserUuid ) return `${this.rampUrl}/?paymentType=${PAYMENT_TYPE.deposit}&bearer=${token}` } - async getOnboardingUrl(managedUserId: string): Promise { + async getOnboardingUrl(managedUserUuid: string): Promise { const token = await this.getIframeAuthorizationToken( this.clientIds.onboarding, ONBOARDING_APP_SCOPE, - managedUserId + managedUserUuid ) return `${this.onboardingUrl}/?bearer=${token}` } - async getExchangeUrl(managedUserId: string): Promise { + async getExchangeUrl(managedUserUuid: string): Promise { const token = await this.getIframeAuthorizationToken( this.clientIds.exchange, DEFAULT_APP_SCOPE, - managedUserId + managedUserUuid ) return `${this.exchangeUrl}/?bearer=${token}` @@ -127,19 +127,19 @@ export class GateHubClient { async getIframeUrl( type: IFRAME_TYPE, - managedUserId: string + managedUserUuid: string ): Promise { if (!this.iframeMappings[type]) { throw new BadRequest('Invalid iframe type') } - return await this.iframeMappings[type](managedUserId) + return await this.iframeMappings[type](managedUserUuid) } async getIframeAuthorizationToken( clientId: string, scope: string[], - managedUserId: string + managedUserUuid: string ): Promise { const url = `${this.apiUrl}/auth/v1/tokens?clientId=${clientId}` const body: ITokenRequest = { scope } @@ -148,7 +148,9 @@ export class GateHubClient { 'POST', url, JSON.stringify(body), - managedUserId + { + managedUserUuid + } ) return response.token @@ -191,21 +193,18 @@ export class GateHubClient { } async connectUserToGateway( - userUuid: string, + managedUserUuid: string, gatewayUuid: string ): Promise { - const url = `${this.apiUrl}/id/v1/users/${userUuid}/hubs/${gatewayUuid}` + const url = `${this.apiUrl}/id/v1/users/${managedUserUuid}/hubs/${gatewayUuid}` - await this.request( - 'POST', - url, - undefined, - userUuid - ) + await this.request('POST', url, undefined, { + managedUserUuid + }) if (!this.isProduction) { // Auto approve user to gateway in sandbox environment - await this.approveUserToGateway(userUuid, gatewayUuid) + await this.approveUserToGateway(managedUserUuid, gatewayUuid) return true } @@ -234,10 +233,10 @@ export class GateHubClient { } async createWallet( - userUuid: string, + managedUserUuid: string, name: string ): Promise { - const url = `${this.apiUrl}/core/v1/users/${userUuid}/wallets` + const url = `${this.apiUrl}/core/v1/users/${managedUserUuid}/wallets` const body: ICreateWalletRequest = { name, type: HOSTED_WALLET_TYPE @@ -247,7 +246,9 @@ export class GateHubClient { 'POST', url, JSON.stringify(body), - userUuid + { + managedUserUuid + } ) return response @@ -312,24 +313,27 @@ export class GateHubClient { // This should be called before creating customers to get the product codes for the card and account async fetchCardApplicationProducts(): Promise { - const path = `/v1/card-applications/${this.env.GATEHUB_CARD_APP_ID}/card-products` - const response = await this.request('GET', path) + const url = `${this.apiUrl}/v1/card-applications/${this.env.GATEHUB_CARD_APP_ID}/card-products` + const response = await this.request('GET', url) return response } async createCustomer( - request: ICreateCustomerRequest + requestBody: ICreateCustomerRequest ): Promise { const url = `${this.apiUrl}/v1/customers` return this.request( 'POST', url, - JSON.stringify(request) + JSON.stringify(requestBody), + { + cardAppId: this.env.GATEHUB_CARD_APP_ID + } ) } async getCardsByCustomer(customerId: string): Promise { - const url = `/v1/customers/${customerId}/cards` + const url = `${this.apiUrl}/v1/customers/${customerId}/cards` return this.request('GET', url) } @@ -341,7 +345,10 @@ export class GateHubClient { const response = await this.request( 'POST', url, - JSON.stringify(requestBody) + JSON.stringify(requestBody), + { + cardAppId: this.env.GATEHUB_CARD_APP_ID + } ) const token = response.token @@ -356,8 +363,9 @@ export class GateHubClient { 'GET', cardDetailsUrl, undefined, - undefined, - token + { + token + } ) return cardDetailsResponse @@ -367,8 +375,11 @@ export class GateHubClient { method: HTTP_METHODS, url: string, body?: string, - managedUserUuid?: string, - token?: string + headersOptions?: { + managedUserUuid?: string + token?: string + cardAppId?: string + } ): Promise { const timestamp = Date.now().toString() const headers = this.getRequestHeaders( @@ -376,8 +387,7 @@ export class GateHubClient { method, url, body ?? '', - managedUserUuid, - token + headersOptions ) try { @@ -412,8 +422,11 @@ export class GateHubClient { method: HTTP_METHODS, url: string, body?: string, - managedUserUuid?: string, - token?: string + headersOptions?: { + managedUserUuid?: string + token?: string + cardAppId?: string + } ) { return { 'Content-Type': 'application/json', @@ -421,10 +434,15 @@ export class GateHubClient { 'x-gatehub-timestamp': timestamp, 'x-gatehub-signature': this.getSignature(timestamp, method, url, body), 'x-gatehub-card-app-id': this.env.GATEHUB_CARD_APP_ID, - ...(managedUserUuid && { - 'x-gatehub-managed-user-uuid': managedUserUuid + ...(headersOptions?.managedUserUuid && { + 'x-gatehub-managed-user-uuid': headersOptions.managedUserUuid }), - ...(token && { Authorization: token }) + ...(headersOptions?.cardAppId && { + 'x-gatehub-card-app-id': headersOptions.cardAppId + }), + ...(headersOptions?.token && { + Authorization: `Bearer ${headersOptions.token}` + }) } } diff --git a/packages/wallet/backend/tests/cards/controller.test.ts b/packages/wallet/backend/tests/cards/controller.test.ts index aafe3032d..3b888e2b9 100644 --- a/packages/wallet/backend/tests/cards/controller.test.ts +++ b/packages/wallet/backend/tests/cards/controller.test.ts @@ -37,10 +37,6 @@ describe('CardController', () => { getCardDetails: jest.fn() } - const mockWalletAddressService = { - getByCardId: jest.fn() - } - const args = mockLogInRequest().body const createReqRes = async () => { @@ -74,11 +70,6 @@ describe('CardController', () => { beforeEach(async (): Promise => { Reflect.set(cardController, 'cardService', mockCardService) - Reflect.set( - cardController, - 'walletAddressService', - mockWalletAddressService - ) await createUser({ ...args, isEmailVerified: true }) await createReqRes() @@ -157,24 +148,15 @@ describe('CardController', () => { req.body = { publicKeyBase64: 'test-public-key' } - const mockedWalletAddress = { - id: 'wallet-address-id', - cardId: 'test-card-id' - } const mockedCardDetails: ICardDetailsResponse = { cipher: 'encrypted-card-data' } - mockWalletAddressService.getByCardId.mockResolvedValue(mockedWalletAddress) mockCardService.getCardDetails.mockResolvedValue(mockedCardDetails) await cardController.getCardDetails(req, res, next) - expect(mockWalletAddressService.getByCardId).toHaveBeenCalledWith( - userId, - 'test-card-id' - ) - expect(mockCardService.getCardDetails).toHaveBeenCalledWith({ + expect(mockCardService.getCardDetails).toHaveBeenCalledWith(userId, { cardId: 'test-card-id', publicKeyBase64: 'test-public-key' })