Skip to content

Commit

Permalink
Upload document flow & get doc status
Browse files Browse the repository at this point in the history
  • Loading branch information
IanPhilips committed Jun 25, 2024
1 parent 44210bb commit fff7c6e
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 154 deletions.
2 changes: 2 additions & 0 deletions backend/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ import { getCurrentPrivateUser } from './get-current-private-user'
import { updatePrivateUser } from './update-private-user'
import { setPushToken } from './push-token'
import { updateNotifSettings } from './update-notif-settings'
import { getVerificationDocuments } from 'api/gidx/get-verification-documents'

const allowCorsUnrestricted: RequestHandler = cors({})

Expand Down Expand Up @@ -345,6 +346,7 @@ const handlers: { [k in APIPath]: APIHandler<k> } = {
'get-verification-status-gidx': getVerificationStatus,
'upload-document-gidx': uploadDocument,
'callback-gidx': callbackGIDX,
'get-verification-documents-gidx': getVerificationDocuments,
}

Object.entries(handlers).forEach(([path, handler]) => {
Expand Down
90 changes: 90 additions & 0 deletions backend/api/src/gidx/get-verification-documents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { APIError, APIHandler } from 'api/helpers/endpoint'
import { log } from 'shared/utils'
import { getGIDXStandardParams } from 'shared/gidx/helpers'
import { GIDX_REGISTATION_ENABLED, GIDXDocument } from 'common/gidx/gidx'

export const getVerificationDocuments: APIHandler<
'get-verification-documents-gidx'
> = async (_, auth) => {
if (!GIDX_REGISTATION_ENABLED)
throw new APIError(400, 'GIDX registration is disabled')
const {
documents,
unrejectedUtilityDocuments,
unrejectedIdDocuments,
rejectedDocuments,
} = await getIdentityVerificationDocuments(auth.uid)

return {
status: 'success',
documents,
utilityDocuments: unrejectedUtilityDocuments,
idDocuments: unrejectedIdDocuments,
rejectedDocuments,
}
}

export const getIdentityVerificationDocuments = async (userId: string) => {
const ENDPOINT =
'https://api.gidx-service.in/v3.0/api/DocumentLibrary/CustomerDocuments'
const body = {
MerchantCustomerID: userId,
...getGIDXStandardParams(),
} as Record<string, string>
const queryParams = new URLSearchParams(body).toString()
const urlWithParams = `${ENDPOINT}?${queryParams}`

const res = await fetch(urlWithParams)
if (!res.ok) {
throw new APIError(400, 'GIDX verification session failed')
}

const data = (await res.json()) as DocumentCheck
log(
'Registration response:',
data.ResponseMessage,
'docs',
data.DocumentCount,
'userId',
data.MerchantCustomerID
)
const { Documents: documents } = data

const isRejected = (doc: GIDXDocument) =>
doc.DocumentStatus === 3 &&
doc.DocumentNotes.length > 0 &&
!doc.DocumentNotes.some((n) => n.NoteText == acceptDocText)

const acceptedDocuments = documents.filter(
(doc) =>
doc.DocumentStatus === 3 &&
doc.DocumentNotes.length > 0 &&
doc.DocumentNotes.some((n) => n.NoteText == acceptDocText)
)
const rejectedDocuments = documents.filter(isRejected)
const unrejectedUtilityDocuments = documents.filter(
(doc) =>
(doc.CategoryType === 7 || doc.CategoryType === 1) && !isRejected(doc)
)
const unrejectedIdDocuments = documents.filter(
(doc) => doc.CategoryType != 7 && doc.CategoryType != 1 && !isRejected(doc)
)

return {
documents,
rejectedDocuments,
acceptedDocuments,
unrejectedUtilityDocuments,
unrejectedIdDocuments,
}
}

type DocumentCheck = {
ResponseCode: number
ResponseMessage: string
MerchantCustomerID: string
DocumentCount: number
Documents: GIDXDocument[]
}

const acceptDocText = 'Review Complete - Customer Identity Verified'
74 changes: 8 additions & 66 deletions backend/api/src/gidx/get-verification-status.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import { APIError, APIHandler } from 'api/helpers/endpoint'
import { getPrivateUserSupabase, log } from 'shared/utils'
import { getPrivateUserSupabase } from 'shared/utils'
import { getPhoneNumber } from 'shared/helpers/get-phone-number'
import {
getGIDXCustomerProfile,
getGIDXStandardParams,
} from 'shared/gidx/helpers'
import {
GIDX_DOCUMENTS_REQUIRED,
GIDX_REGISTATION_ENABLED,
GIDXCustomerProfile,
GIDXDocument,
} from 'common/gidx/gidx'
import { updateUser } from 'shared/supabase/users'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { getGIDXCustomerProfile } from 'shared/gidx/helpers'
import { GIDX_REGISTATION_ENABLED, GIDXCustomerProfile } from 'common/gidx/gidx'
import { processUserReasonCodes } from 'api/gidx/register'
const ENDPOINT =
'https://api.gidx-service.in/v3.0/api/DocumentLibrary/CustomerDocuments'
import { getIdentityVerificationDocuments } from 'api/gidx/get-verification-documents'

export const getVerificationStatus: APIHandler<
'get-verification-status-gidx'
Expand Down Expand Up @@ -43,71 +32,24 @@ export const getVerificationStatusInternal = async (
}
const { ReasonCodes, FraudConfidenceScore, IdentityConfidenceScore } =
customerProfile

const { status, message } = await processUserReasonCodes(
userId,
ReasonCodes,
FraudConfidenceScore,
IdentityConfidenceScore
IdentityConfidenceScore,
true
)
if (status === 'error') {
return {
status: 'error',
message,
}
}
const body = {
MerchantCustomerID: userId,
...getGIDXStandardParams(),
} as Record<string, string>
const queryParams = new URLSearchParams(body).toString()
const urlWithParams = `${ENDPOINT}?${queryParams}`

const res = await fetch(urlWithParams)
if (!res.ok) {
throw new APIError(400, 'GIDX verification session failed')
}

const data = (await res.json()) as DocumentCheck
log(
'Registration response:',
data.ResponseMessage,
'docs',
data.DocumentCount,
'userId',
data.MerchantCustomerID
)
const { Documents: documents } = data

const pg = createSupabaseDirectClient()

if (
documents.filter(
(doc) => doc.DocumentStatus === 3 && doc.DocumentNotes.length === 0
).length >= GIDX_DOCUMENTS_REQUIRED
) {
await updateUser(pg, userId, {
kycStatus: 'verified',
})
} else if (
documents.filter(
(doc) => doc.DocumentStatus === 3 && doc.DocumentNotes.length > 0
).length > 0
) {
await updateUser(pg, userId, {
kycStatus: 'await-more-documents',
})
}
const { documents } = await getIdentityVerificationDocuments(userId)

return {
status: 'success',
documents,
}
}

type DocumentCheck = {
ResponseCode: number
ResponseMessage: string
MerchantCustomerID: string
DocumentCount: number
Documents: GIDXDocument[]
}
57 changes: 52 additions & 5 deletions backend/api/src/gidx/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import {
import { intersection } from 'lodash'
import { getGIDXStandardParams } from 'shared/gidx/helpers'
import {
GIDX_DOCUMENTS_REQUIRED,
GIDX_REGISTATION_ENABLED,
GIDXRegistrationResponse,
} from 'common/gidx/gidx'
import { getIdentityVerificationDocuments } from 'api/gidx/get-verification-documents'

const ENDPOINT =
'https://api.gidx-service.in/v3.0/api/CustomerIdentity/CustomerRegistration'
Expand Down Expand Up @@ -70,7 +72,8 @@ export const register: APIHandler<'register-gidx'> = async (
auth.uid,
ReasonCodes,
FraudConfidenceScore,
IdentityConfidenceScore
IdentityConfidenceScore,
false
)
return {
status,
Expand All @@ -82,7 +85,8 @@ export const processUserReasonCodes = async (
userId: string,
ReasonCodes: string[],
FraudConfidenceScore: number,
IdentityConfidenceScore: number
IdentityConfidenceScore: number,
queryForDocuments: boolean
): Promise<RegistrationReturnType> => {
const pg = createSupabaseDirectClient()

Expand Down Expand Up @@ -185,9 +189,52 @@ export const processUserReasonCodes = async (
// User is not blocked and ID is verified
if (ReasonCodes.includes('ID-VERIFIED')) {
log('Registration passed with allowed codes:', ReasonCodes)
await updateUser(pg, userId, {
kycStatus: 'await-documents',
})
// New user, no documents yet
if (!queryForDocuments) {
await updateUser(pg, userId, {
kycStatus: 'await-documents',
})
return { status: 'success' }
}

const {
documents,
unrejectedUtilityDocuments,
unrejectedIdDocuments,
acceptedDocuments,
rejectedDocuments,
} = await getIdentityVerificationDocuments(userId)
const acceptedUtilityDocuments = unrejectedUtilityDocuments.filter(
(doc) => doc.DocumentStatus === 3
)
const acceptedIdDocuments = unrejectedIdDocuments.filter(
(doc) => doc.DocumentStatus === 3
)
const pendingDocuments = documents.filter((doc) => doc.DocumentStatus !== 3)
if (
acceptedDocuments.length >= GIDX_DOCUMENTS_REQUIRED &&
acceptedUtilityDocuments.length > 0 &&
acceptedIdDocuments.length > 0
) {
// They passed the reason codes and have the required documents
await updateUser(pg, userId, {
kycStatus: 'verified',
})
} else if (
acceptedDocuments.length < GIDX_DOCUMENTS_REQUIRED &&
pendingDocuments.length > 0
) {
await updateUser(pg, userId, {
kycStatus: 'pending',
})
} else if (
rejectedDocuments.length > 0 ||
acceptedDocuments.length < GIDX_DOCUMENTS_REQUIRED
) {
await updateUser(pg, userId, {
kycStatus: 'await-documents',
})
}
return { status: 'success' }
}

Expand Down
20 changes: 17 additions & 3 deletions backend/api/src/gidx/upload-document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { isProd, log } from 'shared/utils'
import * as admin from 'firebase-admin'
import { PROD_CONFIG } from 'common/envs/prod'
import { DEV_CONFIG } from 'common/envs/dev'
import { updateUser } from 'shared/supabase/users'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import {
DocumentRegistrationResponse,
GIDX_DOCUMENTS_REQUIRED,
GIDX_REGISTATION_ENABLED,
} from 'common/gidx/gidx'
import { getIdentityVerificationDocuments } from 'api/gidx/get-verification-documents'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { updateUser } from 'shared/supabase/users'

const ENDPOINT =
'https://api.gidx-service.in/v3.0/api/DocumentLibrary/DocumentRegistration'
Expand Down Expand Up @@ -54,8 +56,20 @@ export const uploadDocument: APIHandler<'upload-document-gidx'> = async (
Document.FileName
)
await deleteFileFromFirebase(fileUrl)
const { documents, unrejectedIdDocuments, unrejectedUtilityDocuments } =
await getIdentityVerificationDocuments(auth.uid)

const pg = createSupabaseDirectClient()
await updateUser(pg, auth.uid, { kycStatus: 'pending' })
if (
documents.length >= GIDX_DOCUMENTS_REQUIRED &&
unrejectedUtilityDocuments.length > 0 &&
unrejectedIdDocuments.length > 0
) {
// They passed the reason codes and have the required documents
await updateUser(pg, auth.uid, {
kycStatus: 'pending',
})
}
return { status: 'success' }
}

Expand Down
13 changes: 13 additions & 0 deletions common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,19 @@ export const API = (_apiTypeCheck = {
},
props: z.object({}),
},
'get-verification-documents-gidx': {
method: 'POST',
visibility: 'undocumented',
authed: true,
returns: {} as {
status: string
documents: GIDXDocument[]
utilityDocuments: GIDXDocument[]
idDocuments: GIDXDocument[]
rejectedDocuments: GIDXDocument[]
},
props: z.object({}),
},
'upload-document-gidx': {
method: 'POST',
visibility: 'undocumented',
Expand Down
12 changes: 7 additions & 5 deletions common/src/gidx/gidx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ export type GIDXDocument = {
FileName: string
FileSize: number
DateTime: string
DocumentNotes: {
AuthorName: string
NoteText: string
DateTime: string
}[]
DocumentNotes: DocumentNote[]
}

export type DocumentNote = {
AuthorName: string
NoteText: string
DateTime: string
}

export type GPSData = {
Expand Down
1 change: 0 additions & 1 deletion common/src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export type User = {
| 'temporary-block'
| 'verified'
| 'pending'
| 'await-more-documents'
| 'await-documents'
}

Expand Down
Loading

0 comments on commit fff7c6e

Please sign in to comment.