diff --git a/payment_paypal/indico_payment_paypal/controllers.py b/payment_paypal/indico_payment_paypal/controllers.py
new file mode 100644
index 0000000..99452be
--- /dev/null
+++ b/payment_paypal/indico_payment_paypal/controllers.py
@@ -0,0 +1,101 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
+#
+# Indico is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# Indico is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Indico; if not, see .
+
+from itertools import chain
+from urllib import urlencode
+from urllib2 import urlopen
+
+import werkzeug.datastructures
+from werkzeug.exceptions import BadRequest
+from flask import request, jsonify
+from flask_pluginengine import current_plugin
+
+from MaKaC.conference import ConferenceHolder
+from MaKaC.webinterface.rh.base import RH
+from indico.core.db import db
+from indico.modules.payment.models.transactions import PaymentTransaction, TransactionStatus
+from indico.util import json
+
+IPN_VERIFY_EXTRA_PARAMS = (('cmd', '_notify-validate'),)
+
+
+class RHPaymentEventNotify(RH):
+ """Process the notification sent by the PayPal"""
+
+ def _checkParams(self):
+ self.registrant = None
+ self.event = ConferenceHolder().getById(request.view_args['confId'])
+ self.registrant = self.event.getRegistrantById(request.args['registrantId'])
+ if self.registrant is None:
+ # TODO: which exception to raise?
+ raise BadRequest
+ if self.registrant.getRandomId() != request.args['authkey']:
+ # TODO: which exception to raise?
+ raise BadRequest
+
+ def _process(self):
+ current_plugin.logger.warning("Verifying...")
+ if not self._verify_business():
+ return
+ request.parameter_storage_class = werkzeug.datastructures.ImmutableOrderedMultiDict
+ verify_params = chain(request.form.iteritems(), IPN_VERIFY_EXTRA_PARAMS)
+ verify_string = urlencode(list(verify_params))
+ response = urlopen(current_plugin.settings.get('url'), data=verify_string)
+ if response.read() != 'VERIFIED':
+ current_plugin.logger.warning("Paypal IPN string {arg} did not validate".format(arg=verify_string))
+ return
+ current_plugin.logger.warning("Transaction was verified successfully")
+ # TODO: Check if the total amount has changed
+ if self._is_transaction_duplicated():
+ current_plugin.logger.error("Transaction was skipped because it is duplicated. Data received: {}"
+ .format(request.form))
+ return
+ # Now it's OK to store the transaction
+ new_transaction = PaymentTransaction(event_id=request.view_args['confId'],
+ registrant_id=request.args['registrantId'],
+ amount=request.form.get('mc_gross'),
+ currency=request.form.get('mc_currency'),
+ provider='paypal',
+ data=json.dumps(request.form))
+
+ # Still missing: Canceled_Reversal, Created, Expired, Pending, Processed, Voided
+ if request.form.get('payment_status') == 'Completed':
+ new_transaction.status = TransactionStatus.successful
+ elif request.form.get('payment_status') == 'Failed':
+ new_transaction.status = TransactionStatus.failed
+ elif request.form.get('payment_status') == 'Denied':
+ new_transaction.status = TransactionStatus.rejected # The comment in transactions.py is confusing
+ elif request.form.get('payment_status') == 'Reversed' or request.form.get('payment_status') == 'Refunded':
+ new_transaction.status = TransactionStatus.cancelled
+ else:
+ current_plugin.logger.error("Payment status '{}' cannot be recognized"
+ .format(request.form.get('payment_status')))
+ current_plugin.logger.error("Failure in storing the transaction. Data received: {}".format(request.form))
+ return
+ db.session.add(new_transaction)
+ db.session.flush()
+
+ return jsonify({'status': 'complete'})
+
+ def _verify_business(self):
+ return current_plugin.event_settings.get(self.event, 'business') == request.form.get('business')
+
+ def _is_transaction_duplicated(self):
+ transaction = PaymentTransaction.find_latest_for_registrant(self.registrant)
+ if not transaction:
+ return False
+ return (transaction.data.payment_status == request.form.get('payment_status') and
+ transaction.data.txn_id == request.form.get('txn_id'))
diff --git a/payment_paypal/indico_payment_paypal/plugin.py b/payment_paypal/indico_payment_paypal/plugin.py
index e445c18..67ac47f 100644
--- a/payment_paypal/indico_payment_paypal/plugin.py
+++ b/payment_paypal/indico_payment_paypal/plugin.py
@@ -20,8 +20,9 @@ from wtforms.fields.core import StringField
from wtforms.fields.html5 import URLField
from wtforms.validators import DataRequired
-from indico.core.plugins import IndicoPlugin
+from indico.core.plugins import IndicoPlugin, IndicoPluginBlueprint
from indico.modules.payment import PaymentPluginMixin, PaymentPluginSettingsFormBase, PaymentEventSettingsFormBase
+from indico_payment_paypal.controllers import RHPaymentEventNotify
from indico.util.i18n import _
@@ -43,3 +44,12 @@ class PaypalPaymentPlugin(PaymentPluginMixin, IndicoPlugin):
settings_form = PluginSettingsForm
event_settings_form = EventSettingsForm
default_settings = {'method_name': 'PayPal'}
+
+ def get_blueprints(self):
+ return blueprint
+
+
+blueprint = IndicoPluginBlueprint('payment_paypal', __name__, url_prefix='/event//registration/payment')
+
+#: Used by PayPal to send asynchronously a notification for the transaction (pending, successful, etc)
+blueprint.add_url_rule('/notify/paypal', 'notify', RHPaymentEventNotify, methods=('GET', 'POST'))
diff --git a/payment_paypal/indico_payment_paypal/templates/event_payment_form.html b/payment_paypal/indico_payment_paypal/templates/event_payment_form.html
index e573293..11ee53f 100644
--- a/payment_paypal/indico_payment_paypal/templates/event_payment_form.html
+++ b/payment_paypal/indico_payment_paypal/templates/event_payment_form.html
@@ -18,7 +18,7 @@ Clicking on the Pay now button you will get redirected to the P
-
+