Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions paybutton/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3655,6 +3655,11 @@ react-jss@10.10.0:
theming "^3.3.0"
tiny-warning "^1.0.2"

react-number-format@^5.4.4:
version "5.4.4"
resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.4.tgz#d31f0e260609431500c8d3f81bbd3ae1fb7cacad"
integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==

react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
Expand Down
122 changes: 109 additions & 13 deletions react/lib/components/PayButton/PayButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
isValidCashAddress,
isValidXecAddress,
CurrencyObject,
generatePaymentId,
getCurrencyObject,
isPropsTrue,
setupAltpaymentSocket,
Expand All @@ -22,6 +21,7 @@ import {
ButtonSize,
DEFAULT_DONATION_RATE
} from '../../util';
import { createPayment } from '../../util/api-client';
import { PaymentDialog } from '../PaymentDialog';
import { AltpaymentCoin, AltpaymentError, AltpaymentPair, AltpaymentShift } from '../../altpayment';
export interface PayButtonProps extends ButtonProps {
Expand Down Expand Up @@ -110,17 +110,20 @@ export const PayButton = ({

const [currencyObj, setCurrencyObj] = useState<CurrencyObject | undefined>();
const [cryptoAmount, setCryptoAmount] = useState<string>();
const [convertedCurrencyObj, setConvertedCurrencyObj] = useState<CurrencyObject | undefined>();

const [price, setPrice] = useState(0);
const [newTxs, setNewTxs] = useState<Transaction[] | undefined>();
const priceRef = useRef<number>(price);
const cryptoAmountRef = useRef<string | undefined>(cryptoAmount);


const [paymentId] = useState(!disablePaymentId ? generatePaymentId(8) : undefined);
const [paymentId, setPaymentId] = useState<string | undefined>(undefined);
const [addressType, setAddressType] = useState<CryptoCurrency>(
getCurrencyTypeFromAddress(to),
);



useEffect(() => {
priceRef.current = price;
}, [price]);
Expand All @@ -137,16 +140,96 @@ export const PayButton = ({
}
}, 300);
};


const getPaymentId = useCallback(async (
currency: Currency,
to: string | undefined,
amount?: number,
): Promise<string | undefined> => {
if (disablePaymentId || !to) return undefined

try {
const convertedBaseAmount = convertedCurrencyObj?.float

const amountToUse =
(isFiat(currency) || randomSatoshis) && convertedBaseAmount !== undefined
? convertedBaseAmount
: amount

const responsePaymentId = await createPayment(amountToUse, to, apiBaseUrl)

setPaymentId(responsePaymentId)
return responsePaymentId
} catch (err) {
console.error('Error creating payment ID:', err)
return undefined
}
},
[disablePaymentId, apiBaseUrl, randomSatoshis, convertedCurrencyObj]
)

const lastPaymentAmount = useRef<number | null | undefined>(undefined)
useEffect(() => {

if (
!dialogOpen ||
disablePaymentId ||
!to
) {
return;
}

let effectiveAmount: number | null
if (isFiat(currency)) {
if (!convertedCurrencyObj) {
// Conversion not ready yet – wait for convertedCurrencyObj update
return;
}
effectiveAmount = convertedCurrencyObj.float;
} else if (amount === undefined) {
effectiveAmount = null
} else {
const amountNumber = Number(amount);
if (Number.isNaN(amountNumber)) {
return;
}
effectiveAmount = amountNumber;
}
if (lastPaymentAmount.current === effectiveAmount) {
return;
}

lastPaymentAmount.current = effectiveAmount;

void getPaymentId(
currency,
to,
effectiveAmount ?? undefined,
);
}, [amount, currency, to, dialogOpen, disablePaymentId, paymentId, getPaymentId, convertedCurrencyObj]);


const handleButtonClick = useCallback(async (): Promise<void> => {
if (onOpen !== undefined) {

if (onOpen) {
if (isFiat(currency)) {
void waitPrice(() => { onOpen(cryptoAmountRef.current, to, paymentId) })
void waitPrice(() => onOpen(cryptoAmountRef.current, to, paymentId))
} else {
onOpen(amount, to, paymentId)
}
}
setDialogOpen(true);
}, [cryptoAmount, to, paymentId, price])

setDialogOpen(true)
}, [
onOpen,
currency,
amount,
to,
paymentId,
disablePaymentId,
getPaymentId,
])

const handleCloseDialog = (success?: boolean, paymentId?: string): void => {
if (onClose !== undefined) onClose(success, paymentId);
Expand Down Expand Up @@ -260,22 +343,33 @@ export const PayButton = ({

useEffect(() => {
(async () => {
if (isFiat(currency) && price === 0) {
await getPrice();
}
if (isFiat(currency) && price === 0) {
await getPrice();
}
})()
}, [currency, getPrice, to, price]);

useEffect(() => {
if (currencyObj && isFiat(currency) && price) {
const addressType: Currency = getCurrencyTypeFromAddress(to);
if (!convertedCurrencyObj) {
const addressType: Currency = getCurrencyTypeFromAddress(to);
const convertedObj = getCurrencyObject(
currencyObj.float / price,
addressType,
randomSatoshis,
);
setCryptoAmount(convertedObj.string);
setConvertedCurrencyObj(convertedObj);
}
} else if (!isFiat(currency) && randomSatoshis && !convertedCurrencyObj) {
const convertedObj = getCurrencyObject(
currencyObj.float / price,
amount as number,
addressType,
randomSatoshis,
);
setCryptoAmount(convertedObj.string);
} else if (!isFiat(currency)) {
setConvertedCurrencyObj(convertedObj);
} else if (!isFiat(currency) && !randomSatoshis) {
setCryptoAmount(amount?.toString());
}
}, [price, currencyObj, amount, currency, randomSatoshis, to]);
Comment on lines 352 to 375
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Conversion guards prevent recalculation when amount changes.

Lines 314 and 325 use !convertedCurrencyObj guards that prevent conversion recalculation when the amount changes. Once convertedCurrencyObj is set, these blocks never execute again, even if amount, price, or randomSatoshis change.

Consider removing these guards or adding amount/price to trigger recalculation:

  useEffect(() => {
    if (currencyObj && isFiat(currency) && price) {
-     if(!convertedCurrencyObj) {
        const addressType: Currency = getCurrencyTypeFromAddress(to);
        const convertedObj = getCurrencyObject(
          currencyObj.float / price,
          addressType,
          randomSatoshis,
        );
        setCryptoAmount(convertedObj.string);
        setConvertedAmount(convertedObj.float);
        setConvertedCurrencyObj(convertedObj);
-     }
    } else if (!isFiat(currency) && randomSatoshis && !convertedCurrencyObj){
      const convertedObj = getCurrencyObject(
        amount as number,
        addressType,
        randomSatoshis,
      ); 
      setCryptoAmount(convertedObj.string);
      setConvertedAmount(convertedObj.float);
      setConvertedCurrencyObj(convertedObj);
    } else if (!isFiat(currency) && !randomSatoshis) {
      setCryptoAmount(amount?.toString());
    }
  }, [price, currencyObj, amount, currency, randomSatoshis, to]);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In react/lib/components/PayButton/PayButton.tsx around lines 312 to 337, the
current guards using !convertedCurrencyObj (lines ~314 and ~325) stop
recalculation once convertedCurrencyObj is set, so changes to amount, price or
randomSatoshis never trigger updates; remove those !convertedCurrencyObj checks
(or replace them with explicit comparisons against current convertedCurrencyObj
values) so the effect recalculates and calls getCurrencyObject whenever its
dependencies (price, currencyObj, amount, currency, randomSatoshis, to) change,
and keep setting cryptoAmount, convertedAmount and convertedCurrencyObj
accordingly.

Expand Down Expand Up @@ -354,6 +448,8 @@ export const PayButton = ({
transactionText={transactionText}
donationAddress={donationAddress}
donationRate={donationRate}
convertedCurrencyObj={convertedCurrencyObj}
setConvertedCurrencyObj={setConvertedCurrencyObj}
/>
{errorMsg && (
<p
Expand Down
8 changes: 7 additions & 1 deletion react/lib/components/PaymentDialog/PaymentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ export interface PaymentDialogProps extends ButtonProps {
newTxs?: Transaction[],
autoClose?: boolean | number | string;
disableSound?: boolean;
transactionText?: string;
donationAddress?: string;
donationRate?: number;
transactionText?: string
convertedCurrencyObj?: CurrencyObject;
setConvertedCurrencyObj?: Function;
}

export const PaymentDialog = ({
Expand Down Expand Up @@ -123,6 +125,8 @@ export const PaymentDialog = ({
disableSound,
transactionText,
disabled,
convertedCurrencyObj,
setConvertedCurrencyObj,
theme: themeProp,
donationAddress,
donationRate
Expand Down Expand Up @@ -253,6 +257,8 @@ export const PaymentDialog = ({
transactionText={transactionText}
donationAddress={donationAddress}
donationRate={donationRate}
convertedCurrencyObj={convertedCurrencyObj}
setConvertedCurrencyObj={setConvertedCurrencyObj}
foot={success && (
<ButtonComponent
onClick={handleWidgetClose}
Expand Down
Loading