feat(payment): unify payment flow with new pure redirect entry and update related endpoints

This commit is contained in:
hamed
2026-07-02 17:08:50 +03:30
parent c247ac2c80
commit 7f5c65129c
17 changed files with 720 additions and 82 deletions
+5
View File
@@ -32,6 +32,7 @@ const schema = z.object({
tax_enabled: z.string(),
tax_percent: z.string(),
sms_panel_fee_rials: z.string(),
sms_price_rials: z.string(),
appointment_fee_rials: z.string(),
// payment gateways
payment_test_mode: z.string(),
@@ -60,6 +61,7 @@ const toForm = (s: Partial<Settings>): FormValues => ({
tax_enabled: s.tax_enabled ?? '0',
tax_percent: s.tax_percent ?? '10',
sms_panel_fee_rials: s.sms_panel_fee_rials ?? '1500000',
sms_price_rials: s.sms_price_rials ?? '500',
appointment_fee_rials: s.appointment_fee_rials ?? '150000',
payment_test_mode: s.payment_test_mode ?? '0',
mellat_enabled: s.mellat_enabled ?? '1',
@@ -452,6 +454,9 @@ export default function SettingsPage() {
: <><ExclamationTriangleIcon style={{ width: 18, height: 18, color: 'var(--danger)' }} /><span style={{ color: 'var(--danger)' }}>تنظیمنشده مقدار KAVENEGAR_API_KEY را در فایل env قرار دهید</span></>}
</div>
</Field>
<Field label="هزینه هر پیامک" hint="مبلغ کسرشده از کیف پول به ازای هر پیامک ارسالی (ریال). مبنای محاسبهٔ تعداد پیامک از موجودی.">
<input {...register('sms_price_rials')} type="number" min={0} dir="ltr" className="input" style={{ maxWidth: 200 }} placeholder="500" />
</Field>
</div>
)}
</div>
+29 -13
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -68,7 +68,10 @@ function SmsWalletPageInner() {
const chargeMutation = useMutation({
mutationFn: ({ amount_rials }: ChargeForm) =>
api.post<{ data: { payment_url: string } }>('/api/v1/sms/wallet/charge', { gateway, amount_rials }),
api.post<{ data: { payment_url: string } }>('/api/v1/sms/wallet/charge', {
gateway, amount_rials,
frontend_address: `${window.location.origin}${window.location.pathname}`,
}),
onSuccess: (res: any) => {
const url = res?.data?.pay_url ?? res?.data?.redirect_url ?? res?.data?.payment_url;
if (url) window.location.href = url;
@@ -80,9 +83,16 @@ function SmsWalletPageInner() {
const [localSettings, setLocalSettings] = useState<SmsSettings | null>(null);
const currentSettings = localSettings ?? settings;
// با هر بار تازه‌شدن داده‌ی سرور، ویرایش محلی صفر شود تا وضعیت واقعی (مثل وضعیت تأیید متن) نمایش داده شود.
useEffect(() => { setLocalSettings(null); }, [settingsData]);
const saveMutation = useMutation({
mutationFn: (body: SmsSettings) => api.patch('/api/v1/sms/settings', body),
onSuccess: () => { qc.invalidateQueries({ queryKey: ['sms-settings'] }); toast.success('تنظیمات ذخیره شد'); },
onSuccess: () => {
setLocalSettings(null);
qc.invalidateQueries({ queryKey: ['sms-settings'] });
toast.success('تنظیمات ذخیره شد');
},
onError: (e: any) => toast.error(e.message),
});
@@ -233,11 +243,14 @@ function SmsWalletPageInner() {
)}
</div>
</div>
<div
className={`switch${currentSettings.reminder_enabled ? ' on' : ''}`}
style={{ marginTop: 4, flexShrink: 0 }}
onClick={() => setLocalSettings({ ...currentSettings, reminder_enabled: !currentSettings.reminder_enabled })}
/>
<label className="switch" style={{ marginTop: 4, flexShrink: 0 }}>
<input
type="checkbox"
checked={currentSettings.reminder_enabled}
onChange={() => setLocalSettings({ ...currentSettings, reminder_enabled: !currentSettings.reminder_enabled })}
/>
<span className="switch-track"><span className="switch-thumb" /></span>
</label>
</div>
{/* ردیف: پیامک بعد از ویزیت */}
@@ -253,11 +266,14 @@ function SmsWalletPageInner() {
<div style={{ color: 'var(--text-3)', fontSize: 12.5, marginTop: 3 }}>متن تشکر پس از پایان مراجعه برای بیمار ارسال میشود</div>
</div>
</div>
<div
className={`switch${currentSettings.post_visit_enabled ? ' on' : ''}`}
style={{ flexShrink: 0 }}
onClick={() => setLocalSettings({ ...currentSettings, post_visit_enabled: !currentSettings.post_visit_enabled })}
/>
<label className="switch" style={{ flexShrink: 0 }}>
<input
type="checkbox"
checked={currentSettings.post_visit_enabled}
onChange={() => setLocalSettings({ ...currentSettings, post_visit_enabled: !currentSettings.post_visit_enabled })}
/>
<span className="switch-track"><span className="switch-thumb" /></span>
</label>
</div>
{/* محتوای باز‌شونده */}
+4 -1
View File
@@ -83,7 +83,10 @@ export default function SubscriptionPage() {
const purchaseMutation = useMutation({
mutationFn: ({ period_uuid, gateway, amount_rials }: { period_uuid: string; gateway: string; amount_rials: number }) =>
api.post<{ data: { payment_url: string } }>('/api/v1/subscription-payment', { period_uuid, gateway, amount_rials }),
api.post<{ data: { payment_url: string } }>('/api/v1/subscription-payment', {
period_uuid, gateway, amount_rials,
frontend_address: `${window.location.origin}/admin/subscription`,
}),
onSuccess: (res: any) => {
const url = res?.data?.pay_url ?? res?.data?.redirect_url ?? res?.data?.payment_url;
if (url) window.location.href = url;