mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-08 23:15:24 +02:00
more progress on Teller
This commit is contained in:
parent
b61fececc7
commit
97dc37fad1
17 changed files with 396 additions and 86 deletions
|
@ -36,6 +36,7 @@ import {
|
|||
valuationsRouter,
|
||||
institutionsRouter,
|
||||
finicityRouter,
|
||||
tellerRouter,
|
||||
transactionsRouter,
|
||||
holdingsRouter,
|
||||
securitiesRouter,
|
||||
|
@ -156,6 +157,7 @@ app.use('/v1/users', usersRouter)
|
|||
app.use('/v1/e2e', e2eRouter)
|
||||
app.use('/v1/plaid', plaidRouter)
|
||||
app.use('/v1/finicity', finicityRouter)
|
||||
app.use('/v1/teller', tellerRouter)
|
||||
app.use('/v1/accounts', accountsRouter)
|
||||
app.use('/v1/account-rollup', accountRollupRouter)
|
||||
app.use('/v1/connections', connectionsRouter)
|
||||
|
|
|
@ -5,6 +5,7 @@ export { default as usersRouter } from './users.router'
|
|||
export { default as webhooksRouter } from './webhooks.router'
|
||||
export { default as plaidRouter } from './plaid.router'
|
||||
export { default as finicityRouter } from './finicity.router'
|
||||
export { default as tellerRouter } from './teller.router'
|
||||
export { default as valuationsRouter } from './valuations.router'
|
||||
export { default as institutionsRouter } from './institutions.router'
|
||||
export { default as transactionsRouter } from './transactions.router'
|
||||
|
|
45
apps/server/src/app/routes/teller.router.ts
Normal file
45
apps/server/src/app/routes/teller.router.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { Router } from 'express'
|
||||
import { z } from 'zod'
|
||||
import endpoint from '../lib/endpoint'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.post(
|
||||
'/handle-enrollment',
|
||||
endpoint.create({
|
||||
input: z.object({
|
||||
institution: z.object({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
}),
|
||||
enrollment: z.object({
|
||||
accessToken: z.string(),
|
||||
user: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
enrollment: z.object({
|
||||
id: z.string(),
|
||||
institution: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
}),
|
||||
signatures: z.array(z.string()).optional(),
|
||||
}),
|
||||
}),
|
||||
resolve: ({ input: { institution, enrollment }, ctx }) => {
|
||||
return ctx.tellerService.handleEnrollment(ctx.user!.id, institution, enrollment)
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/institutions/sync',
|
||||
endpoint.create({
|
||||
resolve: async ({ ctx }) => {
|
||||
ctx.ability.throwUnlessCan('manage', 'Institution')
|
||||
await ctx.queueService.getQueue('sync-institution').add('sync-teller-institutions', {})
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export default router
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useRef, useEffect, useMemo } from 'react'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { RiFolderLine, RiHandCoinLine, RiLockLine, RiSearchLine } from 'react-icons/ri'
|
||||
import maxBy from 'lodash/maxBy'
|
||||
import {
|
||||
|
@ -7,14 +7,14 @@ import {
|
|||
useDebounce,
|
||||
usePlaid,
|
||||
useFinicity,
|
||||
useTellerConfig,
|
||||
useTellerConnect,
|
||||
} from '@maybe-finance/client/shared'
|
||||
import { Input } from '@maybe-finance/design-system'
|
||||
import InstitutionGrid from './InstitutionGrid'
|
||||
import { AccountTypeGrid } from './AccountTypeGrid'
|
||||
import InstitutionList, { MIN_QUERY_LENGTH } from './InstitutionList'
|
||||
import { useLogger } from '@maybe-finance/client/shared'
|
||||
import { BrowserUtil } from '@maybe-finance/client/shared'
|
||||
import { useTellerConnect, type TellerConnectOptions } from 'teller-connect-react'
|
||||
|
||||
const SEARCH_DEBOUNCE_MS = 300
|
||||
|
||||
|
@ -31,21 +31,16 @@ export default function AccountTypeSelector({
|
|||
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||
const debouncedSearchQuery = useDebounce(searchQuery, SEARCH_DEBOUNCE_MS)
|
||||
|
||||
const [institutionId, setInstitutionId] = useState<string | undefined>(undefined)
|
||||
|
||||
const showInstitutionList =
|
||||
searchQuery.length >= MIN_QUERY_LENGTH &&
|
||||
debouncedSearchQuery.length >= MIN_QUERY_LENGTH &&
|
||||
view !== 'manual'
|
||||
|
||||
const config = useMemo(
|
||||
() => BrowserUtil.getTellerConfig(logger, institutionId),
|
||||
[logger, institutionId]
|
||||
) as TellerConnectOptions
|
||||
const config = useTellerConfig(logger)
|
||||
|
||||
const { openPlaid } = usePlaid()
|
||||
const { openFinicity } = useFinicity()
|
||||
const { open: openTeller } = useTellerConnect(config)
|
||||
const { open: openTeller } = useTellerConnect(config, logger)
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
|
@ -55,15 +50,6 @@ export default function AccountTypeSelector({
|
|||
}
|
||||
}, [])
|
||||
|
||||
const configRef = useRef<TellerConnectOptions | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (institutionId) {
|
||||
configRef.current = BrowserUtil.getTellerConfig(logger, institutionId)
|
||||
openTeller()
|
||||
}
|
||||
}, [institutionId, logger, openTeller])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Search */}
|
||||
|
@ -88,8 +74,6 @@ export default function AccountTypeSelector({
|
|||
if (!providerInstitution) {
|
||||
alert('No provider found for institution')
|
||||
return
|
||||
} else {
|
||||
setInstitutionId(providerInstitution.providerId)
|
||||
}
|
||||
|
||||
switch (providerInstitution.provider) {
|
||||
|
@ -100,7 +84,7 @@ export default function AccountTypeSelector({
|
|||
openFinicity(providerInstitution.providerId)
|
||||
break
|
||||
case 'TELLER':
|
||||
openTeller()
|
||||
openTeller(providerInstitution.providerId)
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
@ -163,14 +147,11 @@ export default function AccountTypeSelector({
|
|||
categoryUser: 'crypto',
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return
|
||||
} else {
|
||||
setInstitutionId(data.providerId)
|
||||
}
|
||||
|
||||
switch (data.provider) {
|
||||
|
@ -181,7 +162,7 @@ export default function AccountTypeSelector({
|
|||
openFinicity(data.providerId)
|
||||
break
|
||||
case 'TELLER':
|
||||
openTeller()
|
||||
openTeller(data.providerId)
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
|
|
@ -5,6 +5,7 @@ export * from './useFinicityApi'
|
|||
export * from './useInstitutionApi'
|
||||
export * from './useUserApi'
|
||||
export * from './usePlaidApi'
|
||||
export * from './useTellerApi'
|
||||
export * from './useValuationApi'
|
||||
export * from './useTransactionApi'
|
||||
export * from './useHoldingApi'
|
||||
|
|
63
libs/client/shared/src/api/useTellerApi.ts
Normal file
63
libs/client/shared/src/api/useTellerApi.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { useMemo } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useAxiosWithAuth } from '../hooks/useAxiosWithAuth'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import type { SharedType } from '@maybe-finance/shared'
|
||||
import { invalidateAccountQueries } from '../utils'
|
||||
import type { AxiosInstance } from 'axios'
|
||||
import type { TellerTypes } from '@maybe-finance/teller-api'
|
||||
|
||||
type TellerInstitution = {
|
||||
name: string
|
||||
id: string
|
||||
}
|
||||
|
||||
const TellerApi = (axios: AxiosInstance) => ({
|
||||
async handleEnrollment(input: {
|
||||
institution: TellerInstitution
|
||||
enrollment: TellerTypes.Enrollment
|
||||
}) {
|
||||
const { data } = await axios.post<SharedType.AccountConnection>(
|
||||
'/teller/handle-enrollment',
|
||||
input
|
||||
)
|
||||
return data
|
||||
},
|
||||
})
|
||||
|
||||
export function useTellerApi() {
|
||||
const queryClient = useQueryClient()
|
||||
const { axios } = useAxiosWithAuth()
|
||||
const api = useMemo(() => TellerApi(axios), [axios])
|
||||
|
||||
const addConnectionToState = (connection: SharedType.AccountConnection) => {
|
||||
const accountsData = queryClient.getQueryData<SharedType.AccountsResponse>(['accounts'])
|
||||
if (!accountsData)
|
||||
queryClient.setQueryData<SharedType.AccountsResponse>(['accounts'], {
|
||||
connections: [{ ...connection, accounts: [] }],
|
||||
accounts: [],
|
||||
})
|
||||
else {
|
||||
const { connections, ...rest } = accountsData
|
||||
queryClient.setQueryData<SharedType.AccountsResponse>(['accounts'], {
|
||||
connections: [...connections, { ...connection, accounts: [] }],
|
||||
...rest,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const useHandleEnrollment = () =>
|
||||
useMutation(api.handleEnrollment, {
|
||||
onSuccess: (_connection) => {
|
||||
addConnectionToState(_connection)
|
||||
toast.success(`Account connection added!`)
|
||||
},
|
||||
onSettled: () => {
|
||||
invalidateAccountQueries(queryClient, false)
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
useHandleEnrollment,
|
||||
}
|
||||
}
|
|
@ -9,5 +9,6 @@ export * from './useQueryParam'
|
|||
export * from './useScreenSize'
|
||||
export * from './useAccountNotifications'
|
||||
export * from './usePlaid'
|
||||
export * from './useTeller'
|
||||
export * from './useProviderStatus'
|
||||
export * from './useModalManager'
|
||||
|
|
180
libs/client/shared/src/hooks/useTeller.ts
Normal file
180
libs/client/shared/src/hooks/useTeller.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import type { Logger } from '../providers/LogProvider'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useTellerApi } from '../api'
|
||||
import type {
|
||||
TellerConnectEnrollment,
|
||||
TellerConnectFailure,
|
||||
TellerConnectOptions,
|
||||
TellerConnectInstance,
|
||||
} from 'teller-connect-react'
|
||||
import useScript from 'react-script-hook'
|
||||
type TellerEnvironment = 'sandbox' | 'development' | 'production' | undefined
|
||||
type TellerAccountSelection = 'disabled' | 'single' | 'multiple' | undefined
|
||||
const TC_JS = 'https://cdn.teller.io/connect/connect.js'
|
||||
|
||||
// Create the base configuration for Teller Connect
|
||||
export const useTellerConfig = (logger: Logger) => {
|
||||
return {
|
||||
applicationId: process.env.NEXT_PUBLIC_TELLER_APP_ID ?? 'ADD_TELLER_APP_ID',
|
||||
environment: (process.env.NEXT_PUBLIC_TELLER_ENV as TellerEnvironment) ?? 'sandbox',
|
||||
selectAccount: 'disabled' as TellerAccountSelection,
|
||||
onInit: () => {
|
||||
logger.debug(`Teller Connect has initialized`)
|
||||
},
|
||||
onSuccess: {},
|
||||
onExit: () => {
|
||||
logger.debug(`Teller Connect exited`)
|
||||
},
|
||||
onFailure: (failure: TellerConnectFailure) => {
|
||||
logger.error(`Teller Connect exited with error`, failure)
|
||||
Sentry.captureEvent({
|
||||
level: 'error',
|
||||
message: 'TELLER_CONNECT_ERROR',
|
||||
tags: {
|
||||
'teller.error.code': failure.code,
|
||||
'teller.error.message': failure.message,
|
||||
},
|
||||
})
|
||||
},
|
||||
} as TellerConnectOptions
|
||||
}
|
||||
|
||||
// Custom implementation of useTellerHook to handle institution id being passed in
|
||||
export const useTellerConnect = (options: TellerConnectOptions, logger: Logger) => {
|
||||
const { useHandleEnrollment } = useTellerApi()
|
||||
const handleEnrollment = useHandleEnrollment()
|
||||
const [loading, error] = useScript({
|
||||
src: TC_JS,
|
||||
checkForExisting: true,
|
||||
})
|
||||
|
||||
const [teller, setTeller] = useState<TellerConnectInstance | null>(null)
|
||||
const [iframeLoaded, setIframeLoaded] = useState(false)
|
||||
|
||||
const createTellerInstance = (institutionId: string) => {
|
||||
return createTeller(
|
||||
{
|
||||
...options,
|
||||
onSuccess: async (enrollment: TellerConnectEnrollment) => {
|
||||
logger.debug(`User enrolled successfully`, enrollment)
|
||||
try {
|
||||
await handleEnrollment.mutateAsync({
|
||||
institution: {
|
||||
id: institutionId!,
|
||||
name: enrollment.enrollment.institution.name,
|
||||
},
|
||||
enrollment,
|
||||
})
|
||||
} catch (error) {
|
||||
toast.error(`Failed to add account`)
|
||||
}
|
||||
},
|
||||
institution: institutionId,
|
||||
onInit: () => {
|
||||
setIframeLoaded(true)
|
||||
options.onInit && options.onInit()
|
||||
},
|
||||
},
|
||||
window.TellerConnect.setup
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!options.applicationId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (error || !window.TellerConnect) {
|
||||
console.error('Error loading TellerConnect:', error)
|
||||
return
|
||||
}
|
||||
|
||||
if (teller != null) {
|
||||
teller.destroy()
|
||||
}
|
||||
|
||||
return () => teller?.destroy()
|
||||
}, [
|
||||
loading,
|
||||
error,
|
||||
options.applicationId,
|
||||
options.enrollmentId,
|
||||
options.connectToken,
|
||||
options.products,
|
||||
])
|
||||
|
||||
const ready = teller != null && (!loading || iframeLoaded)
|
||||
|
||||
const logIt = () => {
|
||||
if (!options.applicationId) {
|
||||
console.error('teller-connect-react: open() called without a valid applicationId.')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error,
|
||||
ready,
|
||||
open: (institutionId: string) => {
|
||||
logIt()
|
||||
const tellerInstance = createTellerInstance(institutionId)
|
||||
tellerInstance.open()
|
||||
setTeller(tellerInstance)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
interface ManagerState {
|
||||
teller: TellerConnectInstance | null
|
||||
open: boolean
|
||||
}
|
||||
|
||||
export const createTeller = (
|
||||
config: TellerConnectOptions,
|
||||
creator: (config: TellerConnectOptions) => TellerConnectInstance
|
||||
) => {
|
||||
const state: ManagerState = {
|
||||
teller: null,
|
||||
open: false,
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined' || !window.TellerConnect) {
|
||||
throw new Error('TellerConnect is not loaded')
|
||||
}
|
||||
|
||||
state.teller = creator({
|
||||
...config,
|
||||
onExit: () => {
|
||||
state.open = false
|
||||
config.onExit && config.onExit()
|
||||
},
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
if (!state.teller) {
|
||||
return
|
||||
}
|
||||
|
||||
state.open = true
|
||||
state.teller.open()
|
||||
}
|
||||
|
||||
const destroy = () => {
|
||||
if (!state.teller) {
|
||||
return
|
||||
}
|
||||
|
||||
state.teller.destroy()
|
||||
state.teller = null
|
||||
}
|
||||
|
||||
return {
|
||||
open,
|
||||
destroy,
|
||||
}
|
||||
}
|
|
@ -2,4 +2,3 @@ export * from './image-loaders'
|
|||
export * from './browser-utils'
|
||||
export * from './account-utils'
|
||||
export * from './form-utils'
|
||||
export * from './teller-utils'
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import * as Sentry from '@sentry/react'
|
||||
import type { Logger } from '../providers/LogProvider'
|
||||
import type {
|
||||
TellerConnectEnrollment,
|
||||
TellerConnectFailure,
|
||||
TellerConnectOptions,
|
||||
} from 'teller-connect-react'
|
||||
|
||||
type TellerEnvironment = 'sandbox' | 'development' | 'production' | undefined
|
||||
type TellerAccountSelection = 'disabled' | 'single' | 'multiple' | undefined
|
||||
|
||||
export const getTellerConfig = (logger: Logger, institutionId: string | undefined) => {
|
||||
return {
|
||||
applicationId: process.env.NEXT_PUBLIC_TELLER_APP_ID ?? 'ADD_TELLER_APP_ID',
|
||||
environment: (process.env.NEXT_PUBLIC_TELLER_ENV as TellerEnvironment) ?? 'sandbox',
|
||||
selectAccount: 'disabled' as TellerAccountSelection,
|
||||
...(institutionId !== undefined ? { institution: institutionId } : {}),
|
||||
onInit: () => {
|
||||
logger.debug(`Teller Connect has initialized`)
|
||||
},
|
||||
onSuccess: (enrollment: TellerConnectEnrollment) => {
|
||||
logger.debug(`User enrolled successfully`, enrollment)
|
||||
},
|
||||
onExit: () => {
|
||||
logger.debug(`Teller Connect exited`)
|
||||
},
|
||||
onFailure: (failure: TellerConnectFailure) => {
|
||||
logger.error(`Teller Connect exited with error`, failure)
|
||||
Sentry.captureEvent({
|
||||
level: 'error',
|
||||
message: 'TELLER_CONNECT_ERROR',
|
||||
tags: {
|
||||
'teller.error.code': failure.code,
|
||||
'teller.error.message': failure.message,
|
||||
},
|
||||
})
|
||||
},
|
||||
} as TellerConnectOptions
|
||||
}
|
|
@ -6,10 +6,11 @@ import type {
|
|||
IAccountConnectionProvider,
|
||||
} from '../../account-connection'
|
||||
import { SharedUtil } from '@maybe-finance/shared'
|
||||
import type { SharedType } from '@maybe-finance/shared'
|
||||
import type { SyncConnectionOptions, CryptoService, IETL } from '@maybe-finance/server/shared'
|
||||
import _ from 'lodash'
|
||||
import { ErrorUtil, etl } from '@maybe-finance/server/shared'
|
||||
import type { TellerApi } from '@maybe-finance/teller-api'
|
||||
import type { TellerApi, TellerTypes } from '@maybe-finance/teller-api'
|
||||
|
||||
export interface ITellerConnect {
|
||||
generateConnectUrl(userId: User['id'], institutionId: string): Promise<{ link: string }>
|
||||
|
@ -67,13 +68,22 @@ export class TellerService implements IAccountConnectionProvider, IInstitutionPr
|
|||
|
||||
async delete(connection: AccountConnection) {
|
||||
// purge teller data
|
||||
if (connection.tellerAccessToken && connection.tellerAccountId) {
|
||||
await this.teller.deleteAccount({
|
||||
accessToken: this.crypto.decrypt(connection.tellerAccessToken),
|
||||
accountId: connection.tellerAccountId,
|
||||
if (connection.tellerAccessToken && connection.tellerEnrollmentId) {
|
||||
const accounts = await this.prisma.account.findMany({
|
||||
where: { accountConnectionId: connection.id },
|
||||
})
|
||||
|
||||
this.logger.info(`Item ${connection.tellerAccountId} removed`)
|
||||
for (const account of accounts) {
|
||||
if (!account.tellerAccountId) continue
|
||||
await this.teller.deleteAccount({
|
||||
accessToken: this.crypto.decrypt(connection.tellerAccessToken),
|
||||
accountId: account.tellerAccountId,
|
||||
})
|
||||
|
||||
this.logger.info(`Teller account ${account.id} removed`)
|
||||
}
|
||||
|
||||
this.logger.info(`Teller enrollment ${connection.tellerEnrollmentId} removed`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,4 +134,51 @@ export class TellerService implements IAccountConnectionProvider, IInstitutionPr
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
async handleEnrollment(
|
||||
userId: User['id'],
|
||||
institution: Pick<TellerTypes.Institution, 'name' | 'id'>,
|
||||
enrollment: TellerTypes.Enrollment
|
||||
) {
|
||||
const connections = await this.prisma.accountConnection.findMany({
|
||||
where: { userId },
|
||||
})
|
||||
|
||||
if (connections.length > 40) {
|
||||
throw new Error('MAX_ACCOUNT_CONNECTIONS')
|
||||
}
|
||||
|
||||
const accounts = await this.teller.getAccounts({ accessToken: enrollment.accessToken })
|
||||
|
||||
this.logger.info(`Teller accounts retrieved for enrollment ${enrollment.enrollment.id}`)
|
||||
|
||||
// If all the accounts are Non-USD, throw an error
|
||||
if (accounts.every((a) => a.currency !== 'USD')) {
|
||||
throw new Error('USD_ONLY')
|
||||
}
|
||||
|
||||
// Create account connection on exchange; accounts + txns will sync later with webhook
|
||||
const [accountConnection] = await this.prisma.$transaction([
|
||||
this.prisma.accountConnection.create({
|
||||
data: {
|
||||
name: enrollment.enrollment.institution.name,
|
||||
type: 'teller' as SharedType.AccountConnectionType,
|
||||
tellerEnrollmentId: enrollment.enrollment.id,
|
||||
tellerInstitutionId: institution.id,
|
||||
tellerAccessToken: this.crypto.encrypt(enrollment.accessToken),
|
||||
userId,
|
||||
syncStatus: 'PENDING',
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
tellerUserId: enrollment.user.id,
|
||||
},
|
||||
})
|
||||
|
||||
return accountConnection
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,12 +137,12 @@ export class TellerApi {
|
|||
}
|
||||
|
||||
private async getApi(accessToken: string): Promise<AxiosInstance> {
|
||||
const cert = fs.readFileSync('./certs/certificate.pem', 'utf8')
|
||||
const key = fs.readFileSync('./certs/private_key.pem', 'utf8')
|
||||
const cert = fs.readFileSync('./certs/certificate.pem')
|
||||
const key = fs.readFileSync('./certs/private_key.pem')
|
||||
|
||||
const agent = new https.Agent({
|
||||
cert,
|
||||
key,
|
||||
cert: cert,
|
||||
key: key,
|
||||
})
|
||||
|
||||
if (!this.api) {
|
||||
|
@ -153,15 +153,10 @@ export class TellerApi {
|
|||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
this.api.interceptors.request.use((config) => {
|
||||
// Add the access_token to the auth object
|
||||
config.auth = {
|
||||
username: 'ACCESS_TOKEN',
|
||||
password: accessToken,
|
||||
}
|
||||
return config
|
||||
auth: {
|
||||
username: accessToken,
|
||||
password: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
13
libs/teller-api/src/types/enrollment.ts
Normal file
13
libs/teller-api/src/types/enrollment.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
export type Enrollment = {
|
||||
accessToken: string
|
||||
user: {
|
||||
id: string
|
||||
}
|
||||
enrollment: {
|
||||
id: string
|
||||
institution: {
|
||||
name: string
|
||||
}
|
||||
}
|
||||
signatures?: string[]
|
||||
}
|
|
@ -3,6 +3,7 @@ export * from './account-balance'
|
|||
export * from './account-details'
|
||||
export * from './authentication'
|
||||
export * from './error'
|
||||
export * from './enrollment'
|
||||
export * from './identity'
|
||||
export * from './institutions'
|
||||
export * from './transactions'
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
"react-popper": "^2.3.0",
|
||||
"react-ranger": "^2.1.0",
|
||||
"react-responsive": "^9.0.0-beta.10",
|
||||
"react-script-hook": "^1.7.2",
|
||||
"regenerator-runtime": "0.13.7",
|
||||
"sanitize-html": "^2.8.1",
|
||||
"smooth-scroll-into-view-if-needed": "^1.1.33",
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `teller_account_id` on the `account_connection` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "account_connection" DROP COLUMN "teller_account_id",
|
||||
ADD COLUMN "teller_enrollment_id" TEXT;
|
|
@ -71,8 +71,8 @@ model AccountConnection {
|
|||
finicityError Json? @map("finicity_error")
|
||||
|
||||
// teller data
|
||||
tellerAccountId String? @map("teller_account_id")
|
||||
tellerAccessToken String? @map("teller_access_token")
|
||||
tellerEnrollmentId String? @map("teller_enrollment_id")
|
||||
tellerInstitutionId String? @map("teller_institution_id")
|
||||
tellerError Json? @map("teller_error")
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue