diff --git a/config.py.example b/config.py.example index d649e2f3..38907e87 100644 --- a/config.py.example +++ b/config.py.example @@ -34,9 +34,16 @@ PAYPAL_BUSINESS = "payment@example.org" # Stripe # https://stripe.com/docs/tutorials/dashboard#api-keys STRIPE_KEYS = { - "SECRET": "", - "PUBLISHABLE": "", - "WEBHOOK_SECRET": "" + "USD": { + "SECRET": "", + "PUBLISHABLE": "", + "WEBHOOK_SECRET": "" + }, + "EUR": { + "SECRET": "", + "PUBLISHABLE": "", + "WEBHOOK_SECRET": "" + } } # if developing payment integration locally, change this to your localhost url diff --git a/consul_config.py.ctmpl b/consul_config.py.ctmpl index 89f62dae..5248f1c2 100644 --- a/consul_config.py.ctmpl +++ b/consul_config.py.ctmpl @@ -39,9 +39,16 @@ PAYPAL_ACCOUNT_IDS = { PAYPAL_BUSINESS = '''{{template "KEY" "payments/paypal/business_email"}}''' STRIPE_KEYS = { - "SECRET": '''{{template "KEY" "payments/stripe/secret"}}''', - "PUBLISHABLE": '''{{template "KEY" "payments/stripe/publishable"}}''', - "WEBHOOK_SECRET": '''{{template "KEY" "payments/stripe/webhook_secret"}}''', + "USD": { + "SECRET": '''{{template "KEY" "payments/stripe/secret"}}''', + "PUBLISHABLE": '''{{template "KEY" "payments/stripe/publishable"}}''', + "WEBHOOK_SECRET": '''{{template "KEY" "payments/stripe/webhook_secret"}}''', + }, + "EUR": { + "SECRET": '''{{template "KEY" "payments/stripe-eu/secret"}}''', + "PUBLISHABLE": '''{{template "KEY" "payments/stripe-eu/publishable"}}''', + "WEBHOOK_SECRET": '''{{template "KEY" "payments/stripe-eu/webhook_secret"}}''', + }, } # MusicBrainz Base URL must have a trailing slash. diff --git a/metabrainz/__init__.py b/metabrainz/__init__.py index bc5b7bbf..b4e13e32 100644 --- a/metabrainz/__init__.py +++ b/metabrainz/__init__.py @@ -169,8 +169,6 @@ def create_app(debug=None, config_path=None): if app.config["QUICKBOOKS_CLIENT_ID"]: admin.add_view(QuickBooksView(name='Invoices', endpoint="quickbooks/", category='Quickbooks')) - stripe.api_key = app.config["STRIPE_KEYS"]["SECRET"] - return app diff --git a/metabrainz/model/payment.py b/metabrainz/model/payment.py index 418028c8..1695fa6c 100644 --- a/metabrainz/model/payment.py +++ b/metabrainz/model/payment.py @@ -300,17 +300,26 @@ def _extract_paypal_ipn_options(form: dict) -> dict: return options @classmethod - def log_subscription_charge(cls, invoice): + def log_subscription_charge(cls, currency, invoice): """ Log successful Stripe charges for a recurring payment/donation """ - charge = stripe.Charge.retrieve(invoice["charge"], expand=["balance_transaction"]) + if currency.lower() == "usd": + api_key = current_app.config["STRIPE_KEYS"]["USD"]["SECRET"] + else: + api_key = current_app.config["STRIPE_KEYS"]["EUR"]["SECRET"] + charge = stripe.Charge.retrieve(invoice["charge"], expand=["balance_transaction"], api_key=api_key) metadata = invoice["lines"]["data"][0]["metadata"] return cls._log_stripe_charge(charge, metadata) @classmethod - def log_one_time_charge(cls, session): + def log_one_time_charge(cls, currency, session): """ Log successful Stripe charge for one time payment/donation """ + if currency.lower() == "usd": + api_key = current_app.config["STRIPE_KEYS"]["USD"]["SECRET"] + else: + api_key = current_app.config["STRIPE_KEYS"]["EUR"]["SECRET"] payment_intent = stripe.PaymentIntent.retrieve(session["payment_intent"], - expand=["latest_charge.balance_transaction"]) + expand=["latest_charge.balance_transaction"], + api_key=api_key) charge = payment_intent["latest_charge"] metadata = payment_intent["metadata"] return cls._log_stripe_charge(charge, metadata) @@ -343,7 +352,7 @@ def _log_stripe_charge(cls, charge, metadata): last_name="", amount=transaction["net"] / 100, # cents should be converted fee=transaction["fee"] / 100, # cents should be converted - currency="usd", + currency=currency, transaction_id=charge["id"], payment_method=PAYMENT_METHOD_STRIPE, is_donation=metadata["is_donation"], diff --git a/metabrainz/model/payment_test.py b/metabrainz/model/payment_test.py index 93519f07..d5f1b94c 100644 --- a/metabrainz/model/payment_test.py +++ b/metabrainz/model/payment_test.py @@ -448,7 +448,7 @@ def test_log_stripe_charge_donation(self, mock_stripe): } mock_stripe.retrieve.return_value = payment_intent session = self.session_without_metadata.copy() - Payment.log_one_time_charge(session) + Payment.log_one_time_charge("usd", session) self.assertEqual(len(Payment.query.all()), 1) @patch("stripe.PaymentIntent") @@ -461,5 +461,5 @@ def test_log_stripe_charge_payment(self, mock_stripe): "invoice_number": 42, } mock_stripe.retrieve.return_value = payment_intent - Payment.log_one_time_charge(self.session_without_metadata) + Payment.log_one_time_charge("usd", self.session_without_metadata) self.assertEqual(len(Payment.query.all()), 1) diff --git a/metabrainz/payments/stripe/views.py b/metabrainz/payments/stripe/views.py index 6b70206c..0142990f 100644 --- a/metabrainz/payments/stripe/views.py +++ b/metabrainz/payments/stripe/views.py @@ -23,6 +23,11 @@ def pay(): return redirect(url_for("payments.error", is_donation=is_donation)) is_recurring = form.recurring.data + currency = form.currency.data + if currency.lower() == "usd": + api_key = current_app.config["STRIPE_KEYS"]["USD"]["SECRET"] + else: + api_key = current_app.config["STRIPE_KEYS"]["EUR"]["SECRET"] charge_metadata = { "is_donation": is_donation, @@ -46,7 +51,7 @@ def pay(): { "price_data": { "unit_amount": int(form.amount.data * 100), # amount in cents - "currency": form.currency.data, + "currency": currency, "product_data": { "name": "Support the MetaBrainz Foundation", "description": description @@ -77,18 +82,24 @@ def pay(): } try: - session = stripe.checkout.Session.create(**session_config) + session = stripe.checkout.Session.create(**session_config, api_key=api_key) return redirect(session.url, code=303) except Exception as e: current_app.logger.error(e, exc_info=True) return redirect(url_for("payments.error", is_donation=is_donation)) -@payments_stripe_bp.route("/webhook/", methods=["POST"]) -def webhook(): +@payments_stripe_bp.route("/webhook//", methods=["POST"]) +def webhook(currency): payload = request.data sig_header = request.headers.get("Stripe-Signature") - webhook_secret = current_app.config["STRIPE_KEYS"]["WEBHOOK_SECRET"] + + if currency.lower() == "usd": + webhook_secret = current_app.config["STRIPE_KEYS"]["USD"]["WEBHOOK_SECRET"] + elif currency.lower() == "eur": + webhook_secret = current_app.config["STRIPE_KEYS"]["EUR"]["WEBHOOK_SECRET"] + else: + return jsonify({"error": "invalid currency"}), 400 try: event = stripe.Webhook.construct_event(payload, sig_header, webhook_secret) @@ -105,8 +116,8 @@ def webhook(): if event["type"] == "checkout.session.completed": session = event["data"]["object"] if session["mode"] == "payment": - Payment.log_one_time_charge(session) + Payment.log_one_time_charge(currency, session) elif event["type"] == "invoice.paid": - Payment.log_subscription_charge(event["data"]["object"]) + Payment.log_subscription_charge(currency, event["data"]["object"]) return jsonify({"status": "ok"}) diff --git a/metabrainz/payments/views.py b/metabrainz/payments/views.py index e535b581..119bcb29 100644 --- a/metabrainz/payments/views.py +++ b/metabrainz/payments/views.py @@ -1,7 +1,7 @@ from __future__ import division from flask import Blueprint, request, render_template, url_for, redirect, current_app, jsonify from flask_babel import gettext -from metabrainz.payments import Currency, SUPPORTED_CURRENCIES +from metabrainz.payments import SUPPORTED_CURRENCIES from metabrainz.model.payment import Payment from metabrainz.payments.forms import DonationForm, PaymentForm from metabrainz import flash @@ -18,9 +18,7 @@ @payments_bp.route('/donate') def donate(): """Regular donation page.""" - stripe_public_key = current_app.config['STRIPE_KEYS']['PUBLISHABLE'] - return render_template('payments/donate.html', form=DonationForm(), - stripe_public_key=stripe_public_key) + return render_template('payments/donate.html', form=DonationForm()) @payments_bp.route('/payment/') @@ -35,9 +33,7 @@ def payment(currency): currency = currency.lower() if currency not in SUPPORTED_CURRENCIES: return redirect('.payment_selector') - stripe_public_key = current_app.config['STRIPE_KEYS']['PUBLISHABLE'] - return render_template('payments/payment.html', form=PaymentForm(), currency=currency, - stripe_public_key=stripe_public_key) + return render_template('payments/payment.html', form=PaymentForm(), currency=currency) @payments_bp.route('/donors') diff --git a/metabrainz/templates/payments/donate.html b/metabrainz/templates/payments/donate.html index 610821d8..a15675d8 100644 --- a/metabrainz/templates/payments/donate.html +++ b/metabrainz/templates/payments/donate.html @@ -175,7 +175,6 @@

{{ _('US Check') }}

$.each(buttons, function( index, button ) { button.removeAttr('disabled'); }); - updateButtonState(); } else { // Disabled $.each(buttons, function( index, button ) { @@ -184,19 +183,6 @@

{{ _('US Check') }}

} } - function updateButtonState() { - // This function disables or enables payment buttons depending on what - // features they support. - - // We don't support payments in EUR via Stripe currently. - if (selectedCurrency === CURRENCY.Euro) { - buttons.stripe.attr('disabled', 'disabled'); - } else { - buttons.stripe.removeAttr('disabled'); - } - } - - /////////////// // AMOUNT INPUT /////////////// @@ -226,7 +212,6 @@

{{ _('US Check') }}

} else { console.error("Unknown currency:", this.value) } - updateButtonState(); }); /////////////// diff --git a/metabrainz/templates/payments/payment.html b/metabrainz/templates/payments/payment.html index 98caa10c..2ac42b3f 100644 --- a/metabrainz/templates/payments/payment.html +++ b/metabrainz/templates/payments/payment.html @@ -140,20 +140,6 @@ // PAYMENTS /////////// - function updateRecurringState() { - // We don't support recurring payments via Stripe - if ($('#recurring-flag').is(":checked")) { - buttons.stripe.attr('disabled', 'disabled'); - } else { - buttons.stripe.removeAttr('disabled'); - } - } - - $('#recurring-flag').change(function() { - updateRecurringState(); - }); - - // Stripe buttons.stripe.on('click', function(e) { setButtonsState(false); diff --git a/metabrainz/templates/payments/payment_base.html b/metabrainz/templates/payments/payment_base.html index 38981196..4e4f924b 100644 --- a/metabrainz/templates/payments/payment_base.html +++ b/metabrainz/templates/payments/payment_base.html @@ -7,9 +7,9 @@

{{ _('Make a Payment') }}