mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
241 lines
9.7 KiB
Python
241 lines
9.7 KiB
Python
import json
|
|
import logging
|
|
from collections import OrderedDict
|
|
|
|
import paypalrestsdk
|
|
from django import forms
|
|
from django.contrib import messages
|
|
from django.template.loader import get_template
|
|
from django.utils.translation import ugettext as __, ugettext_lazy as _
|
|
|
|
from pretix.base.models import Quota
|
|
from pretix.base.payment import BasePaymentProvider
|
|
from pretix.base.services.orders import mark_order_paid, mark_order_refunded
|
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
|
|
|
logger = logging.getLogger('pretix.plugins.paypal')
|
|
|
|
|
|
class Paypal(BasePaymentProvider):
|
|
identifier = 'paypal'
|
|
verbose_name = _('PayPal')
|
|
payment_form_fields = OrderedDict([
|
|
])
|
|
|
|
@property
|
|
def settings_form_fields(self):
|
|
return OrderedDict(
|
|
list(super().settings_form_fields.items()) + [
|
|
('endpoint',
|
|
forms.ChoiceField(
|
|
label=_('Endpoint'),
|
|
initial='live',
|
|
choices=(
|
|
('live', 'Live'),
|
|
('sandbox', 'Sandbox'),
|
|
),
|
|
)),
|
|
('client_id',
|
|
forms.CharField(
|
|
label=_('Client ID'),
|
|
)),
|
|
('secret',
|
|
forms.CharField(
|
|
label=_('Secret'),
|
|
))
|
|
]
|
|
)
|
|
|
|
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'))
|
|
|
|
def payment_is_valid_session(self, request):
|
|
return (request.session.get('payment_paypal_id', '') != ''
|
|
and request.session.get('payment_paypal_payer', '') != '')
|
|
|
|
def payment_form_render(self, request) -> str:
|
|
template = get_template('pretixplugins/paypal/checkout_payment_form.html')
|
|
ctx = {'request': request, 'event': self.event, 'settings': self.settings}
|
|
return template.render(ctx)
|
|
|
|
def checkout_prepare(self, request, cart):
|
|
self.init_api()
|
|
items = []
|
|
for cp in cart['positions']:
|
|
items.append({
|
|
"name": str(cp.item.name),
|
|
"description": str(cp.variation) if cp.variation else "",
|
|
"quantity": cp.count,
|
|
"price": str(cp.price),
|
|
"currency": request.event.currency
|
|
})
|
|
if cart['payment_fee']:
|
|
items.append({
|
|
"name": __('Payment method fee'),
|
|
"description": "",
|
|
"quantity": 1,
|
|
"currency": request.event.currency,
|
|
"price": str(cart['payment_fee'])
|
|
})
|
|
payment = paypalrestsdk.Payment({
|
|
'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": items
|
|
},
|
|
"amount": {
|
|
"currency": request.event.currency,
|
|
"total": str(cart['total'])
|
|
},
|
|
"description": __('Event tickets for %s') % request.event.name
|
|
}
|
|
]
|
|
})
|
|
return self._create_payment(request, payment)
|
|
|
|
def _create_payment(self, request, payment):
|
|
try:
|
|
if payment.create():
|
|
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
|
|
request.session['payment_paypal_event'] = self.event.id
|
|
for link in payment.links:
|
|
if link.method == "REDIRECT" and link.rel == "approval_url":
|
|
return str(link.href)
|
|
else:
|
|
messages.error(request, _('We had trouble communicating with PayPal'))
|
|
logger.error('Error on creating payment: ' + str(payment.error))
|
|
except Exception as e:
|
|
messages.error(request, _('We had trouble communicating with PayPal'))
|
|
logger.error('Error on creating payment: ' + str(e))
|
|
|
|
def checkout_confirm_render(self, request) -> str:
|
|
"""
|
|
Returns the HTML that should be displayed when the user selected this provider
|
|
on the 'confirm order' page.
|
|
"""
|
|
template = get_template('pretixplugins/paypal/checkout_payment_confirm.html')
|
|
ctx = {'request': request, 'event': self.event, 'settings': self.settings}
|
|
return template.render(ctx)
|
|
|
|
def payment_perform(self, request, order) -> str:
|
|
"""
|
|
Will be called if the user submitted his order successfully to initiate the
|
|
payment process.
|
|
|
|
It should return a custom redirct URL, if you need special behaviour, or None to
|
|
continue with default behaviour.
|
|
|
|
On errors, it should use Django's message framework to display an error message
|
|
to the user (or the normal form validation error messages).
|
|
|
|
:param order: The order object
|
|
"""
|
|
if (request.session.get('payment_paypal_id', '') == ''
|
|
or request.session.get('payment_paypal_payer', '') == ''):
|
|
messages.error(request, _('We were unable to process your payment. See below for details on how to '
|
|
'proceed.'))
|
|
|
|
self.init_api()
|
|
payment = paypalrestsdk.Payment.find(request.session.get('payment_paypal_id'))
|
|
if str(payment.transactions[0].amount.total) != str(order.total) or payment.transactions[0].amount.currency != \
|
|
self.event.currency:
|
|
messages.error(request, _('We were unable to process your payment. See below for details on how to '
|
|
'proceed.'))
|
|
logger.error('Value mismatch: Order %s vs payment %s' % (order.id, str(payment)))
|
|
return
|
|
|
|
return self._execute_payment(payment, request, order)
|
|
|
|
def _execute_payment(self, payment, request, order):
|
|
payment.execute({"payer_id": request.session.get('payment_paypal_payer')})
|
|
|
|
if payment.state == 'pending':
|
|
messages.warning(request, _('PayPal has not yet approved the payment. We will inform you as soon as the '
|
|
'payment completed.'))
|
|
order.payment_info = json.dumps(payment.to_dict())
|
|
order.save()
|
|
return
|
|
|
|
if payment.state != 'approved':
|
|
messages.error(request, _('We were unable to process your payment. See below for details on how to '
|
|
'proceed.'))
|
|
logger.error('Invalid state: %s' % str(payment))
|
|
return
|
|
|
|
try:
|
|
mark_order_paid(order, 'paypal', json.dumps(payment.to_dict()))
|
|
except Quota.QuotaExceededException as e:
|
|
messages.error(request, str(e))
|
|
return None
|
|
|
|
def order_pending_render(self, request, order) -> str:
|
|
retry = True
|
|
try:
|
|
if order.payment_info and json.loads(order.payment_info)['state'] == 'pending':
|
|
retry = False
|
|
except KeyError:
|
|
pass
|
|
template = get_template('pretixplugins/paypal/pending.html')
|
|
ctx = {'request': request, 'event': self.event, 'settings': self.settings,
|
|
'retry': retry, 'order': order}
|
|
return template.render(ctx)
|
|
|
|
def order_control_render(self, request, order) -> str:
|
|
if order.payment_info:
|
|
payment_info = json.loads(order.payment_info)
|
|
else:
|
|
payment_info = None
|
|
template = get_template('pretixplugins/paypal/control.html')
|
|
ctx = {'request': request, 'event': self.event, 'settings': self.settings,
|
|
'payment_info': payment_info, 'order': order}
|
|
return template.render(ctx)
|
|
|
|
def order_control_refund_render(self, order) -> str:
|
|
return '<div class="alert alert-info">%s</div>' % _('The money will be automatically refunded.')
|
|
|
|
def order_control_refund_perform(self, request, order) -> "bool|str":
|
|
self.init_api()
|
|
|
|
if order.payment_info:
|
|
payment_info = json.loads(order.payment_info)
|
|
else:
|
|
payment_info = None
|
|
|
|
if not payment_info:
|
|
mark_order_refunded(order)
|
|
messages.warning(request, _('We were unable to transfer the money back automatically. '
|
|
'Please get in touch with the customer and transfer it back manually.'))
|
|
return
|
|
|
|
for res in payment_info['transactions'][0]['related_resources']:
|
|
for k, v in res.items():
|
|
if k == 'sale':
|
|
sale = paypalrestsdk.Sale.find(v['id'])
|
|
break
|
|
|
|
refund = sale.refund({})
|
|
if not refund.success():
|
|
mark_order_refunded(order, user=request.user)
|
|
messages.warning(request, _('We were unable to transfer the money back automatically. '
|
|
'Please get in touch with the customer and transfer it back manually.'))
|
|
else:
|
|
sale = paypalrestsdk.Payment.find(payment_info['id'])
|
|
order = mark_order_refunded(order)
|
|
order.payment_info = json.dumps(sale.to_dict())
|
|
order.save()
|