-
Notifications
You must be signed in to change notification settings - Fork 19
feat: generate paymentId on server #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c6537d4
fb4268d
f15ad66
cb09898
625902a
02b80b0
a7df5f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,14 +13,14 @@ import { | |||||||||||||||||||||||
| isValidCashAddress, | ||||||||||||||||||||||||
| isValidXecAddress, | ||||||||||||||||||||||||
| CurrencyObject, | ||||||||||||||||||||||||
| generatePaymentId, | ||||||||||||||||||||||||
| getCurrencyObject, | ||||||||||||||||||||||||
| isPropsTrue, | ||||||||||||||||||||||||
| setupAltpaymentSocket, | ||||||||||||||||||||||||
| setupChronikWebSocket, | ||||||||||||||||||||||||
| CryptoCurrency, | ||||||||||||||||||||||||
| ButtonSize | ||||||||||||||||||||||||
| } from '../../util'; | ||||||||||||||||||||||||
| import { createPayment } from '../../util/api-client'; | ||||||||||||||||||||||||
| import { PaymentDialog } from '../PaymentDialog'; | ||||||||||||||||||||||||
| import { AltpaymentCoin, AltpaymentError, AltpaymentPair, AltpaymentShift } from '../../altpayment'; | ||||||||||||||||||||||||
| export interface PayButtonProps extends ButtonProps { | ||||||||||||||||||||||||
|
|
@@ -105,18 +105,21 @@ export const PayButton = ({ | |||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const [currencyObj, setCurrencyObj] = useState<CurrencyObject | undefined>(); | ||||||||||||||||||||||||
| const [cryptoAmount, setCryptoAmount] = useState<string>(); | ||||||||||||||||||||||||
| const [convertedAmount, setConvertedAmount] = useState<number>(); | ||||||||||||||||||||||||
| 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), | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
Comment on lines
+116
to
+122
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainReset paymentId when address/currency/randomization inputs change to avoid stale IDs Otherwise an ID tied to a previous address or unit policy may be reused. const [paymentId, setPaymentId] = useState<string | undefined>(undefined);
+ useEffect(() => {
+ setPaymentId(undefined);
+ }, [to, currency, randomSatoshis]);If the server binds paymentId to the original amount as well, also include Length of output: 46 🏁 Script executed: #!/bin/bash
# Search for paymentId usage patterns in the codebase
rg -n "paymentId" --type=ts --type=tsx -C 5 | head -100Length of output: 27 🏁 Script executed: #!/bin/bash
# Look for useEffect hooks and state management in PayButton area
fd "PayButton" -type f | xargs -I {} sh -c 'echo "=== {} ===" && cat {}'Length of output: 170 🏁 Script executed: #!/bin/bash
# Search for backend API calls related to payment
rg -n "payment" --type=ts --type=tsx -i -A 2 -B 2 | grep -i "api\|fetch\|axios\|request" | head -50Length of output: 27 Length of output: 12741 🏁 Script executed: #!/bin/bash
# Search for paymentId usage (corrected syntax)
rg -n "paymentId" -C 5Length of output: 90419 🏁 Script executed: #!/bin/bash
# Search for amount usage in payment context
rg -n "amount" -C 3 | head -80Length of output: 4137 🏁 Script executed: #!/bin/bash
# Look for useEffect hooks in the codebase
rg -n "useEffect" ./react/lib/components/PayButton/ -C 3Length of output: 4531 🏁 Script executed: #!/bin/bash
# Search for API calls or backend requests
rg -n "fetch\|axios\|api" -i ./react/lib/components/PayButton/ -C 3Length of output: 0 Reset paymentId when address, currency, amount, or randomization inputs change to prevent reuse of stale IDs Without resetting paymentId when these inputs change, the component can reuse IDs tied to previous addresses, amounts, or unit policies, causing validation mismatches. const [paymentId, setPaymentId] = useState<string | undefined>(undefined);
+ useEffect(() => {
+ setPaymentId(undefined);
+ }, [to, currency, amount, randomSatoshis]);The backend's 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||
| priceRef.current = price; | ||||||||||||||||||||||||
| }, [price]); | ||||||||||||||||||||||||
|
|
@@ -133,16 +136,56 @@ export const PayButton = ({ | |||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }, 300); | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const getPaymentId = useCallback(async ( | ||||||||||||||||||||||||
| currency: Currency, | ||||||||||||||||||||||||
| amount: number, | ||||||||||||||||||||||||
| convertedAmount: number | undefined, | ||||||||||||||||||||||||
| to: string | undefined, | ||||||||||||||||||||||||
| ): Promise<string | undefined> => { | ||||||||||||||||||||||||
| if (disablePaymentId || !to) return paymentId | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const amountToUse = | ||||||||||||||||||||||||
| (isFiat(currency) || randomSatoshis) && convertedAmount | ||||||||||||||||||||||||
| ? convertedAmount | ||||||||||||||||||||||||
| : 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, isFiat, randomSatoshis]) | ||||||||||||||||||||||||
|
Comment on lines
140
to
161
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix amount units for server creation and guard against concurrent calls For fiat or randomized flows, if convertedAmount isn’t ready, wait for price and compute a crypto amount; also guard to prevent duplicate createPayment calls. + const creatingPaymentIdRef = useRef(false);
const getPaymentId = useCallback(async (
currency: Currency,
amount: number,
convertedAmount: number | undefined,
to: string | undefined,
): Promise<string | undefined> => {
- if (disablePaymentId || !to) return paymentId
+ if (disablePaymentId || !to) return paymentId;
+ if (creatingPaymentIdRef.current) return paymentId;
+ creatingPaymentIdRef.current = true;
try {
- const amountToUse =
- (isFiat(currency) || randomSatoshis) && convertedAmount
- ? convertedAmount
- : amount
-
- console.log('Creating payment ID with amount:', amountToUse)
- const responsePaymentId = await createPayment(amountToUse, to, apiBaseUrl)
-
- setPaymentId(responsePaymentId)
- return responsePaymentId
+ let amountToUse = amount;
+ if (isFiat(currency) || randomSatoshis) {
+ if (convertedAmount !== undefined) {
+ amountToUse = convertedAmount;
+ } else {
+ // Wait until price is available if fiat, then compute crypto amount now
+ if (isFiat(currency) && priceRef.current === 0) {
+ await new Promise<void>((resolve) => waitPrice(resolve));
+ }
+ const addressTypeLocal: Currency = getCurrencyTypeFromAddress(to);
+ const convertedObj = getCurrencyObject(
+ isFiat(currency) && priceRef.current ? amount / priceRef.current : amount,
+ addressTypeLocal,
+ randomSatoshis,
+ );
+ amountToUse = convertedObj.float;
+ }
+ }
+ const responsePaymentId = await createPayment(amountToUse, to, apiBaseUrl);
+ setPaymentId(responsePaymentId);
+ return responsePaymentId;
} catch (err) {
- console.error('Error creating payment ID:', err)
- return undefined
+ console.error('Error creating payment ID:', err);
+ return undefined;
+ } finally {
+ creatingPaymentIdRef.current = false;
}
- }, [disablePaymentId, apiBaseUrl, isFiat, randomSatoshis])
+ }, [disablePaymentId, apiBaseUrl, randomSatoshis])
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| 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]) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (!disablePaymentId && !paymentId) { | ||||||||||||||||||||||||
| await getPaymentId(currency, Number(amount), convertedAmount, to) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| setDialogOpen(true) | ||||||||||||||||||||||||
| }, [ | ||||||||||||||||||||||||
| onOpen, | ||||||||||||||||||||||||
| isFiat, | ||||||||||||||||||||||||
| currency, | ||||||||||||||||||||||||
| amount, | ||||||||||||||||||||||||
| to, | ||||||||||||||||||||||||
| paymentId, | ||||||||||||||||||||||||
| disablePaymentId, | ||||||||||||||||||||||||
| getPaymentId, | ||||||||||||||||||||||||
| convertedAmount, | ||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const handleCloseDialog = (success?: boolean, paymentId?: string): void => { | ||||||||||||||||||||||||
| if (onClose !== undefined) onClose(success, paymentId); | ||||||||||||||||||||||||
|
|
@@ -185,42 +228,42 @@ export const PayButton = ({ | |||||||||||||||||||||||
| return | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| (async () => { | ||||||||||||||||||||||||
| if (txsSocket === undefined) { | ||||||||||||||||||||||||
| const expectedAmount = currencyObj ? currencyObj?.float : undefined | ||||||||||||||||||||||||
| await setupChronikWebSocket({ | ||||||||||||||||||||||||
| address: to, | ||||||||||||||||||||||||
| txsSocket, | ||||||||||||||||||||||||
| apiBaseUrl, | ||||||||||||||||||||||||
| wsBaseUrl, | ||||||||||||||||||||||||
| setTxsSocket, | ||||||||||||||||||||||||
| setNewTxs, | ||||||||||||||||||||||||
| setDialogOpen, | ||||||||||||||||||||||||
| checkSuccessInfo: { | ||||||||||||||||||||||||
| currency, | ||||||||||||||||||||||||
| price, | ||||||||||||||||||||||||
| randomSatoshis: randomSatoshis ?? false, | ||||||||||||||||||||||||
| disablePaymentId, | ||||||||||||||||||||||||
| expectedAmount, | ||||||||||||||||||||||||
| expectedOpReturn: opReturn, | ||||||||||||||||||||||||
| expectedPaymentId: paymentId, | ||||||||||||||||||||||||
| currencyObj, | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (altpaymentSocket === undefined && useAltpayment) { | ||||||||||||||||||||||||
| await setupAltpaymentSocket({ | ||||||||||||||||||||||||
| addressType, | ||||||||||||||||||||||||
| altpaymentSocket, | ||||||||||||||||||||||||
| wsBaseUrl, | ||||||||||||||||||||||||
| setAltpaymentSocket, | ||||||||||||||||||||||||
| setCoins, | ||||||||||||||||||||||||
| setCoinPair, | ||||||||||||||||||||||||
| setLoadingPair, | ||||||||||||||||||||||||
| setAltpaymentShift, | ||||||||||||||||||||||||
| setLoadingShift, | ||||||||||||||||||||||||
| setAltpaymentError, | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (txsSocket === undefined) { | ||||||||||||||||||||||||
| const expectedAmount = currencyObj ? currencyObj?.float : undefined | ||||||||||||||||||||||||
| await setupChronikWebSocket({ | ||||||||||||||||||||||||
| address: to, | ||||||||||||||||||||||||
| txsSocket, | ||||||||||||||||||||||||
| apiBaseUrl, | ||||||||||||||||||||||||
| wsBaseUrl, | ||||||||||||||||||||||||
| setTxsSocket, | ||||||||||||||||||||||||
| setNewTxs, | ||||||||||||||||||||||||
| setDialogOpen, | ||||||||||||||||||||||||
| checkSuccessInfo: { | ||||||||||||||||||||||||
| currency, | ||||||||||||||||||||||||
| price, | ||||||||||||||||||||||||
| randomSatoshis: randomSatoshis ?? false, | ||||||||||||||||||||||||
| disablePaymentId, | ||||||||||||||||||||||||
| expectedAmount, | ||||||||||||||||||||||||
| expectedOpReturn: opReturn, | ||||||||||||||||||||||||
| expectedPaymentId: paymentId, | ||||||||||||||||||||||||
| currencyObj, | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (altpaymentSocket === undefined && useAltpayment) { | ||||||||||||||||||||||||
| await setupAltpaymentSocket({ | ||||||||||||||||||||||||
| addressType, | ||||||||||||||||||||||||
| altpaymentSocket, | ||||||||||||||||||||||||
| wsBaseUrl, | ||||||||||||||||||||||||
| setAltpaymentSocket, | ||||||||||||||||||||||||
| setCoins, | ||||||||||||||||||||||||
| setCoinPair, | ||||||||||||||||||||||||
| setLoadingPair, | ||||||||||||||||||||||||
| setAltpaymentShift, | ||||||||||||||||||||||||
| setLoadingShift, | ||||||||||||||||||||||||
| setAltpaymentError, | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| })() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||
|
|
@@ -255,22 +298,35 @@ 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); | ||||||||||||||||||||||||
| setConvertedAmount(convertedObj.float); | ||||||||||||||||||||||||
| setConvertedCurrencyObj(convertedObj); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } else if (!isFiat(currency) && randomSatoshis && !convertedAmount){ | ||||||||||||||||||||||||
| const convertedObj = getCurrencyObject( | ||||||||||||||||||||||||
| currencyObj.float / price, | ||||||||||||||||||||||||
| amount as number, | ||||||||||||||||||||||||
| addressType, | ||||||||||||||||||||||||
| randomSatoshis, | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| setCryptoAmount(convertedObj.string); | ||||||||||||||||||||||||
| } else if (!isFiat(currency)) { | ||||||||||||||||||||||||
| setConvertedAmount(convertedObj.float); | ||||||||||||||||||||||||
| setConvertedCurrencyObj(convertedObj); | ||||||||||||||||||||||||
| } else if (!isFiat(currency) && !randomSatoshis) { | ||||||||||||||||||||||||
| setCryptoAmount(amount?.toString()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }, [price, currencyObj, amount, currency, randomSatoshis, to]); | ||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
|
|
@@ -347,6 +403,8 @@ export const PayButton = ({ | |||||||||||||||||||||||
| newTxs={newTxs} | ||||||||||||||||||||||||
| disableSound={disableSound} | ||||||||||||||||||||||||
| transactionText={transactionText} | ||||||||||||||||||||||||
| convertedCurrencyObj={convertedCurrencyObj} | ||||||||||||||||||||||||
| setConvertedCurrencyObj={setConvertedCurrencyObj} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| {errorMsg && ( | ||||||||||||||||||||||||
| <p | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.