Credits System#

Deduct Credits#

typescriptimport { deductCredits } from "@/modules/billing/credits.service"

const result = await deductCredits({
  user_id: req.user!.user_id,
  credits: 10,
  description: "API call",
  ref_type: "api_call",
  ref_id: requestId,
})

if (!result.success) {
  return sendError(res, 403, result.message) // "No available credits"
}

Deduction Flow#

vbnet1. Query user's available credit packs (ORDER BY priority ASC, expires_at ASC NULLS LAST)
   ↓ No packs → Return "No available credits"
2. Deduct by priority, record transactions to credit_transactions
3. If insufficient, remaining goes to overdraft (credit_overdrafts)

Priority#

Credit packs are consumed by priority ascending (lower number = consumed first). Same priority uses expiration date ascending (expiring sooner = consumed first).

Query Credits#

typescriptimport { getActiveCredits } from "@/modules/billing/credits.service"

const result = await getActiveCredits(user_id)
// result = { all: CreditPackage[], total: number }

Credit Sources#

SourcemodeDescription
Registration bonusfreeAuto-granted on signup
Subscription rewardsubscriptionAuto-credited on renewal
One-time purchaseone_time:creditsUser purchases
  • credits — Credit accounts (one record per top-up/reward)
  • credit_transactions — Transaction log (deductions/additions)
  • credit_overdrafts — Overdraft records