@@ -346,6 +346,13 @@ export type checkCustomerParams = {
346346 stripeApiKey : string ;
347347} ;
348348
349+ export type CheckOrCreateResult = {
350+ customerId : string ;
351+ needsConfirmation ?: boolean ;
352+ current ?: { name ?: string | null ; email ?: string | null } ;
353+ incoming ?: { name : string ; email : string } ;
354+ } ;
355+
349356export const checkOrCreateCustomer = async ( {
350357 acmOrg,
351358 emailDomain,
@@ -354,7 +361,7 @@ export const checkOrCreateCustomer = async ({
354361 customerEmail,
355362 customerName,
356363 stripeApiKey,
357- } : checkCustomerParams ) : Promise < string > => {
364+ } : checkCustomerParams ) : Promise < CheckOrCreateResult > => {
358365 const lock = createLock ( {
359366 adapter : new IoredisAdapter ( redisClient ) ,
360367 key : `stripe:${ acmOrg } :${ emailDomain } ` ,
@@ -363,6 +370,7 @@ export const checkOrCreateCustomer = async ({
363370 } ) as SimpleLock ;
364371
365372 const pk = `${ acmOrg } #${ emailDomain } ` ;
373+ const normalizedEmail = customerEmail . trim ( ) . toLowerCase ( ) ;
366374
367375 return await lock . using ( async ( ) => {
368376 const checkCustomer = new QueryCommand ( {
@@ -379,10 +387,11 @@ export const checkOrCreateCustomer = async ({
379387
380388 if ( customerResponse . Count === 0 ) {
381389 const customer = await createStripeCustomer ( {
382- email : customerEmail ,
390+ email : normalizedEmail ,
383391 name : customerName ,
384392 stripeApiKey,
385393 } ) ;
394+
386395 const createCustomer = new TransactWriteItemsCommand ( {
387396 TransactItems : [
388397 {
@@ -398,15 +407,84 @@ export const checkOrCreateCustomer = async ({
398407 } ,
399408 { removeUndefinedValues : true } ,
400409 ) ,
410+ ConditionExpression :
411+ "attribute_not_exists(primaryKey) AND attribute_not_exists(sortKey)" ,
412+ } ,
413+ } ,
414+ {
415+ Put : {
416+ TableName : genericConfig . StripePaymentsDynamoTableName ,
417+ Item : marshall (
418+ {
419+ primaryKey : pk ,
420+ sortKey : `EMAIL#${ normalizedEmail } ` ,
421+ stripeCustomerId : customer ,
422+ createdAt : new Date ( ) . toISOString ( ) ,
423+ } ,
424+ { removeUndefinedValues : true } ,
425+ ) ,
426+ ConditionExpression :
427+ "attribute_not_exists(primaryKey) AND attribute_not_exists(sortKey)" ,
401428 } ,
402429 } ,
403430 ] ,
404431 } ) ;
405432 await dynamoClient . send ( createCustomer ) ;
406- return customer ;
433+ return { customerId : customer } ;
407434 }
408435
409- return customerResponse . Items ! [ 0 ] . stripeCustomerId . S ! ;
436+ const existingCustomerId = ( customerResponse . Items ! [ 0 ] as any )
437+ . stripeCustomerId . S as string ;
438+
439+ const stripeClient = new Stripe ( stripeApiKey ) ;
440+ const stripeCustomer =
441+ await stripeClient . customers . retrieve ( existingCustomerId ) ;
442+
443+ const liveName =
444+ "name" in stripeCustomer ? ( stripeCustomer as any ) . name : null ;
445+ const liveEmail =
446+ "email" in stripeCustomer ? ( stripeCustomer as any ) . email : null ;
447+
448+ const needsConfirmation =
449+ ( ! ! liveName && liveName !== customerName ) ||
450+ ( ! ! liveEmail && liveEmail . toLowerCase ( ) !== normalizedEmail ) ;
451+
452+ const ensureEmailMap = new TransactWriteItemsCommand ( {
453+ TransactItems : [
454+ {
455+ Put : {
456+ TableName : genericConfig . StripePaymentsDynamoTableName ,
457+ Item : marshall (
458+ {
459+ primaryKey : pk ,
460+ sortKey : `EMAIL#${ normalizedEmail } ` ,
461+ stripeCustomerId : existingCustomerId ,
462+ createdAt : new Date ( ) . toISOString ( ) ,
463+ } ,
464+ { removeUndefinedValues : true } ,
465+ ) ,
466+ ConditionExpression :
467+ "attribute_not_exists(primaryKey) AND attribute_not_exists(sortKey)" ,
468+ } ,
469+ } ,
470+ ] ,
471+ } ) ;
472+ try {
473+ await dynamoClient . send ( ensureEmailMap ) ;
474+ } catch ( e ) {
475+ // ignore
476+ }
477+
478+ if ( needsConfirmation ) {
479+ return {
480+ customerId : existingCustomerId ,
481+ needsConfirmation : true ,
482+ current : { name : liveName ?? null , email : liveEmail ?? null } ,
483+ incoming : { name : customerName , email : normalizedEmail } ,
484+ } ;
485+ }
486+
487+ return { customerId : existingCustomerId } ;
410488 } ) ;
411489} ;
412490
@@ -432,10 +510,10 @@ export const addInvoice = async ({
432510 redisClient,
433511 dynamoClient,
434512 stripeApiKey,
435- } : InvoiceAddParams ) : Promise < string > => {
513+ } : InvoiceAddParams ) : Promise < CheckOrCreateResult > => {
436514 const pk = `${ acmOrg } #${ emailDomain } ` ;
437515
438- const customerId = await checkOrCreateCustomer ( {
516+ const result = await checkOrCreateCustomer ( {
439517 acmOrg,
440518 emailDomain,
441519 redisClient,
@@ -445,6 +523,10 @@ export const addInvoice = async ({
445523 stripeApiKey,
446524 } ) ;
447525
526+ if ( result . needsConfirmation ) {
527+ return result ;
528+ }
529+
448530 const dynamoCommand = new TransactWriteItemsCommand ( {
449531 TransactItems : [
450532 {
@@ -476,5 +558,5 @@ export const addInvoice = async ({
476558 } ) ;
477559
478560 await dynamoClient . send ( dynamoCommand ) ;
479- return customerId ;
561+ return { customerId : result . customerId } ;
480562} ;
0 commit comments