SatSale

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

commit 1be9494e18e25afe05871c1716816454e2869c37
parent 56521ed08941cbcbc59180eff4148cf765ba5c59
Author: Kristaps Kaupe <kristaps@blogiem.lv>
Date:   Thu, 14 Jul 2022 10:12:58 +0300

Multiple currency support

Co-authored-by: Eddie Pease <eddiepease@tutanota.com>

Diffstat:
Mconfig.py | 17++++++++++++++---
Mconfig.toml | 5+++--
Mpayments/database.py | 11+++++++++--
Mpayments/price_feed.py | 5+++++
Msatsale.py | 16+++++++++++-----
Mstatic/satsale.js | 2+-
Mstatic/style.css | 3++-
Mtemplates/donate.html | 7++++++-
8 files changed, 51 insertions(+), 15 deletions(-)

diff --git a/config.py b/config.py @@ -79,10 +79,23 @@ for method_name in config["payment_methods"]: raise KeyError("Mising {}: config {}".format(method_name, "xpub")) else: - Exception("Unknown payment method: {}".format(method_name)) + raise Exception("Unknown payment method: {}".format(method_name)) payment_methods.append(method_config) +supported_currencies = get_opt("supported_currencies", ["USD"]) +if "BTC" in supported_currencies: + supported_currencies.append("sats") + +base_currency = get_opt("base_currency", "USD") +if base_currency not in supported_currencies: + raise Exception("base_currency must be one of supported_currencies") + +currency_provider = get_opt("currency_provider", "COINGECKO") +if currency_provider not in ["COINDESK","COINGECKO"]: + raise Exception("Unsupported currency price feed provider: {}".format( + currency_provider)) + host = get_opt("host", "127.0.0.1") api_key_path = get_opt("api_key_path", "SatSale_API_key") tunnel_host = get_opt("tunnel_host", None) @@ -95,8 +108,6 @@ payment_timeout = get_opt("payment_timeout", 60 * 60) required_confirmations = get_opt("required_confirmations", 2) connection_attempts = get_opt("connection_attempts", 3) redirect = get_opt("redirect", "https://github.com/nickfarrow/satsale") -base_currency = get_opt("base_currency", "USD") -currency_provider = get_opt("currency_provider", "COINGECKO") bitcoin_rate_multiplier = get_opt("bitcoin_rate_multiplier", 1.00) allowed_underpay_amount = get_opt("allowed_underpay_amount", 0.00000001) liquid_address = get_opt("liquid_address", None) diff --git a/config.toml b/config.toml @@ -91,8 +91,9 @@ loglevel = "DEBUG" redirect = "https://github.com/nickfarrow/satsale" # Currency and exchange rate provider -base_currency = "USD" -currency_provider = "COINGECKO" # Supported: COINDESK | COINGECKO +supported_currencies = ["BTC", "USD"] # Currencies to display on donation dropdown +base_currency = "USD" # Selection default for that dropdown +currency_provider = "COINGECKO" # Supported: COINDESK | COINGECKO # Multiplier added on top of BTC / fiat exchange rate. Allows to give discount or add extra commission. # Values above 1.00 gives discount, values below add extra comission. diff --git a/payments/database.py b/payments/database.py @@ -49,7 +49,13 @@ def migrate_database(name="database.db"): conn.execute("CREATE TABLE addresses (n INTEGER, address TEXT, xpub TEXT)") _set_database_schema_version(2) - #if schema_version < 2: + if schema_version < 3: + _log_migrate_database(2, 3, "Adding fiat currency column to payments table") + with sqlite3.connect(name) as conn: + conn.execute("ALTER TABLE payments ADD fiat_currency TEXT") + _set_database_schema_version(3) + + #if schema_version < 4: # do next migration new_version = _get_database_schema_version(name) @@ -65,9 +71,10 @@ def write_to_database(invoice, name="database.db"): with sqlite3.connect(name) as conn: cur = conn.cursor() cur.execute( - "INSERT INTO payments (uuid,fiat_value,btc_value,method,address,time,webhook,rhash) VALUES (?,?,?,?,?,?,?,?)", + "INSERT INTO payments (uuid,fiat_currency,fiat_value,btc_value,method,address,time,webhook,rhash) VALUES (?,?,?,?,?,?,?,?,?)", ( invoice["uuid"], + invoice["fiat_currency"], invoice["fiat_value"], invoice["btc_value"], invoice["method"], diff --git a/payments/price_feed.py b/payments/price_feed.py @@ -56,6 +56,11 @@ def get_price(currency, currency_provider=config.currency_provider, bitcoin_rate def get_btc_value(base_amount, currency): + if currency == "BTC": + return float(base_amount) + elif currency == "sats": + return float(base_amount) / 10**8 + price = get_price(currency) if price is not None: diff --git a/satsale.py b/satsale.py @@ -57,13 +57,14 @@ database.migrate_database() @app.route("/") def index(): params = dict(request.args) - params["currency"] = config.base_currency + params["supported_currencies"] = config.supported_currencies + params["base_currency"] = config.base_currency params["node_info"] = config.node_info headers = {"Content-Type": "text/html"} return make_response(render_template("donate.html", params=params), 200, headers) -# /pay is the main page for initiating a payment, takes a GET request with ?amount= +# /pay is the main page for initiating a payment, takes a GET request with ?amount=[x]&currency=[x] @app.route("/pay") def pay(): params = dict(request.args) @@ -92,6 +93,7 @@ invoice_model = api.model( "invoice", { "uuid": fields.String(), + "fiat_currency": fields.String(), "fiat_value": fields.Float(), "btc_value": fields.Float(), "method": fields.String(), @@ -115,7 +117,8 @@ status_model = api.model( @api.doc( params={ - "amount": "An amount in `config.base_currency`.", + "amount": "An amount.", + "currency": "(Opional) Currency units of the amount (defaults to `config.base_currency`).", "method": "(Optional) Specify a payment method: `bitcoind` for onchain, `lnd` for lightning).", "w_url": "(Optional) Specify a webhook url to call after successful payment. Currently only supports WooCommerce plugin.", } @@ -127,9 +130,11 @@ class create_payment(Resource): @api.response(522, "Error fetching address from node") def get(self): "Create Payment" - """Initiate a new payment with an `amount` in `config.base_currecy`.""" + """Initiate a new payment with an `amount` in specified currency.""" base_amount = request.args.get("amount") - currency = config.base_currency + currency = request.args.get("currency") + if currency is None: + currency = config.base_currency payment_method = request.args.get("method") if payment_method is None: payment_method = enabled_payment_methods[0] @@ -160,6 +165,7 @@ class create_payment(Resource): invoice = { "uuid": str(uuid.uuid4().hex), + "fiat_currency": currency, "fiat_value": base_amount, "btc_value": btc_amount_format(btc_value), "method": payment_method, diff --git a/static/satsale.js b/static/satsale.js @@ -2,7 +2,7 @@ function payment(payment_data) { $('document').ready(function(){ var payment_uuid; - var invoiceData = {amount: payment_data.amount, method: payment_data.method}; + var invoiceData = {amount: payment_data.amount, currency: payment_data.currency, method: payment_data.method}; // If a webhook URL is provided (woocommerce) if (payment_data.w_url) { diff --git a/static/style.css b/static/style.css @@ -101,11 +101,12 @@ h1 { transform: scale(3); } -#amountenter { +.donate_input { background-color: black; color: white; font-size: 16px; border-radius:5px; + display: inline; } #qrImage { diff --git a/templates/donate.html b/templates/donate.html @@ -47,7 +47,12 @@ <form id="pay" action='/pay'> <div style="display:block;text-align: center;"> <h2 style="margin:0;">Amount: - <input id="amountenter" style="display:inline" size="4" type="float" name="amount" id="amount" placeholder="{{ params.currency }}" required> + <input class="donate_input" size="4" type="float" name="amount" placeholder="0" required> + <select class="donate_input" name="currency" required> + {% for currency_option in params.supported_currencies %} + <option value="{{currency_option}}"{% if currency_option == params.base_currency %} selected{% endif %}>{{currency_option}}</option> + {% endfor %} + </select> </h2> <br> <input class="button button1" style="width:40%" type="submit" value="Donate">