import { useState, useCallback, useEffect } from 'react' import { useMutation, useQuery } from '@apollo/client' import { Button } from 'react-bootstrap' import { gql } from 'graphql-tag' import { numWithUnits } from '../lib/format' import AccordianItem from './accordian-item' import Qr, { QrSkeleton } from './qr' import { CopyInput } from './form' import { INVOICE } from '../fragments/wallet' import InvoiceStatus from './invoice-status' import { useMe } from './me' import { useShowModal } from './modal' import { sleep } from '../lib/time' import FundError, { isInsufficientFundsError } from './fund-error' import { usePaymentTokens } from './payment-tokens' export function Invoice ({ invoice, onConfirmation, successVerb }) { let variant = 'default' let status = 'waiting for you' let webLn = true if (invoice.confirmedAt) { variant = 'confirmed' status = `${numWithUnits(invoice.satsReceived, { abbreviate: false })} ${successVerb || 'deposited'}` webLn = false } else if (invoice.cancelled) { variant = 'failed' status = 'cancelled' webLn = false } else if (invoice.expiresAt <= new Date()) { variant = 'failed' status = 'expired' webLn = false } useEffect(() => { if (invoice.confirmedAt) { onConfirmation?.(invoice) } }, [invoice.confirmedAt]) const { nostr } = invoice return ( <>
{nostr ? {JSON.stringify(nostr, null, 2)} } /> : null}
) } const Contacts = ({ invoiceHash, invoiceHmac }) => { const subject = `Support request for payment hash: ${invoiceHash}` const body = 'Hi, I successfully paid for but the action did not work.' return (
payment token save this} type='text' placeholder={invoiceHash + '|' + invoiceHmac} readOnly noForm />
e-mail \ sphinx \ telegram \ simplex
) } const ActionInvoice = ({ id, hash, hmac, errorCount, repeat, ...props }) => { const { data, loading, error } = useQuery(INVOICE, { pollInterval: 1000, variables: { id } }) if (error) { if (error.message?.includes('invoice not found')) { return } return
error
} if (!data || loading) { return } let errorStatus = 'Something went wrong trying to perform the action after payment.' if (errorCount > 1) { errorStatus = 'Something still went wrong.\nPlease contact admins for support or to request a refund.' } return ( <> {errorCount > 0 ? ( <>
) : null} ) } const defaultOptions = { forceInvoice: false, requireSession: false } export const useInvoiceable = (fn, options = defaultOptions) => { const me = useMe() const [createInvoice, { data }] = useMutation(gql` mutation createInvoice($amount: Int!) { createInvoice(amount: $amount, expireSecs: 1800) { id hash hmac } }`) const showModal = useShowModal() const [fnArgs, setFnArgs] = useState() const { addPaymentToken, removePaymentToken } = usePaymentTokens() // fix for bug where `showModal` runs the code for two modals and thus executes `onConfirmation` twice let errorCount = 0 const onConfirmation = useCallback( (onClose, hmac) => { return async ({ id, satsReceived, hash }) => { addPaymentToken(hash, hmac, satsReceived) await sleep(500) const repeat = () => fn(satsReceived, ...fnArgs, hash, hmac) .then(() => { removePaymentToken(hash, hmac) }) .then(onClose) .catch((error) => { console.error(error) errorCount++ onClose() showModal(onClose => ( ), { keepOpen: true }) }) // prevents infinite loop of calling `onConfirmation` if (errorCount === 0) await repeat() } }, [fn, fnArgs] ) const invoice = data?.createInvoice useEffect(() => { if (invoice) { showModal(onClose => ( ), { keepOpen: true } ) } }, [invoice?.id]) const actionFn = useCallback(async (amount, ...args) => { if (!me && options.requireSession) { throw new Error('you must be logged in') } if (!amount || (me && !options.forceInvoice)) { try { return await fn(amount, ...args) } catch (error) { if (isInsufficientFundsError(error)) { showModal(onClose => { return ( { await fn(amount, ...args, invoiceHash, invoiceHmac) }} /> ) }) return { keepLocalStorage: true } } throw error } } setFnArgs(args) await createInvoice({ variables: { amount } }) // tell onSubmit handler that we want to keep local storage // even though the submit handler was "successful" return { keepLocalStorage: true } }, [fn, setFnArgs, createInvoice]) return actionFn }