Designing PayTM Checkout Components

This summer, Open Event project has 3 different payment gateways integrated in its system enabling the user base a wider base of options to buy their tickets and pay the organizers and hence making the platform more user friendly. In the initial period Omise gateway was implemented and properly documented in the first phase while Alipay was subsequently implemented in the middle of the coding period. In the late phase, the focus has been shifted to Indian payment gateways and PayTM came out as prime choice considering it’s popularity and ease of integration with the existing technology stack. This requires two different modals to be added to frontend project to felicitate the open-event-server hits being made on PayTM’s API services. The first modal’s skeleton design is to check paytm wallets as the payment option and acquire the mobile number to be used. // app/templates/components/modals/paytm-payment-options.hbs<div class="header"> {{t 'Amount to be paid:'}} {{currency-symbol currency}} {{amount}}</div><div class="content"> <div class="muted small text">   {{t 'Select an option to pay'}} </div> <form class="ui form" autocomplete="off" {{action 'openOTPController' on='submit' preventDefault=true}}>   <div class="field">     {{ui-radio name='payment_mode' value='paytm' onChange=(action (mut isWalletSelected))}}     {{t 'Paytm Wallet'}}<img src="/images/payment-logos/paytm.png" alt="paytm">   </div>   {{#if isWalletSelected}}     <div class="field">       <div class="label">         {{t 'Please enter your Paytm registered Mobile Number to continue'}}       </div>       {{input type='number' id='mobile_number' value=mobileNumber required=true}}     </div>   {{/if}} </form></div><div class="actions"> <button type="button" class="ui black button" {{action 'close'}}>   {{t 'Cancel'}} </button> <button {{action openOTPController}} class="ui green button" disabled={{not isWalletSelected}}>   {{t 'Proceed'}} </button></div> This simple modal design implementation resulted in the skeleton design of the first modal which can be seen as following: OTP sending modal The second modal required a simple API hit integration which will be validating the acquired OTP. This was designed rather simply with the following snippet: // app/templates/components/modals/paytm-otp.hbs<div class="header"> {{t 'Amount to be paid:'}} {{currency-symbol currency}} {{amount}}</div><div class="content"> {{t 'Enter OTP sent to mobile number'}} <form class="ui form" autocomplete="off">   <div class="field">     {{input type='number' id='otp' value=otp required=true}}   </div> </form></div><div class="actions"> <button type="button" class="ui black button" {{action 'close'}}>   {{t 'Cancel'}} </button> <button class="ui green button">   {{t 'Verify'}} </button></div> OTP confirmation modal These modals were controlled by adding corresponding pop-up logic in the pending.js controller focusing on the clicks on Proceed and verify buttons respectively. // app/controllers/orders/pending.js   openPaytmModal() {     // Model controller for PaytmModal     this.setProperties({       'isPaytmModalOpen': true     });   },   openOTPController() {     // Modal controller for OTP step     this.setProperties({       'isPaytmModalOpen' : false,       'isOTPModalOpen'   : true     });   } This concludes the design walk through of custom PayTM checkout modals. These will be integrated with the APIs to complete the backend workflow and hence adding PayTM as another payment option! Resources Ember ModalsEmber Controller Guides Related Work and Code Repository Front-End RepositoryAPI Server RepositoryDesigning PayTM modalsCreating Modal Integration Tests

Continue ReadingDesigning PayTM Checkout Components

Designing and optimising new invoice PDFs

The Open Event project has proven to be an excellent event management application with a growing user base. With recent workflow refactors in the order process in open-event-frontend and introduction of event invoices (to be rolled out this month as a work product), the open-event-server’s invoices required a makeover. A ticket buyer is now required to give their billing information if the order is comprised of paid tickets and to accommodate this, and long information addresses, optimisation was required. Restructuring order invoices The new order invoices use nested tables concept instead of previously used two-cell tables. The pros of this new design is the accomodation of long-addresses and corresponding changes in billing information display. {% if order.is_billing_enabled %}                       <td style="text-align:center;">                           <table>                               <tr>                                   <td>                                       <strong>Company :</strong>                                   </td>                                   <td>                                       <strong>{{ order.company }}</strong>                                   </td>                               </tr>                               <tr>                                   <td valign="top">                                       <strong>Tax Info :</strong>                                   </td>                                   <td>                                       <strong>{{ order.tax_business_info }}</strong>                                   </td>                               </tr>                               <tr>                                   <td valign="top">                                       <strong>Address :</strong>      …

Continue ReadingDesigning and optimising new invoice PDFs

Implementing Event Invoice Forms

This blog post elaborates on the recent addition of user billing form in Eventyay which is an open source event management solution which allows users to buy & sell tickets, organize events & promote their brand, developed by FOSSASIA. As this project moves forward with the implementation of event invoices coming up,. In the past few weeks, I have collaborated with fellow developers in planning the integration of event invoice payments and this is a necessary step for the same due to its involvement in order invoice templates. This implementation focuses on event invoices billing ( the calculated amount an event organiser has to pay to the platform for their event’s revenue ). This form includes basic details like contact details, tax ID, billing location and additional information (if any). The following is a specimen of this form : Tax Form Implementation First step of this form creation is to employ the account/billing/payment-info route for serving the relevant model data to the frontend. // app/routes/account/billing/payment-info.jsimport Route from '@ember/routing/route';import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';export default class extends Route.extend(AuthenticatedRouteMixin) { titleToken() {   return this.l10n.t('Payment Info'); }} Since the field additions have been done in the user schema in the server side, the corresponding changes have to made in the ember user model as well. // app/models/user.js/**  * Billing Contact Information  */ billingContactName    : attr('string'), billingPhone          : attr('string'), billingCountry        : attr('string'), company               : attr('string'), billingAddress        : attr('string'), billingCity           : attr('string'), billingZipCode        : attr('string'), billingTaxInfo        : attr('string'), billingAdditionalInfo : attr('string'), billingState          : attr('string'), This form has a speciality. Instead of using the current user information directly, it uses an intermediate object and employs manipulation in current user record only when the submit button is clicked. This has been implemented in the following way :  // app/components/user-payment-info-form.jsexport default class extends Component.extend(FormMixin) { didInsertElement() {   super.didInsertElement(...arguments);   this.set('userBillingInfo', pick(this.authManager.currentUser, ['billingContactName', 'billingCity', 'billingPhone', 'company', 'billingTaxInfo', 'billingCountry', 'billingState', 'billingAddress', 'billingZipCode', 'billingAdditionalInfo'])); } @action submit() {   this.onValid(async() => {     this.set('isLoading', true);     try {       this.authManager.currentUser.setProperties(this.userBillingInfo);       await this.authManager.currentUser.save();       this.notify.success(this.l10n.t('Your billing details has been updated'));     } catch (error) {       this.authManager.currentUser.rollbackAttributes();       this.notify.error(this.l10n.t('An unexpected error occurred'));     }     this.set('isLoading', false);   }); }} The usual form validations are employed as expected in this one too and works well in storing the invoice based information. Resources Flask DocumentationEmber Documetation Related Work and Code Repository Front-End RepositoryAPI Server RepositoryCreating billing order form on frontendField implementations for billing form

Continue ReadingImplementing Event Invoice Forms

Implement Order Confirmation Feature in Eventyay

This post elaborates on the details of an endpoint which can be used to explicatively used to resend order confirmations. In the current implementation of the open event project, if the order has been confirmed, the ticket holders and buyers get an email each regarding their order confirmation. But in case that email has been accidentally deleted by any of the attendees, the event organizer / owner should have the power to resend the confirmations. The first step to the implementation was to create the appropriate endpoint for the server to be pinged. I utilized the existing blueprint being used for serving tickets on eventyay frontend project and created a new endpoint on the route : orders/resend-email [POST] # app/api/auth.py@ticket_blueprint.route('/orders/resend-email', methods=['POST'])@limiter.limit(   '5/minute', key_func=lambda: request.json['data']['user'], error_message='Limit for this action exceeded')@limiter.limit(   '60/minute', key_func=get_remote_address, error_message='Limit for this action exceeded')def resend_emails():   """   Sends confirmation email for pending and completed orders on organizer request   :param order_identifier:   :return: JSON response if the email was succesfully sent   """   order_identifier = request.json['data']['order']   order = safe_query(db, Order, 'identifier', order_identifier, 'identifier')   if (has_access('is_coorganizer', event_id=order.event_id)):       if order.status == 'completed' or order.status == 'placed':           # fetch tickets attachment           order_identifier = order.identifier           key = UPLOAD_PATHS['pdf']['tickets_all'].format(identifier=order_identifier)           ticket_path = 'generated/tickets/{}/{}/'.format(key, generate_hash(key)) + order_identifier + '.pdf'           key = UPLOAD_PATHS['pdf']['order'].format(identifier=order_identifier)           invoice_path = 'generated/invoices/{}/{}/'.format(key, generate_hash(key)) + order_identifier + '.pdf'           # send email.           send_email_to_attendees(order=order, purchaser_id=current_user.id, attachments=[ticket_path, invoice_path])           return jsonify(status=True, message="Verification emails for order : {} has been sent succesfully".                          format(order_identifier))       else:           return UnprocessableEntityError({'source': 'data/order'},                                           "Only placed and completed orders have confirmation").respond()   else:       return ForbiddenError({'source': ''}, "Co-Organizer Access Required").respond() I utilized exiting send_email_to_attendees for the email purpose but for security reasons, the endpoint was limited to make sure that an organizer can request only 5 order confrimations to be resent each minute (implemented using flask limiter). This was all for server implementation, to implement this on the front end, I just created a new action named as resendConfirmation implemented as given. // app/controllers/events/view/tickets/orders/list.jsasync resendConfirmation(order) {     let payload = {};     try {       payload = {         'data': {           'order' : order.identifier,           'user'  : this.authManager.currentUser.email         }       };       await this.loader.post('orders/resend-email', payload);       this.notify.success(this.l10n.t('Email confirmation has been sent to attendees successfully'));     } catch (error) {       if (error.status === 429) {        …

Continue ReadingImplement Order Confirmation Feature in Eventyay

Tax Information on Public Ticket Page

This blog post will elaborate on how Tax Information is being displayed on the public page of an event. In current implementation, the user gets to know the total tax inclusive amount only after he/she decides to place an order but no such information was given to them on the public ticket page itself. Order summary example in eventyay Example : In initial implementation, the user gets to know that the order is of only $120 and no information is given about the additional 30% being charged and taking the total to $156. To tackle this issue, I added two hybrid components to the ticket object to handle the two tax cases :  Inclusion in the price : In European and Asian Countries , the tax amount is included in the ticket price itself. For this case, I created the following parameter to store the tax amount included in gross amount. // app/models/ticket.js includedTaxAmount: computed('event.tax.isTaxIncludedInPrice', 'event.tax.rate', function() {   const taxType = this.event.get('tax.isTaxIncludedInPrice');   if (taxType) {     const taxRate = this.event.get('tax.rate');     return ((taxRate * this.price) / (100 + taxRate)).toFixed(2);   }   return 0; }) Added on the ticket price : In basic US tax policy, the tax amount is added on top of the ticket price. For such cases I have added a new attribute to ticket model which calculates the total amount payable for that particular ticket with tax inclusion // app/models/ticket.js ticketPriceWithTax: computed('event.tax.isTaxIncludedInPrice', 'event.tax.rate', function() {   let taxType = this.event.get('tax.isTaxIncludedInPrice');   if (!taxType) {     return ((1 + this.event.get('tax.rate') / 100) * this.price).toFixed(2);   }   return this.price; }) Now, the public ticket page has to be edited accordingly. The design I decided to follow is inspired by eventbrite itself :  Eventbrite specimen of the proposed implementation For this implementation, I modified the ticket list template to accommodate the changes in the following way :  // app/components/public/ticket-list.hbs<td id="{{ticket.id}}_price"> {{currency-symbol eventCurrency}} {{format-number ticket.price}} {{#if (and taxInfo (not-eq ticket.type 'free'))}}   {{#if showTaxIncludedMessage}}     <small class="ui gray-text small">       {{t 'includes'}} {{currency-symbol eventCurrency}} {{format-number ticket.includedTaxAmount}}     </small>   {{else}}     <small class="ui gray-text small">       + {{currency-symbol eventCurrency}} {{format-number (sub ticket.ticketPriceWithTax ticket.price)}}     </small>   {{/if}}   <div>     <small class="ui gray-text tiny aligned right">({{taxInfo.name}})</small>   </div> {{/if}}</td> Tax amount is included in ticket price Hence making the new public ticket list display to look like this in case of tax amount inclusion and additional charge as follows Tax amount is charged over the base price Discount Code application cases: In the cases when a user applies the discount code, the ticket price need to be updated, hence, the tax applied has to be updated accordingly. I achieved this by updating the two computed properties of the ticket model on each togglePromotionalCode and applyPromotionalCode action. When a promotional code is applied, the appropriate attribute is updated according to the discount offered. // app/components/public/ticket-list.jstickets.forEach(ticket => { let ticketPrice = ticket.get('price'); let taxRate = ticket.get('event.tax.rate'); let…

Continue ReadingTax Information on Public Ticket Page

Implementing Attendee Forms in Wizard of Open Event Frontend

This blog post illustrates on how the order form is included in the attendee information of the Open Event Frontend form  and enabling the organizer to choosing what information to collect from the attendee apart from the mandatory data i.e. First Name, Last Name and the Email Id during the creation of event itself. The addition of this feature required alteration in the existing wizard flow to accommodate this extra step. This new wizard flow contains the step : Basic Details : Where organizer fills the basic details regarding the event.Attendee Form : In this step, the organizer can choose what information he/she has to collect from the ticket buyers.Sponsors : This step enables the organizer to fill in the sponsor detailsSession and Speakers : As the name suggests, this final step enables the organizer to fill in session details to be undertaken during the event. This essentially condensed the flow to this : The updated wizard checklist To implement this, the navigation needed to be altered first in the way that Forward and Previous buttons comply to the status bar steps // app/controller/create.jsmove() {     this.saveEventDataAndRedirectTo(       'events.view.edit.attendee',       ['tickets', 'socialLinks', 'copyright', 'tax', 'stripeAuthorization']     );   } //app/controller/events/view/edit/sponsorshipmove(direction) {     this.saveEventDataAndRedirectTo(       direction === 'forwards' ? 'events.view.edit.sessions-speakers' : 'events.view.edit.attendee',       ['sponsors']     );   } Once the navigation was done, I decided to add the step in the progress bar by simply including the attendees form in the event mixin. // app/mixins/event-wizard.js    {       title     : this.l10n.t('Attendee Form'),       description : this.l10n.t('Know your audience'),       icon     : 'list icon',       route     : 'events.view.edit.attendee'     } Now a basic layout for the wizard is prepared, all what is left is setting up the route for this step and including it in the router file. I took my inspiration for setting up the route from events/view/tickets/order-from.js and implemented it like this: // app/routes/events/view/edit/attendee.jsimport Route from '@ember/routing/route';import CustomFormMixin from 'open-event-frontend/mixins/event-wizard';import { A } from '@ember/array';export default Route.extend(CustomFormMixin, { titleToken() {   return this.l10n.t('Attendee Form'); }, async model() {   let filterOptions = [{     name : 'form',     op : 'eq',     val : 'attendee'   }];   let data = {     event: this.modelFor('events.view')   };   data.customForms = await data.event.query('customForms', {     filter       : filterOptions,     sort         : 'id',     'page[size]' : 50   });   return data; }, afterModel(data) {   /**    * Create the additional custom forms if only the compulsory forms exist.    */   if (data.customForms.length === 3) {     let customForms = A();     for (const customForm of data.customForms ? data.customForms.toArray() : []) {       customForms.pushObject(customForm);     }     const createdCustomForms = this.getCustomAttendeeForm(data.event);     for (const customForm of createdCustomForms ? createdCustomForms : []) {…

Continue ReadingImplementing Attendee Forms in Wizard of Open Event Frontend

Omise Integration in Open Event Frontend

This blog post will elaborate on how omise has been integrated into the Open Event Frontend project. Omise is Thailand's leading online payment gateway offering a wide range of processing solutions for this project and integrating it as a payment option widens the possibilities for user base and ease of payment workflow. Similar to Paypal, Omise offers two alternatives for using their gateway, Test mode and Live mode, where the former is generally favoured for usage in Development and Testing phase while the latter is used in actual production for capturing live payments. Both these modes require a Public key and Secret key each and are only update-able on the admin route. This was implemented by introducing appropriate fields in the settings model. // app/models/setting.js omiseMode            : attr('string'), omiseTestPublic      : attr('string'), omiseTestSecret      : attr('string'), omiseLivePublic      : attr('string'), omiseLiveSecret      : attr('string') Once your Omise credentials are configured, you can go ahead and include the options in your event creation form. You will see an option to include Omise in your payment options if you have configured your keys correctly and if the gateway supports the currency your event is dealing with, for example, even if your keys are correctly configured, you will not get the option to use omise gateway for money collection if the currency is INR. For showing omise option in the template, a simple computed property did the trick canAcceptOmise  in the form’s component file and the template as follows: // app/components/forms/wizard/basic-details-step.jscanAcceptOmise: computed('data.event.paymentCurrency', 'settings.isOmiseActivated', function() {   return this.get('settings.isOmiseActivated') && find(paymentCurrencies, ['code', this.get('data.event.paymentCurrency')]).omise; }) // app/templates/components/forms/wizard/basic-details-step.js{{#if canAcceptOmise}}       <label>{{t 'Payment with Omise'}}</label>       <div class="field payments">         <div class="ui checkbox">           {{input type='checkbox' id='payment_by_omise' checked=data.event.canPayByOmise}}           <label for="payment_by_omise">             {{t 'Yes, accept payment through Omise Gateway'}}             <div class="ui hidden divider"></div>             <span class="text muted">               {{t 'Omise can accept Credit and Debit Cards , Net-Banking and AliPay. Find more details '}}               <a href="https://www.omise.co/payment-methods" target="_blank" rel="noopener noreferrer">{{t 'here'}}</a>.             </span>           </label>         </div>       </div>       {{#if data.event.canPayByOmise}}         <label>{{t 'Omise Gateway has been successfully activated'}}</label>       {{/if}}     {{/if}} Once the event has the payment option enabled, an attendee has chosen the option to pay up using omise, they will encounter this screen on their pending order page  On entering the credentials correctly, they will be forwarded to order completion page. On clicking the “Pay” button, the omise cdn used hits the server with a POST request to the order endpoint  and is implemented as follows : //controllers/orders/pending.jsisOmise: computed('model.order.paymentMode', function() {   return this.get('model.order.paymentMode') === 'omise'; }), publicKeyOmise:…

Continue ReadingOmise Integration in Open Event Frontend