diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py index 4c6e65451..185479ad7 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -632,10 +632,9 @@ class SendTab(QWidget, MessageBoxMixin, Logger): return False, repr(e) # success txid = tx.txid() - if self.payment_identifier.needs_round_3(): + if self.payment_identifier.need_merchant_notify(): refund_address = self.wallet.get_receiving_address() - coro = self.payment_identifier.round_3(tx.serialize(), refund_address) - asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) + self.payment_identifier.notify_merchant(tx=tx, refund_address=refund_address) return True, txid # Capture current TL window; override might be removed on return diff --git a/electrum/payment_identifier.py b/electrum/payment_identifier.py index 7374fea44..7137f9397 100644 --- a/electrum/payment_identifier.py +++ b/electrum/payment_identifier.py @@ -182,9 +182,13 @@ class PaymentIdentifierState(IntEnum): # of the channels Electrum supports (on-chain, lightning) NEED_RESOLVE = 3 # PI contains a recognized destination format, but needs an online resolve step LNURLP_FINALIZE = 4 # PI contains a resolved LNURLp, but needs amount and comment to resolve to a bolt11 - BIP70_VIA = 5 # PI contains a valid payment request that should have the tx submitted through bip70 gw + MERCHANT_NOTIFY = 5 # PI contains a valid payment request and on-chain destination. It should notify + # the merchant payment processor of the tx after on-chain broadcast, + # and supply a refund address (bip70) + MERCHANT_ACK = 6 # PI notified merchant. nothing to be done. ERROR = 50 # generic error NOT_FOUND = 51 # PI contains a recognized destination format, but resolve step was unsuccesful + MERCHANT_ERROR = 52 # PI failed notifying the merchant after broadcasting onchain TX class PaymentIdentifier(Logger): @@ -221,6 +225,8 @@ class PaymentIdentifier(Logger): # self.bip70 = None self.bip70_data = None + self.merchant_ack_status = None + self.merchant_ack_message = None # self.lnurl = None self.lnurl_data = None @@ -238,6 +244,9 @@ class PaymentIdentifier(Logger): def need_finalize(self): return self._state == PaymentIdentifierState.LNURLP_FINALIZE + def need_merchant_notify(self): + return self._state == PaymentIdentifierState.MERCHANT_NOTIFY + def is_valid(self): return self._state not in [PaymentIdentifierState.INVALID, PaymentIdentifierState.EMPTY] @@ -250,9 +259,6 @@ class PaymentIdentifier(Logger): def get_error(self) -> str: return self.error - def needs_round_3(self): - return self.bip70 - def parse(self, text): # parse text, set self._type and self.error text = text.strip() @@ -304,11 +310,11 @@ class PaymentIdentifier(Logger): def resolve(self, *, on_finished: 'Callable'): assert self._state == PaymentIdentifierState.NEED_RESOLVE - coro = self.do_resolve(on_finished=on_finished) + coro = self._do_resolve(on_finished=on_finished) asyncio.run_coroutine_threadsafe(coro, get_asyncio_loop()) @log_exceptions - async def do_resolve(self, *, on_finished=None): + async def _do_resolve(self, *, on_finished=None): try: if self.emaillike: data = await self.resolve_openalias() @@ -338,7 +344,7 @@ class PaymentIdentifier(Logger): from . import paymentrequest data = await paymentrequest.get_payment_request(self.bip70) self.bip70_data = data - self.set_state(PaymentIdentifierState.BIP70_VIA) + self.set_state(PaymentIdentifierState.MERCHANT_NOTIFY) elif self.lnurl: data = await request_lnurl(self.lnurl) self.lnurl_data = data @@ -356,11 +362,11 @@ class PaymentIdentifier(Logger): def finalize(self, *, amount_sat: int = 0, comment: str = None, on_finished: Callable = None): assert self._state == PaymentIdentifierState.LNURLP_FINALIZE - coro = self.do_finalize(amount_sat, comment, on_finished=on_finished) + coro = self._do_finalize(amount_sat, comment, on_finished=on_finished) asyncio.run_coroutine_threadsafe(coro, get_asyncio_loop()) @log_exceptions - async def do_finalize(self, amount_sat: int = None, comment: str = None, on_finished: Callable = None): + async def _do_finalize(self, amount_sat: int = None, comment: str = None, on_finished: Callable = None): from .invoices import Invoice try: if not self.lnurl_data: @@ -399,6 +405,32 @@ class PaymentIdentifier(Logger): if on_finished: on_finished(self) + def notify_merchant(self, *, tx: 'Transaction' = None, refund_address: str = None, on_finished: 'Callable' = None): + assert self._state == PaymentIdentifierState.MERCHANT_NOTIFY + assert tx + coro = self._do_notify_merchant(tx, refund_address, on_finished=on_finished) + asyncio.run_coroutine_threadsafe(coro, get_asyncio_loop()) + + @log_exceptions + async def _do_notify_merchant(self, tx, refund_address, *, on_finished: 'Callable'): + try: + if not self.bip70_data: + self.set_state(PaymentIdentifierState.ERROR) + return + + ack_status, ack_msg = await self.bip70_data.send_payment_and_receive_paymentack(tx.serialize(), refund_address) + self.logger.info(f"Payment ACK: {ack_status}. Ack message: {ack_msg}") + self.merchant_ack_status = ack_status + self.merchant_ack_message = ack_msg + self.set_state(PaymentIdentifierState.MERCHANT_ACK) + except Exception as e: + self.error = str(e) + self.logger.error(repr(e)) + self.set_state(PaymentIdentifierState.MERCHANT_ERROR) + finally: + if on_finished: + on_finished(self) + def get_onchain_outputs(self, amount): if self.bip70: return self.bip70_data.get_outputs() @@ -609,13 +641,6 @@ class PaymentIdentifier(Logger): return self.bip70_data.has_expired() return False - @log_exceptions - async def round_3(self, tx, refund_address, *, on_success): - if self.bip70: - ack_status, ack_msg = await self.bip70.send_payment_and_receive_paymentack(tx.serialize(), refund_address) - self.logger.info(f"Payment ACK: {ack_status}. Ack message: {ack_msg}") - on_success(self) - def get_invoice(self, amount_sat, message): from .invoices import Invoice if self.is_lightning():