feat: require compliance confirmation for paid features

Gate payment, redemption, subscription, and invitation reward flows behind an audited compliance acknowledgement.
This commit is contained in:
CaIon
2026-05-13 22:18:46 +08:00
parent aa56667b8f
commit 0526a22643
57 changed files with 1806 additions and 216 deletions
@@ -30,6 +30,7 @@ interface AffiliateRewardsCardProps {
user: UserWalletData | null
affiliateLink: string
onTransfer: () => void
complianceConfirmed?: boolean
loading?: boolean
}
@@ -37,6 +38,7 @@ export function AffiliateRewardsCard({
user,
affiliateLink,
onTransfer,
complianceConfirmed = true,
loading,
}: AffiliateRewardsCardProps) {
const { t } = useTranslation()
@@ -110,6 +112,7 @@ export function AffiliateRewardsCard({
{hasRewards && (
<Button
onClick={onTransfer}
disabled={!complianceConfirmed}
className='h-9 shrink-0 px-3'
size='sm'
>
@@ -117,6 +120,13 @@ export function AffiliateRewardsCard({
</Button>
)}
</div>
{!complianceConfirmed ? (
<p className='text-muted-foreground text-xs lg:col-span-3'>
{t(
'Referral reward transfer is disabled until the administrator confirms compliance terms.'
)}
</p>
) : null}
</CardContent>
</Card>
)
@@ -135,6 +135,7 @@ export function RechargeFormCard({
const hasWaffoPaymentMethods =
Array.isArray(waffoPayMethods) && waffoPayMethods.length > 0
const minTopup = getMinTopupAmount(topupInfo)
const redemptionEnabled = topupInfo?.enable_redemption !== false
if (loading) {
return (
@@ -445,49 +446,59 @@ export function RechargeFormCard({
)}
{/* Redemption Code Section */}
<div className='space-y-2.5 border-t pt-4 sm:space-y-3 sm:pt-6'>
<div className='flex items-center gap-2'>
<Gift className='text-muted-foreground h-4 w-4' />
<Label
htmlFor='redemption-code'
className='text-muted-foreground text-xs font-medium tracking-wider uppercase'
>
{t('Have a Code?')}
</Label>
</div>
<div className='grid grid-cols-[minmax(0,1fr)_auto] gap-2'>
<Input
id='redemption-code'
value={redemptionCode}
onChange={(e) => onRedemptionCodeChange(e.target.value)}
placeholder={t('Enter your redemption code')}
className='h-9 min-w-0'
/>
<Button
onClick={onRedeem}
disabled={redeeming}
variant='outline'
className='h-9 px-4'
>
{redeeming && <Loader2 className='mr-2 h-4 w-4 animate-spin' />}
{t('Redeem')}
</Button>
</div>
{topupLink && (
<p className='text-muted-foreground text-xs'>
{t('Need a redemption code?')}{' '}
<a
href={topupLink}
target='_blank'
rel='noopener noreferrer'
className='inline-flex items-center gap-1 underline-offset-4 hover:underline'
{redemptionEnabled ? (
<div className='space-y-2.5 border-t pt-4 sm:space-y-3 sm:pt-6'>
<div className='flex items-center gap-2'>
<Gift className='text-muted-foreground h-4 w-4' />
<Label
htmlFor='redemption-code'
className='text-muted-foreground text-xs font-medium tracking-wider uppercase'
>
{t('Get one here')}
<ExternalLink className='h-3 w-3' />
</a>
</p>
)}
</div>
{t('Have a Code?')}
</Label>
</div>
<div className='grid grid-cols-[minmax(0,1fr)_auto] gap-2'>
<Input
id='redemption-code'
value={redemptionCode}
onChange={(e) => onRedemptionCodeChange(e.target.value)}
placeholder={t('Enter your redemption code')}
className='h-9 min-w-0'
/>
<Button
onClick={onRedeem}
disabled={redeeming}
variant='outline'
className='h-9 px-4'
>
{redeeming && <Loader2 className='mr-2 h-4 w-4 animate-spin' />}
{t('Redeem')}
</Button>
</div>
{topupLink && (
<p className='text-muted-foreground text-xs'>
{t('Need a redemption code?')}{' '}
<a
href={topupLink}
target='_blank'
rel='noopener noreferrer'
className='inline-flex items-center gap-1 underline-offset-4 hover:underline'
>
{t('Get one here')}
<ExternalLink className='h-3 w-3' />
</a>
</p>
)}
</div>
) : (
<Alert className='border-t'>
<AlertDescription>
{t(
'Redemption codes are disabled until the administrator confirms compliance terms.'
)}
</AlertDescription>
</Alert>
)}
</TitledCard>
)
}
+3
View File
@@ -319,6 +319,9 @@ export function Wallet(props: WalletProps) {
user={user}
affiliateLink={affiliateLink}
onTransfer={() => setTransferDialogOpen(true)}
complianceConfirmed={
topupInfo?.payment_compliance_confirmed !== false
}
loading={affiliateLoading}
/>
</div>
+6
View File
@@ -145,6 +145,12 @@ export interface TopupInfo {
enable_waffo_pancake_topup?: boolean
/** Minimum topup amount for Waffo Pancake */
waffo_pancake_min_topup?: number
/** Whether redemption code usage is enabled */
enable_redemption?: boolean
/** Whether compliance confirmation has been completed */
payment_compliance_confirmed?: boolean
/** Current compliance terms version */
payment_compliance_terms_version?: string
}
/**