609 lines
31 KiB
HTML
609 lines
31 KiB
HTML
<!doctype html>
|
||
<html lang="fa" dir="rtl">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>پرداخت — ایلو</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="styles.css" />
|
||
|
||
<style>
|
||
body { background: var(--bg); }
|
||
.nav { background: var(--surface); border-bottom: 1px solid var(--line); }
|
||
.nav-inner { max-width: 1100px; margin: 0 auto; padding: 14px 24px; display: flex; align-items: center; gap: 24px; }
|
||
.nav-brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 16px; text-decoration: none; color: inherit; }
|
||
.nav-mark { width: 30px; height: 30px; border-radius: 8px; background: linear-gradient(140deg, var(--accent), var(--accent-2)); color: white; font-family: var(--font-en); font-weight: 700; font-size: 12px; display: grid; place-items: center; }
|
||
.secure-pill { margin-inline-start: auto; display: flex; align-items: center; gap: 6px; padding: 6px 12px; background: oklch(0.94 0.05 155); color: oklch(0.45 0.10 155); border-radius: 999px; font-size: 12px; font-weight: 500; }
|
||
|
||
.checkout-stepper { max-width: 1100px; margin: 32px auto; padding: 0 24px; display: flex; align-items: center; gap: 8px; }
|
||
.cs-step { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
|
||
.cs-num { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 600; background: var(--surface-2); color: var(--ink-3); border: 1.5px solid var(--line); font-family: var(--font-num); }
|
||
.cs-step.active .cs-num { background: var(--accent); color: white; border-color: transparent; }
|
||
.cs-step.done .cs-num { background: var(--accent-soft); color: var(--accent-ink); border-color: transparent; }
|
||
.cs-label { font-size: 13px; color: var(--ink-3); }
|
||
.cs-step.active .cs-label, .cs-step.done .cs-label { color: var(--ink); font-weight: 600; }
|
||
.cs-line { flex: 1; height: 2px; background: var(--line); border-radius: 999px; }
|
||
.cs-line.done { background: var(--accent); }
|
||
|
||
.checkout-grid {
|
||
max-width: 1100px; margin: 0 auto;
|
||
display: grid; grid-template-columns: 1.5fr 1fr; gap: 32px;
|
||
padding: 0 24px 56px;
|
||
align-items: flex-start;
|
||
}
|
||
.panel { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 28px; }
|
||
.panel h2 { margin: 0 0 6px; font-size: 20px; }
|
||
.panel .sub { margin: 0 0 24px; font-size: 13px; color: var(--ink-3); }
|
||
|
||
/* method cards */
|
||
.method-grid { display: grid; gap: 10px; }
|
||
.method-card {
|
||
border: 1.5px solid var(--line);
|
||
background: var(--surface);
|
||
border-radius: 12px;
|
||
padding: 14px 18px;
|
||
display: flex; align-items: center; gap: 14px;
|
||
cursor: pointer;
|
||
transition: all .15s;
|
||
}
|
||
.method-card:hover { border-color: var(--ink-4); }
|
||
.method-card.selected {
|
||
border-color: var(--accent);
|
||
background: color-mix(in oklch, var(--accent-soft) 60%, var(--surface));
|
||
}
|
||
.method-radio {
|
||
width: 20px; height: 20px; border-radius: 50%; border: 1.5px solid var(--line);
|
||
flex-shrink: 0; display: grid; place-items: center;
|
||
}
|
||
.method-card.selected .method-radio { border-color: var(--accent); }
|
||
.method-card.selected .method-radio::after { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }
|
||
.method-logo {
|
||
width: 44px; height: 44px; border-radius: 10px;
|
||
background: var(--surface-2);
|
||
display: grid; place-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.method-meta { flex: 1; }
|
||
.method-title { font-size: 14.5px; font-weight: 600; }
|
||
.method-desc { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
|
||
.method-extra { font-size: 11.5px; color: var(--ink-3); }
|
||
|
||
/* card form */
|
||
.field-stack { display: flex; flex-direction: column; gap: 14px; }
|
||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||
.field-lbl { font-size: 12.5px; font-weight: 500; color: var(--ink-2); margin-bottom: 6px; display: flex; justify-content: space-between; }
|
||
.text-input {
|
||
width: 100%; box-sizing: border-box;
|
||
border: 1px solid var(--line);
|
||
background: var(--surface);
|
||
border-radius: 10px;
|
||
padding: 12px 14px;
|
||
font: inherit; font-size: 14px; color: var(--ink);
|
||
outline: 0;
|
||
transition: border-color .15s, box-shadow .15s;
|
||
direction: ltr;
|
||
text-align: start;
|
||
font-family: var(--font-num);
|
||
}
|
||
.text-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
||
.pan-wrap { position: relative; }
|
||
.pan-wrap .brand-mark {
|
||
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
|
||
font-size: 11px; padding: 3px 10px; border-radius: 6px;
|
||
background: var(--surface-2); color: var(--ink-3); font-weight: 600;
|
||
}
|
||
|
||
/* coupon */
|
||
.coupon-row { display: flex; gap: 8px; }
|
||
.coupon-row .text-input { flex: 1; direction: rtl; font-family: inherit; }
|
||
|
||
/* summary card */
|
||
.summary { position: sticky; top: 24px; }
|
||
.summary-h { padding: 18px 22px 14px; border-bottom: 1px solid var(--line-soft); }
|
||
.course-row { display: flex; gap: 12px; padding: 14px 22px; border-bottom: 1px solid var(--line-soft); }
|
||
.course-thumb { width: 64px; height: 48px; border-radius: 8px; background: oklch(0.78 0.12 40); flex-shrink: 0; }
|
||
.summary-body { padding: 18px 22px; display: flex; flex-direction: column; gap: 10px; font-size: 13px; }
|
||
.sb-row { display: flex; justify-content: space-between; }
|
||
.sb-row .num { font-family: var(--font-num); }
|
||
.sb-row.muted { color: var(--ink-3); }
|
||
.sb-row.total { padding-top: 14px; border-top: 1px solid var(--line); font-size: 15px; font-weight: 700; margin-top: 6px; }
|
||
.sb-row.discount { color: oklch(0.55 0.13 155); }
|
||
.save-banner {
|
||
margin: 0 22px;
|
||
padding: 10px 12px;
|
||
background: oklch(0.94 0.05 155);
|
||
color: oklch(0.45 0.10 155);
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.pay-btn {
|
||
width: calc(100% - 44px);
|
||
margin: 14px 22px 18px;
|
||
padding: 14px;
|
||
justify-content: center;
|
||
font-size: 14px; font-weight: 600;
|
||
}
|
||
.trust-row {
|
||
padding: 12px 22px 18px;
|
||
display: flex; gap: 8px; flex-wrap: wrap;
|
||
border-top: 1px solid var(--line-soft);
|
||
color: var(--ink-3);
|
||
font-size: 11.5px;
|
||
}
|
||
|
||
/* otp & processing & success states */
|
||
.center-box {
|
||
max-width: 480px; margin: 56px auto;
|
||
padding: 0 24px;
|
||
}
|
||
.panel-center {
|
||
background: var(--surface);
|
||
border: 1px solid var(--line);
|
||
border-radius: 14px;
|
||
padding: 40px 32px;
|
||
text-align: center;
|
||
}
|
||
.otp-row { display: flex; gap: 8px; direction: ltr; justify-content: center; margin: 24px 0 8px; }
|
||
.otp-cell {
|
||
width: 52px; height: 60px;
|
||
border: 1.5px solid var(--line);
|
||
border-radius: 10px;
|
||
font-size: 22px; font-weight: 700;
|
||
text-align: center; outline: none;
|
||
font-family: var(--font-num);
|
||
background: var(--surface);
|
||
}
|
||
.otp-cell:focus, .otp-cell.filled { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
||
|
||
.spinner {
|
||
width: 56px; height: 56px;
|
||
border-radius: 50%;
|
||
border: 3px solid var(--line);
|
||
border-top-color: var(--accent);
|
||
animation: spin 1s linear infinite;
|
||
margin: 0 auto 20px;
|
||
}
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
|
||
.success-ic {
|
||
width: 80px; height: 80px;
|
||
border-radius: 50%;
|
||
background: oklch(0.94 0.05 155);
|
||
color: oklch(0.55 0.14 155);
|
||
margin: 0 auto 24px;
|
||
display: grid; place-items: center;
|
||
animation: pop .4s cubic-bezier(.18,.9,.32,1.4);
|
||
}
|
||
@keyframes pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }
|
||
|
||
@media (max-width: 900px) {
|
||
.checkout-grid { grid-template-columns: 1fr; }
|
||
.summary { position: static; }
|
||
}
|
||
</style>
|
||
|
||
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
||
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
||
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
||
</head>
|
||
<body>
|
||
<div id="root"></div>
|
||
<script type="text/babel" src="icons.jsx"></script>
|
||
<script type="text/babel" src="student-data.jsx"></script>
|
||
<script type="text/babel">
|
||
const fa = (s) => String(s).replace(/\d/g, d => '۰۱۲۳۴۵۶۷۸۹'[d]);
|
||
|
||
const PAYMENT_METHODS = [
|
||
{ id: 'zarinpal', title: 'درگاه پرداخت زرینپال', desc: 'کارت بانکی · سریعترین روش', logo: 'ZP', logoBg: 'oklch(0.78 0.16 30)', recommended: true },
|
||
{ id: 'idpay', title: 'درگاه آیدیپی', desc: 'کارت بانکی · بدون کارمزد', logo: 'IDP', logoBg: 'oklch(0.74 0.14 145)' },
|
||
{ id: 'wallet', title: 'کیف پول ایلو', desc: 'موجودی فعلی: ۱۲۰٬۰۰۰ تومان', logo: 'IL', logoBg: 'var(--accent)', extra: 'برای پرداخت کامل، ۲٬۳۳۰٬۰۰۰ تومان دیگر نیاز است' },
|
||
{ id: 'install', title: 'پرداخت اقساطی (اسنپپی)', desc: '۴ قسط · بدون کارمزد', logo: 'SP', logoBg: 'oklch(0.74 0.14 320)' },
|
||
];
|
||
|
||
const PROMO_CODES = { 'WELCOME10': 0.10, 'STUDENT': 0.15 };
|
||
|
||
function OtpInput({ value, onChange, autoFocus }) {
|
||
const refs = React.useRef([]);
|
||
React.useEffect(() => { if (autoFocus) refs.current[0]?.focus(); }, [autoFocus]);
|
||
const handle = (i, v) => {
|
||
const digit = v.replace(/\D/g, '').slice(-1);
|
||
const next = (value + '').padEnd(5, ' ').split('');
|
||
next[i] = digit || ' ';
|
||
onChange(next.join('').replace(/ /g, ''));
|
||
if (digit && i < 4) refs.current[i + 1]?.focus();
|
||
};
|
||
const handleKey = (i, e) => {
|
||
if (e.key === 'Backspace' && !value[i] && i > 0) refs.current[i - 1]?.focus();
|
||
};
|
||
return (
|
||
<div className="otp-row">
|
||
{[0,1,2,3,4].map(i => (
|
||
<input key={i} ref={el => refs.current[i] = el}
|
||
className={`otp-cell ${value[i] ? 'filled' : ''}`}
|
||
inputMode="numeric" maxLength={1}
|
||
value={value[i] || ''}
|
||
onChange={e => handle(i, e.target.value)}
|
||
onKeyDown={e => handleKey(i, e)} />
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function App() {
|
||
const c = window.STUDENT_DATA.COURSE_DETAIL;
|
||
const [step, setStep] = React.useState('cart'); // cart, payment, otp, processing, success
|
||
const [method, setMethod] = React.useState('zarinpal');
|
||
const [pan, setPan] = React.useState('');
|
||
const [exp, setExp] = React.useState('');
|
||
const [cvv, setCvv] = React.useState('');
|
||
const [cvv2, setCvv2] = React.useState('');
|
||
const [coupon, setCoupon] = React.useState('');
|
||
const [appliedDiscount, setAppliedDiscount] = React.useState(0);
|
||
const [otp, setOtp] = React.useState('');
|
||
|
||
const subtotal = 3200000;
|
||
const courseDiscount = 750000;
|
||
const tax = 0;
|
||
const couponAmount = Math.round((subtotal - courseDiscount) * appliedDiscount);
|
||
const total = subtotal - courseDiscount - couponAmount + tax;
|
||
const totalFa = total.toLocaleString('fa-IR');
|
||
|
||
const applyCoupon = () => {
|
||
const code = coupon.trim().toUpperCase();
|
||
if (PROMO_CODES[code]) setAppliedDiscount(PROMO_CODES[code]);
|
||
else setAppliedDiscount(-1); // mark error
|
||
};
|
||
|
||
const submitPayment = () => {
|
||
setStep('otp');
|
||
};
|
||
const verifyOtp = () => {
|
||
setStep('processing');
|
||
setTimeout(() => setStep('success'), 2200);
|
||
};
|
||
|
||
const stepIdx = { cart: 0, payment: 1, otp: 1, processing: 2, success: 2 }[step];
|
||
|
||
return (
|
||
<>
|
||
<nav className="nav">
|
||
<div className="nav-inner">
|
||
<a className="nav-brand" href="Ilo Landing.html">
|
||
<div className="nav-mark">il</div>
|
||
ایلو
|
||
</a>
|
||
<span style={{ fontSize: 13, color: 'var(--ink-3)' }}>پرداخت امن</span>
|
||
<span className="secure-pill">
|
||
<Icon name="check" className="ic-sm" />
|
||
اتصال SSL · رمزنگاری ۲۵۶ بیت
|
||
</span>
|
||
</div>
|
||
</nav>
|
||
|
||
{step !== 'success' && step !== 'processing' && (
|
||
<div className="checkout-stepper">
|
||
{[['cart','سبد خرید'],['payment','پرداخت'],['success','تکمیل']].map(([k, l], i) => (
|
||
<React.Fragment key={k}>
|
||
<div className={`cs-step ${stepIdx === i ? 'active' : stepIdx > i ? 'done' : ''}`}>
|
||
<div className="cs-num">{stepIdx > i ? <Icon name="check" className="ic-sm" /> : fa(i+1)}</div>
|
||
<span className="cs-label">{l}</span>
|
||
</div>
|
||
{i < 2 && <div className={`cs-line ${stepIdx > i ? 'done' : ''}`} />}
|
||
</React.Fragment>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* CART STEP */}
|
||
{step === 'cart' && (
|
||
<div className="checkout-grid">
|
||
<div className="panel">
|
||
<h2>سبد خرید</h2>
|
||
<p className="sub">۱ دوره · بازنگری و تأیید</p>
|
||
|
||
<div style={{ display: 'flex', gap: 14, padding: '16px', border: '1px solid var(--line)', borderRadius: 12, marginBottom: 24 }}>
|
||
<div style={{ width: 96, height: 64, borderRadius: 10, background: c.color, flexShrink: 0, display: 'grid', placeItems: 'center', color: 'white' }}>
|
||
<Icon name="play" />
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: 14.5, fontWeight: 600 }}>{c.title}</div>
|
||
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>توسط {c.instructor.name}</div>
|
||
<div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 6, display: 'flex', gap: 12 }}>
|
||
<span><Icon name="clock" className="ic-sm" /> {fa(c.hours)} ساعت</span>
|
||
<span><Icon name="video" className="ic-sm" /> {fa(c.lessons)} درس</span>
|
||
</div>
|
||
</div>
|
||
<div style={{ textAlign: 'end' }}>
|
||
<div style={{ fontSize: 11, color: 'var(--ink-3)', textDecoration: 'line-through', fontFamily: 'var(--font-num)' }}>{c.oldPrice}</div>
|
||
<div style={{ fontSize: 16, fontWeight: 700, fontFamily: 'var(--font-num)' }}>{c.price}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: 18 }}>
|
||
<label className="field-lbl">کد تخفیف</label>
|
||
<div className="coupon-row">
|
||
<input className="text-input" placeholder="مثلاً: WELCOME10" value={coupon}
|
||
onChange={e => { setCoupon(e.target.value); setAppliedDiscount(0); }} />
|
||
<button className="btn primary" onClick={applyCoupon} disabled={!coupon.trim()}>اعمال</button>
|
||
</div>
|
||
{appliedDiscount > 0 && (
|
||
<div style={{ marginTop: 8, fontSize: 12, color: 'oklch(0.55 0.13 155)' }}>
|
||
<Icon name="check" className="ic-sm" /> کد «{coupon.toUpperCase()}» اعمال شد · {fa(Math.round(appliedDiscount * 100))}٪ تخفیف بیشتر
|
||
</div>
|
||
)}
|
||
{appliedDiscount === -1 && (
|
||
<div style={{ marginTop: 8, fontSize: 12, color: 'oklch(0.55 0.18 25)' }}>
|
||
✗ کد تخفیف نامعتبر است
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div style={{ background: 'var(--surface-2)', borderRadius: 10, padding: 14, fontSize: 12.5, color: 'var(--ink-2)', display: 'flex', gap: 10 }}>
|
||
<Icon name="check" className="ic-sm" style={{ color: 'oklch(0.55 0.13 155)' }} />
|
||
<span>گارانتی بازگشت ۷ روزهی وجه — اگر از دوره راضی نبودی، تمام مبلغ پرداختی به حسابت برمیگرده.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<Summary {...{ c, subtotal, courseDiscount, couponAmount, total, totalFa, appliedDiscount, coupon }}
|
||
cta="ادامه به پرداخت" onContinue={() => setStep('payment')} />
|
||
</div>
|
||
)}
|
||
|
||
{/* PAYMENT STEP */}
|
||
{step === 'payment' && (
|
||
<div className="checkout-grid">
|
||
<div>
|
||
<div className="panel" style={{ marginBottom: 16 }}>
|
||
<h2>روش پرداخت</h2>
|
||
<p className="sub">یک روش پرداخت انتخاب کن</p>
|
||
<div className="method-grid">
|
||
{PAYMENT_METHODS.map(m => (
|
||
<div key={m.id} className={`method-card ${method === m.id ? 'selected' : ''}`}
|
||
onClick={() => setMethod(m.id)}>
|
||
<div className="method-radio" />
|
||
<div className="method-logo" style={{ background: m.logoBg, color: 'white', fontWeight: 700, fontSize: 12 }}>{m.logo}</div>
|
||
<div className="method-meta">
|
||
<div className="method-title">{m.title} {m.recommended && <span className="pill accent" style={{ fontSize: 10, marginInlineStart: 6 }}>پیشنهادی</span>}</div>
|
||
<div className="method-desc">{m.desc}</div>
|
||
{m.extra && <div className="method-extra" style={{ marginTop: 4 }}>{m.extra}</div>}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* card form (zarinpal/idpay) */}
|
||
{(method === 'zarinpal' || method === 'idpay') && (
|
||
<div className="panel">
|
||
<h2>اطلاعات کارت</h2>
|
||
<p className="sub">اطلاعات کارت روی درگاه بانک وارد میشود — این صفحه فقط برای پیشنمایش است.</p>
|
||
|
||
<div className="field-stack">
|
||
<div>
|
||
<label className="field-lbl">
|
||
<span>شماره کارت</span>
|
||
<span style={{ color: 'var(--ink-3)' }}>۱۶ رقم</span>
|
||
</label>
|
||
<div className="pan-wrap">
|
||
<input className="text-input" style={{ paddingInlineStart: 60 }}
|
||
placeholder="۶۰۳۷ ۹۹۷۰ ۱۲۳۴ ۵۶۷۸"
|
||
value={pan}
|
||
maxLength={19}
|
||
onChange={e => {
|
||
const digits = e.target.value.replace(/\D/g, '').slice(0, 16);
|
||
setPan(digits.replace(/(.{4})/g, '$1 ').trim());
|
||
}} />
|
||
<span className="brand-mark">{pan.startsWith('6037') ? 'ملی' : pan.startsWith('6219') ? 'سامان' : 'BANK'}</span>
|
||
</div>
|
||
</div>
|
||
<div className="form-row">
|
||
<div>
|
||
<label className="field-lbl">تاریخ انقضا</label>
|
||
<input className="text-input" placeholder="MM / YY" value={exp}
|
||
onChange={e => {
|
||
let v = e.target.value.replace(/\D/g, '').slice(0, 4);
|
||
if (v.length > 2) v = v.slice(0, 2) + ' / ' + v.slice(2);
|
||
setExp(v);
|
||
}} />
|
||
</div>
|
||
<div>
|
||
<label className="field-lbl">CVV2</label>
|
||
<input className="text-input" placeholder="۱۲۳" value={cvv}
|
||
maxLength={4}
|
||
onChange={e => setCvv(e.target.value.replace(/\D/g, '').slice(0, 4))} />
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="field-lbl">رمز دوم پویا</label>
|
||
<input className="text-input" placeholder="رمز ۶ یا ۸ رقمی" value={cvv2}
|
||
onChange={e => setCvv2(e.target.value.replace(/\D/g, '').slice(0, 8))} />
|
||
<div style={{ marginTop: 6, fontSize: 11.5, color: 'var(--ink-3)' }}>
|
||
رمز دوم پویا را از اپ بانک یا با کد دستوری <b className="num" dir="ltr">*۷۲۰*</b> دریافت کن.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{method === 'wallet' && (
|
||
<div className="panel">
|
||
<div style={{ padding: '24px', textAlign: 'center' }}>
|
||
<Icon name="store" style={{ width: 40, height: 40, color: 'var(--accent)', margin: '0 auto 12px', display: 'block' }} />
|
||
<div style={{ fontSize: 16, fontWeight: 700 }}>پرداخت با کیف پول ایلو</div>
|
||
<div style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 8 }}>موجودی شما: <b className="num">۱۲۰٬۰۰۰</b> تومان</div>
|
||
<div style={{ fontSize: 13, color: 'oklch(0.55 0.18 25)', marginTop: 4 }}>برای پرداخت کامل، نیاز به <b className="num">۲٬۳۳۰٬۰۰۰</b> تومان شارژ بیشتر است</div>
|
||
<button className="btn primary" style={{ marginTop: 16 }}>شارژ کیف پول</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{method === 'install' && (
|
||
<div className="panel">
|
||
<h2>پرداخت اقساطی</h2>
|
||
<p className="sub">مبلغ کل به ۴ قسط مساوی تقسیم میشود</p>
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||
{[1,2,3,4].map(n => (
|
||
<div key={n} style={{ display: 'flex', justifyContent: 'space-between', padding: '12px 16px', background: n === 1 ? 'var(--accent-soft)' : 'var(--surface-2)', borderRadius: 10, fontSize: 13 }}>
|
||
<span>قسط {fa(n)} {n === 1 ? '· امروز' : `· ${fa(n-1)} ماه دیگر`}</span>
|
||
<b className="num">{Math.round(total/4).toLocaleString('fa-IR')} تومان</b>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<Summary {...{ c, subtotal, courseDiscount, couponAmount, total, totalFa, appliedDiscount, coupon }}
|
||
cta={`پرداخت ${totalFa} تومان`} onContinue={submitPayment} />
|
||
</div>
|
||
)}
|
||
|
||
{/* OTP STEP */}
|
||
{step === 'otp' && (
|
||
<div className="center-box">
|
||
<div className="panel-center">
|
||
<div style={{ width: 56, height: 56, borderRadius: 14, background: 'var(--accent-soft)', color: 'var(--accent)', display: 'grid', placeItems: 'center', margin: '0 auto 20px' }}>
|
||
<Icon name="phone" />
|
||
</div>
|
||
<h2 style={{ margin: '0 0 8px', fontSize: 20 }}>تأیید پرداخت</h2>
|
||
<p style={{ margin: 0, fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.7 }}>
|
||
کد ۵ رقمی به شماره <b className="num" dir="ltr">+۹۸ ۹۱۲...۴۵۶۷</b> ارسال شد.<br/>
|
||
این کد را برای تأیید تراکنش وارد کنید.
|
||
</p>
|
||
<OtpInput value={otp} onChange={setOtp} autoFocus />
|
||
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginBottom: 20 }}>کد رو دریافت نکردی؟ <a style={{ color: 'var(--accent)', cursor: 'pointer' }}>ارسال مجدد</a></div>
|
||
<button className="btn primary full" disabled={otp.length !== 5}
|
||
style={{ width: '100%', justifyContent: 'center', padding: '12px', opacity: otp.length === 5 ? 1 : 0.5, cursor: otp.length === 5 ? 'pointer' : 'not-allowed' }}
|
||
onClick={verifyOtp}>
|
||
تأیید و پرداخت
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* PROCESSING */}
|
||
{step === 'processing' && (
|
||
<div className="center-box">
|
||
<div className="panel-center">
|
||
<div className="spinner" />
|
||
<h2 style={{ margin: '0 0 8px', fontSize: 20 }}>در حال پردازش پرداخت…</h2>
|
||
<p style={{ margin: 0, fontSize: 13, color: 'var(--ink-3)' }}>لطفاً صفحه را نبند یا رفرش نکن</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* SUCCESS */}
|
||
{step === 'success' && (
|
||
<div className="center-box">
|
||
<div className="panel-center">
|
||
<div className="success-ic">
|
||
<Icon name="check" style={{ width: 36, height: 36, strokeWidth: 2.5 }} />
|
||
</div>
|
||
<h2 style={{ margin: '0 0 8px', fontSize: 24 }}>تبریک! خریدت موفق بود 🎉</h2>
|
||
<p style={{ margin: '0 0 24px', fontSize: 14, color: 'var(--ink-2)' }}>
|
||
از این لحظه به محتوای دوره <b>«{c.title}»</b> دسترسی داری.
|
||
</p>
|
||
|
||
<div style={{ background: 'var(--surface-2)', borderRadius: 12, padding: 16, marginBottom: 24, textAlign: 'start' }}>
|
||
<div className="sb-row muted" style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12 }}>
|
||
<span>کد پیگیری تراکنش</span>
|
||
<b className="num" dir="ltr" style={{ color: 'var(--ink)' }}>IL-2024-AB7392X</b>
|
||
</div>
|
||
<div className="sb-row muted" style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginTop: 6 }}>
|
||
<span>مبلغ پرداختی</span>
|
||
<b className="num" style={{ color: 'var(--ink)' }}>{totalFa} تومان</b>
|
||
</div>
|
||
<div className="sb-row muted" style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginTop: 6 }}>
|
||
<span>روش پرداخت</span>
|
||
<b style={{ color: 'var(--ink)' }}>{PAYMENT_METHODS.find(m => m.id === method)?.title}</b>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', gap: 8 }}>
|
||
<a className="btn primary" href="Ilo Student.html" style={{ flex: 1, justifyContent: 'center', padding: '12px', textDecoration: 'none' }}>
|
||
شروع یادگیری
|
||
<Icon name="chevron" className="ic-sm flipx" />
|
||
</a>
|
||
<a className="btn" href="Ilo Landing.html" style={{ justifyContent: 'center', padding: '12px', textDecoration: 'none' }}>
|
||
دانلود رسید
|
||
</a>
|
||
</div>
|
||
|
||
<div style={{ marginTop: 24, fontSize: 12, color: 'var(--ink-3)' }}>
|
||
رسید خرید به ایمیل و پیامک شما ارسال شد.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
|
||
function Summary({ c, subtotal, courseDiscount, couponAmount, total, totalFa, appliedDiscount, coupon, cta, onContinue }) {
|
||
return (
|
||
<div className="summary">
|
||
<div className="panel" style={{ padding: 0 }}>
|
||
<div className="summary-h">
|
||
<h2 style={{ margin: 0, fontSize: 16 }}>خلاصه سفارش</h2>
|
||
</div>
|
||
<div className="course-row">
|
||
<div className="course-thumb" style={{ background: c.color }} />
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: 13.5, fontWeight: 600, lineHeight: 1.4 }}>{c.title}</div>
|
||
<div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 4 }}>دسترسی مادامالعمر</div>
|
||
</div>
|
||
</div>
|
||
<div className="summary-body">
|
||
<div className="sb-row muted">
|
||
<span>قیمت دوره</span>
|
||
<span className="num">{subtotal.toLocaleString('fa-IR')} تومان</span>
|
||
</div>
|
||
<div className="sb-row discount">
|
||
<span>تخفیف دوره</span>
|
||
<span className="num">−{courseDiscount.toLocaleString('fa-IR')}</span>
|
||
</div>
|
||
{appliedDiscount > 0 && (
|
||
<div className="sb-row discount">
|
||
<span>کد «{coupon.toUpperCase()}»</span>
|
||
<span className="num">−{couponAmount.toLocaleString('fa-IR')}</span>
|
||
</div>
|
||
)}
|
||
<div className="sb-row muted">
|
||
<span>مالیات بر ارزش افزوده</span>
|
||
<span>۰</span>
|
||
</div>
|
||
<div className="sb-row total">
|
||
<span>مبلغ قابل پرداخت</span>
|
||
<span className="num">{totalFa} تومان</span>
|
||
</div>
|
||
</div>
|
||
{courseDiscount + couponAmount > 0 && (
|
||
<div className="save-banner">
|
||
<Icon name="trend" className="ic-sm" />
|
||
در این خرید <b className="num" style={{ marginInline: 4 }}>{(courseDiscount + couponAmount).toLocaleString('fa-IR')}</b> تومان صرفهجویی کردی!
|
||
</div>
|
||
)}
|
||
<button className="btn primary pay-btn" onClick={onContinue}>
|
||
{cta}
|
||
<Icon name="chevron" className="ic-sm flipx" />
|
||
</button>
|
||
<div className="trust-row">
|
||
<span>🔒 پرداخت امن</span>
|
||
<span>·</span>
|
||
<span>گارانتی ۷ روزه</span>
|
||
<span>·</span>
|
||
<span>دسترسی مادامالعمر</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
||
</script>
|
||
</body>
|
||
</html>
|