The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. This blog post explains how the charge layer has been implemented in the Open Event Server in order to charge the user for tickets of an event.
Schema
We currently support payments via Stripe and Paypal. As a result the schema for Charges layer consists of fields for providing the token for stripe or paypal. It also contains a read only id field.
class ChargeSchema(Schema): """ ChargeSchema """ class Meta: """ Meta class for ChargeSchema """ type_ = 'charge' inflect = dasherize self_view = 'v1.charge_list' self_view_kwargs = {'id': '<id>'} id = fields.Str(dump_only=True) stripe = fields.Str(allow_none=True) paypal = fields.Str(allow_none=True)
Resource
The ChargeList resource only supports POST requests since there is no need for other type of requests. We simply declare the schema, supported methods and the data layer. We also check for required permissions by declaring the decorators.
class ChargeList(ResourceList): """ ChargeList ResourceList for ChargesLayer class """ methods = ['POST', ] schema = ChargeSchema data_layer = { 'class': ChargesLayer, 'session': db.session } decorators = (jwt_required,)
Layer
The data layer contains a single method create_object which does all the heavy lifting of charging the user according to the payment medium and the related order. It first loads the related order from the database using the identifier.
We first check if the order contains one or more paid tickets or not. If not, then ConflictException is raised since it doesn’t make sense to charge a user without any paid ticket in the order. Next, it checks the payment mode of the order. If the payment mode is Stripe then it checks if the stripe_token is provided with the request or not. If not, an UnprocessableEntity exception is raised otherwise relevant methods are called in order to charge the user accordingly. A similar procedure is followed for payments via Paypal. Below is the full code for reference.
class ChargesLayer(BaseDataLayer): def create_object(self, data, view_kwargs): """ create_object method for the Charges layer charge the user using paypal or stripe :param data: :param view_kwargs: :return: """ order = Order.query.filter_by(id=view_kwargs['id']).first() if not order: raise ObjectNotFound({'parameter': 'id'}, "Order with id: {} not found".format(view_kwargs['id'])) elif order.status == 'cancelled' or order.status == 'expired': raise ConflictException({'parameter': 'id'}, "You cannot charge payments on a cancelled or expired order") elif (not order.amount) or order.amount == 0: raise ConflictException({'parameter': 'id'}, "You cannot charge payments on a free order") # charge through stripe if order.payment_mode == 'stripe': if not data.get('stripe'): raise UnprocessableEntity({'source': ''}, "stripe token is missing") success, response = TicketingManager.charge_stripe_order_payment(order, data['stripe']) if not success: raise UnprocessableEntity({'source': 'stripe_token_id'}, response) # charge through paypal elif order.payment_mode == 'paypal': if not data.get('paypal'): raise UnprocessableEntity({'source': ''}, "paypal token is missing") success, response = TicketingManager.charge_paypal_order_payment(order, data['paypal']) if not success: raise UnprocessableEntity({'source': 'paypal'}, response) return order
The charge_stripe_order_payment and charge_paypal_order_payment are helper methods defined to abstract away the complications of the procedure from the layer.
References
- Flask REST JSON API: flask-rest-jsonapi
- SQLAlchemy: https://www.sqlalchemy.org/
- Stripe connect: https://stripe.com/connect
- Paypal developer documentation: https://developer.paypal.com/