daily-claim.js
1.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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(),
}
}