fix: consolidate Waffo payment settings save flow (#5110)

This commit is contained in:
Seefs
2026-05-26 12:32:05 +08:00
committed by GitHub
parent f223db9330
commit c91ba0c4eb
3 changed files with 719 additions and 514 deletions
@@ -16,8 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { type ChangeEvent, useEffect, useRef, useState } from 'react'
import { useForm } from 'react-hook-form'
import { type ChangeEvent, useRef, type SetStateAction, useState } from 'react'
import { Plus, Pencil, Trash2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
@@ -43,8 +42,6 @@ import {
} from '@/components/ui/table'
import { Textarea } from '@/components/ui/textarea'
import { SettingsSwitchField } from '../components/settings-form-layout'
import { SettingsPageActionsPortal } from '../components/settings-page-context'
import { useUpdateOption } from '../hooks/use-update-option'
export interface WaffoSettingsValues {
WaffoEnabled: boolean
@@ -64,34 +61,33 @@ export interface WaffoSettingsValues {
WaffoPayMethods: string
}
interface PayMethod {
export interface PayMethod {
name: string
icon: string
payMethodType: string
payMethodName: string
}
type WaffoFieldValues = Omit<WaffoSettingsValues, 'WaffoPayMethods'>
interface Props {
defaultValues: WaffoSettingsValues
values: WaffoSettingsValues
onValueChange: <K extends keyof WaffoFieldValues>(
key: K,
value: WaffoFieldValues[K]
) => void
payMethods: PayMethod[]
onPayMethodsChange: (value: SetStateAction<PayMethod[]>) => void
}
export function WaffoSettingsSection(props: Props) {
export function WaffoSettingsSection({
values,
onValueChange,
payMethods,
onPayMethodsChange,
}: Props) {
const { t } = useTranslation()
const updateOption = useUpdateOption()
const [loading, setLoading] = useState(false)
const iconFileInputRef = useRef<HTMLInputElement | null>(null)
const form = useForm<Omit<WaffoSettingsValues, 'WaffoPayMethods'>>({
defaultValues: props.defaultValues,
})
const [payMethods, setPayMethods] = useState<PayMethod[]>(() => {
try {
return JSON.parse(props.defaultValues.WaffoPayMethods || '[]')
} catch {
return []
}
})
const [methodDialogOpen, setMethodDialogOpen] = useState(false)
const [editingIdx, setEditingIdx] = useState(-1)
const [methodForm, setMethodForm] = useState<PayMethod>({
@@ -101,61 +97,6 @@ export function WaffoSettingsSection(props: Props) {
payMethodName: '',
})
useEffect(() => {
form.reset(props.defaultValues)
try {
setPayMethods(JSON.parse(props.defaultValues.WaffoPayMethods || '[]'))
} catch {
setPayMethods([])
}
}, [props.defaultValues, form])
const handleSave = async () => {
setLoading(true)
try {
const values = form.getValues()
const options: { key: string; value: string }[] = [
{ key: 'WaffoEnabled', value: String(values.WaffoEnabled) },
{ key: 'WaffoSandbox', value: String(values.WaffoSandbox) },
{ key: 'WaffoMerchantId', value: values.WaffoMerchantId || '' },
{ key: 'WaffoCurrency', value: values.WaffoCurrency || 'USD' },
{ key: 'WaffoUnitPrice', value: String(values.WaffoUnitPrice || 1) },
{ key: 'WaffoMinTopUp', value: String(values.WaffoMinTopUp || 1) },
{ key: 'WaffoNotifyUrl', value: values.WaffoNotifyUrl || '' },
{ key: 'WaffoReturnUrl', value: values.WaffoReturnUrl || '' },
{ key: 'WaffoPublicCert', value: values.WaffoPublicCert || '' },
{
key: 'WaffoSandboxPublicCert',
value: values.WaffoSandboxPublicCert || '',
},
{ key: 'WaffoPayMethods', value: JSON.stringify(payMethods) },
]
if (values.WaffoApiKey)
options.push({ key: 'WaffoApiKey', value: values.WaffoApiKey })
if (values.WaffoPrivateKey)
options.push({ key: 'WaffoPrivateKey', value: values.WaffoPrivateKey })
if (values.WaffoSandboxApiKey)
options.push({
key: 'WaffoSandboxApiKey',
value: values.WaffoSandboxApiKey,
})
if (values.WaffoSandboxPrivateKey)
options.push({
key: 'WaffoSandboxPrivateKey',
value: values.WaffoSandboxPrivateKey,
})
for (const opt of options) {
await updateOption.mutateAsync(opt)
}
toast.success(t('Updated successfully'))
} catch {
toast.error(t('Update failed'))
} finally {
setLoading(false)
}
}
const openAdd = () => {
setEditingIdx(-1)
setMethodForm({ name: '', icon: '', payMethodType: '', payMethodName: '' })
@@ -172,9 +113,9 @@ export function WaffoSettingsSection(props: Props) {
if (!methodForm.name.trim())
return toast.error(t('Payment method name is required'))
if (editingIdx === -1) {
setPayMethods((prev) => [...prev, methodForm])
onPayMethodsChange((prev) => [...prev, methodForm])
} else {
setPayMethods((prev) =>
onPayMethodsChange((prev) =>
prev.map((m, i) => (i === editingIdx ? methodForm : m))
)
}
@@ -213,16 +154,6 @@ export function WaffoSettingsSection(props: Props) {
return (
<>
<div className='space-y-4 pt-4'>
<SettingsPageActionsPortal>
<Button
type='button'
size='sm'
onClick={handleSave}
disabled={loading}
>
{loading ? t('Saving...') : t('Save Waffo settings')}
</Button>
</SettingsPageActionsPortal>
<div>
<h3 className='text-lg font-medium'>
{t('Waffo Aggregator Gateway')}
@@ -243,14 +174,14 @@ export function WaffoSettingsSection(props: Props) {
<div className='grid gap-4 sm:grid-cols-2'>
<SettingsSwitchField
checked={form.watch('WaffoEnabled')}
onCheckedChange={(v) => form.setValue('WaffoEnabled', v)}
checked={values.WaffoEnabled}
onCheckedChange={(v) => onValueChange('WaffoEnabled', v)}
label={t('Enable Waffo')}
className='border-b-0 py-0'
/>
<SettingsSwitchField
checked={form.watch('WaffoSandbox')}
onCheckedChange={(v) => form.setValue('WaffoSandbox', v)}
checked={values.WaffoSandbox}
onCheckedChange={(v) => onValueChange('WaffoSandbox', v)}
label={t('Sandbox mode')}
className='border-b-0 py-0'
/>
@@ -259,17 +190,34 @@ export function WaffoSettingsSection(props: Props) {
<div className='grid grid-cols-2 gap-4'>
<div className='grid gap-1.5'>
<Label>{t('API Key (Production)')}</Label>
<Input type='password' {...form.register('WaffoApiKey')} />
<Input
type='password'
value={values.WaffoApiKey}
onChange={(event) =>
onValueChange('WaffoApiKey', event.target.value)
}
/>
</div>
<div className='grid gap-1.5'>
<Label>{t('API Key (Sandbox)')}</Label>
<Input type='password' {...form.register('WaffoSandboxApiKey')} />
<Input
type='password'
value={values.WaffoSandboxApiKey}
onChange={(event) =>
onValueChange('WaffoSandboxApiKey', event.target.value)
}
/>
</div>
</div>
<div className='grid gap-1.5'>
<Label>{t('Merchant ID')}</Label>
<Input {...form.register('WaffoMerchantId')} />
<Input
value={values.WaffoMerchantId}
onChange={(event) =>
onValueChange('WaffoMerchantId', event.target.value)
}
/>
</div>
<div className='grid grid-cols-2 gap-4'>
@@ -277,7 +225,10 @@ export function WaffoSettingsSection(props: Props) {
<Label>{t('RSA Private Key (Production)')}</Label>
<Textarea
rows={3}
{...form.register('WaffoPrivateKey')}
value={values.WaffoPrivateKey}
onChange={(event) =>
onValueChange('WaffoPrivateKey', event.target.value)
}
className='font-mono text-xs'
/>
</div>
@@ -285,7 +236,10 @@ export function WaffoSettingsSection(props: Props) {
<Label>{t('RSA Private Key (Sandbox)')}</Label>
<Textarea
rows={3}
{...form.register('WaffoSandboxPrivateKey')}
value={values.WaffoSandboxPrivateKey}
onChange={(event) =>
onValueChange('WaffoSandboxPrivateKey', event.target.value)
}
className='font-mono text-xs'
/>
</div>
@@ -296,7 +250,10 @@ export function WaffoSettingsSection(props: Props) {
<Label>{t('Waffo Public Key (Production)')}</Label>
<Textarea
rows={3}
{...form.register('WaffoPublicCert')}
value={values.WaffoPublicCert}
onChange={(event) =>
onValueChange('WaffoPublicCert', event.target.value)
}
className='font-mono text-xs'
/>
</div>
@@ -304,7 +261,10 @@ export function WaffoSettingsSection(props: Props) {
<Label>{t('Waffo Public Key (Sandbox)')}</Label>
<Textarea
rows={3}
{...form.register('WaffoSandboxPublicCert')}
value={values.WaffoSandboxPublicCert}
onChange={(event) =>
onValueChange('WaffoSandboxPublicCert', event.target.value)
}
className='font-mono text-xs'
/>
</div>
@@ -313,7 +273,7 @@ export function WaffoSettingsSection(props: Props) {
<div className='grid grid-cols-3 gap-4'>
<div className='grid gap-1.5'>
<Label>{t('Currency')}</Label>
<Input {...form.register('WaffoCurrency')} disabled />
<Input value={values.WaffoCurrency} disabled />
</div>
<div className='grid gap-1.5'>
<Label>{t('Unit price (USD)')}</Label>
@@ -321,12 +281,28 @@ export function WaffoSettingsSection(props: Props) {
type='number'
step={0.1}
min={0}
{...form.register('WaffoUnitPrice')}
value={values.WaffoUnitPrice}
onChange={(event) =>
onValueChange(
'WaffoUnitPrice',
event.target.value === '' ? 0 : event.target.valueAsNumber
)
}
/>
</div>
<div className='grid gap-1.5'>
<Label>{t('Minimum top-up quantity')}</Label>
<Input type='number' min={1} {...form.register('WaffoMinTopUp')} />
<Input
type='number'
min={1}
value={values.WaffoMinTopUp}
onChange={(event) =>
onValueChange(
'WaffoMinTopUp',
event.target.value === '' ? 1 : event.target.valueAsNumber
)
}
/>
</div>
</div>
@@ -335,14 +311,20 @@ export function WaffoSettingsSection(props: Props) {
<Label>{t('Callback notification URL')}</Label>
<Input
placeholder='https://example.com/api/waffo/webhook'
{...form.register('WaffoNotifyUrl')}
value={values.WaffoNotifyUrl}
onChange={(event) =>
onValueChange('WaffoNotifyUrl', event.target.value)
}
/>
</div>
<div className='grid gap-1.5'>
<Label>{t('Payment return URL')}</Label>
<Input
placeholder='https://example.com/console/topup'
{...form.register('WaffoReturnUrl')}
value={values.WaffoReturnUrl}
onChange={(event) =>
onValueChange('WaffoReturnUrl', event.target.value)
}
/>
</div>
</div>
@@ -351,7 +333,7 @@ export function WaffoSettingsSection(props: Props) {
<div className='flex items-center justify-between'>
<h4 className='font-medium'>{t('Payment Methods')}</h4>
<Button variant='outline' size='sm' onClick={openAdd}>
<Button type='button' variant='outline' size='sm' onClick={openAdd}>
<Plus className='mr-1 h-3 w-3' />
{t('Add payment method')}
</Button>
@@ -398,6 +380,7 @@ export function WaffoSettingsSection(props: Props) {
<TableCell className='text-right'>
<div className='flex justify-end gap-1'>
<Button
type='button'
variant='ghost'
size='icon'
className='h-7 w-7'
@@ -406,11 +389,12 @@ export function WaffoSettingsSection(props: Props) {
<Pencil className='h-3 w-3' />
</Button>
<Button
type='button'
variant='ghost'
size='icon'
className='h-7 w-7'
onClick={() =>
setPayMethods((prev) =>
onPayMethodsChange((prev) =>
prev.filter((_, i) => i !== idx)
)
}
@@ -523,12 +507,15 @@ export function WaffoSettingsSection(props: Props) {
</div>
<DialogFooter>
<Button
type='button'
variant='outline'
onClick={() => setMethodDialogOpen(false)}
>
{t('Cancel')}
</Button>
<Button onClick={saveMethod}>{t('Confirm')}</Button>
<Button type='button' onClick={saveMethod}>
{t('Confirm')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>