1- import { veriffDecisionEventSchema } from "@/lib/veriff/schema" ;
1+ import { computeVeriffIdentityHash } from "@/lib/veriff/compute-veriff-identity-hash" ;
2+ import { VeriffDecisionEvent } from "@/lib/veriff/schema" ;
23import {
34 mergeVeriffMetadata ,
45 parseVeriffMetadata ,
@@ -9,9 +10,6 @@ import PartnerIdentityVerified from "@dub/email/templates/partner-identity-verif
910import { prisma } from "@dub/prisma" ;
1011import { IdentityVerificationStatus , Partner } from "@dub/prisma/client" ;
1112import { logAndRespond } from "app/(ee)/api/cron/utils" ;
12- import * as z from "zod/v4" ;
13-
14- type VeriffDecisionEvent = z . infer < typeof veriffDecisionEventSchema > ;
1513
1614const veriffStatusMap : Record <
1715 VeriffDecisionEvent [ "verification" ] [ "status" ] ,
@@ -56,6 +54,7 @@ export const handleDecisionEvent = async ({
5654
5755 // since we're skipping verified partners, by default identityVerifiedAt is null
5856 let identityVerifiedAt : Date | null = null ;
57+ let veriffIdentityHash : string | null | undefined = undefined ;
5958
6059 let { sessionUrl, attemptCount, declineReason, sessionExpiresAt } =
6160 parseVeriffMetadata ( partner . veriffMetadata ) ;
@@ -65,12 +64,22 @@ export const handleDecisionEvent = async ({
6564
6665 // If the verification was approved, check for country mismatch
6766 if ( effectiveStatus === "approved" ) {
67+ veriffIdentityHash = computeVeriffIdentityHash ( verification ) ;
68+ const isDuplicate = await checkDuplicateIdentity ( {
69+ partner,
70+ veriffIdentityHash,
71+ } ) ;
72+
6873 const isCountryMismatch = checkCountryMismatch ( {
6974 partner,
7075 verification,
7176 } ) ;
7277
73- if ( isCountryMismatch ) {
78+ if ( isDuplicate ) {
79+ effectiveStatus = "declined" ;
80+ declineReason =
81+ "This identity has already been verified on another account." ;
82+ } else if ( isCountryMismatch ) {
7483 effectiveStatus = "declined" ;
7584 declineReason = `Your document country (${ verification . document ?. country } ) does not match your account country (${ partner . country } )` ;
7685 } else {
@@ -100,6 +109,8 @@ export const handleDecisionEvent = async ({
100109 data : {
101110 identityVerificationStatus : veriffStatusMap [ effectiveStatus ] ,
102111 identityVerifiedAt,
112+ veriffIdentityHash :
113+ effectiveStatus === "approved" ? veriffIdentityHash : null ,
103114 veriffMetadata,
104115 } ,
105116 select : {
@@ -137,6 +148,32 @@ function checkCountryMismatch({
137148 return partner . country . toUpperCase ( ) !== veriffCountry ;
138149}
139150
151+ async function checkDuplicateIdentity ( {
152+ partner,
153+ veriffIdentityHash,
154+ } : {
155+ partner : Pick < Partner , "id" > ;
156+ veriffIdentityHash : string | null ;
157+ } ) : Promise < boolean > {
158+ if ( ! veriffIdentityHash ) {
159+ return false ;
160+ }
161+
162+ const duplicatePartner = await prisma . partner . findFirst ( {
163+ where : {
164+ veriffIdentityHash,
165+ id : {
166+ not : partner . id ,
167+ } ,
168+ } ,
169+ select : {
170+ id : true ,
171+ } ,
172+ } ) ;
173+
174+ return ! ! duplicatePartner ;
175+ }
176+
140177async function sendEmailNotification ( {
141178 partner,
142179 attemptId,
0 commit comments