312 lines
13 KiB
TypeScript
312 lines
13 KiB
TypeScript
|
|
"use client"
|
||
|
|
|
||
|
|
import type React from "react"
|
||
|
|
import { useState } from "react"
|
||
|
|
import { X, CheckCircle, Bitcoin, Globe, Copy, Check } from "lucide-react"
|
||
|
|
import { Button } from "@/components/ui/button"
|
||
|
|
import { useStore } from "@/lib/store"
|
||
|
|
|
||
|
|
const WechatIcon = () => (
|
||
|
|
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor">
|
||
|
|
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.269-.03-.406-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z" />
|
||
|
|
</svg>
|
||
|
|
)
|
||
|
|
|
||
|
|
const AlipayIcon = () => (
|
||
|
|
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor">
|
||
|
|
<path d="M8.77 20.62l9.92-4.33c-.12-.33-.24-.66-.38-.99-.14-.33-.3-.66-.47-.99H8.08c-2.2 0-3.99-1.79-3.99-3.99V8.08c0-2.2 1.79-3.99 3.99-3.99h7.84c2.2 0 3.99 1.79 3.99 3.99v2.24h-8.66c-.55 0-1 .45-1 1s.45 1 1 1h10.66c-.18 1.73-.71 3.36-1.53 4.83l-2.76 1.2c-.74-1.69-1.74-3.24-2.93-4.6-.52-.59-1.11-1.13-1.76-1.59H4.09v4.24c0 2.2 1.79 3.99 3.99 3.99h.69v.23z" />
|
||
|
|
</svg>
|
||
|
|
)
|
||
|
|
|
||
|
|
type PaymentMethod = "wechat" | "alipay" | "usdt" | "paypal"
|
||
|
|
|
||
|
|
interface PaymentModalProps {
|
||
|
|
isOpen: boolean
|
||
|
|
onClose: () => void
|
||
|
|
type: "section" | "fullbook"
|
||
|
|
sectionId?: string
|
||
|
|
sectionTitle?: string
|
||
|
|
amount: number
|
||
|
|
onSuccess: () => void
|
||
|
|
}
|
||
|
|
|
||
|
|
export function PaymentModal({ isOpen, onClose, type, sectionId, sectionTitle, amount, onSuccess }: PaymentModalProps) {
|
||
|
|
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>("wechat")
|
||
|
|
const [isProcessing, setIsProcessing] = useState(false)
|
||
|
|
const [isSuccess, setIsSuccess] = useState(false)
|
||
|
|
const [showPaymentDetails, setShowPaymentDetails] = useState(false)
|
||
|
|
const [copied, setCopied] = useState(false)
|
||
|
|
|
||
|
|
const { purchaseSection, purchaseFullBook, user, settings } = useStore()
|
||
|
|
|
||
|
|
const paymentConfig = settings?.paymentMethods || {
|
||
|
|
wechat: { enabled: true, qrCode: "", account: "" },
|
||
|
|
alipay: { enabled: true, qrCode: "", account: "" },
|
||
|
|
usdt: { enabled: true, network: "TRC20", address: "", exchangeRate: 7.2 },
|
||
|
|
paypal: { enabled: false, email: "", exchangeRate: 7.2 },
|
||
|
|
}
|
||
|
|
|
||
|
|
const usdtAmount = (amount / (paymentConfig.usdt.exchangeRate || 7.2)).toFixed(2)
|
||
|
|
const paypalAmount = (amount / (paymentConfig.paypal.exchangeRate || 7.2)).toFixed(2)
|
||
|
|
|
||
|
|
const handleCopyAddress = (address: string) => {
|
||
|
|
navigator.clipboard.writeText(address)
|
||
|
|
setCopied(true)
|
||
|
|
setTimeout(() => setCopied(false), 2000)
|
||
|
|
}
|
||
|
|
|
||
|
|
const handlePayment = async () => {
|
||
|
|
if (paymentMethod === "usdt" || paymentMethod === "paypal") {
|
||
|
|
setShowPaymentDetails(true)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
setIsProcessing(true)
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, 1500))
|
||
|
|
|
||
|
|
let success = false
|
||
|
|
if (type === "section" && sectionId) {
|
||
|
|
success = await purchaseSection(sectionId, sectionTitle, paymentMethod)
|
||
|
|
} else if (type === "fullbook") {
|
||
|
|
success = await purchaseFullBook(paymentMethod)
|
||
|
|
}
|
||
|
|
|
||
|
|
setIsProcessing(false)
|
||
|
|
|
||
|
|
if (success) {
|
||
|
|
setIsSuccess(true)
|
||
|
|
setTimeout(() => {
|
||
|
|
onSuccess()
|
||
|
|
onClose()
|
||
|
|
setIsSuccess(false)
|
||
|
|
}, 1500)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const confirmCryptoPayment = async () => {
|
||
|
|
setIsProcessing(true)
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||
|
|
|
||
|
|
let success = false
|
||
|
|
if (type === "section" && sectionId) {
|
||
|
|
success = await purchaseSection(sectionId, sectionTitle, paymentMethod)
|
||
|
|
} else if (type === "fullbook") {
|
||
|
|
success = await purchaseFullBook(paymentMethod)
|
||
|
|
}
|
||
|
|
|
||
|
|
setIsProcessing(false)
|
||
|
|
setShowPaymentDetails(false)
|
||
|
|
|
||
|
|
if (success) {
|
||
|
|
setIsSuccess(true)
|
||
|
|
setTimeout(() => {
|
||
|
|
onSuccess()
|
||
|
|
onClose()
|
||
|
|
setIsSuccess(false)
|
||
|
|
}, 1500)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!isOpen) return null
|
||
|
|
|
||
|
|
const paymentMethods: {
|
||
|
|
id: PaymentMethod
|
||
|
|
name: string
|
||
|
|
icon: React.ReactNode
|
||
|
|
color: string
|
||
|
|
enabled: boolean
|
||
|
|
extra?: string
|
||
|
|
}[] = [
|
||
|
|
{
|
||
|
|
id: "wechat",
|
||
|
|
name: "微信支付",
|
||
|
|
icon: <WechatIcon />,
|
||
|
|
color: "bg-[#07C160]",
|
||
|
|
enabled: paymentConfig.wechat.enabled,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "alipay",
|
||
|
|
name: "支付宝",
|
||
|
|
icon: <AlipayIcon />,
|
||
|
|
color: "bg-[#1677FF]",
|
||
|
|
enabled: paymentConfig.alipay.enabled,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "usdt",
|
||
|
|
name: `USDT (${paymentConfig.usdt.network || "TRC20"})`,
|
||
|
|
icon: <Bitcoin className="w-5 h-5" />,
|
||
|
|
color: "bg-[#26A17B]",
|
||
|
|
enabled: paymentConfig.usdt.enabled,
|
||
|
|
extra: `≈ $${usdtAmount}`,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "paypal",
|
||
|
|
name: "PayPal",
|
||
|
|
icon: <Globe className="w-5 h-5" />,
|
||
|
|
color: "bg-[#003087]",
|
||
|
|
enabled: paymentConfig.paypal.enabled,
|
||
|
|
extra: `≈ $${paypalAmount}`,
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
const availableMethods = paymentMethods.filter((m) => m.enabled)
|
||
|
|
|
||
|
|
// Crypto payment details view
|
||
|
|
if (showPaymentDetails) {
|
||
|
|
const isCrypto = paymentMethod === "usdt"
|
||
|
|
const address = isCrypto ? paymentConfig.usdt.address : paymentConfig.paypal.email
|
||
|
|
const displayAmount = isCrypto ? `$${usdtAmount} USDT` : `$${paypalAmount} USD`
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||
|
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
|
||
|
|
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 overflow-hidden">
|
||
|
|
<button onClick={onClose} className="absolute top-4 right-4 p-2 text-gray-400 hover:text-white">
|
||
|
|
<X className="w-5 h-5" />
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<div className="p-6">
|
||
|
|
<h3 className="text-lg font-semibold text-white mb-4">{isCrypto ? "USDT支付" : "PayPal支付"}</h3>
|
||
|
|
|
||
|
|
<div className="bg-[#0a1628] rounded-xl p-4 mb-4">
|
||
|
|
<p className="text-gray-400 text-sm mb-2">支付金额</p>
|
||
|
|
<p className="text-2xl font-bold text-[#38bdac]">{displayAmount}</p>
|
||
|
|
<p className="text-gray-500 text-sm">≈ ¥{amount.toFixed(2)}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-[#0a1628] rounded-xl p-4 mb-4">
|
||
|
|
<p className="text-gray-400 text-sm mb-2">
|
||
|
|
{isCrypto ? `收款地址 (${paymentConfig.usdt.network})` : "PayPal账户"}
|
||
|
|
</p>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<p className="text-white text-sm break-all flex-1 font-mono">
|
||
|
|
{address || (isCrypto ? "请联系客服获取地址" : "请联系客服获取账户")}
|
||
|
|
</p>
|
||
|
|
{address && (
|
||
|
|
<button onClick={() => handleCopyAddress(address)} className="text-[#38bdac] hover:text-[#4fd4c4]">
|
||
|
|
{copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-orange-500/10 border border-orange-500/30 rounded-xl p-4 mb-6">
|
||
|
|
<p className="text-orange-400 text-sm">
|
||
|
|
请在转账完成后点击"已完成支付"按钮,系统将在1-24小时内确认到账并开通权限。
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex gap-3">
|
||
|
|
<Button
|
||
|
|
onClick={() => setShowPaymentDetails(false)}
|
||
|
|
variant="outline"
|
||
|
|
className="flex-1 border-gray-600 text-white hover:bg-gray-700/50 bg-transparent"
|
||
|
|
>
|
||
|
|
返回
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
onClick={confirmCryptoPayment}
|
||
|
|
disabled={isProcessing}
|
||
|
|
className="flex-1 bg-[#38bdac] hover:bg-[#2da396] text-white"
|
||
|
|
>
|
||
|
|
{isProcessing ? "处理中..." : "已完成支付"}
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||
|
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
|
||
|
|
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 overflow-hidden">
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="absolute top-4 right-4 p-2 text-gray-400 hover:text-white transition-colors"
|
||
|
|
>
|
||
|
|
<X className="w-5 h-5" />
|
||
|
|
</button>
|
||
|
|
|
||
|
|
{isSuccess ? (
|
||
|
|
<div className="p-8 text-center">
|
||
|
|
<div className="w-20 h-20 mx-auto mb-4 rounded-full bg-[#38bdac]/20 flex items-center justify-center">
|
||
|
|
<CheckCircle className="w-10 h-10 text-[#38bdac]" />
|
||
|
|
</div>
|
||
|
|
<h3 className="text-xl font-semibold text-white mb-2">支付成功</h3>
|
||
|
|
<p className="text-gray-400">{type === "fullbook" ? "您已解锁全部内容" : "您已解锁本节内容"}</p>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
<div className="p-6 border-b border-gray-700/50">
|
||
|
|
<h3 className="text-lg font-semibold text-white mb-1">确认支付</h3>
|
||
|
|
<p className="text-gray-400 text-sm">
|
||
|
|
{type === "fullbook" ? "购买整本书,解锁全部内容" : `购买: ${sectionTitle}`}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="p-6 border-b border-gray-700/50 text-center">
|
||
|
|
<p className="text-gray-400 text-sm mb-1">支付金额</p>
|
||
|
|
<p className="text-4xl font-bold text-white">¥{amount.toFixed(2)}</p>
|
||
|
|
{(paymentMethod === "usdt" || paymentMethod === "paypal") && (
|
||
|
|
<p className="text-[#38bdac] text-sm mt-1">
|
||
|
|
≈ ${paymentMethod === "usdt" ? usdtAmount : paypalAmount} USD
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
{user?.referredBy && (
|
||
|
|
<p className="text-[#38bdac] text-sm mt-2">
|
||
|
|
通过邀请注册,{settings?.distributorShare || 90}%将返还给推荐人
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="p-6 space-y-3">
|
||
|
|
<p className="text-gray-400 text-sm mb-3">选择支付方式</p>
|
||
|
|
{availableMethods.map((method) => (
|
||
|
|
<button
|
||
|
|
key={method.id}
|
||
|
|
onClick={() => setPaymentMethod(method.id)}
|
||
|
|
className={`w-full p-4 rounded-xl border flex items-center gap-4 transition-all ${
|
||
|
|
paymentMethod === method.id
|
||
|
|
? "border-[#38bdac] bg-[#38bdac]/10"
|
||
|
|
: "border-gray-700 hover:border-gray-600"
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
<div className={`w-10 h-10 rounded-lg ${method.color} flex items-center justify-center text-white`}>
|
||
|
|
{method.icon}
|
||
|
|
</div>
|
||
|
|
<div className="flex-1 text-left">
|
||
|
|
<span className="text-white">{method.name}</span>
|
||
|
|
{method.extra && <span className="text-gray-400 text-sm ml-2">{method.extra}</span>}
|
||
|
|
</div>
|
||
|
|
{paymentMethod === method.id && <CheckCircle className="w-5 h-5 text-[#38bdac]" />}
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
{availableMethods.length === 0 && <p className="text-gray-500 text-center py-4">暂无可用支付方式</p>}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="p-6 pt-0">
|
||
|
|
<Button
|
||
|
|
onClick={handlePayment}
|
||
|
|
disabled={isProcessing || availableMethods.length === 0}
|
||
|
|
className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-6 text-lg"
|
||
|
|
>
|
||
|
|
{isProcessing ? (
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||
|
|
处理中...
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
`确认支付 ¥${amount.toFixed(2)}`
|
||
|
|
)}
|
||
|
|
</Button>
|
||
|
|
<p className="text-gray-500 text-xs text-center mt-3">支付即表示同意《用户协议》和《隐私政策》</p>
|
||
|
|
</div>
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|