" % (
- _('Please configure a PayPal Webhook to the following endpoint in order to automatically cancel orders '
- 'when payments are refunded externally.'),
- build_global_uri('plugins:paypal:webhook')
- )
+ if self.settings.connect_client_id and not self.settings.secret:
+ # Use PayPal connect
+ if not self.settings.connect_user_id:
+ return (
+ "
{}
"
+ "{}"
+ ).format(
+ _('To accept payments via PayPal, you will need an account at PayPal. By clicking on the '
+ 'following button, you can either create a new PayPal account connect pretix to an existing '
+ 'one.'),
+ self.get_connect_url(request),
+ _('Connect with {icon} PayPal').format(icon='')
+ )
+ else:
+ return (
+ ""
+ ).format(
+ reverse('plugins:paypal:oauth.disconnect', kwargs={
+ 'organizer': self.event.organizer.slug,
+ 'event': self.event.slug,
+ }),
+ _('Disconnect from PayPal')
+ )
+ else:
+ return "
%s %s
" % (
+ _('Please configure a PayPal Webhook to the following endpoint in order to automatically cancel orders '
+ 'when payments are refunded externally.'),
+ build_global_uri('plugins:paypal:webhook')
+ )
def init_api(self):
- paypalrestsdk.set_config(
- mode="sandbox" if "sandbox" in self.settings.get('endpoint') else 'live',
- client_id=self.settings.get('client_id'),
- client_secret=self.settings.get('secret'))
+ if self.settings.connect_client_id:
+ paypalrestsdk.set_config(
+ mode="sandbox" if "sandbox" in self.settings.connect_endpoint else 'live',
+ client_id=self.settings.connect_client_id,
+ client_secret=self.settings.connect_secret_key,
+ openid_client_id=self.settings.connect_client_id,
+ openid_client_secret=self.settings.connect_secret_key,
+ openid_redirect_uri=urlquote(build_global_uri('plugins:paypal:oauth.return')))
+ else:
+ paypalrestsdk.set_config(
+ mode="sandbox" if "sandbox" in self.settings.get('endpoint') else 'live',
+ client_id=self.settings.get('client_id'),
+ client_secret=self.settings.get('secret'))
def payment_is_valid_session(self, request):
return (request.session.get('payment_paypal_id', '') != ''
@@ -90,6 +153,18 @@ class Paypal(BasePaymentProvider):
if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
+ if request.event.settings.payment_paypal_connect_user_id:
+ userinfo = Tokeninfo.create_with_refresh_token(request.event.settings.payment_paypal_connect_refresh_token).userinfo()
+ request.event.settings.payment_paypal_connect_user_id = userinfo.email
+ payee = {
+ "email": request.event.settings.payment_paypal_connect_user_id,
+ # 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
+ # "merchant_id": request.event.settings.payment_paypal_connect_user_id,
+ }
+ else:
+ payee = {}
+
payment = paypalrestsdk.Payment({
'intent': 'sale',
'payer': {
@@ -115,7 +190,8 @@ class Paypal(BasePaymentProvider):
"currency": request.event.currency,
"total": self.format_price(cart['total'])
},
- "description": __('Event tickets for {event}').format(event=request.event.name)
+ "description": __('Event tickets for {event}').format(event=request.event.name),
+ "payee": payee
}
]
})
@@ -333,6 +409,19 @@ class Paypal(BasePaymentProvider):
def payment_prepare(self, request, payment_obj):
self.init_api()
+
+ if request.event.settings.payment_paypal_connect_user_id:
+ userinfo = Tokeninfo.create_with_refresh_token(request.event.settings.payment_paypal_connect_refresh_token).userinfo()
+ request.event.settings.payment_paypal_connect_user_id = userinfo.email
+ payee = {
+ "email": request.event.settings.payment_paypal_connect_user_id,
+ # 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
+ # "merchant_id": request.event.settings.payment_paypal_connect_user_id,
+ }
+ else:
+ payee = {}
+
payment = paypalrestsdk.Payment({
'intent': 'sale',
'payer': {
@@ -362,7 +451,8 @@ class Paypal(BasePaymentProvider):
"description": __('Order {order} for {event}').format(
event=request.event.name,
order=payment_obj.order.code
- )
+ ),
+ "payee": payee
}
]
})
diff --git a/src/pretix/plugins/paypal/signals.py b/src/pretix/plugins/paypal/signals.py
index e2afc0ca5c..c77f2a3522 100644
--- a/src/pretix/plugins/paypal/signals.py
+++ b/src/pretix/plugins/paypal/signals.py
@@ -1,11 +1,14 @@
import json
+from collections import OrderedDict
+from django import forms
from django.dispatch import receiver
from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from pretix.base.signals import (
- logentry_display, register_payment_providers, requiredaction_display,
+ logentry_display, register_global_settings, register_payment_providers,
+ requiredaction_display,
)
@@ -53,3 +56,25 @@ def pretixcontrol_action_display(sender, action, request, **kwargs):
ctx = {'data': data, 'event': sender, 'action': action}
return template.render(ctx, request)
+
+
+@receiver(register_global_settings, dispatch_uid='paypal_global_settings')
+def register_global_settings(sender, **kwargs):
+ return OrderedDict([
+ ('payment_paypal_connect_client_id', forms.CharField(
+ label=_('PayPal Connect: Client ID'),
+ required=False,
+ )),
+ ('payment_paypal_connect_secret_key', forms.CharField(
+ label=_('PayPal Connect: Secret key'),
+ required=False,
+ )),
+ ('payment_paypal_connect_endpoint', forms.ChoiceField(
+ label=_('PayPal Connect Endpoint'),
+ initial='live',
+ choices=(
+ ('live', 'Live'),
+ ('sandbox', 'Sandbox'),
+ ),
+ )),
+ ])
diff --git a/src/pretix/plugins/paypal/urls.py b/src/pretix/plugins/paypal/urls.py
index 02042dc994..773012eae1 100644
--- a/src/pretix/plugins/paypal/urls.py
+++ b/src/pretix/plugins/paypal/urls.py
@@ -2,7 +2,9 @@ from django.conf.urls import include, url
from pretix.multidomain import event_url
-from .views import abort, redirect_view, success, webhook
+from .views import (
+ abort, oauth_disconnect, oauth_return, redirect_view, success, webhook,
+)
event_patterns = [
url(r'^paypal/', include([
@@ -19,5 +21,8 @@ event_patterns = [
urlpatterns = [
+ url(r'^control/event/(?P[^/]+)/(?P[^/]+)/paypal/disconnect/',
+ oauth_disconnect, name='oauth.disconnect'),
url(r'^_paypal/webhook/$', webhook, name='webhook'),
+ url(r'^_paypal/oauth_return/$', oauth_return, name='oauth.return'),
]
diff --git a/src/pretix/plugins/paypal/views.py b/src/pretix/plugins/paypal/views.py
index 38af84117f..b58ae81f71 100644
--- a/src/pretix/plugins/paypal/views.py
+++ b/src/pretix/plugins/paypal/views.py
@@ -7,14 +7,17 @@ from django.contrib import messages
from django.core import signing
from django.db.models import Sum
from django.http import HttpResponse, HttpResponseBadRequest
-from django.shortcuts import redirect, render
+from django.shortcuts import get_object_or_404, redirect, render
+from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
+from paypalrestsdk.openid_connect import Tokeninfo
-from pretix.base.models import Order, OrderPayment, OrderRefund, Quota
+from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.payment import PaymentException
+from pretix.control.permissions import event_permission_required
from pretix.multidomain.urlreverse import eventreverse
from pretix.plugins.paypal.models import ReferencedPayPalObject
from pretix.plugins.paypal.payment import Paypal
@@ -37,6 +40,37 @@ def redirect_view(request, *args, **kwargs):
return r
+def oauth_return(request, *args, **kwargs):
+ if 'payment_paypal_oauth_event' not in request.session:
+ messages.error(request, _('An error occurred during connecting with PayPal, please try again.'))
+ return redirect(reverse('control:index'))
+
+ event = get_object_or_404(Event, pk=request.session['payment_paypal_oauth_event'])
+
+ prov = Paypal(event)
+ prov.init_api()
+
+ try:
+ tokeninfo = Tokeninfo.create(request.GET.get('code'))
+ userinfo = Tokeninfo.create_with_refresh_token(tokeninfo['refresh_token']).userinfo()
+ except:
+ logger.exception('Failed to obtain OAuth token')
+ messages.error(request, _('An error occurred during connecting with PayPal, please try again.'))
+ else:
+ messages.success(request,
+ _('Your PayPal account is now connected to pretix. You can change the settings in '
+ 'detail below.'))
+
+ event.settings.payment_paypal_connect_refresh_token = tokeninfo['refresh_token']
+ event.settings.payment_paypal_connect_user_id = userinfo.email
+
+ return redirect(reverse('control:event.settings.payment.provider', kwargs={
+ 'organizer': event.organizer.slug,
+ 'event': event.slug,
+ 'provider': 'paypal'
+ }))
+
+
def success(request, *args, **kwargs):
pid = request.GET.get('paymentId')
token = request.GET.get('token')
@@ -201,3 +235,18 @@ def webhook(request, *args, **kwargs):
pass
return HttpResponse(status=200)
+
+
+@event_permission_required('can_change_event_settings')
+@require_POST
+def oauth_disconnect(request, **kwargs):
+ del request.event.settings.payment_paypal_connect_refresh_token
+ del request.event.settings.payment_paypal_connect_user_id
+ request.event.settings.payment_paypal__enabled = False
+ messages.success(request, _('Your PayPal account has been disconnected.'))
+
+ return redirect(reverse('control:event.settings.payment.provider', kwargs={
+ 'organizer': request.event.organizer.slug,
+ 'event': request.event.slug,
+ 'provider': 'paypal'
+ }))