"use client"
import { useState, useEffect, useCallback } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import { motion, AnimatePresence } from "motion/react"
import { Key, Smartphone, Tablet, Monitor, RotateCw, Plus, Trash2, Copy, ExternalLink, CreditCard, Store, Loader2, Eye, EyeOff, Expand, Minimize, Sun, Moon } from "lucide-react"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Label } from "@/components/ui/label"
import { Spinner } from "@/components/ui/spinner"
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter } from "@/components/ui/sheet"
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
import { useMerchant } from "@/contexts/merchant-context"
import { TransactionProvider, useTransaction } from "@/contexts/transaction-context"
import { useUser } from "@/contexts/user-context"
import { MerchantService, ConfigService, type PaymentLink, type GetMerchantOrderResponse, type MerchantAPIKey, type UserPayConfig } from "@/lib/services"
import { PayingInfo } from "@/components/common/pay/paying/paying-info"
import { PayingNow } from "@/components/common/pay/paying/paying-now"
import { MerchantSelector } from "@/components/common/merchant/merchant-selector"
import { LoadingPage } from "@/components/layout/loading"
import { EmptyState } from "@/components/layout/empty"
import { TransactionTableList } from "@/components/common/general/table-data"
/** 设备预览配置 */
const DEVICE_CONFIG = {
mobile: {
width: 375,
height: 812,
borderRadius: 50,
borderWidth: 14,
scale: 0.6
},
tablet: {
width: 768,
height: 1024,
borderRadius: 32,
borderWidth: 18,
scale: 0.45
},
desktop: {
width: 1200,
height: 750,
borderRadius: 16,
borderWidth: 8,
scale: 0.35
}
} as const
export function MerchantOnline() {
const router = useRouter()
const searchParams = useSearchParams()
const { apiKeys, loading: loadingKeys, loadAPIKeys } = useMerchant()
useEffect(() => {
loadAPIKeys()
}, [loadAPIKeys])
const apiKeyId = searchParams.get("apiKeyId")
const selectedKey = apiKeys.find(k => k.id.toString() === apiKeyId) || null
if (loadingKeys) {
return
}
if (apiKeys.length === 0) {
return (
在线流转
)
}
return (
)
}
interface MerchantOnlineContentProps {
apiKeys: MerchantAPIKey[]
}
function MerchantOnlineContent({ apiKeys }: MerchantOnlineContentProps) {
const router = useRouter()
const searchParams = useSearchParams()
const {
transactions,
loading: loadingTransactions,
error: transactionsError,
fetchTransactions,
refresh
} = useTransaction()
/* 派生 selectedKey */
const apiKeyId = searchParams.get("apiKeyId")
/* 处理集市应用切换 */
const handleMerchantSelect = useCallback((id: string) => {
const params = new URLSearchParams(searchParams.toString())
params.set("apiKeyId", id.toString())
router.push(`${ window.location.pathname }?${ params.toString() }`)
}, [searchParams, router])
/* 自动选择第一个集市应用 */
useEffect(() => {
if (apiKeys.length > 0 && !apiKeyId) {
handleMerchantSelect(apiKeys[0].id)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [apiKeys.length, apiKeyId])
const selectedKey = apiKeys.find(k => k.id.toString() === apiKeyId) || null
/* 集市状态 */
const [paymentLinks, setPaymentLinks] = useState([])
const [selectedLink, setSelectedLink] = useState(null)
const [previewLink, setPreviewLink] = useState(null)
const [isCreating, setIsCreating] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const [loading, setLoading] = useState(false)
/* 创建/编辑表单状态 */
const [productName, setProductName] = useState("")
const [amount, setAmount] = useState("")
const [remark, setRemark] = useState("")
const [totalLimit, setTotalLimit] = useState("")
const [userLimit, setUserLimit] = useState("")
/* 设备预览状态 */
const [previewDevice, setPreviewDevice] = useState<'mobile' | 'tablet' | 'desktop'>('mobile')
const [showToken, setShowToken] = useState(false)
const [isFullscreen, setIsFullscreen] = useState(false)
const [previewTheme, setPreviewTheme] = useState<'light' | 'dark'>('light')
/* 获取应用列表 */
const fetchLinks = useCallback(async () => {
if (!selectedKey) {
setPaymentLinks([])
return
}
try {
setLoading(true)
const links = await MerchantService.listPaymentLinks(selectedKey.id)
setPaymentLinks(links)
/* 自动设置第一个应用为预览 */
if (links.length > 0) {
setPreviewLink(prev => prev || links[0])
}
} catch (error) {
console.error(error)
toast.error("获取应用列表失败")
} finally {
setLoading(false)
}
}, [selectedKey])
/* 当 selectedKey 变化时,重新获取应用列表和数据 */
useEffect(() => {
if (selectedKey?.client_id) {
fetchLinks()
fetchTransactions({
page: 1,
page_size: 20,
types: ['online'],
client_id: selectedKey.client_id
})
setIsCreating(false)
setSelectedLink(null)
} else {
setPaymentLinks([])
setIsCreating(false)
setSelectedLink(null)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedKey?.client_id])
/* 处理创建 */
const handleCreate = async () => {
if (!selectedKey) return
if (!productName || !amount) {
toast.error("请填写在线积分流转服务的名称和数量")
return
}
if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
toast.error("积分数量必须设置为大于0的数值")
return
}
// 验证限制字段
if (totalLimit && (isNaN(parseInt(totalLimit)) || parseInt(totalLimit) < 1)) {
toast.error("总数量限制必须是大于0的整数")
return
}
if (userLimit && (isNaN(parseInt(userLimit)) || parseInt(userLimit) < 1)) {
toast.error("单用户限制必须是大于0的整数")
return
}
try {
setLoading(true)
const newLink = await MerchantService.createPaymentLink(selectedKey.id, {
product_name: productName,
amount: parseFloat(amount),
remark,
...(totalLimit && { total_limit: parseInt(totalLimit) }),
...(userLimit && { user_limit: parseInt(userLimit) })
})
toast.success("在线积分流转服务创建成功")
/* 重置表单 */
setProductName("")
setAmount("")
setRemark("")
setTotalLimit("")
setUserLimit("")
/* 更新UI */
fetchLinks()
setIsCreating(false)
setSelectedLink(newLink)
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "在线积分流转服务创建失败"
toast.error(message)
} finally {
setLoading(false)
}
}
/* 处理删除 */
const handleDelete = async (link: PaymentLink) => {
if (!selectedKey) return
if (!confirm("确定要删除这个在线积分流转服务吗?该操作不可恢复。")) return
try {
setLoading(true)
await MerchantService.deletePaymentLink(selectedKey.id, link.id)
toast.success("在线积分流转服务已删除")
if (selectedLink?.id === link.id) {
setSelectedLink(null)
}
fetchLinks()
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "在线积分流转服务删除失败"
toast.error(message)
} finally {
setLoading(false)
}
}
/* 处理编辑 */
const handleEdit = (link: PaymentLink) => {
setProductName(link.product_name)
setAmount(link.amount)
setRemark(link.remark || "")
setTotalLimit(link.total_limit?.toString() || "")
setUserLimit(link.user_limit?.toString() || "")
setIsEditing(true)
}
/* 处理更新 */
const handleUpdate = async () => {
if (!selectedKey || !selectedLink) return
if (!productName || !amount) {
toast.error("请填写在线积分流转服务的名称和数量")
return
}
if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
toast.error("积分数量必须设置为大于0的数值")
return
}
// 验证限制字段
if (totalLimit && (isNaN(parseInt(totalLimit)) || parseInt(totalLimit) < 1)) {
toast.error("总数量限制必须是大于0的整数")
return
}
if (userLimit && (isNaN(parseInt(userLimit)) || parseInt(userLimit) < 1)) {
toast.error("单用户限制必须是大于0的整数")
return
}
try {
setLoading(true)
await MerchantService.updatePaymentLink(selectedKey.id, selectedLink.id, {
product_name: productName,
amount: parseFloat(amount),
remark,
...(totalLimit && { total_limit: parseInt(totalLimit) }),
...(userLimit && { user_limit: parseInt(userLimit) })
})
toast.success("在线积分流转服务已更新")
/* 更新UI */
fetchLinks()
setIsEditing(false)
setSelectedLink(null)
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "在线积分流转服务更新失败"
toast.error(message)
} finally {
setLoading(false)
}
}
/* 处理复制链接 */
const handleCopyLink = (token: string) => {
const url = `${ window.location.origin }/paying/online?token=${ token }`
navigator.clipboard.writeText(url)
toast.success("在线积分流转服务链接已复制")
}
/* 获取用户支付配置 */
const [userPayConfigs, setUserPayConfigs] = useState([])
const { user } = useUser()
useEffect(() => {
const loadConfigs = async () => {
try {
const configs = await ConfigService.getUserPayConfigs()
setUserPayConfigs(configs)
} catch (error) {
console.error("Failed to load user pay configs:", error)
}
}
loadConfigs()
}, [])
/* 预览订单信息 */
const currentUserConfig = userPayConfigs.find(c => user?.pay_level !== undefined && c.level === user.pay_level)
const previewOrderInfo: GetMerchantOrderResponse = {
merchant: {
app_name: selectedKey?.app_name || "服务名称",
redirect_uri: "",
},
order: {
id: '0',
order_no: previewLink ? `LINK-${ previewLink.id }` : "PREVIEW",
order_name: isCreating ? (productName || "服务名称") : (previewLink?.product_name || "服务名称"),
payer_username: "user",
payee_username: selectedKey?.app_name || "应用",
amount: isCreating ? (amount || "0.00") : (previewLink?.amount || "0.00"),
status: "pending",
type: "payment",
payment_type: "api",
remark: isCreating ? remark : (previewLink?.remark || ""),
client_id: "",
return_url: "",
notify_url: "",
trade_time: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
user_pay_config: currentUserConfig ? {
...currentUserConfig,
fee_rate: String(currentUserConfig.fee_rate)
} : {
id: '0',
level: user?.pay_level ?? 0,
min_score: 0,
max_score: null,
daily_limit: null,
fee_rate: "0.00",
created_at: "",
updated_at: ""
}
}
return (
在线流转
{apiKeys.length > 0 && (
)}
{!selectedKey ? (
) : (
所有服务
{loading && paymentLinks.length === 0 && (
)}
{paymentLinks.map((link) => (
))}
{isFullscreen ? '全屏预览' : '实时预览'}
{previewDevice === 'mobile' && (
)}
{previewDevice === 'tablet' && (
)}
{previewDevice === 'desktop' && (
)}
{ }} onCurrentStepChange={() => { }} onSelectedMethodChange={() => { }} onIsOpenChange={() => { }} onPayOrder={() => { }} forceMobile={previewDevice === 'mobile'} />
{ if (!open) { setIsCreating(false); setSelectedLink(null); } }}>
{isCreating ? (
创建新服务
创建一个在线积分流转服务。
) : selectedLink ? (
{selectedLink.product_name}
{isEditing ? "编辑在线积分流转服务信息" : "在线积分流转服务内容详情"}
{isEditing ? (
/* 编辑模式 */
) : (
/* 查看模式 */
<>
服务信息
{selectedLink.product_name}
LDC {parseFloat(selectedLink.amount).toFixed(2)}
{(selectedLink.total_limit || selectedLink.user_limit) && (
<>
{selectedLink.total_limit && (
{selectedLink.total_limit}
)}
{selectedLink.user_limit && (
{selectedLink.user_limit}
)}
>
)}
{selectedLink.remark || "无备注"}
{new Date(selectedLink.created_at).toLocaleString()}
{showToken ? selectedLink.token : '•'.repeat(36)}
流转服务管理
>
)}
) : null}
)
}
)
}