diff --git a/README.md b/README.md index 3fa0cca..4e9747c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Interested in learning more? How about a trip to the the `docs/` folder: - [With docker-compose](./docs/local-set-up.md#docker_compose) - [**Configuring `capsul-flask`**](./docs/configuration.md) - [Example configuration from capsul.org (production)](./docs/configuration.md#example) + - [Configuring Currency](./docs/configuration.md#currency) - [Configuration-type-stuff that lives in the database ](./docs/configuration.md#config_that_lives_in_db) - [Loading variables from files (docker secrets)](./docs/configuration.md#docker_secrets) - [**`capsul-flask`'s relationship to its Database Server**](./docs/database.md) diff --git a/capsulflask/__init__.py b/capsulflask/__init__.py index a5eb5ce..0ac0aac 100644 --- a/capsulflask/__init__.py +++ b/capsulflask/__init__.py @@ -76,7 +76,13 @@ app.config.from_mapping( #STRIPE_WEBHOOK_SECRET=os.environ.get("STRIPE_WEBHOOK_SECRET", default="") BTCPAY_PRIVATE_KEY=os.environ.get("BTCPAY_PRIVATE_KEY", default="").replace("\\n", "\n"), - BTCPAY_URL=os.environ.get("BTCPAY_URL", default="") + BTCPAY_URL=os.environ.get("BTCPAY_URL", default=""), + + CURRENCY_NAME=os.environ.get("CURRENCY_NAME", default="dollar"), # display name (singular) + CURRENCY_SYMBOL=os.environ.get("CURRENCY_SYMBOL", default="$"), # display prefix symbol + CURRENCY_SWIPE_NAME=os.environ.get("CURRENCY_SWIPE_NAME", default="usd"), # identifier swipe expects in swipe.checkout.session.create + CURRENCY_BTCPAY_NAME=os.environ.get("CURRENCY_BTCPAY_NAME", default="USD"), # identifier btcpay expects in BTCPAY_CLIENT.create_invoice + CURRENCY_SWIPE_CONVERSION_FACTOR=os.environ.get("CURRENCY_SWIPE_CONVERSION_FACTOR", default=100) # swipe wants to work in the smallest unit - pennies, cents, etc ) app.config['HUB_URL'] = os.environ.get("HUB_URL", default=app.config['BASE_URL']) diff --git a/capsulflask/cli.py b/capsulflask/cli.py index 78ce59c..3508aae 100644 --- a/capsulflask/cli.py +++ b/capsulflask/cli.py @@ -121,7 +121,7 @@ def clean_up_unresolved_btcpay_invoices(): if btcpay_invoice['status'] == "complete": current_app.logger.info( f"resolving btcpay invoice {invoice_id} " - f"({unresolved_invoice['email']}, ${unresolved_invoice['dollars']}) as completed " + f"({unresolved_invoice['email']}, {config['CURRENCY_SYMBOL']}{unresolved_invoice['amount']}) as completed " ) resolved_invoice_email = get_model().btcpay_invoice_resolved(invoice_id, True) @@ -131,13 +131,13 @@ def clean_up_unresolved_btcpay_invoices(): elif days >= 1: current_app.logger.info( f"resolving btcpay invoice {invoice_id} " - f"({unresolved_invoice['email']}, ${unresolved_invoice['dollars']}) as invalidated, " + f"({unresolved_invoice['email']}, {config['CURRENCY_SYMBOL']}{unresolved_invoice['amount']}) as invalidated, " f"btcpay server invoice status: {btcpay_invoice['status']}" ) get_model().btcpay_invoice_resolved(invoice_id, False) get_model().delete_payment_session("btcpay", invoice_id) -delete_at_account_balance_dollars = -10 +delete_at_account_balance_amount = -10 def get_warning_headline(warning_id, pluralize_capsul): return dict( @@ -150,23 +150,23 @@ def get_warning_headline(warning_id, pluralize_capsul): zero_now= ( f"You have run out of funds! You will no longer be able to create Capsuls.\n\n" f"As a courtesy, we'll let your existing Capsul{pluralize_capsul} keep running until your account " - "reaches a -$10 balance, at which point they will be deleted.\n\n" + "reaches a -{config['CURRENCY_SYMBOL']}10 balance, at which point they will be deleted.\n\n" ), delete_1w= ( "You have run out of funds and have not refilled your account.\n\n" f"As a courtesy, we've let your existing Capsul{pluralize_capsul} keep running. " - f"However, your account will reach a -$10 balance some time next week and your Capsul{pluralize_capsul} " + f"However, your account will reach a -{config['CURRENCY_SYMBOL']}10 balance some time next week and your Capsul{pluralize_capsul} " "will be deleted.\n\n" ), delete_1d= ( "You have run out of funds and have not refilled your account.\n\n" f"As a courtesy, we have let your existing Capsul{pluralize_capsul} keep running. " - f"However, your account will reach a -$10 balance by this time tomorrow and " + f"However, your account will reach a -{config['CURRENCY_SYMBOL']}10 balance by this time tomorrow and " f"your Capsul{pluralize_capsul} will be deleted.\n\n" f"Last chance to deposit funds now and keep your Capsul{pluralize_capsul} running! " ), delete_now= ( - f"Your account reached a -$10 balance and your Capsul{pluralize_capsul} were deleted." + f"Your account reached a -{config['CURRENCY_SYMBOL']}10 balance and your Capsul{pluralize_capsul} were deleted." ) )[warning_id] @@ -205,7 +205,7 @@ def get_warnings_list(): ), dict( id='delete_1w', - get_active=lambda balance_1w, balance_1d, balance_now: balance_1w < delete_at_account_balance_dollars, + get_active=lambda balance_1w, balance_1d, balance_now: balance_1w < delete_at_account_balance_amount, get_subject=lambda pluralize_capsul: f"Your Capsul{pluralize_capsul} Will be Deleted In Less Than a Week", get_body=lambda base_url, pluralize_capsul: ( f"{get_warning_headline('delete_1w', pluralize_capsul)}" @@ -216,7 +216,7 @@ def get_warnings_list(): ), dict( id='delete_1d', - get_active=lambda balance_1w, balance_1d, balance_now: balance_1d < delete_at_account_balance_dollars, + get_active=lambda balance_1w, balance_1d, balance_now: balance_1d < delete_at_account_balance_amount, get_subject=lambda pluralize_capsul: f"Last Chance to Save your Capsul{pluralize_capsul}: Gone Tomorrow", get_body=lambda base_url, pluralize_capsul: ( f"{get_warning_headline('delete_1d', pluralize_capsul)}" @@ -225,7 +225,7 @@ def get_warnings_list(): ), dict( id='delete_now', - get_active=lambda balance_1w, balance_1d, balance_now: balance_now < delete_at_account_balance_dollars, + get_active=lambda balance_1w, balance_1d, balance_now: balance_now < delete_at_account_balance_amount, get_subject=lambda pluralize_capsul: f"Capsul{pluralize_capsul} Deleted", get_body=lambda base_url, pluralize_capsul: ( f"{get_warning_headline('delete_now', pluralize_capsul)}" diff --git a/capsulflask/console.py b/capsulflask/console.py index 70b34b5..e77d101 100644 --- a/capsulflask/console.py +++ b/capsulflask/console.py @@ -215,12 +215,12 @@ def create(): # if a user deposits $7.50 and then creates an f1-s vm which costs 7.50 a month, # then they have to delete the vm and re-create it, they will not be able to, they will have to pay again. # so for UX it makes a lot of sense to give a small margin of 25 cents for usability sake - if vm_size["dollars_per_month"] <= account_balance_in_one_month+0.25: + if vm_size["amount_per_month"] <= account_balance_in_one_month+0.25: month_funded_vm_sizes[key] = vm_size one_month_in_hours = float(730.5) two_hours_as_fraction_of_month = float(2)/one_month_in_hours - if float(vm_size["dollars_per_month"])*two_hours_as_fraction_of_month <= account_balance: + if float(vm_size["amount_per_month"])*two_hours_as_fraction_of_month <= account_balance: hour_funded_vm_sizes[key] = vm_size if request.method == "POST": @@ -414,11 +414,11 @@ def account_balance(): vm_months = get_vm_months_float(vm, datetime.utcnow()) vms_billed.append(dict( id=vm["id"], - dollars_per_month=vm["dollars_per_month"], + amount_per_month=vm["amount_per_month"], created=vm["created"].strftime("%b %d %Y"), deleted=vm["deleted"].strftime("%b %d %Y") if vm["deleted"] else "N/A", months=format(vm_months, '.3f'), - dollars=format(vm_months * float(vm["dollars_per_month"]), '.2f') + amount=format(vm_months * float(vm["amount_per_month"]), '.2f') )) return render_template( @@ -430,7 +430,7 @@ def account_balance(): btcpay_is_working="BTCPAY_CLIENT" in current_app.config, payments=list(map( lambda x: dict( - dollars=x["dollars"], + amount=x["amount"], class_name="invalidated" if x["invalidated"] else "", created=x["created"].strftime("%b %d %Y") ), diff --git a/capsulflask/db.py b/capsulflask/db.py index fd0e814..72b3930 100644 --- a/capsulflask/db.py +++ b/capsulflask/db.py @@ -50,7 +50,7 @@ def init_app(app, is_running_server): hasSchemaVersionTable = False actionWasTaken = False schemaVersion = 0 - desiredSchemaVersion = 24 + desiredSchemaVersion = 25 cursor = connection.cursor() diff --git a/capsulflask/db_model.py b/capsulflask/db_model.py index 012c3f7..388baf9 100644 --- a/capsulflask/db_model.py +++ b/capsulflask/db_model.py @@ -106,11 +106,11 @@ class DBModel: return operatingSystems def vm_sizes_dict(self): - self.cursor.execute("SELECT id, dollars_per_month, vcpus, memory_mb, bandwidth_gb_per_month FROM vm_sizes") + self.cursor.execute("SELECT id, amount_per_month, vcpus, memory_mb, bandwidth_gb_per_month FROM vm_sizes") vmSizes = dict() for row in self.cursor.fetchall(): - vmSizes[row[0]] = dict(dollars_per_month=row[1], vcpus=row[2], memory_mb=row[3], bandwidth_gb_per_month=row[4]) + vmSizes[row[0]] = dict(amount_per_month=row[1], vcpus=row[2], memory_mb=row[3], bandwidth_gb_per_month=row[4]) return vmSizes @@ -140,13 +140,13 @@ class DBModel: def list_vms_for_account(self, email): self.cursor.execute(""" - SELECT vms.id, vms.public_ipv4, vms.public_ipv6, vms.size, vms.shortterm, vms.os, vms.created, vms.deleted, vm_sizes.dollars_per_month + SELECT vms.id, vms.public_ipv4, vms.public_ipv6, vms.size, vms.shortterm, vms.os, vms.created, vms.deleted, vm_sizes.amount_per_month FROM vms JOIN vm_sizes on vms.size = vm_sizes.id WHERE vms.email = %s""", (email, ) ) return list(map( - lambda x: dict(id=x[0], ipv4=x[1], ipv6=x[2], size=x[3], shortterm=x[4], os=x[5], created=x[6], deleted=x[7], dollars_per_month=x[8]), + lambda x: dict(id=x[0], ipv4=x[1], ipv6=x[2], size=x[3], shortterm=x[4], os=x[5], created=x[6], deleted=x[7], amount_per_month=x[8]), self.cursor.fetchall() )) @@ -188,7 +188,7 @@ class DBModel: def get_vm_detail(self, email, id): self.cursor.execute(""" SELECT vms.id, vms.public_ipv4, vms.public_ipv6, os_images.description, vms.created, vms.deleted, - vm_sizes.id, vms.shortterm, vm_sizes.dollars_per_month, vm_sizes.vcpus, vm_sizes.memory_mb, vm_sizes.bandwidth_gb_per_month + vm_sizes.id, vms.shortterm, vm_sizes.amount_per_month, vm_sizes.vcpus, vm_sizes.memory_mb, vm_sizes.bandwidth_gb_per_month FROM vms JOIN os_images on vms.os = os_images.id JOIN vm_sizes on vms.size = vm_sizes.id @@ -201,7 +201,7 @@ class DBModel: vm = dict( id=row[0], ipv4=row[1], ipv6=row[2], os_description=row[3], created=row[4], deleted=row[5], - size=row[6], shortterm=row[7], dollars_per_month=row[8], vcpus=row[9], memory_mb=row[10], + size=row[6], shortterm=row[7], amount_per_month=row[8], vcpus=row[9], memory_mb=row[10], bandwidth_gb_per_month=row[11], ) @@ -232,32 +232,32 @@ class DBModel: def list_payments_for_account(self, email): self.cursor.execute(""" - SELECT id, dollars, invalidated, created + SELECT id, amount, invalidated, created FROM payments WHERE payments.email = %s""", (email, ) ) return list(map( - lambda x: dict(id=x[0], dollars=x[1], invalidated=x[2], created=x[3]), + lambda x: dict(id=x[0], amount=x[1], invalidated=x[2], created=x[3]), self.cursor.fetchall() )) - def create_payment_session(self, payment_type, id, email, dollars): + def create_payment_session(self, payment_type, id, email, amount): self.cursor.execute(""" - INSERT INTO payment_sessions (id, type, email, dollars) + INSERT INTO payment_sessions (id, type, email, amount) VALUES (%s, %s, %s, %s) """, - (id, payment_type, email, dollars) + (id, payment_type, email, amount) ) self.connection.commit() def list_payment_sessions_for_account(self, email): self.cursor.execute(""" - SELECT id, type, dollars, created + SELECT id, type, amount, created FROM payment_sessions WHERE email = %s""", (email, ) ) return list(map( - lambda x: dict(id=x[0], type=x[1], dollars=x[2], created=x[3]), + lambda x: dict(id=x[0], type=x[1], amount=x[2], created=x[3]), self.cursor.fetchall() )) @@ -276,21 +276,21 @@ class DBModel: return None - def consume_payment_session(self, payment_type, id, dollars): - self.cursor.execute("SELECT email, dollars FROM payment_sessions WHERE id = %s AND type = %s", (id, payment_type)) + def consume_payment_session(self, payment_type, id, amount): + self.cursor.execute("SELECT email, amount FROM payment_sessions WHERE id = %s AND type = %s", (id, payment_type)) row = self.cursor.fetchone() if row: - if int(row[1]) != int(dollars): + if int(row[1]) != int(amount): current_app.logger.warning(f""" - {payment_type} gave us a completed payment session with a different dollar amount than what we had recorded!! + {payment_type} gave us a completed payment session with a different {current_app.config['CURRENCY_NAME']} amount than what we had recorded!! id: {id} account: {row[0]} Recorded amount: {int(row[1])} - {payment_type} sent: {int(dollars)} + {payment_type} sent: {int(amount)} """) # not sure what to do here. For now just log and do nothing. self.cursor.execute( "DELETE FROM payment_sessions WHERE id = %s AND type = %s", (id, payment_type) ) - self.cursor.execute( "INSERT INTO payments (email, dollars) VALUES (%s, %s) RETURNING id", (row[0], row[1]) ) + self.cursor.execute( "INSERT INTO payments (email, amount) VALUES (%s, %s) RETURNING id", (row[0], row[1]) ) if payment_type == "btcpay": payment_id = self.cursor.fetchone()[0] @@ -326,10 +326,10 @@ class DBModel: def get_unresolved_btcpay_invoices(self): self.cursor.execute(""" - SELECT unresolved_btcpay_invoices.id, payments.created, payments.dollars, unresolved_btcpay_invoices.email + SELECT unresolved_btcpay_invoices.id, payments.created, payments.amount, unresolved_btcpay_invoices.email FROM unresolved_btcpay_invoices JOIN payments on payment_id = payments.id """) - return list(map(lambda row: dict(id=row[0], created=row[1], dollars=row[2], email=row[3]), self.cursor.fetchall())) + return list(map(lambda row: dict(id=row[0], created=row[1], amount=row[2], email=row[3]), self.cursor.fetchall())) def get_account_balance_warning(self, email): self.cursor.execute("SELECT account_balance_warning FROM accounts WHERE email = %s", (email,)) diff --git a/capsulflask/payment.py b/capsulflask/payment.py index 1e2d2fc..74cda00 100644 --- a/capsulflask/payment.py +++ b/capsulflask/payment.py @@ -28,27 +28,27 @@ from capsulflask.shared import my_exec_info_message, get_account_balance, averag bp = Blueprint("payment", __name__, url_prefix="/payment") -def validate_dollars(min: float, max: float): +def validate_amount(min: float, max: float): errors = list() - dollars = None - if "dollars" not in request.form: - errors.append("dollars is required") + amount = None + if "amount" not in request.form: + errors.append("amount is required") else: - dollars = None + amount = None try: - dollars = float(request.form["dollars"]) + amount = float(request.form["amount"]) except: - errors.append("dollars must be a number") + errors.append("amount must be a number") - #current_app.logger.info(f"{str(dollars)} {str(min)} {str(dollars < min)}") + #current_app.logger.info(f"{str(amount)} {str(min)} {str(amount < min)}") - if dollars is not None and dollars < min: - errors.append(f"dollars must be {format(min, '.2f')} or more") - elif dollars is not None and dollars >= max: - errors.append(f"dollars must be less than {format(max, '.2f')}") + if amount is not None and amount < min: + errors.append(f"amount must be {format(min, '.2f')} or more") + elif amount is not None and amount >= max: + errors.append(f"amount must be less than {format(max, '.2f')}") current_app.logger.info(f"{len(errors)} {errors}") - return (dollars, errors) + return (amount, errors) @bp.route("/btcpay", methods=("GET", "POST")) @account_required @@ -64,14 +64,14 @@ def btcpay_payment(): return redirect(url_for("console.account_balance")) if request.method == "POST": - dollars, errors = validate_dollars(0.01, 1000.0) + amount, errors = validate_amount(0.01, 1000.0) #current_app.logger.info(f"{len(errors)} {errors}") if len(errors) == 0: try: invoice = current_app.config['BTCPAY_CLIENT'].create_invoice(dict( - price=float(dollars), - currency="USD", + price=float(amount), + currency=current_app.config['CURRENCY_BTCPAY_NAME'], itemDesc="Capsul Cloud Compute", transactionSpeed="high", redirectURL=f"{current_app.config['BASE_URL']}/console/account-balance", @@ -86,9 +86,9 @@ def btcpay_payment(): # print(invoice) - current_app.logger.info(f"created btcpay invoice_id={invoice['id']} ( {session['account']}, ${dollars} )") + current_app.logger.info(f"created btcpay invoice_id={invoice['id']} ( {session['account']}, {current_app.config['CURRENCY_SYMBOL']}{amount} )") - get_model().create_payment_session("btcpay", invoice["id"], session["account"], dollars) + get_model().create_payment_session("btcpay", invoice["id"], session["account"], amount) return redirect(invoice["url"]) @@ -115,18 +115,18 @@ def poll_btcpay_session(invoice_id): return [503, "error was thrown when contacting btcpay server"] - if invoice['currency'] != "USD": + if invoice['currency'] != current_app.config['CURRENCY_BTCPAY_NAME']: return [400, "invalid currency"] - dollars = invoice['price'] + amount = invoice['price'] - current_app.logger.info(f"poll_btcpay_session invoice_id={invoice_id}, status={invoice['status']} dollars={dollars}") + current_app.logger.info(f"poll_btcpay_session invoice_id={invoice_id}, status={invoice['status']} amount={amount}") if invoice['status'] == "paid" or invoice['status'] == "confirmed" or invoice['status'] == "complete": - success_account = get_model().consume_payment_session("btcpay", invoice_id, dollars) + success_account = get_model().consume_payment_session("btcpay", invoice_id, amount) if success_account: - current_app.logger.info(f"{success_account} paid ${dollars} successfully (btcpay_invoice_id={invoice_id})") + current_app.logger.info(f"{success_account} paid {current_app.config['CURRENCY_SYMBOL']}{amount} successfully (btcpay_invoice_id={invoice_id})") if invoice['status'] == "complete": resolved_invoice_email = get_model().btcpay_invoice_resolved(invoice_id, True) @@ -188,11 +188,11 @@ def stripe_payment(): errors = list() if request.method == "POST": - dollars, errors = validate_dollars(0.5, 1000000) + amount, errors = validate_amount(0.5, 1000000) if len(errors) == 0: - current_app.logger.info(f"creating stripe checkout session for {session['account']}, ${dollars}") + current_app.logger.info(f"creating stripe checkout session for {session['account']}, {current_app.config['CURRENCY_SYMBOL']}{amount}") checkout_session = stripe.checkout.Session.create( success_url=current_app.config['BASE_URL'] + "/payment/stripe/success?session_id={CHECKOUT_SESSION_ID}", @@ -204,16 +204,16 @@ def stripe_payment(): "name": "Capsul Cloud Compute", "images": [current_app.config['BASE_URL']+"/static/capsul-product-image.png"], "quantity": 1, - "currency": "usd", - "amount": int(dollars*100) + "currency": current_app.config['CURRENCY_SWIPE_NAME'], + "amount": int(amount*100) } ] ) stripe_checkout_session_id = checkout_session['id'] - current_app.logger.info(f"stripe_checkout_session_id={stripe_checkout_session_id} ( {session['account']}, ${dollars} )") + current_app.logger.info(f"stripe_checkout_session_id={stripe_checkout_session_id} ( {session['account']}, {current_app.config['CURRENCY_SYMBOL']}{amount} )") - get_model().create_payment_session("stripe", stripe_checkout_session_id, session["account"], dollars) + get_model().create_payment_session("stripe", stripe_checkout_session_id, session["account"], amount) # We can't do this because stripe requires a bunch of server-authenticated data to be sent in the hash # of the URL. I briefly looked into reverse-engineering their proprietary javascript in order to try to figure out @@ -280,17 +280,17 @@ def validate_stripe_checkout_session(stripe_checkout_session_id): if checkout_session and 'id' in checkout_session and checkout_session['id'] == stripe_checkout_session_id: cents = checkout_session['amount_total'] - dollars = decimal.Decimal(cents)/100 + amount = decimal.Decimal(cents)/100 #consume_payment_session deletes the checkout session row and inserts a payment row # its ok to call consume_payment_session more than once because it only takes an action if the session exists - success_email = get_model().consume_payment_session("stripe", stripe_checkout_session_id, dollars) + success_email = get_model().consume_payment_session("stripe", stripe_checkout_session_id, amount) if success_email is not None: check_if_shortterm_flag_can_be_unset(success_email) if success_email: - return dict(email=success_email, dollars=dollars) + return dict(email=success_email, amount=amount) return None @@ -304,7 +304,7 @@ def success(): for _ in range(0, 5): paid = validate_stripe_checkout_session(stripe_checkout_session_id) if paid: - current_app.logger.info(f"{paid['email']} paid ${paid['dollars']} successfully (stripe_checkout_session_id={stripe_checkout_session_id})") + current_app.logger.info(f"{paid['email']} paid {current_app.config['CURRENCY_SYMBOL']}{paid['amount']} successfully (stripe_checkout_session_id={stripe_checkout_session_id})") return redirect(url_for("console.account_balance")) else: @@ -325,14 +325,14 @@ def success(): # secret=current_app.config['STRIPE_WEBHOOK_SECRET'] # ) # if event['type'] == 'checkout.session.completed': -# dollars = event['data']['object']['amount_total'] +# amount = event['data']['object']['amount_total'] # stripe_checkout_session_id = event['data']['object']['id'] # #consume_payment_session deletes the checkout session row and inserts a payment row # # its ok to call consume_payment_session more than once because it only takes an action if the session exists -# success_account = get_model().consume_payment_session("stripe", stripe_checkout_session_id, dollars) +# success_account = get_model().consume_payment_session("stripe", stripe_checkout_session_id, amount) # if success_account: -# print(f"{success_account} paid ${dollars} successfully (stripe_checkout_session_id={stripe_checkout_session_id})") +# print(f"{success_account} paid {current_app.config['CURRENCY_SYMBOL']}{amount} successfully (stripe_checkout_session_id={stripe_checkout_session_id})") # return jsonify({'status': 'success'}) # except ValueError as e: diff --git a/capsulflask/schema_migrations/25_down_generic_currency.sql b/capsulflask/schema_migrations/25_down_generic_currency.sql new file mode 100644 index 0000000..279e13b --- /dev/null +++ b/capsulflask/schema_migrations/25_down_generic_currency.sql @@ -0,0 +1,6 @@ + +ALTER TABLE vm_sizes RENAME amount_per_month TO dollars_per_month; +ALTER TABLE payments RENAME amount TO dollars; +ALTER TABLE payment_sessions RENAME amount TO dollars; + +UPDATE schemaversion SET version = 24; diff --git a/capsulflask/schema_migrations/25_up_generic_currency.sql b/capsulflask/schema_migrations/25_up_generic_currency.sql new file mode 100644 index 0000000..f57aee2 --- /dev/null +++ b/capsulflask/schema_migrations/25_up_generic_currency.sql @@ -0,0 +1,6 @@ + +ALTER TABLE vm_sizes RENAME dollars_per_month TO amount_per_month; +ALTER TABLE payments RENAME dollars TO amount; +ALTER TABLE payment_sessions RENAME dollars TO amount; + +UPDATE schemaversion SET version = 25; diff --git a/capsulflask/shared.py b/capsulflask/shared.py index 4e927e0..54722a7 100644 --- a/capsulflask/shared.py +++ b/capsulflask/shared.py @@ -62,14 +62,14 @@ def authorized_as_hub(headers): def get_account_balance(vms, payments, as_of): - vm_cost_dollars = 0.0 + vm_cost_amount = 0.0 for vm in vms: vm_months = get_vm_months_float(vm, as_of) - vm_cost_dollars += vm_months * float(vm["dollars_per_month"]) + vm_cost_amount += vm_months * float(vm["amount_per_month"]) - payment_dollars_total = float( sum(map(lambda x: 0 if x["invalidated"] else x["dollars"], payments)) ) + payment_amount_total = float( sum(map(lambda x: 0 if x["invalidated"] else x["amount"], payments)) ) - return payment_dollars_total - vm_cost_dollars + return payment_amount_total - vm_cost_amount average_number_of_days_in_a_month = 30.44 @@ -98,4 +98,4 @@ def my_exec_info_message(exec_info): if isinstance(traceback_result, list): traceback_result = "\n".join(traceback_result) - return f"{exec_info[0].__module__}.{exec_info[0].__name__}: {exec_info[1]}: {traceback_result}" \ No newline at end of file + return f"{exec_info[0].__module__}.{exec_info[0].__name__}: {exec_info[1]}: {traceback_result}" diff --git a/capsulflask/templates/account-balance.html b/capsulflask/templates/account-balance.html index 75ce760..d817f7c 100644 --- a/capsulflask/templates/account-balance.html +++ b/capsulflask/templates/account-balance.html @@ -4,7 +4,7 @@ {% block content %}
-

Account Balance: ${{ account_balance }}

+

Account Balance: {{config["CURRENCY_SYMBOL"]}}{{ account_balance }}

@@ -30,8 +30,8 @@ {% for payment in payments %} - ${{ payment["dollars"] }} - {{ payment["created"] }} + {{config["CURRENCY_SYMBOL"]}}{{ payment["amount"] }} + {{payment["created"]}} {% endfor %} @@ -81,9 +81,9 @@ id created deleted - $/month + {{config["CURRENCY_SYMBOL"]}}/month months - $ billed + {{config["CURRENCY_SYMBOL"]}} billed @@ -92,9 +92,9 @@ {{ vm["id"] }} {{ vm["created"] }} {{ vm["deleted"] }} - ${{ vm["dollars_per_month"] }} + {{config["CURRENCY_SYMBOL"]}}{{ vm["amount_per_month"] }} {{ vm["months"] }} - ${{ vm["dollars"] }} + {{config["CURRENCY_SYMBOL"]}}{{ vm["amount"] }} {% endfor %} diff --git a/capsulflask/templates/btcpay.html b/capsulflask/templates/btcpay.html index 5f7e14b..15feb66 100644 --- a/capsulflask/templates/btcpay.html +++ b/capsulflask/templates/btcpay.html @@ -13,10 +13,10 @@
- + diff --git a/capsulflask/templates/capsul-detail.html b/capsulflask/templates/capsul-detail.html index 5e94473..5cbb1b1 100644 --- a/capsulflask/templates/capsul-detail.html +++ b/capsulflask/templates/capsul-detail.html @@ -81,8 +81,8 @@ {% endif %}
- - ${{ vm['dollars_per_month'] }} + + {{config['CURRENCY_SYMBOL']}}{{ vm['amount_per_month'] }}
diff --git a/capsulflask/templates/create-capsul.html b/capsulflask/templates/create-capsul.html index 57be57a..eef863e 100644 --- a/capsulflask/templates/create-capsul.html +++ b/capsulflask/templates/create-capsul.html @@ -13,19 +13,19 @@ ============ type monthly* cpus mem ssd net* ----- ------- ---- --- --- --- - f1-xs $5.00 1 512M 25G .5TB - f1-s $7.50 1 1024M 25G 1TB - f1-m $12.50 1 2048M 25G 2TB - f1-l $20.00 2 3072M 25G 3TB - f1-x $27.50 3 4096M 25G 4TB - f1-xx $50.00 4 8192M 25G 5TB + f1-xs {{config['CURRENCY_SYMBOL']}}5.00 1 512M 25G .5TB + f1-s {{config['CURRENCY_SYMBOL']}}7.50 1 1024M 25G 1TB + f1-m {{config['CURRENCY_SYMBOL']}}12.50 1 2048M 25G 2TB + f1-l {{config['CURRENCY_SYMBOL']}}20.00 2 3072M 25G 3TB + f1-x {{config['CURRENCY_SYMBOL']}}27.50 3 4096M 25G 4TB + f1-xx {{config['CURRENCY_SYMBOL']}}50.00 4 8192M 25G 5TB * all VMs come standard with one public IPv4 address * vms are billed for a minimum of 1 hour upon creation
-    Your current account balance: ${{ account_balance }} {% if account_balance != account_balance_in_one_month  %} 
+    Your current account balance: {{config['CURRENCY_SYMBOL']}}{{ account_balance }} {% if account_balance != account_balance_in_one_month  %} 
 
-    Projected account balance in one month: ${{ account_balance_in_one_month }}{% endif %}
+    Projected account balance in one month: {{config['CURRENCY_SYMBOL']}}{{ account_balance_in_one_month }}{% endif %}
   
{% if cant_afford %}

Please fund your account in order to create Capsuls

diff --git a/capsulflask/templates/pricing.html b/capsulflask/templates/pricing.html index 6888660..632ce04 100644 --- a/capsulflask/templates/pricing.html +++ b/capsulflask/templates/pricing.html @@ -22,7 +22,7 @@ {% for vm_size_key, vm_size in vm_sizes.items() %} {{ vm_size_key }} - ${{ vm_size['dollars_per_month'] }} + {{config['CURRENCY_SYMBOL']}}{{ vm_size['amount_per_month'] }} {{ vm_size['vcpus'] }} {{ vm_size['memory_mb'] }} 25G diff --git a/capsulflask/templates/stripe.html b/capsulflask/templates/stripe.html index 5c10928..6763e96 100644 --- a/capsulflask/templates/stripe.html +++ b/capsulflask/templates/stripe.html @@ -26,8 +26,8 @@
- - + +
diff --git a/docs/configuration.md b/docs/configuration.md index 51ff7ee..78e7628 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -63,6 +63,16 @@ BTCPAY_URL="https://beeteeceepae2.cyberia.club" BTCPAY_PRIVATE_KEY='-----BEGIN EC PRIVATE KEY-----\n\n-----END EC PRIVATE KEY-----' ``` +## Configuring Currency +By default, capsul operates in USD. You can change that with the currency configuration settings: +``` +CURRENCY_NAME=dollar +CURRENCY_SYMBOL=$ +CURRENCY_STRIPE_NAME=usd +CURRENCY_BTCPAY_NAME=USD +CURRENCY_STRIPE_CONVERSION_FACTOR=100 +``` +**Don't change the currency once capsul is up and running!** Amounts of money are stored in the database as plain numbers and won't be converted, so if someone has $10 and you move to using gbp they'll now have £10 in their account. ## Configuration-type-stuff that lives in the database - `hosts` table: