Skip to content
This repository was archived by the owner on May 16, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions alembic/versions/38bc5fa8b6d6_added_payment_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Added payment model

Revision ID: 38bc5fa8b6d6
Revises: 155bdd6d893d
Create Date: 2013-12-23 19:35:00.124877

"""

# revision identifiers, used by Alembic.
revision = '38bc5fa8b6d6'
down_revision = '155bdd6d893d'

from alembic import op
import sqlalchemy as sa


def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('payment_gateway_log',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('event_id', sa.Integer(), nullable=False),
sa.Column('status', sa.Integer(), nullable=False),
sa.Column('order_no', sa.Unicode(20), nullable=False),
sa.Column('server_response', sa.UnicodeText(), nullable=True),
sa.Column('start_datetime', sa.DateTime(), nullable=False),
sa.Column('end_datetime', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['event_id'], ['event.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)

op.add_column(u'event', sa.Column('currency', sa.Unicode(length=3), nullable=True))
op.add_column(u'event', sa.Column('payment_credentials', sa.Unicode(length=100), nullable=True))
op.add_column(u'event', sa.Column('payment_service', sa.Unicode(length=100), nullable=True))

op.add_column(u'participant', sa.Column('purchased_ticket', sa.Boolean(), nullable=False, server_default=sa.text(u"'f'")))
op.alter_column(u'participant', 'purchased_ticket', server_default=None)
conn = op.get_bind()
conn.execute("update participant set purchased_ticket='t' where status=2")
### end Alembic commands ###


def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column(u'event', 'payment_service')
op.drop_column(u'event', 'payment_credentials')
op.drop_column(u'event', 'currency')
op.drop_column(u'participant', 'purchased_ticket')
op.drop_table('payment_gateway_log')
### end Alembic commands ###
41 changes: 37 additions & 4 deletions hacknight/forms/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from baseframe.forms import Form, RichTextField, DateTimeField, ValidName
from baseframe.forms.sqlalchemy import AvailableName
from hacknight.models import Venue, EVENT_STATUS, SYNC_SERVICE
from hacknight.models import Venue, EVENT_STATUS, SYNC_SERVICE, PAYMENT_GATEWAY

__all__ = ['EventForm', 'ConfirmWithdrawForm', 'SendEmailForm']

Expand All @@ -27,10 +27,25 @@

SYNC_CHOICES = [
# Empty value for opting out.
(u"", u""),
(u"N/A", u""),
(SYNC_SERVICE.DOATTEND, u"DoAttend"),
]

PAYMENT_GATEWAY_CHOICES = [
# Empty value for opting out.
(u"N/A", u""),
(PAYMENT_GATEWAY.EXPLARA, u"Explara"),
]

CURRENCY_CHOICES = [
(u"INR", u"INR - India Rupee"),
(u"USD", u"USD - United States Dollar"),
(u"GBP", u"GBP - Great Britain Pound"),
(u"EUR", u"EUR - Euro"),
(u"PHP", u"PHP - Philippines Peso"),
(u"ZAR", u"ZAR - South Africa Rand"),
]


class EventForm(Form):
title = wtforms.TextField("Title", description="Name of the Event", validators=[wtforms.validators.Required(), wtforms.validators.NoneOf(values=["new"]), wtforms.validators.length(max=250)])
Expand Down Expand Up @@ -61,10 +76,13 @@ class EventForm(Form):
maximum_participants = wtforms.IntegerField("Venue capacity", description="The number of people this venue can accommodate.", default=50, validators=[wtforms.validators.Required()])
website = wtforms.fields.html5.URLField("Website", description="Related Website (Optional)", validators=[wtforms.validators.Optional(), wtforms.validators.length(max=250), wtforms.validators.URL()])
status = wtforms.SelectField("Event status", description="Current status of this hacknight", coerce=int, choices=STATUS_CHOICES)
sync_service = wtforms.SelectField("Sync service name", description="Name of the ticket sync service like doattend", choices= SYNC_CHOICES, validators=[wtforms.validators.Optional(), wtforms.validators.length(max=100)])
sync_service = wtforms.SelectField("Sync service name", description="Name of the ticket sync service like doattend", choices=SYNC_CHOICES, validators=[wtforms.validators.Optional(), wtforms.validators.length(max=100)])
sync_eventsid = wtforms.TextField("Sync event ID", description="Sync events id like DoAttend event ID. More than one event ID is allowed separated by ,.", validators=[wtforms.validators.Optional(), wtforms.validators.length(max=100)])
sync_credentials = wtforms.TextField("Sync credentials", description="Sync credentials like API Key for the event", validators=[wtforms.validators.Optional(), wtforms.validators.length(max=100)])

payment_service = wtforms.SelectField("Payment service", description="Name of the payment gateway service", choices=PAYMENT_GATEWAY_CHOICES, validators=[wtforms.validators.Optional(), wtforms.validators.length(max=100)])
payment_credentials = wtforms.TextField("Payment gateway credentials", description="Payment gateway credentials like API Key", validators=[wtforms.validators.Optional(), wtforms.validators.length(max=100)])
currency = wtforms.SelectField("Currency", description="Currency in which participant should pay", choices=CURRENCY_CHOICES, validators=[wtforms.validators.Optional(), wtforms.validators.length(max=100)])

def validate_end_datetime(self, field):
if field.data < self.start_datetime.data:
raise wtforms.ValidationError(u"Your event can’t end before it starts.")
Expand All @@ -85,6 +103,21 @@ def validate_sync_eventsid(self, field):
if events_id:
field.data = ",".join(events_id)

def validate_payment_credentials(self, field):
if self.payment_service.data == PAYMENT_GATEWAY.EXPLARA:
if len(field.data) < 10:
raise wtforms.ValidationError(u"Payment credentials must be more than 10 characters")

def validate_ticket_price(self, field):
if self.payment_service.data == PAYMENT_GATEWAY.EXPLARA:
try:
data = field.data.strip()
if data[0] == '-':
raise wtforms.ValidationError(u"Event price must be positive number")
float(data)
except ValueError:
raise wtforms.ValidationError(u"Event price must be positive number. E.G: 500.00")


class EmailEventParticipantsForm(Form):
pending_message = RichTextField("Pending Message", description="Message to be sent for pending participants. '*|FULLNAME|*' will be replaced with user's fullname.", validators=[wtforms.validators.Optional()], tinymce_options = {'convert_urls': False, 'remove_script_host': False})
Expand Down
22 changes: 21 additions & 1 deletion hacknight/forms/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import wtforms
import wtforms.fields.html5
from baseframe.forms import Form, RichTextField
from baseframe.staticdata import country_codes

__all__ = ['ParticipantForm']

__all__ = ['ParticipantForm', 'ExplaraForm']


class ParticipantForm(Form):
Expand All @@ -31,3 +33,21 @@ class ParticipantForm(Form):
validators=[wtforms.validators.Optional(), wtforms.validators.length(max=1200)])
skill_level = wtforms.RadioField("Skill Level", description="What is your skill level?",
choices=skill_levels)


class ExplaraForm(Form):
name = wtforms.TextField("Name", description="Name of the purchaser",
validators=[wtforms.validators.Required(), wtforms.validators.length(max=200)])
email = wtforms.fields.html5.EmailField("Email", description="Email address",
validators=[wtforms.validators.Required(), wtforms.validators.length(max=200)])
phone_no = wtforms.TextField("Telephone No", description="Telephone No",
validators=[wtforms.validators.Required(), wtforms.validators.length(max=15)])
country = wtforms.SelectField("Country", description="Country", choices=country_codes, validators=[wtforms.validators.Required()])
""" Longest state name
Taumatawhakatangihangakoauauotamateahaumaitawhitiurehaeaturipuk-
akapikimaungahoronukupokaiwhenuakitanatahu in NewZeland.
"""
state = wtforms.TextField("State", description="State", validators=[wtforms.validators.Required(), wtforms.validators.length(max=110)])
city = wtforms.TextField("City", description="City", validators=[wtforms.validators.Required(), wtforms.validators.length(max=110)])
address = wtforms.TextField("Address", description="Address", validators=[wtforms.validators.Required(), wtforms.validators.length(max=1000)])
zip_code = wtforms.TextField("Zip code", description="Zip code", validators=[wtforms.validators.Required(), wtforms.validators.length(max=6)])
1 change: 1 addition & 0 deletions hacknight/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from hacknight.models.project import *
from hacknight.models.participant import *
from hacknight.models.sponsor import *
from hacknight.models.log import *
29 changes: 27 additions & 2 deletions hacknight/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from hacknight.models import db, BaseNameMixin, BaseScopedNameMixin, BaseMixin


__all__ = ['Profile', 'Event', 'EVENT_STATUS', 'SYNC_SERVICE', 'PROFILE_TYPE', 'EventRedirect']
__all__ = ['Profile', 'Event', 'EVENT_STATUS', 'SYNC_SERVICE', 'PROFILE_TYPE', 'EventRedirect', 'PAYMENT_GATEWAY']
#need to add EventTurnOut, EventPayment later


Expand Down Expand Up @@ -45,6 +45,10 @@ class SYNC_SERVICE:
DOATTEND = u"doattend"


class PAYMENT_GATEWAY:
EXPLARA = u"Explara"


class SyncException(Exception):
pass

Expand Down Expand Up @@ -95,6 +99,11 @@ class Event(BaseScopedNameMixin, db.Model):
sync_credentials = db.Column(db.Unicode(100), nullable=True)
sync_eventsid = db.Column(db.Unicode(100), nullable=True)

# Payment gateway details
payment_service = db.Column(db.Unicode(100), nullable=True)
payment_credentials = db.Column(db.Unicode(100), nullable=True)
currency = db.Column(db.Unicode(3), nullable=True)

__table_args__ = (db.UniqueConstraint('name', 'profile_id'),)

# List of statuses which are not allowed to be displayed in index page.
Expand All @@ -112,6 +121,9 @@ def owner_is(self, user):
"""Check if a user is an owner of this event"""
return user is not None and self.profile.userid in user.user_organizations_owned_ids()

def has_payment_gateway(self):
return self.payment_service and self.payment_credentials and self.currency

def has_sync(self):
return self.sync_service and self.sync_credentials and self.sync_eventsid

Expand Down Expand Up @@ -150,6 +162,13 @@ def sync_participants(self, participants):
yield u"Sync credentials missing.\n"
yield Markup(final_msg)

def is_tickets_available(self):
if self.has_payment_gateway():
if self.confirmed_participants_count() < self.maximum_participants:
return True
return False
return False

def participant_is(self, user):
from hacknight.models.participant import Participant
return Participant.get(user, self) is not None
Expand All @@ -161,7 +180,7 @@ def confirmed_participant_is(self, user):

def confirmed_participants_count(self):
from hacknight.models.participant import Participant, PARTICIPANT_STATUS
return Participant.query.filter_by(status=PARTICIPANT_STATUS.CONFIRMED, event=self).count()
return Participant.query.filter_by(status=PARTICIPANT_STATUS.CONFIRMED, event=self, purchased_ticket=True).count()

def permissions(self, user, inherited=None):
perms = super(Event, self).permissions(user, inherited)
Expand All @@ -173,6 +192,8 @@ def permissions(self, user, inherited=None):
perms.add('edit')
perms.add('delete')
perms.add('send-email')
if self.has_payment_gateway():
perms.add('buy-ticket')
return perms

def url_for(self, action='view', _external=False):
Expand Down Expand Up @@ -200,6 +221,10 @@ def url_for(self, action='view', _external=False):
return url_for('email_template_form', profile=self.profile.name, event=self.name, _external=_external)
elif action == 'sync':
return url_for('event_sync', profile=self.profile.name, event=self.name, _external=_external)
elif action == 'purchase_ticket':
return url_for('purchase_ticket_explara', profile=self.profile.name, event=self.name, _external=_external)
elif action == 'payment_redirect':
return url_for('payment_redirect_explara', profile=self.profile.name, event=self.name, _external=_external)


class EventRedirect(BaseMixin, db.Model):
Expand Down
41 changes: 41 additions & 0 deletions hacknight/models/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-

import datetime

from hacknight.models import db, BaseMixin, User, Event

__all__ = ['PaymentGatewayLog', 'TRANSACTION_STATUS', 'transaction_status']


class TRANSACTION_STATUS:
PENDING = 0
FAILURE = 1
SUCCESS = 2


transaction_status = {
u'success': TRANSACTION_STATUS.SUCCESS,
u'pending': TRANSACTION_STATUS.PENDING,
u'failure': TRANSACTION_STATUS.FAILURE,
}


class PaymentGatewayLog(BaseMixin, db.Model):
__tablename__ = "payment_gateway_log"

user_id = db.Column(None, db.ForeignKey('user.id'), nullable=False)
user = db.relationship(User)

event_id = db.Column(None, db.ForeignKey('event.id'), nullable=False)
event = db.relationship(Event, backref=db.backref('payment_gateway_logs'))

status = db.Column(db.Integer, default=TRANSACTION_STATUS.PENDING, nullable=False)
order_no = db.Column(db.Unicode(20), nullable=False)

server_response = db.Column(db.UnicodeText(), nullable=True)
start_datetime = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now)
end_datetime = db.Column(db.DateTime, nullable=True)

@classmethod
def get_recent_transaction(cls, user):
return cls.query.filter_by(user=user).order_by(PaymentGatewayLog.id.desc()).first()
2 changes: 2 additions & 0 deletions hacknight/models/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Participant(BaseMixin, db.Model):
event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False)
event = db.relationship(Event, backref=db.backref('participants', cascade='all, delete-orphan'))
status = db.Column(db.Integer, default=PARTICIPANT_STATUS.PENDING, nullable=False)
purchased_ticket = db.Column(db.Boolean, default=False, nullable=False)
mentor = db.Column(db.Boolean, default=False, nullable=False)
reason_to_join = db.Column(db.UnicodeText, default=u'', nullable=False)
email = db.Column(db.Unicode(80), default=u'', nullable=False)
Expand All @@ -46,6 +47,7 @@ def save_defaults(self):

def confirm(self):
self.status = PARTICIPANT_STATUS.CONFIRMED
self.purchased_ticket = True

@classmethod
def get(cls, user, event):
Expand Down
28 changes: 19 additions & 9 deletions hacknight/templates/event.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<a href="{{ p.user.profile_url }}"><div class="participant-img"><i class="icon-user icon-4x"></i></div></a>
<div class="participant-name">
<b><a href="{{ p.user.profile_url }}">{{ p.user.fullname }}</a></b>

<div class="participating-projects">
{% with projects = p.user.projects_in(event) %}
{%- if projects %}
Expand Down Expand Up @@ -87,11 +87,21 @@ <h1>{{ self.title() }}</h1>
{%- elif current_participant.status == 4 -%}
<li><i class="icon-user"></i><span>Withdrawn</span></li>
{%- endif -%}

{% if workflow.can_apply() -%}
{% if event.is_tickets_available() -%}
{% if not current_participant.purchased_ticket -%}
<li><a href="{{ event.url_for('purchase_ticket') }}"><i class="icon-money"></i><span>Purchase Ticket</span></a></li>
{% else %}
<li><a href="{{ event.url_for('withdraw') }}"><i class="icon-signout"></i><span>Withdraw registration</span></a></li>
{%- endif %}
{% else %}
<li><a href="{{ event.url_for('withdraw') }}"><i class="icon-signout"></i><span>Withdraw registration</span></a></li>
{%- endif %}
{%- endif %}
{%- endif %}
{% if workflow.can_apply() -%}
<li><a href="{{ event.url_for('withdraw') }}"><i class="icon-signout"></i><span>Withdraw registration</span></a></li>
{%- endif %}
{% endif %}
{%- endif %}


{%- if event.owner_is(g.user) %}
<li class="nav-header">Manage event</li>
Expand All @@ -110,7 +120,7 @@ <h1>{{ self.title() }}</h1>
{% endif -%}
{% endif -%}
</ul>

<div class="share">
<ul>
<li>
Expand All @@ -133,8 +143,8 @@ <h1>{{ self.title() }}</h1>
{% if event.sponsors %}
<hr class="clear-line">
<div class="sidebar-heading nav nav-list">
<h4 class="nav-header">Sponsors</h4>
</div>
<h4 class="nav-header">Sponsors</h4>
</div>
<ul class="sponsor-box">
{% for sponsor in event.sponsors %}
<li><a href="{{ sponsor.url_for() }}"><img src="{{ sponsor.image_url }}" class="sponsor-logo"></a></li>
Expand Down Expand Up @@ -244,7 +254,7 @@ <h2>New project...</h2>
$(".sync").on('click', function(e){
e.preventDefault();
$("#sync_form").submit();
});
});
});
</script>

Expand Down
Loading