@@ -40,6 +40,10 @@ const UNVERIFIED_DAILY_LIMIT = Math.max(
4040 1 ,
4141 Number ( process . env . UNVERIFIED_DAILY_LIMIT || 5 )
4242) ;
43+ const RECIPIENT_DAILY_RESERVATION_LIMIT = Math . max (
44+ 1 ,
45+ Number ( process . env . RECIPIENT_DAILY_RESERVATION_LIMIT || 5 )
46+ ) ;
4347
4448function sha256 ( value ) {
4549 return crypto . createHash ( 'sha256' ) . update ( value , 'utf8' ) . digest ( 'hex' ) ;
@@ -552,6 +556,22 @@ app.post('/recipient/listings/:listingId/reserve', async (req, res) => {
552556 throw new Error ( 'Idempotency key conflict.' ) ;
553557 }
554558
559+ const now = nowDate ( ) ;
560+ const from = startOfDay ( now ) ;
561+ const to = endOfDay ( now ) ;
562+ const recipientDailySnap = await tx . get (
563+ db
564+ . collection ( RESERVATIONS )
565+ . where ( 'claimerUid' , '==' , claimerUid )
566+ . where ( 'createdAt' , '>=' , from )
567+ . where ( 'createdAt' , '<' , to )
568+ ) ;
569+ if ( recipientDailySnap . size >= RECIPIENT_DAILY_RESERVATION_LIMIT ) {
570+ throw new Error (
571+ `Recipient daily reservation limit reached (${ RECIPIENT_DAILY_RESERVATION_LIMIT } ).`
572+ ) ;
573+ }
574+
555575 const listingRef = db . collection ( LISTINGS ) . doc ( listingId ) ;
556576 const listingSnap = await tx . get ( listingRef ) ;
557577 if ( ! listingSnap . exists ) {
@@ -562,7 +582,6 @@ app.post('/recipient/listings/:listingId/reserve', async (req, res) => {
562582 const expiresAt = listing . expiresAt ?. toDate
563583 ? listing . expiresAt . toDate ( )
564584 : new Date ( listing . expiresAt ) ;
565- const now = nowDate ( ) ;
566585 const quantityRemaining = Number ( listing . quantityRemaining || 0 ) ;
567586 const status = String ( listing . status || 'active' ) ;
568587
@@ -664,12 +683,17 @@ app.post('/recipient/listings/:listingId/reserve', async (req, res) => {
664683 const status = [
665684 'Listing not found.' ,
666685 'This listing is no longer available.' ,
667- 'Idempotency key conflict.'
686+ 'Idempotency key conflict.' ,
687+ `Recipient daily reservation limit reached (${ RECIPIENT_DAILY_RESERVATION_LIMIT } ).`
668688 ] . includes ( message )
669- ? 400
689+ ? message . startsWith ( 'Recipient daily reservation limit reached' )
690+ ? 429
691+ : 400
670692 : 500 ;
671693 const reasonCode = message === 'Idempotency key conflict.'
672694 ? 'IDEMPOTENCY_KEY_CONFLICT'
695+ : message . startsWith ( 'Recipient daily reservation limit reached' )
696+ ? 'RECIPIENT_DAILY_LIMIT_REACHED'
673697 : status == 400
674698 ? 'RESERVE_FAILED_BUSINESS_RULE'
675699 : 'RESERVE_FAILED_INTERNAL' ;
0 commit comments