commit 80ef59928c071fd2bf0d8d32519c94f3f7b9c9a8
parent 53e18a39b25da49c4fc1937733f9a730befcfbb2
Author: NicholasFarrow <nicholas.w.farrow@gmail.com>
Date: Fri, 22 Jan 2021 22:21:33 +1100
Merge branch 'master' of github.com:nickfarrow/BTCPyment
Diffstat:
7 files changed, 61 insertions(+), 72 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,22 +51,23 @@ 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>
```
Changing `YOUR_SERVER_IP` to the IP address of the machine you're running BTCPyment through. Optionally, you can redirect a domain to that IP and use that instead.
-### Using HTTPS & Domains
-Embedded iframes are easy if your site only uses HTTP. But if your site uses HTTPS, then you can see your donation button at `http://YOUR_SERVER_IP:8000/` but will not be able to in an embedded iframe. See [HTTPS instructions](docs/HTTPS.md).
-
-## Payment Gateway (Woocommerce)
-Currently we have a plugin for Woocommerce in Wordpress, [please click here for installation instructions](docs/woocommerce.md) (another easy install!). BTCPyment acts as a custom payment gateway for Woocommerce via the php plugin found in `/gateways`. We have plans to extend to other web stores in the future.
## Security
For maximum security, we recommend hosting on a machine where your node only has access to a **watch-only** wallet.
+## Using HTTPS & Domains
+Embedded iframes are easy if your site only uses HTTP. But if your site uses HTTPS, then you can see your donation button at `http://YOUR_SERVER_IP:8000/` but will not be able to in an embedded iframe. See [HTTPS instructions](docs/HTTPS.md).
+
+# Payment Gateway (Woocommerce)
+Currently we have a plugin for Woocommerce in Wordpress, [please click here for installation instructions](docs/woocommerce.md) (another easy install!). BTCPyment acts as a custom payment gateway for Woocommerce via the php plugin found in `/gateways`. We have plans to extend to other web stores in the future.
+
# Developers
### You only need a little python!
The main code can be found in [server.py](server.py). The client-side logic for the donation button sits in [static/server_connection.js](static/server_connection.js), invoice structure and bitcoind interface in [invoice/](invoice/), button appearance in [template/index.html](template/index.html), and Woocommerce plugin in [gateways/woo_btcpyment.php](gateways/woo_btcpyment.php). Please have ago at implementing some of the things below!
@@ -74,8 +75,6 @@ The main code can be found in [server.py](server.py). The client-side logic for
More documentation will be added in the near future.
# Coming soon:
-* Payment API to process payments from any desired point of sale or web shop (woocommerce, shopify)
-* Lightning support (almost ready!)
* **Better UI** with more variety of size and theme.
* Handle unconfirmed payments. RBF?
* More readily customisable donation button (text/color/QR code)
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, payment)
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