import { useCallback, useMemo, useState } from 'react'; import { getErrorSummary, getSikshyaApi, SIKSHYA_ENDPOINTS } from '../api'; import { EmbeddableShell } from '../components/shared/EmbeddableShell'; import { ApiErrorPanel } from '../components/shared/ApiErrorPanel'; import { ButtonPrimary, ButtonSecondary } from '../components/shared/buttons'; import { StatusBadge } from '../components/shared/list/StatusBadge'; import { Modal } from '../components/shared/Modal'; import { useAsyncData } from '../hooks/useAsyncData'; import { useAdminRouting } from '../lib/adminRouting'; import { useSikshyaDialog } from '../components/shared/SikshyaDialogContext'; import { appViewHref } from '../lib/appUrl'; import { formatPostDate } from '../lib/formatPostDate'; import type { SikshyaReactConfig } from '../types'; import type { ApiError } from '../api/errors'; import { __ } from '../lib/i18n'; type OrderLine = { course_id: number; course_title: string; quantity: number; unit_price: number; line_total: number; }; type OrderSubscriptionSummary = { is_subscription_checkout?: boolean; plan_id?: number; interval_unit?: string; gateway_subscription_ref?: string; plan_name?: string; plan_amount?: number; plan_currency?: string; }; type OrderDetails = { id: number; user_id: number; status: string; currency: string; subtotal: number; discount_total: number; total: number; gateway: string; gateway_intent_id: string; public_token: string; created_at: string; payer_name: string; payer_email: string; meta?: Record; subscription?: OrderSubscriptionSummary; dynamic_fields?: Record; dynamic_fields_display?: Array<{ id: string; label: string; value: string }>; receipt_url?: string; invoice_number?: string; invoice_issued_at?: string; invoice_url?: string; lines: OrderLine[]; }; type DetailsResponse = { ok?: boolean; order?: OrderDetails; message?: string }; function canMarkPaid(o: OrderDetails): boolean { if (o.status === 'paid') return false; if (o.status !== 'pending' && o.status !== 'on-hold') return false; const gw = (o.gateway || '').toLowerCase(); return gw === 'offline' || gw === ''; } export function OrderDetailsPage(props: { config: SikshyaReactConfig; title: string; embedded?: boolean }) { const { config, embedded, title } = props; const dialog = useSikshyaDialog(); const { route, navigateView } = useAdminRouting(); const orderId = useMemo(() => parseInt(route.query?.id || '0', 10) || 0, [route.query]); const [markBusy, setMarkBusy] = useState(false); const [editOpen, setEditOpen] = useState(false); const [editStatus, setEditStatus] = useState<'pending' | 'on-hold' | 'paid'>('pending'); const loader = useCallback(async () => { if (!orderId) throw new Error(__('Missing order id.', 'sikshya')); return getSikshyaApi().get(SIKSHYA_ENDPOINTS.admin.order(orderId)); }, [orderId]); const { loading, data, error, refetch } = useAsyncData(loader, [orderId]); const order = data?.order; const [saving, setSaving] = useState(false); return ( navigateView('orders')}> Back to orders { if (!order) return; setEditStatus((order.status === 'paid' ? 'paid' : order.status === 'on-hold' ? __('on-hold', 'sikshya') : __('pending', 'sikshya')) as any); setEditOpen(true); }} > Edit refetch()}> Refresh } > {error ? (
refetch()} />
) : null} {loading ? (
Loading order…
) : !order ? (
Order not found.
) : (
{ if (!saving) setEditOpen(false); }} footer={
{ setSaving(true); try { await getSikshyaApi().patch<{ ok?: boolean; message?: string }>( SIKSHYA_ENDPOINTS.admin.orderUpdate(order.id), { status: editStatus } ); setEditOpen(false); await refetch(); } catch (err) { void dialog.alert({ title: __('Something went wrong', 'sikshya'), message: getErrorSummary(err) }); } finally { setSaving(false); } }} > {saving ? __('Saving…', 'sikshya') : __('Save', 'sikshya')}
} >
Status
{formatPostDate(order.created_at)}
Gateway
{(order.gateway || '—').toUpperCase()}
{order.gateway_intent_id ? (
{order.gateway_intent_id}
) : null}
{canMarkPaid(order) ? ( ) : null} {order.receipt_url ? ( View receipt ) : null} {order.invoice_url ? ( {order.invoice_number ? `Invoice ${order.invoice_number}` : 'View invoice'} ) : null} {order.invoice_url ? ( Download PDF ) : null}
Customer
{order.payer_name || `User #${order.user_id}`}
{order.payer_email || '—'}
User ID: #{order.user_id || '—'}
Totals
{__('Subtotal', 'sikshya')} {order.subtotal.toFixed(2)} {order.currency}
{__('Discount', 'sikshya')} {order.discount_total.toFixed(2)} {order.currency}
{__('Total', 'sikshya')} {order.total.toFixed(2)} {order.currency}
{order.subscription?.is_subscription_checkout ? (
Subscription checkout

This order created or renewed access through a membership plan. The row in{' '} {__('Subscriptions', 'sikshya')} for this learner is keyed by user + plan; gateway webhooks keep status and billing period in sync when configured.

Plan
{(order.subscription.plan_name && String(order.subscription.plan_name)) || (order.subscription.plan_id ? `Plan #${order.subscription.plan_id}` : '—')} {order.subscription.plan_id ? ( (ID {order.subscription.plan_id}) ) : null}
Billing interval
{(order.subscription.interval_unit && String(order.subscription.interval_unit)) || '—'}
Open subscriptions list

Gateway intent / subscription id (above) is what your provider uses for renewals; it may differ from the internal subscription row id in Sikshya.

) : null}
{__('Items', 'sikshya')}
{(order.lines || []).map((ln, idx) => ( ))}
{__('Course', 'sikshya')} {__('Qty', 'sikshya')} {__('Unit', 'sikshya')} {__('Line', 'sikshya')}
{ln.course_title || `Course #${ln.course_id}`}
ID: {ln.course_id}
{ln.quantity || 1} {ln.unit_price.toFixed(2)} {order.currency} {ln.line_total.toFixed(2)} {order.currency}
{order.dynamic_fields_display && order.dynamic_fields_display.length ? (
{__('Dynamic checkout fields', 'sikshya')}
{order.dynamic_fields_display.map((it) => (
{it.label || it.id}
{String(it.value ?? '') || '—'}
))}
) : null}
)}
); }