daily-claim.js 1.78 KB
import { BillingAction } from './action-policies.js'
import { calendarDayOf, midnightInTimezone, shiftCalendarDay } from './calendar-day.js'

/** @param {{ actionPolicies:{ get:Function }, grant:Function, balance:Function }} billing */
export function createDailyClaim(billing) {
  async function claim({ user_id, now = new Date() }) {
    const policy = await billing.actionPolicies.get(BillingAction.DAILY_CLAIM)
    if (!policy.enabled) throw new Error('daily_claim is disabled')
    const window = losAngelesDay(now, policy.timezone)
    const idempotency_key = `daily_claim:${user_id}:${window.date}`
    const granted = await billing.grant({
      user_id,
      amount: policy.points,
      source: 'daily_reward',
      credit_kind: 'daily',
      reason: 'daily_claim',
      ref_type: 'daily_claim',
      ref_id: window.date,
      idempotency_key,
    })
    return {
      ...(await billing.balance({ user_id })),
      claimed: !granted.replayed,
      claimed_today: true,
      next_available_at: window.next_available_at,
    }
  }

  async function status({ user_id, now = new Date() }) {
    const policy = await billing.actionPolicies.get(BillingAction.DAILY_CLAIM)
    const window = losAngelesDay(now, policy.timezone)
    const entry = await billing.findLedgerByIdempotencyKey(`daily_claim:${user_id}:${window.date}`)
    return {
      claimed_today: entry !== null,
      next_available_at: window.next_available_at,
      points: policy.points,
    }
  }

  return { claim, status }
}

function losAngelesDay(now, timezone) {
  if (timezone !== 'America/Los_Angeles') throw new Error(`Unsupported daily claim timezone: ${timezone}`)
  const date = calendarDayOf(now, timezone)
  return {
    date,
    next_available_at: midnightInTimezone(shiftCalendarDay(date, 1), timezone).toISOString(),
  }
}