feat: implement tax calculations for subscription and SMS wallet payments
- Updated SubscriptionPeriod interface to include tax-related fields: tax_percent, tax_rials, and payable_rials. - Modified payment API documentation to reflect changes in tax handling for subscriptions and SMS wallet charges. - Adjusted PaymentController to calculate payment amounts based on subscription period details instead of client input. - Enhanced PaymentManager to handle net amounts for SMS wallet charges, ensuring tax is not credited to the wallet. - Created PaymentTaxCalculator and SubscriptionTaxCalculator services to manage tax calculations consistently across payment types. - Added tests for tax calculations in both subscription and SMS wallet contexts, ensuring correct behavior with and without tax enabled. - Updated frontend components to display tax information appropriately during payment processes.
This commit is contained in:
@@ -66,7 +66,7 @@ function SmsWalletPageInner() {
|
||||
queryFn: () => api.get('/api/v1/sms/settings'),
|
||||
});
|
||||
|
||||
const { isTestMode } = usePaymentConfig();
|
||||
const { isTestMode, taxPercent } = usePaymentConfig();
|
||||
|
||||
const balance = balanceData?.data;
|
||||
const logs = logsData?.data ?? EMPTY_LOGS;
|
||||
@@ -76,6 +76,12 @@ function SmsWalletPageInner() {
|
||||
const chargeForm = useForm<ChargeForm>({ resolver: zodResolver(chargeSchema) });
|
||||
const watchAmount = chargeForm.watch('amount_rials');
|
||||
|
||||
// مبلغِ واردشده خالص است — همان چیزی که به کیف پول مینشیند. مالیات رویش اضافه
|
||||
// میشود، دقیقاً با همان فرمولِ بکاند، تا عددِ مودال با صفحهٔ بانک یکی باشد.
|
||||
const chargeNet = tomanToRial(Number(watchAmount) || 0);
|
||||
const chargeTax = Math.round(chargeNet * taxPercent / 100);
|
||||
const chargePayable = chargeNet + chargeTax;
|
||||
|
||||
const chargeMutation = useMutation({
|
||||
mutationFn: ({ amount_rials }: ChargeForm) =>
|
||||
api.post<{ data: { payment_url: string } }>('/api/v1/sms/wallet/charge', {
|
||||
@@ -548,6 +554,31 @@ function SmsWalletPageInner() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{watchAmount && Number(watchAmount) >= 1000 && taxPercent > 0 && (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 6,
|
||||
background: 'var(--surface-2)', borderRadius: 8,
|
||||
padding: '10px 14px', fontSize: 13, color: 'var(--text-2)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>اعتباری که به کیف پول اضافه میشود</span>
|
||||
<span style={{ direction: 'ltr' }}>{formatRial(chargeNet)}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>مالیات بر ارزش افزوده {formatNumber(taxPercent)}٪</span>
|
||||
<span style={{ direction: 'ltr' }}>{formatRial(chargeTax)}</span>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex', justifyContent: 'space-between',
|
||||
borderTop: '1px solid var(--border)', paddingTop: 6,
|
||||
color: 'var(--text)', fontWeight: 700,
|
||||
}}>
|
||||
<span>مبلغ قابل پرداخت</span>
|
||||
<span style={{ direction: 'ltr' }}>{formatRial(chargePayable)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{watchAmount && Number(watchAmount) >= 1000 && (
|
||||
<div style={{
|
||||
background: isTestMode ? 'var(--warning-bg)' : 'var(--primary-soft)',
|
||||
@@ -556,8 +587,8 @@ function SmsWalletPageInner() {
|
||||
fontSize: 13, color: isTestMode ? 'var(--warning)' : 'var(--primary)', fontWeight: 500,
|
||||
}}>
|
||||
{isTestMode
|
||||
? `پرداخت آزمایشی ${formatRial(tomanToRial(Number(watchAmount)))}`
|
||||
: `پرداخت ${formatRial(tomanToRial(Number(watchAmount)))} از طریق ${gateway === 'mellat' ? 'بانک ملت' : 'سپ'}`
|
||||
? `پرداخت آزمایشی ${formatRial(chargePayable)}`
|
||||
: `پرداخت ${formatRial(chargePayable)} از طریق ${gateway === 'mellat' ? 'بانک ملت' : 'سپ'}`
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user