Repository: liberapay/liberapay.com

Issue:
500 error when renewing payment with Paypal
I wanted to renew a payment.
It was a 0.24/week so 1€/month
So I wanted to pay 12€ upfront as usual (not the first renewal with PayPal, it already worked for other donations) to keep the fees not too high but not to pay too much in advance.

So I input 12, clicked on "Initiate payment" and boom

b57e12b4473f4dbb84195917a3706c28
c1f75c51ac2c42d1888557719a76a854


Discussion:

Resolving patch:
diff --git a/liberapay/payin/paypal.py b/liberapay/payin/paypal.py
--- a/liberapay/payin/paypal.py
+++ b/liberapay/payin/paypal.py
@@ -26,6 +26,17 @@
 session = requests.Session()
 
 
+def _extract_error_message(response):
+    try:
+        error = response.json()['message']
+        assert error
+        return error
+    except Exception:
+        error = response.text  # for Sentry
+        logger.debug(error)
+        raise PaymentError('PayPal')
+
+
 def _init_session():
     # TODO switch to bearer tokens to reduce the risk of exposing the long-lived secret
     if 'Authentication' in session.headers:
@@ -97,17 +108,6 @@ def create_payment(db, payin, payer, return_url, state):
                 "allowed_payment_method": "UNRESTRICTED"
             },
             "soft_descriptor": "Liberapay",
-            "item_list": {
-                "items": [{
-                    "name":
-                        _("One week of the donation") if pt.period == 'weekly' else
-                        _("One month of the donation") if pt.period == 'monthly' else
-                        _("One year of the donation"),
-                    "quantity": pt.n_units,
-                    "price": str(pt.unit_amount.amount),
-                    "currency": pt.unit_amount.currency
-                }]
-            } if pt.n_units >= 1 else {}
         } for pt in transfers],
         "redirect_urls": {
             "return_url": return_url,
@@ -120,9 +120,8 @@ def create_payment(db, payin, payer, return_url, state):
     }
     response = _init_session().post(url, json=data, headers=headers)
     if response.status_code != 201:
-        error = response.text  # for Sentry
-        logger.debug(error)
-        raise PaymentError('PayPal', error)
+        error = _extract_error_message(response)
+        return update_payin(db, payin.id, None, 'failed', error)
     payment = response.json()
     status = PAYMENT_STATES_MAP[payment['state']]
     error = payment.get('failure_reason')
@@ -145,9 +144,8 @@ def execute_payment(db, payin, payer_id):
     data = {"payer_id": payer_id}
     response = _init_session().post(url, json=data, headers=headers)
     if response.status_code != 200:
-        error = response.text  # for Sentry
-        logger.debug(error)
-        raise PaymentError('PayPal')
+        error = _extract_error_message(response)
+        return update_payin(db, payin.id, None, 'failed', error)
     payment = response.json()
 
     # Update the payin
diff --git a/liberapay/utils/state_chain.py b/liberapay/utils/state_chain.py
--- a/liberapay/utils/state_chain.py
+++ b/liberapay/utils/state_chain.py
@@ -205,7 +205,7 @@ def bypass_csp_for_form_redirects(response, state, website, request=None):
     if request is None:
         return
     # https://github.com/liberapay/liberapay.com/issues/952
-    if request.method == 'POST' and response.code == 302:
+    if response.code == 302:
         target = response.headers[b'Location']
         is_internal = (
             target[:1] in (b'/', b'.') or


Test patch: