diff --git a/components/invoice.js b/components/invoice.js
index 7cfd5a74f..951070457 100644
--- a/components/invoice.js
+++ b/components/invoice.js
@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from 'react'
-import { useMutation, useQuery } from '@apollo/client'
+import { useApolloClient, useMutation, useQuery } from '@apollo/client'
import { Button } from 'react-bootstrap'
import { gql } from 'graphql-tag'
import { numWithUnits } from '../lib/format'
@@ -12,6 +12,7 @@ import { useShowModal } from './modal'
import Countdown from './countdown'
import PayerData from './payer-data'
import Bolt11Info from './bolt11-info'
+import { useWebLN } from './webln'
export function Invoice ({ invoice, modal, onPayment, info, successVerb }) {
const [expired, setExpired] = useState(new Date(invoice.expiredAt) <= new Date())
@@ -161,6 +162,7 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => {
mutation createInvoice($amount: Int!) {
createInvoice(amount: $amount, hodlInvoice: true, expireSecs: 180) {
id
+ bolt11
hash
hmac
expiresAt
@@ -175,6 +177,9 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => {
`)
const showModal = useShowModal()
+ const provider = useWebLN()
+ const client = useApolloClient()
+ const pollInvoice = (id) => client.query({ query: INVOICE, variables: { id } })
const onSubmitWrapper = useCallback(async ({ cost, ...formValues }, ...submitArgs) => {
// some actions require a session
@@ -206,24 +211,13 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => {
const inv = data.createInvoice
// wait until invoice is paid or modal is closed
- let modalClose
- await new Promise((resolve, reject) => {
- showModal(onClose => {
- modalClose = onClose
- return (
-
- )
- }, { keepOpen: true, onClose: reject })
- })
+ const modalClose = await waitForPayment(inv, showModal, provider, pollInvoice)
const retry = () => onSubmit({ hash: inv.hash, hmac: inv.hmac, ...formValues }, ...submitArgs)
// first retry
try {
const ret = await retry()
- modalClose()
+ modalClose?.()
return ret
} catch (error) {
console.error('retry error:', error)
@@ -255,6 +249,49 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => {
return onSubmitWrapper
}
+const waitForPayment = async (inv, showModal, provider, pollInvoice) => {
+ try {
+ // try WebLN provider first
+ return await new Promise((resolve, reject) => {
+ // don't use await here since we're using HODL invoices
+ // and sendPaymentAsync is not supported yet.
+ // see https://www.webln.guide/building-lightning-apps/webln-reference/webln.sendpaymentasync
+ provider.sendPayment(inv.bolt11)
+ const interval = setInterval(async () => {
+ try {
+ const { data, error } = await pollInvoice(inv.id)
+ if (error) {
+ clearInterval(interval)
+ return reject(error)
+ }
+ const { invoice } = data
+ if (invoice.isHeld && invoice.satsReceived) {
+ clearInterval(interval)
+ resolve()
+ }
+ } catch (err) {
+ clearInterval(interval)
+ reject(err)
+ }
+ }, 1000)
+ })
+ } catch (err) {
+ console.error('WebLN payment failed:', err)
+ }
+
+ // QR code as fallback
+ return await new Promise((resolve, reject) => {
+ showModal(onClose => {
+ return (
+ resolve(onClose)}
+ />
+ )
+ }, { keepOpen: true, onClose: reject })
+ })
+}
+
export const useInvoiceModal = (onPayment, deps) => {
const onPaymentMemo = useCallback(onPayment, deps)
return useInvoiceable(onPaymentMemo, { replaceModal: true })