🐛 fix(changelog): Update change log style
This commit is contained in:
commit
cfa3fc024d
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,49 +1,43 @@
|
|||||||
<a name="readme-top"></a>
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
# [1.0.0-beta.26](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.25...v1.0.0-beta.26) (2025-03-02)
|
# [1.0.0-beta.26](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.25...v1.0.0-beta.26) (2025-03-02)
|
||||||
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
* **icon**: Comment out unused icon collection imports ([f17bf8d](https://github.com/perfect-panel/ppanel-web/commit/f17bf8d))
|
- **icon**: Comment out unused icon collection imports ([f17bf8d](https://github.com/perfect-panel/ppanel-web/commit/f17bf8d))
|
||||||
|
|
||||||
# [1.0.0-beta.25](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.24...v1.0.0-beta.25) (2025-03-01)
|
# [1.0.0-beta.25](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.24...v1.0.0-beta.25) (2025-03-01)
|
||||||
|
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|
||||||
* **auth**: Add privacy policy link to the footer ([8e16ef1](https://github.com/perfect-panel/ppanel-web/commit/8e16ef1))
|
- **auth**: Add privacy policy link to the footer ([8e16ef1](https://github.com/perfect-panel/ppanel-web/commit/8e16ef1))
|
||||||
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
* **dashboard**: Display subscription creation date in user dashboard ([d0e6df0](https://github.com/perfect-panel/ppanel-web/commit/d0e6df0))
|
- **dashboard**: Display subscription creation date in user dashboard ([d0e6df0](https://github.com/perfect-panel/ppanel-web/commit/d0e6df0))
|
||||||
* **request**: Add error code 40005 to trigger logout ([71bf002](https://github.com/perfect-panel/ppanel-web/commit/71bf002))
|
- **request**: Add error code 40005 to trigger logout ([71bf002](https://github.com/perfect-panel/ppanel-web/commit/71bf002))
|
||||||
* **subscribe**: Update payment return URL ([2b80496](https://github.com/perfect-panel/ppanel-web/commit/2b80496))
|
- **subscribe**: Update payment return URL ([2b80496](https://github.com/perfect-panel/ppanel-web/commit/2b80496))
|
||||||
|
|
||||||
# [1.0.0-beta.24](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.23...v1.0.0-beta.24) (2025-02-27)
|
# [1.0.0-beta.24](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.23...v1.0.0-beta.24) (2025-02-27)
|
||||||
|
|
||||||
|
|
||||||
### ♻ Code Refactoring
|
### ♻ Code Refactoring
|
||||||
|
|
||||||
* **ui**: Optimize document display ([2ca2992](https://github.com/perfect-panel/ppanel-web/commit/2ca2992))
|
- **ui**: Optimize document display ([2ca2992](https://github.com/perfect-panel/ppanel-web/commit/2ca2992))
|
||||||
* Reduce code complexity and improve readability ([e11f18c](https://github.com/perfect-panel/ppanel-web/commit/e11f18c))
|
- Reduce code complexity and improve readability ([e11f18c](https://github.com/perfect-panel/ppanel-web/commit/e11f18c))
|
||||||
|
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|
||||||
* **loading**: Add loading components and integrate them in Providers ([d5847fa](https://github.com/perfect-panel/ppanel-web/commit/d5847fa))
|
- **loading**: Add loading components and integrate them in Providers ([d5847fa](https://github.com/perfect-panel/ppanel-web/commit/d5847fa))
|
||||||
|
|
||||||
|
|
||||||
### 🎫 Chores
|
### 🎫 Chores
|
||||||
|
|
||||||
* **merge**: Add advertising module and device settings ([0130e02](https://github.com/perfect-panel/ppanel-web/commit/0130e02))
|
- **merge**: Add advertising module and device settings ([0130e02](https://github.com/perfect-panel/ppanel-web/commit/0130e02))
|
||||||
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
* **locales**: Order recharge related fields ([35210fe](https://github.com/perfect-panel/ppanel-web/commit/35210fe))
|
- **locales**: Order recharge related fields ([35210fe](https://github.com/perfect-panel/ppanel-web/commit/35210fe))
|
||||||
|
|
||||||
<a name="readme-top"></a>
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
|||||||
@ -116,7 +116,7 @@ export default function Page(props: any) {
|
|||||||
<ul className='grid gap-3'>
|
<ul className='grid gap-3'>
|
||||||
<li className='flex items-center justify-between'>
|
<li className='flex items-center justify-between'>
|
||||||
<span className='text-muted-foreground'>{t('method')}</span>
|
<span className='text-muted-foreground'>{t('method')}</span>
|
||||||
<span>{t(`methods.${row.original.method}`)}</span>
|
<span>{row.original?.payment?.name || row.original?.payment?.platform}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
|
|||||||
@ -1,256 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { getAlipayF2FPaymentConfig, updateAlipayF2FPaymentConfig } from '@/services/admin/payment';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Label } from '@workspace/ui/components/label';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@workspace/ui/components/select';
|
|
||||||
import { Switch } from '@workspace/ui/components/switch';
|
|
||||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
|
||||||
import { Textarea } from '@workspace/ui/components/textarea';
|
|
||||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
|
||||||
import { unitConversion } from '@workspace/ui/utils';
|
|
||||||
import { useTranslations } from 'next-intl';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
export default function AlipayF2F() {
|
|
||||||
const t = useTranslations('payment');
|
|
||||||
|
|
||||||
const { data, refetch } = useQuery({
|
|
||||||
queryKey: ['getAlipayF2FPaymentConfig'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await getAlipayF2FPaymentConfig();
|
|
||||||
|
|
||||||
return data.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function updateConfig(key: string, value: unknown) {
|
|
||||||
if (data?.[key] === value) return;
|
|
||||||
try {
|
|
||||||
await updateAlipayF2FPaymentConfig({
|
|
||||||
...data,
|
|
||||||
[key]: value,
|
|
||||||
} as API.UpdateAlipayF2fRequest);
|
|
||||||
toast.success(t('saveSuccess'));
|
|
||||||
refetch();
|
|
||||||
} catch (error) {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('enable')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Switch
|
|
||||||
checked={data?.enable}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
updateConfig('enable', checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('alipayf2f.sandbox')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('alipayf2f.sandboxDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Switch
|
|
||||||
checked={data?.config.sandbox}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
sandbox: checked,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('showName')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.name}
|
|
||||||
onValueBlur={(value) => updateConfig('name', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('iconUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.icon_url}
|
|
||||||
onValueBlur={(value) => updateConfig('icon', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('notifyUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.domain}
|
|
||||||
onValueBlur={(value) => updateConfig('domain', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feeMode')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Select
|
|
||||||
value={String(data?.fee_mode)}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
updateConfig('fee_mode', Number(value));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder='请选择' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
|
||||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
|
||||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
|
||||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feePercent')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
type='number'
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
maxLength={3}
|
|
||||||
value={data?.fee_percent}
|
|
||||||
onValueBlur={(value) => updateConfig('fee_percent', value)}
|
|
||||||
suffix='%'
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('fixedFee')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
min={0}
|
|
||||||
value={data?.fee_amount}
|
|
||||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
|
||||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
|
||||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('alipayf2f.appId')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.app_id}
|
|
||||||
onValueBlur={(value) =>
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
app_id: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('alipayf2f.privateKey')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Textarea
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
defaultValue={data?.config.private_key}
|
|
||||||
onBlur={(e) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
private_key: e.target.value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('alipayf2f.publicKey')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Textarea
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
defaultValue={data?.config.public_key}
|
|
||||||
onBlur={(e) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
public_key: e.target.value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('alipayf2f.invoiceName')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('alipayf2f.invoiceNameDescription')}
|
|
||||||
value={data?.config.invoice_name}
|
|
||||||
onValueBlur={(value) =>
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
invoice_name: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,221 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { getEpayPaymentConfig, updateEpayPaymentConfig } from '@/services/admin/payment';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Label } from '@workspace/ui/components/label';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@workspace/ui/components/select';
|
|
||||||
import { Switch } from '@workspace/ui/components/switch';
|
|
||||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
|
||||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
|
||||||
import { unitConversion } from '@workspace/ui/utils';
|
|
||||||
import { useTranslations } from 'next-intl';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
export default function Epay() {
|
|
||||||
const t = useTranslations('payment');
|
|
||||||
|
|
||||||
const { data, refetch } = useQuery({
|
|
||||||
queryKey: ['getEpayPaymentConfig'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await getEpayPaymentConfig();
|
|
||||||
|
|
||||||
return data.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function updateConfig(key: string, value: unknown) {
|
|
||||||
if (data?.[key] === value) return;
|
|
||||||
try {
|
|
||||||
await updateEpayPaymentConfig({
|
|
||||||
...data,
|
|
||||||
[key]: value,
|
|
||||||
} as API.UpdateEpayRequest);
|
|
||||||
toast.success(t('saveSuccess'));
|
|
||||||
refetch();
|
|
||||||
} catch (error) {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('enable')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Switch
|
|
||||||
checked={data?.enable}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
updateConfig('enable', checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('showName')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.name}
|
|
||||||
onValueBlur={(value) => updateConfig('name', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('iconUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.icon}
|
|
||||||
onValueBlur={(value) => updateConfig('icon', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('notifyUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.domain}
|
|
||||||
onValueBlur={(value) => updateConfig('domain', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feeMode')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Select
|
|
||||||
value={String(data?.fee_mode)}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
updateConfig('fee_mode', Number(value));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder='请选择' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
|
||||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
|
||||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
|
||||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feePercent')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
type='number'
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
maxLength={3}
|
|
||||||
value={data?.fee_percent}
|
|
||||||
onValueBlur={(value) => updateConfig('fee_percent', value)}
|
|
||||||
suffix='%'
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('fixedFee')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
type='number'
|
|
||||||
min={0}
|
|
||||||
value={data?.fee_amount}
|
|
||||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
|
||||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
|
||||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('epay.url')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.url}
|
|
||||||
onValueBlur={(value) =>
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
url: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('epay.pid')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.pid}
|
|
||||||
onValueBlur={(value) =>
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
pid: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('epay.key')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.key}
|
|
||||||
onValueBlur={(value) =>
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
key: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,34 +1,11 @@
|
|||||||
import Billing from '@/components/billing';
|
import Billing from '@/components/billing';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
import PaymentTable from './payment-table';
|
||||||
import AlipayF2F from './alipayf2f';
|
|
||||||
import Epay from './epay';
|
|
||||||
import StripeAlipay from './stripe-alipay';
|
|
||||||
import StripeWeChatPay from './stripe-wechat-pay';
|
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabs defaultValue='Epay'>
|
<PaymentTable />
|
||||||
<TabsList className='h-full flex-wrap'>
|
<div className='mt-5 flex flex-col gap-3'>
|
||||||
<TabsTrigger value='Epay'>Epay</TabsTrigger>
|
|
||||||
<TabsTrigger value='Stripe-Alipay'>Stripe(AliPay)</TabsTrigger>
|
|
||||||
<TabsTrigger value='Strip-WeChatPay'>Stripe(WeChatPay)</TabsTrigger>
|
|
||||||
<TabsTrigger value='AlipayF2F'>AlipayF2F</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value='Epay'>
|
|
||||||
<Epay />
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value='Stripe-Alipay'>
|
|
||||||
<StripeAlipay />
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value='Strip-WeChatPay'>
|
|
||||||
<StripeWeChatPay />
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value='AlipayF2F'>
|
|
||||||
<AlipayF2F />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
<div className='flex flex-col gap-3'>
|
|
||||||
<Billing type='payment' />
|
<Billing type='payment' />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
415
apps/admin/app/dashboard/payment/payment-form.tsx
Normal file
415
apps/admin/app/dashboard/payment/payment-form.tsx
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import useGlobalStore from '@/config/use-global';
|
||||||
|
import { getPaymentPlatform } from '@/services/admin/payment';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@workspace/ui/components/form';
|
||||||
|
import { RadioGroup, RadioGroupItem } from '@workspace/ui/components/radio-group';
|
||||||
|
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@workspace/ui/components/select';
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetFooter,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
|
} from '@workspace/ui/components/sheet';
|
||||||
|
import { MarkdownEditor } from '@workspace/ui/custom-components/editor';
|
||||||
|
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||||
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
|
import { unitConversion } from '@workspace/ui/utils';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
|
interface PaymentFormProps<T> {
|
||||||
|
trigger: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
loading?: boolean;
|
||||||
|
initialValues?: T;
|
||||||
|
onSubmit: (values: T) => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PaymentForm<T>({
|
||||||
|
trigger,
|
||||||
|
title,
|
||||||
|
loading,
|
||||||
|
initialValues,
|
||||||
|
onSubmit,
|
||||||
|
}: PaymentFormProps<T>) {
|
||||||
|
const t = useTranslations('payment');
|
||||||
|
const { common } = useGlobalStore();
|
||||||
|
const { currency } = common;
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const { data: platformData } = useQuery({
|
||||||
|
queryKey: ['getPaymentPlatform'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await getPaymentPlatform();
|
||||||
|
return data?.data?.list || [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z.string().min(1, { message: t('nameRequired') }),
|
||||||
|
platform: z.string().optional(),
|
||||||
|
icon: z.string().optional(),
|
||||||
|
domain: z.string().optional(),
|
||||||
|
config: z.any(),
|
||||||
|
fee_mode: z.coerce.number().min(0).max(2),
|
||||||
|
fee_percent: z.coerce.number().optional(),
|
||||||
|
fee_amount: z.coerce.number().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: '',
|
||||||
|
platform: '',
|
||||||
|
icon: '',
|
||||||
|
domain: '',
|
||||||
|
config: {},
|
||||||
|
fee_mode: 0,
|
||||||
|
fee_percent: 0,
|
||||||
|
fee_amount: 0,
|
||||||
|
...(initialValues as any),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const feeMode = form.watch('fee_mode');
|
||||||
|
const platformValue = form.watch('platform');
|
||||||
|
const configValues = form.watch('config');
|
||||||
|
|
||||||
|
const currentPlatform = platformData?.find((p) => p.platform === platformValue);
|
||||||
|
const currentFieldDescriptions = currentPlatform?.platform_field_description || {};
|
||||||
|
const configFields = Object.keys(currentFieldDescriptions) || [];
|
||||||
|
const platformUrl = currentPlatform?.platform_url || '';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (feeMode === 0) {
|
||||||
|
form.setValue('fee_amount', 0);
|
||||||
|
form.setValue('fee_percent', 0);
|
||||||
|
} else if (feeMode === 1) {
|
||||||
|
form.setValue('fee_amount', 0);
|
||||||
|
} else if (feeMode === 2) {
|
||||||
|
form.setValue('fee_percent', 0);
|
||||||
|
}
|
||||||
|
}, [feeMode, form]);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
form.reset();
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||||
|
const cleanedValues = { ...values };
|
||||||
|
|
||||||
|
if (values.fee_mode === 0) {
|
||||||
|
cleanedValues.fee_amount = undefined;
|
||||||
|
cleanedValues.fee_percent = undefined;
|
||||||
|
} else if (values.fee_mode === 1) {
|
||||||
|
cleanedValues.fee_amount = undefined;
|
||||||
|
} else if (values.fee_mode === 2) {
|
||||||
|
cleanedValues.fee_percent = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = await onSubmit(cleanedValues as unknown as T);
|
||||||
|
if (success) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openPlatformUrl = () => {
|
||||||
|
if (platformUrl) {
|
||||||
|
window.open(platformUrl, '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
|
<SheetTrigger asChild>{trigger}</SheetTrigger>
|
||||||
|
<SheetContent className='w-[550px] max-w-full md:max-w-screen-md'>
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>{title}</SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
<ScrollArea className='-mx-6 h-[calc(100vh-48px-36px-36px-env(safe-area-inset-top))]'>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-6 px-6 pt-4'>
|
||||||
|
{/* 基本信息分组 */}
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2'>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='name'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
placeholder={t('namePlaceholder')}
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value) => form.setValue('name', value as string)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='icon'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('icon')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
placeholder={t('iconPlaceholder')}
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value) => form.setValue('icon', value as string)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='domain'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('domain')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
placeholder={t('domainPlaceholder', { example: 'http(s)://example.com' })}
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value) => form.setValue('domain', value as string)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='fee_mode'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('handlingFee')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroup
|
||||||
|
onValueChange={(value) => field.onChange(parseInt(value))}
|
||||||
|
value={field.value.toString()}
|
||||||
|
className='flex flex-wrap gap-4'
|
||||||
|
>
|
||||||
|
<FormItem className='flex items-center space-x-2'>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value='0' />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className='!mt-0 cursor-pointer'>{t('noFee')}</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className='flex items-center space-x-2'>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value='1' />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className='!mt-0 cursor-pointer'>
|
||||||
|
{t('percentFee')}
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className='flex items-center space-x-2'>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value='2' />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className='!mt-0 cursor-pointer'>{t('fixedFee')}</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{feeMode === 1 && (
|
||||||
|
<div className='grid grid-cols-1 sm:w-1/2'>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='fee_percent'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('feePercent')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
type='number'
|
||||||
|
step='0.01'
|
||||||
|
suffix='%'
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{feeMode === 2 && (
|
||||||
|
<div className='grid grid-cols-1 sm:w-1/2'>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='fee_amount'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('feeAmount')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
type='number'
|
||||||
|
step='0.01'
|
||||||
|
prefix={currency.currency_symbol}
|
||||||
|
suffix={currency.currency_unit}
|
||||||
|
value={unitConversion('centsToDollars', field.value)}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
field.onChange(unitConversion('dollarsToCents', value))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='space-y-4'>
|
||||||
|
{(!platformValue || platformData?.find((p) => p.platform === platformValue)) && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='platform'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('platform')}</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => {
|
||||||
|
form.setValue('platform', value as string);
|
||||||
|
form.setValue('config', {});
|
||||||
|
}}
|
||||||
|
defaultValue={field.value}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t('selectPlatform')} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{platformData?.map((platform) => (
|
||||||
|
<SelectItem key={platform.platform} value={platform.platform}>
|
||||||
|
{platform.platform}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{platformUrl ? (
|
||||||
|
<div className='mt-1 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
size='sm'
|
||||||
|
className='h-6 px-2 text-xs'
|
||||||
|
onClick={openPlatformUrl}
|
||||||
|
>
|
||||||
|
<Icon icon='tabler:external-link' className='mr-1 h-3 w-3' />
|
||||||
|
{t('applyForPayment')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='mt-1 h-6'></div>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{configFields.length > 0 && (
|
||||||
|
<div className='mt-4 space-y-4'>
|
||||||
|
{configFields.map((fieldKey) => (
|
||||||
|
<FormItem key={fieldKey}>
|
||||||
|
<FormLabel>{currentFieldDescriptions[fieldKey]}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
placeholder={t('configPlaceholder', {
|
||||||
|
field: currentFieldDescriptions[fieldKey],
|
||||||
|
})}
|
||||||
|
value={
|
||||||
|
configValues && configValues[fieldKey] !== undefined
|
||||||
|
? configValues[fieldKey]
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const newConfig = { ...configValues };
|
||||||
|
newConfig[fieldKey] = value;
|
||||||
|
form.setValue('config', newConfig);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='description'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('description')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<MarkdownEditor
|
||||||
|
value={field.value}
|
||||||
|
onChange={(value) => form.setValue(field.name, value as string)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
|
||||||
|
<Button variant='outline' disabled={loading} onClick={handleClose}>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button disabled={loading} onClick={form.handleSubmit(handleSubmit)}>
|
||||||
|
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
|
||||||
|
{t('submit')}
|
||||||
|
</Button>
|
||||||
|
</SheetFooter>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
222
apps/admin/app/dashboard/payment/payment-table.tsx
Normal file
222
apps/admin/app/dashboard/payment/payment-table.tsx
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Display } from '@/components/display';
|
||||||
|
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||||
|
import {
|
||||||
|
createPaymentMethod,
|
||||||
|
deletePaymentMethod,
|
||||||
|
getPaymentMethodList,
|
||||||
|
updatePaymentMethod,
|
||||||
|
} from '@/services/admin/payment';
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@workspace/ui/components/avatar';
|
||||||
|
import { Badge } from '@workspace/ui/components/badge';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import { Switch } from '@workspace/ui/components/switch';
|
||||||
|
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import PaymentForm from './payment-form';
|
||||||
|
|
||||||
|
export default function PaymentTable() {
|
||||||
|
const t = useTranslations('payment');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const ref = useRef<ProTableActions>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProTable<API.PaymentConfig, { search: string }>
|
||||||
|
action={ref}
|
||||||
|
header={{
|
||||||
|
title: t('paymentManagement'),
|
||||||
|
toolbar: (
|
||||||
|
<PaymentForm<API.CreatePaymentMethodRequest>
|
||||||
|
trigger={<Button>{t('create')}</Button>}
|
||||||
|
title={t('createPayment')}
|
||||||
|
loading={loading}
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await createPaymentMethod({
|
||||||
|
...values,
|
||||||
|
enable: false,
|
||||||
|
});
|
||||||
|
toast.success(t('createSuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
setLoading(false);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessorKey: 'enable',
|
||||||
|
header: t('enable'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={Boolean(row.getValue('enable'))}
|
||||||
|
onCheckedChange={async (checked) => {
|
||||||
|
await updatePaymentMethod({
|
||||||
|
...row.original,
|
||||||
|
enable: checked,
|
||||||
|
});
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'icon',
|
||||||
|
header: t('icon'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const icon = row.getValue('icon') as string;
|
||||||
|
return (
|
||||||
|
<Avatar className='h-8 w-8'>
|
||||||
|
{icon ? <AvatarImage src={icon} alt={row.getValue('name')} /> : null}
|
||||||
|
<AvatarFallback>
|
||||||
|
{(row.getValue('name') as string)?.charAt(0) || '?'}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: t('name'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'platform',
|
||||||
|
header: t('platform'),
|
||||||
|
cell: ({ row }) => <Badge>{t(row.getValue('platform'))}</Badge>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'notify_url',
|
||||||
|
header: t('notify_url'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'fee',
|
||||||
|
header: t('handlingFee'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const feeMode = row.original.fee_mode;
|
||||||
|
if (feeMode === 1) {
|
||||||
|
return <Badge>{row.original.fee_percent}%</Badge>;
|
||||||
|
} else if (feeMode === 2) {
|
||||||
|
return (
|
||||||
|
<Badge>
|
||||||
|
<Display value={row.original.fee_amount} type='currency' />
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '--';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
params={[
|
||||||
|
{
|
||||||
|
key: 'search',
|
||||||
|
placeholder: t('searchPlaceholder'),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
request={async (pagination, filter) => {
|
||||||
|
const { data } = await getPaymentMethodList({
|
||||||
|
...pagination,
|
||||||
|
...filter,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
list: data?.data?.list || [],
|
||||||
|
total: data?.data?.total || 0,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
actions={{
|
||||||
|
render: (row) => [
|
||||||
|
<PaymentForm<API.UpdatePaymentMethodRequest>
|
||||||
|
key='edit'
|
||||||
|
trigger={<Button>{t('edit')}</Button>}
|
||||||
|
title={t('editPayment')}
|
||||||
|
loading={loading}
|
||||||
|
initialValues={row}
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await updatePaymentMethod({
|
||||||
|
...row,
|
||||||
|
...values,
|
||||||
|
});
|
||||||
|
toast.success(t('updateSuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
setLoading(false);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
<ConfirmButton
|
||||||
|
key='delete'
|
||||||
|
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
||||||
|
title={t('confirmDelete')}
|
||||||
|
description={t('deleteWarning')}
|
||||||
|
onConfirm={async () => {
|
||||||
|
await deletePaymentMethod({
|
||||||
|
id: row.id,
|
||||||
|
});
|
||||||
|
toast.success(t('deleteSuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
cancelText={t('cancel')}
|
||||||
|
confirmText={t('confirm')}
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
key='copy'
|
||||||
|
variant='outline'
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const { id, ...params } = row;
|
||||||
|
await createPaymentMethod({
|
||||||
|
...params,
|
||||||
|
enable: false,
|
||||||
|
});
|
||||||
|
toast.success(t('copySuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
setLoading(false);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('copy')}
|
||||||
|
</Button>,
|
||||||
|
],
|
||||||
|
batchRender(rows) {
|
||||||
|
return [
|
||||||
|
<ConfirmButton
|
||||||
|
key='delete'
|
||||||
|
trigger={<Button variant='destructive'>{t('batchDelete')}</Button>}
|
||||||
|
title={t('confirmDelete')}
|
||||||
|
description={t('deleteWarning')}
|
||||||
|
onConfirm={async () => {
|
||||||
|
for (const row of rows) {
|
||||||
|
await deletePaymentMethod({ id: row.id });
|
||||||
|
}
|
||||||
|
toast.success(t('deleteSuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
cancelText={t('cancel')}
|
||||||
|
confirmText={t('confirm')}
|
||||||
|
/>,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,232 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getStripeAlipayPaymentConfig,
|
|
||||||
updateStripeAlipayPaymentConfig,
|
|
||||||
} from '@/services/admin/payment';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Label } from '@workspace/ui/components/label';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@workspace/ui/components/select';
|
|
||||||
import { Switch } from '@workspace/ui/components/switch';
|
|
||||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
|
||||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
|
||||||
import { unitConversion } from '@workspace/ui/utils';
|
|
||||||
import { useTranslations } from 'next-intl';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
export default function Stripe() {
|
|
||||||
const t = useTranslations('payment');
|
|
||||||
|
|
||||||
const { data, refetch } = useQuery({
|
|
||||||
queryKey: ['getStripeAlipayPaymentConfig'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await getStripeAlipayPaymentConfig();
|
|
||||||
return data.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function updateConfig(key: string, value: unknown) {
|
|
||||||
if (data?.[key] === value) return;
|
|
||||||
try {
|
|
||||||
await updateStripeAlipayPaymentConfig({
|
|
||||||
...data,
|
|
||||||
mark: 'stripe_alipay',
|
|
||||||
[key]: value,
|
|
||||||
} as any);
|
|
||||||
toast.success(t('saveSuccess'));
|
|
||||||
refetch();
|
|
||||||
} catch (error) {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('aliPay')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Switch
|
|
||||||
checked={data?.enable}
|
|
||||||
onCheckedChange={async (checked) => {
|
|
||||||
updateConfig('enable', checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('showName')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.name}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('name', value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('iconUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.icon}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('icon', value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('notifyUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.domain}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('domain', value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feeMode')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Select
|
|
||||||
value={String(data?.fee_mode)}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
updateConfig('fee_mode', Number(value));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder='请选择' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
|
||||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
|
||||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
|
||||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feePercent')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
type='number'
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
value={data?.fee_percent}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('fee_percent', value);
|
|
||||||
}}
|
|
||||||
suffix='%'
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('fixedFee')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
type='number'
|
|
||||||
min={0}
|
|
||||||
value={data?.fee_amount}
|
|
||||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
|
||||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
|
||||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('stripe.publicKey')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.public_key}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
public_key: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('stripe.secretKey')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.secret_key}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
secret_key: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('stripe.webhookSecret')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.webhook_secret}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
webhook_secret: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,232 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getStripeWeChatPayPaymentConfig,
|
|
||||||
updateStripeWeChatPayPaymentConfig,
|
|
||||||
} from '@/services/admin/payment';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Label } from '@workspace/ui/components/label';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@workspace/ui/components/select';
|
|
||||||
import { Switch } from '@workspace/ui/components/switch';
|
|
||||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
|
||||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
|
||||||
import { unitConversion } from '@workspace/ui/utils';
|
|
||||||
import { useTranslations } from 'next-intl';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
export default function StripeWeChatPay() {
|
|
||||||
const t = useTranslations('payment');
|
|
||||||
|
|
||||||
const { data, refetch } = useQuery({
|
|
||||||
queryKey: ['getStripeWeChatPayPaymentConfig'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await getStripeWeChatPayPaymentConfig();
|
|
||||||
return data.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function updateConfig(key: string, value: unknown) {
|
|
||||||
if (data?.[key] === value) return;
|
|
||||||
try {
|
|
||||||
await updateStripeWeChatPayPaymentConfig({
|
|
||||||
...data,
|
|
||||||
mark: 'stripe_wechat_pay',
|
|
||||||
[key]: value,
|
|
||||||
} as any);
|
|
||||||
toast.success(t('saveSuccess'));
|
|
||||||
refetch();
|
|
||||||
} catch (error) {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('wechatPay')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Switch
|
|
||||||
checked={data?.enable}
|
|
||||||
onCheckedChange={async (checked) => {
|
|
||||||
updateConfig('enable', checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('showName')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.name}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('name', value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('iconUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.icon}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('icon', value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('notifyUrl')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.domain}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('domain', value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feeMode')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<Select
|
|
||||||
value={String(data?.fee_mode)}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
updateConfig('fee_mode', Number(value));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder='请选择' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
|
||||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
|
||||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
|
||||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('feePercent')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
type='number'
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
value={data?.fee_percent}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('fee_percent', value);
|
|
||||||
}}
|
|
||||||
suffix='%'
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('fixedFee')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
type='number'
|
|
||||||
min={0}
|
|
||||||
value={data?.fee_amount}
|
|
||||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
|
||||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
|
||||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('stripe.publicKey')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.public_key}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
public_key: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('stripe.secretKey')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.secret_key}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
secret_key: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Label>{t('stripe.webhookSecret')}</Label>
|
|
||||||
<p className='text-muted-foreground text-xs' />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className='text-right'>
|
|
||||||
<EnhancedInput
|
|
||||||
placeholder={t('inputPlaceholder')}
|
|
||||||
value={data?.config.webhook_secret}
|
|
||||||
onValueBlur={(value) => {
|
|
||||||
updateConfig('config', {
|
|
||||||
...data?.config,
|
|
||||||
webhook_secret: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -57,150 +57,154 @@ export function SubscriptionDetail({
|
|||||||
{t('onlineDevices')}
|
{t('onlineDevices')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value='logs'>
|
<div className='mt-4 max-h-[60dvh] overflow-y-auto'>
|
||||||
<ProTable<API.UserSubscribeLog, Record<string, unknown>>
|
<TabsContent value='logs'>
|
||||||
columns={[
|
<ProTable<API.UserSubscribeLog, Record<string, unknown>>
|
||||||
{
|
columns={[
|
||||||
accessorKey: 'ip',
|
{
|
||||||
header: 'IP',
|
accessorKey: 'ip',
|
||||||
},
|
header: 'IP',
|
||||||
{
|
},
|
||||||
accessorKey: 'user_agent',
|
{
|
||||||
header: t('userAgent'),
|
accessorKey: 'user_agent',
|
||||||
},
|
header: t('userAgent'),
|
||||||
{
|
},
|
||||||
accessorKey: 'token',
|
{
|
||||||
header: t('token'),
|
accessorKey: 'token',
|
||||||
},
|
header: t('token'),
|
||||||
{
|
},
|
||||||
accessorKey: 'created_at',
|
{
|
||||||
header: t('time'),
|
accessorKey: 'created_at',
|
||||||
cell: ({ row }) => formatDate(row.getValue('created_at')),
|
header: t('time'),
|
||||||
},
|
cell: ({ row }) => formatDate(row.getValue('created_at')),
|
||||||
]}
|
},
|
||||||
request={async (pagination) => {
|
]}
|
||||||
const { data } = await getUserSubscribeLogs({
|
request={async (pagination) => {
|
||||||
user_id: userId,
|
const { data } = await getUserSubscribeLogs({
|
||||||
subscribe_id: subscriptionId,
|
user_id: userId,
|
||||||
...pagination,
|
subscribe_id: subscriptionId,
|
||||||
});
|
...pagination,
|
||||||
return {
|
});
|
||||||
list: data.data?.list || [],
|
return {
|
||||||
total: data.data?.total || 0,
|
list: data.data?.list || [],
|
||||||
};
|
total: data.data?.total || 0,
|
||||||
}}
|
};
|
||||||
/>
|
}}
|
||||||
</TabsContent>
|
/>
|
||||||
<TabsContent value='traffic'>
|
</TabsContent>
|
||||||
<ProTable<API.TrafficLog, Record<string, unknown>>
|
<TabsContent value='traffic'>
|
||||||
columns={[
|
<ProTable<API.TrafficLog, Record<string, unknown>>
|
||||||
{
|
columns={[
|
||||||
accessorKey: 'download',
|
{
|
||||||
header: t('download'),
|
accessorKey: 'download',
|
||||||
cell: ({ row }) => <Display type='traffic' value={row.getValue('download')} />,
|
header: t('download'),
|
||||||
},
|
cell: ({ row }) => (
|
||||||
{
|
<Display type='traffic' value={row.getValue('download')} />
|
||||||
accessorKey: 'upload',
|
),
|
||||||
header: t('upload'),
|
},
|
||||||
cell: ({ row }) => <Display type='traffic' value={row.getValue('upload')} />,
|
{
|
||||||
},
|
accessorKey: 'upload',
|
||||||
{
|
header: t('upload'),
|
||||||
accessorKey: 'timestamp',
|
cell: ({ row }) => <Display type='traffic' value={row.getValue('upload')} />,
|
||||||
header: t('time'),
|
},
|
||||||
cell: ({ row }) => formatDate(row.getValue('timestamp')),
|
{
|
||||||
},
|
accessorKey: 'timestamp',
|
||||||
]}
|
header: t('time'),
|
||||||
request={async (pagination) => {
|
cell: ({ row }) => formatDate(row.getValue('timestamp')),
|
||||||
const { data } = await getUserSubscribeTrafficLogs({
|
},
|
||||||
user_id: userId,
|
]}
|
||||||
subscribe_id: subscriptionId,
|
request={async (pagination) => {
|
||||||
...pagination,
|
const { data } = await getUserSubscribeTrafficLogs({
|
||||||
});
|
user_id: userId,
|
||||||
return {
|
subscribe_id: subscriptionId,
|
||||||
list: data.data?.list || [],
|
...pagination,
|
||||||
total: data.data?.total || 0,
|
} as API.GetUserSubscribeTrafficLogsParams);
|
||||||
};
|
return {
|
||||||
}}
|
list: data.data?.list || [],
|
||||||
/>
|
total: data.data?.total || 0,
|
||||||
</TabsContent>
|
};
|
||||||
<TabsContent value='devices'>
|
}}
|
||||||
<ProTable<API.UserDevice, Record<string, unknown>>
|
/>
|
||||||
columns={[
|
</TabsContent>
|
||||||
{
|
<TabsContent value='devices'>
|
||||||
accessorKey: 'enabled',
|
<ProTable<API.UserDevice, Record<string, unknown>>
|
||||||
header: t('enable'),
|
columns={[
|
||||||
cell: ({ row }) => (
|
{
|
||||||
<Switch
|
accessorKey: 'enabled',
|
||||||
checked={row.getValue('enabled')}
|
header: t('enable'),
|
||||||
onChange={(checked) => {
|
cell: ({ row }) => (
|
||||||
console.log('Switch:', checked);
|
<Switch
|
||||||
}}
|
checked={row.getValue('enabled')}
|
||||||
/>
|
onChange={(checked) => {
|
||||||
),
|
console.log('Switch:', checked);
|
||||||
},
|
}}
|
||||||
{
|
/>
|
||||||
accessorKey: 'id',
|
),
|
||||||
header: 'ID',
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: 'id',
|
||||||
accessorKey: 'identifier',
|
header: 'ID',
|
||||||
header: 'IMEI',
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: 'identifier',
|
||||||
accessorKey: 'user_agent',
|
header: 'IMEI',
|
||||||
header: t('userAgent'),
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: 'user_agent',
|
||||||
accessorKey: 'ip',
|
header: t('userAgent'),
|
||||||
header: 'IP',
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: 'ip',
|
||||||
accessorKey: 'online',
|
header: 'IP',
|
||||||
header: t('loginStatus'),
|
},
|
||||||
cell: ({ row }) => (
|
{
|
||||||
<Badge variant={row.getValue('online') ? 'default' : 'destructive'}>
|
accessorKey: 'online',
|
||||||
{row.getValue('online') ? t('online') : t('offline')}
|
header: t('loginStatus'),
|
||||||
</Badge>
|
cell: ({ row }) => (
|
||||||
),
|
<Badge variant={row.getValue('online') ? 'default' : 'destructive'}>
|
||||||
},
|
{row.getValue('online') ? t('online') : t('offline')}
|
||||||
{
|
</Badge>
|
||||||
accessorKey: 'updated_at',
|
),
|
||||||
header: t('lastSeen'),
|
},
|
||||||
cell: ({ row }) => formatDate(row.getValue('updated_at')),
|
{
|
||||||
},
|
accessorKey: 'updated_at',
|
||||||
]}
|
header: t('lastSeen'),
|
||||||
request={async (pagination) => {
|
cell: ({ row }) => formatDate(row.getValue('updated_at')),
|
||||||
const { data } = await getUserSubscribeDevices({
|
},
|
||||||
user_id: userId,
|
]}
|
||||||
subscribe_id: subscriptionId,
|
request={async (pagination) => {
|
||||||
...pagination,
|
const { data } = await getUserSubscribeDevices({
|
||||||
});
|
user_id: userId,
|
||||||
return {
|
subscribe_id: subscriptionId,
|
||||||
list: data.data?.list || [],
|
...pagination,
|
||||||
total: data.data?.total || 0,
|
});
|
||||||
};
|
return {
|
||||||
}}
|
list: data.data?.list || [],
|
||||||
actions={{
|
total: data.data?.total || 0,
|
||||||
render: (row) => {
|
};
|
||||||
if (!row.identifier) return [];
|
}}
|
||||||
return [
|
actions={{
|
||||||
<ConfirmButton
|
render: (row) => {
|
||||||
key='offline'
|
if (!row.identifier) return [];
|
||||||
trigger={<Button variant='destructive'>{t('confirmOffline')}</Button>}
|
return [
|
||||||
title={t('confirmOffline')}
|
<ConfirmButton
|
||||||
description={t('kickOfflineConfirm', { ip: row.ip })}
|
key='offline'
|
||||||
onConfirm={async () => {
|
trigger={<Button variant='destructive'>{t('confirmOffline')}</Button>}
|
||||||
await kickOfflineByUserDevice({ id: row.id });
|
title={t('confirmOffline')}
|
||||||
toast.success(t('kickOfflineSuccess'));
|
description={t('kickOfflineConfirm', { ip: row.ip })}
|
||||||
}}
|
onConfirm={async () => {
|
||||||
cancelText={t('cancel')}
|
await kickOfflineByUserDevice({ id: row.id });
|
||||||
confirmText={t('confirm')}
|
toast.success(t('kickOfflineSuccess'));
|
||||||
/>,
|
}}
|
||||||
];
|
cancelText={t('cancel')}
|
||||||
},
|
confirmText={t('confirm')}
|
||||||
}}
|
/>,
|
||||||
/>
|
];
|
||||||
</TabsContent>
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -63,6 +63,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
|
|||||||
verify_code_interval: 60,
|
verify_code_interval: 60,
|
||||||
},
|
},
|
||||||
oauth_methods: [],
|
oauth_methods: [],
|
||||||
|
web_ad: false,
|
||||||
},
|
},
|
||||||
user: undefined,
|
user: undefined,
|
||||||
setCommon: (common) =>
|
setCommon: (common) =>
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Platba Alipay",
|
"AlipayF2F": "Alipay Osobně",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID aplikace Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Název vlastního produktu",
|
"applyForPayment": "Žádat o platbu",
|
||||||
"invoiceNameDescription": "Bude zobrazeno na faktuře Alipay",
|
"balance": "Zůstatek",
|
||||||
"privateKey": "Soukromý klíč Alipay",
|
"batchDelete": "Hromadné smazání",
|
||||||
"publicKey": "Veřejný klíč Alipay",
|
"cancel": "Zrušit",
|
||||||
"sandbox": "Režim sandbox",
|
"config": "Nastavení",
|
||||||
"sandboxDescription": "Povolte režim sandbox pro testování plateb"
|
"configInfo": "Informace o nastavení",
|
||||||
},
|
"configPlaceholder": "Vyplňte prosím poskytnutou konfiguraci {field}",
|
||||||
|
"confirm": "Potvrdit",
|
||||||
|
"confirmDelete": "Potvrdit smazání",
|
||||||
|
"copy": "Kopírovat",
|
||||||
|
"copySuccess": "Úspěšně zkopírováno",
|
||||||
|
"create": "Přidat platební metodu",
|
||||||
|
"createPayment": "Přidat platební metodu",
|
||||||
|
"createSuccess": "Úspěšně vytvořeno",
|
||||||
|
"delete": "Smazat",
|
||||||
|
"deleteSuccess": "Úspěšně smazáno",
|
||||||
|
"deleteWarning": "Opravdu chcete smazat tuto platební metodu? Tuto akci nelze vrátit zpět.",
|
||||||
|
"description": "Popis",
|
||||||
|
"descriptionPlaceholder": "Zadejte popis platební metody",
|
||||||
|
"domain": "Doména",
|
||||||
|
"domainPlaceholder": "volitelné, např. {example}",
|
||||||
|
"edit": "Upravit",
|
||||||
|
"editPayment": "Upravit platební metodu",
|
||||||
"enable": "Povolit",
|
"enable": "Povolit",
|
||||||
"enableDescription": "Povolit tento způsob platby",
|
"feeAmount": "Pevná částka",
|
||||||
"epay": {
|
|
||||||
"key": "KLÍČ",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Způsob účtování",
|
"feeMode": "Způsob účtování",
|
||||||
"feeModeDescription": "Způsob účtování dodatečných poplatků",
|
|
||||||
"feeModeItems": ["Bez poplatku", "Procento", "Pevná částka", "Obojí výše uvedené"],
|
|
||||||
"feePercent": "Procentuální poplatek (volitelně)",
|
"feePercent": "Procentuální poplatek (volitelně)",
|
||||||
"feePercentDescription": "Přidání poplatku k částce objednávky",
|
|
||||||
"fixedFee": "Pevný poplatek (volitelný)",
|
"fixedFee": "Pevný poplatek (volitelný)",
|
||||||
"fixedFeeDescription": "Přidání pevného poplatku k částce objednávky",
|
"handlingFee": "Poplatek za zpracování",
|
||||||
"iconUrl": "URL ikony (volitelné)",
|
"icon": "Ikona",
|
||||||
"iconUrlDescription": "Používá se pro zobrazení na frontendu",
|
"iconPlaceholder": "Zadejte URL ikony",
|
||||||
"inputPlaceholder": "Prosím zadejte",
|
"mark": "Identifikátor",
|
||||||
"key": "KLÍČ",
|
"markPlaceholder": "Zadejte identifikátor platební metody",
|
||||||
"notifyUrl": "Vlastní oznámení URL (volitelné)",
|
"name": "Název",
|
||||||
"notifyUrlDescription": "Oznámení brány budou odeslána na tuto doménu",
|
"namePlaceholder": "Zadejte název platební metody",
|
||||||
"payForRecommendations": "Zaplatit za doporučení",
|
"nameRequired": "Název je povinný",
|
||||||
"pid": "PID",
|
"noFee": "Žádný poplatek",
|
||||||
"saveSuccess": "Uložení bylo úspěšné",
|
"notify_url": "URL pro oznámení",
|
||||||
"showName": "Zobrazit název",
|
"paymentManagement": "Správa plateb",
|
||||||
"showNameDescription": "Používá se pro zobrazení na frontendu",
|
"percentFee": "Procento",
|
||||||
"stripe": {
|
"platform": "Platforma",
|
||||||
"publicKey": "VEŘEJNÝ KLÍČ",
|
"searchPlaceholder": "Zadejte hledané výrazy",
|
||||||
"secretKey": "TAJNÝ KLÍČ",
|
"selectPlatform": "Vyberte platformu",
|
||||||
"webhookSecret": "PODPIS WEBHOOK KLÍČE"
|
"selectType": "Vyberte typ platby",
|
||||||
},
|
"submit": "Odeslat",
|
||||||
"url": "URL",
|
"type": "Typ",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Úspěšně aktualizováno"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Alipay-Zahlung",
|
"AlipayF2F": "Alipay Gesicht zu Gesicht",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "Alipay Anwendungs-ID",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Benutzerdefinierter Produktname",
|
"applyForPayment": "Zahlung beantragen",
|
||||||
"invoiceNameDescription": "Wird in der Alipay-Rechnung angezeigt",
|
"balance": "Guthaben",
|
||||||
"privateKey": "Alipay Privatschlüssel",
|
"batchDelete": "Massenlöschung",
|
||||||
"publicKey": "Alipay Öffentlicher Schlüssel",
|
"cancel": "Abbrechen",
|
||||||
"sandbox": "Sandbox-Modus",
|
"config": "Konfiguration",
|
||||||
"sandboxDescription": "Aktivieren Sie den Sandbox-Modus, um Zahlungen zu testen"
|
"configInfo": "Konfigurationsinformationen",
|
||||||
},
|
"configPlaceholder": "Bitte füllen Sie die bereitgestellte {field} Konfiguration aus",
|
||||||
|
"confirm": "Bestätigen",
|
||||||
|
"confirmDelete": "Löschen bestätigen",
|
||||||
|
"copy": "Kopieren",
|
||||||
|
"copySuccess": "Erfolgreich kopiert",
|
||||||
|
"create": "Zahlungsmethode hinzufügen",
|
||||||
|
"createPayment": "Zahlungsmethode hinzufügen",
|
||||||
|
"createSuccess": "Erfolgreich erstellt",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"deleteSuccess": "Erfolgreich gelöscht",
|
||||||
|
"deleteWarning": "Sind Sie sicher, dass Sie diese Zahlungsmethode löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||||
|
"description": "Beschreibung",
|
||||||
|
"descriptionPlaceholder": "Geben Sie die Beschreibung der Zahlungsmethode ein",
|
||||||
|
"domain": "Domain",
|
||||||
|
"domainPlaceholder": "optional, z.B. {example}",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"editPayment": "Zahlungsmethode bearbeiten",
|
||||||
"enable": "Aktivieren",
|
"enable": "Aktivieren",
|
||||||
"enableDescription": "Diese Zahlungsmethode aktivieren",
|
"feeAmount": "Fester Betrag",
|
||||||
"epay": {
|
|
||||||
"key": "SCHLÜSSEL",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Gebührenmodus",
|
"feeMode": "Gebührenmodus",
|
||||||
"feeModeDescription": "Gebührenmodus für zusätzliche Gebühren",
|
|
||||||
"feeModeItems": ["Keine Gebühr", "Prozentsatz", "Fester Betrag", "Beides"],
|
|
||||||
"feePercent": "Prozentgebühr (optional)",
|
"feePercent": "Prozentgebühr (optional)",
|
||||||
"feePercentDescription": "Gebühren werden zusätzlich zum Bestellbetrag hinzugefügt",
|
|
||||||
"fixedFee": "Feste Gebühr (optional)",
|
"fixedFee": "Feste Gebühr (optional)",
|
||||||
"fixedFeeDescription": "Feste Gebühr zusätzlich zum Bestellbetrag hinzufügen",
|
"handlingFee": "Bearbeitungsgebühr",
|
||||||
"iconUrl": "Symbol-URL (optional)",
|
"icon": "Symbol",
|
||||||
"iconUrlDescription": "Zur Anzeige im Frontend verwendet",
|
"iconPlaceholder": "Geben Sie die URL des Symbols ein",
|
||||||
"inputPlaceholder": "Bitte eingeben",
|
"mark": "Kennung",
|
||||||
"key": "SCHLÜSSEL",
|
"markPlaceholder": "Geben Sie die Kennung der Zahlungsmethode ein",
|
||||||
"notifyUrl": "Benutzerdefinierte Benachrichtigungs-URL (optional)",
|
"name": "Name",
|
||||||
"notifyUrlDescription": "Gateway-Benachrichtigungen werden an diese Domain gesendet",
|
"namePlaceholder": "Geben Sie den Namen der Zahlungsmethode ein",
|
||||||
"payForRecommendations": "Für Empfehlungen bezahlen",
|
"nameRequired": "Name ist erforderlich",
|
||||||
"pid": "PID",
|
"noFee": "Keine Gebühr",
|
||||||
"saveSuccess": "Erfolgreich gespeichert",
|
"notify_url": "Benachrichtigungs-URL",
|
||||||
"showName": "Anzeigename",
|
"paymentManagement": "Zahlungsverwaltung",
|
||||||
"showNameDescription": "Zur Anzeige im Frontend verwendet",
|
"percentFee": "Prozentsatz",
|
||||||
"stripe": {
|
"platform": "Plattform",
|
||||||
"publicKey": "ÖFFENTLICHER SCHLÜSSEL",
|
"searchPlaceholder": "Suchbegriffe eingeben",
|
||||||
"secretKey": "GEHEIMER SCHLÜSSEL",
|
"selectPlatform": "Plattform auswählen",
|
||||||
"webhookSecret": "WEBHOOK-SCHLÜSSEL-SIGNATUR"
|
"selectType": "Zahlungsart auswählen",
|
||||||
},
|
"submit": "Einreichen",
|
||||||
"url": "URL",
|
"type": "Typ",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Erfolgreich aktualisiert"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Alipay payment",
|
"AlipayF2F": "Alipay Face to Face",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "Alipay App ID",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Custom Product Name",
|
"applyForPayment": "Apply for Payment",
|
||||||
"invoiceNameDescription": "Will be displayed on Alipay bill",
|
"balance": "Balance",
|
||||||
"privateKey": "Alipay Private Key",
|
"batchDelete": "Batch Delete",
|
||||||
"publicKey": "Alipay Public Key",
|
"cancel": "Cancel",
|
||||||
"sandbox": "Sandbox Mode",
|
"config": "Configuration",
|
||||||
"sandboxDescription": "Enable sandbox mode to test payments"
|
"configInfo": "Configuration Information",
|
||||||
},
|
"configPlaceholder": "Please fill in the provided {field} configuration",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"confirmDelete": "Confirm Delete",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copySuccess": "Copied successfully",
|
||||||
|
"create": "Add Payment Method",
|
||||||
|
"createPayment": "Add Payment Method",
|
||||||
|
"createSuccess": "Created successfully",
|
||||||
|
"delete": "Delete",
|
||||||
|
"deleteSuccess": "Deleted successfully",
|
||||||
|
"deleteWarning": "Are you sure you want to delete this payment method? This action cannot be undone.",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "Enter payment method description",
|
||||||
|
"domain": "Domain",
|
||||||
|
"domainPlaceholder": "optional, e.g. {example}",
|
||||||
|
"edit": "Edit",
|
||||||
|
"editPayment": "Edit Payment Method",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
"enableDescription": "Enable this payment method",
|
"feeAmount": "Fixed Amount",
|
||||||
"epay": {
|
|
||||||
"key": "KEY",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Fee Mode",
|
"feeMode": "Fee Mode",
|
||||||
"feeModeDescription": "Fee charging method for additional fees",
|
"feePercent": "Fee Percentage",
|
||||||
"feeModeItems": ["No Fee", "Percentage", "Fixed Amount", "Both"],
|
"fixedFee": "Fixed Amount",
|
||||||
"feePercent": "Percentage Fee (optional)",
|
"handlingFee": "Handling Fee",
|
||||||
"feePercentDescription": "Add fee on top of order amount",
|
"icon": "Icon",
|
||||||
"fixedFee": "Fixed Fee (optional)",
|
"iconPlaceholder": "Enter icon URL",
|
||||||
"fixedFeeDescription": "Add fixed fee on top of order amount",
|
"mark": "Identifier",
|
||||||
"iconUrl": "Icon URL (optional)",
|
"markPlaceholder": "Enter payment method identifier",
|
||||||
"iconUrlDescription": "Used for frontend display",
|
"name": "Name",
|
||||||
"inputPlaceholder": "Please enter",
|
"namePlaceholder": "Enter payment method name",
|
||||||
"key": "KEY",
|
"nameRequired": "Name is required",
|
||||||
"notifyUrl": "Custom Notification Domain (optional)",
|
"noFee": "No Fee",
|
||||||
"notifyUrlDescription": "Gateway notifications will be sent to this domain",
|
"notify_url": "Notify URL",
|
||||||
"payForRecommendations": "Pay for Recommendations",
|
"paymentManagement": "Payment Management",
|
||||||
"pid": "PID",
|
"percentFee": "Percentage",
|
||||||
"saveSuccess": "Save Successful",
|
"platform": "Platform",
|
||||||
"showName": "Display Name",
|
"searchPlaceholder": "Enter search terms",
|
||||||
"showNameDescription": "Used for frontend display",
|
"selectPlatform": "Select Platform",
|
||||||
"stripe": {
|
"selectType": "Select payment type",
|
||||||
"publicKey": "PUBLIC KEY",
|
"submit": "Submit",
|
||||||
"secretKey": "SECRET KEY",
|
"type": "Type",
|
||||||
"webhookSecret": "WEBHOOK Secret Key"
|
"updateSuccess": "Updated successfully"
|
||||||
},
|
|
||||||
"url": "URL",
|
|
||||||
"wechatPay": "WeChat Pay"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Pago con Alipay",
|
"AlipayF2F": "Alipay Cara a Cara",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID de la aplicación de Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Nombre personalizado del producto",
|
"applyForPayment": "Solicitar Pago",
|
||||||
"invoiceNameDescription": "Se mostrará en la factura de Alipay",
|
"balance": "Saldo",
|
||||||
"privateKey": "Clave privada de Alipay",
|
"batchDelete": "Eliminar en Lote",
|
||||||
"publicKey": "Clave pública de Alipay",
|
"cancel": "Cancelar",
|
||||||
"sandbox": "Modo sandbox",
|
"config": "Configuración",
|
||||||
"sandboxDescription": "Habilitar el modo sandbox para probar el pago"
|
"configInfo": "Información de Configuración",
|
||||||
},
|
"configPlaceholder": "Por favor, complete la configuración de {field} proporcionada",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"confirmDelete": "Confirmar Eliminación",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"copySuccess": "Copiado con éxito",
|
||||||
|
"create": "Agregar Método de Pago",
|
||||||
|
"createPayment": "Agregar Método de Pago",
|
||||||
|
"createSuccess": "Creado con éxito",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"deleteSuccess": "Eliminado con éxito",
|
||||||
|
"deleteWarning": "¿Está seguro de que desea eliminar este método de pago? Esta acción no se puede deshacer.",
|
||||||
|
"description": "Descripción",
|
||||||
|
"descriptionPlaceholder": "Ingrese la descripción del método de pago",
|
||||||
|
"domain": "Dominio",
|
||||||
|
"domainPlaceholder": "opcional, p. ej. {example}",
|
||||||
|
"edit": "Editar",
|
||||||
|
"editPayment": "Editar Método de Pago",
|
||||||
"enable": "Habilitar",
|
"enable": "Habilitar",
|
||||||
"enableDescription": "Habilitar este método de pago",
|
"feeAmount": "Monto Fijo",
|
||||||
"epay": {
|
|
||||||
"key": "CLAVE",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Modo de cobro",
|
"feeMode": "Modo de cobro",
|
||||||
"feeModeDescription": "Modo de cobro de tarifas adicionales",
|
|
||||||
"feeModeItems": ["Sin tarifa", "Porcentaje", "Cantidad fija", "Ambos anteriores"],
|
|
||||||
"feePercent": "Porcentaje de tarifa (opcional)",
|
"feePercent": "Porcentaje de tarifa (opcional)",
|
||||||
"feePercentDescription": "Se añade una tarifa sobre el monto del pedido",
|
|
||||||
"fixedFee": "Tarifa fija (opcional)",
|
"fixedFee": "Tarifa fija (opcional)",
|
||||||
"fixedFeeDescription": "Se añade una tarifa fija sobre el monto del pedido",
|
"handlingFee": "Tarifa de manejo",
|
||||||
"iconUrl": "URL del icono (opcional)",
|
"icon": "Ícono",
|
||||||
"iconUrlDescription": "Usado para mostrar en el frontend",
|
"iconPlaceholder": "Ingrese la URL del ícono",
|
||||||
"inputPlaceholder": "Por favor, introduzca",
|
"mark": "Identificador",
|
||||||
"key": "CLAVE",
|
"markPlaceholder": "Ingrese el identificador del método de pago",
|
||||||
"notifyUrl": "Dominio de notificación personalizado (opcional)",
|
"name": "Nombre",
|
||||||
"notifyUrlDescription": "La notificación del gateway se enviará a este dominio",
|
"namePlaceholder": "Ingrese el nombre del método de pago",
|
||||||
"payForRecommendations": "Pagar por recomendaciones",
|
"nameRequired": "El nombre es obligatorio",
|
||||||
"pid": "PID",
|
"noFee": "Sin Tarifa",
|
||||||
"saveSuccess": "Guardado exitosamente",
|
"notify_url": "URL de notificación",
|
||||||
"showName": "Mostrar nombre",
|
"paymentManagement": "Gestión de Pagos",
|
||||||
"showNameDescription": "Usado para mostrar en el frontend",
|
"percentFee": "Porcentaje",
|
||||||
"stripe": {
|
"platform": "Plataforma",
|
||||||
"publicKey": "CLAVE PÚBLICA",
|
"searchPlaceholder": "Ingrese términos de búsqueda",
|
||||||
"secretKey": "CLAVE SECRETA",
|
"selectPlatform": "Seleccionar Plataforma",
|
||||||
"webhookSecret": "FIRMA DE CLAVE WEBHOOK"
|
"selectType": "Seleccionar tipo de pago",
|
||||||
},
|
"submit": "Enviar",
|
||||||
"url": "URL",
|
"type": "Tipo",
|
||||||
"wechatPay": "Pago con WeChat"
|
"updateSuccess": "Actualizado con éxito"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Pago con Alipay",
|
"AlipayF2F": "Alipay Cara a Cara",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID de la aplicación de Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Nombre personalizado del producto",
|
"applyForPayment": "Solicitar Pago",
|
||||||
"invoiceNameDescription": "Se mostrará en la factura de Alipay",
|
"balance": "Saldo",
|
||||||
"privateKey": "Clave privada de Alipay",
|
"batchDelete": "Eliminar en Lote",
|
||||||
"publicKey": "Clave pública de Alipay",
|
"cancel": "Cancelar",
|
||||||
"sandbox": "Modo sandbox",
|
"config": "Configuración",
|
||||||
"sandboxDescription": "Habilitar el modo sandbox para probar el pago"
|
"configInfo": "Información de Configuración",
|
||||||
},
|
"configPlaceholder": "Por favor, complete la configuración de {field} proporcionada",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"confirmDelete": "Confirmar Eliminación",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"copySuccess": "Copiado con éxito",
|
||||||
|
"create": "Agregar Método de Pago",
|
||||||
|
"createPayment": "Agregar Método de Pago",
|
||||||
|
"createSuccess": "Creado con éxito",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"deleteSuccess": "Eliminado con éxito",
|
||||||
|
"deleteWarning": "¿Está seguro de que desea eliminar este método de pago? Esta acción no se puede deshacer.",
|
||||||
|
"description": "Descripción",
|
||||||
|
"descriptionPlaceholder": "Ingresa la descripción del método de pago",
|
||||||
|
"domain": "Dominio",
|
||||||
|
"domainPlaceholder": "opcional, p. ej. {example}",
|
||||||
|
"edit": "Editar",
|
||||||
|
"editPayment": "Editar Método de Pago",
|
||||||
"enable": "Habilitar",
|
"enable": "Habilitar",
|
||||||
"enableDescription": "Habilitar este método de pago",
|
"feeAmount": "Monto Fijo",
|
||||||
"epay": {
|
|
||||||
"key": "CLAVE",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Modo de cobro",
|
"feeMode": "Modo de cobro",
|
||||||
"feeModeDescription": "Modo de cobro de tarifas adicionales",
|
|
||||||
"feeModeItems": ["Sin tarifa", "Porcentaje", "Cantidad fija", "Ambos"],
|
|
||||||
"feePercent": "Porcentaje de tarifa (opcional)",
|
"feePercent": "Porcentaje de tarifa (opcional)",
|
||||||
"feePercentDescription": "Se añade una tarifa sobre el monto del pedido",
|
|
||||||
"fixedFee": "Cuota fija (opcional)",
|
"fixedFee": "Cuota fija (opcional)",
|
||||||
"fixedFeeDescription": "Agregar una tarifa fija sobre el monto del pedido",
|
"handlingFee": "Cargo por manejo",
|
||||||
"iconUrl": "URL del ícono (opcional)",
|
"icon": "Ícono",
|
||||||
"iconUrlDescription": "Usado para mostrar en el frontend",
|
"iconPlaceholder": "Ingrese la URL del ícono",
|
||||||
"inputPlaceholder": "Por favor ingrese",
|
"mark": "Identificador",
|
||||||
"key": "LLAVE",
|
"markPlaceholder": "Ingrese el identificador del método de pago",
|
||||||
"notifyUrl": "Dominio de notificación personalizado (opcional)",
|
"name": "Nombre",
|
||||||
"notifyUrlDescription": "La notificación del gateway se enviará a este dominio",
|
"namePlaceholder": "Ingrese el nombre del método de pago",
|
||||||
"payForRecommendations": "Pagar por recomendaciones",
|
"nameRequired": "El nombre es obligatorio",
|
||||||
"pid": "PID",
|
"noFee": "Sin Tarifa",
|
||||||
"saveSuccess": "Guardado exitoso",
|
"notify_url": "URL de notificación",
|
||||||
"showName": "Mostrar nombre",
|
"paymentManagement": "Gestión de Pagos",
|
||||||
"showNameDescription": "Usado para mostrar en el frontend",
|
"percentFee": "Porcentaje",
|
||||||
"stripe": {
|
"platform": "Plataforma",
|
||||||
"publicKey": "CLAVE PÚBLICA",
|
"searchPlaceholder": "Ingrese términos de búsqueda",
|
||||||
"secretKey": "CLAVE SECRETA",
|
"selectPlatform": "Seleccionar Plataforma",
|
||||||
"webhookSecret": "FIRMA DE SECRETO DE WEBHOOK"
|
"selectType": "Seleccionar tipo de pago",
|
||||||
},
|
"submit": "Enviar",
|
||||||
"url": "URL",
|
"type": "Tipo",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Actualizado con éxito"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "پرداخت علیپی",
|
"AlipayF2F": "علیپی چهره به چهره",
|
||||||
"alipayf2f": {
|
"EPay": "ایپی",
|
||||||
"appId": "شناسه برنامه Alipay",
|
"Stripe": "استرایپ",
|
||||||
"invoiceName": "نام محصول سفارشی",
|
"applyForPayment": "درخواست پرداخت",
|
||||||
"invoiceNameDescription": "در صورتحساب Alipay نمایش داده خواهد شد",
|
"balance": "موجودی",
|
||||||
"privateKey": "کلید خصوصی Alipay",
|
"batchDelete": "حذف دستهای",
|
||||||
"publicKey": "کلید عمومی Alipay",
|
"cancel": "لغو",
|
||||||
"sandbox": "حالت Sandbox",
|
"config": "پیکربندی",
|
||||||
"sandboxDescription": "حالت sandbox را برای آزمایش پرداختها فعال کنید"
|
"configInfo": "اطلاعات پیکربندی",
|
||||||
},
|
"configPlaceholder": "لطفاً پیکربندی {field} ارائه شده را پر کنید",
|
||||||
|
"confirm": "تأیید",
|
||||||
|
"confirmDelete": "تأیید حذف",
|
||||||
|
"copy": "کپی",
|
||||||
|
"copySuccess": "با موفقیت کپی شد",
|
||||||
|
"create": "افزودن روش پرداخت",
|
||||||
|
"createPayment": "افزودن روش پرداخت",
|
||||||
|
"createSuccess": "با موفقیت ایجاد شد",
|
||||||
|
"delete": "حذف",
|
||||||
|
"deleteSuccess": "با موفقیت حذف شد",
|
||||||
|
"deleteWarning": "آیا مطمئن هستید که میخواهید این روش پرداخت را حذف کنید؟ این عمل قابل بازگشت نیست.",
|
||||||
|
"description": "توضیحات",
|
||||||
|
"descriptionPlaceholder": "توضیحات روش پرداخت را وارد کنید",
|
||||||
|
"domain": "دامنه",
|
||||||
|
"domainPlaceholder": "اختیاری، به عنوان مثال {example}",
|
||||||
|
"edit": "ویرایش",
|
||||||
|
"editPayment": "ویرایش روش پرداخت",
|
||||||
"enable": "فعال کردن",
|
"enable": "فعال کردن",
|
||||||
"enableDescription": "این روش پرداخت را فعال کنید",
|
"feeAmount": "مقدار ثابت",
|
||||||
"epay": {
|
|
||||||
"key": "کلید",
|
|
||||||
"pid": "شناسه محصول",
|
|
||||||
"url": "نشانی وب"
|
|
||||||
},
|
|
||||||
"feeMode": "حالت هزینه",
|
"feeMode": "حالت هزینه",
|
||||||
"feeModeDescription": "روش دریافت هزینه برای هزینههای اضافی",
|
|
||||||
"feeModeItems": ["بدون هزینه", "درصدی", "مبلغ ثابت", "هر دو"],
|
|
||||||
"feePercent": "درصد کارمزد (اختیاری)",
|
"feePercent": "درصد کارمزد (اختیاری)",
|
||||||
"feePercentDescription": "افزودن کارمزد به مبلغ سفارش",
|
|
||||||
"fixedFee": "هزینه ثابت (اختیاری)",
|
"fixedFee": "هزینه ثابت (اختیاری)",
|
||||||
"fixedFeeDescription": "افزودن هزینه ثابت به مبلغ سفارش",
|
"handlingFee": "هزینه خدمات",
|
||||||
"iconUrl": "آدرس آیکون (اختیاری)",
|
"icon": "آیکون",
|
||||||
"iconUrlDescription": "برای نمایش در رابط کاربری استفاده میشود",
|
"iconPlaceholder": "آدرس URL آیکون را وارد کنید",
|
||||||
"inputPlaceholder": "لطفاً وارد کنید",
|
"mark": "شناسه",
|
||||||
"key": "کلید",
|
"markPlaceholder": "شناسه روش پرداخت را وارد کنید",
|
||||||
"notifyUrl": "دامنه اعلان سفارشی (اختیاری)",
|
"name": "نام",
|
||||||
"notifyUrlDescription": "اعلانهای درگاه به این دامنه ارسال خواهند شد",
|
"namePlaceholder": "نام روش پرداخت را وارد کنید",
|
||||||
"payForRecommendations": "پرداخت برای توصیهها",
|
"nameRequired": "نام الزامی است",
|
||||||
"pid": "شناسه فرآیند",
|
"noFee": "بدون هزینه",
|
||||||
"saveSuccess": "ذخیره با موفقیت انجام شد",
|
"notify_url": "آدرس اطلاعرسانی",
|
||||||
"showName": "نمایش نام",
|
"paymentManagement": "مدیریت پرداخت",
|
||||||
"showNameDescription": "برای نمایش در قسمت جلویی استفاده میشود",
|
"percentFee": "درصد",
|
||||||
"stripe": {
|
"platform": "پلتفرم",
|
||||||
"publicKey": "کلید عمومی",
|
"searchPlaceholder": "عبارات جستجو را وارد کنید",
|
||||||
"secretKey": "کلید مخفی",
|
"selectPlatform": "انتخاب پلتفرم",
|
||||||
"webhookSecret": "کلید مخفی وبهوک"
|
"selectType": "انتخاب نوع پرداخت",
|
||||||
},
|
"submit": "ارسال",
|
||||||
"url": "آدرس اینترنتی",
|
"type": "نوع",
|
||||||
"wechatPay": "ویچت پی"
|
"updateSuccess": "با موفقیت بهروزرسانی شد"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Alipay-maksu",
|
"AlipayF2F": "Alipay Kasvokkain",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "Alipay-sovelluksen ID",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Mukautettu tuotteen nimi",
|
"applyForPayment": "Hae maksua",
|
||||||
"invoiceNameDescription": "Näkyy Alipay-laskussa",
|
"balance": "Saldo",
|
||||||
"privateKey": "Alipay-yksityinen avain",
|
"batchDelete": "Ryhmän poisto",
|
||||||
"publicKey": "Alipay-julkinen avain",
|
"cancel": "Peruuta",
|
||||||
"sandbox": "Hiekkalaatikkotila",
|
"config": "Konfiguraatio",
|
||||||
"sandboxDescription": "Ota hiekkalaatikkotila käyttöön maksujen testaamiseksi"
|
"configInfo": "Konfiguraatio tiedot",
|
||||||
},
|
"configPlaceholder": "Täytä annettu {field} konfiguraatio",
|
||||||
|
"confirm": "Vahvista",
|
||||||
|
"confirmDelete": "Vahvista poisto",
|
||||||
|
"copy": "Kopioi",
|
||||||
|
"copySuccess": "Kopiointi onnistui",
|
||||||
|
"create": "Lisää maksutapa",
|
||||||
|
"createPayment": "Lisää maksutapa",
|
||||||
|
"createSuccess": "Luonti onnistui",
|
||||||
|
"delete": "Poista",
|
||||||
|
"deleteSuccess": "Poisto onnistui",
|
||||||
|
"deleteWarning": "Oletko varma, että haluat poistaa tämän maksutavan? Tätä toimintoa ei voi peruuttaa.",
|
||||||
|
"description": "Kuvaus",
|
||||||
|
"descriptionPlaceholder": "Syötä maksutavan kuvaus",
|
||||||
|
"domain": "Verkkotunnus",
|
||||||
|
"domainPlaceholder": "valinnainen, esim. {esimerkki}",
|
||||||
|
"edit": "Muokkaa",
|
||||||
|
"editPayment": "Muokkaa maksutapaa",
|
||||||
"enable": "Ota käyttöön",
|
"enable": "Ota käyttöön",
|
||||||
"enableDescription": "Ota tämä maksutapa käyttöön",
|
"feeAmount": "Kiinteä summa",
|
||||||
"epay": {
|
|
||||||
"key": "AVAIN",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Maksutapa",
|
"feeMode": "Maksutapa",
|
||||||
"feeModeDescription": "Lisämaksun veloitustapa",
|
|
||||||
"feeModeItems": ["Ei käsittelymaksua", "Prosenttiosuus", "Kiinteä summa", "Molemmat yllä olevat"],
|
|
||||||
"feePercent": "Prosenttiosuusmaksu (valinnainen)",
|
"feePercent": "Prosenttiosuusmaksu (valinnainen)",
|
||||||
"feePercentDescription": "Lisätään maksu tilauksen summan päälle",
|
|
||||||
"fixedFee": "Kiinteä maksu (valinnainen)",
|
"fixedFee": "Kiinteä maksu (valinnainen)",
|
||||||
"fixedFeeDescription": "Lisää kiinteä maksu tilauksen summan päälle",
|
"handlingFee": "Käsittelymaksu",
|
||||||
"iconUrl": "Kuvakkeen URL (valinnainen)",
|
"icon": "Ikoni",
|
||||||
"iconUrlDescription": "Käytetään etunäytössä",
|
"iconPlaceholder": "Syötä ikonin URL-osoite",
|
||||||
"inputPlaceholder": "Kirjoita tähän",
|
"mark": "Tunniste",
|
||||||
"key": "AVAIN",
|
"markPlaceholder": "Syötä maksutavan tunniste",
|
||||||
"notifyUrl": "Mukautettu ilmoitusalue (valinnainen)",
|
"name": "Nimi",
|
||||||
"notifyUrlDescription": "Yhdyskäytävän ilmoitus lähetetään tähän verkkotunnukseen",
|
"namePlaceholder": "Syötä maksutavan nimi",
|
||||||
"payForRecommendations": "Maksa suosituksista",
|
"nameRequired": "Nimi on pakollinen",
|
||||||
"pid": "PID",
|
"noFee": "Ei maksua",
|
||||||
"saveSuccess": "Tallennus onnistui",
|
"notify_url": "Ilmoitus-URL",
|
||||||
"showName": "Näytä nimi",
|
"paymentManagement": "Maksujen hallinta",
|
||||||
"showNameDescription": "Näytetään käyttöliittymässä",
|
"percentFee": "Prosentti",
|
||||||
"stripe": {
|
"platform": "Alusta",
|
||||||
"publicKey": "JULKINEN AVAIN",
|
"searchPlaceholder": "Syötä hakusanat",
|
||||||
"secretKey": "SALAINEN AVAIN",
|
"selectPlatform": "Valitse alusta",
|
||||||
"webhookSecret": "WEBHOOK-SALAINEN ALLEKIRJOITUS"
|
"selectType": "Valitse maksutyyppi",
|
||||||
},
|
"submit": "Lähetä",
|
||||||
"url": "URL",
|
"type": "Tyyppi",
|
||||||
"wechatPay": "WeChat-maksu"
|
"updateSuccess": "Päivitys onnistui"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Paiement par Alipay",
|
"AlipayF2F": "Alipay Face à Face",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID de l'application Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Nom personnalisé du produit",
|
"applyForPayment": "Demander un paiement",
|
||||||
"invoiceNameDescription": "S'affichera sur la facture Alipay",
|
"balance": "Solde",
|
||||||
"privateKey": "Clé privée Alipay",
|
"batchDelete": "Suppression par lot",
|
||||||
"publicKey": "Clé publique Alipay",
|
"cancel": "Annuler",
|
||||||
"sandbox": "Mode bac à sable",
|
"config": "Configuration",
|
||||||
"sandboxDescription": "Activez le mode bac à sable pour tester le paiement"
|
"configInfo": "Informations de configuration",
|
||||||
},
|
"configPlaceholder": "Veuillez remplir la configuration {field} fournie",
|
||||||
|
"confirm": "Confirmer",
|
||||||
|
"confirmDelete": "Confirmer la suppression",
|
||||||
|
"copy": "Copier",
|
||||||
|
"copySuccess": "Copié avec succès",
|
||||||
|
"create": "Ajouter un mode de paiement",
|
||||||
|
"createPayment": "Ajouter un mode de paiement",
|
||||||
|
"createSuccess": "Créé avec succès",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
"deleteSuccess": "Supprimé avec succès",
|
||||||
|
"deleteWarning": "Êtes-vous sûr de vouloir supprimer ce mode de paiement ? Cette action ne peut pas être annulée.",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "Entrez la description du mode de paiement",
|
||||||
|
"domain": "Domaine",
|
||||||
|
"domainPlaceholder": "facultatif, par exemple {exemple}",
|
||||||
|
"edit": "Modifier",
|
||||||
|
"editPayment": "Modifier le mode de paiement",
|
||||||
"enable": "Activer",
|
"enable": "Activer",
|
||||||
"enableDescription": "Activer ce mode de paiement",
|
"feeAmount": "Montant fixe",
|
||||||
"epay": {
|
|
||||||
"key": "CLÉ",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Mode de facturation",
|
"feeMode": "Mode de facturation",
|
||||||
"feeModeDescription": "Mode de facturation des frais supplémentaires",
|
|
||||||
"feeModeItems": ["Pas de frais", "Pourcentage", "Montant fixe", "Les deux ci-dessus"],
|
|
||||||
"feePercent": "Pourcentage de frais (optionnel)",
|
"feePercent": "Pourcentage de frais (optionnel)",
|
||||||
"feePercentDescription": "Ajouter des frais au montant de la commande",
|
|
||||||
"fixedFee": "Frais fixes (optionnel)",
|
"fixedFee": "Frais fixes (optionnel)",
|
||||||
"fixedFeeDescription": "Ajouter des frais fixes au montant de la commande",
|
"handlingFee": "Frais de traitement",
|
||||||
"iconUrl": "URL de l'icône (facultatif)",
|
"icon": "Icône",
|
||||||
"iconUrlDescription": "Utilisé pour l'affichage frontal",
|
"iconPlaceholder": "Entrez l'URL de l'icône",
|
||||||
"inputPlaceholder": "Veuillez entrer",
|
"mark": "Identifiant",
|
||||||
"key": "CLÉ",
|
"markPlaceholder": "Entrez l'identifiant du mode de paiement",
|
||||||
"notifyUrl": "Domaine de notification personnalisé (facultatif)",
|
"name": "Nom",
|
||||||
"notifyUrlDescription": "Les notifications de la passerelle seront envoyées à ce domaine",
|
"namePlaceholder": "Entrez le nom du mode de paiement",
|
||||||
"payForRecommendations": "Payer pour les recommandations",
|
"nameRequired": "Le nom est requis",
|
||||||
"pid": "PID",
|
"noFee": "Pas de frais",
|
||||||
"saveSuccess": "Enregistrement réussi",
|
"notify_url": "URL de notification",
|
||||||
"showName": "Afficher le nom",
|
"paymentManagement": "Gestion des paiements",
|
||||||
"showNameDescription": "Utilisé pour l'affichage frontal",
|
"percentFee": "Pourcentage",
|
||||||
"stripe": {
|
"platform": "Plateforme",
|
||||||
"publicKey": "CLÉ PUBLIQUE",
|
"searchPlaceholder": "Entrez des termes de recherche",
|
||||||
"secretKey": "CLÉ SECRÈTE",
|
"selectPlatform": "Sélectionner la plateforme",
|
||||||
"webhookSecret": "SIGNATURE DE CLÉ WEBHOOK"
|
"selectType": "Sélectionner le type de paiement",
|
||||||
},
|
"submit": "Soumettre",
|
||||||
"url": "URL",
|
"type": "Type",
|
||||||
"wechatPay": "Paiement WeChat"
|
"updateSuccess": "Mis à jour avec succès"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "अलीपे भुगतान",
|
"AlipayF2F": "अलीपे फेस टू फेस",
|
||||||
"alipayf2f": {
|
"EPay": "ईपे",
|
||||||
"appId": "अलीपे एप्लिकेशन आईडी",
|
"Stripe": "स्ट्राइप",
|
||||||
"invoiceName": "कस्टम उत्पाद नाम",
|
"applyForPayment": "भुगतान के लिए आवेदन करें",
|
||||||
"invoiceNameDescription": "अलीपे बिल में प्रदर्शित होगा",
|
"balance": "शेष",
|
||||||
"privateKey": "अलीपे निजी कुंजी",
|
"batchDelete": "बैच हटाएं",
|
||||||
"publicKey": "अलीपे सार्वजनिक कुंजी",
|
"cancel": "रद्द करें",
|
||||||
"sandbox": "सैंडबॉक्स मोड",
|
"config": "कॉन्फ़िगरेशन",
|
||||||
"sandboxDescription": "भुगतान का परीक्षण करने के लिए सैंडबॉक्स मोड सक्षम करें"
|
"configInfo": "कॉन्फ़िगरेशन जानकारी",
|
||||||
},
|
"configPlaceholder": "कृपया प्रदान किए गए {field} कॉन्फ़िगरेशन को भरें",
|
||||||
|
"confirm": "पुष्टि करें",
|
||||||
|
"confirmDelete": "हटाने की पुष्टि करें",
|
||||||
|
"copy": "कॉपी करें",
|
||||||
|
"copySuccess": "सफलता से कॉपी किया गया",
|
||||||
|
"create": "भुगतान विधि जोड़ें",
|
||||||
|
"createPayment": "भुगतान विधि जोड़ें",
|
||||||
|
"createSuccess": "सफलता से बनाया गया",
|
||||||
|
"delete": "हटाएं",
|
||||||
|
"deleteSuccess": "सफलता से हटाया गया",
|
||||||
|
"deleteWarning": "क्या आप वास्तव में इस भुगतान विधि को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।",
|
||||||
|
"description": "विवरण",
|
||||||
|
"descriptionPlaceholder": "भुगतान विधि का विवरण दर्ज करें",
|
||||||
|
"domain": "डोमेन",
|
||||||
|
"domainPlaceholder": "वैकल्पिक, जैसे कि {example}",
|
||||||
|
"edit": "संपादित करें",
|
||||||
|
"editPayment": "भुगतान विधि संपादित करें",
|
||||||
"enable": "सक्षम करें",
|
"enable": "सक्षम करें",
|
||||||
"enableDescription": "इस भुगतान विधि को सक्षम करें",
|
"feeAmount": "निश्चित राशि",
|
||||||
"epay": {
|
|
||||||
"key": "कुंजी",
|
|
||||||
"pid": "पीआईडी",
|
|
||||||
"url": "यूआरएल"
|
|
||||||
},
|
|
||||||
"feeMode": "शुल्क मोड",
|
"feeMode": "शुल्क मोड",
|
||||||
"feeModeDescription": "अतिरिक्त शुल्क की वसूली का तरीका",
|
|
||||||
"feeModeItems": ["कोई शुल्क नहीं", "प्रतिशत", "निश्चित राशि", "उपरोक्त दोनों"],
|
|
||||||
"feePercent": "प्रतिशत शुल्क (वैकल्पिक)",
|
"feePercent": "प्रतिशत शुल्क (वैकल्पिक)",
|
||||||
"feePercentDescription": "ऑर्डर राशि के ऊपर शुल्क जोड़ा जाएगा",
|
|
||||||
"fixedFee": "निश्चित शुल्क (वैकल्पिक)",
|
"fixedFee": "निश्चित शुल्क (वैकल्पिक)",
|
||||||
"fixedFeeDescription": "ऑर्डर राशि के ऊपर एक निश्चित शुल्क जोड़ा जाता है",
|
"handlingFee": "हैंडलिंग शुल्क",
|
||||||
"iconUrl": "आइकन URL (वैकल्पिक)",
|
"icon": "चिन्ह",
|
||||||
"iconUrlDescription": "फ्रंटएंड प्रदर्शन के लिए उपयोग किया जाता है",
|
"iconPlaceholder": "चिन्ह URL दर्ज करें",
|
||||||
"inputPlaceholder": "कृपया दर्ज करें",
|
"mark": "पहचानकर्ता",
|
||||||
"key": "कुंजी",
|
"markPlaceholder": "भुगतान विधि पहचानकर्ता दर्ज करें",
|
||||||
"notifyUrl": "कस्टम अधिसूचना डोमेन (वैकल्पिक)",
|
"name": "नाम",
|
||||||
"notifyUrlDescription": "गेटवे सूचना इस डोमेन पर भेजी जाएगी",
|
"namePlaceholder": "भुगतान विधि का नाम दर्ज करें",
|
||||||
"payForRecommendations": "सिफारिशों के लिए भुगतान करें",
|
"nameRequired": "नाम आवश्यक है",
|
||||||
"pid": "पीआईडी",
|
"noFee": "कोई शुल्क नहीं",
|
||||||
"saveSuccess": "सहेजा गया",
|
"notify_url": "सूचना URL",
|
||||||
"showName": "नाम दिखाएं",
|
"paymentManagement": "भुगतान प्रबंधन",
|
||||||
"showNameDescription": "फ्रंटएंड प्रदर्शन के लिए उपयोग किया जाता है",
|
"percentFee": "प्रतिशत",
|
||||||
"stripe": {
|
"platform": "प्लेटफ़ॉर्म",
|
||||||
"publicKey": "सार्वजनिक कुंजी",
|
"searchPlaceholder": "खोज शब्द दर्ज करें",
|
||||||
"secretKey": "गुप्त कुंजी",
|
"selectPlatform": "प्लेटफ़ॉर्म चुनें",
|
||||||
"webhookSecret": "वेबहुक कुंजी हस्ताक्षर"
|
"selectType": "भुगतान प्रकार चुनें",
|
||||||
},
|
"submit": "जमा करें",
|
||||||
"url": "यूआरएल",
|
"type": "प्रकार",
|
||||||
"wechatPay": "वीचैट पे"
|
"updateSuccess": "सफलता से अपडेट किया गया"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Alipay fizetés",
|
"AlipayF2F": "Alipay Személyesen",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "Alipay alkalmazásazonosító",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Egyedi terméknév",
|
"applyForPayment": "Kérjen kifizetést",
|
||||||
"invoiceNameDescription": "A számlán az Alipay-ben fog megjelenni",
|
"balance": "Egyenleg",
|
||||||
"privateKey": "Alipay privát kulcs",
|
"batchDelete": "Tömeges törlés",
|
||||||
"publicKey": "Alipay nyilvános kulcs",
|
"cancel": "Mégse",
|
||||||
"sandbox": "Tesztkörnyezet",
|
"config": "Beállítások",
|
||||||
"sandboxDescription": "Tesztkörnyezet engedélyezése a fizetések teszteléséhez"
|
"configInfo": "Beállítási információk",
|
||||||
},
|
"configPlaceholder": "Kérjük, töltse ki a megadott {field} beállítást",
|
||||||
|
"confirm": "Megerősít",
|
||||||
|
"confirmDelete": "Törlés megerősítése",
|
||||||
|
"copy": "Másolás",
|
||||||
|
"copySuccess": "Sikeresen másolva",
|
||||||
|
"create": "Fizetési mód hozzáadása",
|
||||||
|
"createPayment": "Fizetési mód hozzáadása",
|
||||||
|
"createSuccess": "Sikeresen létrehozva",
|
||||||
|
"delete": "Törlés",
|
||||||
|
"deleteSuccess": "Sikeresen törölve",
|
||||||
|
"deleteWarning": "Biztosan törölni szeretné ezt a fizetési módot? Ez a művelet nem vonható vissza.",
|
||||||
|
"description": "Leírás",
|
||||||
|
"descriptionPlaceholder": "Írja be a fizetési mód leírását",
|
||||||
|
"domain": "Domain",
|
||||||
|
"domainPlaceholder": "opcionális, pl. {példa}",
|
||||||
|
"edit": "Szerkesztés",
|
||||||
|
"editPayment": "Fizetési mód szerkesztése",
|
||||||
"enable": "Engedélyezés",
|
"enable": "Engedélyezés",
|
||||||
"enableDescription": "Engedélyezze ezt a fizetési módot",
|
"feeAmount": "Fix összeg",
|
||||||
"epay": {
|
|
||||||
"key": "KULCS",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Díjfizetési mód",
|
"feeMode": "Díjfizetési mód",
|
||||||
"feeModeDescription": "További díjak felszámításának módja",
|
|
||||||
"feeModeItems": ["Nincs díj", "Százalékos", "Fix összeg", "Mindkettő"],
|
|
||||||
"feePercent": "Díjszázalék (opcionális)",
|
"feePercent": "Díjszázalék (opcionális)",
|
||||||
"feePercentDescription": "Díj hozzáadása a rendelés összegéhez",
|
|
||||||
"fixedFee": "Fix díj (opcionális)",
|
"fixedFee": "Fix díj (opcionális)",
|
||||||
"fixedFeeDescription": "Rögzített díj hozzáadása a rendelés összegéhez",
|
"handlingFee": "Kezelési díj",
|
||||||
"iconUrl": "Ikon URL (opcionális)",
|
"icon": "Ikon",
|
||||||
"iconUrlDescription": "Előnézeti kép megjelenítéséhez",
|
"iconPlaceholder": "Adja meg az ikon URL-jét",
|
||||||
"inputPlaceholder": "Kérjük, írja be",
|
"mark": "Azonosító",
|
||||||
"key": "KULCS",
|
"markPlaceholder": "Adja meg a fizetési mód azonosítóját",
|
||||||
"notifyUrl": "Egyéni értesítési domain (opcionális)",
|
"name": "Név",
|
||||||
"notifyUrlDescription": "A kapu értesítéseket erre a tartományra küldi",
|
"namePlaceholder": "Adja meg a fizetési mód nevét",
|
||||||
"payForRecommendations": "Fizetés ajánlásokért",
|
"nameRequired": "A név megadása kötelező",
|
||||||
"pid": "PID",
|
"noFee": "Nincs díj",
|
||||||
"saveSuccess": "Sikeres mentés",
|
"notify_url": "Értesítési URL",
|
||||||
"showName": "Megjelenítési név",
|
"paymentManagement": "Fizetéskezelés",
|
||||||
"showNameDescription": "Előnézet megjelenítésére használatos",
|
"percentFee": "Százalékos díj",
|
||||||
"stripe": {
|
"platform": "Platform",
|
||||||
"publicKey": "NYILVÁNOS KULCS",
|
"searchPlaceholder": "Adja meg a keresési kifejezéseket",
|
||||||
"secretKey": "TITKOS KULCS",
|
"selectPlatform": "Válassza ki a platformot",
|
||||||
"webhookSecret": "WEBHOOK TITKOS ALÁÍRÁS"
|
"selectType": "Válassza ki a fizetési típust",
|
||||||
},
|
"submit": "Beküldés",
|
||||||
"url": "URL",
|
"type": "Típus",
|
||||||
"wechatPay": "WeChat fizetés"
|
"updateSuccess": "Sikeresen frissítve"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "アリペイ決済",
|
"AlipayF2F": "アリペイ対面",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "支付宝アプリID",
|
"Stripe": "ストライプ",
|
||||||
"invoiceName": "カスタム製品名",
|
"applyForPayment": "支払いを申請する",
|
||||||
"invoiceNameDescription": "支付宝の請求書に表示されます",
|
"balance": "残高",
|
||||||
"privateKey": "支付宝プライベートキー",
|
"batchDelete": "一括削除",
|
||||||
"publicKey": "支付宝パブリックキー",
|
"cancel": "キャンセル",
|
||||||
"sandbox": "サンドボックスモード",
|
"config": "設定",
|
||||||
"sandboxDescription": "支払いをテストするためにサンドボックスモードを有効にします"
|
"configInfo": "設定情報",
|
||||||
},
|
"configPlaceholder": "提供された{field}の設定を入力してください",
|
||||||
|
"confirm": "確認",
|
||||||
|
"confirmDelete": "削除を確認",
|
||||||
|
"copy": "コピー",
|
||||||
|
"copySuccess": "コピー成功",
|
||||||
|
"create": "支払い方法を追加",
|
||||||
|
"createPayment": "支払い方法を追加",
|
||||||
|
"createSuccess": "作成成功",
|
||||||
|
"delete": "削除",
|
||||||
|
"deleteSuccess": "削除成功",
|
||||||
|
"deleteWarning": "この支払い方法を削除してもよろしいですか?この操作は元に戻せません。",
|
||||||
|
"description": "説明",
|
||||||
|
"descriptionPlaceholder": "支払い方法の説明を入力してください",
|
||||||
|
"domain": "ドメイン",
|
||||||
|
"domainPlaceholder": "任意、例:{example}",
|
||||||
|
"edit": "編集",
|
||||||
|
"editPayment": "支払い方法を編集",
|
||||||
"enable": "有効",
|
"enable": "有効",
|
||||||
"enableDescription": "この支払い方法を有効にする",
|
"feeAmount": "固定額",
|
||||||
"epay": {
|
|
||||||
"key": "キー",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "料金方式",
|
"feeMode": "料金方式",
|
||||||
"feeModeDescription": "追加手数料の課金方式",
|
|
||||||
"feeModeItems": ["手数料なし", "パーセンテージ", "固定金額", "上記の2つ"],
|
|
||||||
"feePercent": "パーセンテージ手数料(オプション)",
|
"feePercent": "パーセンテージ手数料(オプション)",
|
||||||
"feePercentDescription": "注文金額に追加される手数料",
|
|
||||||
"fixedFee": "固定料金(オプション)",
|
"fixedFee": "固定料金(オプション)",
|
||||||
"fixedFeeDescription": "注文金額に固定料金を追加する",
|
"handlingFee": "手数料",
|
||||||
"iconUrl": "アイコンURL(任意)",
|
"icon": "アイコン",
|
||||||
"iconUrlDescription": "フロントエンド表示用",
|
"iconPlaceholder": "アイコンのURLを入力してください",
|
||||||
"inputPlaceholder": "入力してください",
|
"mark": "識別子",
|
||||||
"key": "キー",
|
"markPlaceholder": "支払い方法の識別子を入力してください",
|
||||||
"notifyUrl": "カスタム通知ドメイン(オプション)",
|
"name": "名前",
|
||||||
"notifyUrlDescription": "ゲートウェイ通知はこのドメインに送信されます",
|
"namePlaceholder": "支払い方法の名前を入力してください",
|
||||||
"payForRecommendations": "おすすめの支払い",
|
"nameRequired": "名前は必須です",
|
||||||
"pid": "PID",
|
"noFee": "手数料なし",
|
||||||
"saveSuccess": "保存が成功しました",
|
"notify_url": "通知URL",
|
||||||
"showName": "表示名",
|
"paymentManagement": "支払い管理",
|
||||||
"showNameDescription": "フロントエンド表示用",
|
"percentFee": "パーセンテージ",
|
||||||
"stripe": {
|
"platform": "プラットフォーム",
|
||||||
"publicKey": "パブリックキー",
|
"searchPlaceholder": "検索用語を入力してください",
|
||||||
"secretKey": "シークレットキー",
|
"selectPlatform": "プラットフォームを選択",
|
||||||
"webhookSecret": "WEBHOOK シークレットサイン"
|
"selectType": "支払いタイプを選択",
|
||||||
},
|
"submit": "送信",
|
||||||
"url": "URL",
|
"type": "タイプ",
|
||||||
"wechatPay": "WeChatペイ"
|
"updateSuccess": "更新成功"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "알리페이 결제",
|
"AlipayF2F": "알리페이 대면 결제",
|
||||||
"alipayf2f": {
|
"EPay": "이페이",
|
||||||
"appId": "알리페이 애플리케이션 ID",
|
"Stripe": "스트라이프",
|
||||||
"invoiceName": "사용자 정의 제품 이름",
|
"applyForPayment": "결제 신청",
|
||||||
"invoiceNameDescription": "알리페이 청구서에 표시됩니다",
|
"balance": "잔액",
|
||||||
"privateKey": "알리페이 개인 키",
|
"batchDelete": "일괄 삭제",
|
||||||
"publicKey": "알리페이 공개 키",
|
"cancel": "취소",
|
||||||
"sandbox": "샌드박스 모드",
|
"config": "설정",
|
||||||
"sandboxDescription": "결제를 테스트하기 위해 샌드박스 모드를 활성화합니다"
|
"configInfo": "설정 정보",
|
||||||
},
|
"configPlaceholder": "{field} 설정을 입력해 주세요",
|
||||||
|
"confirm": "확인",
|
||||||
|
"confirmDelete": "삭제 확인",
|
||||||
|
"copy": "복사",
|
||||||
|
"copySuccess": "복사 성공",
|
||||||
|
"create": "결제 수단 추가",
|
||||||
|
"createPayment": "결제 수단 추가",
|
||||||
|
"createSuccess": "생성 성공",
|
||||||
|
"delete": "삭제",
|
||||||
|
"deleteSuccess": "삭제 성공",
|
||||||
|
"deleteWarning": "이 결제 수단을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
|
||||||
|
"description": "설명",
|
||||||
|
"descriptionPlaceholder": "결제 수단 설명을 입력하세요",
|
||||||
|
"domain": "도메인",
|
||||||
|
"domainPlaceholder": "선택 사항, 예: {example}",
|
||||||
|
"edit": "편집",
|
||||||
|
"editPayment": "결제 수단 편집",
|
||||||
"enable": "사용",
|
"enable": "사용",
|
||||||
"enableDescription": "이 결제 수단을 활성화합니다",
|
"feeAmount": "고정 금액",
|
||||||
"epay": {
|
|
||||||
"key": "키",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "요금 방식",
|
"feeMode": "요금 방식",
|
||||||
"feeModeDescription": "추가 수수료의 부과 방식",
|
|
||||||
"feeModeItems": ["수수료 없음", "백분율", "고정 금액", "이상 두 가지"],
|
|
||||||
"feePercent": "수수료 비율(선택 사항)",
|
"feePercent": "수수료 비율(선택 사항)",
|
||||||
"feePercentDescription": "주문 금액에 추가 요금 부과",
|
|
||||||
"fixedFee": "고정 수수료(선택 사항)",
|
"fixedFee": "고정 수수료(선택 사항)",
|
||||||
"fixedFeeDescription": "주문 금액에 고정 요금 추가",
|
"handlingFee": "수수료",
|
||||||
"iconUrl": "아이콘 URL(선택 사항)",
|
"icon": "아이콘",
|
||||||
"iconUrlDescription": "프론트엔드 표시용",
|
"iconPlaceholder": "아이콘 URL 입력",
|
||||||
"inputPlaceholder": "입력하세요",
|
"mark": "식별자",
|
||||||
"key": "키",
|
"markPlaceholder": "결제 수단 식별자 입력",
|
||||||
"notifyUrl": "사용자 정의 알림 도메인 (선택 사항)",
|
"name": "이름",
|
||||||
"notifyUrlDescription": "게이트웨이 알림이 이 도메인으로 전송됩니다",
|
"namePlaceholder": "결제 수단 이름 입력",
|
||||||
"payForRecommendations": "추천에 대한 지불",
|
"nameRequired": "이름은 필수입니다",
|
||||||
"pid": "PID",
|
"noFee": "수수료 없음",
|
||||||
"saveSuccess": "저장 성공",
|
"notify_url": "알림 URL",
|
||||||
"showName": "이름 표시",
|
"paymentManagement": "결제 관리",
|
||||||
"showNameDescription": "프론트엔드 표시용",
|
"percentFee": "백분율",
|
||||||
"stripe": {
|
"platform": "플랫폼",
|
||||||
"publicKey": "공개 키",
|
"searchPlaceholder": "검색어 입력",
|
||||||
"secretKey": "비밀 키",
|
"selectPlatform": "플랫폼 선택",
|
||||||
"webhookSecret": "웹훅 비밀 서명"
|
"selectType": "결제 유형 선택",
|
||||||
},
|
"submit": "제출",
|
||||||
"url": "URL",
|
"type": "유형",
|
||||||
"wechatPay": "위챗페이"
|
"updateSuccess": "업데이트 성공"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Betaling med Alipay",
|
"AlipayF2F": "Alipay Ansikt-til-Ansikt",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "Alipay-applikasjons-ID",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Tilpasset produktnavn",
|
"applyForPayment": "Søk om betaling",
|
||||||
"invoiceNameDescription": "Vil vises på Alipay-fakturaen",
|
"balance": "Saldo",
|
||||||
"privateKey": "Alipay privat nøkkel",
|
"batchDelete": "Slett i batch",
|
||||||
"publicKey": "Alipay offentlig nøkkel",
|
"cancel": "Avbryt",
|
||||||
"sandbox": "Sandkassemodus",
|
"config": "Konfigurasjon",
|
||||||
"sandboxDescription": "Aktiver sandkassemodus for å teste betaling"
|
"configInfo": "Konfigurasjonsinformasjon",
|
||||||
},
|
"configPlaceholder": "Vennligst fyll inn den angitte {field} konfigurasjonen",
|
||||||
|
"confirm": "Bekreft",
|
||||||
|
"confirmDelete": "Bekreft sletting",
|
||||||
|
"copy": "Kopier",
|
||||||
|
"copySuccess": "Kopiert med suksess",
|
||||||
|
"create": "Legg til betalingsmetode",
|
||||||
|
"createPayment": "Legg til betalingsmetode",
|
||||||
|
"createSuccess": "Opprettet med suksess",
|
||||||
|
"delete": "Slett",
|
||||||
|
"deleteSuccess": "Slettet med suksess",
|
||||||
|
"deleteWarning": "Er du sikker på at du vil slette denne betalingsmetoden? Denne handlingen kan ikke angres.",
|
||||||
|
"description": "Beskrivelse",
|
||||||
|
"descriptionPlaceholder": "Skriv inn beskrivelse av betalingsmetode",
|
||||||
|
"domain": "Domene",
|
||||||
|
"domainPlaceholder": "valgfritt, f.eks. {example}",
|
||||||
|
"edit": "Rediger",
|
||||||
|
"editPayment": "Rediger betalingsmetode",
|
||||||
"enable": "Aktiver",
|
"enable": "Aktiver",
|
||||||
"enableDescription": "Aktiver denne betalingsmetoden",
|
"feeAmount": "Fast beløp",
|
||||||
"epay": {
|
|
||||||
"key": "NØKKEL",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Avgiftsmåte",
|
"feeMode": "Avgiftsmåte",
|
||||||
"feeModeDescription": "Gebyrmodus for ekstra avgifter",
|
|
||||||
"feeModeItems": ["Ingen gebyr", "Prosentandel", "Fast beløp", "Begge ovennevnte"],
|
|
||||||
"feePercent": "Prosentavgift (valgfritt)",
|
"feePercent": "Prosentavgift (valgfritt)",
|
||||||
"feePercentDescription": "Legg til et gebyr på toppen av ordrebeløpet",
|
|
||||||
"fixedFee": "Fast gebyr (valgfritt)",
|
"fixedFee": "Fast gebyr (valgfritt)",
|
||||||
"fixedFeeDescription": "Legg til et fast gebyr i tillegg til ordrebeløpet",
|
"handlingFee": "Håndteringsgebyr",
|
||||||
"iconUrl": "Ikon URL (valgfritt)",
|
"icon": "Ikon",
|
||||||
"iconUrlDescription": "Brukes for visning på frontend",
|
"iconPlaceholder": "Skriv inn ikon-URL",
|
||||||
"inputPlaceholder": "Vennligst skriv inn",
|
"mark": "Identifikator",
|
||||||
"key": "NØKKEL",
|
"markPlaceholder": "Skriv inn identifikator for betalingsmetode",
|
||||||
"notifyUrl": "Tilpasset varslingsdomene (valgfritt)",
|
"name": "Navn",
|
||||||
"notifyUrlDescription": "Gateway-varsler vil bli sendt til dette domenet",
|
"namePlaceholder": "Skriv inn navn på betalingsmetode",
|
||||||
"payForRecommendations": "Betal for anbefalinger",
|
"nameRequired": "Navn er påkrevd",
|
||||||
"pid": "PID",
|
"noFee": "Ingen gebyr",
|
||||||
"saveSuccess": "Lagring vellykket",
|
"notify_url": "Varslings-URL",
|
||||||
"showName": "Vis navn",
|
"paymentManagement": "Betalingsadministrasjon",
|
||||||
"showNameDescription": "Brukes for visning på frontend",
|
"percentFee": "Prosentandel",
|
||||||
"stripe": {
|
"platform": "Plattform",
|
||||||
"publicKey": "OFFENTLIG NØKKEL",
|
"searchPlaceholder": "Skriv inn søkeord",
|
||||||
"secretKey": "HEMMELIG NØKKEL",
|
"selectPlatform": "Velg plattform",
|
||||||
"webhookSecret": "WEBHOOK SIGNATUR"
|
"selectType": "Velg betalingstype",
|
||||||
},
|
"submit": "Send inn",
|
||||||
"url": "URL",
|
"type": "Type",
|
||||||
"wechatPay": "WeChat-betaling"
|
"updateSuccess": "Oppdatert med suksess"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Płatność Alipay",
|
"AlipayF2F": "Alipay Twarzą w Twarz",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID aplikacji Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Niestandardowa nazwa produktu",
|
"applyForPayment": "Złóż wniosek o płatność",
|
||||||
"invoiceNameDescription": "Będzie wyświetlana na rachunku Alipay",
|
"balance": "Saldo",
|
||||||
"privateKey": "Klucz prywatny Alipay",
|
"batchDelete": "Usuwanie zbiorcze",
|
||||||
"publicKey": "Klucz publiczny Alipay",
|
"cancel": "Anuluj",
|
||||||
"sandbox": "Tryb piaskownicy",
|
"config": "Konfiguracja",
|
||||||
"sandboxDescription": "Włącz tryb piaskownicy, aby przetestować płatności"
|
"configInfo": "Informacje o konfiguracji",
|
||||||
},
|
"configPlaceholder": "Proszę wypełnić konfigurację {field}",
|
||||||
|
"confirm": "Potwierdź",
|
||||||
|
"confirmDelete": "Potwierdź usunięcie",
|
||||||
|
"copy": "Kopiuj",
|
||||||
|
"copySuccess": "Skopiowano pomyślnie",
|
||||||
|
"create": "Dodaj metodę płatności",
|
||||||
|
"createPayment": "Dodaj metodę płatności",
|
||||||
|
"createSuccess": "Utworzono pomyślnie",
|
||||||
|
"delete": "Usuń",
|
||||||
|
"deleteSuccess": "Usunięto pomyślnie",
|
||||||
|
"deleteWarning": "Czy na pewno chcesz usunąć tę metodę płatności? Ta akcja jest nieodwracalna.",
|
||||||
|
"description": "Opis",
|
||||||
|
"descriptionPlaceholder": "Wprowadź opis metody płatności",
|
||||||
|
"domain": "Domena",
|
||||||
|
"domainPlaceholder": "opcjonalnie, np. {example}",
|
||||||
|
"edit": "Edytuj",
|
||||||
|
"editPayment": "Edytuj metodę płatności",
|
||||||
"enable": "Włącz",
|
"enable": "Włącz",
|
||||||
"enableDescription": "Włącz tę metodę płatności",
|
"feeAmount": "Kwota stała",
|
||||||
"epay": {
|
|
||||||
"key": "KLUCZ",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Sposób naliczania opłat",
|
"feeMode": "Sposób naliczania opłat",
|
||||||
"feeModeDescription": "Sposób naliczania dodatkowej opłaty",
|
|
||||||
"feeModeItems": ["Bez opłaty", "Procent", "Stała kwota", "Oba powyższe"],
|
|
||||||
"feePercent": "Procentowa opłata (opcjonalnie)",
|
"feePercent": "Procentowa opłata (opcjonalnie)",
|
||||||
"feePercentDescription": "Dodaj opłatę do kwoty zamówienia",
|
|
||||||
"fixedFee": "Stała opłata (opcjonalnie)",
|
"fixedFee": "Stała opłata (opcjonalnie)",
|
||||||
"fixedFeeDescription": "Dodaj stałą opłatę do kwoty zamówienia",
|
"handlingFee": "Opłata manipulacyjna",
|
||||||
"iconUrl": "URL ikony (opcjonalnie)",
|
"icon": "Ikona",
|
||||||
"iconUrlDescription": "Używane do wyświetlania na froncie",
|
"iconPlaceholder": "Wprowadź URL ikony",
|
||||||
"inputPlaceholder": "Wprowadź tekst",
|
"mark": "Identyfikator",
|
||||||
"key": "KLUCZ",
|
"markPlaceholder": "Wprowadź identyfikator metody płatności",
|
||||||
"notifyUrl": "Niestandardowa domena powiadomień (opcjonalnie)",
|
"name": "Nazwa",
|
||||||
"notifyUrlDescription": "Powiadomienie bramki zostanie wysłane na tę domenę",
|
"namePlaceholder": "Wprowadź nazwę metody płatności",
|
||||||
"payForRecommendations": "Zapłać za rekomendacje",
|
"nameRequired": "Nazwa jest wymagana",
|
||||||
"pid": "PID",
|
"noFee": "Brak opłaty",
|
||||||
"saveSuccess": "Zapisano pomyślnie",
|
"notify_url": "URL powiadomienia",
|
||||||
"showName": "Pokaż nazwę",
|
"paymentManagement": "Zarządzanie płatnościami",
|
||||||
"showNameDescription": "Używane do wyświetlania na froncie",
|
"percentFee": "Procent",
|
||||||
"stripe": {
|
"platform": "Platforma",
|
||||||
"publicKey": "KLUCZ PUBLICZNY",
|
"searchPlaceholder": "Wprowadź hasła do wyszukiwania",
|
||||||
"secretKey": "KLUCZ TAJNY",
|
"selectPlatform": "Wybierz platformę",
|
||||||
"webhookSecret": "PODPIS KLUCZA WEBHOOK"
|
"selectType": "Wybierz typ płatności",
|
||||||
},
|
"submit": "Wyślij",
|
||||||
"url": "URL",
|
"type": "Typ",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Zaktualizowano pomyślnie"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Pagamento com Alipay",
|
"AlipayF2F": "Alipay Face a Face",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID do aplicativo Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Nome personalizado do produto",
|
"applyForPayment": "Solicitar Pagamento",
|
||||||
"invoiceNameDescription": "Será exibido na fatura do Alipay",
|
"balance": "Saldo",
|
||||||
"privateKey": "Chave privada do Alipay",
|
"batchDelete": "Excluir em Lote",
|
||||||
"publicKey": "Chave pública do Alipay",
|
"cancel": "Cancelar",
|
||||||
"sandbox": "Modo sandbox",
|
"config": "Configuração",
|
||||||
"sandboxDescription": "Ative o modo sandbox para testar pagamentos"
|
"configInfo": "Informações de Configuração",
|
||||||
},
|
"configPlaceholder": "Por favor, preencha a configuração de {field} fornecida",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"confirmDelete": "Confirmar Exclusão",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"copySuccess": "Copiado com sucesso",
|
||||||
|
"create": "Adicionar Método de Pagamento",
|
||||||
|
"createPayment": "Adicionar Método de Pagamento",
|
||||||
|
"createSuccess": "Criado com sucesso",
|
||||||
|
"delete": "Excluir",
|
||||||
|
"deleteSuccess": "Excluído com sucesso",
|
||||||
|
"deleteWarning": "Você tem certeza de que deseja excluir este método de pagamento? Esta ação não pode ser desfeita.",
|
||||||
|
"description": "Descrição",
|
||||||
|
"descriptionPlaceholder": "Insira a descrição do método de pagamento",
|
||||||
|
"domain": "Domínio",
|
||||||
|
"domainPlaceholder": "opcional, por exemplo, {exemplo}",
|
||||||
|
"edit": "Editar",
|
||||||
|
"editPayment": "Editar Método de Pagamento",
|
||||||
"enable": "Habilitar",
|
"enable": "Habilitar",
|
||||||
"enableDescription": "Ativar este método de pagamento",
|
"feeAmount": "Valor Fixo",
|
||||||
"epay": {
|
|
||||||
"key": "CHAVE",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Modo de Cobrança",
|
"feeMode": "Modo de Cobrança",
|
||||||
"feeModeDescription": "Modo de cobrança de taxas adicionais",
|
|
||||||
"feeModeItems": ["Sem taxa", "Porcentagem", "Valor fixo", "Ambos os anteriores"],
|
|
||||||
"feePercent": "Percentual de taxa (opcional)",
|
"feePercent": "Percentual de taxa (opcional)",
|
||||||
"feePercentDescription": "Adicionar taxa sobre o valor do pedido",
|
|
||||||
"fixedFee": "Taxa fixa (opcional)",
|
"fixedFee": "Taxa fixa (opcional)",
|
||||||
"fixedFeeDescription": "Adicionar uma taxa fixa sobre o valor do pedido",
|
"handlingFee": "Taxa de Manipulação",
|
||||||
"iconUrl": "URL do ícone (opcional)",
|
"icon": "Ícone",
|
||||||
"iconUrlDescription": "Usado para exibição no front-end",
|
"iconPlaceholder": "Digite a URL do ícone",
|
||||||
"inputPlaceholder": "Por favor, insira",
|
"mark": "Identificador",
|
||||||
"key": "CHAVE",
|
"markPlaceholder": "Digite o identificador do método de pagamento",
|
||||||
"notifyUrl": "Domínio de notificação personalizado (opcional)",
|
"name": "Nome",
|
||||||
"notifyUrlDescription": "A notificação do gateway será enviada para este domínio",
|
"namePlaceholder": "Digite o nome do método de pagamento",
|
||||||
"payForRecommendations": "Pagar por recomendações",
|
"nameRequired": "O nome é obrigatório",
|
||||||
"pid": "PID",
|
"noFee": "Sem Taxa",
|
||||||
"saveSuccess": "Salvo com sucesso",
|
"notify_url": "URL de Notificação",
|
||||||
"showName": "Mostrar Nome",
|
"paymentManagement": "Gerenciamento de Pagamento",
|
||||||
"showNameDescription": "Usado para exibição no front-end",
|
"percentFee": "Porcentagem",
|
||||||
"stripe": {
|
"platform": "Plataforma",
|
||||||
"publicKey": "CHAVE PÚBLICA",
|
"searchPlaceholder": "Digite os termos de pesquisa",
|
||||||
"secretKey": "CHAVE SECRETA",
|
"selectPlatform": "Selecionar Plataforma",
|
||||||
"webhookSecret": "ASSINATURA DA CHAVE DO WEBHOOK"
|
"selectType": "Selecionar tipo de pagamento",
|
||||||
},
|
"submit": "Enviar",
|
||||||
"url": "URL",
|
"type": "Tipo",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Atualizado com sucesso"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Plată cu Alipay",
|
"AlipayF2F": "Alipay Față în Față",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID aplicație Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Nume personalizat produs",
|
"applyForPayment": "Solicită Plată",
|
||||||
"invoiceNameDescription": "Va apărea în factura Alipay",
|
"balance": "Sold",
|
||||||
"privateKey": "Cheie privată Alipay",
|
"batchDelete": "Ștergere în Lot",
|
||||||
"publicKey": "Cheie publică Alipay",
|
"cancel": "Anulează",
|
||||||
"sandbox": "Modul sandbox",
|
"config": "Configurare",
|
||||||
"sandboxDescription": "Activează modul sandbox pentru a testa plățile"
|
"configInfo": "Informații de Configurare",
|
||||||
},
|
"configPlaceholder": "Te rugăm să completezi configurația {field} furnizată",
|
||||||
|
"confirm": "Confirmă",
|
||||||
|
"confirmDelete": "Confirmă Ștergerea",
|
||||||
|
"copy": "Copiază",
|
||||||
|
"copySuccess": "Copiat cu succes",
|
||||||
|
"create": "Adaugă Metodă de Plată",
|
||||||
|
"createPayment": "Adaugă Metodă de Plată",
|
||||||
|
"createSuccess": "Creat cu succes",
|
||||||
|
"delete": "Șterge",
|
||||||
|
"deleteSuccess": "Șters cu succes",
|
||||||
|
"deleteWarning": "Ești sigur că vrei să ștergi această metodă de plată? Această acțiune nu poate fi anulată.",
|
||||||
|
"description": "Descriere",
|
||||||
|
"descriptionPlaceholder": "Introduceți descrierea metodei de plată",
|
||||||
|
"domain": "Domeniu",
|
||||||
|
"domainPlaceholder": "opțional, de exemplu {example}",
|
||||||
|
"edit": "Editează",
|
||||||
|
"editPayment": "Editează Metodă de Plată",
|
||||||
"enable": "Activare",
|
"enable": "Activare",
|
||||||
"enableDescription": "Activează această metodă de plată",
|
"feeAmount": "Sumă Fixă",
|
||||||
"epay": {
|
|
||||||
"key": "CHEIE",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Mod de taxare",
|
"feeMode": "Mod de taxare",
|
||||||
"feeModeDescription": "Modul de taxare a comisioanelor suplimentare",
|
|
||||||
"feeModeItems": ["Fără comision", "Procentaj", "Sumă fixă", "Ambele"],
|
|
||||||
"feePercent": "Taxă procentuală (opțional)",
|
"feePercent": "Taxă procentuală (opțional)",
|
||||||
"feePercentDescription": "Se adaugă o taxă peste suma comenzii",
|
|
||||||
"fixedFee": "Taxă fixă (opțional)",
|
"fixedFee": "Taxă fixă (opțional)",
|
||||||
"fixedFeeDescription": "Adăugarea unei taxe fixe peste suma comenzii",
|
"handlingFee": "Taxă de manipulare",
|
||||||
"iconUrl": "URL pictogramă (opțional)",
|
"icon": "Iconiță",
|
||||||
"iconUrlDescription": "Utilizat pentru afișarea în front-end",
|
"iconPlaceholder": "Introdu URL-ul iconiței",
|
||||||
"inputPlaceholder": "Introduceți",
|
"mark": "Identificator",
|
||||||
"key": "CHEIE",
|
"markPlaceholder": "Introdu identificatorul metodei de plată",
|
||||||
"notifyUrl": "Domeniu de notificare personalizat (opțional)",
|
"name": "Nume",
|
||||||
"notifyUrlDescription": "Notificările de la gateway vor fi trimise la acest domeniu",
|
"namePlaceholder": "Introdu numele metodei de plată",
|
||||||
"payForRecommendations": "Plătește pentru recomandări",
|
"nameRequired": "Numele este necesar",
|
||||||
"pid": "PID",
|
"noFee": "Fără Taxă",
|
||||||
"saveSuccess": "Salvare reușită",
|
"notify_url": "URL de notificare",
|
||||||
"showName": "Afișează Numele",
|
"paymentManagement": "Gestionare Plăți",
|
||||||
"showNameDescription": "Utilizat pentru afișarea în interfața utilizatorului",
|
"percentFee": "Procentaj",
|
||||||
"stripe": {
|
"platform": "Platformă",
|
||||||
"publicKey": "CHEIE PUBLICĂ",
|
"searchPlaceholder": "Introdu termeni de căutare",
|
||||||
"secretKey": "CHEIE SECRETĂ",
|
"selectPlatform": "Selectează Platforma",
|
||||||
"webhookSecret": "SEMNĂTURA CHEII WEBHOOK"
|
"selectType": "Selectează tipul de plată",
|
||||||
},
|
"submit": "Trimite",
|
||||||
"url": "URL",
|
"type": "Tip",
|
||||||
"wechatPay": "Plată WeChat"
|
"updateSuccess": "Actualizat cu succes"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Оплата через Alipay",
|
"AlipayF2F": "Alipay Лицом к Лицу",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID приложения Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Пользовательское название продукта",
|
"applyForPayment": "Подать заявку на оплату",
|
||||||
"invoiceNameDescription": "Будет отображаться в счете Alipay",
|
"balance": "Баланс",
|
||||||
"privateKey": "Приватный ключ Alipay",
|
"batchDelete": "Пакетное удаление",
|
||||||
"publicKey": "Публичный ключ Alipay",
|
"cancel": "Отмена",
|
||||||
"sandbox": "Режим песочницы",
|
"config": "Конфигурация",
|
||||||
"sandboxDescription": "Включите режим песочницы для тестирования платежей"
|
"configInfo": "Информация о конфигурации",
|
||||||
},
|
"configPlaceholder": "Пожалуйста, заполните предоставленную конфигурацию {field}",
|
||||||
|
"confirm": "Подтвердить",
|
||||||
|
"confirmDelete": "Подтвердить удаление",
|
||||||
|
"copy": "Копировать",
|
||||||
|
"copySuccess": "Успешно скопировано",
|
||||||
|
"create": "Добавить способ оплаты",
|
||||||
|
"createPayment": "Добавить способ оплаты",
|
||||||
|
"createSuccess": "Успешно создано",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"deleteSuccess": "Успешно удалено",
|
||||||
|
"deleteWarning": "Вы уверены, что хотите удалить этот способ оплаты? Это действие нельзя отменить.",
|
||||||
|
"description": "Описание",
|
||||||
|
"descriptionPlaceholder": "Введите описание метода оплаты",
|
||||||
|
"domain": "Домен",
|
||||||
|
"domainPlaceholder": "необязательно, например, {example}",
|
||||||
|
"edit": "Редактировать",
|
||||||
|
"editPayment": "Редактировать способ оплаты",
|
||||||
"enable": "Включить",
|
"enable": "Включить",
|
||||||
"enableDescription": "Включить этот способ оплаты",
|
"feeAmount": "Фиксированная сумма",
|
||||||
"epay": {
|
|
||||||
"key": "КЛЮЧ",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Способ оплаты",
|
"feeMode": "Способ оплаты",
|
||||||
"feeModeDescription": "Способ взимания дополнительной комиссии",
|
|
||||||
"feeModeItems": ["Без комиссии", "Процент", "Фиксированная сумма", "Оба вышеуказанных"],
|
|
||||||
"feePercent": "Процентная комиссия (необязательно)",
|
"feePercent": "Процентная комиссия (необязательно)",
|
||||||
"feePercentDescription": "Добавление комиссии к сумме заказа",
|
|
||||||
"fixedFee": "Фиксированная плата (по желанию)",
|
"fixedFee": "Фиксированная плата (по желанию)",
|
||||||
"fixedFeeDescription": "Добавление фиксированной платы к сумме заказа",
|
"handlingFee": "Плата за обработку",
|
||||||
"iconUrl": "URL значка (необязательно)",
|
"icon": "Иконка",
|
||||||
"iconUrlDescription": "Используется для отображения на фронтенде",
|
"iconPlaceholder": "Введите URL иконки",
|
||||||
"inputPlaceholder": "Пожалуйста, введите",
|
"mark": "Идентификатор",
|
||||||
"key": "КЛЮЧ",
|
"markPlaceholder": "Введите идентификатор способа оплаты",
|
||||||
"notifyUrl": "Пользовательский домен уведомлений (необязательно)",
|
"name": "Название",
|
||||||
"notifyUrlDescription": "Уведомление шлюза будет отправлено на этот домен",
|
"namePlaceholder": "Введите название способа оплаты",
|
||||||
"payForRecommendations": "Оплатить рекомендации",
|
"nameRequired": "Название обязательно",
|
||||||
"pid": "PID",
|
"noFee": "Без комиссии",
|
||||||
"saveSuccess": "Сохранение успешно",
|
"notify_url": "URL уведомления",
|
||||||
"showName": "Показать имя",
|
"paymentManagement": "Управление платежами",
|
||||||
"showNameDescription": "Используется для отображения на фронтенде",
|
"percentFee": "Процент",
|
||||||
"stripe": {
|
"platform": "Платформа",
|
||||||
"publicKey": "ОТКРЫТЫЙ КЛЮЧ",
|
"searchPlaceholder": "Введите поисковые запросы",
|
||||||
"secretKey": "СЕКРЕТНЫЙ КЛЮЧ",
|
"selectPlatform": "Выберите платформу",
|
||||||
"webhookSecret": "СЕКРЕТ ПОДПИСИ ВЕБХУКА"
|
"selectType": "Выберите тип оплаты",
|
||||||
},
|
"submit": "Отправить",
|
||||||
"url": "URL",
|
"type": "Тип",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Успешно обновлено"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "ชำระเงินผ่านอาลีเพย์",
|
"AlipayF2F": "Alipay แบบพบหน้า",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "รหัสแอปพลิเคชัน Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "ชื่อผลิตภัณฑ์ที่กำหนดเอง",
|
"applyForPayment": "ขอชำระเงิน",
|
||||||
"invoiceNameDescription": "จะแสดงในใบแจ้งหนี้ของ Alipay",
|
"balance": "ยอดคงเหลือ",
|
||||||
"privateKey": "กุญแจส่วนตัวของ Alipay",
|
"batchDelete": "ลบเป็นชุด",
|
||||||
"publicKey": "กุญแจสาธารณะของ Alipay",
|
"cancel": "ยกเลิก",
|
||||||
"sandbox": "โหมดแซนด์บ็อกซ์",
|
"config": "การตั้งค่า",
|
||||||
"sandboxDescription": "เปิดใช้งานโหมดแซนด์บ็อกซ์เพื่อทดสอบการชำระเงิน"
|
"configInfo": "ข้อมูลการตั้งค่า",
|
||||||
},
|
"configPlaceholder": "กรุณากรอกการตั้งค่า {field} ที่ให้มา",
|
||||||
|
"confirm": "ยืนยัน",
|
||||||
|
"confirmDelete": "ยืนยันการลบ",
|
||||||
|
"copy": "คัดลอก",
|
||||||
|
"copySuccess": "คัดลอกสำเร็จ",
|
||||||
|
"create": "เพิ่มวิธีการชำระเงิน",
|
||||||
|
"createPayment": "เพิ่มวิธีการชำระเงิน",
|
||||||
|
"createSuccess": "สร้างสำเร็จ",
|
||||||
|
"delete": "ลบ",
|
||||||
|
"deleteSuccess": "ลบสำเร็จ",
|
||||||
|
"deleteWarning": "คุณแน่ใจหรือไม่ว่าต้องการลบวิธีการชำระเงินนี้? การกระทำนี้ไม่สามารถย้อนกลับได้",
|
||||||
|
"description": "คำอธิบาย",
|
||||||
|
"descriptionPlaceholder": "กรอกคำอธิบายวิธีการชำระเงิน",
|
||||||
|
"domain": "โดเมน",
|
||||||
|
"domainPlaceholder": "ไม่บังคับ เช่น {example}",
|
||||||
|
"edit": "แก้ไข",
|
||||||
|
"editPayment": "แก้ไขวิธีการชำระเงิน",
|
||||||
"enable": "เปิดใช้งาน",
|
"enable": "เปิดใช้งาน",
|
||||||
"enableDescription": "เปิดใช้งานวิธีการชำระเงินนี้",
|
"feeAmount": "จำนวนคงที่",
|
||||||
"epay": {
|
|
||||||
"key": "คีย์",
|
|
||||||
"pid": "พีไอดี",
|
|
||||||
"url": "ยูอาร์แอล"
|
|
||||||
},
|
|
||||||
"feeMode": "วิธีการคิดค่าบริการ",
|
"feeMode": "วิธีการคิดค่าบริการ",
|
||||||
"feeModeDescription": "วิธีการคิดค่าธรรมเนียมเพิ่มเติม",
|
|
||||||
"feeModeItems": ["ไม่มีค่าธรรมเนียม", "เปอร์เซ็นต์", "จำนวนเงินคงที่", "ทั้งสองอย่าง"],
|
|
||||||
"feePercent": "เปอร์เซ็นต์ค่าธรรมเนียม (ไม่บังคับ)",
|
"feePercent": "เปอร์เซ็นต์ค่าธรรมเนียม (ไม่บังคับ)",
|
||||||
"feePercentDescription": "เพิ่มค่าธรรมเนียมจากยอดสั่งซื้อ",
|
|
||||||
"fixedFee": "ค่าธรรมเนียมคงที่ (ไม่บังคับ)",
|
"fixedFee": "ค่าธรรมเนียมคงที่ (ไม่บังคับ)",
|
||||||
"fixedFeeDescription": "เพิ่มค่าธรรมเนียมคงที่เหนือจำนวนเงินในคำสั่งซื้อ",
|
"handlingFee": "ค่าธรรมเนียมการจัดการ",
|
||||||
"iconUrl": "URL ไอคอน (ไม่บังคับ)",
|
"icon": "ไอคอน",
|
||||||
"iconUrlDescription": "ใช้สำหรับแสดงผลด้านหน้า",
|
"iconPlaceholder": "กรอก URL ไอคอน",
|
||||||
"inputPlaceholder": "กรุณาใส่ข้อมูล",
|
"mark": "ตัวระบุ",
|
||||||
"key": "กุญแจ",
|
"markPlaceholder": "กรอกตัวระบุวิธีการชำระเงิน",
|
||||||
"notifyUrl": "โดเมนการแจ้งเตือนที่กำหนดเอง (ไม่บังคับ)",
|
"name": "ชื่อ",
|
||||||
"notifyUrlDescription": "การแจ้งเตือนเกตเวย์จะถูกส่งไปยังโดเมนนี้",
|
"namePlaceholder": "กรอกชื่อวิธีการชำระเงิน",
|
||||||
"payForRecommendations": "ชำระเงินสำหรับคำแนะนำ",
|
"nameRequired": "ชื่อเป็นสิ่งจำเป็น",
|
||||||
"pid": "PID",
|
"noFee": "ไม่มีค่าธรรมเนียม",
|
||||||
"saveSuccess": "บันทึกสำเร็จ",
|
"notify_url": "URL การแจ้งเตือน",
|
||||||
"showName": "แสดงชื่อ",
|
"paymentManagement": "การจัดการการชำระเงิน",
|
||||||
"showNameDescription": "ใช้สำหรับแสดงผลด้านหน้า",
|
"percentFee": "เปอร์เซ็นต์",
|
||||||
"stripe": {
|
"platform": "แพลตฟอร์ม",
|
||||||
"publicKey": "PUBLIC KEY",
|
"searchPlaceholder": "กรอกคำค้นหา",
|
||||||
"secretKey": "SECRET KEY",
|
"selectPlatform": "เลือกแพลตฟอร์ม",
|
||||||
"webhookSecret": "WEBHOOK คีย์ลับ"
|
"selectType": "เลือกประเภทการชำระเงิน",
|
||||||
},
|
"submit": "ส่ง",
|
||||||
"url": "URL",
|
"type": "ประเภท",
|
||||||
"wechatPay": "วีแชทเพย์"
|
"updateSuccess": "อัปเดตสำเร็จ"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Alipay ile ödeme",
|
"AlipayF2F": "Alipay Yüz Yüze",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "Alipay Uygulama Kimliği",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Özel Ürün Adı",
|
"applyForPayment": "Ödeme Başvurusu",
|
||||||
"invoiceNameDescription": "Alipay faturasına gösterilecektir",
|
"balance": "Bakiye",
|
||||||
"privateKey": "Alipay Özel Anahtarı",
|
"batchDelete": "Toplu Sil",
|
||||||
"publicKey": "Alipay Genel Anahtarı",
|
"cancel": "İptal",
|
||||||
"sandbox": "Sandbox Modu",
|
"config": "Yapılandırma",
|
||||||
"sandboxDescription": "Ödemeyi test etmek için sandbox modunu etkinleştirin"
|
"configInfo": "Yapılandırma Bilgisi",
|
||||||
},
|
"configPlaceholder": "Lütfen sağlanan {field} yapılandırmasını doldurun",
|
||||||
|
"confirm": "Onayla",
|
||||||
|
"confirmDelete": "Silme İşlemini Onayla",
|
||||||
|
"copy": "Kopyala",
|
||||||
|
"copySuccess": "Başarıyla kopyalandı",
|
||||||
|
"create": "Ödeme Yöntemi Ekle",
|
||||||
|
"createPayment": "Ödeme Yöntemi Ekle",
|
||||||
|
"createSuccess": "Başarıyla oluşturuldu",
|
||||||
|
"delete": "Sil",
|
||||||
|
"deleteSuccess": "Başarıyla silindi",
|
||||||
|
"deleteWarning": "Bu ödeme yöntemini silmek istediğinize emin misiniz? Bu işlem geri alınamaz.",
|
||||||
|
"description": "Açıklama",
|
||||||
|
"descriptionPlaceholder": "Ödeme yöntemi açıklamasını girin",
|
||||||
|
"domain": "Alan Adı",
|
||||||
|
"domainPlaceholder": "isteğe bağlı, örn. {example}",
|
||||||
|
"edit": "Düzenle",
|
||||||
|
"editPayment": "Ödeme Yöntemini Düzenle",
|
||||||
"enable": "Etkinleştir",
|
"enable": "Etkinleştir",
|
||||||
"enableDescription": "Bu ödeme yöntemini etkinleştir",
|
"feeAmount": "Sabit Tutar",
|
||||||
"epay": {
|
|
||||||
"key": "ANAHTAR",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Ücretlendirme Modu",
|
"feeMode": "Ücretlendirme Modu",
|
||||||
"feeModeDescription": "Ekstra ücretin tahsilat yöntemi",
|
|
||||||
"feeModeItems": ["Ücretsiz", "Yüzde", "Sabit Tutar", "Her İkisi"],
|
|
||||||
"feePercent": "Yüzde Ücreti (isteğe bağlı)",
|
"feePercent": "Yüzde Ücreti (isteğe bağlı)",
|
||||||
"feePercentDescription": "Sipariş tutarının üzerine eklenen ücret",
|
|
||||||
"fixedFee": "Sabit Ücret (isteğe bağlı)",
|
"fixedFee": "Sabit Ücret (isteğe bağlı)",
|
||||||
"fixedFeeDescription": "Sipariş tutarına ek olarak sabit ücret eklenir",
|
"handlingFee": "İşlem Ücreti",
|
||||||
"iconUrl": "Simge URL'si (isteğe bağlı)",
|
"icon": "Simge",
|
||||||
"iconUrlDescription": "Ön uçta görüntüleme için kullanılır",
|
"iconPlaceholder": "Simge URL'sini girin",
|
||||||
"inputPlaceholder": "Lütfen girin",
|
"mark": "Tanımlayıcı",
|
||||||
"key": "ANAHTAR",
|
"markPlaceholder": "Ödeme yöntemi tanımlayıcısını girin",
|
||||||
"notifyUrl": "Özel bildirim alanı (isteğe bağlı)",
|
"name": "İsim",
|
||||||
"notifyUrlDescription": "Ağ geçidi bildirimi bu alana gönderilecektir",
|
"namePlaceholder": "Ödeme yöntemi adını girin",
|
||||||
"payForRecommendations": "Öneriler için ödeme yap",
|
"nameRequired": "İsim gereklidir",
|
||||||
"pid": "PID",
|
"noFee": "Ücret Yok",
|
||||||
"saveSuccess": "Başarıyla kaydedildi",
|
"notify_url": "Bildirim URL'si",
|
||||||
"showName": "Görünen Ad",
|
"paymentManagement": "Ödeme Yönetimi",
|
||||||
"showNameDescription": "Ön uçta görüntüleme için kullanılır",
|
"percentFee": "Yüzde",
|
||||||
"stripe": {
|
"platform": "Platform",
|
||||||
"publicKey": "GENEL ANAHTAR",
|
"searchPlaceholder": "Arama terimlerini girin",
|
||||||
"secretKey": "GİZLİ ANAHTAR",
|
"selectPlatform": "Platform Seçin",
|
||||||
"webhookSecret": "WEBHOOK GİZLİ İMZA"
|
"selectType": "Ödeme türünü seçin",
|
||||||
},
|
"submit": "Gönder",
|
||||||
"url": "URL",
|
"type": "Tür",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Başarıyla güncellendi"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Оплата через Alipay",
|
"AlipayF2F": "Alipay Face to Face",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "Ідентифікатор додатку Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Назва продукту на замовлення",
|
"applyForPayment": "Подати заявку на оплату",
|
||||||
"invoiceNameDescription": "Буде відображено у рахунку Alipay",
|
"balance": "Баланс",
|
||||||
"privateKey": "Приватний ключ Alipay",
|
"batchDelete": "Пакетне видалення",
|
||||||
"publicKey": "Публічний ключ Alipay",
|
"cancel": "Скасувати",
|
||||||
"sandbox": "Режим пісочниці",
|
"config": "Налаштування",
|
||||||
"sandboxDescription": "Увімкніть режим пісочниці для тестування платежів"
|
"configInfo": "Інформація про налаштування",
|
||||||
},
|
"configPlaceholder": "Будь ласка, заповніть надану конфігурацію {field}",
|
||||||
|
"confirm": "Підтвердити",
|
||||||
|
"confirmDelete": "Підтвердити видалення",
|
||||||
|
"copy": "Копіювати",
|
||||||
|
"copySuccess": "Успішно скопійовано",
|
||||||
|
"create": "Додати спосіб оплати",
|
||||||
|
"createPayment": "Додати спосіб оплати",
|
||||||
|
"createSuccess": "Успішно створено",
|
||||||
|
"delete": "Видалити",
|
||||||
|
"deleteSuccess": "Успішно видалено",
|
||||||
|
"deleteWarning": "Ви впевнені, що хочете видалити цей спосіб оплати? Цю дію не можна скасувати.",
|
||||||
|
"description": "Опис",
|
||||||
|
"descriptionPlaceholder": "Введіть опис способу оплати",
|
||||||
|
"domain": "Домен",
|
||||||
|
"domainPlaceholder": "необов'язково, наприклад, {example}",
|
||||||
|
"edit": "Редагувати",
|
||||||
|
"editPayment": "Редагувати спосіб оплати",
|
||||||
"enable": "Увімкнути",
|
"enable": "Увімкнути",
|
||||||
"enableDescription": "Увімкнути цей спосіб оплати",
|
"feeAmount": "Фіксована сума",
|
||||||
"epay": {
|
|
||||||
"key": "КЛЮЧ",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Спосіб оплати",
|
"feeMode": "Спосіб оплати",
|
||||||
"feeModeDescription": "Спосіб стягнення додаткової комісії",
|
|
||||||
"feeModeItems": ["Без комісії", "Відсоток", "Фіксована сума", "Обидва варіанти"],
|
|
||||||
"feePercent": "Відсоткова комісія (необов'язково)",
|
"feePercent": "Відсоткова комісія (необов'язково)",
|
||||||
"feePercentDescription": "Додати комісію до суми замовлення",
|
|
||||||
"fixedFee": "Фіксована плата (необов'язково)",
|
"fixedFee": "Фіксована плата (необов'язково)",
|
||||||
"fixedFeeDescription": "Додати фіксовану плату до суми замовлення",
|
"handlingFee": "Плата за обробку",
|
||||||
"iconUrl": "URL значка (необов'язково)",
|
"icon": "Іконка",
|
||||||
"iconUrlDescription": "Використовується для відображення на фронтенді",
|
"iconPlaceholder": "Введіть URL іконки",
|
||||||
"inputPlaceholder": "Введіть, будь ласка",
|
"mark": "Ідентифікатор",
|
||||||
"key": "КЛЮЧ",
|
"markPlaceholder": "Введіть ідентифікатор способу оплати",
|
||||||
"notifyUrl": "Користувацька область сповіщень (необов'язково)",
|
"name": "Назва",
|
||||||
"notifyUrlDescription": "Сповіщення шлюзу буде надіслано на цей домен",
|
"namePlaceholder": "Введіть назву способу оплати",
|
||||||
"payForRecommendations": "Оплатити рекомендації",
|
"nameRequired": "Назва є обов'язковою",
|
||||||
"pid": "PID",
|
"noFee": "Без комісії",
|
||||||
"saveSuccess": "Збережено успішно",
|
"notify_url": "URL сповіщення",
|
||||||
"showName": "Показати ім'я",
|
"paymentManagement": "Управління платежами",
|
||||||
"showNameDescription": "Використовується для відображення на фронтенді",
|
"percentFee": "Відсоток",
|
||||||
"stripe": {
|
"platform": "Платформа",
|
||||||
"publicKey": "ПУБЛІЧНИЙ КЛЮЧ",
|
"searchPlaceholder": "Введіть пошукові терміни",
|
||||||
"secretKey": "СЕКРЕТНИЙ КЛЮЧ",
|
"selectPlatform": "Виберіть платформу",
|
||||||
"webhookSecret": "ПІДПИС ВЕБХУКА"
|
"selectType": "Виберіть тип оплати",
|
||||||
},
|
"submit": "Надіслати",
|
||||||
"url": "URL",
|
"type": "Тип",
|
||||||
"wechatPay": "WeChat Pay"
|
"updateSuccess": "Успішно оновлено"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "Thanh toán qua Alipay",
|
"AlipayF2F": "Alipay Gặp Mặt",
|
||||||
"alipayf2f": {
|
"EPay": "EPay",
|
||||||
"appId": "ID ứng dụng Alipay",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "Tên sản phẩm tùy chỉnh",
|
"applyForPayment": "Đăng Ký Thanh Toán",
|
||||||
"invoiceNameDescription": "Sẽ hiển thị trên hóa đơn Alipay",
|
"balance": "Số Dư",
|
||||||
"privateKey": "Khóa riêng Alipay",
|
"batchDelete": "Xóa Theo Lô",
|
||||||
"publicKey": "Khóa công khai Alipay",
|
"cancel": "Hủy",
|
||||||
"sandbox": "Chế độ sandbox",
|
"config": "Cấu Hình",
|
||||||
"sandboxDescription": "Kích hoạt chế độ sandbox để kiểm tra thanh toán"
|
"configInfo": "Thông Tin Cấu Hình",
|
||||||
},
|
"configPlaceholder": "Vui lòng điền vào cấu hình {field} đã cung cấp",
|
||||||
|
"confirm": "Xác Nhận",
|
||||||
|
"confirmDelete": "Xác Nhận Xóa",
|
||||||
|
"copy": "Sao Chép",
|
||||||
|
"copySuccess": "Sao chép thành công",
|
||||||
|
"create": "Thêm Phương Thức Thanh Toán",
|
||||||
|
"createPayment": "Thêm Phương Thức Thanh Toán",
|
||||||
|
"createSuccess": "Tạo thành công",
|
||||||
|
"delete": "Xóa",
|
||||||
|
"deleteSuccess": "Xóa thành công",
|
||||||
|
"deleteWarning": "Bạn có chắc chắn muốn xóa phương thức thanh toán này không? Hành động này không thể hoàn tác.",
|
||||||
|
"description": "Mô tả",
|
||||||
|
"descriptionPlaceholder": "Nhập mô tả phương thức thanh toán",
|
||||||
|
"domain": "Miền",
|
||||||
|
"domainPlaceholder": "tùy chọn, ví dụ: {example}",
|
||||||
|
"edit": "Chỉnh Sửa",
|
||||||
|
"editPayment": "Chỉnh Sửa Phương Thức Thanh Toán",
|
||||||
"enable": "Kích hoạt",
|
"enable": "Kích hoạt",
|
||||||
"enableDescription": "Kích hoạt phương thức thanh toán này",
|
"feeAmount": "Số Tiền Cố Định",
|
||||||
"epay": {
|
|
||||||
"key": "KHÓA",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "Phương thức tính phí",
|
"feeMode": "Phương thức tính phí",
|
||||||
"feeModeDescription": "Cách tính phí bổ sung",
|
|
||||||
"feeModeItems": ["Không phí dịch vụ", "Phần trăm", "Số tiền cố định", "Hai loại trên"],
|
|
||||||
"feePercent": "Phí phần trăm (tùy chọn)",
|
"feePercent": "Phí phần trăm (tùy chọn)",
|
||||||
"feePercentDescription": "Thêm phí trên số tiền đơn hàng",
|
|
||||||
"fixedFee": "Phí cố định (tùy chọn)",
|
"fixedFee": "Phí cố định (tùy chọn)",
|
||||||
"fixedFeeDescription": "Thêm phí cố định vào trên số tiền đơn hàng",
|
"handlingFee": "Phí Xử Lý",
|
||||||
"iconUrl": "URL biểu tượng (tùy chọn)",
|
"icon": "Biểu Tượng",
|
||||||
"iconUrlDescription": "Dùng để hiển thị phía trước",
|
"iconPlaceholder": "Nhập URL biểu tượng",
|
||||||
"inputPlaceholder": "Vui lòng nhập",
|
"mark": "Định Danh",
|
||||||
"key": "KEY",
|
"markPlaceholder": "Nhập định danh phương thức thanh toán",
|
||||||
"notifyUrl": "Miền thông báo tùy chỉnh (tùy chọn)",
|
"name": "Tên",
|
||||||
"notifyUrlDescription": "Thông báo cổng sẽ được gửi đến miền này",
|
"namePlaceholder": "Nhập tên phương thức thanh toán",
|
||||||
"payForRecommendations": "Thanh toán cho đề xuất",
|
"nameRequired": "Tên là bắt buộc",
|
||||||
"pid": "PID",
|
"noFee": "Không Có Phí",
|
||||||
"saveSuccess": "Lưu thành công",
|
"notify_url": "URL Thông Báo",
|
||||||
"showName": "Hiển thị tên",
|
"paymentManagement": "Quản Lý Thanh Toán",
|
||||||
"showNameDescription": "Dùng để hiển thị phía trước",
|
"percentFee": "Phần Trăm",
|
||||||
"stripe": {
|
"platform": "Nền Tảng",
|
||||||
"publicKey": "KHÓA CÔNG KHAI",
|
"searchPlaceholder": "Nhập từ khóa tìm kiếm",
|
||||||
"secretKey": "KHÓA BÍ MẬT",
|
"selectPlatform": "Chọn Nền Tảng",
|
||||||
"webhookSecret": "CHỮ KÝ KHÓA WEBHOOK"
|
"selectType": "Chọn loại thanh toán",
|
||||||
},
|
"submit": "Gửi",
|
||||||
"url": "URL",
|
"type": "Loại",
|
||||||
"wechatPay": "Thanh toán WeChat"
|
"updateSuccess": "Cập nhật thành công"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "支付宝支付",
|
"AlipayF2F": "支付宝面对面",
|
||||||
"alipayf2f": {
|
"EPay": "电子支付",
|
||||||
"appId": "支付宝应用ID",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "自定义产品名称",
|
"applyForPayment": "申请付款",
|
||||||
"invoiceNameDescription": "将显示在支付宝账单中",
|
"balance": "余额",
|
||||||
"privateKey": "支付宝私钥",
|
"batchDelete": "批量删除",
|
||||||
"publicKey": "支付宝公钥",
|
"cancel": "取消",
|
||||||
"sandbox": "沙箱模式",
|
"config": "配置",
|
||||||
"sandboxDescription": "启用沙箱模式以测试支付"
|
"configInfo": "配置信息",
|
||||||
},
|
"configPlaceholder": "请填写提供的 {field} 配置",
|
||||||
|
"confirm": "确认",
|
||||||
|
"confirmDelete": "确认删除",
|
||||||
|
"copy": "复制",
|
||||||
|
"copySuccess": "复制成功",
|
||||||
|
"create": "添加支付方式",
|
||||||
|
"createPayment": "添加支付方式",
|
||||||
|
"createSuccess": "创建成功",
|
||||||
|
"delete": "删除",
|
||||||
|
"deleteSuccess": "删除成功",
|
||||||
|
"deleteWarning": "您确定要删除此支付方式吗?此操作无法撤销。",
|
||||||
|
"description": "描述",
|
||||||
|
"descriptionPlaceholder": "输入支付方式描述",
|
||||||
|
"domain": "域名",
|
||||||
|
"domainPlaceholder": "可选,例如 {example}",
|
||||||
|
"edit": "编辑",
|
||||||
|
"editPayment": "编辑支付方式",
|
||||||
"enable": "启用",
|
"enable": "启用",
|
||||||
"enableDescription": "启用此支付方式",
|
"feeAmount": "固定金额",
|
||||||
"epay": {
|
|
||||||
"key": "KEY",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "收费方式",
|
"feeMode": "收费方式",
|
||||||
"feeModeDescription": "额外手续费的收费方式",
|
"feePercent": "百分比费用",
|
||||||
"feeModeItems": ["无手续费", "百分比", "固定金额", "以上两种"],
|
"fixedFee": "固定费用",
|
||||||
"feePercent": "百分比费用(可选)",
|
"handlingFee": "手续费",
|
||||||
"feePercentDescription": "在订单金额之上添加费用",
|
"icon": "图标",
|
||||||
"fixedFee": "固定费用(可选)",
|
"iconPlaceholder": "输入图标 URL",
|
||||||
"fixedFeeDescription": "在订单金额之上添加固定费用",
|
"mark": "标识符",
|
||||||
"iconUrl": "图标 URL(可选)",
|
"markPlaceholder": "输入支付方式标识符",
|
||||||
"iconUrlDescription": "用于前端显示",
|
"name": "名称",
|
||||||
"inputPlaceholder": "请输入",
|
"namePlaceholder": "输入支付方式名称",
|
||||||
"key": "KEY",
|
"nameRequired": "名称是必填项",
|
||||||
"notifyUrl": "自定义通知域(可选)",
|
"noFee": "无费用",
|
||||||
"notifyUrlDescription": "网关通知将发送到此域",
|
"notify_url": "通知网址",
|
||||||
"payForRecommendations": "支付推荐",
|
"paymentManagement": "支付管理",
|
||||||
"pid": "PID",
|
"percentFee": "百分比",
|
||||||
"saveSuccess": "保存成功",
|
"platform": "平台",
|
||||||
"showName": "显示名称",
|
"searchPlaceholder": "输入搜索词",
|
||||||
"showNameDescription": "用于前端显示",
|
"selectPlatform": "选择平台",
|
||||||
"stripe": {
|
"selectType": "选择支付类型",
|
||||||
"publicKey": "PUBLIC KEY",
|
"submit": "提交",
|
||||||
"secretKey": "SECRET KEY",
|
"type": "类型",
|
||||||
"webhookSecret": "WEBHOOK 密钥签名"
|
"updateSuccess": "更新成功"
|
||||||
},
|
|
||||||
"url": "URL",
|
|
||||||
"wechatPay": "微信支付"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,52 @@
|
|||||||
{
|
{
|
||||||
"aliPay": "支付寶支付",
|
"AlipayF2F": "支付寶面對面",
|
||||||
"alipayf2f": {
|
"EPay": "電子支付",
|
||||||
"appId": "支付寶應用ID",
|
"Stripe": "Stripe",
|
||||||
"invoiceName": "自訂產品名稱",
|
"applyForPayment": "申請付款",
|
||||||
"invoiceNameDescription": "將顯示在支付寶賬單中",
|
"balance": "餘額",
|
||||||
"privateKey": "支付寶私鑰",
|
"batchDelete": "批量刪除",
|
||||||
"publicKey": "支付寶公鑰",
|
"cancel": "取消",
|
||||||
"sandbox": "沙箱模式",
|
"config": "配置",
|
||||||
"sandboxDescription": "啟用沙箱模式以測試支付"
|
"configInfo": "配置資訊",
|
||||||
},
|
"configPlaceholder": "請填寫提供的 {field} 配置",
|
||||||
|
"confirm": "確認",
|
||||||
|
"confirmDelete": "確認刪除",
|
||||||
|
"copy": "複製",
|
||||||
|
"copySuccess": "複製成功",
|
||||||
|
"create": "新增付款方式",
|
||||||
|
"createPayment": "新增付款方式",
|
||||||
|
"createSuccess": "創建成功",
|
||||||
|
"delete": "刪除",
|
||||||
|
"deleteSuccess": "刪除成功",
|
||||||
|
"deleteWarning": "您確定要刪除此付款方式嗎?此操作無法撤銷。",
|
||||||
|
"description": "描述",
|
||||||
|
"descriptionPlaceholder": "輸入付款方式描述",
|
||||||
|
"domain": "域名",
|
||||||
|
"domainPlaceholder": "可選,例如 {example}",
|
||||||
|
"edit": "編輯",
|
||||||
|
"editPayment": "編輯付款方式",
|
||||||
"enable": "啟用",
|
"enable": "啟用",
|
||||||
"enableDescription": "啟用此支付方式",
|
"feeAmount": "固定金額",
|
||||||
"epay": {
|
|
||||||
"key": "KEY",
|
|
||||||
"pid": "PID",
|
|
||||||
"url": "URL"
|
|
||||||
},
|
|
||||||
"feeMode": "收費方式",
|
"feeMode": "收費方式",
|
||||||
"feeModeDescription": "額外手續費的收費方式",
|
|
||||||
"feeModeItems": ["無手續費", "百分比", "固定金額", "以上兩種"],
|
|
||||||
"feePercent": "百分比費用(可選)",
|
"feePercent": "百分比費用(可選)",
|
||||||
"feePercentDescription": "在訂單金額之上添加費用",
|
|
||||||
"fixedFee": "固定費用(可選)",
|
"fixedFee": "固定費用(可選)",
|
||||||
"fixedFeeDescription": "在訂單金額之上添加固定費用",
|
"handlingFee": "手續費",
|
||||||
"iconUrl": "圖示 URL(可選)",
|
"icon": "圖標",
|
||||||
"iconUrlDescription": "用於前端顯示",
|
"iconPlaceholder": "輸入圖標網址",
|
||||||
"inputPlaceholder": "請輸入",
|
"mark": "標識",
|
||||||
"key": "KEY",
|
"markPlaceholder": "輸入付款方式標識",
|
||||||
"notifyUrl": "自訂通知域(可選)",
|
"name": "名稱",
|
||||||
"notifyUrlDescription": "閘道通知將發送到此網域",
|
"namePlaceholder": "輸入付款方式名稱",
|
||||||
"payForRecommendations": "支付推薦",
|
"nameRequired": "名稱為必填",
|
||||||
"pid": "PID",
|
"noFee": "無費用",
|
||||||
"saveSuccess": "儲存成功",
|
"notify_url": "通知網址",
|
||||||
"showName": "顯示名稱",
|
"paymentManagement": "付款管理",
|
||||||
"showNameDescription": "用於前端顯示",
|
"percentFee": "百分比",
|
||||||
"stripe": {
|
"platform": "平台",
|
||||||
"publicKey": "PUBLIC KEY",
|
"searchPlaceholder": "輸入搜索詞",
|
||||||
"secretKey": "SECRET KEY",
|
"selectPlatform": "選擇平台",
|
||||||
"webhookSecret": "WEBHOOK 密鑰簽名"
|
"selectType": "選擇付款類型",
|
||||||
},
|
"submit": "提交",
|
||||||
"url": "URL",
|
"type": "類型",
|
||||||
"wechatPay": "微信支付"
|
"updateSuccess": "更新成功"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,20 +2,12 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
/** Get alipay f2f payment config GET /v1/admin/payment/alipay_f2f */
|
/** Update Payment Method PUT /v1/admin/payment/ */
|
||||||
export async function getAlipayF2FPaymentConfig(options?: { [key: string]: any }) {
|
export async function updatePaymentMethod(
|
||||||
return request<API.Response & { data?: API.PaymentConfig }>('/v1/admin/payment/alipay_f2f', {
|
body: API.UpdatePaymentMethodRequest,
|
||||||
method: 'GET',
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Update alipay f2f payment config PUT /v1/admin/payment/alipay_f2f */
|
|
||||||
export async function updateAlipayF2FPaymentConfig(
|
|
||||||
body: API.UpdateAlipayF2fRequest,
|
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<API.Response & { data?: any }>('/v1/admin/payment/alipay_f2f', {
|
return request<API.Response & { data?: API.PaymentConfig }>('/v1/admin/payment/', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -25,85 +17,58 @@ export async function updateAlipayF2FPaymentConfig(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get all payment config GET /v1/admin/payment/all */
|
/** Create Payment Method POST /v1/admin/payment/ */
|
||||||
export async function getAllPaymentConfig(options?: { [key: string]: any }) {
|
export async function createPaymentMethod(
|
||||||
return request<API.Response & { data?: API.GetAllPaymentConfigResponse }>(
|
body: API.CreatePaymentMethodRequest,
|
||||||
'/v1/admin/payment/all',
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.Response & { data?: API.PaymentConfig }>('/v1/admin/payment/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delete Payment Method DELETE /v1/admin/payment/ */
|
||||||
|
export async function deletePaymentMethod(
|
||||||
|
body: API.DeletePaymentMethodRequest,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.Response & { data?: any }>('/v1/admin/payment/', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Payment Method List GET /v1/admin/payment/list */
|
||||||
|
export async function getPaymentMethodList(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.GetPaymentMethodListParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.Response & { data?: API.GetPaymentMethodListResponse }>(
|
||||||
|
'/v1/admin/payment/list',
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get epay payment config GET /v1/admin/payment/epay */
|
/** Get supported payment platform GET /v1/admin/payment/platform */
|
||||||
export async function getEpayPaymentConfig(options?: { [key: string]: any }) {
|
export async function getPaymentPlatform(options?: { [key: string]: any }) {
|
||||||
return request<API.Response & { data?: API.PaymentConfig }>('/v1/admin/payment/epay', {
|
return request<API.Response & { data?: API.PlatformResponse }>('/v1/admin/payment/platform', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update epay payment config PUT /v1/admin/payment/epay */
|
|
||||||
export async function updateEpayPaymentConfig(
|
|
||||||
body: API.UpdateEpayRequest,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.Response & { data?: any }>('/v1/admin/payment/epay', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get stripe alipay payment config GET /v1/admin/payment/stripe_alipay */
|
|
||||||
export async function getStripeAlipayPaymentConfig(options?: { [key: string]: any }) {
|
|
||||||
return request<API.Response & { data?: API.PaymentConfig }>('/v1/admin/payment/stripe_alipay', {
|
|
||||||
method: 'GET',
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Update stripe alipay payment config PUT /v1/admin/payment/stripe_alipay */
|
|
||||||
export async function updateStripeAlipayPaymentConfig(
|
|
||||||
body: API.UpdateStripeRequest,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.Response & { data?: any }>('/v1/admin/payment/stripe_alipay', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get stripe wechat pay payment config GET /v1/admin/payment/stripe_wechat_pay */
|
|
||||||
export async function getStripeWeChatPayPaymentConfig(options?: { [key: string]: any }) {
|
|
||||||
return request<API.Response & { data?: API.PaymentConfig }>(
|
|
||||||
'/v1/admin/payment/stripe_wechat_pay',
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Update stripe wechat pay payment config PUT /v1/admin/payment/stripe_wechat_pay */
|
|
||||||
export async function updateStripeWeChatPayPaymentConfig(
|
|
||||||
body: API.UpdateStripeRequest,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.Response & { data?: any }>('/v1/admin/payment/stripe_wechat_pay', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
203
apps/admin/services/admin/typings.d.ts
vendored
203
apps/admin/services/admin/typings.d.ts
vendored
@ -13,12 +13,8 @@ declare namespace API {
|
|||||||
updated_at: number;
|
updated_at: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AlipayF2FConfig = {
|
type AlipayNotifyResponse = {
|
||||||
app_id: string;
|
return_code: string;
|
||||||
private_key: string;
|
|
||||||
public_key: string;
|
|
||||||
invoice_name: string;
|
|
||||||
sandbox: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Announcement = {
|
type Announcement = {
|
||||||
@ -120,17 +116,6 @@ declare namespace API {
|
|||||||
ids: number[];
|
ids: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type CheckoutOrderRequest = {
|
|
||||||
orderNo: string;
|
|
||||||
returnUrl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CheckoutOrderResponse = {
|
|
||||||
type: string;
|
|
||||||
checkout_url?: string;
|
|
||||||
stripe?: StripePayment;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CloseOrderRequest = {
|
type CloseOrderRequest = {
|
||||||
orderNo: string;
|
orderNo: string;
|
||||||
};
|
};
|
||||||
@ -239,12 +224,25 @@ declare namespace API {
|
|||||||
coupon_discount?: number;
|
coupon_discount?: number;
|
||||||
commission: number;
|
commission: number;
|
||||||
fee_amount: number;
|
fee_amount: number;
|
||||||
method?: string;
|
payment_id: number;
|
||||||
trade_no?: string;
|
trade_no?: string;
|
||||||
status?: number;
|
status?: number;
|
||||||
subscribe_id?: number;
|
subscribe_id?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CreatePaymentMethodRequest = {
|
||||||
|
name: string;
|
||||||
|
platform: string;
|
||||||
|
description: string;
|
||||||
|
icon?: string;
|
||||||
|
domain?: string;
|
||||||
|
config: Record<string, any>;
|
||||||
|
fee_mode: number;
|
||||||
|
fee_percent?: number;
|
||||||
|
fee_amount?: number;
|
||||||
|
enable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type CreateRuleGroupRequest = {
|
type CreateRuleGroupRequest = {
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
@ -352,6 +350,10 @@ declare namespace API {
|
|||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DeletePaymentMethodRequest = {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
type DeleteRuleGroupRequest = {
|
type DeleteRuleGroupRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
@ -398,10 +400,17 @@ declare namespace API {
|
|||||||
domain_suffix_list: string;
|
domain_suffix_list: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EpayConfig = {
|
type EPayNotifyRequest = {
|
||||||
pid: string;
|
pid: number;
|
||||||
url: string;
|
trade_no: string;
|
||||||
key: string;
|
out_trade_no: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
money: string;
|
||||||
|
trade_status: string;
|
||||||
|
param: string;
|
||||||
|
sign: string;
|
||||||
|
sign_type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Follow = {
|
type Follow = {
|
||||||
@ -440,10 +449,6 @@ declare namespace API {
|
|||||||
list: Ads[];
|
list: Ads[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetAllPaymentConfigResponse = {
|
|
||||||
list: PaymentConfig[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type GetAnnouncementListParams = {
|
type GetAnnouncementListParams = {
|
||||||
page: number;
|
page: number;
|
||||||
size: number;
|
size: number;
|
||||||
@ -488,7 +493,7 @@ declare namespace API {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type GetAvailablePaymentMethodsResponse = {
|
type GetAvailablePaymentMethodsResponse = {
|
||||||
list: PaymentConfig[];
|
list: PaymentMethod[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetCouponListParams = {
|
type GetCouponListParams = {
|
||||||
@ -625,6 +630,27 @@ declare namespace API {
|
|||||||
list: Order[];
|
list: Order[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetPaymentMethodListParams = {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
platform?: string;
|
||||||
|
search?: string;
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetPaymentMethodListRequest = {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
platform?: string;
|
||||||
|
search?: string;
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetPaymentMethodListResponse = {
|
||||||
|
total: number;
|
||||||
|
list: PaymentMethodDetail[];
|
||||||
|
};
|
||||||
|
|
||||||
type GetRuleGroupResponse = {
|
type GetRuleGroupResponse = {
|
||||||
total: number;
|
total: number;
|
||||||
list: ServerRuleGroup[];
|
list: ServerRuleGroup[];
|
||||||
@ -797,6 +823,8 @@ declare namespace API {
|
|||||||
size: number;
|
size: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
subscribe_id: number;
|
subscribe_id: number;
|
||||||
|
start_time: number;
|
||||||
|
end_time: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetUserSubscribeTrafficLogsRequest = {
|
type GetUserSubscribeTrafficLogsRequest = {
|
||||||
@ -804,6 +832,8 @@ declare namespace API {
|
|||||||
size: number;
|
size: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
subscribe_id: number;
|
subscribe_id: number;
|
||||||
|
start_time: number;
|
||||||
|
end_time: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetUserSubscribeTrafficLogsResponse = {
|
type GetUserSubscribeTrafficLogsResponse = {
|
||||||
@ -891,7 +921,7 @@ declare namespace API {
|
|||||||
coupon: string;
|
coupon: string;
|
||||||
coupon_discount: number;
|
coupon_discount: number;
|
||||||
commission?: number;
|
commission?: number;
|
||||||
method: string;
|
payment: PaymentMethod;
|
||||||
fee_amount: number;
|
fee_amount: number;
|
||||||
trade_no: string;
|
trade_no: string;
|
||||||
status: number;
|
status: number;
|
||||||
@ -913,6 +943,7 @@ declare namespace API {
|
|||||||
coupon: string;
|
coupon: string;
|
||||||
coupon_discount: number;
|
coupon_discount: number;
|
||||||
commission?: number;
|
commission?: number;
|
||||||
|
payment: PaymentMethod;
|
||||||
method: string;
|
method: string;
|
||||||
fee_amount: number;
|
fee_amount: number;
|
||||||
trade_no: string;
|
trade_no: string;
|
||||||
@ -934,7 +965,8 @@ declare namespace API {
|
|||||||
type PaymentConfig = {
|
type PaymentConfig = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
mark: string;
|
platform: string;
|
||||||
|
description: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
config: Record<string, any>;
|
config: Record<string, any>;
|
||||||
@ -944,6 +976,32 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PaymentMethod = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
platform: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
fee_mode: number;
|
||||||
|
fee_percent: number;
|
||||||
|
fee_amount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PaymentMethodDetail = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
platform: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
domain: string;
|
||||||
|
config: Record<string, any>;
|
||||||
|
fee_mode: number;
|
||||||
|
fee_percent: number;
|
||||||
|
fee_amount: number;
|
||||||
|
enable: boolean;
|
||||||
|
notify_url: string;
|
||||||
|
};
|
||||||
|
|
||||||
type PlatformInfo = {
|
type PlatformInfo = {
|
||||||
platform: string;
|
platform: string;
|
||||||
platform_url: string;
|
platform_url: string;
|
||||||
@ -986,7 +1044,7 @@ declare namespace API {
|
|||||||
type PurchaseOrderRequest = {
|
type PurchaseOrderRequest = {
|
||||||
subscribe_id: number;
|
subscribe_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
coupon?: string;
|
coupon?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1029,9 +1087,24 @@ declare namespace API {
|
|||||||
list: OrderDetail[];
|
list: OrderDetail[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type QuerySubscribeGroupListResponse = {
|
||||||
|
list: SubscribeGroup[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QuerySubscribeListResponse = {
|
||||||
|
list: Subscribe[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QueryUserAffiliateListResponse = {
|
||||||
|
list: UserAffiliate[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
type RechargeOrderRequest = {
|
type RechargeOrderRequest = {
|
||||||
amount: number;
|
amount: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RechargeOrderResponse = {
|
type RechargeOrderResponse = {
|
||||||
@ -1052,7 +1125,7 @@ declare namespace API {
|
|||||||
type RenewalOrderRequest = {
|
type RenewalOrderRequest = {
|
||||||
user_subscribe_id: number;
|
user_subscribe_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
coupon?: string;
|
coupon?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1062,7 +1135,7 @@ declare namespace API {
|
|||||||
|
|
||||||
type ResetTrafficOrderRequest = {
|
type ResetTrafficOrderRequest = {
|
||||||
user_subscribe_id: number;
|
user_subscribe_id: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResetTrafficOrderResponse = {
|
type ResetTrafficOrderResponse = {
|
||||||
@ -1187,13 +1260,6 @@ declare namespace API {
|
|||||||
sort: number;
|
sort: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StripeConfig = {
|
|
||||||
public_key: string;
|
|
||||||
secret_key: string;
|
|
||||||
webhook_secret: string;
|
|
||||||
payment: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StripePayment = {
|
type StripePayment = {
|
||||||
method: string;
|
method: string;
|
||||||
client_secret: string;
|
client_secret: string;
|
||||||
@ -1365,19 +1431,6 @@ declare namespace API {
|
|||||||
status: number;
|
status: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UpdateAlipayF2fRequest = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
mark: string;
|
|
||||||
icon?: string;
|
|
||||||
domain?: string;
|
|
||||||
config: AlipayF2FConfig;
|
|
||||||
fee_mode: number;
|
|
||||||
fee_percent?: number;
|
|
||||||
fee_amount?: number;
|
|
||||||
enable: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UpdateAnnouncementEnableRequest = {
|
type UpdateAnnouncementEnableRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
@ -1441,19 +1494,6 @@ declare namespace API {
|
|||||||
show: boolean;
|
show: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UpdateEpayRequest = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
mark: string;
|
|
||||||
icon?: string;
|
|
||||||
domain?: string;
|
|
||||||
config: EpayConfig;
|
|
||||||
fee_mode: number;
|
|
||||||
fee_percent?: number;
|
|
||||||
fee_amount?: number;
|
|
||||||
enable: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UpdateNodeGroupRequest = {
|
type UpdateNodeGroupRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@ -1481,10 +1521,24 @@ declare namespace API {
|
|||||||
type UpdateOrderStatusRequest = {
|
type UpdateOrderStatusRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
status: number;
|
status: number;
|
||||||
method?: string;
|
payment_id?: number;
|
||||||
trade_no?: string;
|
trade_no?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UpdatePaymentMethodRequest = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
platform: string;
|
||||||
|
description: string;
|
||||||
|
icon?: string;
|
||||||
|
domain?: string;
|
||||||
|
config: Record<string, any>;
|
||||||
|
fee_mode: number;
|
||||||
|
fee_percent?: number;
|
||||||
|
fee_amount?: number;
|
||||||
|
enable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type UpdateRuleGroupRequest = {
|
type UpdateRuleGroupRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@ -1493,19 +1547,6 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UpdateStripeRequest = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
mark: string;
|
|
||||||
icon?: string;
|
|
||||||
domain?: string;
|
|
||||||
config: StripeConfig;
|
|
||||||
fee_mode: number;
|
|
||||||
fee_percent?: number;
|
|
||||||
fee_amount?: number;
|
|
||||||
enable: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UpdateSubscribeGroupRequest = {
|
type UpdateSubscribeGroupRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@ -1603,8 +1644,8 @@ declare namespace API {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type UserAffiliate = {
|
type UserAffiliate = {
|
||||||
email: string;
|
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
identifier: string;
|
||||||
registered_at: number;
|
registered_at: number;
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,21 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
/** Get Ads GET /v1/common/ads */
|
||||||
|
export async function getAds(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.GetAdsParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.Response & { data?: API.GetAdsResponse }>('/v1/common/ads', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Get Tos Content GET /v1/common/application */
|
/** Get Tos Content GET /v1/common/application */
|
||||||
export async function getApplication(options?: { [key: string]: any }) {
|
export async function getApplication(options?: { [key: string]: any }) {
|
||||||
return request<API.Response & { data?: API.GetAppcationResponse }>('/v1/common/application', {
|
return request<API.Response & { data?: API.GetAppcationResponse }>('/v1/common/application', {
|
||||||
@ -58,17 +73,6 @@ export async function getStat(options?: { [key: string]: any }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get Subscription GET /v1/common/site/subscribe */
|
|
||||||
export async function getSubscription(options?: { [key: string]: any }) {
|
|
||||||
return request<API.Response & { data?: API.GetSubscriptionResponse }>(
|
|
||||||
'/v1/common/site/subscribe',
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get Tos Content GET /v1/common/site/tos */
|
/** Get Tos Content GET /v1/common/site/tos */
|
||||||
export async function getTos(options?: { [key: string]: any }) {
|
export async function getTos(options?: { [key: string]: any }) {
|
||||||
return request<API.Response & { data?: API.GetTosResponse }>('/v1/common/site/tos', {
|
return request<API.Response & { data?: API.GetTosResponse }>('/v1/common/site/tos', {
|
||||||
|
|||||||
130
apps/admin/services/common/typings.d.ts
vendored
130
apps/admin/services/common/typings.d.ts
vendored
@ -13,6 +13,10 @@ declare namespace API {
|
|||||||
updated_at: number;
|
updated_at: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AlipayNotifyResponse = {
|
||||||
|
return_code: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Announcement = {
|
type Announcement = {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
@ -90,17 +94,6 @@ declare namespace API {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CheckoutOrderRequest = {
|
|
||||||
orderNo: string;
|
|
||||||
returnUrl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CheckoutOrderResponse = {
|
|
||||||
type: string;
|
|
||||||
checkout_url?: string;
|
|
||||||
stripe?: StripePayment;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CheckUserParams = {
|
type CheckUserParams = {
|
||||||
email: string;
|
email: string;
|
||||||
};
|
};
|
||||||
@ -160,6 +153,19 @@ declare namespace API {
|
|||||||
domain_suffix_list: string;
|
domain_suffix_list: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type EPayNotifyRequest = {
|
||||||
|
pid: number;
|
||||||
|
trade_no: string;
|
||||||
|
out_trade_no: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
money: string;
|
||||||
|
trade_status: string;
|
||||||
|
param: string;
|
||||||
|
sign: string;
|
||||||
|
sign_type: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Follow = {
|
type Follow = {
|
||||||
id: number;
|
id: number;
|
||||||
ticket_id: number;
|
ticket_id: number;
|
||||||
@ -169,13 +175,27 @@ declare namespace API {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetAdsParams = {
|
||||||
|
device: string;
|
||||||
|
position: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetAdsRequest = {
|
||||||
|
device: string;
|
||||||
|
position: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetAdsResponse = {
|
||||||
|
list: Ads[];
|
||||||
|
};
|
||||||
|
|
||||||
type GetAppcationResponse = {
|
type GetAppcationResponse = {
|
||||||
config: ApplicationConfig;
|
config: ApplicationConfig;
|
||||||
applications: ApplicationResponseInfo[];
|
applications: ApplicationResponseInfo[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetAvailablePaymentMethodsResponse = {
|
type GetAvailablePaymentMethodsResponse = {
|
||||||
list: PaymentConfig[];
|
list: PaymentMethod[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetGlobalConfigResponse = {
|
type GetGlobalConfigResponse = {
|
||||||
@ -187,6 +207,7 @@ declare namespace API {
|
|||||||
subscribe: SubscribeConfig;
|
subscribe: SubscribeConfig;
|
||||||
verify_code: PubilcVerifyCodeConfig;
|
verify_code: PubilcVerifyCodeConfig;
|
||||||
oauth_methods: string[];
|
oauth_methods: string[];
|
||||||
|
web_ad: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetStatResponse = {
|
type GetStatResponse = {
|
||||||
@ -196,14 +217,24 @@ declare namespace API {
|
|||||||
protocol: string[];
|
protocol: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetSubscriptionResponse = {
|
|
||||||
list: Subscribe[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type GetTosResponse = {
|
type GetTosResponse = {
|
||||||
tos_content: string;
|
tos_content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetUserSubscribeTrafficLogsRequest = {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
user_id: number;
|
||||||
|
subscribe_id: number;
|
||||||
|
start_time: number;
|
||||||
|
end_time: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetUserSubscribeTrafficLogsResponse = {
|
||||||
|
list: TrafficLog[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
type GoogleLoginCallbackRequest = {
|
type GoogleLoginCallbackRequest = {
|
||||||
code: string;
|
code: string;
|
||||||
state: string;
|
state: string;
|
||||||
@ -297,7 +328,7 @@ declare namespace API {
|
|||||||
coupon: string;
|
coupon: string;
|
||||||
coupon_discount: number;
|
coupon_discount: number;
|
||||||
commission?: number;
|
commission?: number;
|
||||||
method: string;
|
payment: PaymentMethod;
|
||||||
fee_amount: number;
|
fee_amount: number;
|
||||||
trade_no: string;
|
trade_no: string;
|
||||||
status: number;
|
status: number;
|
||||||
@ -319,6 +350,7 @@ declare namespace API {
|
|||||||
coupon: string;
|
coupon: string;
|
||||||
coupon_discount: number;
|
coupon_discount: number;
|
||||||
commission?: number;
|
commission?: number;
|
||||||
|
payment: PaymentMethod;
|
||||||
method: string;
|
method: string;
|
||||||
fee_amount: number;
|
fee_amount: number;
|
||||||
trade_no: string;
|
trade_no: string;
|
||||||
@ -332,7 +364,8 @@ declare namespace API {
|
|||||||
type PaymentConfig = {
|
type PaymentConfig = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
mark: string;
|
platform: string;
|
||||||
|
description: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
config: Record<string, any>;
|
config: Record<string, any>;
|
||||||
@ -342,6 +375,42 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PaymentMethod = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
platform: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
fee_mode: number;
|
||||||
|
fee_percent: number;
|
||||||
|
fee_amount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PaymentMethodDetail = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
platform: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
domain: string;
|
||||||
|
config: Record<string, any>;
|
||||||
|
fee_mode: number;
|
||||||
|
fee_percent: number;
|
||||||
|
fee_amount: number;
|
||||||
|
enable: boolean;
|
||||||
|
notify_url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlatformInfo = {
|
||||||
|
platform: string;
|
||||||
|
platform_url: string;
|
||||||
|
platform_field_description: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlatformResponse = {
|
||||||
|
list: PlatformInfo[];
|
||||||
|
};
|
||||||
|
|
||||||
type PreOrderResponse = {
|
type PreOrderResponse = {
|
||||||
price: number;
|
price: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
@ -374,7 +443,7 @@ declare namespace API {
|
|||||||
type PurchaseOrderRequest = {
|
type PurchaseOrderRequest = {
|
||||||
subscribe_id: number;
|
subscribe_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
coupon?: string;
|
coupon?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -417,9 +486,24 @@ declare namespace API {
|
|||||||
list: OrderDetail[];
|
list: OrderDetail[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type QuerySubscribeGroupListResponse = {
|
||||||
|
list: SubscribeGroup[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QuerySubscribeListResponse = {
|
||||||
|
list: Subscribe[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QueryUserAffiliateListResponse = {
|
||||||
|
list: UserAffiliate[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
type RechargeOrderRequest = {
|
type RechargeOrderRequest = {
|
||||||
amount: number;
|
amount: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RechargeOrderResponse = {
|
type RechargeOrderResponse = {
|
||||||
@ -440,7 +524,7 @@ declare namespace API {
|
|||||||
type RenewalOrderRequest = {
|
type RenewalOrderRequest = {
|
||||||
user_subscribe_id: number;
|
user_subscribe_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
coupon?: string;
|
coupon?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -457,7 +541,7 @@ declare namespace API {
|
|||||||
|
|
||||||
type ResetTrafficOrderRequest = {
|
type ResetTrafficOrderRequest = {
|
||||||
user_subscribe_id: number;
|
user_subscribe_id: number;
|
||||||
payment: string;
|
payment: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResetTrafficOrderResponse = {
|
type ResetTrafficOrderResponse = {
|
||||||
@ -737,8 +821,8 @@ declare namespace API {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type UserAffiliate = {
|
type UserAffiliate = {
|
||||||
email: string;
|
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
identifier: string;
|
||||||
registered_at: number;
|
registered_at: number;
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ NEXT_PUBLIC_DEFAULT_LANGUAGE=en-US
|
|||||||
# Site URL and API URL
|
# Site URL and API URL
|
||||||
NEXT_PUBLIC_SITE_URL=https://user.ppanel.dev
|
NEXT_PUBLIC_SITE_URL=https://user.ppanel.dev
|
||||||
NEXT_PUBLIC_API_URL=https://api.ppanel.dev
|
NEXT_PUBLIC_API_URL=https://api.ppanel.dev
|
||||||
|
NEXT_PUBLIC_CDN_URL=https://cdn.jsdelivr.net
|
||||||
|
|
||||||
# Home Page Settings
|
# Home Page Settings
|
||||||
NEXT_PUBLIC_HOME_USER_COUNT=999
|
NEXT_PUBLIC_HOME_USER_COUNT=999
|
||||||
|
|||||||
@ -32,8 +32,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/componen
|
|||||||
import { Separator } from '@workspace/ui/components/separator';
|
import { Separator } from '@workspace/ui/components/separator';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
import { formatDate, isBrowser } from '@workspace/ui/utils';
|
import { differenceInDays, formatDate, isBrowser } from '@workspace/ui/utils';
|
||||||
import { differenceInDays } from 'date-fns';
|
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@ -58,7 +57,11 @@ export default function Content() {
|
|||||||
|
|
||||||
const [protocol, setProtocol] = useState('');
|
const [protocol, setProtocol] = useState('');
|
||||||
|
|
||||||
const { data: userSubscribe = [], refetch } = useQuery({
|
const {
|
||||||
|
data: userSubscribe = [],
|
||||||
|
refetch,
|
||||||
|
isLoading,
|
||||||
|
} = useQuery({
|
||||||
queryKey: ['queryUserSubscribe'],
|
queryKey: ['queryUserSubscribe'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await queryUserSubscribe();
|
const { data } = await queryUserSubscribe();
|
||||||
@ -89,10 +92,27 @@ export default function Content() {
|
|||||||
<>
|
<>
|
||||||
{userSubscribe.length ? (
|
{userSubscribe.length ? (
|
||||||
<>
|
<>
|
||||||
<h2 className='flex items-center gap-1.5 font-semibold'>
|
<div className='flex items-center justify-between'>
|
||||||
<Icon icon='uil:servers' className='size-5' />
|
<h2 className='flex items-center gap-1.5 font-semibold'>
|
||||||
{t('mySubscriptions')}
|
<Icon icon='uil:servers' className='size-5' />
|
||||||
</h2>
|
{t('mySubscriptions')}
|
||||||
|
</h2>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<Button
|
||||||
|
size='sm'
|
||||||
|
variant='outline'
|
||||||
|
onClick={() => {
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
className={isLoading ? 'animate-pulse' : ''}
|
||||||
|
>
|
||||||
|
<Icon icon='uil:sync' />
|
||||||
|
</Button>
|
||||||
|
<Button size='sm' asChild>
|
||||||
|
<Link href='/subscribe'>{t('purchaseSubscription')}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className='flex flex-wrap justify-between gap-4'>
|
<div className='flex flex-wrap justify-between gap-4'>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={platform}
|
value={platform}
|
||||||
@ -206,12 +226,13 @@ export default function Content() {
|
|||||||
<span className='text-2xl font-semibold'>
|
<span className='text-2xl font-semibold'>
|
||||||
{item.reset_time
|
{item.reset_time
|
||||||
? differenceInDays(new Date(item.reset_time), new Date())
|
? differenceInDays(new Date(item.reset_time), new Date())
|
||||||
: t('unknown')}
|
: t('noReset')}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span className='text-muted-foreground'>{t('expirationDays')}</span>
|
<span className='text-muted-foreground'>{t('expirationDays')}</span>
|
||||||
<span className='text-2xl font-semibold'>
|
<span className='text-2xl font-semibold'>
|
||||||
|
{}
|
||||||
{item.expire_time
|
{item.expire_time
|
||||||
? differenceInDays(new Date(item.expire_time), new Date()) || t('unknown')
|
? differenceInDays(new Date(item.expire_time), new Date()) || t('unknown')
|
||||||
: t('noLimit')}
|
: t('noLimit')}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Display } from '@/components/display';
|
import { Display } from '@/components/display';
|
||||||
|
import StripePayment from '@/components/payment/stripe';
|
||||||
import { SubscribeBilling } from '@/components/subscribe/billing';
|
import { SubscribeBilling } from '@/components/subscribe/billing';
|
||||||
import { SubscribeDetail } from '@/components/subscribe/detail';
|
import { SubscribeDetail } from '@/components/subscribe/detail';
|
||||||
import useGlobalStore from '@/config/use-global';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { checkoutOrder, queryOrderDetail } from '@/services/user/order';
|
import { queryOrderDetail } from '@/services/user/order';
|
||||||
|
import { purchaseCheckout } from '@/services/user/portal';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Badge } from '@workspace/ui/components/badge';
|
import { Badge } from '@workspace/ui/components/badge';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
@ -24,7 +26,6 @@ import { useTranslations } from 'next-intl';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeCanvas } from 'qrcode.react';
|
import { QRCodeCanvas } from 'qrcode.react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import StripePayment from './stripe';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('order');
|
const t = useTranslations('order');
|
||||||
@ -48,9 +49,15 @@ export default function Page() {
|
|||||||
|
|
||||||
const { data: payment } = useQuery({
|
const { data: payment } = useQuery({
|
||||||
enabled: !!orderNo && data?.status === 1,
|
enabled: !!orderNo && data?.status === 1,
|
||||||
queryKey: ['checkoutOrder', orderNo],
|
queryKey: ['purchaseCheckout', orderNo],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await checkoutOrder({ orderNo: orderNo!, returnUrl: window.location.href });
|
const { data } = await purchaseCheckout({
|
||||||
|
orderNo: orderNo!,
|
||||||
|
returnUrl: window.location.href,
|
||||||
|
});
|
||||||
|
if (data.data?.type === 'url' && data.data.checkout_url) {
|
||||||
|
window.open(data.data.checkout_url, '_blank');
|
||||||
|
}
|
||||||
return data?.data;
|
return data?.data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -99,7 +106,7 @@ export default function Page() {
|
|||||||
<dl className='grid gap-3'>
|
<dl className='grid gap-3'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<dt className='text-muted-foreground'>
|
<dt className='text-muted-foreground'>
|
||||||
{data?.method && <Badge>{t(`methods.${data?.method}`)}</Badge>}
|
<Badge>{data?.payment.name || data?.payment.platform}</Badge>
|
||||||
</dt>
|
</dt>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
@ -169,7 +176,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data?.status === 1 && payment?.type === 'link' && (
|
{data?.status === 1 && payment?.type === 'url' && (
|
||||||
<div className='flex flex-col items-center gap-8 text-center'>
|
<div className='flex flex-col items-center gap-8 text-center'>
|
||||||
<h3 className='text-2xl font-bold tracking-tight'>{t('waitingForPayment')}</h3>
|
<h3 className='text-2xl font-bold tracking-tight'>{t('waitingForPayment')}</h3>
|
||||||
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
|
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
|
||||||
@ -218,17 +225,17 @@ export default function Page() {
|
|||||||
|
|
||||||
{data?.status === 1 && payment?.type === 'stripe' && (
|
{data?.status === 1 && payment?.type === 'stripe' && (
|
||||||
<div className='flex flex-col items-center gap-8 text-center'>
|
<div className='flex flex-col items-center gap-8 text-center'>
|
||||||
<h3 className='text-2xl font-bold tracking-tight'>{t('scanToPay')}</h3>
|
<h3 className='text-2xl font-bold tracking-tight'>{t('waitingForPayment')}</h3>
|
||||||
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
|
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
|
||||||
{payment.stripe && <StripePayment {...payment.stripe} />}
|
{payment.stripe && <StripePayment {...payment.stripe} />}
|
||||||
<div className='flex gap-4'>
|
{/* <div className='flex gap-4'>
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
<Link href='/subscribe'>{t('productList')}</Link>
|
<Link href='/subscribe'>{t('productList')}</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant='outline'>
|
<Button asChild variant='outline'>
|
||||||
<Link href='/order'>{t('orderList')}</Link>
|
<Link href='/order'>{t('orderList')}</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -1,109 +0,0 @@
|
|||||||
import { Elements, useStripe } from '@stripe/react-stripe-js';
|
|
||||||
import { loadStripe, PaymentIntentResult } from '@stripe/stripe-js';
|
|
||||||
import { QRCodeCanvas } from 'qrcode.react';
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
interface StripePaymentProps {
|
|
||||||
method: string;
|
|
||||||
client_secret: string;
|
|
||||||
publishable_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StripePayment: React.FC<StripePaymentProps> = ({
|
|
||||||
method,
|
|
||||||
client_secret,
|
|
||||||
publishable_key,
|
|
||||||
}) => {
|
|
||||||
const stripePromise = useMemo(() => loadStripe(publishable_key), [publishable_key]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Elements stripe={stripePromise}>
|
|
||||||
<CheckoutForm method={method} client_secret={client_secret} />
|
|
||||||
</Elements>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CheckoutForm: React.FC<Omit<StripePaymentProps, 'publishable_key'>> = ({
|
|
||||||
client_secret,
|
|
||||||
method,
|
|
||||||
}) => {
|
|
||||||
const stripe = useStripe();
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
||||||
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
|
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
|
|
||||||
const handleError = useCallback((message: string) => {
|
|
||||||
setErrorMessage(message);
|
|
||||||
setIsSubmitted(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const confirmPayment = useCallback(async (): Promise<PaymentIntentResult | null> => {
|
|
||||||
if (!stripe) {
|
|
||||||
handleError('Stripe.js is not loaded.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method === 'alipay') {
|
|
||||||
return await stripe.confirmAlipayPayment(
|
|
||||||
client_secret,
|
|
||||||
{ return_url: window.location.href },
|
|
||||||
{ handleActions: false },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await stripe.confirmWechatPayPayment(
|
|
||||||
client_secret,
|
|
||||||
{
|
|
||||||
payment_method_options: { wechat_pay: { client: 'web' } },
|
|
||||||
},
|
|
||||||
{ handleActions: false },
|
|
||||||
);
|
|
||||||
}, [client_secret, method, stripe, handleError]);
|
|
||||||
|
|
||||||
const autoSubmit = useCallback(async () => {
|
|
||||||
if (isSubmitted) return;
|
|
||||||
|
|
||||||
setIsSubmitted(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await confirmPayment();
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
const { error, paymentIntent } = result;
|
|
||||||
if (error) return handleError(error.message!);
|
|
||||||
|
|
||||||
if (paymentIntent?.status === 'requires_action') {
|
|
||||||
const nextAction = paymentIntent.next_action as any;
|
|
||||||
const qrUrl =
|
|
||||||
method === 'alipay'
|
|
||||||
? nextAction?.alipay_handle_redirect?.url
|
|
||||||
: nextAction?.wechat_pay_display_qr_code?.image_url_svg;
|
|
||||||
|
|
||||||
setQrCodeUrl(qrUrl || null);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
handleError('An unexpected error occurred');
|
|
||||||
}
|
|
||||||
}, [confirmPayment, isSubmitted, handleError, method]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
autoSubmit();
|
|
||||||
}, [autoSubmit]);
|
|
||||||
|
|
||||||
return qrCodeUrl ? (
|
|
||||||
<QRCodeCanvas
|
|
||||||
value={qrCodeUrl}
|
|
||||||
size={208}
|
|
||||||
imageSettings={{
|
|
||||||
src: `/payment/${method}.svg`,
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
excavate: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
errorMessage
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StripePayment;
|
|
||||||
@ -21,11 +21,11 @@ export default function Page() {
|
|||||||
<>
|
<>
|
||||||
<Card className='mb-4'>
|
<Card className='mb-4'>
|
||||||
<CardContent className='p-6'>
|
<CardContent className='p-6'>
|
||||||
<h2 className='text-foreground mb-4 text-2xl font-bold'>{t('totalAssets')}</h2>
|
<h2 className='text-foreground mb-4 text-2xl font-bold'>{t('assetOverview')}</h2>
|
||||||
<div className='mb-4'>
|
<div className='mb-4'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div>
|
<div>
|
||||||
<p className='text-sm font-medium'>总资产</p>
|
<p className='text-sm font-medium'>{t('totalAssets')}</p>
|
||||||
<p className='text-3xl font-bold'>
|
<p className='text-3xl font-bold'>
|
||||||
<Display type='currency' value={totalAssets} />
|
<Display type='currency' value={totalAssets} />
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { GlobalMap } from '@/components/main/global-map';
|
import { GlobalMap } from '@/components/main/global-map';
|
||||||
import { Hero } from '@/components/main/hero';
|
import { Hero } from '@/components/main/hero';
|
||||||
import { ProductShowcase } from '@/components/main/product-showcase';
|
import { ProductShowcase } from '@/components/main/product-showcase/index';
|
||||||
import { Stats } from '@/components/main/stats';
|
import { Stats } from '@/components/main/stats';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|||||||
221
apps/user/app/(main)/purchasing/content.tsx
Normal file
221
apps/user/app/(main)/purchasing/content.tsx
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { SubscribeBilling } from '@/components/subscribe/billing';
|
||||||
|
import CouponInput from '@/components/subscribe/coupon-input';
|
||||||
|
import { SubscribeDetail } from '@/components/subscribe/detail';
|
||||||
|
import DurationSelector from '@/components/subscribe/duration-selector';
|
||||||
|
import PaymentMethods from '@/components/subscribe/payment-methods';
|
||||||
|
import useGlobalStore from '@/config/use-global';
|
||||||
|
import { prePurchaseOrder, purchase } from '@/services/user/portal';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import { Card, CardContent, CardHeader } from '@workspace/ui/components/card';
|
||||||
|
import { Separator } from '@workspace/ui/components/separator';
|
||||||
|
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||||
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
|
import { LoaderCircle } from 'lucide-react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useCallback, useEffect, useState, useTransition } from 'react';
|
||||||
|
|
||||||
|
export default function Content({ subscription }: { subscription?: API.Subscribe }) {
|
||||||
|
const t = useTranslations('subscribe');
|
||||||
|
const { common } = useGlobalStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const [params, setParams] = useState<API.PortalPurchaseRequest>({
|
||||||
|
quantity: 1,
|
||||||
|
subscribe_id: 0,
|
||||||
|
payment: -1,
|
||||||
|
coupon: '',
|
||||||
|
platform: 'email',
|
||||||
|
identifier: '',
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
const [loading, startTransition] = useTransition();
|
||||||
|
const [isEmailValid, setIsEmailValid] = useState({
|
||||||
|
valid: false,
|
||||||
|
message: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: order } = useQuery({
|
||||||
|
enabled: !!subscription?.id && !!params.payment,
|
||||||
|
queryKey: ['preCreateOrder', params.coupon, params.quantity, params.payment],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await prePurchaseOrder({
|
||||||
|
...params,
|
||||||
|
subscribe_id: subscription?.id as number,
|
||||||
|
} as API.PrePurchaseOrderRequest);
|
||||||
|
return data.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (subscription) {
|
||||||
|
setParams((prev) => ({
|
||||||
|
...prev,
|
||||||
|
quantity: 1,
|
||||||
|
subscribe_id: subscription?.id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [subscription]);
|
||||||
|
|
||||||
|
const handleChange = useCallback((field: keyof typeof params, value: string | number) => {
|
||||||
|
setParams((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(async () => {
|
||||||
|
startTransition(async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await purchase(params);
|
||||||
|
console.log(data);
|
||||||
|
const { order_no, check_url, type } = data.data!;
|
||||||
|
if (order_no) {
|
||||||
|
if (type === 'link') {
|
||||||
|
window.location.href = check_url!;
|
||||||
|
}
|
||||||
|
router.push(`/purchasing/order?order_no=${order_no}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [params, router, subscription?.id]);
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
return <div className='p-6 text-center'>{t('subscriptionNotFound')}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mx-auto mt-8 flex max-w-4xl flex-col gap-8 md:grid md:grid-cols-2 md:flex-row'>
|
||||||
|
<div className='flex flex-col gap-6'>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>输入要用于 {common.site.site_name} 账户的电子邮件地址</CardHeader>
|
||||||
|
<CardContent className='flex flex-col gap-2'>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<EnhancedInput
|
||||||
|
className={cn({
|
||||||
|
'border-destructive': !isEmailValid.valid && params.identifier !== '',
|
||||||
|
})}
|
||||||
|
placeholder='Email'
|
||||||
|
type='email'
|
||||||
|
value={params.identifier || ''}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const email = value as string;
|
||||||
|
setParams((prev) => ({
|
||||||
|
...prev,
|
||||||
|
identifier: email,
|
||||||
|
}));
|
||||||
|
const reg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!reg.test(email)) {
|
||||||
|
setIsEmailValid({
|
||||||
|
valid: false,
|
||||||
|
message: '请输入有效的邮箱地址',
|
||||||
|
});
|
||||||
|
} else if (common.auth.email.enable_domain_suffix) {
|
||||||
|
const domain = email.split('@')[1];
|
||||||
|
const isValid = common.auth.email?.domain_suffix_list
|
||||||
|
.split('\n')
|
||||||
|
.includes(domain || '');
|
||||||
|
if (!isValid) {
|
||||||
|
setIsEmailValid({
|
||||||
|
valid: false,
|
||||||
|
message: '邮箱域名不在白名单中',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsEmailValid({
|
||||||
|
valid: true,
|
||||||
|
message: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
className={cn('text-muted-foreground text-xs', {
|
||||||
|
'text-destructive': !isEmailValid.valid && params.identifier !== '',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{isEmailValid.message || '请填写您的电子邮件地址。'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{params.identifier && isEmailValid.valid && (
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<EnhancedInput
|
||||||
|
placeholder='Password'
|
||||||
|
type='password'
|
||||||
|
value={params.password || ''}
|
||||||
|
onValueChange={(value) => handleChange('password', value)}
|
||||||
|
/>
|
||||||
|
<p className='text-muted-foreground text-xs'>
|
||||||
|
如果您不填写密码,我们将会自动生成密码并发送到您的邮箱。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* <div>
|
||||||
|
<OAuthMethods />
|
||||||
|
</div> */}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardContent className='grid gap-3 p-6 text-sm'>
|
||||||
|
<h2 className='text-xl font-semibold'>{subscription.name}</h2>
|
||||||
|
<p className='text-muted-foreground'>{subscription.description}</p>
|
||||||
|
<SubscribeDetail
|
||||||
|
subscribe={{
|
||||||
|
...subscription,
|
||||||
|
quantity: params.quantity,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<SubscribeBilling
|
||||||
|
order={{
|
||||||
|
...order,
|
||||||
|
quantity: params.quantity,
|
||||||
|
unit_price: subscription?.unit_price,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-6'>
|
||||||
|
<Card>
|
||||||
|
<CardContent className='p-6'>
|
||||||
|
<div className='grid gap-6'>
|
||||||
|
<DurationSelector
|
||||||
|
quantity={params.quantity!}
|
||||||
|
unitTime={subscription?.unit_time}
|
||||||
|
discounts={subscription?.discount}
|
||||||
|
onChange={(value) => handleChange('quantity', value)}
|
||||||
|
/>
|
||||||
|
<CouponInput
|
||||||
|
coupon={params.coupon}
|
||||||
|
onChange={(value) => handleChange('coupon', value)}
|
||||||
|
/>
|
||||||
|
<PaymentMethods
|
||||||
|
balance={false}
|
||||||
|
value={params.payment!}
|
||||||
|
onChange={(value) => handleChange('payment', value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className='w-full'
|
||||||
|
size='lg'
|
||||||
|
disabled={!isEmailValid.valid || loading}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
{loading && <LoaderCircle className='mr-2 animate-spin' />}
|
||||||
|
{t('buyNow')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
266
apps/user/app/(main)/purchasing/order/page.tsx
Normal file
266
apps/user/app/(main)/purchasing/order/page.tsx
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Display } from '@/components/display';
|
||||||
|
import StripePayment from '@/components/payment/stripe';
|
||||||
|
import { SubscribeBilling } from '@/components/subscribe/billing';
|
||||||
|
import { SubscribeDetail } from '@/components/subscribe/detail';
|
||||||
|
import useGlobalStore from '@/config/use-global';
|
||||||
|
import { purchaseCheckout, queryPurchaseOrder } from '@/services/user/portal';
|
||||||
|
import { setAuthorization } from '@/utils/common';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { Badge } from '@workspace/ui/components/badge';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@workspace/ui/components/card';
|
||||||
|
import { Separator } from '@workspace/ui/components/separator';
|
||||||
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
|
import { formatDate } from '@workspace/ui/utils';
|
||||||
|
import { useCountDown } from 'ahooks';
|
||||||
|
import { addMinutes, format } from 'date-fns';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { QRCodeCanvas } from 'qrcode.react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const t = useTranslations('order');
|
||||||
|
const { getUserInfo } = useGlobalStore();
|
||||||
|
const [orderNo, setOrderNo] = useState<string>();
|
||||||
|
const [enabled, setEnabled] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { data } = useQuery({
|
||||||
|
enabled: enabled,
|
||||||
|
queryKey: ['queryPurchaseOrder', orderNo],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await queryPurchaseOrder({ order_no: orderNo! });
|
||||||
|
if (data?.data?.status !== 1) {
|
||||||
|
setEnabled(false);
|
||||||
|
if (data?.data?.token) {
|
||||||
|
setAuthorization(data?.data?.token);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
await getUserInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data?.data;
|
||||||
|
},
|
||||||
|
refetchInterval: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: payment } = useQuery({
|
||||||
|
enabled: !!orderNo && data?.status === 1,
|
||||||
|
queryKey: ['purchaseCheckout', orderNo],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await purchaseCheckout({
|
||||||
|
orderNo: orderNo!,
|
||||||
|
returnUrl: window.location.href,
|
||||||
|
});
|
||||||
|
if (data.data?.type === 'url' && data.data?.checkout_url) {
|
||||||
|
window.open(data.data.checkout_url, '_blank');
|
||||||
|
}
|
||||||
|
return data?.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
if (searchParams.get('order_no')) {
|
||||||
|
setOrderNo(searchParams.get('order_no')!);
|
||||||
|
setEnabled(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [countDown, formattedRes] = useCountDown({
|
||||||
|
targetDate: data && format(addMinutes(data?.created_at, 15), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { hours, minutes, seconds } = formattedRes;
|
||||||
|
|
||||||
|
const countdownDisplay =
|
||||||
|
countDown > 0 ? (
|
||||||
|
<>
|
||||||
|
{hours.toString().length === 1 ? `0${hours}` : hours} :{' '}
|
||||||
|
{minutes.toString().length === 1 ? `0${minutes}` : minutes} :{' '}
|
||||||
|
{seconds.toString().length === 1 ? `0${seconds}` : seconds}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>{t('timeExpired')}</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className='container lg:mt-16'>
|
||||||
|
<div className='grid gap-4 xl:grid-cols-2'>
|
||||||
|
<Card className='order-2 xl:order-1'>
|
||||||
|
<CardHeader className='bg-muted/50 flex flex-row items-start'>
|
||||||
|
<div className='grid gap-0.5'>
|
||||||
|
<CardTitle className='flex flex-col text-lg'>
|
||||||
|
{t('orderNumber')}
|
||||||
|
<span>{data?.order_no}</span>
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{t('createdAt')}: {formatDate(data?.created_at)}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='grid gap-3 p-6 text-sm'>
|
||||||
|
<div className='font-semibold'>{t('paymentMethod')}</div>
|
||||||
|
<dl className='grid gap-3'>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<dt className='text-muted-foreground'>
|
||||||
|
<Badge>{data?.payment.name || data?.payment.platform}</Badge>
|
||||||
|
</dt>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{data?.status && [1, 2].includes(data.status) && (
|
||||||
|
<SubscribeDetail
|
||||||
|
subscribe={{
|
||||||
|
...data?.subscribe,
|
||||||
|
quantity: data?.quantity,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{data?.status === 3 && (
|
||||||
|
<>
|
||||||
|
<div className='font-semibold'>{t('resetTraffic')}</div>
|
||||||
|
<ul className='grid grid-cols-2 gap-3 *:flex *:items-center *:justify-between lg:grid-cols-1'>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground line-clamp-2 flex-1'>
|
||||||
|
{t('resetPrice')}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<Display type='currency' value={data.amount} />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data?.status === 4 && (
|
||||||
|
<>
|
||||||
|
<div className='font-semibold'>{t('balanceRecharge')}</div>
|
||||||
|
<ul className='grid grid-cols-2 gap-3 *:flex *:items-center *:justify-between lg:grid-cols-1'>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground line-clamp-2 flex-1'>
|
||||||
|
{t('rechargeAmount')}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<Display type='currency' value={data.amount} />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Separator />
|
||||||
|
<SubscribeBilling
|
||||||
|
order={{
|
||||||
|
...data,
|
||||||
|
unit_price: data?.subscribe?.unit_price,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className='order-1 flex flex-auto items-center justify-center xl:order-2'>
|
||||||
|
<CardContent className='py-16'>
|
||||||
|
{data?.status && [2, 5].includes(data?.status) && (
|
||||||
|
<div className='flex flex-col items-center gap-8 text-center'>
|
||||||
|
<h3 className='text-2xl font-bold tracking-tight'>{t('paymentSuccess')}</h3>
|
||||||
|
<Icon icon='mdi:success-circle-outline' className='text-7xl text-green-500' />
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<Button asChild>
|
||||||
|
<Link href='/dashboard'>{t('subscribeNow')}</Link>
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline'>
|
||||||
|
<Link href='/document'>{t('viewDocument')}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data?.status === 1 && payment?.type === 'url' && (
|
||||||
|
<div className='flex flex-col items-center gap-8 text-center'>
|
||||||
|
<h3 className='text-2xl font-bold tracking-tight'>{t('waitingForPayment')}</h3>
|
||||||
|
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
|
||||||
|
<Icon icon='mdi:access-time' className='text-muted-foreground text-7xl' />
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (payment?.checkout_url) {
|
||||||
|
window.location.href = payment?.checkout_url;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('goToPayment')}
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline'>
|
||||||
|
<Link href='/'>{t('productList')}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data?.status === 1 && payment?.type === 'qr' && (
|
||||||
|
<div className='flex flex-col items-center gap-8 text-center'>
|
||||||
|
<h3 className='text-2xl font-bold tracking-tight'>{t('scanToPay')}</h3>
|
||||||
|
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
|
||||||
|
<QRCodeCanvas
|
||||||
|
value={payment?.checkout_url || ''}
|
||||||
|
size={208}
|
||||||
|
imageSettings={{
|
||||||
|
src: `/payment/alipay_f2f.svg`,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
excavate: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<Button asChild>
|
||||||
|
<Link href='/subscribe'>{t('productList')}</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild variant='outline'>
|
||||||
|
<Link href='/order'>{t('orderList')}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data?.status === 1 && payment?.type === 'stripe' && (
|
||||||
|
<div className='flex flex-col items-center gap-8 text-center'>
|
||||||
|
<h3 className='text-2xl font-bold tracking-tight'>{t('waitingForPayment')}</h3>
|
||||||
|
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
|
||||||
|
{payment.stripe && <StripePayment {...payment.stripe} />}
|
||||||
|
{/* <div className='flex gap-4'>
|
||||||
|
<Button asChild>
|
||||||
|
<Link href='/subscribe'>{t('productList')}</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild variant='outline'>
|
||||||
|
<Link href='/order'>{t('orderList')}</Link>
|
||||||
|
</Button>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data?.status && [3, 4].includes(data?.status) && (
|
||||||
|
<div className='flex flex-col items-center gap-8 text-center'>
|
||||||
|
<h3 className='text-2xl font-bold tracking-tight'>{t('orderClosed')}</h3>
|
||||||
|
<Icon icon='mdi:cancel' className='text-7xl text-red-500' />
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<Button asChild>
|
||||||
|
<Link href='/subscribe'>{t('productList')}</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild variant='outline'>
|
||||||
|
<Link href='/order'>{t('orderList')}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
apps/user/app/(main)/purchasing/page.tsx
Normal file
23
apps/user/app/(main)/purchasing/page.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { getSubscription } from '@/services/user/portal';
|
||||||
|
import Content from './content';
|
||||||
|
|
||||||
|
export default async function Page({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<{
|
||||||
|
id: string;
|
||||||
|
}>;
|
||||||
|
}) {
|
||||||
|
const { id } = await searchParams;
|
||||||
|
const { data } = await getSubscription({
|
||||||
|
skipErrorHandler: true,
|
||||||
|
});
|
||||||
|
const subscriptionList = data.data?.list || [];
|
||||||
|
const subscription = subscriptionList.find((item) => item.id === Number(id));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className='container space-y-16'>
|
||||||
|
<Content subscription={subscription} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,13 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { OAuthMethods } from '@/components/auth/oauth-methods';
|
||||||
import LanguageSwitch from '@/components/language-switch';
|
import LanguageSwitch from '@/components/language-switch';
|
||||||
import ThemeSwitch from '@/components/theme-switch';
|
import ThemeSwitch from '@/components/theme-switch';
|
||||||
import useGlobalStore from '@/config/use-global';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { oAuthLogin } from '@/services/common/oauth';
|
|
||||||
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
|
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
|
||||||
import LoginLottie from '@workspace/ui/lotties/login.json';
|
import LoginLottie from '@workspace/ui/lotties/login.json';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Image from 'next/legacy/image';
|
import Image from 'next/legacy/image';
|
||||||
@ -15,18 +13,10 @@ import Link from 'next/link';
|
|||||||
import EmailAuthForm from './email/auth-form';
|
import EmailAuthForm from './email/auth-form';
|
||||||
import PhoneAuthForm from './phone/auth-form';
|
import PhoneAuthForm from './phone/auth-form';
|
||||||
|
|
||||||
const icons = {
|
|
||||||
apple: 'uil:apple',
|
|
||||||
google: 'logos:google-icon',
|
|
||||||
facebook: 'logos:facebook',
|
|
||||||
github: 'uil:github',
|
|
||||||
telegram: 'logos:telegram',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('auth');
|
const t = useTranslations('auth');
|
||||||
const { common } = useGlobalStore();
|
const { common } = useGlobalStore();
|
||||||
const { site, auth, oauth_methods } = common;
|
const { site, auth } = common;
|
||||||
|
|
||||||
const AUTH_METHODS = [
|
const AUTH_METHODS = [
|
||||||
{
|
{
|
||||||
@ -41,10 +31,6 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
].filter((method) => method.enabled);
|
].filter((method) => method.enabled);
|
||||||
|
|
||||||
const OAUTH_METHODS = oauth_methods?.filter(
|
|
||||||
(method) => !['mobile', 'email', 'device'].includes(method),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className='bg-muted/50 flex h-full min-h-screen items-center'>
|
<main className='bg-muted/50 flex h-full min-h-screen items-center'>
|
||||||
<div className='flex size-full flex-auto flex-col lg:flex-row'>
|
<div className='flex size-full flex-auto flex-col lg:flex-row'>
|
||||||
@ -95,38 +81,7 @@ export default function Page() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='py-8'>
|
<div className='py-8'>
|
||||||
{OAUTH_METHODS?.length > 0 && (
|
<OAuthMethods />
|
||||||
<>
|
|
||||||
<div className='after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t'>
|
|
||||||
<span className='bg-background text-muted-foreground relative z-10 px-2'>
|
|
||||||
Or continue with
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='mt-6 flex justify-center gap-4 *:size-12 *:p-2'>
|
|
||||||
{OAUTH_METHODS?.map((method: any) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={method}
|
|
||||||
variant='ghost'
|
|
||||||
size='icon'
|
|
||||||
asChild
|
|
||||||
onClick={async () => {
|
|
||||||
const { data } = await oAuthLogin({
|
|
||||||
method,
|
|
||||||
redirect: `${window.location.origin}/oauth/${method}`,
|
|
||||||
});
|
|
||||||
if (data.data?.redirect) {
|
|
||||||
window.location.href = data.data?.redirect;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon={icons[method as keyof typeof icons]} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='flex items-center gap-5'>
|
<div className='flex items-center gap-5'>
|
||||||
|
|||||||
@ -96,8 +96,8 @@ export default function Affiliate() {
|
|||||||
<CardContent className='p-3 text-sm'>
|
<CardContent className='p-3 text-sm'>
|
||||||
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col'>
|
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col'>
|
||||||
<li className='font-semibold'>
|
<li className='font-semibold'>
|
||||||
<span className='text-muted-foreground'>{t('userEmail')}</span>
|
<span className='text-muted-foreground'>{t('userIdentifier')}</span>
|
||||||
<span>{item.email}</span>
|
<span>{item.identifier}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className='font-semibold'>
|
<li className='font-semibold'>
|
||||||
<span className='text-muted-foreground'>{t('registrationTime')}</span>
|
<span className='text-muted-foreground'>{t('registrationTime')}</span>
|
||||||
|
|||||||
56
apps/user/components/auth/oauth-methods.tsx
Normal file
56
apps/user/components/auth/oauth-methods.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import useGlobalStore from '@/config/use-global';
|
||||||
|
import { oAuthLogin } from '@/services/common/oauth';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
apple: 'uil:apple',
|
||||||
|
google: 'logos:google-icon',
|
||||||
|
facebook: 'logos:facebook',
|
||||||
|
github: 'uil:github',
|
||||||
|
telegram: 'logos:telegram',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function OAuthMethods() {
|
||||||
|
const { common } = useGlobalStore();
|
||||||
|
const { oauth_methods } = common;
|
||||||
|
const OAUTH_METHODS = oauth_methods?.filter(
|
||||||
|
(method) => !['mobile', 'email', 'device'].includes(method),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
OAUTH_METHODS?.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className='after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t'>
|
||||||
|
<span className='bg-background text-muted-foreground relative z-10 px-2'>
|
||||||
|
Or continue with
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='mt-6 flex justify-center gap-4 *:size-12 *:p-2'>
|
||||||
|
{OAUTH_METHODS?.map((method: any) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={method}
|
||||||
|
variant='ghost'
|
||||||
|
size='icon'
|
||||||
|
asChild
|
||||||
|
onClick={async () => {
|
||||||
|
const { data } = await oAuthLogin({
|
||||||
|
method,
|
||||||
|
redirect: `${window.location.origin}/oauth/${method}`,
|
||||||
|
});
|
||||||
|
if (data.data?.redirect) {
|
||||||
|
window.location.href = data.data?.redirect;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon={icons[method as keyof typeof icons]} />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -67,12 +67,14 @@ export default function Footer() {
|
|||||||
</nav>
|
</nav>
|
||||||
<div>
|
<div>
|
||||||
<strong className='text-foreground'>{site.site_name}</strong> © All rights reserved.
|
<strong className='text-foreground'>{site.site_name}</strong> © All rights reserved.
|
||||||
<Link href='/tos' className='ml-2 underline'>
|
<div>
|
||||||
{t('tos')}
|
<Link href='/tos' className='underline'>
|
||||||
</Link>
|
{t('tos')}
|
||||||
<Link href='/privacy-policy' className='ml-2 underline'>
|
</Link>
|
||||||
{t('privacyPolicy')}
|
<Link href='/privacy-policy' className='ml-2 underline'>
|
||||||
</Link>
|
{t('privacyPolicy')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export default function Header() {
|
|||||||
{site.site_logo && (
|
{site.site_logo && (
|
||||||
<Image src={site.site_logo} width={48} height={48} alt='logo' unoptimized />
|
<Image src={site.site_logo} width={48} height={48} alt='logo' unoptimized />
|
||||||
)}
|
)}
|
||||||
<span>{site.site_name}</span>
|
<span className=''>{site.site_name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import { Display } from '@/components/display';
|
import { Display } from '@/components/display';
|
||||||
import { SubscribeDetail } from '@/components/subscribe/detail';
|
import { SubscribeDetail } from '@/components/subscribe/detail';
|
||||||
import { getSubscription } from '@/services/common/common';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Card, CardContent, CardFooter, CardHeader } from '@workspace/ui/components/card';
|
import { Card, CardContent, CardFooter, CardHeader } from '@workspace/ui/components/card';
|
||||||
import { Separator } from '@workspace/ui/components/separator';
|
import { Separator } from '@workspace/ui/components/separator';
|
||||||
@ -14,19 +13,15 @@ import { useTranslations } from 'next-intl';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Key, ReactNode } from 'react';
|
import { Key, ReactNode } from 'react';
|
||||||
|
|
||||||
export function ProductShowcase() {
|
interface ProductShowcaseProps {
|
||||||
|
subscriptionData: API.Subscribe[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Content({ subscriptionData }: ProductShowcaseProps) {
|
||||||
const t = useTranslations('index');
|
const t = useTranslations('index');
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { user } = useGlobalStore();
|
||||||
queryKey: ['getSubscription'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await getSubscription({
|
|
||||||
skipErrorHandler: true,
|
|
||||||
});
|
|
||||||
return data.data?.list || [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (data?.length === 0) return null;
|
|
||||||
return (
|
return (
|
||||||
<motion.section
|
<motion.section
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
@ -51,7 +46,7 @@ export function ProductShowcase() {
|
|||||||
{t('product_showcase_description')}
|
{t('product_showcase_description')}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
<div className='mx-auto flex flex-wrap justify-center gap-8 overflow-x-auto overflow-y-hidden *:max-w-80 *:flex-auto'>
|
<div className='mx-auto flex flex-wrap justify-center gap-8 overflow-x-auto overflow-y-hidden *:max-w-80 *:flex-auto'>
|
||||||
{data?.map((item, index) => (
|
{subscriptionData?.map((item, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
initial={{ opacity: 0, y: 50 }}
|
initial={{ opacity: 0, y: 50 }}
|
||||||
@ -132,7 +127,9 @@ export function ProductShowcase() {
|
|||||||
className='absolute bottom-0 left-0 w-full rounded-b-xl rounded-t-none'
|
className='absolute bottom-0 left-0 w-full rounded-b-xl rounded-t-none'
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<Link href='/subscribe'>{t('subscribe')}</Link>
|
<Link href={user ? '/subscribe' : `/purchasing?id=${item.id}`}>
|
||||||
|
{t('subscribe')}
|
||||||
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
17
apps/user/components/main/product-showcase/index.tsx
Normal file
17
apps/user/components/main/product-showcase/index.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { getSubscription } from '@/services/user/portal';
|
||||||
|
import { Content } from './content';
|
||||||
|
|
||||||
|
export async function ProductShowcase() {
|
||||||
|
try {
|
||||||
|
const { data } = await getSubscription({
|
||||||
|
skipErrorHandler: true,
|
||||||
|
});
|
||||||
|
const subscriptionList = data.data?.list || [];
|
||||||
|
|
||||||
|
if (subscriptionList.length === 0) return null;
|
||||||
|
|
||||||
|
return <Content subscriptionData={subscriptionList} />;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
329
apps/user/components/payment/stripe.tsx
Normal file
329
apps/user/components/payment/stripe.tsx
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
import {
|
||||||
|
CardCvcElement,
|
||||||
|
CardExpiryElement,
|
||||||
|
CardNumberElement,
|
||||||
|
Elements,
|
||||||
|
useElements,
|
||||||
|
useStripe,
|
||||||
|
} from '@stripe/react-stripe-js';
|
||||||
|
import {
|
||||||
|
loadStripe,
|
||||||
|
PaymentIntentResult,
|
||||||
|
StripeCardNumberElementOptions,
|
||||||
|
StripeElementStyle,
|
||||||
|
} from '@stripe/stripe-js';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import { Input } from '@workspace/ui/components/input';
|
||||||
|
import { Label } from '@workspace/ui/components/label';
|
||||||
|
import { CheckCircle } from 'lucide-react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { QRCodeCanvas } from 'qrcode.react';
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
interface StripePaymentProps {
|
||||||
|
method: string;
|
||||||
|
client_secret: string;
|
||||||
|
publishable_key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CardPaymentFormProps {
|
||||||
|
clientSecret: string;
|
||||||
|
onError: (message: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CardPaymentForm: React.FC<CardPaymentFormProps> = ({ clientSecret, onError }) => {
|
||||||
|
const stripe = useStripe();
|
||||||
|
const { theme, systemTheme } = useTheme();
|
||||||
|
const elements = useElements();
|
||||||
|
const [processing, setProcessing] = useState(false);
|
||||||
|
const [succeeded, setSucceeded] = useState(false);
|
||||||
|
const [errors, setErrors] = useState<{
|
||||||
|
cardNumber?: string;
|
||||||
|
cardExpiry?: string;
|
||||||
|
cardCvc?: string;
|
||||||
|
name?: string;
|
||||||
|
}>({});
|
||||||
|
const [cardholderName, setCardholderName] = useState('');
|
||||||
|
const t = useTranslations('payment.stripe.card');
|
||||||
|
|
||||||
|
const currentTheme = theme === 'system' ? systemTheme : theme;
|
||||||
|
const elementStyle: StripeElementStyle = {
|
||||||
|
base: {
|
||||||
|
'fontSize': '16px',
|
||||||
|
'color': currentTheme === 'dark' ? '#fff' : '#000',
|
||||||
|
'::placeholder': {
|
||||||
|
color: '#aab7c4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invalid: {
|
||||||
|
color: '#EF4444',
|
||||||
|
iconColor: '#EF4444',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const elementOptions: StripeCardNumberElementOptions = {
|
||||||
|
style: elementStyle,
|
||||||
|
showIcon: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (event: any, field: keyof typeof errors) => {
|
||||||
|
if (event.error) {
|
||||||
|
setErrors((prev) => ({ ...prev, [field]: event.error.message }));
|
||||||
|
} else {
|
||||||
|
setErrors((prev) => ({ ...prev, [field]: undefined }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!stripe || !elements) {
|
||||||
|
onError(t('loading'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cardholderName.trim()) {
|
||||||
|
setErrors((prev) => ({ ...prev, name: t('name_required') }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProcessing(true);
|
||||||
|
|
||||||
|
const cardNumber = elements.getElement(CardNumberElement);
|
||||||
|
const cardExpiry = elements.getElement(CardExpiryElement);
|
||||||
|
const cardCvc = elements.getElement(CardCvcElement);
|
||||||
|
|
||||||
|
if (!cardNumber || !cardExpiry || !cardCvc) {
|
||||||
|
onError(t('element_error'));
|
||||||
|
setProcessing(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
|
||||||
|
payment_method: {
|
||||||
|
card: cardNumber,
|
||||||
|
billing_details: {
|
||||||
|
name: cardholderName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
onError(error.message || t('payment_failed'));
|
||||||
|
setProcessing(false);
|
||||||
|
} else if (paymentIntent && paymentIntent.status === 'succeeded') {
|
||||||
|
setSucceeded(true);
|
||||||
|
setProcessing(false);
|
||||||
|
} else {
|
||||||
|
onError(t('processing'));
|
||||||
|
setProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{succeeded ? (
|
||||||
|
<div className='py-6 text-center'>
|
||||||
|
<div className='mb-4 flex justify-center'>
|
||||||
|
<CheckCircle className='h-12 w-12 text-green-500' />
|
||||||
|
</div>
|
||||||
|
<p className='text-xl font-medium'>{t('success_title')}</p>
|
||||||
|
<p className='text-muted-foreground mt-2'>{t('success_message')}</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className='space-y-4'>
|
||||||
|
{/* Cardholder Name */}
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<Label htmlFor='cardholderName' className='text-sm font-medium'>
|
||||||
|
{t('card_name')}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id='cardholderName'
|
||||||
|
type='text'
|
||||||
|
value={cardholderName}
|
||||||
|
onChange={(e) => setCardholderName(e.target.value)}
|
||||||
|
placeholder={t('name_placeholder')}
|
||||||
|
className={errors.name ? 'border-destructive' : ''}
|
||||||
|
/>
|
||||||
|
{errors.name && <p className='text-destructive text-xs'>{errors.name}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Number */}
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<Label htmlFor='cardNumber' className='text-sm font-medium'>
|
||||||
|
{t('card_number')}
|
||||||
|
</Label>
|
||||||
|
<div className='relative'>
|
||||||
|
<div
|
||||||
|
className={`focus-within:border-primary focus-within:ring-primary rounded-md border p-3 focus-within:ring-1 ${errors.cardNumber ? 'border-red-500' : ''}`}
|
||||||
|
>
|
||||||
|
<CardNumberElement
|
||||||
|
id='cardNumber'
|
||||||
|
options={elementOptions}
|
||||||
|
onChange={(e) => handleChange(e, 'cardNumber')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{errors.cardNumber && <p className='text-destructive text-xs'>{errors.cardNumber}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-2 gap-4'>
|
||||||
|
{/* Expiry Date */}
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<Label htmlFor='cardExpiry' className='text-sm font-medium'>
|
||||||
|
{t('expiry_date')}
|
||||||
|
</Label>
|
||||||
|
<div
|
||||||
|
className={`focus-within:border-primary focus-within:ring-primary rounded-md border p-3 focus-within:ring-1 ${errors.cardExpiry ? 'border-red-500' : ''}`}
|
||||||
|
>
|
||||||
|
<CardExpiryElement
|
||||||
|
id='cardExpiry'
|
||||||
|
options={{ style: elementStyle }}
|
||||||
|
onChange={(e) => handleChange(e, 'cardExpiry')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.cardExpiry && (
|
||||||
|
<p className='text-destructive text-xs'>{errors.cardExpiry}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Security Code */}
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<Label htmlFor='cardCvc' className='text-sm font-medium'>
|
||||||
|
{t('security_code')}
|
||||||
|
</Label>
|
||||||
|
<div
|
||||||
|
className={`focus-within:border-primary focus-within:ring-primary rounded-md border p-3 focus-within:ring-1 ${errors.cardCvc ? 'border-red-500' : ''}`}
|
||||||
|
>
|
||||||
|
<CardCvcElement
|
||||||
|
id='cardCvc'
|
||||||
|
options={{ style: elementStyle }}
|
||||||
|
onChange={(e) => handleChange(e, 'cardCvc')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.cardCvc && <p className='text-destructive text-xs'>{errors.cardCvc}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-6 flex flex-col space-y-4'>
|
||||||
|
<Button type='submit' disabled={processing || !stripe || !elements} className='w-full'>
|
||||||
|
{processing ? t('processing_button') : t('pay_button')}
|
||||||
|
</Button>
|
||||||
|
<p className='text-muted-foreground text-center text-xs'>{t('secure_notice')}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StripePayment: React.FC<StripePaymentProps> = ({
|
||||||
|
method,
|
||||||
|
client_secret,
|
||||||
|
publishable_key,
|
||||||
|
}) => {
|
||||||
|
const stripePromise = useMemo(() => loadStripe(publishable_key), [publishable_key]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Elements stripe={stripePromise}>
|
||||||
|
<CheckoutForm method={method} client_secret={client_secret} />
|
||||||
|
</Elements>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CheckoutForm: React.FC<Omit<StripePaymentProps, 'publishable_key'>> = ({
|
||||||
|
client_secret,
|
||||||
|
method,
|
||||||
|
}) => {
|
||||||
|
const stripe = useStripe();
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
|
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
|
||||||
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
const t = useTranslations('payment.stripe');
|
||||||
|
|
||||||
|
const handleError = useCallback((message: string) => {
|
||||||
|
setErrorMessage(message);
|
||||||
|
setIsSubmitted(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const confirmPayment = useCallback(async (): Promise<PaymentIntentResult | null> => {
|
||||||
|
if (!stripe) {
|
||||||
|
handleError(t('card.loading'));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'alipay') {
|
||||||
|
return await stripe.confirmAlipayPayment(
|
||||||
|
client_secret,
|
||||||
|
{ return_url: window.location.href },
|
||||||
|
{ handleActions: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (method === 'wechat_pay') {
|
||||||
|
return await stripe.confirmWechatPayPayment(
|
||||||
|
client_secret,
|
||||||
|
{
|
||||||
|
payment_method_options: { wechat_pay: { client: 'web' } },
|
||||||
|
},
|
||||||
|
{ handleActions: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [client_secret, method, stripe, handleError, t]);
|
||||||
|
|
||||||
|
const autoSubmit = useCallback(async () => {
|
||||||
|
if (isSubmitted || method === 'card') return;
|
||||||
|
|
||||||
|
setIsSubmitted(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await confirmPayment();
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
const { error, paymentIntent } = result;
|
||||||
|
if (error) return handleError(error.message!);
|
||||||
|
|
||||||
|
if (paymentIntent?.status === 'requires_action') {
|
||||||
|
const nextAction = paymentIntent.next_action as any;
|
||||||
|
const qrUrl =
|
||||||
|
method === 'alipay'
|
||||||
|
? nextAction?.alipay_handle_redirect?.url
|
||||||
|
: nextAction?.wechat_pay_display_qr_code?.image_url_svg;
|
||||||
|
|
||||||
|
setQrCodeUrl(qrUrl || null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(t('error'));
|
||||||
|
}
|
||||||
|
}, [confirmPayment, isSubmitted, handleError, method, t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
autoSubmit();
|
||||||
|
}, [autoSubmit]);
|
||||||
|
|
||||||
|
return method === 'card' ? (
|
||||||
|
<div className='min-w-80 text-left'>
|
||||||
|
<CardPaymentForm clientSecret={client_secret} onError={handleError} />
|
||||||
|
</div>
|
||||||
|
) : qrCodeUrl ? (
|
||||||
|
<>
|
||||||
|
<QRCodeCanvas
|
||||||
|
value={qrCodeUrl}
|
||||||
|
size={208}
|
||||||
|
imageSettings={{
|
||||||
|
src: `/payment/${method}.svg`,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
excavate: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p className='text-muted-foreground mt-4 text-center'>{t(`qrcode.${method}`)}</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
errorMessage
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StripePayment;
|
||||||
@ -27,11 +27,7 @@ const DurationSelector: React.FC<DurationSelectorProps> = ({
|
|||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const DurationOption: React.FC<{ value: string; label: string; discount?: number }> = ({
|
const DurationOption: React.FC<{ value: string; label: string }> = ({ value, label }) => (
|
||||||
value,
|
|
||||||
label,
|
|
||||||
discount,
|
|
||||||
}) => (
|
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<RadioGroupItem value={value} id={value} className='peer sr-only' />
|
<RadioGroupItem value={value} id={value} className='peer sr-only' />
|
||||||
<Label
|
<Label
|
||||||
@ -39,11 +35,14 @@ const DurationSelector: React.FC<DurationSelectorProps> = ({
|
|||||||
className='border-muted bg-popover hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary relative flex h-full flex-col items-center justify-center gap-2 rounded-md border-2 p-2'
|
className='border-muted bg-popover hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary relative flex h-full flex-col items-center justify-center gap-2 rounded-md border-2 p-2'
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{discount && <Badge variant='destructive'>-{discount}%</Badge>}
|
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 查找当前选中项的折扣信息
|
||||||
|
const currentDiscount = discounts?.find((item) => item.quantity === quantity)?.discount;
|
||||||
|
const discountPercentage = currentDiscount ? 100 - currentDiscount : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='font-semibold'>{t('purchaseDuration')}</div>
|
<div className='font-semibold'>{t('purchaseDuration')}</div>
|
||||||
@ -58,10 +57,19 @@ const DurationSelector: React.FC<DurationSelectorProps> = ({
|
|||||||
key={item.quantity}
|
key={item.quantity}
|
||||||
value={String(item.quantity)}
|
value={String(item.quantity)}
|
||||||
label={`${item.quantity} / ${t(unitTime)}`}
|
label={`${item.quantity} / ${t(unitTime)}`}
|
||||||
discount={100 - item.discount}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground text-sm'>{t('discountInfo')}:</span>
|
||||||
|
{discountPercentage > 0 ? (
|
||||||
|
<Badge variant='destructive' className='h-6 text-sm'>
|
||||||
|
-{discountPercentage}% {t('discount')}
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className='text-muted-foreground h-6 text-sm'>--</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,49 +1,68 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { getAvailablePaymentMethods } from '@/services/user/payment';
|
import { getAvailablePaymentMethods } from '@/services/user/portal';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Label } from '@workspace/ui/components/label';
|
import { Label } from '@workspace/ui/components/label';
|
||||||
import { RadioGroup, RadioGroupItem } from '@workspace/ui/components/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@workspace/ui/components/radio-group';
|
||||||
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
interface PaymentMethodsProps {
|
interface PaymentMethodsProps {
|
||||||
value: string;
|
value: number;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: number) => void;
|
||||||
|
balance?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaymentMethods: React.FC<PaymentMethodsProps> = ({ value, onChange }) => {
|
const PaymentMethods: React.FC<PaymentMethodsProps> = ({ value, onChange, balance = true }) => {
|
||||||
const t = useTranslations('subscribe');
|
const t = useTranslations('subscribe');
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
queryKey: ['getAvailablePaymentMethods'],
|
queryKey: ['getAvailablePaymentMethods', { balance }],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await getAvailablePaymentMethods();
|
const { data } = await getAvailablePaymentMethods();
|
||||||
return data.data?.list || [];
|
const methods = data.data?.list || [];
|
||||||
|
if (!value && methods[0]?.id) onChange(methods[0]?.id);
|
||||||
|
if (balance) return methods;
|
||||||
|
return methods.filter((item) => item.id !== -1);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='font-semibold'>{t('paymentMethod')}</div>
|
<div className='font-semibold'>{t('paymentMethod')}</div>
|
||||||
<RadioGroup className='grid grid-cols-5 gap-2' value={value} onValueChange={onChange}>
|
<RadioGroup
|
||||||
|
className='grid grid-cols-2 gap-2 md:grid-cols-5'
|
||||||
|
value={String(value)}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
console.log(val);
|
||||||
|
onChange(Number(val));
|
||||||
|
}}
|
||||||
|
>
|
||||||
{data?.map((item) => (
|
{data?.map((item) => (
|
||||||
<div key={item.mark}>
|
<div key={item.id} className='relative'>
|
||||||
<RadioGroupItem value={item.mark} id={item.mark} className='peer sr-only' />
|
<RadioGroupItem
|
||||||
|
value={String(item.id)}
|
||||||
|
id={String(item.id)}
|
||||||
|
className='absolute inset-0 z-10 h-full w-full cursor-pointer opacity-0'
|
||||||
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={item.mark}
|
htmlFor={String(item.id)}
|
||||||
className='border-muted bg-popover hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary flex flex-col items-center justify-between rounded-md border-2 py-2'
|
className={cn(
|
||||||
|
'border-muted bg-popover hover:bg-accent hover:text-accent-foreground flex flex-col items-center justify-between rounded-md border-2 py-2',
|
||||||
|
String(value) === String(item.id) ? 'border-primary' : '',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className='mb-3 size-12'>
|
<div className='mb-3 size-12'>
|
||||||
<Image
|
<Image
|
||||||
src={item.icon || `/payment/${item.mark}.svg`}
|
src={item.icon || `/payment/balance.svg`}
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
alt={item.name || t(`methods.${item.mark}`)}
|
alt={item.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className='w-full overflow-hidden text-ellipsis whitespace-nowrap text-center'>
|
<span className='w-full overflow-hidden text-ellipsis whitespace-nowrap text-center'>
|
||||||
{item.name || t(`methods.${item.mark}`)}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import CouponInput from '@/components/subscribe/coupon-input';
|
|||||||
import DurationSelector from '@/components/subscribe/duration-selector';
|
import DurationSelector from '@/components/subscribe/duration-selector';
|
||||||
import PaymentMethods from '@/components/subscribe/payment-methods';
|
import PaymentMethods from '@/components/subscribe/payment-methods';
|
||||||
import useGlobalStore from '@/config/use-global';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { checkoutOrder, preCreateOrder, purchase } from '@/services/user/order';
|
import { preCreateOrder, purchase } from '@/services/user/order';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Card, CardContent } from '@workspace/ui/components/card';
|
import { Card, CardContent } from '@workspace/ui/components/card';
|
||||||
@ -29,7 +29,7 @@ export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseP
|
|||||||
const [params, setParams] = useState<Partial<API.PurchaseOrderRequest>>({
|
const [params, setParams] = useState<Partial<API.PurchaseOrderRequest>>({
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
subscribe_id: 0,
|
subscribe_id: 0,
|
||||||
payment: 'balance',
|
payment: -1,
|
||||||
coupon: '',
|
coupon: '',
|
||||||
});
|
});
|
||||||
const [loading, startTransition] = useTransition();
|
const [loading, startTransition] = useTransition();
|
||||||
@ -69,20 +69,11 @@ export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseP
|
|||||||
const response = await purchase(params as API.PurchaseOrderRequest);
|
const response = await purchase(params as API.PurchaseOrderRequest);
|
||||||
const orderNo = response.data.data?.order_no;
|
const orderNo = response.data.data?.order_no;
|
||||||
if (orderNo) {
|
if (orderNo) {
|
||||||
const { data } = await checkoutOrder({
|
|
||||||
orderNo,
|
|
||||||
returnUrl: `${window.location.origin}/payment?order_no=${orderNo}`,
|
|
||||||
});
|
|
||||||
const type = data.data?.type;
|
|
||||||
const checkout_url = data.data?.checkout_url;
|
|
||||||
if (type === 'link') {
|
|
||||||
window.location.href = checkout_url!;
|
|
||||||
}
|
|
||||||
getUserInfo();
|
getUserInfo();
|
||||||
router.push(`/payment?order_no=${orderNo}`);
|
router.push(`/payment?order_no=${orderNo}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
/* empty */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [params, router, getUserInfo]);
|
}, [params, router, getUserInfo]);
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import useGlobalStore from '@/config/use-global';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { checkoutOrder, recharge } from '@/services/user/order';
|
import { recharge } from '@/services/user/order';
|
||||||
import { getAvailablePaymentMethods } from '@/services/user/payment';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Button, ButtonProps } from '@workspace/ui/components/button';
|
import { Button, ButtonProps } from '@workspace/ui/components/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -13,15 +11,13 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from '@workspace/ui/components/dialog';
|
} from '@workspace/ui/components/dialog';
|
||||||
import { Label } from '@workspace/ui/components/label';
|
|
||||||
import { RadioGroup, RadioGroupItem } from '@workspace/ui/components/radio-group';
|
|
||||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||||
import { unitConversion } from '@workspace/ui/utils';
|
import { unitConversion } from '@workspace/ui/utils';
|
||||||
import { LoaderCircle } from 'lucide-react';
|
import { LoaderCircle } from 'lucide-react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Image from 'next/image';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState, useTransition } from 'react';
|
import { useState, useTransition } from 'react';
|
||||||
|
import PaymentMethods from './payment-methods';
|
||||||
|
|
||||||
export default function Recharge(props: Readonly<ButtonProps>) {
|
export default function Recharge(props: Readonly<ButtonProps>) {
|
||||||
const t = useTranslations('subscribe');
|
const t = useTranslations('subscribe');
|
||||||
@ -34,27 +30,9 @@ export default function Recharge(props: Readonly<ButtonProps>) {
|
|||||||
|
|
||||||
const [params, setParams] = useState<API.RechargeOrderRequest>({
|
const [params, setParams] = useState<API.RechargeOrderRequest>({
|
||||||
amount: 0,
|
amount: 0,
|
||||||
payment: '',
|
payment: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: paymentMethods } = useQuery({
|
|
||||||
enabled: open,
|
|
||||||
queryKey: ['getAvailablePaymentMethods'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await getAvailablePaymentMethods();
|
|
||||||
return data.data?.list || [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (paymentMethods?.length) {
|
|
||||||
setParams((prev) => ({
|
|
||||||
...prev,
|
|
||||||
payment: paymentMethods.find((item) => item.mark !== 'balance')?.mark as string,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}, [paymentMethods]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@ -86,43 +64,10 @@ export default function Recharge(props: Readonly<ButtonProps>) {
|
|||||||
suffix={currency.currency_unit}
|
suffix={currency.currency_unit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='font-semibold'>{t('paymentMethod')}</div>
|
<PaymentMethods
|
||||||
<RadioGroup
|
|
||||||
className='grid grid-cols-5 gap-2'
|
|
||||||
value={params.payment}
|
value={params.payment}
|
||||||
onValueChange={(value) => {
|
onChange={(value) => setParams({ ...params, payment: value })}
|
||||||
setParams({
|
/>
|
||||||
...params,
|
|
||||||
payment: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{paymentMethods
|
|
||||||
?.filter((item) => item.mark !== 'balance')
|
|
||||||
?.map((item) => {
|
|
||||||
return (
|
|
||||||
<div key={item.mark}>
|
|
||||||
<RadioGroupItem value={item.mark} id={item.mark} className='peer sr-only' />
|
|
||||||
<Label
|
|
||||||
htmlFor={item.mark}
|
|
||||||
className='border-muted bg-popover hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary flex flex-col items-center justify-between rounded-md border-2 py-2'
|
|
||||||
>
|
|
||||||
<div className='mb-3 size-12'>
|
|
||||||
<Image
|
|
||||||
src={item.icon || `/payment/${item.mark}.svg`}
|
|
||||||
width={48}
|
|
||||||
height={48}
|
|
||||||
alt={item.name!}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className='w-full overflow-hidden text-ellipsis whitespace-nowrap text-center'>
|
|
||||||
{item.name || t(`methods.${item.mark}`)}
|
|
||||||
</span>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className='fixed bottom-0 left-0 w-full rounded-none md:relative md:mt-6'
|
className='fixed bottom-0 left-0 w-full rounded-none md:relative md:mt-6'
|
||||||
@ -133,15 +78,6 @@ export default function Recharge(props: Readonly<ButtonProps>) {
|
|||||||
const response = await recharge(params);
|
const response = await recharge(params);
|
||||||
const orderNo = response.data.data?.order_no;
|
const orderNo = response.data.data?.order_no;
|
||||||
if (orderNo) {
|
if (orderNo) {
|
||||||
const { data } = await checkoutOrder({
|
|
||||||
orderNo,
|
|
||||||
returnUrl: `${window.location.origin}/payment?order_no=${orderNo}`,
|
|
||||||
});
|
|
||||||
const type = data.data?.type;
|
|
||||||
const checkout_url = data.data?.checkout_url;
|
|
||||||
if (type === 'link') {
|
|
||||||
window.location.href = checkout_url!;
|
|
||||||
}
|
|
||||||
router.push(`/payment?order_no=${orderNo}`);
|
router.push(`/payment?order_no=${orderNo}`);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import CouponInput from '@/components/subscribe/coupon-input';
|
|||||||
import DurationSelector from '@/components/subscribe/duration-selector';
|
import DurationSelector from '@/components/subscribe/duration-selector';
|
||||||
import PaymentMethods from '@/components/subscribe/payment-methods';
|
import PaymentMethods from '@/components/subscribe/payment-methods';
|
||||||
import useGlobalStore from '@/config/use-global';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { checkoutOrder, preCreateOrder, renewal } from '@/services/user/order';
|
import { preCreateOrder, renewal } from '@/services/user/order';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Card, CardContent } from '@workspace/ui/components/card';
|
import { Card, CardContent } from '@workspace/ui/components/card';
|
||||||
@ -35,7 +35,7 @@ export default function Renewal({ id, subscribe }: Readonly<RenewalProps>) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [params, setParams] = useState<Partial<API.RenewalOrderRequest>>({
|
const [params, setParams] = useState<Partial<API.RenewalOrderRequest>>({
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
payment: 'balance',
|
payment: -1,
|
||||||
coupon: '',
|
coupon: '',
|
||||||
user_subscribe_id: id,
|
user_subscribe_id: id,
|
||||||
});
|
});
|
||||||
@ -77,20 +77,11 @@ export default function Renewal({ id, subscribe }: Readonly<RenewalProps>) {
|
|||||||
const response = await renewal(params as API.RenewalOrderRequest);
|
const response = await renewal(params as API.RenewalOrderRequest);
|
||||||
const orderNo = response.data.data?.order_no;
|
const orderNo = response.data.data?.order_no;
|
||||||
if (orderNo) {
|
if (orderNo) {
|
||||||
const { data } = await checkoutOrder({
|
|
||||||
orderNo,
|
|
||||||
returnUrl: `${window.location.origin}/payment?order_no=${orderNo}`,
|
|
||||||
});
|
|
||||||
const type = data.data?.type;
|
|
||||||
const checkout_url = data.data?.checkout_url;
|
|
||||||
if (type === 'link') {
|
|
||||||
window.location.href = checkout_url!;
|
|
||||||
}
|
|
||||||
getUserInfo();
|
getUserInfo();
|
||||||
router.push(`/payment?order_no=${orderNo}`);
|
router.push(`/payment?order_no=${orderNo}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
/* empty */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [params, router, getUserInfo]);
|
}, [params, router, getUserInfo]);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Display } from '@/components/display';
|
import { Display } from '@/components/display';
|
||||||
import useGlobalStore from '@/config/use-global';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { checkoutOrder, resetTraffic } from '@/services/user/order';
|
import { resetTraffic } from '@/services/user/order';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -28,7 +28,7 @@ export default function ResetTraffic({ id, replacement }: Readonly<ResetTrafficP
|
|||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [params, setParams] = useState<API.ResetTrafficOrderRequest>({
|
const [params, setParams] = useState<API.ResetTrafficOrderRequest>({
|
||||||
payment: 'balance',
|
payment: -1,
|
||||||
user_subscribe_id: id,
|
user_subscribe_id: id,
|
||||||
});
|
});
|
||||||
const [loading, startTransition] = useTransition();
|
const [loading, startTransition] = useTransition();
|
||||||
@ -84,15 +84,6 @@ export default function ResetTraffic({ id, replacement }: Readonly<ResetTrafficP
|
|||||||
const response = await resetTraffic(params);
|
const response = await resetTraffic(params);
|
||||||
const orderNo = response.data.data?.order_no;
|
const orderNo = response.data.data?.order_no;
|
||||||
if (orderNo) {
|
if (orderNo) {
|
||||||
const { data } = await checkoutOrder({
|
|
||||||
orderNo,
|
|
||||||
returnUrl: `${window.location.origin}/payment?order_no=${orderNo}`,
|
|
||||||
});
|
|
||||||
const type = data.data?.type;
|
|
||||||
const checkout_url = data.data?.checkout_url;
|
|
||||||
if (type === 'link') {
|
|
||||||
window.location.href = checkout_url!;
|
|
||||||
}
|
|
||||||
getUserInfo();
|
getUserInfo();
|
||||||
router.push(`/payment?order_no=${orderNo}`);
|
router.push(`/payment?order_no=${orderNo}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,11 @@ import { navs } from '@/config/navs';
|
|||||||
import useGlobalStore from '@/config/use-global';
|
import useGlobalStore from '@/config/use-global';
|
||||||
import { Logout } from '@/utils/common';
|
import { Logout } from '@/utils/common';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@workspace/ui/components/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@workspace/ui/components/avatar';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@workspace/ui/components/dropdown-menu';
|
} from '@workspace/ui/components/dropdown-menu';
|
||||||
@ -27,37 +25,61 @@ export function UserNav() {
|
|||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button size='icon' variant='default'>
|
<div className='bg-background hover:bg-accent flex cursor-pointer items-center gap-2 rounded-full border px-2 py-1.5 transition-colors duration-200'>
|
||||||
<Avatar className='size-8'>
|
<Avatar className='h-6 w-6'>
|
||||||
<AvatarImage alt={user?.avatar ?? ''} src={user?.avatar ?? ''} />
|
<AvatarImage
|
||||||
<AvatarFallback className='rounded-none bg-transparent'>
|
alt={user?.avatar ?? ''}
|
||||||
|
src={user?.auth_methods?.[0]?.auth_identifier ?? ''}
|
||||||
|
className='object-cover'
|
||||||
|
/>
|
||||||
|
<AvatarFallback className='from-primary/90 to-primary text-background bg-gradient-to-br font-medium'>
|
||||||
{user?.auth_methods?.[0]?.auth_identifier.toUpperCase().charAt(0)}
|
{user?.auth_methods?.[0]?.auth_identifier.toUpperCase().charAt(0)}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</Button>
|
<span className='max-w-[40px] truncate text-sm sm:max-w-[100px]'>
|
||||||
|
{user?.auth_methods?.[0]?.auth_identifier.split('@')[0]}
|
||||||
|
</span>
|
||||||
|
<Icon icon='lucide:chevron-down' className='text-muted-foreground size-4' />
|
||||||
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent forceMount align='end' className='w-56'>
|
<DropdownMenuContent forceMount align='end' className='w-64'>
|
||||||
<DropdownMenuLabel className='font-normal'>
|
<div className='flex items-center justify-start gap-2 p-2'>
|
||||||
<div className='flex flex-col space-y-1'>
|
<Avatar className='h-10 w-10'>
|
||||||
<p className='text-muted-foreground text-xs leading-none'>ID: {user?.id}</p>
|
<AvatarImage
|
||||||
|
alt={user?.avatar ?? ''}
|
||||||
|
src={user?.avatar ?? ''}
|
||||||
|
className='object-cover'
|
||||||
|
/>
|
||||||
|
<AvatarFallback className='from-primary/90 to-primary text-background bg-gradient-to-br'>
|
||||||
|
{user?.auth_methods?.[0]?.auth_identifier.toUpperCase().charAt(0)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className='flex flex-col space-y-0.5'>
|
||||||
<p className='text-sm font-medium leading-none'>
|
<p className='text-sm font-medium leading-none'>
|
||||||
|
{user?.auth_methods?.[0]?.auth_identifier.split('@')[0]}
|
||||||
|
</p>
|
||||||
|
<p className='text-muted-foreground text-xs'>
|
||||||
{user?.auth_methods?.[0]?.auth_identifier}
|
{user?.auth_methods?.[0]?.auth_identifier}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuLabel>
|
</div>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{navs.map((nav) => (
|
{navs.map((nav) => (
|
||||||
<DropdownMenuGroup key={nav.title}>
|
<DropdownMenuGroup key={nav.title}>
|
||||||
{/* {nav.items && <DropdownMenuLabel>{t(nav.title)}</DropdownMenuLabel>} */}
|
|
||||||
{(nav.items || [nav]).map((item) => (
|
{(nav.items || [nav]).map((item) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={item.title}
|
key={item.title}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(`${item.url}`);
|
router.push(`${item.url}`);
|
||||||
}}
|
}}
|
||||||
|
className='flex cursor-pointer items-center gap-2 py-2'
|
||||||
>
|
>
|
||||||
<Icon className='mr-2 size-4 flex-none' icon={item.icon!} />
|
<Icon className='text-muted-foreground size-4 flex-none' icon={item.icon!} />
|
||||||
<span className='truncate'>{t(item.title)}</span>
|
<span className='flex-grow truncate'>{t(item.title)}</span>
|
||||||
|
<Icon
|
||||||
|
icon='lucide:chevron-right'
|
||||||
|
className='text-muted-foreground size-4 opacity-50'
|
||||||
|
/>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
@ -68,10 +90,10 @@ export function UserNav() {
|
|||||||
Logout();
|
Logout();
|
||||||
setUser();
|
setUser();
|
||||||
}}
|
}}
|
||||||
|
className='text-destructive focus:text-destructive flex cursor-pointer items-center gap-2 py-2'
|
||||||
>
|
>
|
||||||
<Icon className='mr-2 size-4 flex-none' icon='uil:exit' />
|
<Icon className='size-4 flex-none' icon='uil:exit' />
|
||||||
|
<span className='flex-grow'>{t('logout')}</span>
|
||||||
{t('logout')}
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export const NEXT_PUBLIC_DEFAULT_LANGUAGE =
|
|||||||
|
|
||||||
export const NEXT_PUBLIC_SITE_URL = env('NEXT_PUBLIC_SITE_URL') ?? process.env.NEXT_PUBLIC_SITE_URL;
|
export const NEXT_PUBLIC_SITE_URL = env('NEXT_PUBLIC_SITE_URL') ?? process.env.NEXT_PUBLIC_SITE_URL;
|
||||||
export const NEXT_PUBLIC_API_URL = env('NEXT_PUBLIC_API_URL') ?? process.env.NEXT_PUBLIC_API_URL;
|
export const NEXT_PUBLIC_API_URL = env('NEXT_PUBLIC_API_URL') ?? process.env.NEXT_PUBLIC_API_URL;
|
||||||
|
export const NEXT_PUBLIC_CDN_URL =
|
||||||
|
env('NEXT_PUBLIC_CDN_URL') || process.env.NEXT_PUBLIC_CDN_URL || 'https://fastly.jsdelivr.net';
|
||||||
|
|
||||||
export const NEXT_PUBLIC_DEFAULT_USER_EMAIL =
|
export const NEXT_PUBLIC_DEFAULT_USER_EMAIL =
|
||||||
env('NEXT_PUBLIC_DEFAULT_USER_EMAIL') ?? process.env.NEXT_PUBLIC_DEFAULT_USER_EMAIL;
|
env('NEXT_PUBLIC_DEFAULT_USER_EMAIL') ?? process.env.NEXT_PUBLIC_DEFAULT_USER_EMAIL;
|
||||||
|
|||||||
@ -69,6 +69,7 @@ export const useGlobalStore = create<GlobalStore>((set, get) => ({
|
|||||||
verify_code_interval: 60,
|
verify_code_interval: 60,
|
||||||
},
|
},
|
||||||
oauth_methods: [],
|
oauth_methods: [],
|
||||||
|
web_ad: false,
|
||||||
},
|
},
|
||||||
user: undefined,
|
user: undefined,
|
||||||
setCommon: (common) =>
|
setCommon: (common) =>
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "Záznamy o pozvání",
|
"inviteRecords": "Záznamy o pozvání",
|
||||||
"registrationTime": "Čas registrace",
|
"registrationTime": "Čas registrace",
|
||||||
"totalCommission": "Celková provize",
|
"totalCommission": "Celková provize",
|
||||||
"userEmail": "Uživatelský e-mail"
|
"userIdentifier": "Identifikátor uživatele"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"mySubscriptions": "Moje předplatné",
|
"mySubscriptions": "Moje předplatné",
|
||||||
"nextResetDays": "Příští reset/den",
|
"nextResetDays": "Příští reset/den",
|
||||||
"noLimit": "Bez omezení",
|
"noLimit": "Bez omezení",
|
||||||
|
"noReset": "Žádné obnovení",
|
||||||
"prompt": "Výzva",
|
"prompt": "Výzva",
|
||||||
"purchaseSubscription": "Zakoupit předplatné",
|
"purchaseSubscription": "Zakoupit předplatné",
|
||||||
"qrCode": "QR kód",
|
"qrCode": "QR kód",
|
||||||
|
|||||||
26
apps/user/locales/cs-CZ/payment.json
Normal file
26
apps/user/locales/cs-CZ/payment.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"stripe": {
|
||||||
|
"card": {
|
||||||
|
"card_name": "Jméno držitele karty",
|
||||||
|
"card_number": "Číslo karty",
|
||||||
|
"element_error": "Nelze získat prvek karty.",
|
||||||
|
"expiry_date": "Datum expirace",
|
||||||
|
"loading": "Stripe.js není načten. Zkuste to prosím znovu později.",
|
||||||
|
"name_placeholder": "Zadejte jméno držitele karty",
|
||||||
|
"name_required": "Jméno držitele karty je povinné",
|
||||||
|
"pay_button": "Zaplatit nyní",
|
||||||
|
"payment_failed": "Platba se nezdařila, zkuste to prosím znovu.",
|
||||||
|
"processing": "Platba se zpracovává, prosím zkontrolujte výsledek později.",
|
||||||
|
"processing_button": "Zpracovává se...",
|
||||||
|
"secure_notice": "Vaše platební informace jsou bezpečně šifrovány",
|
||||||
|
"security_code": "Bezpečnostní kód",
|
||||||
|
"success_message": "Děkujeme za vaši platbu!",
|
||||||
|
"success_title": "Platba byla úspěšná"
|
||||||
|
},
|
||||||
|
"error": "Došlo k neočekávané chybě",
|
||||||
|
"qrcode": {
|
||||||
|
"alipay": "Naskenujte pomocí Alipay pro platbu",
|
||||||
|
"wechat_pay": "Naskenujte pomocí WeChat pro platbu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"connectionSpeed": "Rychlost připojení",
|
"connectionSpeed": "Rychlost připojení",
|
||||||
"productDetail": "Detaily produktu"
|
"productDetail": "Detaily produktu"
|
||||||
},
|
},
|
||||||
|
"discount": "Sleva",
|
||||||
|
"discountInfo": "Informace o slevě",
|
||||||
"enterAmount": "Zadejte částku dobití",
|
"enterAmount": "Zadejte částku dobití",
|
||||||
"enterCoupon": "Zadejte kód kupónu",
|
"enterCoupon": "Zadejte kód kupónu",
|
||||||
"methods": {
|
"methods": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"amount": "Částka",
|
"amount": "Částka",
|
||||||
|
"assetOverview": "Přehled aktiv",
|
||||||
"balance": "zůstatek",
|
"balance": "zůstatek",
|
||||||
"commission": "Provize",
|
"commission": "Provize",
|
||||||
"createdAt": "čas",
|
"createdAt": "čas",
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "Einladungsprotokolle",
|
"inviteRecords": "Einladungsprotokolle",
|
||||||
"registrationTime": "Registrierungszeit",
|
"registrationTime": "Registrierungszeit",
|
||||||
"totalCommission": "Gesamtprovision",
|
"totalCommission": "Gesamtprovision",
|
||||||
"userEmail": "Benutzer-E-Mail"
|
"userIdentifier": "Benutzerkennung"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"mySubscriptions": "Meine Abonnements",
|
"mySubscriptions": "Meine Abonnements",
|
||||||
"nextResetDays": "Nächster Reset/Tage",
|
"nextResetDays": "Nächster Reset/Tage",
|
||||||
"noLimit": "Kein Limit",
|
"noLimit": "Kein Limit",
|
||||||
|
"noReset": "Kein Zurücksetzen",
|
||||||
"prompt": "Aufforderung",
|
"prompt": "Aufforderung",
|
||||||
"purchaseSubscription": "Abonnement kaufen",
|
"purchaseSubscription": "Abonnement kaufen",
|
||||||
"qrCode": "QR-Code",
|
"qrCode": "QR-Code",
|
||||||
|
|||||||
26
apps/user/locales/de-DE/payment.json
Normal file
26
apps/user/locales/de-DE/payment.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"stripe": {
|
||||||
|
"card": {
|
||||||
|
"card_name": "Karteninhabername",
|
||||||
|
"card_number": "Kartennummer",
|
||||||
|
"element_error": "Konnte das Kartenelement nicht abrufen.",
|
||||||
|
"expiry_date": "Ablaufdatum",
|
||||||
|
"loading": "Stripe.js ist nicht geladen. Bitte versuchen Sie es später erneut.",
|
||||||
|
"name_placeholder": "Geben Sie den Namen des Karteninhabers ein",
|
||||||
|
"name_required": "Der Name des Karteninhabers ist erforderlich",
|
||||||
|
"pay_button": "Jetzt bezahlen",
|
||||||
|
"payment_failed": "Zahlung fehlgeschlagen, bitte versuchen Sie es erneut.",
|
||||||
|
"processing": "Zahlung wird verarbeitet, bitte überprüfen Sie das Ergebnis später.",
|
||||||
|
"processing_button": "Wird verarbeitet...",
|
||||||
|
"secure_notice": "Ihre Zahlungsinformationen sind sicher verschlüsselt",
|
||||||
|
"security_code": "Sicherheitscode",
|
||||||
|
"success_message": "Vielen Dank für Ihre Zahlung!",
|
||||||
|
"success_title": "Zahlung Erfolgreich"
|
||||||
|
},
|
||||||
|
"error": "Ein unerwarteter Fehler ist aufgetreten",
|
||||||
|
"qrcode": {
|
||||||
|
"alipay": "Mit Alipay scannen, um zu bezahlen",
|
||||||
|
"wechat_pay": "Mit WeChat scannen, um zu bezahlen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"connectionSpeed": "Verbindungsgeschwindigkeit",
|
"connectionSpeed": "Verbindungsgeschwindigkeit",
|
||||||
"productDetail": "Produktdetails"
|
"productDetail": "Produktdetails"
|
||||||
},
|
},
|
||||||
|
"discount": "Rabatt",
|
||||||
|
"discountInfo": "Rabattinformationen",
|
||||||
"enterAmount": "Geben Sie den Aufladebetrag ein",
|
"enterAmount": "Geben Sie den Aufladebetrag ein",
|
||||||
"enterCoupon": "Gutscheincode eingeben",
|
"enterCoupon": "Gutscheincode eingeben",
|
||||||
"methods": {
|
"methods": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"amount": "Betrag",
|
"amount": "Betrag",
|
||||||
|
"assetOverview": "Vermögensübersicht",
|
||||||
"balance": "Kontostand",
|
"balance": "Kontostand",
|
||||||
"commission": "Provision",
|
"commission": "Provision",
|
||||||
"createdAt": "Zeit",
|
"createdAt": "Zeit",
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "Invite Records",
|
"inviteRecords": "Invite Records",
|
||||||
"registrationTime": "Registration Time",
|
"registrationTime": "Registration Time",
|
||||||
"totalCommission": "Total Commission",
|
"totalCommission": "Total Commission",
|
||||||
"userEmail": "User Email"
|
"userIdentifier": "User Identifier"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"mySubscriptions": "My Subscriptions",
|
"mySubscriptions": "My Subscriptions",
|
||||||
"nextResetDays": "Next Reset in Days",
|
"nextResetDays": "Next Reset in Days",
|
||||||
"noLimit": "No Limit",
|
"noLimit": "No Limit",
|
||||||
|
"noReset": "No Reset",
|
||||||
"prompt": "Prompt",
|
"prompt": "Prompt",
|
||||||
"purchaseSubscription": "Purchase Subscription",
|
"purchaseSubscription": "Purchase Subscription",
|
||||||
"qrCode": "QR Code",
|
"qrCode": "QR Code",
|
||||||
|
|||||||
26
apps/user/locales/en-US/payment.json
Normal file
26
apps/user/locales/en-US/payment.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"stripe": {
|
||||||
|
"card": {
|
||||||
|
"card_name": "Cardholder Name",
|
||||||
|
"card_number": "Card Number",
|
||||||
|
"element_error": "Unable to get card element.",
|
||||||
|
"expiry_date": "Expiry Date",
|
||||||
|
"loading": "Stripe.js is not loaded. Please try again later.",
|
||||||
|
"name_placeholder": "Enter cardholder name",
|
||||||
|
"name_required": "Cardholder name is required",
|
||||||
|
"pay_button": "Pay Now",
|
||||||
|
"payment_failed": "Payment failed, please try again.",
|
||||||
|
"processing": "Payment processing, please check the result later.",
|
||||||
|
"processing_button": "Processing...",
|
||||||
|
"secure_notice": "Your payment information is securely encrypted",
|
||||||
|
"security_code": "Security Code",
|
||||||
|
"success_message": "Thank you for your payment!",
|
||||||
|
"success_title": "Payment Successful"
|
||||||
|
},
|
||||||
|
"error": "An unexpected error occurred",
|
||||||
|
"qrcode": {
|
||||||
|
"alipay": "Scan with Alipay to pay",
|
||||||
|
"wechat_pay": "Scan with WeChat to pay"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"connectionSpeed": "Connection Speed",
|
"connectionSpeed": "Connection Speed",
|
||||||
"productDetail": "Product Details"
|
"productDetail": "Product Details"
|
||||||
},
|
},
|
||||||
|
"discount": "Discount",
|
||||||
|
"discountInfo": "Discount Info",
|
||||||
"enterAmount": "Enter recharge amount",
|
"enterAmount": "Enter recharge amount",
|
||||||
"enterCoupon": "Enter Coupon Code",
|
"enterCoupon": "Enter Coupon Code",
|
||||||
"methods": {
|
"methods": {
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
|
"assetOverview": "Asset Overview",
|
||||||
"balance": "Balance",
|
"balance": "Balance",
|
||||||
"commission": "Commission",
|
"commission": "Commission",
|
||||||
"createdAt": "Time",
|
"createdAt": "Time",
|
||||||
"giftAmount": "Girt Amount",
|
"giftAmount": "Girt Amount",
|
||||||
"totalAssets": "Asset overview",
|
"totalAssets": "Total Assets",
|
||||||
"type": {
|
"type": {
|
||||||
"0": "Type",
|
"0": "Type",
|
||||||
"1": "Recharge",
|
"1": "Recharge",
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "Registros de invitación",
|
"inviteRecords": "Registros de invitación",
|
||||||
"registrationTime": "Hora de registro",
|
"registrationTime": "Hora de registro",
|
||||||
"totalCommission": "Comisión total",
|
"totalCommission": "Comisión total",
|
||||||
"userEmail": "Correo electrónico del usuario"
|
"userIdentifier": "Identificador de usuario"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"mySubscriptions": "Mis suscripciones",
|
"mySubscriptions": "Mis suscripciones",
|
||||||
"nextResetDays": "Próximo reinicio/días",
|
"nextResetDays": "Próximo reinicio/días",
|
||||||
"noLimit": "Sin límite",
|
"noLimit": "Sin límite",
|
||||||
|
"noReset": "Sin Reinicio",
|
||||||
"prompt": "sugerencia",
|
"prompt": "sugerencia",
|
||||||
"purchaseSubscription": "Comprar suscripción",
|
"purchaseSubscription": "Comprar suscripción",
|
||||||
"qrCode": "Código QR",
|
"qrCode": "Código QR",
|
||||||
|
|||||||
26
apps/user/locales/es-ES/payment.json
Normal file
26
apps/user/locales/es-ES/payment.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"stripe": {
|
||||||
|
"card": {
|
||||||
|
"card_name": "Nombre del titular de la tarjeta",
|
||||||
|
"card_number": "Número de tarjeta",
|
||||||
|
"element_error": "No se pudo obtener el elemento de la tarjeta.",
|
||||||
|
"expiry_date": "Fecha de caducidad",
|
||||||
|
"loading": "Stripe.js no está cargado. Por favor, inténtalo de nuevo más tarde.",
|
||||||
|
"name_placeholder": "Introduce el nombre del titular de la tarjeta",
|
||||||
|
"name_required": "Se requiere el nombre del titular de la tarjeta",
|
||||||
|
"pay_button": "Pagar Ahora",
|
||||||
|
"payment_failed": "El pago ha fallado, por favor inténtalo de nuevo.",
|
||||||
|
"processing": "Procesando el pago, por favor verifica el resultado más tarde.",
|
||||||
|
"processing_button": "Procesando...",
|
||||||
|
"secure_notice": "Tu información de pago está encriptada de forma segura",
|
||||||
|
"security_code": "Código de seguridad",
|
||||||
|
"success_message": "¡Gracias por tu pago!",
|
||||||
|
"success_title": "Pago Exitoso"
|
||||||
|
},
|
||||||
|
"error": "Ocurrió un error inesperado",
|
||||||
|
"qrcode": {
|
||||||
|
"alipay": "Escanea con Alipay para pagar",
|
||||||
|
"wechat_pay": "Escanea con WeChat para pagar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"connectionSpeed": "Velocidad de conexión",
|
"connectionSpeed": "Velocidad de conexión",
|
||||||
"productDetail": "Detalles del producto"
|
"productDetail": "Detalles del producto"
|
||||||
},
|
},
|
||||||
|
"discount": "Descuento",
|
||||||
|
"discountInfo": "Información del Descuento",
|
||||||
"enterAmount": "Ingrese el monto de recarga",
|
"enterAmount": "Ingrese el monto de recarga",
|
||||||
"enterCoupon": "Introduce el código de cupón",
|
"enterCoupon": "Introduce el código de cupón",
|
||||||
"methods": {
|
"methods": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"amount": "Cantidad",
|
"amount": "Cantidad",
|
||||||
|
"assetOverview": "Descripción del activo",
|
||||||
"balance": "saldo",
|
"balance": "saldo",
|
||||||
"commission": "Comisión",
|
"commission": "Comisión",
|
||||||
"createdAt": "hora",
|
"createdAt": "hora",
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "Registros de invitación",
|
"inviteRecords": "Registros de invitación",
|
||||||
"registrationTime": "Hora de registro",
|
"registrationTime": "Hora de registro",
|
||||||
"totalCommission": "Comisión total",
|
"totalCommission": "Comisión total",
|
||||||
"userEmail": "Correo electrónico del usuario"
|
"userIdentifier": "Identificador de Usuario"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"mySubscriptions": "Mis suscripciones",
|
"mySubscriptions": "Mis suscripciones",
|
||||||
"nextResetDays": "Próximo reinicio/días",
|
"nextResetDays": "Próximo reinicio/días",
|
||||||
"noLimit": "Sin Límite",
|
"noLimit": "Sin Límite",
|
||||||
|
"noReset": "Sin Reinicio",
|
||||||
"prompt": "Sugerencia",
|
"prompt": "Sugerencia",
|
||||||
"purchaseSubscription": "Comprar suscripción",
|
"purchaseSubscription": "Comprar suscripción",
|
||||||
"qrCode": "Código QR",
|
"qrCode": "Código QR",
|
||||||
|
|||||||
26
apps/user/locales/es-MX/payment.json
Normal file
26
apps/user/locales/es-MX/payment.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"stripe": {
|
||||||
|
"card": {
|
||||||
|
"card_name": "Nombre del titular de la tarjeta",
|
||||||
|
"card_number": "Número de tarjeta",
|
||||||
|
"element_error": "No se pudo obtener el elemento de la tarjeta.",
|
||||||
|
"expiry_date": "Fecha de expiración",
|
||||||
|
"loading": "Stripe.js no se ha cargado. Por favor, inténtalo de nuevo más tarde.",
|
||||||
|
"name_placeholder": "Ingresa el nombre del titular de la tarjeta",
|
||||||
|
"name_required": "Se requiere el nombre del titular de la tarjeta",
|
||||||
|
"pay_button": "Pagar Ahora",
|
||||||
|
"payment_failed": "El pago falló, por favor inténtalo de nuevo.",
|
||||||
|
"processing": "Procesando el pago, por favor verifica el resultado más tarde.",
|
||||||
|
"processing_button": "Procesando...",
|
||||||
|
"secure_notice": "Tu información de pago está encriptada de forma segura",
|
||||||
|
"security_code": "Código de seguridad",
|
||||||
|
"success_message": "¡Gracias por tu pago!",
|
||||||
|
"success_title": "Pago Exitoso"
|
||||||
|
},
|
||||||
|
"error": "Ocurrió un error inesperado",
|
||||||
|
"qrcode": {
|
||||||
|
"alipay": "Escanea con Alipay para pagar",
|
||||||
|
"wechat_pay": "Escanea con WeChat para pagar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"connectionSpeed": "Velocidad de conexión",
|
"connectionSpeed": "Velocidad de conexión",
|
||||||
"productDetail": "Detalles del producto"
|
"productDetail": "Detalles del producto"
|
||||||
},
|
},
|
||||||
|
"discount": "Descuento",
|
||||||
|
"discountInfo": "Información del Descuento",
|
||||||
"enterAmount": "Ingresa el monto de recarga",
|
"enterAmount": "Ingresa el monto de recarga",
|
||||||
"enterCoupon": "Ingresa el código de cupón",
|
"enterCoupon": "Ingresa el código de cupón",
|
||||||
"methods": {
|
"methods": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"amount": "Monto",
|
"amount": "Monto",
|
||||||
|
"assetOverview": "Descripción del Activo",
|
||||||
"balance": "saldo",
|
"balance": "saldo",
|
||||||
"commission": "Comisión",
|
"commission": "Comisión",
|
||||||
"createdAt": "Hora",
|
"createdAt": "Hora",
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "سوابق دعوت",
|
"inviteRecords": "سوابق دعوت",
|
||||||
"registrationTime": "زمان ثبتنام",
|
"registrationTime": "زمان ثبتنام",
|
||||||
"totalCommission": "کمیسیون کل",
|
"totalCommission": "کمیسیون کل",
|
||||||
"userEmail": "ایمیل کاربر"
|
"userIdentifier": "شناسه کاربر"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"mySubscriptions": "اشتراکهای من",
|
"mySubscriptions": "اشتراکهای من",
|
||||||
"nextResetDays": "روزهای باقیمانده تا بازنشانی بعدی",
|
"nextResetDays": "روزهای باقیمانده تا بازنشانی بعدی",
|
||||||
"noLimit": "بدون محدودیت",
|
"noLimit": "بدون محدودیت",
|
||||||
|
"noReset": "عدم بازنشانی",
|
||||||
"prompt": "پیشنهاد",
|
"prompt": "پیشنهاد",
|
||||||
"purchaseSubscription": "خرید اشتراک",
|
"purchaseSubscription": "خرید اشتراک",
|
||||||
"qrCode": "کد QR",
|
"qrCode": "کد QR",
|
||||||
|
|||||||
26
apps/user/locales/fa-IR/payment.json
Normal file
26
apps/user/locales/fa-IR/payment.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"stripe": {
|
||||||
|
"card": {
|
||||||
|
"card_name": "نام دارنده کارت",
|
||||||
|
"card_number": "شماره کارت",
|
||||||
|
"element_error": "عدم توانایی در دریافت عنصر کارت.",
|
||||||
|
"expiry_date": "تاریخ انقضا",
|
||||||
|
"loading": "Stripe.js بارگذاری نشده است. لطفاً بعداً دوباره تلاش کنید.",
|
||||||
|
"name_placeholder": "نام دارنده کارت را وارد کنید",
|
||||||
|
"name_required": "نام دارنده کارت الزامی است",
|
||||||
|
"pay_button": "همین حالا پرداخت کنید",
|
||||||
|
"payment_failed": "پرداخت ناموفق بود، لطفاً دوباره تلاش کنید.",
|
||||||
|
"processing": "پرداخت در حال پردازش است، لطفاً نتیجه را بعداً بررسی کنید.",
|
||||||
|
"processing_button": "در حال پردازش...",
|
||||||
|
"secure_notice": "اطلاعات پرداخت شما به صورت ایمن رمزگذاری شده است",
|
||||||
|
"security_code": "کد امنیتی",
|
||||||
|
"success_message": "از پرداخت شما متشکریم!",
|
||||||
|
"success_title": "پرداخت موفق"
|
||||||
|
},
|
||||||
|
"error": "یک خطای غیرمنتظره رخ داده است",
|
||||||
|
"qrcode": {
|
||||||
|
"alipay": "برای پرداخت با Alipay اسکن کنید",
|
||||||
|
"wechat_pay": "برای پرداخت با WeChat اسکن کنید"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"connectionSpeed": "سرعت اتصال",
|
"connectionSpeed": "سرعت اتصال",
|
||||||
"productDetail": "جزئیات محصول"
|
"productDetail": "جزئیات محصول"
|
||||||
},
|
},
|
||||||
|
"discount": "تخفیف",
|
||||||
|
"discountInfo": "اطلاعات تخفیف",
|
||||||
"enterAmount": "مبلغ شارژ را وارد کنید",
|
"enterAmount": "مبلغ شارژ را وارد کنید",
|
||||||
"enterCoupon": "کد تخفیف را وارد کنید",
|
"enterCoupon": "کد تخفیف را وارد کنید",
|
||||||
"methods": {
|
"methods": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"amount": "مقدار",
|
"amount": "مقدار",
|
||||||
|
"assetOverview": "بررسی دارایی",
|
||||||
"balance": "تعادل",
|
"balance": "تعادل",
|
||||||
"commission": "کمیسیون",
|
"commission": "کمیسیون",
|
||||||
"createdAt": "زمان",
|
"createdAt": "زمان",
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "Kutsuhistoria",
|
"inviteRecords": "Kutsuhistoria",
|
||||||
"registrationTime": "Rekisteröintiaika",
|
"registrationTime": "Rekisteröintiaika",
|
||||||
"totalCommission": "Kokonaisprovisio",
|
"totalCommission": "Kokonaisprovisio",
|
||||||
"userEmail": "Käyttäjän sähköposti"
|
"userIdentifier": "Käyttäjän tunnus"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"mySubscriptions": "Omat tilaukset",
|
"mySubscriptions": "Omat tilaukset",
|
||||||
"nextResetDays": "Seuraava nollaus/päivää",
|
"nextResetDays": "Seuraava nollaus/päivää",
|
||||||
"noLimit": "Ei rajoitusta",
|
"noLimit": "Ei rajoitusta",
|
||||||
|
"noReset": "Ei nollata",
|
||||||
"prompt": "kehotus",
|
"prompt": "kehotus",
|
||||||
"purchaseSubscription": "Osta tilaus",
|
"purchaseSubscription": "Osta tilaus",
|
||||||
"qrCode": "QR-koodi",
|
"qrCode": "QR-koodi",
|
||||||
|
|||||||
26
apps/user/locales/fi-FI/payment.json
Normal file
26
apps/user/locales/fi-FI/payment.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"stripe": {
|
||||||
|
"card": {
|
||||||
|
"card_name": "Kortinhaltijan nimi",
|
||||||
|
"card_number": "Korttinumero",
|
||||||
|
"element_error": "Korttielementtiä ei voitu saada.",
|
||||||
|
"expiry_date": "Voimassaoloaika",
|
||||||
|
"loading": "Stripe.js ei ole ladattu. Yritä myöhemmin uudelleen.",
|
||||||
|
"name_placeholder": "Syötä kortinhaltijan nimi",
|
||||||
|
"name_required": "Kortinhaltijan nimi on pakollinen",
|
||||||
|
"pay_button": "Maksa nyt",
|
||||||
|
"payment_failed": "Maksu epäonnistui, yritä uudelleen.",
|
||||||
|
"processing": "Maksua käsitellään, tarkista tulos myöhemmin.",
|
||||||
|
"processing_button": "Käsitellään...",
|
||||||
|
"secure_notice": "Maksutietosi on salattu turvallisesti",
|
||||||
|
"security_code": "Turvakoodi",
|
||||||
|
"success_message": "Kiitos maksustasi!",
|
||||||
|
"success_title": "Maksu onnistui"
|
||||||
|
},
|
||||||
|
"error": "Odottamaton virhe tapahtui",
|
||||||
|
"qrcode": {
|
||||||
|
"alipay": "Skannaa Alipaylla maksamista varten",
|
||||||
|
"wechat_pay": "Skannaa WeChatilla maksamista varten"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"connectionSpeed": "Yhteysnopeus",
|
"connectionSpeed": "Yhteysnopeus",
|
||||||
"productDetail": "Tuotteen tiedot"
|
"productDetail": "Tuotteen tiedot"
|
||||||
},
|
},
|
||||||
|
"discount": "Alennus",
|
||||||
|
"discountInfo": "Alennustiedot",
|
||||||
"enterAmount": "Syötä latausmäärä",
|
"enterAmount": "Syötä latausmäärä",
|
||||||
"enterCoupon": "Syötä alennuskoodi",
|
"enterCoupon": "Syötä alennuskoodi",
|
||||||
"methods": {
|
"methods": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"amount": "Määrä",
|
"amount": "Määrä",
|
||||||
|
"assetOverview": "Omaisuuden yleiskatsaus",
|
||||||
"balance": "Saldo",
|
"balance": "Saldo",
|
||||||
"commission": "Komissio",
|
"commission": "Komissio",
|
||||||
"createdAt": "Aika",
|
"createdAt": "Aika",
|
||||||
|
|||||||
@ -7,5 +7,5 @@
|
|||||||
"inviteRecords": "Historique des invitations",
|
"inviteRecords": "Historique des invitations",
|
||||||
"registrationTime": "Heure d'inscription",
|
"registrationTime": "Heure d'inscription",
|
||||||
"totalCommission": "Commission totale",
|
"totalCommission": "Commission totale",
|
||||||
"userEmail": "Adresse e-mail de l'utilisateur"
|
"userIdentifier": "Identifiant de l'utilisateur"
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user