Phase 3 scaffold: chat engine (WebSocket, FCM, pricing, timer, extension, history)

- Backend: WebSocket plugin, chat/pricing/timer/extension/closure/notification services
- Client app: ChatBloc, pricing dialog, chat screen with message status, extension/goodbye flow, history
- Mitra app: MitraChatBloc, ExtensionBloc, chat screen, extension accept/reject, history
- Control center: free trial, extension timeout, early end config toggles
- DB migration: chat_messages, session_closures, session_extensions, customer_transactions tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 23:58:11 +08:00
parent 844d7234e6
commit b4efcf14c2
47 changed files with 4361 additions and 44 deletions

View File

@@ -0,0 +1,54 @@
import { getDb } from '../db/client.js'
const sql = getDb()
// Mock price tiers (will come from Control Center config later)
const PRICE_TIERS = [
{ duration_minutes: 15, price: 30000, label: '15 Menit' },
{ duration_minutes: 30, price: 60000, label: '30 Menit' },
{ duration_minutes: 45, price: 100000, label: '45 Menit' },
{ duration_minutes: 60, price: 150000, label: '60 Menit' },
{ duration_minutes: 1440, price: 250000, label: '24 Jam' },
]
export const getPriceTiers = () => PRICE_TIERS
export const isValidTier = (durationMinutes, price) => {
return PRICE_TIERS.some(
(t) => t.duration_minutes === durationMinutes && t.price === price
)
}
export const getFreeTrial = async () => {
const [enabledRow] = await sql`SELECT value FROM app_config WHERE key = 'free_trial_enabled'`
const [durationRow] = await sql`SELECT value FROM app_config WHERE key = 'free_trial_duration_minutes'`
return {
enabled: enabledRow?.value?.value ?? false,
duration_minutes: durationRow?.value?.value ?? 5,
}
}
export const isCustomerEligibleForFreeTrial = async (customerId) => {
const freeTrial = await getFreeTrial()
if (!freeTrial.enabled) return false
const [tx] = await sql`
SELECT id FROM customer_transactions
WHERE customer_id = ${customerId}
LIMIT 1
`
return !tx // Eligible only if no transactions at all
}
export const getPricingForCustomer = async (customerId) => {
const tiers = getPriceTiers()
const freeTrialEligible = await isCustomerEligibleForFreeTrial(customerId)
const freeTrial = await getFreeTrial()
return {
tiers,
free_trial: freeTrialEligible
? { eligible: true, duration_minutes: freeTrial.duration_minutes }
: { eligible: false },
}
}