Add payment provider PayPal

This commit is contained in:
Raphael Michel
2015-03-15 17:33:50 +01:00
parent d397c03fde
commit a67e09215b
10 changed files with 372 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from pretix.base.plugins import PluginType
class PaypalApp(AppConfig):
name = 'pretix.plugins.paypal'
verbose_name = _("Stripe")
class PretixPluginMeta:
type = PluginType.PAYMENT
name = _("PayPal")
author = _("the pretix team")
version = '1.0.0'
description = _("This plugin allows you to receive payments via PayPal")
def ready(self):
from . import signals # NOQA
default_app_config = 'pretix.plugins.paypal.PaypalApp'

View File

@@ -0,0 +1,188 @@
from collections import OrderedDict
import json
import logging
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.template import Context
from django.template.loader import get_template
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as __
from django import forms
import paypalrestsdk
from pretix.base.payment import BasePaymentProvider
logger = logging.getLogger('pretix.plugins.paypal')
class Paypal(BasePaymentProvider):
identifier = 'paypal'
verbose_name = _('PayPal')
settings_form_fields = OrderedDict([
('client_id',
forms.CharField(
label=_('Client ID'),
required=False
)),
('endpoint',
forms.CharField(
label=_('Endpoint'),
initial='api.paypal.com',
required=False
)),
('secret',
forms.CharField(
label=_('Secret'),
required=False
))
])
checkout_form_fields = OrderedDict([
])
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 checkout_is_valid_session(self, request):
return (request.session.get('payment_paypal_id', '') != ''
and request.session.get('payment_paypal_payer', '') != '')
def checkout_form_render(self, request) -> str:
template = get_template('pretixplugins/paypal/checkout_payment_form.html')
ctx = Context({'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": 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": request.build_absolute_uri(reverse('plugins:paypal.return')),
"cancel_url": request.build_absolute_uri(reverse('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):
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))
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 = Context({'request': request, 'event': self.event, 'settings': self.settings})
return template.render(ctx)
def checkout_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[1].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 = order.clone()
order.payment_info = str(payment)
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
order.mark_paid('paypal', str(payment))
messages.success(request, _('We successfully received your payment. Thank you!'))
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 = Context({'request': request, 'event': self.event, 'settings': self.settings,
'retry': retry, 'order': order})
return template.render(ctx)

View File

@@ -0,0 +1,10 @@
from django.dispatch import receiver
from pretix.base.signals import register_payment_providers
from .payment import Paypal
@receiver(register_payment_providers)
def register_payment_provider(sender, **kwargs):
return Paypal

View File

@@ -0,0 +1,6 @@
{% load i18n %}
<p>{% blocktrans trimmed %}
The total amount listed above will be withdrawn from your PayPal acocunt after the
confirmation of your purchase.
{% endblocktrans %}</p>

View File

@@ -0,0 +1,6 @@
{% load i18n %}
<p>{% blocktrans trimmed %}
After you clicked continue, we will redirect you to PayPal to fill in your payment
details. You will then be redirected back here to review and confirm your order.
{% endblocktrans %}</p>

View File

@@ -0,0 +1,15 @@
{% load i18n %}
{% if retry %}
<p>{% blocktrans trimmed %}
Our attempt to execute your Payment via PayPal has failed. Please try again or contact us.
{% endblocktrans %}</p>
<p>
<a href="{% url "plugins:paypal.retry" order=order.code %}" class="btn btn-default">{% trans "Try again" %}</a>
</p>
{% else %}
<p>{% blocktrans trimmed %}
We're waiting for an answer from PayPal regarding your payment. Please contact us, if this
takes more than a few hours.
{% endblocktrans %}</p>
{% endif %}

View File

@@ -0,0 +1,12 @@
from django.conf.urls import url, include
from .views import success, abort, retry
urlpatterns = [
url(r'^paypal/', include([
url(r'^abort/$', abort, name='paypal.abort'),
url(r'^return/$', success, name='paypal.return'),
url(r'^retry/(?P<order>[^/]+)/', retry, name='paypal.retry')
])),
]

View File

@@ -0,0 +1,111 @@
import logging
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
import paypalrestsdk
from pretix.base.models import Event, Order
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as __
from pretix.base.settings import SettingsSandbox
from pretix.plugins.paypal.payment import Paypal
logger = logging.getLogger('pretix.plugins.paypal')
@login_required
def success(request):
pid = request.GET.get('paymentId')
token = request.GET.get('token')
payer = request.GET.get('PayerID')
if pid == request.session['payment_paypal_id']:
request.session['payment_paypal_token'] = token
request.session['payment_paypal_payer'] = payer
try:
event = Event.objects.current.get(identity=request.session['payment_paypal_event'])
return redirect(reverse('presale:event.checkout.confirm', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
}))
except Event.DoesNotExist:
pass # TODO: Handle this
else:
pass # TODO: Handle this
@login_required
def abort(request):
messages.error(request, _('It looks like you cancelled the PayPal payment'))
try:
event = Event.objects.current.get(identity=request.session['payment_paypal_event'])
return redirect(reverse('presale:event.checkout.payment', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
}))
except Event.DoesNotExist:
pass # TODO: Handle this
@login_required
def retry(request, order):
try:
order = Order.objects.current.get(
user=request.user,
code=order,
)
except Order.DoesNotExist:
return # TODO: Handle this
provider = Paypal(order.event)
provider.init_api()
if 'token' in request.GET:
if 'PayerID' in request.GET:
payment = paypalrestsdk.Payment.find(request.session.get('payment_paypal_id'))
provider._execute_payment(payment, request, order)
else:
messages.error(request, _('It looks like you cancelled the PayPal payment'))
else:
payment = paypalrestsdk.Payment({
'intent': 'sale',
'payer': {
"payment_method": "paypal",
},
"redirect_urls": {
"return_url": request.build_absolute_uri(reverse('plugins:paypal.retry', kwargs={
'order': order.code
})),
"cancel_url": request.build_absolute_uri(reverse('plugins:paypal.retry', kwargs={
'order': order.code
})),
},
"transactions": [
{
"item_list": {
"items": [
{
"name": 'Order %s' % order.code,
"quantity": 1,
"price": str(order.total),
"currency": order.event.currency
}
]
},
"amount": {
"currency": order.event.currency,
"total": str(order.total)
},
"description": __('Event tickets for %s') % order.event.name
}
]
})
resp = provider._create_payment(request, payment)
if resp:
return redirect(resp)
return redirect(reverse('presale:event.order', kwargs={
'event': order.event.slug,
'organizer': order.event.organizer.slug,
'order': order.code,
}))

View File

@@ -48,6 +48,7 @@ INSTALLED_APPS = (
'pretix.plugins.timerestriction', 'pretix.plugins.timerestriction',
'pretix.plugins.banktransfer', 'pretix.plugins.banktransfer',
'pretix.plugins.stripe', 'pretix.plugins.stripe',
'pretix.plugins.paypal',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (

View File

@@ -0,0 +1,2 @@
paypalrestsdk