forked from CGM_Public/pretix_original
Merge remote-tracking branch 'origin/stripe-connect-fees'
This commit is contained in:
@@ -2,6 +2,8 @@ from django import template
|
|||||||
from django.template import Node
|
from django.template import Node
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from pretix.base.models import Event
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@@ -39,15 +41,19 @@ class PropagatedNode(Node):
|
|||||||
</div>
|
</div>
|
||||||
""".format(
|
""".format(
|
||||||
body=body,
|
body=body,
|
||||||
text_inh=_("Organizer-level settings"),
|
text_inh=_("Organizer-level settings") if isinstance(event, Event) else _('Site-level settings'),
|
||||||
fnames=','.join(self.field_names),
|
fnames=','.join(self.field_names),
|
||||||
text_expl=_(
|
text_expl=_(
|
||||||
'These settings are currently set on organizer level. This way, you can easily change them for '
|
'These settings are currently set on organizer level. This way, you can easily change them for '
|
||||||
'all of your events at the same time. You can either go to the organizer settings to change them '
|
'all of your events at the same time. You can either go to the organizer settings to change them '
|
||||||
'or decouple them from the organizer account to change them for this event individually.'
|
'or decouple them from the organizer account to change them for this event individually.'
|
||||||
|
) if isinstance(event, Event) else _(
|
||||||
|
'These settings are currently set on global level. This way, you can easily change them for '
|
||||||
|
'all organizers at the same time. You can either go to the global settings to change them '
|
||||||
|
'or decouple them from the global settings to change them for this event individually.'
|
||||||
),
|
),
|
||||||
text_unlink=_('Change only for this event'),
|
text_unlink=_('Change only for this event') if isinstance(event, Event) else _('Change only for this organizer'),
|
||||||
text_orga=_('Change for all events'),
|
text_orga=_('Change for all events') if isinstance(event, Event) else _('Change for all organizers'),
|
||||||
url=url
|
url=url
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from pretix.base.forms import SettingsForm
|
||||||
|
|
||||||
|
|
||||||
class StripeKeyValidator:
|
class StripeKeyValidator:
|
||||||
def __init__(self, prefix):
|
def __init__(self, prefix):
|
||||||
@@ -21,3 +23,18 @@ class StripeKeyValidator:
|
|||||||
'prefix': self._prefixes[0],
|
'prefix': self._prefixes[0],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizerStripeSettingsForm(SettingsForm):
|
||||||
|
payment_stripe_connect_app_fee_percent = forms.DecimalField(
|
||||||
|
label=_('Stripe Connect: App fee (percent)'),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
payment_stripe_connect_app_fee_max = forms.DecimalField(
|
||||||
|
label=_('Stripe Connect: App fee (max)'),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
payment_stripe_connect_app_fee_min = forms.DecimalField(
|
||||||
|
label=_('Stripe Connect: App fee (min)'),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
import stripe
|
import stripe
|
||||||
from django import forms
|
from django import forms
|
||||||
@@ -256,6 +257,20 @@ class StripeMethod(BasePaymentProvider):
|
|||||||
def _get_amount(self, payment):
|
def _get_amount(self, payment):
|
||||||
return self._decimal_to_int(payment.amount)
|
return self._decimal_to_int(payment.amount)
|
||||||
|
|
||||||
|
def _connect_kwargs(self, payment):
|
||||||
|
d = {}
|
||||||
|
if self.settings.connect_client_id and self.settings.connect_user_id:
|
||||||
|
fee = Decimal('0.00')
|
||||||
|
if self.settings.get('connect_app_fee_percent', as_type=Decimal):
|
||||||
|
fee = round_decimal(self.settings.get('connect_app_fee_percent', as_type=Decimal) * payment.amount / Decimal('100.00'), self.event.currency)
|
||||||
|
if self.settings.connect_app_fee_max:
|
||||||
|
fee = min(fee, self.settings.get('connect_app_fee_max', as_type=Decimal))
|
||||||
|
if self.settings.get('connect_app_fee_min', as_type=Decimal):
|
||||||
|
fee = max(fee, self.settings.get('connect_app_fee_min', as_type=Decimal))
|
||||||
|
if fee:
|
||||||
|
d['application_fee_amount'] = self._decimal_to_int(fee)
|
||||||
|
return d
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_kwargs(self):
|
def api_kwargs(self):
|
||||||
if self.settings.connect_client_id and self.settings.connect_user_id:
|
if self.settings.connect_client_id and self.settings.connect_user_id:
|
||||||
@@ -301,6 +316,7 @@ class StripeMethod(BasePaymentProvider):
|
|||||||
code=payment.order.code
|
code=payment.order.code
|
||||||
)[:22]
|
)[:22]
|
||||||
params.update(self.api_kwargs)
|
params.update(self.api_kwargs)
|
||||||
|
params.update(self._connect_kwargs(payment))
|
||||||
charge = stripe.Charge.create(
|
charge = stripe.Charge.create(
|
||||||
amount=self._get_amount(payment),
|
amount=self._get_amount(payment),
|
||||||
currency=self.event.currency.lower(),
|
currency=self.event.currency.lower(),
|
||||||
@@ -612,6 +628,9 @@ class StripeCC(StripeMethod):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self.payment_is_valid_session(request):
|
if self.payment_is_valid_session(request):
|
||||||
|
params = {}
|
||||||
|
params.update(self._connect_kwargs(payment))
|
||||||
|
params.update(self.api_kwargs)
|
||||||
intent = stripe.PaymentIntent.create(
|
intent = stripe.PaymentIntent.create(
|
||||||
amount=self._get_amount(payment),
|
amount=self._get_amount(payment),
|
||||||
currency=self.event.currency.lower(),
|
currency=self.event.currency.lower(),
|
||||||
@@ -638,7 +657,7 @@ class StripeCC(StripeMethod):
|
|||||||
'payment': payment.pk,
|
'payment': payment.pk,
|
||||||
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
||||||
}),
|
}),
|
||||||
**self.api_kwargs
|
**params
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
payment_info = json.loads(payment.info)
|
payment_info = json.loads(payment.info)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from collections import OrderedDict
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import resolve
|
from django.urls import resolve, reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from pretix.base.settings import settings_hierarkey
|
from pretix.base.settings import settings_hierarkey
|
||||||
@@ -12,6 +12,7 @@ from pretix.base.signals import (
|
|||||||
logentry_display, register_global_settings, register_payment_providers,
|
logentry_display, register_global_settings, register_payment_providers,
|
||||||
requiredaction_display,
|
requiredaction_display,
|
||||||
)
|
)
|
||||||
|
from pretix.control.signals import nav_organizer
|
||||||
from pretix.plugins.stripe.forms import StripeKeyValidator
|
from pretix.plugins.stripe.forms import StripeKeyValidator
|
||||||
from pretix.presale.signals import html_head
|
from pretix.presale.signals import html_head
|
||||||
|
|
||||||
@@ -121,6 +122,18 @@ def register_global_settings(sender, **kwargs):
|
|||||||
StripeKeyValidator('pk_test_'),
|
StripeKeyValidator('pk_test_'),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
('payment_stripe_connect_app_fee_percent', forms.DecimalField(
|
||||||
|
label=_('Stripe Connect: App fee (percent)'),
|
||||||
|
required=False,
|
||||||
|
)),
|
||||||
|
('payment_stripe_connect_app_fee_max', forms.DecimalField(
|
||||||
|
label=_('Stripe Connect: App fee (max)'),
|
||||||
|
required=False,
|
||||||
|
)),
|
||||||
|
('payment_stripe_connect_app_fee_min', forms.DecimalField(
|
||||||
|
label=_('Stripe Connect: App fee (min)'),
|
||||||
|
required=False,
|
||||||
|
)),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@@ -141,3 +154,20 @@ def pretixcontrol_action_display(sender, action, request, **kwargs):
|
|||||||
|
|
||||||
ctx = {'data': data, 'event': sender, 'action': action}
|
ctx = {'data': data, 'event': sender, 'action': action}
|
||||||
return template.render(ctx, request)
|
return template.render(ctx, request)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(nav_organizer, dispatch_uid="stripe_nav_organizer")
|
||||||
|
def nav_o(sender, request, organizer, **kwargs):
|
||||||
|
if request.user.has_active_staff_session(request.session.session_key):
|
||||||
|
url = resolve(request.path_info)
|
||||||
|
return [{
|
||||||
|
'label': _('Stripe Connect'),
|
||||||
|
'url': reverse('plugins:stripe:settings.connect', kwargs={
|
||||||
|
'organizer': request.organizer.slug
|
||||||
|
}),
|
||||||
|
'parent': reverse('control:organizer.edit', kwargs={
|
||||||
|
'organizer': request.organizer.slug
|
||||||
|
}),
|
||||||
|
'active': 'settings.connect' in url.url_name,
|
||||||
|
}]
|
||||||
|
return []
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends "pretixcontrol/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load hierarkey_form %}
|
||||||
|
{% load formset_tags %}
|
||||||
|
{% block title %}{% trans "Stripe Connect" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>
|
||||||
|
{% trans "Stripe Connect" %}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% url "control:global.settings" as g_url %}
|
||||||
|
{% propagated request.organizer g_url "payment_stripe_connect_app_fee_percent" "payment_stripe_connect_app_fee_min" "payment_stripe_connect_app_fee_max" %}
|
||||||
|
{% bootstrap_form form layout="control" %}
|
||||||
|
{% endpropagated %}
|
||||||
|
<div class="form-group submit-group">
|
||||||
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
{% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -3,8 +3,9 @@ from django.conf.urls import include, url
|
|||||||
from pretix.multidomain import event_url
|
from pretix.multidomain import event_url
|
||||||
|
|
||||||
from .views import (
|
from .views import (
|
||||||
ReturnView, ScaReturnView, ScaView, applepay_association, oauth_disconnect,
|
OrganizerSettingsFormView, ReturnView, ScaReturnView, ScaView,
|
||||||
oauth_return, redirect_view, webhook,
|
applepay_association, oauth_disconnect, oauth_return, redirect_view,
|
||||||
|
webhook,
|
||||||
)
|
)
|
||||||
|
|
||||||
event_patterns = [
|
event_patterns = [
|
||||||
@@ -24,6 +25,8 @@ organizer_patterns = [
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/stripe/disconnect/',
|
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/stripe/disconnect/',
|
||||||
oauth_disconnect, name='oauth.disconnect'),
|
oauth_disconnect, name='oauth.disconnect'),
|
||||||
|
url(r'^control/organizer/(?P<organizer>[^/]+)/stripeconnect/',
|
||||||
|
OrganizerSettingsFormView.as_view(), name='settings.connect'),
|
||||||
url(r'^_stripe/webhook/$', webhook, name='webhook'),
|
url(r'^_stripe/webhook/$', webhook, name='webhook'),
|
||||||
url(r'^_stripe/oauth_return/$', oauth_return, name='oauth.return'),
|
url(r'^_stripe/oauth_return/$', oauth_return, name='oauth.return'),
|
||||||
url(r'^.well-known/apple-developer-merchantid-domain-association$', applepay_association, name='applepay.association'),
|
url(r'^.well-known/apple-developer-merchantid-domain-association$', applepay_association, name='applepay.association'),
|
||||||
|
|||||||
@@ -17,14 +17,20 @@ from django.views import View
|
|||||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
from django.views.generic import FormView
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
|
|
||||||
from pretix.base.models import Event, Order, OrderPayment, Quota
|
from pretix.base.models import Event, Order, OrderPayment, Organizer, Quota
|
||||||
from pretix.base.payment import PaymentException
|
from pretix.base.payment import PaymentException
|
||||||
from pretix.base.services.locking import LockTimeoutException
|
from pretix.base.services.locking import LockTimeoutException
|
||||||
from pretix.base.settings import GlobalSettingsObject
|
from pretix.base.settings import GlobalSettingsObject
|
||||||
from pretix.control.permissions import event_permission_required
|
from pretix.control.permissions import (
|
||||||
|
AdministratorPermissionRequiredMixin, event_permission_required,
|
||||||
|
)
|
||||||
|
from pretix.control.views.event import DecoupleMixin
|
||||||
|
from pretix.control.views.organizer import OrganizerDetailViewMixin
|
||||||
from pretix.multidomain.urlreverse import eventreverse
|
from pretix.multidomain.urlreverse import eventreverse
|
||||||
|
from pretix.plugins.stripe.forms import OrganizerStripeSettingsForm
|
||||||
from pretix.plugins.stripe.models import ReferencedStripeObject
|
from pretix.plugins.stripe.models import ReferencedStripeObject
|
||||||
from pretix.plugins.stripe.payment import StripeCC, StripeSettingsHolder
|
from pretix.plugins.stripe.payment import StripeCC, StripeSettingsHolder
|
||||||
from pretix.plugins.stripe.tasks import (
|
from pretix.plugins.stripe.tasks import (
|
||||||
@@ -580,3 +586,37 @@ class ScaReturnView(StripeOrderView, View):
|
|||||||
self.order.refresh_from_db()
|
self.order.refresh_from_db()
|
||||||
|
|
||||||
return render(request, 'pretixplugins/stripe/sca_return.html', {'order': self.order})
|
return render(request, 'pretixplugins/stripe/sca_return.html', {'order': self.order})
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizerSettingsFormView(DecoupleMixin, OrganizerDetailViewMixin, AdministratorPermissionRequiredMixin, FormView):
|
||||||
|
model = Organizer
|
||||||
|
permission = 'can_change_organizer_settings'
|
||||||
|
form_class = OrganizerStripeSettingsForm
|
||||||
|
template_name = 'pretixplugins/stripe/organizer_stripe.html'
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('plugins:stripe:settings.connect', kwargs={
|
||||||
|
'organizer': self.request.organizer.slug,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['obj'] = self.request.organizer
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
form = self.get_form()
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
if form.has_changed():
|
||||||
|
self.request.organizer.log_action(
|
||||||
|
'pretix.organizer.settings', user=self.request.user, data={
|
||||||
|
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
else:
|
||||||
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||||
|
return self.get(request)
|
||||||
|
|||||||
Reference in New Issue
Block a user