Move e-payment to Settings and add ZarinPal credentials.

Gateway config now lives on the general Settings page, with Mellat fields in a 3-column layout and ZarinPal available at checkout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-16 01:45:41 +03:30
co-authored by Cursor
parent 9a59bc8f65
commit e79211e3cd
35 changed files with 614 additions and 212 deletions
@@ -7,9 +7,11 @@ import { ApiError, isAbortError } from '../../lib/api'
import {
checkoutCart,
getCart,
getPaymentMethods,
submitPaymentRedirect,
type CheckoutPaymentType,
type OnlineGatewayType,
type PublicPaymentGateway,
} from '../../services/cartService'
import { syncGuestCartToServer } from '../../services/syncGuestCart'
import { formatIrtPrice } from '../../utils/irtPrice'
@@ -17,11 +19,6 @@ import { CheckoutActionBar } from '../../components/checkout/CheckoutActionBar'
import { CheckoutBackButton } from '../../components/checkout/CheckoutBackButton'
import styles from './CheckoutSteps.module.css'
const ONLINE_GATEWAYS: { id: OnlineGatewayType; label: string; ready: boolean }[] = [
{ id: 'mellat', label: 'درگاه پرداخت بانک ملت - به‌پرداخت', ready: true },
{ id: 'sep', label: 'درگاه پرداخت بانک سامان - سامان کیش', ready: false },
]
export function CheckoutPaymentStep() {
const navigate = useNavigate()
const { showToast } = useToast()
@@ -31,6 +28,8 @@ export function CheckoutPaymentStep() {
const [loading, setLoading] = useState(true)
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState('')
const [onlineGateways, setOnlineGateways] = useState<PublicPaymentGateway[]>([])
const [onlinePaymentsEnabled, setOnlinePaymentsEnabled] = useState(false)
const [paymentType, setPaymentType] = useState<CheckoutPaymentType>('e_payment_gate')
const [gatewayType, setGatewayType] = useState<OnlineGatewayType>('mellat')
const [transferAccount, setTransferAccount] = useState('')
@@ -43,9 +42,13 @@ export function CheckoutPaymentStep() {
async function load() {
setLoading(true)
try {
let data = await getCart(controller.signal)
const [cartResult, methodsResult] = await Promise.all([
getCart(controller.signal),
getPaymentMethods(controller.signal).catch(() => null),
])
if (controller.signal.aborted) return
let data = cartResult
if (data.cart.items.length === 0) {
try {
const synced = await syncGuestCartToServer(controller.signal)
@@ -59,6 +62,23 @@ export function CheckoutPaymentStep() {
setSubtotal(data.cart.subtotal)
if (data.cart.items.length === 0) {
navigate('/checkout/cart', { replace: true })
return
}
const gateways = methodsResult?.gateways ?? []
const enabled = Boolean(methodsResult?.enabled && gateways.length > 0)
setOnlineGateways(gateways)
setOnlinePaymentsEnabled(enabled)
if (enabled) {
const preferred =
(methodsResult?.defaultGateway &&
gateways.find((g) => g.id === methodsResult.defaultGateway)?.id) ||
gateways[0]?.id
if (preferred) setGatewayType(preferred)
setPaymentType('e_payment_gate')
} else {
setPaymentType('transfer')
}
} catch (err) {
if (isAbortError(err)) return
@@ -102,9 +122,8 @@ export function CheckoutPaymentStep() {
}
if (paymentType === 'e_payment_gate') {
const gateway = ONLINE_GATEWAYS.find((item) => item.id === gatewayType)
if (!gateway?.ready) {
setError('این درگاه هنوز فعال نیست. لطفاً ملت را انتخاب کنید.')
if (!onlinePaymentsEnabled || !onlineGateways.some((g) => g.id === gatewayType)) {
setError('درگاه پرداخت آنلاین در دسترس نیست. لطفاً روش دیگری انتخاب کنید.')
return
}
}
@@ -227,27 +246,29 @@ export function CheckoutPaymentStep() {
<div className={styles.field}>
<label>روش پرداخت</label>
<div className={styles.paymentOptions}>
<button
type="button"
className={[
styles.paymentOption,
paymentType === 'e_payment_gate' ? styles.paymentOptionActive : '',
]
.filter(Boolean)
.join(' ')}
onClick={() => setPaymentType('e_payment_gate')}
disabled={submitting}
>
<CreditCard size={20} />
<span className={styles.paymentOptionBody}>
<span className={styles.paymentOptionTitle}>پرداخت آنلاین</span>
<span className={styles.paymentOptionHint}>پرداخت امن از طریق درگاه</span>
</span>
</button>
{onlinePaymentsEnabled ? (
<button
type="button"
className={[
styles.paymentOption,
paymentType === 'e_payment_gate' ? styles.paymentOptionActive : '',
]
.filter(Boolean)
.join(' ')}
onClick={() => setPaymentType('e_payment_gate')}
disabled={submitting}
>
<CreditCard size={20} />
<span className={styles.paymentOptionBody}>
<span className={styles.paymentOptionTitle}>پرداخت آنلاین</span>
<span className={styles.paymentOptionHint}>پرداخت امن از طریق درگاه</span>
</span>
</button>
) : null}
{paymentType === 'e_payment_gate' && (
{onlinePaymentsEnabled && paymentType === 'e_payment_gate' && (
<div className={styles.paymentSubOptions} role="radiogroup" aria-label="انتخاب درگاه">
{ONLINE_GATEWAYS.map((gateway) => {
{onlineGateways.map((gateway) => {
const active = gatewayType === gateway.id
return (
<button
@@ -262,15 +283,12 @@ export function CheckoutPaymentStep() {
.filter(Boolean)
.join(' ')}
onClick={() => setGatewayType(gateway.id)}
disabled={submitting || !gateway.ready}
disabled={submitting}
>
<span className={styles.paymentSubRadio}>
{active ? <span className={styles.paymentSubRadioInner} /> : null}
</span>
<span>
{gateway.label}
{!gateway.ready ? ' (به‌زودی)' : ''}
</span>
<span>{gateway.labelFa || gateway.label}</span>
</button>
)
})}