SatSale

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit adf4840d69cf2ef9cf53ff8d34fb6f630f747e53
parent f5bc78446132e746f7312f3a0df5c4c92dacd6b8
Author: Nick <nicholas.w.farrow@gmail.com>
Date:   Fri, 22 Jan 2021 21:28:55 +1100

Merge pull request #1 from nickfarrow/lnd

Merging lightning network support into master - Featuring LND support.
Diffstat:
A.gitignore | 3+++
MREADME.md | 6+++---
Minvoice/payment_invoice.py | 6+++++-
Mpay/lnd.py | 93++++++++++++++++++++++++++++++++-----------------------------------------------
Mrequirements.txt | 1+
Mserver.py | 8++++----
Mssh_tunnel.py | 3++-
7 files changed, 55 insertions(+), 65 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +*.macaroon +*.cert +config.py diff --git a/README.md b/README.md @@ -1,5 +1,5 @@ # BTCPyment -Existing non-custodial Bitcoin payment processors are bloated, difficult to install, and not easily customisable. BTCPyment strives to serve as an easily deployable, lightweight Bitcoin payment processor that keeps your coins with your keys by connecting to your own Bitcoin node. +Existing non-custodial Bitcoin payment processors are bloated, difficult to install, and not easily customisable. BTCPyment strives to serve as an easily deployable, lightweight Bitcoin payment processor that keeps your coins with your keys by connecting to your own Bitcoin node or Lightning network node. Donation Button | Bitcoin Payment Gateway :-------------------------:|:-------------------------: @@ -36,7 +36,7 @@ rpcport = "8332" username = "bitcoinrpc" password = "RPCPASSWORD" ``` -(You can find these in `~/.bitcoin/bitcoin.conf`). If your node is remote to your website, you can specify an SSH `tunnel_host = "pi@192.168.0.252"` that will forward `rpcport`. You may also need to set `rpcallowip=YOUR_SERVER_IP` in your `~/.bitcoin/bitcoin.conf`. +(You can find these in `~/.bitcoin/bitcoin.conf`). If your node is remote to your website, you can specify an SSH `tunnel_host = "pi@192.168.0.252"` that will forward `rpcport`. You may also need to set `rpcallowip=YOUR_SERVER_IP` in your `~/.bitcoin/bitcoin.conf`. If you want to use lightning network payments, see [Lightning instructions](docs/lightning.md)] ### Run BTCPyment Run BTCPyment with @@ -51,7 +51,7 @@ nohup gunicorn --worker-class eventlet -w 1 -b 0.0.0.0:8000 server:app > log.txt tail -f log.txt ``` -## Embed Donation Button +## Embed a Donation Button Now embed the donation button into your website: ```html <iframe src="http://YOUR_SERVER_IP:8000/" style="margin: 0 auto;display:block;height:320px;border:none;overflow:hidden;" scrolling="no"></iframe> diff --git a/invoice/payment_invoice.py b/invoice/payment_invoice.py @@ -21,7 +21,11 @@ class invoice: def create_qr(self): - qr_str = "BITCOIN:{}?amount={}&label={}".format(self.address.upper(), self.value, self.label) + if config.pay_method == 'lnd': + qr_str = "{}".format(self.address.upper()) + else: + qr_str = "{}?amount={}&label={}".format(self.address.upper(), self.value, self.label) + img = qrcode.make(qr_str) img.save('static/qr_codes/{}.png'.format(self.uuid)) return diff --git a/pay/lnd.py b/pay/lnd.py @@ -13,34 +13,36 @@ class lnd(invoice): super().__init__(dollar_value, currency, label) print(self.__dict__) - from lnd_grpc import Client + from lndgrpc import LNDClient # Copy admin macaroon and tls cert to local machine - try: - tls_file = os.path.join(config.lnd_dir, "tls.cert") - macaroon_file = os.path.join(config.lnd_dir, "data/chain/bitcoin/mainnet/admin.macaroon") - subprocess.run(["scp", "{}:{}".format(config.tunnel_host, tls_file), "."]) - subprocess.run(["scp", "-r", "{}:{}".format(config.tunnel_host, macaroon_file), "."]) - - except Exception as e: - print(e) - print("Failed to copy tls and macaroon files to local machine.") + if (not os.path.isfile("tls.cert")) or (not os.path.isfile("admin.macaroon")): + print("Could not find tls.cert or admin.macaroon in BTCPyment folder. Attempting to download from lnd directory.") + try: + tls_file = os.path.join(config.lnd_dir, "tls.cert") + macaroon_file = os.path.join(config.lnd_dir, "data/chain/bitcoin/mainnet/admin.macaroon") + subprocess.run(["scp", "{}:{}".format(config.tunnel_host, tls_file), "."]) + subprocess.run(["scp", "-r", "{}:{}".format(config.tunnel_host, macaroon_file), "."]) + except Exception as e: + print(e) + print("Failed to copy tls and macaroon files to local machine.") + else: + print("Found tls.cert and admin.macaroon.") connection_str = "{}:{}".format(config.host, config.rpcport) - print("Attempting to connect to {}. This may take a few minutes...".format(connection_str)) + print("Attempting to connect to lightning node {}. This may take a few minutes...".format(connection_str)) for i in range(config.connection_attempts): try: # Require admin=True for creating invoices print("Attempting to initialise lnd rpc client...") - self.lnd = Client(grpc_host=config.host, - grpc_port=config.rpcport, - macaroon_path="admin.macaroon", - tls_cert_path="tls.cert") + self.lnd = LNDClient("{}:{}".format(config.host, config.rpcport), + macaroon_filepath="admin.macaroon", + cert_filepath="tls.cert") - # print("Getting lnd info...") - # info = self.lnd.get_info() - # print(info) + print("Getting lnd info...") + info = self.lnd.get_info() + print(info) print("Successfully contacted lnd.") break @@ -52,57 +54,36 @@ class lnd(invoice): else: raise Exception("Could not connect to lnd. Check your gRPC / port tunneling settings and try again.") + print("Ready for payments requests.") def create_lnd_invoice(self, btc_amount): # Multiplying by 10^8 to convert to satoshi units sats_amount = int(btc_amount*10**8) res = self.lnd.add_invoice(value=sats_amount) self.lnd_invoice = json.loads(MessageToJson(res)) - self.hash = res.r_hash #self.lnd_invoice['r_hash'] - print("Create invoice response:") - print(res) - return self.lnd_invoice['payment_request'] + self.hash = self.lnd_invoice['r_hash'] + + print("Created lightning invoice:") + print(self.lnd_invoice) - # self.lnd_invoice = json.loads(MessageToJson(self.lnd.add_invoice(sats_amount))) - # print(self.lnd_invoice) - # print("printed") - # - # self.hash = str(b64decode(self.lnd_invoice['r_hash']).hex()) - # # self.hash = str(b64decode(self.lnd_invoice['rHash']).hex()) - # - # print("Created invoice: {}".format(self.hash)) - # return self.lnd_invoice['payment_request'] - # return self.lnd_invoice['paymentRequest'] + return self.lnd_invoice['payment_request'] def get_address(self): self.address = self.create_lnd_invoice(self.value) - - # for i in range(config.connection_attempts): - # try: - # self.address = self.create_lnd_invoice(self.value) - # except Exception as e: - # print(e) - # print("Attempting again... {}/{}...".format(i+1, config.connection_attempts)) - return def check_payment(self): print("Looking up invoice") - # For some reason this does not work, I think lookup_invoice() may be broken - # as it does not return the correct response that includes the amount paid among other fields. - print(self.lnd.list_invoices()) - print(self.lnd.lookup_invoice(r_hash=self.hash)) - print("Invoice ^") - print(type(self.hash)) - print(str(self.hash.hex())) - - # print(str(b64decode(self.hash.strip('\\')))) - # invoice_status = json.loads(MessageToJson(self.lnd.lookup_invoice(self.hash))) - # print(invoice_status) - # print(self.lnd.lookup_invoice("8893044a07c2c5e2a50252f044224f297487242e05758a970d5ba28ece75f66d")) - # subprocess.run([""]) - - conf_paid = 0 #invoice_status['amt_paid'] - unconf_paid = 0 + + invoice_status = json.loads(MessageToJson(self.lnd.lookup_invoice(r_hash_str=b64decode(self.hash).hex()))) + + if 'amt_paid_sat' not in invoice_status.keys(): + conf_paid = 0 + unconf_paid = 0 + else: + # Store amount paid and convert to BTC units + conf_paid = int(invoice_status['amt_paid_sat']) * 10**8 + unconf_paid = 0 + return conf_paid, unconf_paid diff --git a/requirements.txt b/requirements.txt @@ -7,3 +7,4 @@ requests==2.25.0 gunicorn==20.0.4 eventlet==0.30.0 Pillow==8.0.1 +lndgrpc==0.2.0 diff --git a/server.py b/server.py @@ -16,11 +16,11 @@ async_mode = None app = Flask(__name__) # Load API key -if os.path.exists("BTCPyment_API"): - with open("BTCPyment.key", 'r') as f: +if os.path.exists("BTCPyment_API_key"): + with open("BTCPyment_API_key", 'r') as f: app.config['SECRET_KEY'] = f.read() else: - with open("BTCPyment.key", 'w') as f: + with open("BTCPyment_API_key", 'w') as f: app.config['SECRET_KEY'] = os.urandom(64).hex() f.write(app.config['SECRET_KEY']) @@ -86,7 +86,7 @@ def make_payment(payload): payment.response = 'Payment finalised. Thankyou!' update_status(payment) - # Call webhook if woocommerce + # Call webhook if woocommerce webhook url has been provided. if 'w_url' in payload.keys(): response = woo_webhook.hook(app.config['SECRET_KEY'], payload) diff --git a/ssh_tunnel.py b/ssh_tunnel.py @@ -14,7 +14,8 @@ try: tunnel_proc = subprocess.Popen(command) else: tunnel_proc = None -except Exception: +except Exception as e: + print("FAILED TO OPEN TUNNEL. Exception: {}".format(e)) tunnel_proc = None pass