Integrating Stripe OAuth in Open Event Frontend

Why is Stripe Oauth needed in frontend? Open event allows organizers to add tickets and accepts payments for tickets through various modes for example, Credit card, Debit card, Netbanking and offline payments. Stripe allows users to accept payments into their linked accounts on various online platforms after they provide client secret and publishable key. So to enable online payments in open event, organizers were required to authenticate their stripe account. This is done through Stripe OAuth.

Flow of OAuth

To allow organizers to link their stripe account admin has to enable stripe under payment gateway in admin settings. Admin provides his client ID and secret key. Admin also sets the redirect URL for his app on the stripe dashboard. After enabling these settings organizer will see an option to link their stripe account to open event when they are creating an event with paid tickets.

Here is what open event frontend does when we click connect to stripe button:

  1. Opens a popup to allow organizer to fill his stripe credentials and authorize open event app to access their secret and publishable key.
  2. Once the organizer fills his credentials and authorizes open event app, open event frontend fetches organizers auth code and saves it to server.
  3. Server on receiving auth code from frontend makes a request to stripe using the auth code to retrieve the publishable key and secret key.
  4. Once these are fetched server saves this information against the event so that all payments for that event can go to the linked stripe account.

Implementing the Frontend portion:

  • Choosing the library:

After looking at various libraries that support OAuth for Ember applications we decided to use Torii. Torii is the library that allows the addition of OAuth for various social apps such as Facebook, Google and Stripe too. It allows writing a custom provider for OAuth in case we do not want to use clients for which torii provides supports by default.

  • Implementing Stripe Provider:

Default provider for stripe given by torii fetched the client ID and redirect URL from environment.js file. But since in open event we have already saved client id of admin in our database so we will extend default stripe provider and modify its client Id so that it fetches client id from server. Code for extending default provider is given here:

import stripeConnect from 'torii/providers/stripe-connect';
import { alias } from '@ember/object/computed';
import { inject } from '@ember/service';
import { configurable } from 'torii/configuration';

function currentUrl() {
 let url = [window.location.protocol,
   '//',
   window.location.host].join('');
 if (url.substr(-1) !== '/') {
   url += '/';
 }
 return url;
}

export default stripeConnect.extend({

 settings: inject(),

 clientId: alias('settings.stripeClientId'),

 redirectUri: configurable('redirectUri', function() {
   return `${currentUrl()}torii/redirect.html`;
 })

});

 

We have fetched clientId from our settings service as alias(‘settings.stripeClientId’).

We have already defined settings in our services so we just need to inject the service here to be able to use it.

By default torii provides redirect url as {currentUrl}/torii/redirect.html. But in open event frontend we allow organizers to edit information on two routes and torii suggests in its docs to use {baseUrl}/torii/redirect.html as the redirect url to avoid potential vulnerability. So we also modified the default redirect url building method.

Saving information to server

Once we get the authorization token from stripe we send it to the server and save it to stripe-authorization model. The logic for the same is given below:

connectStripe() {
     this.get('data.event.stripeAuthorization.content') ? '' : this.set('data.event.stripeAuthorization', this.store.createRecord('stripe-authorization'));
     this.get('torii').open('stripe')
       .then(authorization => {
         this.set('data.event.stripeAuthorization.stripeAuthCode', authorization.authorizationCode);
       })
       .catch(error => {
         this.get('notify').error(this.get('l10n').t(`${error.message}. Please try again`));
       });
   },

 

This action gets called when we click on connect to stripe button. This action calls the stripe provider and opens a popup to enable the organizer to authenticate his stripe account.
Full code for this can be seen here.

In this way we connect the stripe service to open event to allow the organizer to receive payments for his events.

Resources
  • Stripe : Documentation on Stripe-Connect : Link
  • Torii: Library to implement Oauth. : Link
  • Implementation: Link to PR showing its implementation : Link
Continue Reading

Implementing Permissions for Orders API in Open Event API Server

Open Event API Server Orders API is one of the core APIs. The permissions in Orders API are robust and secure enough to ensure no leak on payment and ticketing.The permission manager provides the permissions framework to implement the permissions and proper access controls based on the dev handbook.

The following table is the permissions in the developer handbook.

 

List View Create Update Delete
Superadmin/admin
Event organizer [1] [1] [1] [1][2] [1][3]
Registered user [4]
Everyone else
  1. Only self-owned events
  2. Can only change order status
  3. A refund will also be initiated if paid ticket
  4. Only if order placed by self

Super Admins and admins are allowed to create any order with any amount but any coupon they apply is not consumed on creating order. They can update almost every field of the order and can provide any custom status to the order. Permissions are applied with the help of Permission Manager which takes care the authorization roles. For example, if a permission is set based on admin access then it is automatically set for super admin as well i.e., to the people with higher rank.

Self-owned events

This allows the event admins, Organizer and Co-Organizer to manage the orders of the event they own. This allows then to view all orders and create orders with or without discount coupon with any custom price and update status of orders. Event admins can provide specific status while others cannot

if not has_access('is_coorganizer', event_id=data['event']):
   data['status'] = 'pending'

And Listing requires Co-Organizer access

elif not has_access('is_coorganizer', event_id=kwargs['event_id']):
   raise ForbiddenException({'source': ''}, "Co-Organizer Access Required")

Can only change order status

The organizer cannot change the order fields except the status of the order. Only Server Admin and Super Admins are allowed to update any field of the order.

if not has_access('is_admin'):
   for element in data:
       if element != 'status':
           setattr(data, element, getattr(order, element))

And Delete access is prohibited to event admins thus only Server admins can delete orders by providing a cancelling note which will be provided to the Attendee/Buyer.

def before_delete_object(self, order, view_kwargs):
   if not has_access('is_coorganizer', event_id=order.event.id):
       raise ForbiddenException({'source': ''}, 'Access Forbidden')

Registered User

A registered user can create order with basic details like the attendees’ records and payment method with fields like country and city. They are not allowed to provide any custom status to the order they are creating. All orders will be set by default to “pending”

Also, they are not allowed to update any field in their order. Any status update will be done internally thus maintaining the security of Order System. Although they are allowed to view their place orders. This is done by comparing their logged in user id with the user id of the purchaser.

if not has_access('is_coorganizer_or_user_itself', event_id=order.event_id, user_id=order.user_id):
   return ForbiddenException({'source': ''}, 'Access Forbidden')

Event Admins

The event admins have one more restriction, as an event admin, you cannot provide discount coupon and even if you do it will be ignored.

# Apply discount only if the user is not event admin
if data.get('discount') and not has_access('is_coorganizer', event_id=data['event']):

Also an event admin any amount you will provide on creating order will be final and there will be no further calculation of the amount will take place

if not has_access('is_coorganizer', event_id=data['event']):
   TicketingManager.calculate_update_amount(order)

Creating Attendees Records

Before sending a request to Orders API it is required to create to attendees mapped to some ticket and for this registered users are allowed to create the attendees without adding a relationship of the order. The mapping with the order is done internally by Orders API and its helpers.

Resources

  1. Dev Handbook – Niranjan R
    The Open Event Developer Handbook
  2. Flask-REST-JSONAPI Docs
    Permissions and Data layer | Flask-REST-JSONAPI
  3. A guide to use permission manager in API Server
    https://blog.fossasia.org/a-guide-to-use-permission-manager-in-open-event-api-server/

 

Continue Reading
Close Menu
%d bloggers like this: