Skip to content

Billing & Usage Cron Jobs #3724

Billing & Usage Cron Jobs

Billing & Usage Cron Jobs #3724

Workflow file for this run

name: Billing & Usage Cron Jobs
on:
schedule:
# Aggregate & Debit Usage - Every 15 minutes (Instant Billing)
- cron: '*/15 * * * *'
# Storage snapshots — midnight UTC every day (MUST run before aggregate-daily)
- cron: '0 0 * * *'
# Process billing cycle - 1st of each month at 3 AM UTC
- cron: '0 3 1 * *'
# Enforce grace periods - daily at 1 AM UTC
- cron: '0 1 * * *'
# Send reminders - daily at 9 AM UTC
- cron: '0 9 * * *'
# Expire trials - daily at 2 AM UTC
- cron: '0 2 * * *'
# Evaluate usage alerts - hourly at the 30th minute
- cron: '30 * * * *'
# Sync AI model pricing - every 30 minutes at :05 and :35
- cron: '5,35 * * * *'
workflow_dispatch:
inputs:
job:
description: 'Job to run'
required: true
default: 'enforce-grace'
type: choice
options:
- process-cycle
- enforce-grace
- expire-trials
- send-reminders
- evaluate-alerts
- aggregate-daily
- storage-snapshot
- sync-pricing
# Note: All billing operations use USD as the primary currency.
# Razorpay payments are processed in INR with live conversion rates.
env:
API_BASE_URL: ${{ secrets.NEXT_PUBLIC_APP_URL }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
jobs:
billing-cron:
runs-on: ubuntu-latest
steps:
- name: Determine job to run
id: job
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "job=${{ github.event.inputs.job }}" >> $GITHUB_OUTPUT
else
# Determine based on schedule
HOUR=$(date -u +%H)
MINUTE=$(date -u +%M)
DAY=$(date -u +%d)
# 1. MAINTENANCE JOBS (Prioritized at specific hours)
if [ "$MINUTE" == "00" ]; then
if [ "$HOUR" == "00" ]; then
echo "job=storage-snapshot" >> $GITHUB_OUTPUT
elif [ "$HOUR" == "01" ]; then
echo "job=enforce-grace" >> $GITHUB_OUTPUT
elif [ "$HOUR" == "02" ]; then
echo "job=expire-trials" >> $GITHUB_OUTPUT
elif [ "$HOUR" == "03" ] && [ "$DAY" == "01" ]; then
echo "job=process-cycle" >> $GITHUB_OUTPUT
elif [ "$HOUR" == "09" ]; then
echo "job=send-reminders" >> $GITHUB_OUTPUT
else
# Default to aggregation at top of most hours
echo "job=aggregate-daily" >> $GITHUB_OUTPUT
fi
# 2. EVALUATE ALERTS (Every hour at :30)
elif [ "$MINUTE" == "30" ]; then
echo "job=evaluate-alerts" >> $GITHUB_OUTPUT
# 3. INSTANT AGGREGATION & DEBIT (Every 15, 45 minutes)
elif [ "$MINUTE" == "15" ] || [ "$MINUTE" == "45" ]; then
echo "job=aggregate-daily" >> $GITHUB_OUTPUT
# 4. AI PRICING SYNC (Every 30 minutes at :05 and :35)
elif [ "$MINUTE" == "05" ] || [ "$MINUTE" == "35" ]; then
echo "job=sync-pricing" >> $GITHUB_OUTPUT
else
echo "job=none" >> $GITHUB_OUTPUT
fi
fi
- name: Capture daily storage snapshots
if: steps.job.outputs.job == 'storage-snapshot'
run: |
echo "📦 Capturing daily storage snapshots for all workspaces..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/storage/snapshot")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Storage snapshot failed with status $http_code"
exit 1
fi
echo "✅ Storage snapshots captured successfully"
- name: Run billing cycle processing
if: steps.job.outputs.job == 'process-cycle' || steps.job.outputs.run_billing_cycle == 'true'
run: |
echo "📅 Running monthly billing cycle processing..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/billing/process-cycle")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Failed with status $http_code"
exit 1
fi
echo "✅ Billing cycle processed successfully"
- name: Run grace period enforcement
if: steps.job.outputs.job == 'enforce-grace'
run: |
echo "⏰ Running daily grace period enforcement..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/billing/enforce-grace")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Failed with status $http_code"
exit 1
fi
echo "✅ Grace periods enforced successfully"
- name: Expire trials
if: steps.job.outputs.job == 'expire-trials'
run: |
echo "⏳ Running daily trial expiry check..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/billing/expire-trials")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Failed with status $http_code"
exit 1
fi
echo "✅ Trials expired successfully"
- name: Send reminder emails
if: steps.job.outputs.job == 'send-reminders'
run: |
echo "📧 Sending grace period reminder emails..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/billing/send-reminders")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Failed with status $http_code"
exit 1
fi
echo "✅ Reminders sent successfully"
- name: Evaluate usage alerts
if: steps.job.outputs.job == 'evaluate-alerts'
run: |
echo "🔔 Evaluating usage alerts..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/billing/evaluate-alerts")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Failed with status $http_code"
exit 1
fi
echo "✅ Usage alerts evaluated successfully"
- name: Aggregate daily usage
if: steps.job.outputs.job == 'aggregate-daily'
run: |
echo "📊 Aggregating yesterday's usage events into daily summary..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/usage/aggregate-daily")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Failed with status $http_code"
exit 1
fi
echo "✅ Daily usage aggregated successfully"
- name: Sync AI model pricing
if: steps.job.outputs.job == 'sync-pricing'
run: |
echo "🤖 Syncing AI model pricing from Google sources..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
-H "Content-Type: application/json" \
"$API_BASE_URL/api/cron/ai/sync-pricing")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "Response: $body"
if [ "$http_code" != "200" ]; then
echo "❌ Failed with status $http_code"
exit 1
fi
echo "✅ AI model pricing synced successfully"
- name: Skip (no matching job)
if: steps.job.outputs.job == 'none'
run: echo "⏭️ No job matched current schedule, skipping..."