async def r_pay_invoice(user: User, *_, invoice: str, amt: Optional[int] = None): # determine true invoice amount pay_string = ln.PayReqString(pay_req=invoice.replace("lightning:", "")) try: decoded = await LND.stub.DecodePayReq(pay_string) except GRPCError as e: return Error("PaymentError", str(e)) if amt is not None and decoded.num_satoshis != amt and decoded.num_satoshis > 0: return Error("PaymentError", "Payment amount does not match invoice amount") if decoded.num_satoshis == 0 and not amt: return Error("PaymentError", "You must specify an amount for this tip invoice") payment_amt = amt or decoded.num_satoshis fee_limit = ceil(payment_amt * 0.01) # convert decoded hex to string b64 b64_payment_hash = b64encode(b16decode(decoded.payment_hash, casefold=True)).decode() # lock payer's db row before determining balance async with GINO.db.transaction(): # potentially user.query.with_for.. await user.query.with_for_update().gino.status() # obtain lock user_balance = await user.balance() if payment_amt + fee_limit > user_balance: return Error( "InsufficientFunds", f"""Attempting to pay {payment_amt} sat with fee limit {fee_limit} sat with only {user_balance} sat""", ) # determine if external node invoice if LND.id_pubkey != decoded.destination: req = ln.SendRequest( payment_request=invoice, amt=payment_amt, fee_limit=ln.FeeLimit(fixed=fee_limit), ) invoice_obj = Invoice( payment_hash=b64_payment_hash, payment_request=invoice, timestamp=decoded.timestamp, expiry=decoded.expiry, memo=decoded.description, paid=False, # not yet paid amount=decoded.num_satoshis, payer=user.username, ) payment_res = await LND.stub.SendPaymentSync(req) if payment_res.payment_error or not payment_res.payment_preimage: return Error("PaymentError", payment_res.payment_error) invoice_obj.payment_preimage = b64encode( payment_res.payment_preimage).decode() # impose maximum fee invoice_obj.fee = max(fee_limit, payment_res.payment_route.total_fees) invoice_obj.paid = True invoice_obj.paid_at = int(time()) return await invoice_obj.create() # determine if internal user invoice elif LND.id_pubkey == decoded.destination and ( invoice_obj := await Invoice.get(b64_payment_hash)): if invoice_obj.paid: return Error("PaymentError", "This invoice has already been paid") # internal invoice, get payee from db if not (payee := await User.get(invoice_obj.payee)): # could not find the invoice payee in the db return Error("PaymentError", "This invoice is invalid") await invoice_obj.update(paid=True, payer=user.username, fee=fee_limit, paid_at=time()).apply() # check if there are clients in the subscribe channel for this invoice if payee.username in PUBSUB.keys(): # clients are listening, push to all open clients for client in PUBSUB[payee.username]: await client.put(invoice_obj) return invoice_obj