@@ -14,7 +14,7 @@ import {
1414import { and , eq , isNull , like , not , or } from 'drizzle-orm' ;
1515import type Stripe from 'stripe' ;
1616
17- import { db } from '@/lib/drizzle' ;
17+ import { db , type DrizzleTransaction } from '@/lib/drizzle' ;
1818import { client } from '@/lib/stripe-client' ;
1919
2020type StripeReference = string | { id : string } | null | undefined ;
@@ -60,7 +60,10 @@ function stripeReferenceId(reference: StripeReference): string | null {
6060 return reference ?. id || null ;
6161}
6262
63- async function resolveOwner ( customerId : string | null ) : Promise < OwnerResolution > {
63+ async function resolveOwner (
64+ database : DrizzleTransaction ,
65+ customerId : string | null
66+ ) : Promise < OwnerResolution > {
6467 if ( ! customerId ) {
6568 return {
6669 classification : StripeEarlyFraudWarningOwnerClassification . Unmatched ,
@@ -70,28 +73,26 @@ async function resolveOwner(customerId: string | null): Promise<OwnerResolution>
7073 } ;
7174 }
7275
73- const [ personalOwners , organizationOwners ] = await Promise . all ( [
74- db
75- . select ( { id : kilocode_users . id } )
76- . from ( kilocode_users )
77- . where (
78- and (
79- eq ( kilocode_users . stripe_customer_id , customerId ) ,
80- or (
81- isNull ( kilocode_users . blocked_reason ) ,
82- not ( like ( kilocode_users . blocked_reason , 'soft-deleted at %' ) )
83- )
76+ // Keep the case insert ordered with softDeleteUser's link scrubbing for matched user rows.
77+ const personalOwners = await database
78+ . select ( { id : kilocode_users . id } )
79+ . from ( kilocode_users )
80+ . where (
81+ and (
82+ eq ( kilocode_users . stripe_customer_id , customerId ) ,
83+ or (
84+ isNull ( kilocode_users . blocked_reason ) ,
85+ not ( like ( kilocode_users . blocked_reason , 'soft-deleted at %' ) )
8486 )
8587 )
86- . limit ( 2 ) ,
87- db
88- . select ( { id : organizations . id } )
89- . from ( organizations )
90- . where (
91- and ( eq ( organizations . stripe_customer_id , customerId ) , isNull ( organizations . deleted_at ) )
92- )
93- . limit ( 2 ) ,
94- ] ) ;
88+ )
89+ . limit ( 2 )
90+ . for ( 'update' ) ;
91+ const organizationOwners = await database
92+ . select ( { id : organizations . id } )
93+ . from ( organizations )
94+ . where ( and ( eq ( organizations . stripe_customer_id , customerId ) , isNull ( organizations . deleted_at ) ) )
95+ . limit ( 2 ) ;
9596
9697 if ( personalOwners . length === 1 && organizationOwners . length === 0 ) {
9798 return {
@@ -128,8 +129,11 @@ async function resolveOwner(customerId: string | null): Promise<OwnerResolution>
128129 } ;
129130}
130131
131- async function persistReviewCase ( values : ReviewCaseValues ) : Promise < void > {
132- await db
132+ async function persistReviewCase (
133+ database : typeof db | DrizzleTransaction ,
134+ values : ReviewCaseValues
135+ ) : Promise < void > {
136+ await database
133137 . insert ( stripe_early_fraud_warning_cases )
134138 . values ( {
135139 stripe_early_fraud_warning_id : values . earlyFraudWarningId ,
@@ -165,7 +169,7 @@ export async function observeStripeEarlyFraudWarningCreated({
165169 const paymentIntentId = stripeReferenceId ( earlyFraudWarning . payment_intent ) ;
166170
167171 if ( ! chargeId ) {
168- await persistReviewCase ( {
172+ await persistReviewCase ( db , {
169173 eventId,
170174 earlyFraudWarningId : earlyFraudWarning . id ,
171175 chargeId : null ,
@@ -196,7 +200,7 @@ export async function observeStripeEarlyFraudWarningCreated({
196200 stripe_charge_id : chargeId ,
197201 } ,
198202 } ) ;
199- await persistReviewCase ( {
203+ await persistReviewCase ( db , {
200204 eventId,
201205 earlyFraudWarningId : earlyFraudWarning . id ,
202206 chargeId,
@@ -216,22 +220,24 @@ export async function observeStripeEarlyFraudWarningCreated({
216220 return null ;
217221 }
218222
219- const owner = await resolveOwner ( stripeReferenceId ( charge . customer ) ) ;
220- await persistReviewCase ( {
221- eventId,
222- earlyFraudWarningId : earlyFraudWarning . id ,
223- chargeId,
224- paymentIntentId : paymentIntentId ?? stripeReferenceId ( charge . payment_intent ) ,
225- customerId : stripeReferenceId ( charge . customer ) ,
226- amountMinorUnits : charge . amount ,
227- currency : charge . currency ,
228- owner : charge . disputed
229- ? {
230- ...owner ,
231- reason : 'Warned charge is already disputed; manual review required' ,
232- }
233- : owner ,
234- warningCreatedAt,
223+ await db . transaction ( async tx => {
224+ const owner = await resolveOwner ( tx , stripeReferenceId ( charge . customer ) ) ;
225+ await persistReviewCase ( tx , {
226+ eventId,
227+ earlyFraudWarningId : earlyFraudWarning . id ,
228+ chargeId,
229+ paymentIntentId : paymentIntentId ?? stripeReferenceId ( charge . payment_intent ) ,
230+ customerId : stripeReferenceId ( charge . customer ) ,
231+ amountMinorUnits : charge . amount ,
232+ currency : charge . currency ,
233+ owner : charge . disputed
234+ ? {
235+ ...owner ,
236+ reason : 'Warned charge is already disputed; manual review required' ,
237+ }
238+ : owner ,
239+ warningCreatedAt,
240+ } ) ;
235241 } ) ;
236242
237243 return charge ;
0 commit comments