( code: string, companyName: string, contactEmail: string )
| 300 | * Tracks company name and contact email for later reconciliation. |
| 301 | */ |
| 302 | export async function redeemReferralCodeForInvoice( |
| 303 | code: string, |
| 304 | companyName: string, |
| 305 | contactEmail: string |
| 306 | ): Promise<Referral | null> { |
| 307 | const referralCode = await getReferralCode(code); |
| 308 | |
| 309 | if (!referralCode || referralCode.status !== 'active') return null; |
| 310 | if (referralCode.expires_at && referralCode.expires_at < new Date()) return null; |
| 311 | if (referralCode.max_uses !== null && referralCode.used_count >= referralCode.max_uses) return null; |
| 312 | |
| 313 | const result = await query<Referral>( |
| 314 | `WITH updated_code AS ( |
| 315 | UPDATE referral_codes |
| 316 | SET used_count = used_count + 1, updated_at = NOW() |
| 317 | WHERE id = $1 |
| 318 | AND status = 'active' |
| 319 | AND (max_uses IS NULL OR used_count < max_uses) |
| 320 | RETURNING id, referrer_org_id, referrer_user_id, target_company_name |
| 321 | ) |
| 322 | INSERT INTO referrals |
| 323 | (referral_code_id, referral_code, referrer_org_id, referrer_user_id, |
| 324 | target_company_name, referred_company_name, referred_contact_email, status) |
| 325 | SELECT |
| 326 | uc.id, $2, uc.referrer_org_id, uc.referrer_user_id, |
| 327 | uc.target_company_name, $3, $4, 'pending' |
| 328 | FROM updated_code uc |
| 329 | RETURNING *`, |
| 330 | [referralCode.id, code.toUpperCase(), companyName, contactEmail] |
| 331 | ); |
| 332 | |
| 333 | const referral = result.rows[0] || null; |
| 334 | if (referral) { |
| 335 | logger.info( |
| 336 | { referralId: referral.id, code, company: companyName }, |
| 337 | 'Referral code redeemed for invoice' |
| 338 | ); |
| 339 | } |
| 340 | return referral; |
| 341 | } |
| 342 | |
| 343 | /** |
| 344 | * Mark an accepted referral as converted after successful payment. |
nothing calls this directly
no test coverage detected