@open-mercato/core 0.6.6-develop.6339.1.193c6c7c71 → 0.6.6-develop.6344.1.c4b17c07c4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/catalog/components/products/ProductComplianceSection.js +1 -1
  3. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +2 -2
  4. package/dist/modules/catalog/data/validators.js +1 -1
  5. package/dist/modules/catalog/data/validators.js.map +2 -2
  6. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +1 -1
  7. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +2 -2
  8. package/dist/modules/communication_channels/lib/route-mutation-guard.js +19 -29
  9. package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +2 -2
  10. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +4 -4
  11. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  12. package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -1
  13. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  14. package/dist/modules/integrations/api/[id]/credentials/route.js +14 -4
  15. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  16. package/dist/modules/integrations/lib/credentials-masking.js +50 -0
  17. package/dist/modules/integrations/lib/credentials-masking.js.map +7 -0
  18. package/dist/modules/notifications/lib/routeHelpers.js +18 -25
  19. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  20. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +1 -1
  21. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
  22. package/package.json +7 -7
  23. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +1 -1
  24. package/src/modules/catalog/data/validators.ts +1 -1
  25. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +1 -1
  26. package/src/modules/communication_channels/lib/route-mutation-guard.ts +32 -32
  27. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +4 -4
  28. package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -1
  29. package/src/modules/integrations/api/[id]/credentials/route.ts +18 -4
  30. package/src/modules/integrations/lib/credentials-masking.ts +95 -0
  31. package/src/modules/notifications/lib/routeHelpers.ts +18 -26
  32. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/payment_gateways/backend/payment-gateways/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Page, PageHeader, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { LogList, type LogListEntry } from '@open-mercato/ui/backend/LogList'\nimport { CreditCard, HandCoins, RefreshCw, Webhook } from 'lucide-react'\n\ntype TransactionRow = {\n id: string\n paymentId: string\n providerKey: string\n providerSessionId?: string | null\n gatewayPaymentId?: string | null\n gatewayRefundId?: string | null\n unifiedStatus: string\n gatewayStatus?: string | null\n amount: string\n currencyCode: string\n redirectUrl?: string | null\n lastWebhookAt?: string | null\n lastPolledAt?: string | null\n createdAt: string | null\n updatedAt: string | null\n}\n\ntype TransactionLogEntry = {\n id: string\n integrationId: string\n runId?: string | null\n scopeEntityType?: string | null\n scopeEntityId?: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n code?: string | null\n payload?: Record<string, unknown> | null\n createdAt: string | null\n}\n\ntype TransactionDetail = {\n transaction: {\n id: string\n paymentId: string\n providerKey: string\n providerSessionId?: string | null\n gatewayPaymentId?: string | null\n gatewayRefundId?: string | null\n unifiedStatus: string\n gatewayStatus?: string | null\n redirectUrl?: string | null\n amount: string\n currencyCode: string\n gatewayMetadata?: Record<string, unknown> | null\n webhookLog?: Array<{\n eventType: string\n receivedAt: string\n idempotencyKey: string\n unifiedStatus: string\n processed: boolean\n }> | null\n lastWebhookAt?: string | null\n lastPolledAt?: string | null\n expiresAt?: string | null\n createdAt: string | null\n updatedAt: string | null\n }\n logs: TransactionLogEntry[]\n}\n\ntype TransactionsResponse = {\n items: TransactionRow[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nconst STATUS_STYLES: Record<string, string> = {\n pending: 'bg-slate-100 text-slate-800',\n authorized: 'bg-blue-100 text-blue-800',\n captured: 'bg-green-100 text-green-800',\n partially_captured: 'bg-emerald-100 text-emerald-800',\n refunded: 'bg-amber-100 text-amber-800',\n partially_refunded: 'bg-orange-100 text-orange-800',\n cancelled: 'bg-zinc-200 text-zinc-900',\n failed: 'bg-red-100 text-red-800',\n expired: 'bg-neutral-200 text-neutral-900',\n unknown: 'bg-purple-100 text-purple-800',\n}\n\nfunction formatDateTime(value: string | null | undefined): string {\n if (!value) return '\u2014'\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return value\n return parsed.toLocaleString()\n}\n\nfunction formatAmount(value: string, currencyCode: string): string {\n const amount = Number(value)\n if (Number.isNaN(amount)) return `${value} ${currencyCode}`\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: currencyCode,\n }).format(amount)\n } catch {\n return `${amount.toFixed(2)} ${currencyCode}`\n }\n}\n\nfunction formatTypeLabel(value: string): string {\n return value\n .split('_')\n .filter(Boolean)\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nfunction formatLogDetailLabel(key: string): string {\n return key\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .split(' ')\n .filter(Boolean)\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nfunction isPrimitiveLogValue(value: unknown): value is string | number | boolean | null {\n return value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n}\n\nfunction splitLogPayload(payload: Record<string, unknown> | null | undefined) {\n if (!payload) {\n return {\n inlineEntries: [] as Array<[string, string | number | boolean | null]>,\n nestedEntries: [] as Array<[string, unknown]>,\n }\n }\n\n const inlineEntries: Array<[string, string | number | boolean | null]> = []\n const nestedEntries: Array<[string, unknown]> = []\n Object.entries(payload).forEach(([key, value]) => {\n if (isPrimitiveLogValue(value)) {\n inlineEntries.push([key, value])\n return\n }\n nestedEntries.push([key, value])\n })\n return { inlineEntries, nestedEntries }\n}\n\nfunction DetailStat({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div className=\"rounded-lg border bg-muted/30 px-4 py-3\">\n <div className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{label}</div>\n <div className=\"mt-1 text-sm font-medium\">{value}</div>\n </div>\n )\n}\n\nexport default function PaymentTransactionsPage() {\n const t = useT()\n const searchParams = useSearchParams()\n const scopeVersion = useOrganizationScopeVersion()\n const [rows, setRows] = React.useState<TransactionRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [selectedId, setSelectedId] = React.useState<string | null>(null)\n const [detail, setDetail] = React.useState<TransactionDetail | null>(null)\n const [isLoadingDetail, setIsLoadingDetail] = React.useState(false)\n const [detailError, setDetailError] = React.useState<string | null>(null)\n const [isRefreshingStatus, setIsRefreshingStatus] = React.useState(false)\n const [isCapturing, setIsCapturing] = React.useState(false)\n const { runMutation } = useGuardedMutation<{\n entityType: string\n entityId?: string\n }>({\n contextId: 'payment_gateways:transactions-detail',\n })\n const noneLabel = t('common.none', 'None')\n const requestedTransactionId = React.useMemo(() => {\n const raw = searchParams.get('transactionId')\n return raw && raw.trim().length > 0 ? raw.trim() : null\n }, [searchParams])\n\n const formatLogPrimitiveValue = React.useCallback((value: string | number | boolean | null): string => {\n if (value === null) return noneLabel\n if (typeof value === 'boolean') return value ? t('common.yes', 'Yes') : t('common.no', 'No')\n return String(value)\n }, [noneLabel, t])\n\n const loadRows = React.useCallback(async () => {\n setIsLoading(true)\n const params = new URLSearchParams({\n page: String(page),\n pageSize: '20',\n })\n if (search.trim()) params.set('search', search.trim())\n if (typeof filterValues.providerKey === 'string' && filterValues.providerKey) {\n params.set('providerKey', filterValues.providerKey)\n }\n if (typeof filterValues.status === 'string' && filterValues.status) {\n params.set('status', filterValues.status)\n }\n const fallback: TransactionsResponse = { items: [], total: 0, page, pageSize: 20, totalPages: 1 }\n const call = await apiCall<TransactionsResponse>(`/api/payment_gateways/transactions?${params.toString()}`, undefined, { fallback })\n if (call.ok && call.result) {\n setRows(Array.isArray(call.result.items) ? call.result.items : [])\n setTotal(call.result.total ?? 0)\n setTotalPages(call.result.totalPages ?? 1)\n } else {\n flash(t('payment_gateways.transactions.error.load', 'Failed to load payment transactions'), 'error')\n setRows([])\n setTotal(0)\n setTotalPages(1)\n }\n setIsLoading(false)\n }, [filterValues.providerKey, filterValues.status, page, search, t])\n\n const loadDetail = React.useCallback(async (transactionId: string) => {\n setIsLoadingDetail(true)\n setDetailError(null)\n const call = await apiCall<TransactionDetail>(`/api/payment_gateways/transactions/${encodeURIComponent(transactionId)}`, undefined, { fallback: null })\n if (call.ok && call.result) {\n setDetail(call.result)\n } else {\n setDetail(null)\n setDetailError(t('payment_gateways.transactions.error.loadDetail', 'Failed to load transaction details'))\n }\n setIsLoadingDetail(false)\n }, [t])\n\n React.useEffect(() => {\n void loadRows()\n }, [loadRows, scopeVersion])\n\n React.useEffect(() => {\n if (!selectedId) {\n setDetail(null)\n setDetailError(null)\n return\n }\n void loadDetail(selectedId)\n }, [loadDetail, selectedId])\n\n React.useEffect(() => {\n setSelectedId((current) => (current && rows.some((row) => row.id === current) ? current : null))\n }, [rows])\n\n React.useEffect(() => {\n if (!requestedTransactionId) return\n if (!rows.some((row) => row.id === requestedTransactionId)) return\n setSelectedId(requestedTransactionId)\n }, [requestedTransactionId, rows])\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n const next: FilterValues = {}\n Object.entries(values).forEach(([key, value]) => {\n if (typeof value === 'string' && value.trim()) next[key] = value\n })\n setFilterValues(next)\n setPage(1)\n }, [])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const handleRefreshStatus = React.useCallback(async () => {\n if (!selectedId) return\n setIsRefreshingStatus(true)\n try {\n await runMutation({\n operation: async () => {\n await apiCallOrThrow('/api/payment_gateways/status', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ transactionId: selectedId }),\n })\n },\n context: {\n entityType: 'payment_gateways:gateway_transaction',\n entityId: selectedId,\n },\n mutationPayload: { transactionId: selectedId },\n })\n await Promise.all([\n loadRows(),\n loadDetail(selectedId),\n ])\n flash(t('payment_gateways.transactions.success.refreshStatus', 'Transaction status refreshed'), 'success')\n } catch (error) {\n const message = error instanceof Error && error.message\n ? error.message\n : t('payment_gateways.transactions.error.refreshStatus', 'Failed to refresh transaction status')\n flash(message, 'error')\n } finally {\n setIsRefreshingStatus(false)\n }\n }, [loadDetail, loadRows, runMutation, selectedId, t])\n\n const handleCapturePayment = React.useCallback(async () => {\n if (!selectedId) return\n setIsCapturing(true)\n try {\n await runMutation({\n operation: async () => {\n await apiCallOrThrow('/api/payment_gateways/capture', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ transactionId: selectedId }),\n })\n },\n context: {\n entityType: 'payment_gateways:gateway_transaction',\n entityId: selectedId,\n },\n mutationPayload: { transactionId: selectedId },\n })\n await Promise.all([\n loadRows(),\n loadDetail(selectedId),\n ])\n flash(t('payment_gateways.transactions.success.capture', 'Payment captured'), 'success')\n } catch (error) {\n const message = error instanceof Error && error.message\n ? error.message\n : t('payment_gateways.transactions.error.capture', 'Failed to capture payment')\n flash(message, 'error')\n } finally {\n setIsCapturing(false)\n }\n }, [loadDetail, loadRows, runMutation, selectedId, t])\n\n const providerOptions = React.useMemo(() => {\n const values = Array.from(new Set(rows.map((row) => row.providerKey).filter(Boolean))).sort()\n return values.map((value) => ({\n label: formatTypeLabel(value),\n value,\n }))\n }, [rows])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'providerKey',\n type: 'select',\n label: t('payment_gateways.transactions.filters.provider', 'Provider'),\n options: [\n { label: t('payment_gateways.transactions.filters.allProviders', 'All providers'), value: '' },\n ...providerOptions,\n ],\n },\n {\n id: 'status',\n type: 'select',\n label: t('payment_gateways.transactions.filters.status', 'Status'),\n options: [\n { label: t('payment_gateways.transactions.filters.allStatuses', 'All statuses'), value: '' },\n { label: t('payment_gateways.status.pending', 'Pending'), value: 'pending' },\n { label: t('payment_gateways.status.authorized', 'Authorized'), value: 'authorized' },\n { label: t('payment_gateways.status.captured', 'Captured'), value: 'captured' },\n { label: t('payment_gateways.status.partially_captured', 'Partially Captured'), value: 'partially_captured' },\n { label: t('payment_gateways.status.refunded', 'Refunded'), value: 'refunded' },\n { label: t('payment_gateways.status.partially_refunded', 'Partially Refunded'), value: 'partially_refunded' },\n { label: t('payment_gateways.status.cancelled', 'Cancelled'), value: 'cancelled' },\n { label: t('payment_gateways.status.failed', 'Failed'), value: 'failed' },\n { label: t('payment_gateways.status.expired', 'Expired'), value: 'expired' },\n { label: t('payment_gateways.status.unknown', 'Unknown'), value: 'unknown' },\n ],\n },\n ], [providerOptions, t])\n\n const columns = React.useMemo<ColumnDef<TransactionRow>[]>(() => [\n {\n accessorKey: 'paymentId',\n header: t('payment_gateways.transactions.columns.paymentId', 'Payment'),\n cell: ({ row }) => (\n <div className=\"space-y-1\">\n <div className=\"font-medium\">{row.original.paymentId}</div>\n <div className=\"text-xs text-muted-foreground\">{row.original.id}</div>\n </div>\n ),\n meta: { maxWidth: '20rem' },\n },\n {\n accessorKey: 'providerKey',\n header: t('payment_gateways.transactions.columns.provider', 'Provider'),\n cell: ({ row }) => (\n <div className=\"flex items-center gap-2\">\n <CreditCard className=\"h-4 w-4 text-muted-foreground\" />\n <span>{formatTypeLabel(row.original.providerKey)}</span>\n </div>\n ),\n },\n {\n accessorKey: 'unifiedStatus',\n header: t('payment_gateways.transactions.columns.status', 'Status'),\n cell: ({ row }) => (\n <Badge variant=\"secondary\" className={STATUS_STYLES[row.original.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${row.original.unifiedStatus}`, formatTypeLabel(row.original.unifiedStatus))}\n </Badge>\n ),\n },\n {\n accessorKey: 'amount',\n header: t('payment_gateways.transactions.columns.amount', 'Amount'),\n cell: ({ row }) => formatAmount(row.original.amount, row.original.currencyCode),\n },\n {\n accessorKey: 'providerSessionId',\n header: t('payment_gateways.transactions.columns.session', 'Session'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs text-muted-foreground\">\n {row.original.providerSessionId ?? '\u2014'}\n </span>\n ),\n meta: { maxWidth: '18rem', truncate: true },\n },\n {\n accessorKey: 'updatedAt',\n header: t('payment_gateways.transactions.columns.updatedAt', 'Updated'),\n cell: ({ row }) => formatDateTime(row.original.updatedAt),\n },\n ], [t])\n\n const selectedSummary = React.useMemo(\n () => rows.find((row) => row.id === selectedId) ?? null,\n [rows, selectedId],\n )\n const canCaptureSelected = React.useMemo(() => {\n const status = detail?.transaction.unifiedStatus ?? selectedSummary?.unifiedStatus ?? null\n return status === 'authorized'\n }, [detail?.transaction.unifiedStatus, selectedSummary?.unifiedStatus])\n\n return (\n <Page>\n <PageHeader\n title={t('payment_gateways.transactions.title', 'Payment Transactions')}\n description={t('payment_gateways.transactions.description', 'Track all payment-gateway transactions, inspect webhook activity, and review provider logs from one place.')}\n />\n <PageBody className=\"space-y-6\">\n <DataTable\n stickyActionsColumn\n title={t('payment_gateways.transactions.tableTitle', 'Transactions')}\n columns={columns}\n data={rows}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('payment_gateways.transactions.searchPlaceholder', 'Search by payment, transaction, session, or gateway id')}\n perspective={{ tableId: 'payment_gateways.transactions.list' }}\n pagination={{ page, pageSize: 20, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n onRowClick={(row) => setSelectedId((current) => current === row.id ? null : row.id)}\n rowActions={(row) => (\n <RowActions items={[\n {\n id: 'details',\n label: selectedId === row.id\n ? t('payment_gateways.transactions.actions.hideDetails', 'Hide details')\n : t('payment_gateways.transactions.actions.showDetails', 'Show details'),\n onSelect: () => setSelectedId((current) => current === row.id ? null : row.id),\n },\n ]} />\n )}\n />\n\n {selectedId ? (\n <Card>\n <CardHeader className=\"gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-1\">\n <CardTitle>{t('payment_gateways.transactions.detail.title', 'Transaction details')}</CardTitle>\n {selectedSummary ? (\n <div className=\"flex flex-wrap items-center gap-2 text-sm text-muted-foreground\">\n <span>{selectedSummary.paymentId}</span>\n <span>\u2022</span>\n <span>{formatTypeLabel(selectedSummary.providerKey)}</span>\n <Badge variant=\"secondary\" className={STATUS_STYLES[selectedSummary.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${selectedSummary.unifiedStatus}`, formatTypeLabel(selectedSummary.unifiedStatus))}\n </Badge>\n </div>\n ) : null}\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void handleRefreshStatus()}\n disabled={isRefreshingStatus || isLoadingDetail || isCapturing}\n >\n {isRefreshingStatus ? <Spinner className=\"mr-2 h-4 w-4\" /> : <RefreshCw className=\"mr-2 h-4 w-4\" />}\n {t('payment_gateways.transactions.actions.refreshStatus', 'Refresh status')}\n </Button>\n {canCaptureSelected ? (\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void handleCapturePayment()}\n disabled={isRefreshingStatus || isLoadingDetail || isCapturing}\n >\n {isCapturing ? <Spinner className=\"mr-2 h-4 w-4\" /> : <HandCoins className=\"mr-2 h-4 w-4\" />}\n {t('payment_gateways.transactions.actions.capture', 'Capture payment')}\n </Button>\n ) : null}\n </CardHeader>\n <CardContent className=\"space-y-6\">\n {isLoadingDetail ? <LoadingMessage label={t('payment_gateways.transactions.detail.loading', 'Loading transaction details')} /> : null}\n {!isLoadingDetail && detailError ? <ErrorMessage label={detailError} /> : null}\n {!isLoadingDetail && !detailError && detail ? (\n <>\n <div className=\"grid gap-4 lg:grid-cols-4\">\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.status', 'Status')}\n value={(\n <Badge variant=\"secondary\" className={STATUS_STYLES[detail.transaction.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${detail.transaction.unifiedStatus}`, formatTypeLabel(detail.transaction.unifiedStatus))}\n </Badge>\n )}\n />\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.gatewayStatus', 'Gateway status')}\n value={detail.transaction.gatewayStatus ?? noneLabel}\n />\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.amount', 'Amount')}\n value={formatAmount(detail.transaction.amount, detail.transaction.currencyCode)}\n />\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.provider', 'Provider')}\n value={formatTypeLabel(detail.transaction.providerKey)}\n />\n </div>\n\n <div className=\"grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]\">\n <div className=\"space-y-6\">\n <section className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold\">{t('payment_gateways.transactions.detail.identifiers', 'Identifiers')}</h3>\n <dl className=\"grid gap-3 md:grid-cols-2\">\n {[\n [t('payment_gateways.transactions.columns.transactionId', 'Transaction ID'), detail.transaction.id],\n [t('payment_gateways.transactions.columns.paymentId', 'Payment ID'), detail.transaction.paymentId],\n [t('payment_gateways.transactions.columns.session', 'Session ID'), detail.transaction.providerSessionId ?? '\u2014'],\n [t('payment_gateways.transactions.columns.gatewayPaymentId', 'Gateway payment ID'), detail.transaction.gatewayPaymentId ?? '\u2014'],\n [t('payment_gateways.transactions.columns.gatewayRefundId', 'Gateway refund ID'), detail.transaction.gatewayRefundId ?? '\u2014'],\n [t('payment_gateways.transactions.columns.redirectUrl', 'Redirect URL'), detail.transaction.redirectUrl ?? '\u2014'],\n [t('payment_gateways.transactions.columns.createdAt', 'Created at'), formatDateTime(detail.transaction.createdAt)],\n [t('payment_gateways.transactions.columns.updatedAt', 'Updated at'), formatDateTime(detail.transaction.updatedAt)],\n [t('payment_gateways.transactions.columns.lastWebhookAt', 'Last webhook'), formatDateTime(detail.transaction.lastWebhookAt)],\n [t('payment_gateways.transactions.columns.lastPolledAt', 'Last poll'), formatDateTime(detail.transaction.lastPolledAt)],\n ].map(([label, value]) => (\n <div key={label} className=\"rounded-lg border bg-muted/30 px-4 py-3\">\n <dt className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{label}</dt>\n <dd className=\"mt-1 break-all text-sm\">{value}</dd>\n </div>\n ))}\n </dl>\n </section>\n\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Webhook className=\"h-4 w-4 text-muted-foreground\" />\n <h3 className=\"text-sm font-semibold\">{t('payment_gateways.transactions.detail.webhooks', 'Webhook activity')}</h3>\n </div>\n {detail.transaction.webhookLog && detail.transaction.webhookLog.length > 0 ? (\n <div className=\"overflow-hidden rounded-lg border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/50\">\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.eventType', 'Event')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.status', 'Status')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.processed', 'Processed')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.receivedAt', 'Received')}</th>\n </tr>\n </thead>\n <tbody>\n {detail.transaction.webhookLog.map((entry) => (\n <tr key={`${entry.idempotencyKey}:${entry.receivedAt}`} className=\"border-b last:border-0\">\n <td className=\"px-4 py-2 font-medium\">{entry.eventType}</td>\n <td className=\"px-4 py-2\">\n <Badge variant=\"secondary\" className={STATUS_STYLES[entry.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${entry.unifiedStatus}`, formatTypeLabel(entry.unifiedStatus))}\n </Badge>\n </td>\n <td className=\"px-4 py-2\">{entry.processed ? t('common.yes', 'Yes') : t('common.no', 'No')}</td>\n <td className=\"px-4 py-2 text-muted-foreground\">{formatDateTime(entry.receivedAt)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('payment_gateways.transactions.detail.webhooksEmpty', 'No webhook events have been recorded for this transaction yet.')}</p>\n )}\n </section>\n\n <section className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold\">{t('payment_gateways.transactions.detail.logs', 'Gateway logs')}</h3>\n <LogList\n entries={detail.logs.map<LogListEntry>((log) => {\n const metadataEntries = [\n [t('payment_gateways.transactions.log.time', 'Time'), formatDateTime(log.createdAt)],\n [t('payment_gateways.transactions.log.level', 'Level'), t(`payment_gateways.transactions.level.${log.level}`, log.level)],\n [t('payment_gateways.transactions.log.code', 'Code'), log.code ?? null],\n [t('payment_gateways.transactions.log.runId', 'Run ID'), log.runId ?? null],\n [t('payment_gateways.transactions.log.entityType', 'Entity Type'), log.scopeEntityType ?? null],\n [t('payment_gateways.transactions.log.entityId', 'Entity ID'), log.scopeEntityId ?? null],\n ].filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim().length > 0)\n const { inlineEntries, nestedEntries } = splitLogPayload(log.payload)\n\n return {\n id: log.id,\n time: formatDateTime(log.createdAt),\n level: log.level,\n levelLabel: t(`payment_gateways.transactions.level.${log.level}`, log.level),\n message: log.message,\n body: (\n <div className=\"space-y-4\">\n {metadataEntries.length > 0 ? (\n <dl className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {metadataEntries.map(([label, value]) => (\n <div key={label} className=\"rounded-md border bg-muted/30 px-3 py-2\">\n <dt className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{label}</dt>\n <dd className=\"mt-1 break-all text-sm\">{value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n {inlineEntries.length > 0 ? (\n <dl className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {inlineEntries.map(([key, value]) => (\n <div key={key} className=\"rounded-md border bg-muted/30 px-3 py-2\">\n <dt className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{formatLogDetailLabel(key)}</dt>\n <dd className=\"mt-1 break-words text-sm\">{formatLogPrimitiveValue(value)}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n {nestedEntries.map(([key, value]) => (\n <JsonDisplay\n key={key}\n data={value}\n title={formatLogDetailLabel(key)}\n defaultExpanded\n maxInitialDepth={1}\n theme=\"dark\"\n maxHeight=\"16rem\"\n className=\"p-4\"\n />\n ))}\n </div>\n ),\n }\n })}\n emptyMessage={t('payment_gateways.transactions.detail.logsEmpty', 'No transaction-scoped logs are available yet.')}\n />\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <JsonDisplay\n data={detail.transaction.gatewayMetadata ?? {}}\n title={t('payment_gateways.transactions.detail.gatewayMetadata', 'Gateway metadata')}\n defaultExpanded\n maxInitialDepth={1}\n theme=\"dark\"\n maxHeight=\"24rem\"\n className=\"p-4\"\n />\n <InjectionSpot\n spotId=\"admin.page:payment-gateways/transactions:after\"\n context={{ selectedPaymentId: detail.transaction.paymentId }}\n />\n </div>\n </div>\n </>\n ) : null}\n </CardContent>\n </Card>\n ) : null}\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Page, PageHeader, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { LogList, type LogListEntry } from '@open-mercato/ui/backend/LogList'\nimport { CreditCard, HandCoins, RefreshCw, Webhook } from 'lucide-react'\n\ntype TransactionRow = {\n id: string\n paymentId: string\n providerKey: string\n providerSessionId?: string | null\n gatewayPaymentId?: string | null\n gatewayRefundId?: string | null\n unifiedStatus: string\n gatewayStatus?: string | null\n amount: string\n currencyCode: string\n redirectUrl?: string | null\n lastWebhookAt?: string | null\n lastPolledAt?: string | null\n createdAt: string | null\n updatedAt: string | null\n}\n\ntype TransactionLogEntry = {\n id: string\n integrationId: string\n runId?: string | null\n scopeEntityType?: string | null\n scopeEntityId?: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n code?: string | null\n payload?: Record<string, unknown> | null\n createdAt: string | null\n}\n\ntype TransactionDetail = {\n transaction: {\n id: string\n paymentId: string\n providerKey: string\n providerSessionId?: string | null\n gatewayPaymentId?: string | null\n gatewayRefundId?: string | null\n unifiedStatus: string\n gatewayStatus?: string | null\n redirectUrl?: string | null\n amount: string\n currencyCode: string\n gatewayMetadata?: Record<string, unknown> | null\n webhookLog?: Array<{\n eventType: string\n receivedAt: string\n idempotencyKey: string\n unifiedStatus: string\n processed: boolean\n }> | null\n lastWebhookAt?: string | null\n lastPolledAt?: string | null\n expiresAt?: string | null\n createdAt: string | null\n updatedAt: string | null\n }\n logs: TransactionLogEntry[]\n}\n\ntype TransactionsResponse = {\n items: TransactionRow[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nconst STATUS_STYLES: Record<string, string> = {\n pending: 'bg-slate-100 text-slate-800',\n authorized: 'bg-blue-100 text-blue-800',\n captured: 'bg-green-100 text-green-800',\n partially_captured: 'bg-emerald-100 text-emerald-800',\n refunded: 'bg-amber-100 text-amber-800',\n partially_refunded: 'bg-orange-100 text-orange-800',\n cancelled: 'bg-zinc-200 text-zinc-900',\n failed: 'bg-red-100 text-red-800',\n expired: 'bg-neutral-200 text-neutral-900',\n unknown: 'bg-purple-100 text-purple-800',\n}\n\nfunction formatDateTime(value: string | null | undefined): string {\n if (!value) return '\u2014'\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return value\n return parsed.toLocaleString()\n}\n\nfunction formatAmount(value: string, currencyCode: string): string {\n const amount = Number(value)\n if (Number.isNaN(amount)) return `${value} ${currencyCode}`\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: currencyCode,\n }).format(amount)\n } catch {\n return `${amount.toFixed(2)} ${currencyCode}`\n }\n}\n\nfunction formatTypeLabel(value: string): string {\n return value\n .split('_')\n .filter(Boolean)\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nfunction formatLogDetailLabel(key: string): string {\n return key\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .split(' ')\n .filter(Boolean)\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nfunction isPrimitiveLogValue(value: unknown): value is string | number | boolean | null {\n return value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n}\n\nfunction splitLogPayload(payload: Record<string, unknown> | null | undefined) {\n if (!payload) {\n return {\n inlineEntries: [] as Array<[string, string | number | boolean | null]>,\n nestedEntries: [] as Array<[string, unknown]>,\n }\n }\n\n const inlineEntries: Array<[string, string | number | boolean | null]> = []\n const nestedEntries: Array<[string, unknown]> = []\n Object.entries(payload).forEach(([key, value]) => {\n if (isPrimitiveLogValue(value)) {\n inlineEntries.push([key, value])\n return\n }\n nestedEntries.push([key, value])\n })\n return { inlineEntries, nestedEntries }\n}\n\nfunction DetailStat({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div className=\"rounded-lg border bg-muted/30 px-4 py-3\">\n <div className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{label}</div>\n <div className=\"mt-1 text-sm font-medium\">{value}</div>\n </div>\n )\n}\n\nexport default function PaymentTransactionsPage() {\n const t = useT()\n const searchParams = useSearchParams()\n const scopeVersion = useOrganizationScopeVersion()\n const [rows, setRows] = React.useState<TransactionRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [selectedId, setSelectedId] = React.useState<string | null>(null)\n const [detail, setDetail] = React.useState<TransactionDetail | null>(null)\n const [isLoadingDetail, setIsLoadingDetail] = React.useState(false)\n const [detailError, setDetailError] = React.useState<string | null>(null)\n const [isRefreshingStatus, setIsRefreshingStatus] = React.useState(false)\n const [isCapturing, setIsCapturing] = React.useState(false)\n const { runMutation } = useGuardedMutation<{\n entityType: string\n entityId?: string\n }>({\n contextId: 'payment_gateways:transactions-detail',\n })\n const noneLabel = t('common.none', 'None')\n const requestedTransactionId = React.useMemo(() => {\n const raw = searchParams.get('transactionId')\n return raw && raw.trim().length > 0 ? raw.trim() : null\n }, [searchParams])\n\n const formatLogPrimitiveValue = React.useCallback((value: string | number | boolean | null): string => {\n if (value === null) return noneLabel\n if (typeof value === 'boolean') return value ? t('common.yes', 'Yes') : t('common.no', 'No')\n return String(value)\n }, [noneLabel, t])\n\n const loadRows = React.useCallback(async () => {\n setIsLoading(true)\n const params = new URLSearchParams({\n page: String(page),\n pageSize: '20',\n })\n if (search.trim()) params.set('search', search.trim())\n if (typeof filterValues.providerKey === 'string' && filterValues.providerKey) {\n params.set('providerKey', filterValues.providerKey)\n }\n if (typeof filterValues.status === 'string' && filterValues.status) {\n params.set('status', filterValues.status)\n }\n const fallback: TransactionsResponse = { items: [], total: 0, page, pageSize: 20, totalPages: 1 }\n const call = await apiCall<TransactionsResponse>(`/api/payment_gateways/transactions?${params.toString()}`, undefined, { fallback })\n if (call.ok && call.result) {\n setRows(Array.isArray(call.result.items) ? call.result.items : [])\n setTotal(call.result.total ?? 0)\n setTotalPages(call.result.totalPages ?? 1)\n } else {\n flash(t('payment_gateways.transactions.error.load', 'Failed to load payment transactions'), 'error')\n setRows([])\n setTotal(0)\n setTotalPages(1)\n }\n setIsLoading(false)\n }, [filterValues.providerKey, filterValues.status, page, search, t])\n\n const loadDetail = React.useCallback(async (transactionId: string) => {\n setIsLoadingDetail(true)\n setDetailError(null)\n const call = await apiCall<TransactionDetail>(`/api/payment_gateways/transactions/${encodeURIComponent(transactionId)}`, undefined, { fallback: null })\n if (call.ok && call.result) {\n setDetail(call.result)\n } else {\n setDetail(null)\n setDetailError(t('payment_gateways.transactions.error.loadDetail', 'Failed to load transaction details'))\n }\n setIsLoadingDetail(false)\n }, [t])\n\n React.useEffect(() => {\n void loadRows()\n }, [loadRows, scopeVersion])\n\n React.useEffect(() => {\n if (!selectedId) {\n setDetail(null)\n setDetailError(null)\n return\n }\n void loadDetail(selectedId)\n }, [loadDetail, selectedId])\n\n React.useEffect(() => {\n setSelectedId((current) => (current && rows.some((row) => row.id === current) ? current : null))\n }, [rows])\n\n React.useEffect(() => {\n if (!requestedTransactionId) return\n if (!rows.some((row) => row.id === requestedTransactionId)) return\n setSelectedId(requestedTransactionId)\n }, [requestedTransactionId, rows])\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n const next: FilterValues = {}\n Object.entries(values).forEach(([key, value]) => {\n if (typeof value === 'string' && value.trim()) next[key] = value\n })\n setFilterValues(next)\n setPage(1)\n }, [])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const handleRefreshStatus = React.useCallback(async () => {\n if (!selectedId) return\n setIsRefreshingStatus(true)\n try {\n await runMutation({\n operation: async () => {\n await apiCallOrThrow('/api/payment_gateways/status', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ transactionId: selectedId }),\n })\n },\n context: {\n entityType: 'payment_gateways:gateway_transaction',\n entityId: selectedId,\n },\n mutationPayload: { transactionId: selectedId },\n })\n await Promise.all([\n loadRows(),\n loadDetail(selectedId),\n ])\n flash(t('payment_gateways.transactions.success.refreshStatus', 'Transaction status refreshed'), 'success')\n } catch (error) {\n const message = error instanceof Error && error.message\n ? error.message\n : t('payment_gateways.transactions.error.refreshStatus', 'Failed to refresh transaction status')\n flash(message, 'error')\n } finally {\n setIsRefreshingStatus(false)\n }\n }, [loadDetail, loadRows, runMutation, selectedId, t])\n\n const handleCapturePayment = React.useCallback(async () => {\n if (!selectedId) return\n setIsCapturing(true)\n try {\n await runMutation({\n operation: async () => {\n await apiCallOrThrow('/api/payment_gateways/capture', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ transactionId: selectedId }),\n })\n },\n context: {\n entityType: 'payment_gateways:gateway_transaction',\n entityId: selectedId,\n },\n mutationPayload: { transactionId: selectedId },\n })\n await Promise.all([\n loadRows(),\n loadDetail(selectedId),\n ])\n flash(t('payment_gateways.transactions.success.capture', 'Payment captured'), 'success')\n } catch (error) {\n const message = error instanceof Error && error.message\n ? error.message\n : t('payment_gateways.transactions.error.capture', 'Failed to capture payment')\n flash(message, 'error')\n } finally {\n setIsCapturing(false)\n }\n }, [loadDetail, loadRows, runMutation, selectedId, t])\n\n const providerOptions = React.useMemo(() => {\n const values = Array.from(new Set(rows.map((row) => row.providerKey).filter(Boolean))).sort((a, b) => a.localeCompare(b))\n return values.map((value) => ({\n label: formatTypeLabel(value),\n value,\n }))\n }, [rows])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'providerKey',\n type: 'select',\n label: t('payment_gateways.transactions.filters.provider', 'Provider'),\n options: [\n { label: t('payment_gateways.transactions.filters.allProviders', 'All providers'), value: '' },\n ...providerOptions,\n ],\n },\n {\n id: 'status',\n type: 'select',\n label: t('payment_gateways.transactions.filters.status', 'Status'),\n options: [\n { label: t('payment_gateways.transactions.filters.allStatuses', 'All statuses'), value: '' },\n { label: t('payment_gateways.status.pending', 'Pending'), value: 'pending' },\n { label: t('payment_gateways.status.authorized', 'Authorized'), value: 'authorized' },\n { label: t('payment_gateways.status.captured', 'Captured'), value: 'captured' },\n { label: t('payment_gateways.status.partially_captured', 'Partially Captured'), value: 'partially_captured' },\n { label: t('payment_gateways.status.refunded', 'Refunded'), value: 'refunded' },\n { label: t('payment_gateways.status.partially_refunded', 'Partially Refunded'), value: 'partially_refunded' },\n { label: t('payment_gateways.status.cancelled', 'Cancelled'), value: 'cancelled' },\n { label: t('payment_gateways.status.failed', 'Failed'), value: 'failed' },\n { label: t('payment_gateways.status.expired', 'Expired'), value: 'expired' },\n { label: t('payment_gateways.status.unknown', 'Unknown'), value: 'unknown' },\n ],\n },\n ], [providerOptions, t])\n\n const columns = React.useMemo<ColumnDef<TransactionRow>[]>(() => [\n {\n accessorKey: 'paymentId',\n header: t('payment_gateways.transactions.columns.paymentId', 'Payment'),\n cell: ({ row }) => (\n <div className=\"space-y-1\">\n <div className=\"font-medium\">{row.original.paymentId}</div>\n <div className=\"text-xs text-muted-foreground\">{row.original.id}</div>\n </div>\n ),\n meta: { maxWidth: '20rem' },\n },\n {\n accessorKey: 'providerKey',\n header: t('payment_gateways.transactions.columns.provider', 'Provider'),\n cell: ({ row }) => (\n <div className=\"flex items-center gap-2\">\n <CreditCard className=\"h-4 w-4 text-muted-foreground\" />\n <span>{formatTypeLabel(row.original.providerKey)}</span>\n </div>\n ),\n },\n {\n accessorKey: 'unifiedStatus',\n header: t('payment_gateways.transactions.columns.status', 'Status'),\n cell: ({ row }) => (\n <Badge variant=\"secondary\" className={STATUS_STYLES[row.original.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${row.original.unifiedStatus}`, formatTypeLabel(row.original.unifiedStatus))}\n </Badge>\n ),\n },\n {\n accessorKey: 'amount',\n header: t('payment_gateways.transactions.columns.amount', 'Amount'),\n cell: ({ row }) => formatAmount(row.original.amount, row.original.currencyCode),\n },\n {\n accessorKey: 'providerSessionId',\n header: t('payment_gateways.transactions.columns.session', 'Session'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs text-muted-foreground\">\n {row.original.providerSessionId ?? '\u2014'}\n </span>\n ),\n meta: { maxWidth: '18rem', truncate: true },\n },\n {\n accessorKey: 'updatedAt',\n header: t('payment_gateways.transactions.columns.updatedAt', 'Updated'),\n cell: ({ row }) => formatDateTime(row.original.updatedAt),\n },\n ], [t])\n\n const selectedSummary = React.useMemo(\n () => rows.find((row) => row.id === selectedId) ?? null,\n [rows, selectedId],\n )\n const canCaptureSelected = React.useMemo(() => {\n const status = detail?.transaction.unifiedStatus ?? selectedSummary?.unifiedStatus ?? null\n return status === 'authorized'\n }, [detail?.transaction.unifiedStatus, selectedSummary?.unifiedStatus])\n\n return (\n <Page>\n <PageHeader\n title={t('payment_gateways.transactions.title', 'Payment Transactions')}\n description={t('payment_gateways.transactions.description', 'Track all payment-gateway transactions, inspect webhook activity, and review provider logs from one place.')}\n />\n <PageBody className=\"space-y-6\">\n <DataTable\n stickyActionsColumn\n title={t('payment_gateways.transactions.tableTitle', 'Transactions')}\n columns={columns}\n data={rows}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('payment_gateways.transactions.searchPlaceholder', 'Search by payment, transaction, session, or gateway id')}\n perspective={{ tableId: 'payment_gateways.transactions.list' }}\n pagination={{ page, pageSize: 20, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n onRowClick={(row) => setSelectedId((current) => current === row.id ? null : row.id)}\n rowActions={(row) => (\n <RowActions items={[\n {\n id: 'details',\n label: selectedId === row.id\n ? t('payment_gateways.transactions.actions.hideDetails', 'Hide details')\n : t('payment_gateways.transactions.actions.showDetails', 'Show details'),\n onSelect: () => setSelectedId((current) => current === row.id ? null : row.id),\n },\n ]} />\n )}\n />\n\n {selectedId ? (\n <Card>\n <CardHeader className=\"gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-1\">\n <CardTitle>{t('payment_gateways.transactions.detail.title', 'Transaction details')}</CardTitle>\n {selectedSummary ? (\n <div className=\"flex flex-wrap items-center gap-2 text-sm text-muted-foreground\">\n <span>{selectedSummary.paymentId}</span>\n <span>\u2022</span>\n <span>{formatTypeLabel(selectedSummary.providerKey)}</span>\n <Badge variant=\"secondary\" className={STATUS_STYLES[selectedSummary.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${selectedSummary.unifiedStatus}`, formatTypeLabel(selectedSummary.unifiedStatus))}\n </Badge>\n </div>\n ) : null}\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void handleRefreshStatus()}\n disabled={isRefreshingStatus || isLoadingDetail || isCapturing}\n >\n {isRefreshingStatus ? <Spinner className=\"mr-2 h-4 w-4\" /> : <RefreshCw className=\"mr-2 h-4 w-4\" />}\n {t('payment_gateways.transactions.actions.refreshStatus', 'Refresh status')}\n </Button>\n {canCaptureSelected ? (\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void handleCapturePayment()}\n disabled={isRefreshingStatus || isLoadingDetail || isCapturing}\n >\n {isCapturing ? <Spinner className=\"mr-2 h-4 w-4\" /> : <HandCoins className=\"mr-2 h-4 w-4\" />}\n {t('payment_gateways.transactions.actions.capture', 'Capture payment')}\n </Button>\n ) : null}\n </CardHeader>\n <CardContent className=\"space-y-6\">\n {isLoadingDetail ? <LoadingMessage label={t('payment_gateways.transactions.detail.loading', 'Loading transaction details')} /> : null}\n {!isLoadingDetail && detailError ? <ErrorMessage label={detailError} /> : null}\n {!isLoadingDetail && !detailError && detail ? (\n <>\n <div className=\"grid gap-4 lg:grid-cols-4\">\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.status', 'Status')}\n value={(\n <Badge variant=\"secondary\" className={STATUS_STYLES[detail.transaction.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${detail.transaction.unifiedStatus}`, formatTypeLabel(detail.transaction.unifiedStatus))}\n </Badge>\n )}\n />\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.gatewayStatus', 'Gateway status')}\n value={detail.transaction.gatewayStatus ?? noneLabel}\n />\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.amount', 'Amount')}\n value={formatAmount(detail.transaction.amount, detail.transaction.currencyCode)}\n />\n <DetailStat\n label={t('payment_gateways.transactions.detail.summary.provider', 'Provider')}\n value={formatTypeLabel(detail.transaction.providerKey)}\n />\n </div>\n\n <div className=\"grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]\">\n <div className=\"space-y-6\">\n <section className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold\">{t('payment_gateways.transactions.detail.identifiers', 'Identifiers')}</h3>\n <dl className=\"grid gap-3 md:grid-cols-2\">\n {[\n [t('payment_gateways.transactions.columns.transactionId', 'Transaction ID'), detail.transaction.id],\n [t('payment_gateways.transactions.columns.paymentId', 'Payment ID'), detail.transaction.paymentId],\n [t('payment_gateways.transactions.columns.session', 'Session ID'), detail.transaction.providerSessionId ?? '\u2014'],\n [t('payment_gateways.transactions.columns.gatewayPaymentId', 'Gateway payment ID'), detail.transaction.gatewayPaymentId ?? '\u2014'],\n [t('payment_gateways.transactions.columns.gatewayRefundId', 'Gateway refund ID'), detail.transaction.gatewayRefundId ?? '\u2014'],\n [t('payment_gateways.transactions.columns.redirectUrl', 'Redirect URL'), detail.transaction.redirectUrl ?? '\u2014'],\n [t('payment_gateways.transactions.columns.createdAt', 'Created at'), formatDateTime(detail.transaction.createdAt)],\n [t('payment_gateways.transactions.columns.updatedAt', 'Updated at'), formatDateTime(detail.transaction.updatedAt)],\n [t('payment_gateways.transactions.columns.lastWebhookAt', 'Last webhook'), formatDateTime(detail.transaction.lastWebhookAt)],\n [t('payment_gateways.transactions.columns.lastPolledAt', 'Last poll'), formatDateTime(detail.transaction.lastPolledAt)],\n ].map(([label, value]) => (\n <div key={label} className=\"rounded-lg border bg-muted/30 px-4 py-3\">\n <dt className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{label}</dt>\n <dd className=\"mt-1 break-all text-sm\">{value}</dd>\n </div>\n ))}\n </dl>\n </section>\n\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Webhook className=\"h-4 w-4 text-muted-foreground\" />\n <h3 className=\"text-sm font-semibold\">{t('payment_gateways.transactions.detail.webhooks', 'Webhook activity')}</h3>\n </div>\n {detail.transaction.webhookLog && detail.transaction.webhookLog.length > 0 ? (\n <div className=\"overflow-hidden rounded-lg border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/50\">\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.eventType', 'Event')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.status', 'Status')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.processed', 'Processed')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('payment_gateways.transactions.columns.receivedAt', 'Received')}</th>\n </tr>\n </thead>\n <tbody>\n {detail.transaction.webhookLog.map((entry) => (\n <tr key={`${entry.idempotencyKey}:${entry.receivedAt}`} className=\"border-b last:border-0\">\n <td className=\"px-4 py-2 font-medium\">{entry.eventType}</td>\n <td className=\"px-4 py-2\">\n <Badge variant=\"secondary\" className={STATUS_STYLES[entry.unifiedStatus] ?? ''}>\n {t(`payment_gateways.status.${entry.unifiedStatus}`, formatTypeLabel(entry.unifiedStatus))}\n </Badge>\n </td>\n <td className=\"px-4 py-2\">{entry.processed ? t('common.yes', 'Yes') : t('common.no', 'No')}</td>\n <td className=\"px-4 py-2 text-muted-foreground\">{formatDateTime(entry.receivedAt)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('payment_gateways.transactions.detail.webhooksEmpty', 'No webhook events have been recorded for this transaction yet.')}</p>\n )}\n </section>\n\n <section className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold\">{t('payment_gateways.transactions.detail.logs', 'Gateway logs')}</h3>\n <LogList\n entries={detail.logs.map<LogListEntry>((log) => {\n const metadataEntries = [\n [t('payment_gateways.transactions.log.time', 'Time'), formatDateTime(log.createdAt)],\n [t('payment_gateways.transactions.log.level', 'Level'), t(`payment_gateways.transactions.level.${log.level}`, log.level)],\n [t('payment_gateways.transactions.log.code', 'Code'), log.code ?? null],\n [t('payment_gateways.transactions.log.runId', 'Run ID'), log.runId ?? null],\n [t('payment_gateways.transactions.log.entityType', 'Entity Type'), log.scopeEntityType ?? null],\n [t('payment_gateways.transactions.log.entityId', 'Entity ID'), log.scopeEntityId ?? null],\n ].filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim().length > 0)\n const { inlineEntries, nestedEntries } = splitLogPayload(log.payload)\n\n return {\n id: log.id,\n time: formatDateTime(log.createdAt),\n level: log.level,\n levelLabel: t(`payment_gateways.transactions.level.${log.level}`, log.level),\n message: log.message,\n body: (\n <div className=\"space-y-4\">\n {metadataEntries.length > 0 ? (\n <dl className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {metadataEntries.map(([label, value]) => (\n <div key={label} className=\"rounded-md border bg-muted/30 px-3 py-2\">\n <dt className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{label}</dt>\n <dd className=\"mt-1 break-all text-sm\">{value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n {inlineEntries.length > 0 ? (\n <dl className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {inlineEntries.map(([key, value]) => (\n <div key={key} className=\"rounded-md border bg-muted/30 px-3 py-2\">\n <dt className=\"text-overline font-medium uppercase tracking-wide text-muted-foreground\">{formatLogDetailLabel(key)}</dt>\n <dd className=\"mt-1 break-words text-sm\">{formatLogPrimitiveValue(value)}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n {nestedEntries.map(([key, value]) => (\n <JsonDisplay\n key={key}\n data={value}\n title={formatLogDetailLabel(key)}\n defaultExpanded\n maxInitialDepth={1}\n theme=\"dark\"\n maxHeight=\"16rem\"\n className=\"p-4\"\n />\n ))}\n </div>\n ),\n }\n })}\n emptyMessage={t('payment_gateways.transactions.detail.logsEmpty', 'No transaction-scoped logs are available yet.')}\n />\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <JsonDisplay\n data={detail.transaction.gatewayMetadata ?? {}}\n title={t('payment_gateways.transactions.detail.gatewayMetadata', 'Gateway metadata')}\n defaultExpanded\n maxInitialDepth={1}\n theme=\"dark\"\n maxHeight=\"24rem\"\n className=\"p-4\"\n />\n <InjectionSpot\n spotId=\"admin.page:payment-gateways/transactions:after\"\n context={{ selectedPaymentId: detail.transaction.paymentId }}\n />\n </div>\n </div>\n </>\n ) : null}\n </CardContent>\n </Card>\n ) : null}\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6339.1.193c6c7c71",
3
+ "version": "0.6.6-develop.6344.1.c4b17c07c4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -246,16 +246,16 @@
246
246
  "zod": "^4.4.3"
247
247
  },
248
248
  "peerDependencies": {
249
- "@open-mercato/ai-assistant": "0.6.6-develop.6339.1.193c6c7c71",
250
- "@open-mercato/shared": "0.6.6-develop.6339.1.193c6c7c71",
251
- "@open-mercato/ui": "0.6.6-develop.6339.1.193c6c7c71",
249
+ "@open-mercato/ai-assistant": "0.6.6-develop.6344.1.c4b17c07c4",
250
+ "@open-mercato/shared": "0.6.6-develop.6344.1.c4b17c07c4",
251
+ "@open-mercato/ui": "0.6.6-develop.6344.1.c4b17c07c4",
252
252
  "react": "^19.0.0",
253
253
  "react-dom": "^19.0.0"
254
254
  },
255
255
  "devDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6339.1.193c6c7c71",
257
- "@open-mercato/shared": "0.6.6-develop.6339.1.193c6c7c71",
258
- "@open-mercato/ui": "0.6.6-develop.6339.1.193c6c7c71",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6344.1.c4b17c07c4",
257
+ "@open-mercato/shared": "0.6.6-develop.6344.1.c4b17c07c4",
258
+ "@open-mercato/ui": "0.6.6-develop.6344.1.c4b17c07c4",
259
259
  "@testing-library/dom": "^10.4.1",
260
260
  "@testing-library/jest-dom": "^6.9.1",
261
261
  "@testing-library/react": "^16.3.1",
@@ -64,7 +64,7 @@ export function ProductComplianceSection({
64
64
  (code: string, checked: boolean) => {
65
65
  const current = Array.isArray(values.gtuCodes) ? values.gtuCodes : [];
66
66
  const next = checked
67
- ? Array.from(new Set([...current, code])).sort()
67
+ ? Array.from(new Set([...current, code])).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
68
68
  : current.filter((entry) => entry !== code);
69
69
  setValue("gtuCodes", next);
70
70
  },
@@ -236,7 +236,7 @@ const productBaseSchema = scoped.extend({
236
236
  gtuCodes: z
237
237
  .array(z.enum(CATALOG_GTU_CODES))
238
238
  .max(13)
239
- .transform((codes) => Array.from(new Set(codes)).sort())
239
+ .transform((codes) => Array.from(new Set(codes)).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)))
240
240
  .nullable()
241
241
  .optional(),
242
242
  ageMin: z.coerce.number().int().min(0).max(120).nullable().optional(),
@@ -159,7 +159,7 @@ export default function ChannelDetailPage() {
159
159
  </p>
160
160
  ) : (
161
161
  <dl className="grid grid-cols-2 gap-x-6 gap-y-1 text-sm md:grid-cols-3">
162
- {capabilityKeys.sort().map((key) => (
162
+ {capabilityKeys.sort((a, b) => a.localeCompare(b)).map((key) => (
163
163
  <React.Fragment key={key}>
164
164
  <dt className="text-muted-foreground">{key}</dt>
165
165
  <dd className="md:col-span-2">{renderCapabilityValue(capabilities[key])}</dd>
@@ -1,9 +1,5 @@
1
1
  import type { AwilixContainer } from 'awilix'
2
- import {
3
- runCrudMutationGuardAfterSuccess,
4
- validateCrudMutationGuard,
5
- type CrudMutationGuardValidationResult,
6
- } from '@open-mercato/shared/lib/crud/mutation-guard'
2
+ import { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'
7
3
 
8
4
  type RouteAuth = {
9
5
  sub?: string | null
@@ -19,10 +15,21 @@ type RouteMutationGuardInput = {
19
15
  }
20
16
 
21
17
  export type RouteMutationGuardContext = {
22
- result: CrudMutationGuardValidationResult | null
18
+ /**
19
+ * Marker kept for source compatibility with callers; the registry result is
20
+ * fully handled inside the wrapper, so callers only need `afterSuccess`.
21
+ */
22
+ result: { ok: true } | null
23
23
  afterSuccess: () => Promise<void>
24
24
  }
25
25
 
26
+ /**
27
+ * Run a communication-channels custom write route through the full mutation
28
+ * guard registry (`runRouteMutationGuards`) instead of only the legacy DI
29
+ * service. Returns `{ response }` when a guard blocks the mutation, or
30
+ * `{ result, afterSuccess }` to run after a successful write — preserving the
31
+ * shape the dependent routes already consume.
32
+ */
26
33
  export async function validateRouteMutationGuard(params: {
27
34
  container: AwilixContainer
28
35
  req: Request
@@ -34,35 +41,28 @@ export async function validateRouteMutationGuard(params: {
34
41
  return { result: null, afterSuccess: async () => undefined }
35
42
  }
36
43
 
37
- const base = {
38
- tenantId: auth.tenantId,
39
- organizationId: auth.orgId ?? null,
40
- userId: auth.sub,
41
- resourceKind: input.resourceKind,
42
- resourceId: input.resourceId,
43
- operation: input.operation ?? 'custom',
44
- requestMethod: req.method,
45
- requestHeaders: req.headers,
46
- } as const
47
-
48
- const result = await validateCrudMutationGuard(container, {
49
- ...base,
50
- mutationPayload: input.mutationPayload ?? null,
44
+ const guarded = await runRouteMutationGuards({
45
+ container,
46
+ req,
47
+ auth: {
48
+ userId: auth.sub,
49
+ tenantId: auth.tenantId,
50
+ organizationId: auth.orgId ?? null,
51
+ },
52
+ input: {
53
+ resourceKind: input.resourceKind,
54
+ resourceId: input.resourceId,
55
+ operation: input.operation ?? 'custom',
56
+ mutationPayload: input.mutationPayload ?? null,
57
+ },
51
58
  })
52
- if (result && !result.ok) {
53
- return {
54
- response: Response.json(result.body, { status: result.status }),
55
- }
59
+
60
+ if (!guarded.ok) {
61
+ return { response: guarded.response }
56
62
  }
57
63
 
58
64
  return {
59
- result,
60
- afterSuccess: async () => {
61
- if (!result?.ok || !result.shouldRunAfterSuccess) return
62
- await runCrudMutationGuardAfterSuccess(container, {
63
- ...base,
64
- metadata: result.metadata ?? null,
65
- })
66
- },
65
+ result: { ok: true },
66
+ afterSuccess: guarded.runAfterSuccess,
67
67
  }
68
68
  }
@@ -606,10 +606,10 @@ export default function DealsKanbanPage(): React.ReactElement {
606
606
  () => ({
607
607
  pipelineId: selectedPipelineId,
608
608
  search: search.trim(),
609
- status: statusFilters.slice().sort().join(','),
610
- owners: ownerFilters.slice().sort().join(','),
611
- people: peopleFilters.slice().sort().join(','),
612
- companies: companyFilters.slice().sort().join(','),
609
+ status: statusFilters.slice().sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join(','),
610
+ owners: ownerFilters.slice().sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join(','),
611
+ people: peopleFilters.slice().sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join(','),
612
+ companies: companyFilters.slice().sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join(','),
613
613
  closeFrom: closeDateFilter.from ?? '',
614
614
  closeTo: closeDateFilter.to ?? '',
615
615
  currency: currencyFilter ?? '',
@@ -223,7 +223,7 @@ export function CalendarScreen() {
223
223
  const values = new Set<string>(Object.keys(typeLabels))
224
224
  for (const item of items) values.add(item.interactionType)
225
225
  return [...values]
226
- .sort()
226
+ .sort((a, b) => a.localeCompare(b))
227
227
  .map((value) => ({ value, label: typeLabels[value] ?? value }))
228
228
  }, [items, typeLabels])
229
229
 
@@ -12,6 +12,10 @@ import {
12
12
  type CredentialsService,
13
13
  } from '../../../lib/credentials-service'
14
14
  import { collectCredentialUrlValidationErrors } from '../../../lib/credentials-field-validation'
15
+ import {
16
+ maskSecretCredentials,
17
+ mergeMaskedSecretCredentials,
18
+ } from '../../../lib/credentials-masking'
15
19
  import {
16
20
  resolveUserFeatures,
17
21
  runIntegrationMutationGuardAfterSuccess,
@@ -71,10 +75,14 @@ export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }>
71
75
  throw error
72
76
  }
73
77
 
78
+ const schema = credentialsService.getSchema(integration.id)
79
+ const { credentials, secretFieldsConfigured } = maskSecretCredentials(schema, values ?? {})
80
+
74
81
  return NextResponse.json({
75
82
  integrationId: integration.id,
76
- schema: credentialsService.getSchema(integration.id),
77
- credentials: values ?? {},
83
+ schema,
84
+ credentials,
85
+ secretFieldsConfigured,
78
86
  updatedAt: updatedAt?.toISOString() ?? null,
79
87
  })
80
88
  }
@@ -134,6 +142,7 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
134
142
 
135
143
  const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
136
144
  const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
145
+ const schema = credentialsService.getSchema(integration.id)
137
146
 
138
147
  try {
139
148
  const currentUpdatedAt = await credentialsService.resolveUpdatedAt(integration.id, scope)
@@ -154,7 +163,7 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
154
163
  }
155
164
 
156
165
  const credentialFieldErrors = collectCredentialUrlValidationErrors(
157
- credentialsService.getSchema(integration.id),
166
+ schema,
158
167
  payloadData.credentials,
159
168
  )
160
169
  if (Object.keys(credentialFieldErrors).length > 0) {
@@ -165,7 +174,12 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
165
174
  }
166
175
 
167
176
  try {
168
- await credentialsService.save(integration.id, payloadData.credentials, scope)
177
+ // Secret fields are returned masked on GET; when the client round-trips the
178
+ // mask sentinel it means "unchanged", so restore the existing stored secret
179
+ // instead of overwriting it with the placeholder.
180
+ const existing = await credentialsService.resolve(integration.id, scope)
181
+ const credentialsToSave = mergeMaskedSecretCredentials(schema, payloadData.credentials, existing ?? {})
182
+ await credentialsService.save(integration.id, credentialsToSave, scope)
169
183
  } catch (error) {
170
184
  if (isCredentialsEncryptionUnavailableError(error)) {
171
185
  return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })
@@ -0,0 +1,95 @@
1
+ import type {
2
+ CredentialFieldType,
3
+ IntegrationCredentialsSchema,
4
+ } from '@open-mercato/shared/modules/integrations/types'
5
+
6
+ /**
7
+ * Credential field types whose stored value is a secret (API keys, OAuth client
8
+ * secrets/tokens, SSH private keys). These MUST never be returned in plaintext
9
+ * from the credentials API — they are masked on read and treated as write-only.
10
+ */
11
+ export const SECRET_CREDENTIAL_FIELD_TYPES: ReadonlySet<CredentialFieldType> = new Set<CredentialFieldType>([
12
+ 'secret',
13
+ 'oauth',
14
+ 'ssh_keypair',
15
+ ])
16
+
17
+ /**
18
+ * Opaque sentinel returned in place of a configured secret value. The client
19
+ * round-trips this value back on save when the user did not change the field;
20
+ * the PUT handler then preserves the existing stored secret instead of writing
21
+ * the sentinel. Chosen to be extremely unlikely to collide with a real secret.
22
+ */
23
+ export const MASKED_SECRET_VALUE = '__om_secret_unchanged__'
24
+
25
+ function isSecretField(type: CredentialFieldType): boolean {
26
+ return SECRET_CREDENTIAL_FIELD_TYPES.has(type)
27
+ }
28
+
29
+ function hasPresentValue(value: unknown): boolean {
30
+ if (value === undefined || value === null) return false
31
+ if (typeof value === 'string') return value.length > 0
32
+ if (typeof value === 'object') return Object.keys(value as Record<string, unknown>).length > 0
33
+ return true
34
+ }
35
+
36
+ export type MaskSecretCredentialsResult = {
37
+ credentials: Record<string, unknown>
38
+ secretFieldsConfigured: Record<string, boolean>
39
+ }
40
+
41
+ /**
42
+ * Replace every configured secret-typed field with an opaque sentinel so the
43
+ * decrypted plaintext never reaches the API response (and therefore the
44
+ * browser, devtools, proxies, or editable DOM inputs). Non-secret config fields
45
+ * pass through unchanged. A `secretFieldsConfigured` map reports which secret
46
+ * fields currently hold a value without exposing it.
47
+ */
48
+ export function maskSecretCredentials(
49
+ schema: IntegrationCredentialsSchema | undefined,
50
+ values: Record<string, unknown>,
51
+ ): MaskSecretCredentialsResult {
52
+ const credentials: Record<string, unknown> = { ...values }
53
+ const secretFieldsConfigured: Record<string, boolean> = {}
54
+
55
+ for (const field of schema?.fields ?? []) {
56
+ if (!isSecretField(field.type)) continue
57
+ const configured = hasPresentValue(credentials[field.key])
58
+ secretFieldsConfigured[field.key] = configured
59
+ if (configured) {
60
+ credentials[field.key] = MASKED_SECRET_VALUE
61
+ } else {
62
+ delete credentials[field.key]
63
+ }
64
+ }
65
+
66
+ return { credentials, secretFieldsConfigured }
67
+ }
68
+
69
+ /**
70
+ * Reverse of {@link maskSecretCredentials} for the save path. When the client
71
+ * submits the mask sentinel for a secret field it means "leave it unchanged":
72
+ * restore the existing stored secret, or drop the field entirely when nothing
73
+ * was previously stored (so the literal sentinel is never persisted). Any other
74
+ * value (including an empty string, which clears the secret) is written as-is.
75
+ */
76
+ export function mergeMaskedSecretCredentials(
77
+ schema: IntegrationCredentialsSchema | undefined,
78
+ incoming: Record<string, unknown>,
79
+ existing: Record<string, unknown>,
80
+ ): Record<string, unknown> {
81
+ const merged: Record<string, unknown> = { ...incoming }
82
+
83
+ for (const field of schema?.fields ?? []) {
84
+ if (!isSecretField(field.type)) continue
85
+ if (merged[field.key] !== MASKED_SECRET_VALUE) continue
86
+
87
+ if (hasPresentValue(existing[field.key])) {
88
+ merged[field.key] = existing[field.key]
89
+ } else {
90
+ delete merged[field.key]
91
+ }
92
+ }
93
+
94
+ return merged
95
+ }