PayPal: Improve handling of exceptions form paypalrestsdk

This commit is contained in:
Raphael Michel
2020-06-12 13:21:23 +02:00
parent d1c96aa77c
commit 9eacd38ec7
2 changed files with 153 additions and 147 deletions

View File

@@ -5,6 +5,7 @@ from collections import OrderedDict
from decimal import Decimal from decimal import Decimal
import paypalrestsdk import paypalrestsdk
import paypalrestsdk.exceptions
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.core import signing from django.core import signing
@@ -196,68 +197,72 @@ class Paypal(BasePaymentProvider):
if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs: if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace'] kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
if request.event.settings.payment_paypal_connect_user_id: try:
try: if request.event.settings.payment_paypal_connect_user_id:
tokeninfo = Tokeninfo.create_with_refresh_token(request.event.settings.payment_paypal_connect_refresh_token) try:
except BadRequest as ex: tokeninfo = Tokeninfo.create_with_refresh_token(request.event.settings.payment_paypal_connect_refresh_token)
ex = json.loads(ex.content) except BadRequest as ex:
messages.error(request, '{}: {} ({})'.format( ex = json.loads(ex.content)
_('We had trouble communicating with PayPal'), messages.error(request, '{}: {} ({})'.format(
ex['error_description'], _('We had trouble communicating with PayPal'),
ex['correlation_id']) ex['error_description'],
) ex['correlation_id'])
return )
return
# Even if the token has been refreshed, calling userinfo() can fail. In this case we just don't # Even if the token has been refreshed, calling userinfo() can fail. In this case we just don't
# get the userinfo again and use the payment_paypal_connect_user_id that we already have on file # get the userinfo again and use the payment_paypal_connect_user_id that we already have on file
try: try:
userinfo = tokeninfo.userinfo() userinfo = tokeninfo.userinfo()
request.event.settings.payment_paypal_connect_user_id = userinfo.email request.event.settings.payment_paypal_connect_user_id = userinfo.email
except UnauthorizedAccess: except UnauthorizedAccess:
pass pass
payee = { payee = {
"email": request.event.settings.payment_paypal_connect_user_id, "email": request.event.settings.payment_paypal_connect_user_id,
# If PayPal ever offers a good way to get the MerchantID via the Identifity API, # If PayPal ever offers a good way to get the MerchantID via the Identifity API,
# we should use it instead of the merchant's eMail-address # we should use it instead of the merchant's eMail-address
# "merchant_id": request.event.settings.payment_paypal_connect_user_id, # "merchant_id": request.event.settings.payment_paypal_connect_user_id,
}
else:
payee = {}
payment = paypalrestsdk.Payment({
'header': {'PayPal-Partner-Attribution-Id': 'ramiioSoftwareentwicklung_SP'},
'intent': 'sale',
'payer': {
"payment_method": "paypal",
},
"redirect_urls": {
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return', kwargs=kwargs),
"cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort', kwargs=kwargs),
},
"transactions": [
{
"item_list": {
"items": [
{
"name": __('Order for %s') % str(request.event),
"quantity": 1,
"price": self.format_price(cart['total']),
"currency": request.event.currency
}
]
},
"amount": {
"currency": request.event.currency,
"total": self.format_price(cart['total'])
},
"description": __('Event tickets for {event}').format(event=request.event.name),
"payee": payee
} }
] else:
}) payee = {}
request.session['payment_paypal_payment'] = None
return self._create_payment(request, payment) payment = paypalrestsdk.Payment({
'header': {'PayPal-Partner-Attribution-Id': 'ramiioSoftwareentwicklung_SP'},
'intent': 'sale',
'payer': {
"payment_method": "paypal",
},
"redirect_urls": {
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return', kwargs=kwargs),
"cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort', kwargs=kwargs),
},
"transactions": [
{
"item_list": {
"items": [
{
"name": __('Order for %s') % str(request.event),
"quantity": 1,
"price": self.format_price(cart['total']),
"currency": request.event.currency
}
]
},
"amount": {
"currency": request.event.currency,
"total": self.format_price(cart['total'])
},
"description": __('Event tickets for {event}').format(event=request.event.name),
"payee": payee
}
]
})
request.session['payment_paypal_payment'] = None
return self._create_payment(request, payment)
except paypalrestsdk.exceptions.ConnectionError as e:
messages.error(request, _('We had trouble communicating with PayPal'))
logger.exception('Error on creating payment: ' + str(e))
def format_price(self, value): def format_price(self, value):
return str(round_decimal(value, self.event.currency, { return str(round_decimal(value, self.event.currency, {
@@ -292,29 +297,25 @@ class Paypal(BasePaymentProvider):
return False return False
def _create_payment(self, request, payment): def _create_payment(self, request, payment):
try: if payment.create():
if payment.create(): if payment.state not in ('created', 'approved', 'pending'):
if payment.state not in ('created', 'approved', 'pending'):
messages.error(request, _('We had trouble communicating with PayPal'))
logger.error('Invalid payment state: ' + str(payment))
return
request.session['payment_paypal_id'] = payment.id
for link in payment.links:
if link.method == "REDIRECT" and link.rel == "approval_url":
if request.session.get('iframe_session', False):
signer = signing.Signer(salt='safe-redirect')
return (
build_absolute_uri(request.event, 'plugins:paypal:redirect') + '?url=' +
urllib.parse.quote(signer.sign(link.href))
)
else:
return str(link.href)
else:
messages.error(request, _('We had trouble communicating with PayPal')) messages.error(request, _('We had trouble communicating with PayPal'))
logger.error('Error on creating payment: ' + str(payment.error)) logger.error('Invalid payment state: ' + str(payment))
except Exception as e: return
request.session['payment_paypal_id'] = payment.id
for link in payment.links:
if link.method == "REDIRECT" and link.rel == "approval_url":
if request.session.get('iframe_session', False):
signer = signing.Signer(salt='safe-redirect')
return (
build_absolute_uri(request.event, 'plugins:paypal:redirect') + '?url=' +
urllib.parse.quote(signer.sign(link.href))
)
else:
return str(link.href)
else:
messages.error(request, _('We had trouble communicating with PayPal')) messages.error(request, _('We had trouble communicating with PayPal'))
logger.exception('Error on creating payment: ' + str(e)) logger.error('Error on creating payment: ' + str(payment.error))
def checkout_confirm_render(self, request) -> str: def checkout_confirm_render(self, request) -> str:
""" """
@@ -375,7 +376,7 @@ class Paypal(BasePaymentProvider):
]) ])
try: try:
payment.execute({"payer_id": request.session.get('payment_paypal_payer')}) payment.execute({"payer_id": request.session.get('payment_paypal_payer')})
except Exception as e: except paypalrestsdk.exceptions.ConnectionError as e:
messages.error(request, _('We had trouble communicating with PayPal')) messages.error(request, _('We had trouble communicating with PayPal'))
logger.exception('Error on creating payment: ' + str(e)) logger.exception('Error on creating payment: ' + str(e))
@@ -511,72 +512,76 @@ class Paypal(BasePaymentProvider):
def payment_prepare(self, request, payment_obj): def payment_prepare(self, request, payment_obj):
self.init_api() self.init_api()
if request.event.settings.payment_paypal_connect_user_id: try:
try: if request.event.settings.payment_paypal_connect_user_id:
tokeninfo = Tokeninfo.create_with_refresh_token(request.event.settings.payment_paypal_connect_refresh_token) try:
except BadRequest as ex: tokeninfo = Tokeninfo.create_with_refresh_token(request.event.settings.payment_paypal_connect_refresh_token)
ex = json.loads(ex.content) except BadRequest as ex:
messages.error(request, '{}: {} ({})'.format( ex = json.loads(ex.content)
_('We had trouble communicating with PayPal'), messages.error(request, '{}: {} ({})'.format(
ex['error_description'], _('We had trouble communicating with PayPal'),
ex['correlation_id']) ex['error_description'],
) ex['correlation_id'])
return )
return
# Even if the token has been refreshed, calling userinfo() can fail. In this case we just don't # Even if the token has been refreshed, calling userinfo() can fail. In this case we just don't
# get the userinfo again and use the payment_paypal_connect_user_id that we already have on file # get the userinfo again and use the payment_paypal_connect_user_id that we already have on file
try: try:
userinfo = tokeninfo.userinfo() userinfo = tokeninfo.userinfo()
request.event.settings.payment_paypal_connect_user_id = userinfo.email request.event.settings.payment_paypal_connect_user_id = userinfo.email
except UnauthorizedAccess: except UnauthorizedAccess:
pass pass
payee = { payee = {
"email": request.event.settings.payment_paypal_connect_user_id, "email": request.event.settings.payment_paypal_connect_user_id,
# If PayPal ever offers a good way to get the MerchantID via the Identifity API, # If PayPal ever offers a good way to get the MerchantID via the Identifity API,
# we should use it instead of the merchant's eMail-address # we should use it instead of the merchant's eMail-address
# "merchant_id": request.event.settings.payment_paypal_connect_user_id, # "merchant_id": request.event.settings.payment_paypal_connect_user_id,
}
else:
payee = {}
payment = paypalrestsdk.Payment({
'header': {'PayPal-Partner-Attribution-Id': 'ramiioSoftwareentwicklung_SP'},
'intent': 'sale',
'payer': {
"payment_method": "paypal",
},
"redirect_urls": {
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return'),
"cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort'),
},
"transactions": [
{
"item_list": {
"items": [
{
"name": __('Order {slug}-{code}').format(slug=self.event.slug.upper(),
code=payment_obj.order.code),
"quantity": 1,
"price": self.format_price(payment_obj.amount),
"currency": payment_obj.order.event.currency
}
]
},
"amount": {
"currency": request.event.currency,
"total": self.format_price(payment_obj.amount)
},
"description": __('Order {order} for {event}').format(
event=request.event.name,
order=payment_obj.order.code
),
"payee": payee
} }
] else:
}) payee = {}
request.session['payment_paypal_payment'] = payment_obj.pk
return self._create_payment(request, payment) payment = paypalrestsdk.Payment({
'header': {'PayPal-Partner-Attribution-Id': 'ramiioSoftwareentwicklung_SP'},
'intent': 'sale',
'payer': {
"payment_method": "paypal",
},
"redirect_urls": {
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return'),
"cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort'),
},
"transactions": [
{
"item_list": {
"items": [
{
"name": __('Order {slug}-{code}').format(slug=self.event.slug.upper(),
code=payment_obj.order.code),
"quantity": 1,
"price": self.format_price(payment_obj.amount),
"currency": payment_obj.order.event.currency
}
]
},
"amount": {
"currency": request.event.currency,
"total": self.format_price(payment_obj.amount)
},
"description": __('Order {order} for {event}').format(
event=request.event.name,
order=payment_obj.order.code
),
"payee": payee
}
]
})
request.session['payment_paypal_payment'] = payment_obj.pk
return self._create_payment(request, payment)
except paypalrestsdk.exceptions.ConnectionError as e:
messages.error(request, _('We had trouble communicating with PayPal'))
logger.exception('Error on creating payment: ' + str(e))
def shred_payment_info(self, obj): def shred_payment_info(self, obj):
if obj.info: if obj.info:

View File

@@ -3,6 +3,7 @@ import logging
from decimal import Decimal from decimal import Decimal
import paypalrestsdk import paypalrestsdk
import paypalrestsdk.exceptions
from django.contrib import messages from django.contrib import messages
from django.core import signing from django.core import signing
from django.db.models import Sum from django.db.models import Sum
@@ -55,7 +56,7 @@ def oauth_return(request, *args, **kwargs):
try: try:
tokeninfo = Tokeninfo.create(request.GET.get('code')) tokeninfo = Tokeninfo.create(request.GET.get('code'))
userinfo = Tokeninfo.create_with_refresh_token(tokeninfo['refresh_token']).userinfo() userinfo = Tokeninfo.create_with_refresh_token(tokeninfo['refresh_token']).userinfo()
except: except paypalrestsdk.exceptions.ConnectionError:
logger.exception('Failed to obtain OAuth token') logger.exception('Failed to obtain OAuth token')
messages.error(request, _('An error occurred during connecting with PayPal, please try again.')) messages.error(request, _('An error occurred during connecting with PayPal, please try again.'))
else: else:
@@ -170,7 +171,7 @@ def webhook(request, *args, **kwargs):
try: try:
sale = paypalrestsdk.Sale.find(saleid) sale = paypalrestsdk.Sale.find(saleid)
except: except paypalrestsdk.exceptions.ConnectionError:
logger.exception('PayPal error on webhook. Event data: %s' % str(event_json)) logger.exception('PayPal error on webhook. Event data: %s' % str(event_json))
return HttpResponse('Sale not found', status=500) return HttpResponse('Sale not found', status=500)
@@ -197,7 +198,7 @@ def webhook(request, *args, **kwargs):
if event_json['resource_type'] == 'refund': if event_json['resource_type'] == 'refund':
try: try:
refund = paypalrestsdk.Refund.find(event_json['resource']['id']) refund = paypalrestsdk.Refund.find(event_json['resource']['id'])
except: except paypalrestsdk.exceptions.ConnectionError:
logger.exception('PayPal error on webhook. Event data: %s' % str(event_json)) logger.exception('PayPal error on webhook. Event data: %s' % str(event_json))
return HttpResponse('Refund not found', status=500) return HttpResponse('Refund not found', status=500)