Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/analytics/googleSheet.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('Google Sheets Conversion Logger', () => {
body: JSON.stringify({
gclid: 'test-gclid',
name: 'test-name',
value: '5',
value: '5.00',
currency: 'USD',
timestamp: expectedTimestamp,
captcha: 'mocked-token',
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
},
"apply": "Anwenden",
"taxes": "Steuern",
"annualBillingTemplate": "Jahresplan, monatlich abgerechnet mit {{price}}{{currency}}/Monat für 12 Monate"
"annualBillingTemplate": "Jahresplan, Abrechnung von {{priceNow}}{{currency}} für den ersten Monat, dann Verlängerung für {{price}}{{currency}}/Monat"
},
"confirmCryptoPayment": {
"title": "Zahlung bestätigen",
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@
},
"apply": "Apply",
"taxes": "Taxes",
"annualBillingTemplate": "Annual plan, billed monthly at {{price}}{{currency}}/month for 12 months"
"annualBillingTemplate": "Annual plan, billed at {{priceNow}}{{currency}} for the first month, then renews at {{price}}{{currency}}/month"
},
"confirmCryptoPayment": {
"title": "Confirm the payment",
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@
},
"apply": "Aplicar",
"taxes": "Impuestos",
"annualBillingTemplate": "Plan anual, facturado mensualmente a {{price}}{{currency}}/mes durante 12 meses"
"annualBillingTemplate": "Plan anual, facturado a {{priceNow}}{{currency}} el primer mes, luego se renueva a {{price}}{{currency}}/mes"
},
"confirmCryptoPayment": {
"title": "Confirmar el pago",
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
},
"apply": "Appliquer",
"taxes": "Taxes",
"annualBillingTemplate": "Plan annuel, facturé mensuellement à {{price}}{{currency}}/mois pendant 12 mois"
"annualBillingTemplate": "Plan annuel, facturé {{priceNow}}{{currency}} le premier mois, puis renouvelé à {{price}}{{currency}}/mois"
},
"confirmCryptoPayment": {
"title": "Confirmer le paiement",
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@
},
"apply": "Applica",
"taxes": "Tasse",
"annualBillingTemplate": "Piano annuale, fatturato mensilmente a {{price}}{{currency}}/mese per 12 mesi"
"annualBillingTemplate": "Piano annuale, fatturato a {{priceNow}}{{currency}} per il primo mese, poi si rinnova a {{price}}{{currency}}/mese"
},
"confirmCryptoPayment": {
"title": "Conferma il pagamento",
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
},
"apply": "Применить",
"taxes": "Налоги",
"annualBillingTemplate": "Годовой план, ежемесячная оплата {{price}}{{currency}}/мес. в течение 12 месяцев"
"annualBillingTemplate": "Годовой план, оплата {{priceNow}}{{currency}} за первый месяц, далее продлевается за {{price}}{{currency}}/мес."
},
"confirmCryptoPayment": {
"title": "Подтвердить платеж",
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@
},
"apply": "應用",
"taxes": "稅金",
"annualBillingTemplate": "年度計劃,每月 {{price}}{{currency}}/月,持續 12 個月"
"annualBillingTemplate": "年度計劃,第一個月費用為 {{priceNow}}{{currency}},之後以 {{price}}{{currency}}/月續訂"
},
"confirmCryptoPayment": {
"title": "確認付款",
Expand Down
2 changes: 1 addition & 1 deletion src/app/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@
},
"apply": "应用",
"taxes": "税费",
"annualBillingTemplate": "年度计划,每月 {{price}}{{currency}}/月,持续 12 个月"
"annualBillingTemplate": "年度计划,第一个月费用为 {{priceNow}}{{currency}},之后以 {{price}}{{currency}}/月续订"
},
"confirmCryptoPayment": {
"title": "确认付款",
Expand Down
16 changes: 7 additions & 9 deletions src/views/Checkout/components/CheckoutProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,11 @@ export const CheckoutProductCard = ({
const normalPriceAmount = priceData.decimalAmount;

const totalLabel = translate('checkout.productCard.total');
const renewalPeriodLabel = `${translate('checkout.productCard.renewalPeriod.renewsAt')}
${currencySymbol}${normalPriceAmount}/${translate(
`checkout.productCard.renewalPeriod.${priceData.interval}`,
)}`;

const planAmountWithoutTaxes = getProductAmount(priceData.decimalAmount, 1, couponCodeData);
const totalAmountFormatted = formatPrice(taxesData.decimalAmountWithTax);
const derivedTax = Math.max(0, Number(totalAmountFormatted) - Number(planAmountWithoutTaxes));
const derivedTaxFormatted = formatPrice(derivedTax);

const discountPercentage =
couponCodeData?.amountOff && couponCodeData?.amountOff < taxesData.amountWithTax
Expand Down Expand Up @@ -109,12 +108,12 @@ export const CheckoutProductCard = ({
</p>
</div>

{taxesData.decimalTax > 0 && (
{Number(derivedTaxFormatted) > 0 && (
<div className="flex flex-row items-center justify-between text-gray-100">
<p className="font-medium">{translate('checkout.productCard.taxes')}</p>
<p className="font-semibold">
{currencySymbol}
{taxesData.decimalTax}
{derivedTaxFormatted}
</p>
</div>
)}
Expand Down Expand Up @@ -163,7 +162,7 @@ export const CheckoutProductCard = ({
<p>{totalLabel}</p>
<p>
{currencySymbol}
{formatPrice(taxesData.decimalAmountWithTax)}
{totalAmountFormatted}
</p>
</div>

Expand Down Expand Up @@ -254,11 +253,10 @@ export const CheckoutProductCard = ({
)}
</div>
</div>
{couponCodeData && priceData.interval !== 'lifetime' && <p className="text-gray-60">{renewalPeriodLabel}</p>}
{showHardcodedRenewal && <p className="text-gray-60">{showHardcodedRenewal}</p>}
{priceData.interval === 'month' && (
<p className="text-gray-60">
{translate('checkout.productCard.annualBillingTemplate', {
priceNow: totalAmountFormatted,
price: normalPriceAmount,
currency: currencySymbol,
})}
Expand Down
3 changes: 2 additions & 1 deletion src/views/Checkout/components/CryptoPaymentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import notificationsService, { ToastType } from 'app/notifications/services/notifications.service';
import checkoutService from '../services/checkout.service';
import { Currency } from '../types';
import { formatPrice } from '../utils/formatPrice';
import { useEffect, useState } from 'react';
import { errorService } from 'services';

Expand Down Expand Up @@ -166,7 +167,7 @@ export const CryptoPaymentDialog = () => {
</p>
<div className="flex flex-row gap-3 items-center">
<p className="text-gray-100 dark:text-white text-lg font-normal">
{fiat.amount} {Currency[fiat.currency]}
{formatPrice(fiat.amount)} {Currency[fiat.currency]}
</p>
</div>
</div>
Expand Down
17 changes: 9 additions & 8 deletions src/views/Checkout/utils/formatPrice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest';
import { formatPrice } from './formatPrice';

describe('Formatting the price to have 2 decimals', () => {
it('When the price does not have decimals, the function returns it again', () => {
expect(formatPrice(10)).toBe('10');
it('When the price does not have decimals, the function returns it with .00', () => {
expect(formatPrice(10)).toBe('10.00');
});

describe('The value has less or exactly 2 decimals', () => {
Expand All @@ -16,18 +16,19 @@ describe('Formatting the price to have 2 decimals', () => {
});
});

it('When the price is just 1 decimal and it is a 0, then the function returns an integer (without 0s)', () => {
expect(formatPrice(20.0)).toBe('20');
it('When the price is just 1 decimal and it is a 0, then the function returns with .00', () => {
expect(formatPrice(20.0)).toBe('20.00');
});

describe('The price has more than 2 decimals', () => {
it('When the price has more than 2 decimals, then the function returns the price with 2 decimals (rounded, 10.456 -> 10.46 - 10.001 -> 10)', () => {
expect(formatPrice(10.456)).toBe('10.46');
expect(formatPrice(10.001)).toBe('10');
it('When the price has more than 2 decimals, then the function returns the price with 2 decimals (truncated with high precision, 10.456 -> 10.45 - 10.001 -> 10.00 - 1.999 -> 1.99)', () => {
expect(formatPrice(10.456)).toBe('10.45');
expect(formatPrice(10.001)).toBe('10.00');
expect(formatPrice(1.999)).toBe('1.99');
});
});

it('Handles edge case where value is nearly integer due to float error', () => {
expect(formatPrice(10.0000001)).toBe('10');
expect(formatPrice(10.0000001)).toBe('10.00');
});
});
4 changes: 2 additions & 2 deletions src/views/Checkout/utils/formatPrice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const formatPrice = (price: number) => {
const formattedAmount = Number(price.toFixed(2));
return Number.isInteger(formattedAmount) ? formattedAmount.toString() : price.toFixed(2);
const truncated = Math.floor(Number(price.toFixed(8)) * 100) / 100;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using toFixed(8) here? It looks arbitrary and makes the intent unclear.
If the goal is simply truncating to 2 decimals, Math.floor(price * 100) / 100 should be enough.
If this is a workaround for floating-point precision issues, it would be good to document it explicitly or use a more deterministic decimal handling approach.

return truncated.toFixed(2);
};
14 changes: 7 additions & 7 deletions src/views/Checkout/utils/getProductAmount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types';

describe('Calculating final price of a product', () => {
it('When there is no coupon, returns base amount multiplied by users', () => {
expect(getProductAmount(10, 2)).toBe('20');
expect(getProductAmount(10, 2)).toBe('20.00');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to introduce decimals when they are 00? Is this approved from product? I recall last time I had to remove them if they were .00.

});

describe('When amountOff coupon is applied', () => {
Expand All @@ -16,7 +16,7 @@ describe('Calculating final price of a product', () => {
codeName: 'DISCOUNT',
};

expect(getProductAmount(10, 2, coupon)).toBe('18');
expect(getProductAmount(10, 2, coupon)).toBe('18.00');
});
});

Expand All @@ -29,7 +29,7 @@ describe('Calculating final price of a product', () => {
codeName: 'HALFOFF',
};

expect(getProductAmount(10, 3, coupon)).toBe('15');
expect(getProductAmount(10, 3, coupon)).toBe('15.00');
});

it('Supports non-integer results rounded to 2 decimals', () => {
Expand All @@ -44,8 +44,8 @@ describe('Calculating final price of a product', () => {
});
});

it('Handles case with 0 users gracefully (should return 0)', () => {
expect(getProductAmount(10, 0)).toBe('0');
it('Handles case with 0 users gracefully (should return 0.00)', () => {
expect(getProductAmount(10, 0)).toBe('0.00');
});

describe('When discount results in negative price', () => {
Expand All @@ -57,7 +57,7 @@ describe('Calculating final price of a product', () => {
codeName: 'BIGDISCOUNT',
};

expect(getProductAmount(10, 1, coupon)).toBe('0');
expect(getProductAmount(10, 1, coupon)).toBe('0.00');
});

it('Returns 0 if percentOff is 100% or more', () => {
Expand All @@ -68,7 +68,7 @@ describe('Calculating final price of a product', () => {
codeName: 'FREE',
};

expect(getProductAmount(10, 2, coupon)).toBe('0');
expect(getProductAmount(10, 2, coupon)).toBe('0.00');
});
});
});
Loading