From 60d7e2b30cf12607e59fca90950418f417a4068e Mon Sep 17 00:00:00 2001 From: Swiss Bitcoin Pay Date: Sun, 8 Sep 2024 15:19:29 +0200 Subject: [PATCH] better support for underpaid onchain payments --- src/assets/images/bitcoin-to-slot.png | Bin 0 -> 17971 bytes src/assets/translations/de.json | 2 + src/assets/translations/en.json | 2 + src/assets/translations/es.json | 2 + src/assets/translations/fr.json | 2 + src/assets/translations/it.json | 2 + src/assets/translations/pt.json | 2 + src/screens/Invoice/Invoice.tsx | 127 +++++++++++------- .../components/FooterLine/FooterLine.tsx | 14 +- src/screens/Invoice/styled.tsx | 18 ++- src/utils/getFormattedUnit.ts | 4 +- src/utils/index.ts | 3 +- src/utils/numberWithSpaces.ts | 2 + 13 files changed, 117 insertions(+), 63 deletions(-) create mode 100644 src/assets/images/bitcoin-to-slot.png create mode 100644 src/utils/numberWithSpaces.ts diff --git a/src/assets/images/bitcoin-to-slot.png b/src/assets/images/bitcoin-to-slot.png new file mode 100644 index 0000000000000000000000000000000000000000..e89345b23d6608edbf0d4bc890b463c7c80181f7 GIT binary patch literal 17971 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

kxm*c-op&A=79XGWSM0da=z-H_k6AD^$d>rfH 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 literal 0 HcmV?d00001 diff --git a/src/assets/translations/de.json b/src/assets/translations/de.json index db7a4d5b..d266ac57 100644 --- a/src/assets/translations/de.json +++ b/src/assets/translations/de.json @@ -119,6 +119,8 @@ "status": "Status", "timeLeft": "Verbleibende Zeit", "amount": "Betrag", + "alreadyPaid": "Bereits bezahlt", + "payTheRest": "Bezahlen Sie den Rest ({{sats}} sats) über Lightning oder Onchain", "paidOn": "Bezahlt am", "awaitingPayment": "Erwartete Zahlung", "returningToTerminal": "Rückkehr zum Terminal", diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json index 66caeb38..db14cf4e 100644 --- a/src/assets/translations/en.json +++ b/src/assets/translations/en.json @@ -120,6 +120,8 @@ "status": "Status", "timeLeft": "Time left", "amount": "Amount", + "alreadyPaid": "Already paid", + "payTheRest": "Pay the rest ({{sats}} sats) via Lightning or Onchain", "paidOn": "Paid on", "awaitingPayment": "Awaiting payment", "returningToTerminal": "Returning to terminal", diff --git a/src/assets/translations/es.json b/src/assets/translations/es.json index a323168b..75505c2b 100644 --- a/src/assets/translations/es.json +++ b/src/assets/translations/es.json @@ -119,6 +119,8 @@ "status": "Estado", "timeLeft": "Tiempo restante", "amount": "Importe", + "alreadyPaid": "Ya pagado", + "payTheRest": "Pagar el resto ({{sats}} sats) a través de Lightning o Onchain", "paidOn": "Pagado el", "awaitingPayment": "En espera de pago", "returningToTerminal": "Volver al terminal", diff --git a/src/assets/translations/fr.json b/src/assets/translations/fr.json index f58f1b98..c988c024 100644 --- a/src/assets/translations/fr.json +++ b/src/assets/translations/fr.json @@ -120,6 +120,8 @@ "status": "Statut", "timeLeft": "Temps restant", "amount": "Montant", + "alreadyPaid": "Déjà payé", + "payTheRest": "Payez le reste ({{sats}} sats) via Lightning ou Onchain", "paidOn": "Payé le", "awaitingPayment": "En attente de paiement", "returningToTerminal": "Retour au terminal", diff --git a/src/assets/translations/it.json b/src/assets/translations/it.json index e5846767..85c6de8a 100644 --- a/src/assets/translations/it.json +++ b/src/assets/translations/it.json @@ -119,6 +119,8 @@ "status": "Stato", "timeLeft": "Tempo rimanente", "amount": "Importo", + "alreadyPaid": "Già pagato", + "payTheRest": "Pagare il resto ({{sats}} sats) tramite Lightning o Onchain", "paidOn": "Pagato il", "awaitingPayment": "In attesa di pagamento", "returningToTerminal": "Ritorno al terminale", diff --git a/src/assets/translations/pt.json b/src/assets/translations/pt.json index e2ee231f..eab51985 100644 --- a/src/assets/translations/pt.json +++ b/src/assets/translations/pt.json @@ -119,6 +119,8 @@ "status": "Estado", "timeLeft": "Tempo restante", "amount": "Montante", + "alreadyPaid": "Já pago", + "payTheRest": "Pagar o restante ({{sats}} sats) via Lightning ou Onchain", "paidOn": "Pago em", "awaitingPayment": "A aguardar pagamento", "returningToTerminal": "Regresso ao terminal", diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index 9ef2dee9..c0a6ae35 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -62,14 +62,12 @@ import { import LottieView from "lottie-react-native"; import * as S from "./styled"; import { XOR } from "ts-essentials"; +import { numberWithSpaces } from "@utils/numberWithSpaces"; const PAID_ANIMATION_DURATION = 350; const getTrue = () => true; -const numberWithSpaces = (nb: number) => - nb.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, " "); - const { isWeb, isIos } = platform; type Status = @@ -87,6 +85,7 @@ type OnchainTx = { network: "onchain"; address: string; txId?: string; + amount?: number; vout_index?: number; confirmations?: number; minConfirmations?: number; @@ -198,7 +197,7 @@ export const Invoice = () => { const [paidAt, setPaidAt] = useState(); // Onchain data - const [onChainTx, setOnchainTx] = useState(); + const [onChainTxs, setOnchainTxs] = useState(); const isAlive = useMemo( () => !["settled", "expired", "unconfirmed"].includes(status), @@ -330,7 +329,7 @@ export const Invoice = () => { ); const updateInvoice = useCallback( - async (getInvoiceData: InvoiceType, isInitialData?: boolean) => { + (getInvoiceData: InvoiceType, isInitialData?: boolean) => { try { const _pr = getInvoiceData.paymentDetails.find( @@ -339,11 +338,9 @@ export const Invoice = () => { getInvoiceData.paymentDetails.find((p) => p.network === "lightning") ?.paymentRequest; - const _onChainData = - getInvoiceData.paymentDetails.find( - (p) => p.network === "onchain" && p.paidAt - ) || - getInvoiceData.paymentDetails.find((p) => p.network === "onchain"); + const unpaidOnchain = getInvoiceData.paymentDetails.find( + (p) => p.network === "onchain" && !p.paidAt + ); const _paymentMethod = getInvoiceData.paymentDetails.find( (p) => p.paidAt @@ -355,13 +352,17 @@ export const Invoice = () => { setDelay(getInvoiceData.expiry - getInvoiceData.time); setPr(_pr); setReadingNfcData(_pr); - setOnChainAddr(_onChainData?.address); + setOnChainAddr(unpaidOnchain?.address); setAmount(getInvoiceData.amount * 1000); setInvoiceCurrency(getInvoiceData.input.unit || "CHF"); setInvoiceFiatAmount(getInvoiceData.input.amount); setStatus(getInvoiceData.status); setPaymentMethod(_paymentMethod); - setOnchainTx(_onChainData); + setOnchainTxs( + getInvoiceData.paymentDetails.filter( + (p) => p.network === "onchain" && p.paidAt + ) + ); setPaidAt(getInvoiceData.paidAt); setIsInit(true); @@ -387,24 +388,6 @@ export const Invoice = () => { if (status === "expired") { return; } - - if (_paymentMethod === "onchain") { - try { - const { data: txDetails } = await axios.get( - `https://mempool.space/api/tx/${_onChainData?.txId}` - ); - if (txDetails.status.confirmed) { - const { data: blockHeight } = await axios.get( - "https://mempool.space/api/blocks/tip/height" - ); - setOnchainTx({ - ..._onChainData, - minConfirmations: - blockHeight - txDetails.status.block_height + 1 - }); - } - } catch (e) {} - } } catch (e) { setIsInvalidInvoice(true); return; @@ -478,6 +461,20 @@ export const Invoice = () => { [status, isExternalInvoice] ); + const alreadyPaidAmount = useMemo( + () => onChainTxs?.reduce((result, o) => result + (o.amount || 0), 0) || 0, + [onChainTxs] + ); + + const { confirmations, minConfirmations } = useMemo( + () => + (onChainTxs || []).reduce( + (result, o) => (o.confirmations < result.confirmations ? o : result), + { confirmations: 100, minConfirmations: 0 } + ), + [onChainTxs] + ); + const [isQrModalOpen, setIsQrModalOpen] = useState(false); const onOpenQrModal = useCallback(() => { @@ -635,7 +632,7 @@ export const Invoice = () => { /> ) : null} {!isAlive && ( - + {status === "unconfirmed" && ( )} @@ -741,6 +738,28 @@ export const Invoice = () => { {amount ? numberWithSpaces(amount / 1000) : ""} sats )} + {alreadyPaidAmount > 0 && status === "underpaid" && ( + <> + + + {t("alreadyPaid")}: {numberWithSpaces(alreadyPaidAmount)}{" "} + sats + + + {t("payTheRest", { + sats: numberWithSpaces( + amount / 1000 - alreadyPaidAmount + ) + })} + + + )} {status === "settled" && isExternalInvoice && @@ -868,26 +887,32 @@ export const Invoice = () => { .toString()} /> )} - {onChainTx?.txId && ( - - )} - {onChainTx?.confirmations !== undefined && ( - - )} + {onChainTxs?.map((tx) => { + const success = tx.confirmations >= tx.minConfirmations; + const color = success ? colors.success : colors.warning; + + return ( + <> + {tx.txId && ( + + })} + url={`https://mempool.space/tx/${tx.txId}${ + tx.vout_index !== undefined + ? `#vout=${tx.vout_index}` + : "" + }`} + value={truncate(tx.txId, 16)} + /> + )} + + ); + })} )} diff --git a/src/screens/Invoice/components/FooterLine/FooterLine.tsx b/src/screens/Invoice/components/FooterLine/FooterLine.tsx index c967285e..3c136e03 100644 --- a/src/screens/Invoice/components/FooterLine/FooterLine.tsx +++ b/src/screens/Invoice/components/FooterLine/FooterLine.tsx @@ -60,13 +60,6 @@ export const FooterLine = ({ target="_blank" rel="noopener noreferrer" > - {(copyable || url) && ( - - )} {prefixIcon && ( )} {prefixComponent && cloneElement(prefixComponent, { color })} + {(copyable || url) && ( + + )} {value} {valueSuffix} diff --git a/src/screens/Invoice/styled.tsx b/src/screens/Invoice/styled.tsx index ba2b2e0c..2a61ac97 100644 --- a/src/screens/Invoice/styled.tsx +++ b/src/screens/Invoice/styled.tsx @@ -15,7 +15,7 @@ import { import { platform } from "@config"; import { Circle } from "react-native-progress"; -const isNative = { platform }; +const { isNative } = platform; type InvoicePageContainerProps = { isLoading: boolean }; @@ -173,16 +173,28 @@ export const NFCSwitchContainerCircle = styled(View)` type AmountTextProps = { subAmount?: boolean; + color?: string; }; export const AmountText = styled(Text).attrs( - ({ theme, subAmount }) => ({ + ({ theme, subAmount, color }) => ({ ...(subAmount ? { h4: true } : { h2: true }), weight: 700, - color: theme.colors.white + color: color || theme.colors.white }) )``; +export const BitcoinSlotText = styled(AmountText)` + display: flex; + margin-top: 16px; +`; + +export const BitcoinSlotImage = styled(Image)` + width: 20px; + height: 20px; + margin-right: 6px; +`; + export const ProgressBar = styled(RootProgressBar)` width: 90%; `; diff --git a/src/utils/getFormattedUnit.ts b/src/utils/getFormattedUnit.ts index f0386a14..148c59ed 100644 --- a/src/utils/getFormattedUnit.ts +++ b/src/utils/getFormattedUnit.ts @@ -1,3 +1,5 @@ +import { numberWithSpaces } from "@utils"; + export const getFormattedUnit = ( amount: number, unit: string, @@ -10,7 +12,7 @@ export const getFormattedUnit = ( } if (unit === "sat" || unit === "sats") { - return `${prefix}${amount} sats`; + return `${prefix}${numberWithSpaces(amount)} sats`; } return `${prefix}${Intl.NumberFormat(undefined, { diff --git a/src/utils/index.ts b/src/utils/index.ts index 1b6d9efa..630d4ed7 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -18,4 +18,5 @@ export { generateBtcAddress } from "./generateBtcAddress"; export { validateBitcoinAddress } from "./validateBitcoinAddress"; export { hexToRgb } from "./hexToRgb"; export { mergeDeep } from "./mergeDeep"; -export { isMinUserType } from "./isMinUserType"; \ No newline at end of file +export { isMinUserType } from "./isMinUserType"; +export { numberWithSpaces } from "./numberWithSpaces"; \ No newline at end of file diff --git a/src/utils/numberWithSpaces.ts b/src/utils/numberWithSpaces.ts new file mode 100644 index 00000000..17c7c68b --- /dev/null +++ b/src/utils/numberWithSpaces.ts @@ -0,0 +1,2 @@ +export const numberWithSpaces = (nb: number) => + nb.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, " ");