import { useMutation, useQuery } from '@apollo/client' import { GraphQLError } from 'graphql' import { gql } from 'graphql-tag' import { useCallback, useEffect, useState } from 'react' import { useShowModal } from '../components/modal' import { Invoice as QrInvoice } from '../components/invoice' import { QrSkeleton } from '../components/qr' import { useMe } from '../components/me' import { msatsToSats } from './format' import FundError from '../components/fund-error' import { INVOICE } from '../fragments/wallet' import InvoiceStatus from '../components/invoice-status' import { sleep } from './time' import { Button } from 'react-bootstrap' import { CopyInput } from '../components/form' const Contacts = ({ invoiceHash }) => { const subject = `Support request for payment hash: ${invoiceHash}` const body = 'Hi, I successfully paid for but the action did not work.' return (
Payment hash
e-mail \ sphinx \ telegram \ simplex
) } const Invoice = ({ id, hash, 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. Please try again.' if (errorCount > 1) { errorStatus = 'Something still went wrong.\nPlease contact admins for support or to request a refund.' } return ( <> {errorCount > 0 ? ( <> {errorCount === 1 ?
: } ) : null} ) } export const isInsufficientFundsError = (error) => { if (Array.isArray(error)) { return error.some(({ message }) => message.includes('insufficient funds')) } return error.toString().includes('insufficient funds') } const defaultOptions = { forceInvoice: false, requireSession: false } export const useAnonymous = (fn, options = defaultOptions) => { const me = useMe() const [createInvoice, { data }] = useMutation(gql` mutation createInvoice($amount: Int!) { createInvoice(amount: $amount) { id hash } }`) const showModal = useShowModal() const [fnArgs, setFnArgs] = useState() // fix for bug where `showModal` runs the code for two modals and thus executes `onConfirmation` twice let called = false let errorCount = 0 const onConfirmation = useCallback( onClose => { called = false return async ({ id, satsReceived, hash }) => { if (called) return called = true await sleep(2000) const repeat = () => fn(satsReceived, ...fnArgs, hash) .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 anonFn = useCallback(async (amount, ...args) => { if (!me && options.requireSession) { throw new Error('you must be logged in') } if (me && !options.forceInvoice) { try { return await fn(amount, ...args) } catch (error) { if (isInsufficientFundsError(error)) { showModal(onClose => { return ( { await fn(amount, ...args, invoiceHash) }} /> ) }) return } throw new Error({ message: error.toString() }) } } setFnArgs(args) return createInvoice({ variables: { amount } }) }, [fn, setFnArgs, createInvoice]) return anonFn } export const checkInvoice = async (models, invoiceHash, fee) => { const invoice = await models.invoice.findUnique({ where: { hash: invoiceHash }, include: { user: true } }) if (!invoice) { throw new GraphQLError('invoice not found', { extensions: { code: 'BAD_INPUT' } }) } if (!invoice.msatsReceived) { throw new GraphQLError('invoice was not paid', { extensions: { code: 'BAD_INPUT' } }) } if (msatsToSats(invoice.msatsReceived) < fee) { throw new GraphQLError('invoice amount too low', { extensions: { code: 'BAD_INPUT' } }) } return invoice }