2020-09-04 17:56:10 +10:00
|
|
|
import hashlib
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import re
|
|
|
|
import urllib.parse
|
|
|
|
from collections import OrderedDict
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
|
|
import stripe
|
|
|
|
from django import forms
|
|
|
|
from django.conf import settings
|
|
|
|
from django.contrib import messages
|
|
|
|
from django.core import signing
|
|
|
|
from django.db import transaction
|
|
|
|
from django.http import HttpRequest
|
|
|
|
from django.template.loader import get_template
|
|
|
|
from django.urls import reverse
|
|
|
|
from django.utils.crypto import get_random_string
|
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from django.utils.timezone import now
|
|
|
|
from django.utils.translation import gettext, gettext_lazy as _, pgettext
|
|
|
|
from django_countries import countries
|
|
|
|
|
|
|
|
from pretix import __version__
|
|
|
|
from pretix.base.decimal import round_decimal
|
|
|
|
from pretix.base.models import Event, OrderPayment, OrderRefund, Quota
|
|
|
|
from pretix.base.payment import BasePaymentProvider, PaymentException
|
|
|
|
from pretix.base.plugins import get_all_plugins
|
|
|
|
from pretix.base.services.mail import SendMailException
|
|
|
|
from pretix.base.settings import SettingsSandbox
|
|
|
|
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
|
|
|
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
2020-09-04 17:58:25 +10:00
|
|
|
from pretix_stripe2.forms import StripeKeyValidator
|
|
|
|
from pretix_stripe2.models import (
|
|
|
|
ReferencedStripe2Object, RegisteredStripe2ApplePayDomain,
|
2020-09-04 17:56:10 +10:00
|
|
|
)
|
2020-09-04 17:58:25 +10:00
|
|
|
from pretix_stripe2.tasks import (
|
2020-09-04 17:56:10 +10:00
|
|
|
get_stripe_account_key, stripe_verify_domain,
|
|
|
|
)
|
|
|
|
|
2020-09-04 17:58:25 +10:00
|
|
|
logger = logging.getLogger('pretix_stripe2')
|
2020-09-04 17:56:10 +10:00
|
|
|
|
|
|
|
|
|
|
|
class StripeSettingsHolder(BasePaymentProvider):
|
|
|
|
identifier = 'stripe_settings'
|
2020-09-04 17:58:25 +10:00
|
|
|
verbose_name = _('Stripe2')
|
2020-09-04 17:56:10 +10:00
|
|
|
is_enabled = False
|
|
|
|
is_meta = True
|
|
|
|
|
|
|
|
def __init__(self, event: Event):
|
|
|
|
super().__init__(event)
|
2020-09-04 17:58:25 +10:00
|
|
|
self.settings = SettingsSandbox('payment', 'stripe2', event)
|
2020-09-04 17:56:10 +10:00
|
|
|
|
|
|
|
def settings_content_render(self, request):
|
2020-09-04 17:58:25 +10:00
|
|
|
return "<div class='alert alert-info'>%s<br /><code>%s</code></div>" % (
|
|
|
|
_('Please configure a <a href="https://dashboard.stripe.com/account/webhooks">Stripe Webhook</a> to '
|
|
|
|
'the following endpoint in order to automatically cancel orders when charges are refunded externally '
|
|
|
|
'and to process asynchronous payment methods like SOFORT.'),
|
|
|
|
build_global_uri('plugins:pretix_stripe2:webhook')
|
|
|
|
)
|
2020-09-04 17:56:10 +10:00
|
|
|
|
|
|
|
@property
|
|
|
|
def settings_form_fields(self):
|
|
|
|
if 'pretix_resellers' in [p.module for p in get_all_plugins()]:
|
|
|
|
moto_settings = [
|
|
|
|
('reseller_moto',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('Enable MOTO payments for resellers'),
|
|
|
|
help_text=(
|
|
|
|
_('Gated feature (needs to be enabled for your account by Stripe support first)') +
|
|
|
|
'<div class="alert alert-danger">%s</div>' % _(
|
|
|
|
'We can flag the credit card transaction you make through the reseller interface as MOTO '
|
|
|
|
'(Mail Order / Telephone Order), which will exempt them from Strong Customer '
|
|
|
|
'Authentication (SCA) requirements. However: By enabling this feature, you will need to '
|
|
|
|
'fill out yearly PCI-DSS self-assessment forms like the 40 page SAQ D. Please consult the '
|
|
|
|
'%s for further information on this subject.' %
|
|
|
|
'<a href="https://stripe.com/docs/security">{}</a>'.format(
|
|
|
|
_('Stripe Integration security guide')
|
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
|
|
|
required=False,
|
|
|
|
))
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
moto_settings = []
|
|
|
|
|
2020-09-04 17:58:25 +10:00
|
|
|
fields = [
|
|
|
|
('endpoint',
|
|
|
|
forms.ChoiceField(
|
|
|
|
label=_('Endpoint'),
|
|
|
|
initial='live',
|
|
|
|
choices=(
|
|
|
|
('live', pgettext('stripe', 'Live')),
|
|
|
|
('test', pgettext('stripe', 'Testing')),
|
|
|
|
),
|
|
|
|
help_text=_('If your event is in test mode, we will always use Stripe\'s test API, '
|
|
|
|
'regardless of this setting.')
|
|
|
|
)),
|
|
|
|
]
|
|
|
|
|
2020-09-04 17:56:10 +10:00
|
|
|
d = OrderedDict(
|
|
|
|
fields + [
|
|
|
|
('method_cc',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('Credit card payments'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_giropay',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('giropay'),
|
|
|
|
disabled=self.event.currency != 'EUR',
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_ideal',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('iDEAL'),
|
|
|
|
disabled=self.event.currency != 'EUR',
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_alipay',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('Alipay'),
|
|
|
|
disabled=self.event.currency not in ('EUR', 'AUD', 'CAD', 'GBP', 'HKD', 'JPY', 'NZD', 'SGD', 'USD'),
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_bancontact',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('Bancontact'),
|
|
|
|
disabled=self.event.currency != 'EUR',
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_sofort',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('SOFORT'),
|
|
|
|
disabled=self.event.currency != 'EUR',
|
|
|
|
help_text=(
|
|
|
|
_('Needs to be enabled in your Stripe account first.') +
|
|
|
|
'<div class="alert alert-warning">%s</div>' % _(
|
|
|
|
'Despite the name, Sofort payments via Stripe are <strong>not</strong> processed '
|
|
|
|
'instantly but might take up to <strong>14 days</strong> to be confirmed in some cases. '
|
|
|
|
'Please only activate this payment method if your payment term allows for this lag.'
|
|
|
|
)
|
|
|
|
),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_eps',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('EPS'),
|
|
|
|
disabled=self.event.currency != 'EUR',
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_multibanco',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('Multibanco'),
|
|
|
|
disabled=self.event.currency != 'EUR',
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_przelewy24',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('Przelewy24'),
|
|
|
|
disabled=self.event.currency not in ['EUR', 'PLN'],
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
('method_wechatpay',
|
|
|
|
forms.BooleanField(
|
|
|
|
label=_('WeChat Pay'),
|
|
|
|
disabled=self.event.currency not in ['AUD', 'CAD', 'EUR', 'GBP', 'HKD', 'JPY', 'SGD', 'USD'],
|
|
|
|
help_text=_('Needs to be enabled in your Stripe account first.'),
|
|
|
|
required=False,
|
|
|
|
)),
|
|
|
|
] + list(super().settings_form_fields.items()) + moto_settings
|
|
|
|
)
|
|
|
|
d.move_to_end('_enabled', last=False)
|
|
|
|
return d
|
|
|
|
|
|
|
|
|
|
|
|
class StripeMethod(BasePaymentProvider):
|
|
|
|
identifier = ''
|
|
|
|
method = ''
|
|
|
|
|
|
|
|
def __init__(self, event: Event):
|
|
|
|
super().__init__(event)
|
2020-09-04 17:58:25 +10:00
|
|
|
self.settings = SettingsSandbox('payment', 'stripe2', event)
|
2020-09-04 17:56:10 +10:00
|
|
|
|
|
|
|
@property
|
|
|
|
def test_mode_message(self):
|
2020-09-04 17:58:25 +10:00
|
|
|
if not (self.settings.get('endpoint', 'live') == 'live' and not self.event.testmode):
|
2020-09-04 17:56:10 +10:00
|
|
|
return mark_safe(
|
|
|
|
_('The Stripe plugin is operating in test mode. You can use one of <a {args}>many test '
|
|
|
|
'cards</a> to perform a transaction. No money will actually be transferred.').format(
|
|
|
|
args='href="https://stripe.com/docs/testing#cards" target="_blank"'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def settings_form_fields(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_enabled(self) -> bool:
|
|
|
|
return self.settings.get('_enabled', as_type=bool) and self.settings.get('method_{}'.format(self.method),
|
|
|
|
as_type=bool)
|
|
|
|
|
|
|
|
def payment_refund_supported(self, payment: OrderPayment) -> bool:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def payment_partial_refund_supported(self, payment: OrderPayment) -> bool:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def payment_prepare(self, request, payment):
|
|
|
|
return self.checkout_prepare(request, None)
|
|
|
|
|
|
|
|
def _amount_to_decimal(self, cents):
|
|
|
|
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
|
|
|
|
return round_decimal(float(cents) / (10 ** places), self.event.currency)
|
|
|
|
|
|
|
|
def _decimal_to_int(self, amount):
|
|
|
|
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
|
|
|
|
return int(amount * 10 ** places)
|
|
|
|
|
|
|
|
def _get_amount(self, payment):
|
|
|
|
return self._decimal_to_int(payment.amount)
|
|
|
|
|
|
|
|
def statement_descriptor(self, payment, length=22):
|
|
|
|
return '{event}-{code} {eventname}'.format(
|
|
|
|
event=self.event.slug.upper(),
|
|
|
|
code=payment.order.code,
|
|
|
|
eventname=re.sub('[^a-zA-Z0-9 ]', '', str(self.event.name))
|
|
|
|
)[:length]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def api_kwargs(self):
|
2020-09-04 17:58:25 +10:00
|
|
|
if self.settings.get('endpoint', 'live') == 'live' and not self.event.testmode:
|
2020-09-04 17:56:10 +10:00
|
|
|
kwargs = {
|
|
|
|
'api_key': self.settings.secret_key,
|
|
|
|
}
|
2020-09-04 17:58:25 +10:00
|
|
|
else:
|
|
|
|
kwargs = {
|
|
|
|
'api_key': self.settings.test_secret_key,
|
|
|
|
}
|
2020-09-04 17:56:10 +10:00
|
|
|
return kwargs
|
|
|
|
|
|
|
|
def _init_api(self):
|
|
|
|
stripe.api_version = '2019-05-16'
|
|
|
|
stripe.set_app_info(
|
|
|
|
"pretix",
|
|
|
|
partner_id="pp_partner_FSaz4PpKIur7Ox",
|
|
|
|
version=__version__,
|
|
|
|
url="https://pretix.eu"
|
|
|
|
)
|
|
|
|
|
|
|
|
def checkout_confirm_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_confirm.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {'request': request, 'event': self.event, 'settings': self.settings, 'provider': self}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def payment_can_retry(self, payment):
|
|
|
|
return self._is_still_available(order=payment.order)
|
|
|
|
|
|
|
|
def _charge_source(self, request, source, payment):
|
|
|
|
try:
|
|
|
|
params = {}
|
|
|
|
if not source.startswith('src_'):
|
|
|
|
params['statement_descriptor'] = self.statement_descriptor(payment)
|
|
|
|
params.update(self.api_kwargs)
|
|
|
|
charge = stripe.Charge.create(
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
source=source,
|
|
|
|
description='{event}-{code}'.format(
|
|
|
|
event=self.event.slug.upper(),
|
|
|
|
code=payment.order.code
|
|
|
|
),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
# TODO: Is this sufficient?
|
|
|
|
idempotency_key=str(self.event.id) + payment.order.code + source,
|
|
|
|
**params
|
|
|
|
)
|
|
|
|
except stripe.error.CardError as e:
|
|
|
|
if e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
logger.info('Stripe card error: %s' % str(err))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('Stripe reported an error with your card: %s') % err['message'])
|
|
|
|
|
|
|
|
except stripe.error.StripeError as e:
|
|
|
|
if e.json_body and 'error' in e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
|
|
|
|
'with us if this problem persists.'))
|
|
|
|
else:
|
2020-09-04 17:58:25 +10:00
|
|
|
ReferencedStripe2Object.objects.get_or_create(
|
2020-09-04 17:56:10 +10:00
|
|
|
reference=charge.id,
|
|
|
|
defaults={'order': payment.order, 'payment': payment}
|
|
|
|
)
|
|
|
|
if charge.status == 'succeeded' and charge.paid:
|
|
|
|
try:
|
|
|
|
payment.info = str(charge)
|
|
|
|
payment.confirm()
|
|
|
|
except Quota.QuotaExceededException as e:
|
|
|
|
raise PaymentException(str(e))
|
|
|
|
|
|
|
|
except SendMailException:
|
|
|
|
raise PaymentException(_('There was an error sending the confirmation mail.'))
|
|
|
|
elif charge.status == 'pending':
|
|
|
|
if request:
|
|
|
|
messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '
|
|
|
|
'payment completed.'))
|
|
|
|
payment.info = str(charge)
|
|
|
|
payment.state = OrderPayment.PAYMENT_STATE_PENDING
|
|
|
|
payment.save()
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
logger.info('Charge failed: %s' % str(charge))
|
|
|
|
payment.fail(info=str(charge))
|
|
|
|
raise PaymentException(_('Stripe reported an error: %s') % charge.failure_message)
|
|
|
|
|
|
|
|
def payment_pending_render(self, request, payment) -> str:
|
|
|
|
if payment.info:
|
|
|
|
payment_info = json.loads(payment.info)
|
|
|
|
else:
|
|
|
|
payment_info = None
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/pending.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'provider': self,
|
|
|
|
'order': payment.order,
|
|
|
|
'payment': payment,
|
|
|
|
'payment_info': payment_info,
|
|
|
|
'payment_hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest()
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def matching_id(self, payment: OrderPayment):
|
|
|
|
return payment.info_data.get("id", None)
|
|
|
|
|
|
|
|
def api_payment_details(self, payment: OrderPayment):
|
|
|
|
return {
|
|
|
|
"id": payment.info_data.get("id", None),
|
|
|
|
"payment_method": payment.info_data.get("payment_method", None)
|
|
|
|
}
|
|
|
|
|
|
|
|
def payment_control_render(self, request, payment) -> str:
|
|
|
|
if payment.info:
|
|
|
|
payment_info = json.loads(payment.info)
|
|
|
|
if 'amount' in payment_info:
|
|
|
|
payment_info['amount'] /= 10 ** settings.CURRENCY_PLACES.get(self.event.currency, 2)
|
|
|
|
else:
|
|
|
|
payment_info = None
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/control.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'payment_info': payment_info,
|
|
|
|
'payment': payment,
|
|
|
|
'method': self.method,
|
|
|
|
'provider': self,
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
@transaction.atomic()
|
|
|
|
def execute_refund(self, refund: OrderRefund):
|
|
|
|
self._init_api()
|
|
|
|
|
|
|
|
payment_info = refund.payment.info_data
|
|
|
|
OrderPayment.objects.select_for_update().get(pk=refund.payment.pk)
|
|
|
|
|
|
|
|
if not payment_info:
|
|
|
|
raise PaymentException(_('No payment information found.'))
|
|
|
|
|
|
|
|
try:
|
|
|
|
if payment_info['id'].startswith('pi_'):
|
|
|
|
chargeid = payment_info['charges']['data'][0]['id']
|
|
|
|
else:
|
|
|
|
chargeid = payment_info['id']
|
|
|
|
|
|
|
|
ch = stripe.Charge.retrieve(chargeid, **self.api_kwargs)
|
|
|
|
r = ch.refunds.create(
|
|
|
|
amount=self._get_amount(refund),
|
|
|
|
)
|
|
|
|
ch.refresh()
|
|
|
|
except (stripe.error.InvalidRequestError, stripe.error.AuthenticationError, stripe.error.APIConnectionError) \
|
|
|
|
as e:
|
|
|
|
if e.json_body and 'error' in e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and contact '
|
|
|
|
'support if the problem persists.'))
|
|
|
|
except stripe.error.StripeError as err:
|
|
|
|
logger.error('Stripe error: %s' % str(err))
|
|
|
|
raise PaymentException(_('Stripe returned an error'))
|
|
|
|
else:
|
|
|
|
refund.info = str(r)
|
|
|
|
if r.status in ('succeeded', 'pending'):
|
|
|
|
refund.done()
|
|
|
|
elif r.status in ('failed', 'canceled'):
|
|
|
|
refund.state = OrderRefund.REFUND_STATE_FAILED
|
|
|
|
refund.execution_date = now()
|
|
|
|
refund.save()
|
|
|
|
|
|
|
|
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
|
|
|
|
self._init_api()
|
|
|
|
try:
|
|
|
|
source = self._create_source(request, payment)
|
|
|
|
except stripe.error.StripeError as e:
|
|
|
|
if e.json_body and 'err' in e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
|
|
|
|
'with us if this problem persists.'))
|
|
|
|
|
2020-09-04 17:58:25 +10:00
|
|
|
ReferencedStripe2Object.objects.get_or_create(
|
2020-09-04 17:56:10 +10:00
|
|
|
reference=source.id,
|
|
|
|
defaults={'order': payment.order, 'payment': payment}
|
|
|
|
)
|
|
|
|
payment.info = str(source)
|
|
|
|
payment.state = OrderPayment.PAYMENT_STATE_PENDING
|
|
|
|
payment.save()
|
|
|
|
request.session['payment_stripe_order_secret'] = payment.order.secret
|
|
|
|
return self.redirect(request, source.redirect.url)
|
|
|
|
|
|
|
|
def redirect(self, request, url):
|
|
|
|
if request.session.get('iframe_session', False):
|
|
|
|
signer = signing.Signer(salt='safe-redirect')
|
|
|
|
return (
|
2020-09-04 17:58:25 +10:00
|
|
|
build_absolute_uri(request.event, 'plugins:pretix_stripe2:redirect') + '?url=' +
|
2020-09-04 17:56:10 +10:00
|
|
|
urllib.parse.quote(signer.sign(url))
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return str(url)
|
|
|
|
|
|
|
|
def shred_payment_info(self, obj: OrderPayment):
|
|
|
|
if not obj.info:
|
|
|
|
return
|
|
|
|
d = json.loads(obj.info)
|
|
|
|
new = {}
|
|
|
|
if 'source' in d:
|
|
|
|
new['source'] = {
|
|
|
|
'id': d['source'].get('id'),
|
|
|
|
'type': d['source'].get('type'),
|
|
|
|
'brand': d['source'].get('brand'),
|
|
|
|
'last4': d['source'].get('last4'),
|
|
|
|
'bank_name': d['source'].get('bank_name'),
|
|
|
|
'bank': d['source'].get('bank'),
|
|
|
|
'bic': d['source'].get('bic'),
|
|
|
|
'card': {
|
|
|
|
'brand': d['source'].get('card', {}).get('brand'),
|
|
|
|
'country': d['source'].get('card', {}).get('cuntry'),
|
|
|
|
'last4': d['source'].get('card', {}).get('last4'),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if 'amount' in d:
|
|
|
|
new['amount'] = d['amount']
|
|
|
|
if 'currency' in d:
|
|
|
|
new['currency'] = d['currency']
|
|
|
|
if 'status' in d:
|
|
|
|
new['status'] = d['status']
|
|
|
|
if 'id' in d:
|
|
|
|
new['id'] = d['id']
|
|
|
|
|
|
|
|
new['_shredded'] = True
|
|
|
|
obj.info = json.dumps(new)
|
|
|
|
obj.save(update_fields=['info'])
|
|
|
|
|
|
|
|
for le in obj.order.all_logentries().filter(
|
2020-09-04 17:58:25 +10:00
|
|
|
action_type="pretix_stripe2.event"
|
2020-09-04 17:56:10 +10:00
|
|
|
).exclude(data="", shredded=True):
|
|
|
|
d = le.parsed_data
|
|
|
|
if 'data' in d:
|
|
|
|
for k, v in list(d['data']['object'].items()):
|
|
|
|
if v not in ('reason', 'status', 'failure_message', 'object', 'id'):
|
|
|
|
d['data']['object'][k] = '█'
|
|
|
|
le.data = json.dumps(d)
|
|
|
|
le.shredded = True
|
|
|
|
le.save(update_fields=['data', 'shredded'])
|
|
|
|
|
|
|
|
|
|
|
|
class StripeCC(StripeMethod):
|
|
|
|
identifier = 'stripe'
|
|
|
|
verbose_name = _('Credit card via Stripe')
|
|
|
|
public_name = _('Credit card')
|
|
|
|
method = 'cc'
|
|
|
|
|
|
|
|
def payment_form_render(self, request, total) -> str:
|
|
|
|
account = get_stripe_account_key(self)
|
2020-09-04 17:58:25 +10:00
|
|
|
if not RegisteredStripe2ApplePayDomain.objects.filter(account=account, domain=request.host).exists():
|
2020-09-04 17:56:10 +10:00
|
|
|
stripe_verify_domain.apply_async(args=(self.event.pk, request.host))
|
|
|
|
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_cc.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'total': self._decimal_to_int(total),
|
|
|
|
'settings': self.settings,
|
|
|
|
'is_moto': self.is_moto(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return request.session.get('payment_stripe_payment_method_id', '') != ''
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
payment_method_id = request.POST.get('stripe_payment_method_id', '')
|
|
|
|
request.session['payment_stripe_payment_method_id'] = payment_method_id
|
|
|
|
request.session['payment_stripe_brand'] = request.POST.get('stripe_card_brand', '')
|
|
|
|
request.session['payment_stripe_last4'] = request.POST.get('stripe_card_last4', '')
|
|
|
|
if payment_method_id == '':
|
|
|
|
messages.warning(request, _('You may need to enable JavaScript for Stripe payments.'))
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
|
|
|
|
try:
|
|
|
|
return self._handle_payment_intent(request, payment)
|
|
|
|
finally:
|
|
|
|
del request.session['payment_stripe_payment_method_id']
|
|
|
|
|
|
|
|
def is_moto(self, request, payment=None) -> bool:
|
|
|
|
# We don't have a payment yet when checking if we should display the MOTO-flag
|
|
|
|
# However, before we execute the payment, we absolutely have to check if the request-SalesChannel as well as the
|
|
|
|
# order are tagged as a reseller-transaction. Else, a user with a valid reseller-session might be able to place
|
|
|
|
# a MOTO transaction trough the WebShop.
|
|
|
|
|
|
|
|
moto = self.settings.get('reseller_moto', False, as_type=bool) and \
|
|
|
|
request.sales_channel.identifier == 'resellers'
|
|
|
|
|
|
|
|
if payment:
|
|
|
|
return moto and payment.order.sales_channel == 'resellers'
|
|
|
|
|
|
|
|
return moto
|
|
|
|
|
|
|
|
def _handle_payment_intent(self, request, payment, intent=None):
|
|
|
|
self._init_api()
|
|
|
|
|
|
|
|
try:
|
|
|
|
if self.payment_is_valid_session(request):
|
|
|
|
params = {}
|
|
|
|
params.update(self.api_kwargs)
|
|
|
|
|
|
|
|
if self.is_moto(request, payment):
|
|
|
|
params.update({
|
|
|
|
'payment_method_options': {
|
|
|
|
'card': {
|
|
|
|
'moto': True
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
intent = stripe.PaymentIntent.create(
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
payment_method=request.session['payment_stripe_payment_method_id'],
|
|
|
|
confirmation_method='manual',
|
|
|
|
confirm=True,
|
|
|
|
description='{event}-{code}'.format(
|
|
|
|
event=self.event.slug.upper(),
|
|
|
|
code=payment.order.code
|
|
|
|
),
|
|
|
|
statement_descriptor=self.statement_descriptor(payment),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
# TODO: Is this sufficient?
|
|
|
|
idempotency_key=str(self.event.id) + payment.order.code + request.session['payment_stripe_payment_method_id'],
|
2020-09-04 17:58:25 +10:00
|
|
|
return_url=build_absolute_uri(self.event, 'plugins:pretix_stripe2:sca.return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
}),
|
|
|
|
**params
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
payment_info = json.loads(payment.info)
|
|
|
|
|
|
|
|
if 'id' in payment_info:
|
|
|
|
if not intent:
|
|
|
|
intent = stripe.PaymentIntent.retrieve(
|
|
|
|
payment_info['id'],
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
except stripe.error.CardError as e:
|
|
|
|
if e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
logger.info('Stripe card error: %s' % str(err))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('Stripe reported an error with your card: %s') % err['message'])
|
|
|
|
|
|
|
|
except stripe.error.StripeError as e:
|
|
|
|
if e.json_body and 'error' in e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
|
|
|
|
'with us if this problem persists.'))
|
|
|
|
else:
|
2020-09-04 17:58:25 +10:00
|
|
|
ReferencedStripe2Object.objects.get_or_create(
|
2020-09-04 17:56:10 +10:00
|
|
|
reference=intent.id,
|
|
|
|
defaults={'order': payment.order, 'payment': payment}
|
|
|
|
)
|
|
|
|
if intent.status == 'requires_action':
|
|
|
|
payment.info = str(intent)
|
|
|
|
payment.state = OrderPayment.PAYMENT_STATE_CREATED
|
|
|
|
payment.save()
|
2020-09-04 17:58:25 +10:00
|
|
|
return build_absolute_uri(self.event, 'plugins:pretix_stripe2:sca', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if intent.status == 'requires_confirmation':
|
|
|
|
payment.info = str(intent)
|
|
|
|
payment.state = OrderPayment.PAYMENT_STATE_CREATED
|
|
|
|
payment.save()
|
|
|
|
self._confirm_payment_intent(request, payment)
|
|
|
|
|
|
|
|
elif intent.status == 'succeeded' and intent.charges.data[-1].paid:
|
|
|
|
try:
|
|
|
|
payment.info = str(intent)
|
|
|
|
payment.confirm()
|
|
|
|
except Quota.QuotaExceededException as e:
|
|
|
|
raise PaymentException(str(e))
|
|
|
|
|
|
|
|
except SendMailException:
|
|
|
|
raise PaymentException(_('There was an error sending the confirmation mail.'))
|
|
|
|
elif intent.status == 'processing':
|
|
|
|
if request:
|
|
|
|
messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '
|
|
|
|
'payment completed.'))
|
|
|
|
payment.info = str(intent)
|
|
|
|
payment.state = OrderPayment.PAYMENT_STATE_PENDING
|
|
|
|
payment.save()
|
|
|
|
return
|
|
|
|
elif intent.status == 'requires_payment_method':
|
|
|
|
if request:
|
|
|
|
messages.warning(request, _('Your payment failed. Please try again.'))
|
|
|
|
payment.fail(info=str(intent))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
logger.info('Charge failed: %s' % str(intent))
|
|
|
|
payment.fail(info=str(intent))
|
|
|
|
raise PaymentException(_('Stripe reported an error: %s') % intent.last_payment_error.message)
|
|
|
|
|
|
|
|
def _confirm_payment_intent(self, request, payment):
|
|
|
|
self._init_api()
|
|
|
|
|
|
|
|
try:
|
|
|
|
payment_info = json.loads(payment.info)
|
|
|
|
|
|
|
|
intent = stripe.PaymentIntent.confirm(
|
|
|
|
payment_info['id'],
|
2020-09-04 17:58:25 +10:00
|
|
|
return_url=build_absolute_uri(self.event, 'plugins:pretix_stripe2:sca.return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
}),
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
payment.info = str(intent)
|
|
|
|
payment.save()
|
|
|
|
|
|
|
|
self._handle_payment_intent(request, payment)
|
|
|
|
except stripe.error.CardError as e:
|
|
|
|
if e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
logger.info('Stripe card error: %s' % str(err))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('Stripe reported an error with your card: %s') % err['message'])
|
|
|
|
except stripe.error.InvalidRequestError as e:
|
|
|
|
if e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
|
|
|
|
'with us if this problem persists.'))
|
|
|
|
|
|
|
|
|
|
|
|
class StripeGiropay(StripeMethod):
|
|
|
|
identifier = 'stripe_giropay'
|
|
|
|
verbose_name = _('giropay via Stripe')
|
|
|
|
public_name = _('giropay')
|
|
|
|
method = 'giropay'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'form': self.payment_form(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def payment_form_fields(self):
|
|
|
|
return OrderedDict([
|
|
|
|
('account', forms.CharField(label=_('Account holder'))),
|
|
|
|
])
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
try:
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='giropay',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
owner={
|
|
|
|
'name': request.session.get('payment_stripe_giropay_account') or gettext('unknown name')
|
|
|
|
},
|
|
|
|
statement_descriptor=self.statement_descriptor(payment, 35),
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
finally:
|
|
|
|
if 'payment_stripe_giropay_account' in request.session:
|
|
|
|
del request.session['payment_stripe_giropay_account']
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return (
|
|
|
|
request.session.get('payment_stripe_giropay_account', '') != ''
|
|
|
|
)
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
form = self.payment_form(request)
|
|
|
|
if form.is_valid():
|
|
|
|
request.session['payment_stripe_giropay_account'] = form.cleaned_data['account']
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
class StripeIdeal(StripeMethod):
|
|
|
|
identifier = 'stripe_ideal'
|
|
|
|
verbose_name = _('iDEAL via Stripe')
|
|
|
|
public_name = _('iDEAL')
|
|
|
|
method = 'ideal'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple_noform.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='ideal',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
statement_descriptor=self.statement_descriptor(payment),
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class StripeAlipay(StripeMethod):
|
|
|
|
identifier = 'stripe_alipay'
|
|
|
|
verbose_name = _('Alipay via Stripe')
|
|
|
|
public_name = _('Alipay')
|
|
|
|
method = 'alipay'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple_noform.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='alipay',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class StripeBancontact(StripeMethod):
|
|
|
|
identifier = 'stripe_bancontact'
|
|
|
|
verbose_name = _('Bancontact via Stripe')
|
|
|
|
public_name = _('Bancontact')
|
|
|
|
method = 'bancontact'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'form': self.payment_form(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def payment_form_fields(self):
|
|
|
|
return OrderedDict([
|
|
|
|
('account', forms.CharField(label=_('Account holder'), min_length=3)),
|
|
|
|
])
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
try:
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='bancontact',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
owner={
|
|
|
|
'name': request.session.get('payment_stripe_bancontact_account') or gettext('unknown name')
|
|
|
|
},
|
|
|
|
statement_descriptor=self.statement_descriptor(payment, 35),
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
finally:
|
|
|
|
if 'payment_stripe_bancontact_account' in request.session:
|
|
|
|
del request.session['payment_stripe_bancontact_account']
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return (
|
|
|
|
request.session.get('payment_stripe_bancontact_account', '') != ''
|
|
|
|
)
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
form = self.payment_form(request)
|
|
|
|
if form.is_valid():
|
|
|
|
request.session['payment_stripe_bancontact_account'] = form.cleaned_data['account']
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
class StripeSofort(StripeMethod):
|
|
|
|
identifier = 'stripe_sofort'
|
|
|
|
verbose_name = _('SOFORT via Stripe')
|
|
|
|
public_name = _('SOFORT')
|
|
|
|
method = 'sofort'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'form': self.payment_form(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def payment_form_fields(self):
|
|
|
|
return OrderedDict([
|
|
|
|
('bank_country', forms.ChoiceField(label=_('Country of your bank'), choices=(
|
|
|
|
('de', _('Germany')),
|
|
|
|
('at', _('Austria')),
|
|
|
|
('be', _('Belgium')),
|
|
|
|
('nl', _('Netherlands')),
|
|
|
|
('es', _('Spain'))
|
|
|
|
))),
|
|
|
|
])
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='sofort',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
statement_descriptor=self.statement_descriptor(payment, 35),
|
|
|
|
sofort={
|
|
|
|
'country': request.session.get('payment_stripe_sofort_bank_country'),
|
|
|
|
},
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return (
|
|
|
|
request.session.get('payment_stripe_sofort_bank_country', '') != ''
|
|
|
|
)
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
form = self.payment_form(request)
|
|
|
|
if form.is_valid():
|
|
|
|
request.session['payment_stripe_sofort_bank_country'] = form.cleaned_data['bank_country']
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def payment_can_retry(self, payment):
|
|
|
|
return payment.state != OrderPayment.PAYMENT_STATE_PENDING and self._is_still_available(order=payment.order)
|
|
|
|
|
|
|
|
|
|
|
|
class StripeEPS(StripeMethod):
|
|
|
|
identifier = 'stripe_eps'
|
|
|
|
verbose_name = _('EPS via Stripe')
|
|
|
|
public_name = _('EPS')
|
|
|
|
method = 'eps'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'form': self.payment_form(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def payment_form_fields(self):
|
|
|
|
return OrderedDict([
|
|
|
|
('account', forms.CharField(label=_('Account holder'))),
|
|
|
|
])
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
try:
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='eps',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
owner={
|
|
|
|
'name': request.session.get('payment_stripe_eps_account') or gettext('unknown name')
|
|
|
|
},
|
|
|
|
statement_descriptor=self.statement_descriptor(payment),
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
finally:
|
|
|
|
if 'payment_stripe_eps_account' in request.session:
|
|
|
|
del request.session['payment_stripe_eps_account']
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return (
|
|
|
|
request.session.get('payment_stripe_eps_account', '') != ''
|
|
|
|
)
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
form = self.payment_form(request)
|
|
|
|
if form.is_valid():
|
|
|
|
request.session['payment_stripe_eps_account'] = form.cleaned_data['account']
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
class StripeMultibanco(StripeMethod):
|
|
|
|
identifier = 'stripe_multibanco'
|
|
|
|
verbose_name = _('Multibanco via Stripe')
|
|
|
|
public_name = _('Multibanco')
|
|
|
|
method = 'multibanco'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple_noform.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'form': self.payment_form(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='multibanco',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
owner={
|
|
|
|
'email': payment.order.email
|
|
|
|
},
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class StripePrzelewy24(StripeMethod):
|
|
|
|
identifier = 'stripe_przelewy24'
|
|
|
|
verbose_name = _('Przelewy24 via Stripe')
|
|
|
|
public_name = _('Przelewy24')
|
|
|
|
method = 'przelewy24'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple_noform.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'form': self.payment_form(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='p24',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
owner={
|
|
|
|
'email': payment.order.email
|
|
|
|
},
|
|
|
|
statement_descriptor=self.statement_descriptor(payment, 35),
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class StripeWeChatPay(StripeMethod):
|
|
|
|
identifier = 'stripe_wechatpay'
|
|
|
|
verbose_name = _('WeChat Pay via Stripe')
|
|
|
|
public_name = _('WeChat Pay')
|
|
|
|
method = 'wechatpay'
|
|
|
|
|
|
|
|
def payment_form_render(self, request) -> str:
|
2020-09-04 17:58:25 +10:00
|
|
|
template = get_template('pretixplugins/stripe2/checkout_payment_form_simple_noform.html')
|
2020-09-04 17:56:10 +10:00
|
|
|
ctx = {
|
|
|
|
'request': request,
|
|
|
|
'event': self.event,
|
|
|
|
'settings': self.settings,
|
|
|
|
'form': self.payment_form(request)
|
|
|
|
}
|
|
|
|
return template.render(ctx)
|
|
|
|
|
|
|
|
def _create_source(self, request, payment):
|
|
|
|
source = stripe.Source.create(
|
|
|
|
type='wechat',
|
|
|
|
amount=self._get_amount(payment),
|
|
|
|
currency=self.event.currency.lower(),
|
|
|
|
metadata={
|
|
|
|
'order': str(payment.order.id),
|
|
|
|
'event': self.event.id,
|
|
|
|
'code': payment.order.code
|
|
|
|
},
|
|
|
|
statement_descriptor=self.statement_descriptor(payment, 32),
|
|
|
|
redirect={
|
2020-09-04 17:58:25 +10:00
|
|
|
'return_url': build_absolute_uri(self.event, 'plugins:pretix_stripe2:return', kwargs={
|
2020-09-04 17:56:10 +10:00
|
|
|
'order': payment.order.code,
|
|
|
|
'payment': payment.pk,
|
|
|
|
'hash': hashlib.sha1(payment.order.secret.lower().encode()).hexdigest(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
**self.api_kwargs
|
|
|
|
)
|
|
|
|
return source
|
|
|
|
|
|
|
|
def payment_is_valid_session(self, request):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
|
|
|
|
self._init_api()
|
|
|
|
try:
|
|
|
|
source = self._create_source(request, payment)
|
|
|
|
except stripe.error.StripeError as e:
|
|
|
|
if e.json_body and 'err' in e.json_body:
|
|
|
|
err = e.json_body['error']
|
|
|
|
logger.exception('Stripe error: %s' % str(err))
|
|
|
|
else:
|
|
|
|
err = {'message': str(e)}
|
|
|
|
logger.exception('Stripe error: %s' % str(e))
|
|
|
|
payment.fail(info={
|
|
|
|
'error': True,
|
|
|
|
'message': err['message'],
|
|
|
|
})
|
|
|
|
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
|
|
|
|
'with us if this problem persists.'))
|
|
|
|
|
2020-09-04 17:58:25 +10:00
|
|
|
ReferencedStripe2Object.objects.get_or_create(
|
2020-09-04 17:56:10 +10:00
|
|
|
reference=source.id,
|
|
|
|
defaults={'order': payment.order, 'payment': payment}
|
|
|
|
)
|
|
|
|
payment.info = str(source)
|
|
|
|
payment.save()
|
|
|
|
|
|
|
|
return eventreverse(request.event, 'presale:event.order', kwargs={
|
|
|
|
'order': payment.order.code,
|
|
|
|
'secret': payment.order.secret
|
|
|
|
})
|