(params: {
userId: string
stripeCustomerId?: string | null
creditsToConsume: number
logger: Logger
})
| 441 | * @returns Promise resolving to number of credits consumed |
| 442 | */ |
| 443 | export async function consumeCredits(params: { |
| 444 | userId: string |
| 445 | stripeCustomerId?: string | null |
| 446 | creditsToConsume: number |
| 447 | logger: Logger |
| 448 | }): Promise<CreditConsumptionResult> { |
| 449 | const { userId, creditsToConsume, logger } = params |
| 450 | |
| 451 | const { result, lockWaitMs } = await withAdvisoryLockTransaction({ |
| 452 | callback: async (tx) => { |
| 453 | const now = new Date() |
| 454 | const activeGrants = await getOrderedActiveGrantsForConsumption({ |
| 455 | ...params, |
| 456 | now, |
| 457 | conn: tx, |
| 458 | }) |
| 459 | |
| 460 | if (activeGrants.length === 0) { |
| 461 | logger.error( |
| 462 | { userId, creditsToConsume }, |
| 463 | 'No active grants found to consume credits from', |
| 464 | ) |
| 465 | throw new Error('No active grants found') |
| 466 | } |
| 467 | |
| 468 | const consumeResult = await consumeFromOrderedGrants({ |
| 469 | ...params, |
| 470 | creditsToConsume, |
| 471 | grants: activeGrants, |
| 472 | tx, |
| 473 | }) |
| 474 | |
| 475 | return consumeResult |
| 476 | }, |
| 477 | lockKey: `user:${userId}`, |
| 478 | context: { userId, creditsToConsume }, |
| 479 | logger, |
| 480 | }) |
| 481 | |
| 482 | // Log successful credit consumption with lock timing |
| 483 | logger.info( |
| 484 | { |
| 485 | userId, |
| 486 | creditsConsumed: result.consumed, |
| 487 | creditsRequested: creditsToConsume, |
| 488 | fromPurchased: result.fromPurchased, |
| 489 | lockWaitMs, |
| 490 | }, |
| 491 | 'Credits consumed', |
| 492 | ) |
| 493 | |
| 494 | // Track credit consumption analytics |
| 495 | trackEvent({ |
| 496 | event: AnalyticsEvent.CREDIT_CONSUMED, |
| 497 | userId, |
| 498 | properties: { |
| 499 | creditsConsumed: result.consumed, |
| 500 | creditsRequested: creditsToConsume, |
no test coverage detected