Stripe connect: Allow to set application fee

This commit is contained in:
Raphael Michel
2019-10-13 13:14:22 +02:00
parent b9a911dd97
commit 02c81e0fa7
7 changed files with 148 additions and 9 deletions

View File

@@ -1,6 +1,8 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from pretix.base.forms import SettingsForm
class StripeKeyValidator:
def __init__(self, prefix):
@@ -21,3 +23,18 @@ class StripeKeyValidator:
'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,
)

View File

@@ -3,6 +3,7 @@ import json
import logging
import urllib.parse
from collections import OrderedDict
from decimal import Decimal
import stripe
from django import forms
@@ -256,6 +257,20 @@ class StripeMethod(BasePaymentProvider):
def _get_amount(self, payment):
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
def api_kwargs(self):
if self.settings.connect_client_id and self.settings.connect_user_id:
@@ -301,6 +316,7 @@ class StripeMethod(BasePaymentProvider):
code=payment.order.code
)[:22]
params.update(self.api_kwargs)
params.update(self._connect_kwargs(payment))
charge = stripe.Charge.create(
amount=self._get_amount(payment),
currency=self.event.currency.lower(),
@@ -612,6 +628,9 @@ class StripeCC(StripeMethod):
try:
if self.payment_is_valid_session(request):
params = {}
params.update(self._connect_kwargs(payment))
params.update(self.api_kwargs)
intent = stripe.PaymentIntent.create(
amount=self._get_amount(payment),
currency=self.event.currency.lower(),
@@ -638,7 +657,7 @@ class StripeCC(StripeMethod):
'payment': payment.pk,
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
}),
**self.api_kwargs
**params
)
else:
payment_info = json.loads(payment.info)

View File

@@ -4,7 +4,7 @@ from collections import OrderedDict
from django import forms
from django.dispatch import receiver
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 pretix.base.settings import settings_hierarkey
@@ -12,6 +12,7 @@ from pretix.base.signals import (
logentry_display, register_global_settings, register_payment_providers,
requiredaction_display,
)
from pretix.control.signals import nav_organizer
from pretix.plugins.stripe.forms import StripeKeyValidator
from pretix.presale.signals import html_head
@@ -121,6 +122,18 @@ def register_global_settings(sender, **kwargs):
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}
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 []

View File

@@ -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 %}

View File

@@ -3,8 +3,9 @@ from django.conf.urls import include, url
from pretix.multidomain import event_url
from .views import (
ReturnView, ScaReturnView, ScaView, applepay_association, oauth_disconnect,
oauth_return, redirect_view, webhook,
OrganizerSettingsFormView, ReturnView, ScaReturnView, ScaView,
applepay_association, oauth_disconnect, oauth_return, redirect_view,
webhook,
)
event_patterns = [
@@ -24,6 +25,8 @@ organizer_patterns = [
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/stripe/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/oauth_return/$', oauth_return, name='oauth.return'),
url(r'^.well-known/apple-developer-merchantid-domain-association$', applepay_association, name='applepay.association'),

View File

@@ -17,14 +17,20 @@ from django.views import View
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 django.views.generic import FormView
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.services.locking import LockTimeoutException
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.plugins.stripe.forms import OrganizerStripeSettingsForm
from pretix.plugins.stripe.models import ReferencedStripeObject
from pretix.plugins.stripe.payment import StripeCC, StripeSettingsHolder
from pretix.plugins.stripe.tasks import (
@@ -580,3 +586,37 @@ class ScaReturnView(StripeOrderView, View):
self.order.refresh_from_db()
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)