( code: string, referredOrgId: string, referredUserId: string )
| 170 | * Atomically increments used_count to prevent over-use. |
| 171 | */ |
| 172 | export async function acceptReferralCode( |
| 173 | code: string, |
| 174 | referredOrgId: string, |
| 175 | referredUserId: string |
| 176 | ): Promise<Referral | null> { |
| 177 | const referralCode = await getReferralCode(code); |
| 178 | |
| 179 | if (!referralCode) { |
| 180 | logger.warn({ code }, 'Referral code not found'); |
| 181 | return null; |
| 182 | } |
| 183 | |
| 184 | if (referralCode.status !== 'active') { |
| 185 | logger.warn({ code, status: referralCode.status }, 'Referral code is not active'); |
| 186 | return null; |
| 187 | } |
| 188 | |
| 189 | if (referralCode.expires_at && referralCode.expires_at < new Date()) { |
| 190 | await query( |
| 191 | `UPDATE referral_codes SET status = 'expired', updated_at = NOW() WHERE id = $1`, |
| 192 | [referralCode.id] |
| 193 | ); |
| 194 | logger.warn({ code }, 'Referral code expired'); |
| 195 | return null; |
| 196 | } |
| 197 | |
| 198 | if (referralCode.max_uses !== null && referralCode.used_count >= referralCode.max_uses) { |
| 199 | logger.warn({ code }, 'Referral code has reached max uses'); |
| 200 | return null; |
| 201 | } |
| 202 | |
| 203 | // Atomically increment used_count and create the referral in 'accepted' state |
| 204 | const referralResult = await query<Referral>( |
| 205 | `WITH updated_code AS ( |
| 206 | UPDATE referral_codes |
| 207 | SET used_count = used_count + 1, updated_at = NOW() |
| 208 | WHERE id = $1 |
| 209 | AND status = 'active' |
| 210 | AND (max_uses IS NULL OR used_count < max_uses) |
| 211 | RETURNING id, referrer_org_id, referrer_user_id, target_company_name |
| 212 | ) |
| 213 | INSERT INTO referrals |
| 214 | (referral_code_id, referral_code, referrer_org_id, referrer_user_id, |
| 215 | target_company_name, referred_org_id, referred_user_id, |
| 216 | status, accepted_at, expires_at) |
| 217 | SELECT |
| 218 | uc.id, $2, uc.referrer_org_id, uc.referrer_user_id, |
| 219 | uc.target_company_name, $3, $4, |
| 220 | 'accepted', NOW(), NOW() + INTERVAL '30 days' |
| 221 | FROM updated_code uc |
| 222 | RETURNING *`, |
| 223 | [referralCode.id, code.toUpperCase(), referredOrgId, referredUserId] |
| 224 | ); |
| 225 | |
| 226 | if (referralResult.rows.length === 0) { |
| 227 | logger.warn({ code }, 'Referral code could not be accepted (race condition or already maxed)'); |
| 228 | return null; |
| 229 | } |
no test coverage detected