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:
@@ -1,6 +1,8 @@
|
||||
import { getDb } from '../db/client.js'
|
||||
import { getMaxCustomersPerMitra } from './config.service.js'
|
||||
import { publish } from '../plugins/valkey.js'
|
||||
import { startSessionTimer } from './session-timer.service.js'
|
||||
import { startSessionListener } from './chat-handler.service.js'
|
||||
|
||||
const sql = getDb()
|
||||
|
||||
@@ -23,7 +25,7 @@ export const findAvailableMitras = async () => {
|
||||
return mitras
|
||||
}
|
||||
|
||||
export const createPairingRequest = async (customerId) => {
|
||||
export const createPairingRequest = async (customerId, { duration_minutes, price, is_free_trial } = {}) => {
|
||||
// Check for existing active session or request
|
||||
const [existing] = await sql`
|
||||
SELECT id, status FROM chat_sessions
|
||||
@@ -43,11 +45,11 @@ export const createPairingRequest = async (customerId) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Create session
|
||||
// Create session with duration/price
|
||||
const [session] = await sql`
|
||||
INSERT INTO chat_sessions (customer_id, status)
|
||||
VALUES (${customerId}, 'pending_acceptance')
|
||||
RETURNING id, customer_id, status, created_at
|
||||
INSERT INTO chat_sessions (customer_id, status, duration_minutes, price, is_free_trial)
|
||||
VALUES (${customerId}, 'pending_acceptance', ${duration_minutes || null}, ${price || 0}, ${is_free_trial || false})
|
||||
RETURNING id, customer_id, status, duration_minutes, price, is_free_trial, created_at
|
||||
`
|
||||
|
||||
// Create notifications for all available mitras
|
||||
@@ -111,13 +113,35 @@ export const acceptPairingRequest = async (sessionId, mitraId) => {
|
||||
pairingTimeouts.delete(sessionId)
|
||||
}
|
||||
|
||||
// Auto-skip payment for now: move to active
|
||||
// Auto-skip payment for now: move to active and set expires_at
|
||||
const [activeSession] = await sql`
|
||||
UPDATE chat_sessions SET status = 'active'
|
||||
UPDATE chat_sessions
|
||||
SET status = 'active',
|
||||
expires_at = CASE
|
||||
WHEN duration_minutes IS NOT NULL THEN NOW() + (duration_minutes || ' minutes')::interval
|
||||
ELSE NULL
|
||||
END
|
||||
WHERE id = ${sessionId}
|
||||
RETURNING id, customer_id, mitra_id, status, paired_at
|
||||
RETURNING id, customer_id, mitra_id, status, paired_at, duration_minutes, price, is_free_trial, expires_at
|
||||
`
|
||||
|
||||
// Record transaction
|
||||
if (activeSession.duration_minutes) {
|
||||
const txType = activeSession.is_free_trial ? 'free_trial' : 'paid'
|
||||
await sql`
|
||||
INSERT INTO customer_transactions (customer_id, session_id, type, amount)
|
||||
VALUES (${activeSession.customer_id}, ${sessionId}, ${txType}, ${activeSession.price || 0})
|
||||
`
|
||||
}
|
||||
|
||||
// Start session timer if duration is set
|
||||
if (activeSession.expires_at) {
|
||||
startSessionTimer(sessionId, activeSession.expires_at)
|
||||
}
|
||||
|
||||
// Start chat message listener for this session
|
||||
startSessionListener(sessionId)
|
||||
|
||||
// Get mitra display name for customer notification
|
||||
const [mitra] = await sql`
|
||||
SELECT display_name FROM mitras WHERE id = ${mitraId}
|
||||
|
||||
Reference in New Issue
Block a user