(params: {
organizationId: string
creditsToConsume: number
logger: Logger
})
| 269 | * Uses advisory locks to serialize credit operations per organization. |
| 270 | */ |
| 271 | export async function consumeOrganizationCredits(params: { |
| 272 | organizationId: string |
| 273 | creditsToConsume: number |
| 274 | logger: Logger |
| 275 | }): Promise<CreditConsumptionResult> { |
| 276 | const { organizationId, creditsToConsume, logger } = params |
| 277 | |
| 278 | const { result, lockWaitMs } = await withAdvisoryLockTransaction({ |
| 279 | callback: async (tx) => { |
| 280 | const now = new Date() |
| 281 | const activeGrants = await getOrderedActiveOrganizationGrants({ |
| 282 | ...params, |
| 283 | now, |
| 284 | conn: tx, |
| 285 | }) |
| 286 | |
| 287 | if (activeGrants.length === 0) { |
| 288 | logger.error( |
| 289 | { organizationId, creditsToConsume }, |
| 290 | 'No active organization grants found to consume credits from', |
| 291 | ) |
| 292 | throw new Error('No active organization grants found') |
| 293 | } |
| 294 | |
| 295 | const consumeResult = await consumeFromOrderedGrants({ |
| 296 | userId: organizationId, |
| 297 | creditsToConsume, |
| 298 | grants: activeGrants, |
| 299 | tx, |
| 300 | logger, |
| 301 | }) |
| 302 | |
| 303 | return consumeResult |
| 304 | }, |
| 305 | lockKey: `org:${organizationId}`, |
| 306 | context: { organizationId, creditsToConsume }, |
| 307 | logger, |
| 308 | }) |
| 309 | |
| 310 | // Log successful organization credit consumption with lock timing |
| 311 | logger.info( |
| 312 | { |
| 313 | organizationId, |
| 314 | creditsConsumed: result.consumed, |
| 315 | creditsRequested: creditsToConsume, |
| 316 | fromPurchased: result.fromPurchased, |
| 317 | lockWaitMs, |
| 318 | }, |
| 319 | 'Organization credits consumed', |
| 320 | ) |
| 321 | |
| 322 | return result |
| 323 | } |
| 324 | |
| 325 | /** |
| 326 | * Grants credits to an organization. |
no test coverage detected