Define blueprint and RH for notification

This commit is contained in:
Ilias Trichopoulos 2014-12-10 14:36:24 +01:00 committed by Adrian Moennich
parent a5c8628663
commit 6cfff0becd
3 changed files with 113 additions and 2 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
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'))

View File

@ -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/<confId>/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'))

View File

@ -18,7 +18,7 @@ Clicking on the <strong>Pay now</strong> button you will get redirected to the P
<input type="hidden" name="charset" value="utf-8">
<input type="hidden" name="return" value="{{ url_for('payment.event_payment_confirm', event, _external=True) }}">
<input type="hidden" name="cancel_return" value="{{ url_for('payment.event_payment_cancel', event, _external=True) }}">
<input type="hidden" name="notify_url" value="{{ url_for('payment.event_payment_notify', event, _external=True) }}">
<input type="hidden" name="notify_url" value="{{ url_for_plugin('payment_paypal.notify', registrant, authkey=registrant.getRandomId(), _external=True) }}">
<input type="image" name="submit" border="0" src=" https://www.paypalobjects.com/webstatic/en_US/btn/btn_paynow_86x21.png" alt="PayPal - The safer, easier way to pay online">
</form>
</dd>