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:
co-authored by
Cursor
parent
9a59bc8f65
commit
e79211e3cd
@@ -81,18 +81,16 @@
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
inset-inline: 0;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
position: static;
|
||||
margin-top: 6px;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
background: var(--elevated-surface);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--glass-shadow);
|
||||
z-index: 30;
|
||||
list-style: none;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
|
||||
@@ -81,18 +81,16 @@
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
inset-inline: 0;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
position: static;
|
||||
margin-top: 6px;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
background: var(--elevated-surface);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--glass-shadow);
|
||||
z-index: 20;
|
||||
list-style: none;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
|
||||
@@ -77,3 +77,8 @@ html[data-theme='dark'] body::before {
|
||||
radial-gradient(ellipse 520px 520px at 72% 18%, rgba(255, 255, 255, 0.04), transparent 72%),
|
||||
radial-gradient(ellipse 420px 420px at 22% 82%, rgba(255, 255, 255, 0.03), transparent 72%);
|
||||
}
|
||||
|
||||
body:has([aria-modal='true']) #root {
|
||||
filter: blur(var(--modal-overlay-blur, 16px));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -83,6 +83,18 @@ export interface CheckoutResponse {
|
||||
payment?: CheckoutPaymentResult
|
||||
}
|
||||
|
||||
export interface PublicPaymentGateway {
|
||||
id: OnlineGatewayType
|
||||
label: string
|
||||
labelFa: string
|
||||
}
|
||||
|
||||
export interface PublicPaymentMethods {
|
||||
enabled: boolean
|
||||
defaultGateway: OnlineGatewayType | null
|
||||
gateways: PublicPaymentGateway[]
|
||||
}
|
||||
|
||||
function cartPath(suffix = '') {
|
||||
const businessId = getActiveBusinessId()
|
||||
if (!businessId) {
|
||||
@@ -91,6 +103,14 @@ function cartPath(suffix = '') {
|
||||
return `/businesses/${businessId}/cart${suffix}`
|
||||
}
|
||||
|
||||
function paymentsMethodsPath() {
|
||||
const businessId = getActiveBusinessId()
|
||||
if (!businessId) {
|
||||
throw new Error('No active business selected. Please sign in again.')
|
||||
}
|
||||
return `/businesses/${businessId}/payments/methods`
|
||||
}
|
||||
|
||||
export async function getCart(signal?: AbortSignal) {
|
||||
return apiRequest<{ cart: Cart }>(cartPath(), { auth: true, signal })
|
||||
}
|
||||
@@ -126,6 +146,13 @@ export async function checkoutCart(input: CheckoutInput) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function getPaymentMethods(signal?: AbortSignal) {
|
||||
return apiRequest<PublicPaymentMethods>(paymentsMethodsPath(), {
|
||||
auth: false,
|
||||
signal,
|
||||
})
|
||||
}
|
||||
|
||||
/** Submit a gateway redirect (POST form or GET navigation). */
|
||||
export function submitPaymentRedirect(redirect: PaymentRedirect) {
|
||||
if (redirect.method === 'GET') {
|
||||
|
||||
Reference in New Issue
Block a user