Adding time counter on ordering tickets in Eventyay Attendee

In Eventyay Attendee, ordering tickets for events has always been a core functionality that we focus on. When ordering tickets, adding a time counter to make a reservation and release tickets after timeout is a common way to help organizers control their tickets’ distribution and help users save up their tickets. Let’s take a look at how to implement this feature

  • Implementing the time counter 
  • Some notes on implementing time counter
  • Conclusion
  • Resources

INTEGRATING TIME COUNTER TO YOUR SYSTEM

Step 1: Create the UI for your time counter. In here, we made a simple View container with TextView inside to update the time.

Step 2: Set up the time counter with Android CountdownTimer with the total countdown time and the ticking time. In Eventyay, the default countdown time is 10 minutes (600,000 ms) with the ticking time is (1,000 ms), which means the UI is updated every one second.

private fun setupCountDownTimer(orderExpiryTime: Int) {
   rootView.timeoutCounterLayout.isVisible = true
   rootView.timeoutInfoTextView.text =
       getString(R.string.ticket_timeout_info_message, orderExpiryTime.toString())

   val timeLeft: Long = if (attendeeViewModel.timeout == -1L) orderExpiryTime * 60 * 1000L
                           else attendeeViewModel.timeout
   timer = object : CountDownTimer(timeLeft, 1000) {
       override fun onFinish() {
           findNavController(rootView).navigate(AttendeeFragmentDirections
               .actionAttendeeToTicketPop(safeArgs.eventId, safeArgs.currency, true))
       }

       override fun onTick(millisUntilFinished: Long) {
           attendeeViewModel.timeout = millisUntilFinished
           val minutes = millisUntilFinished / 1000 / 60
           val seconds = millisUntilFinished / 1000 % 60
           rootView.timeoutTextView.text = "$minutes:$seconds"
       }
   }
   timer.start()
}

Step 3: Set up creating a pending order when the timer starts counting so that users can hold a reservation for their tickets. A simple POST request about empty order to the API is made

fun initializeOrder(eventId: Long) {
   val emptyOrder = Order(id = getId(), status = ORDER_STATUS_INITIALIZING, event = EventId(eventId))

   compositeDisposable += orderService.placeOrder(emptyOrder)
       .withDefaultSchedulers()
       .subscribe({
           mutablePendingOrder.value = it
           orderIdentifier = it.identifier.toString()
       }, {
           Timber.e(it, "Fail on creating pending order")
       })
}

Step 4: Set up canceling order when the time counter finishes. As time goes down, the user should be redirected to the previous fragment and a pop-up dialog should show with a message about reservation time has finished. There is no need to send an HTTP request to cancel the pending order as it is automatically handled by the server.

Step 5: Cancel the time counter in case the user leaves the app unexpectedly or move to another fragment. If this step is not made, the CountdownTimer still keeps counting in the background and possibly call onFinished() at some point that could evoke functions and crash the app

override fun onDestroy() {
   super.onDestroy()
   if (this::timer.isInitialized)
       timer.cancel()
}

RESULTS

CONCLUSION

For a project with a ticketing system, adding a time counter for ordering is a really helpful feature to have. With the help of Android CountdownTimer, it is really to implement this function to enhance your user experience.

RESOURCES

Eventyay Attendee Android Codebase: https://github.com/fossasia/open-event-android

Eventyay Attendee Android PR: #1843 – Add time counter on ordering ticket

Documentation: https://developer.android.com/reference/android/os/CountDownTimer

Continue ReadingAdding time counter on ordering tickets in Eventyay Attendee

Using Order Endpoints in Open Event API Server

The main feature i.e., Ordering API is added into API server. These endpoints provide the ability to work with the ordering system. This API is not simple like other as it checks for the discount codes and various other things as well.
The process in layman terms is very simple, first, a user must be registered or added as an attendee into Server without any order_id associated and then the attendee details will be sent to API server as a relationship.

Things needed to take care:

  1. Validating the discount code and ensure it is not exhausted
  2. Calculating the total amount on the server side by applying coupon
  3. Do not calculate amount if the user is the event admin
  4. Do not use coupon if user is event admin
  5. Handling payment modes and generating payment links
  6. Ensure that default status is always pending, unless the user is event admin

Creating Order

    • Prerequisite
      Before initiating the order, attendee records needs to be created associated with the event. These records will not have any order_id associated with them initially. The Order API will add the relationships.
    • Required Body
      Order API requires you to send event relationship and attendee records to create order_tickets
    • Permissions
      Only organizers can provide custom amount and status. Others users will get their status as pending and amount will be recalculated in server. The response will reflect the calculated amount and updated status.
      Also to initiate any order, user must be logged in. Guest can not create any order
    • Payment Modes
      There are three payment modes, free, stripe and paypal. If payment_mode is not provided then API will consider it as “free”.
    • Discount Codes
      Discount code can be sent as a relationship to the API. The Server will validate the code and will act accordingly.

Validating Discount Codes

Discount codes are checked to ensure they are valid, first check ensures that the user is not co-organizer

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

Second, check ensures that the discount code is active

if not discount_code.is_active:
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code")

The third, Check ensures its validity is not expired

if not (valid_from <= now <= valid_till):
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code")

Fourth Check ensure that the quantity is not exhausted

if not TicketingManager.match_discount_quantity(discount_code, data['ticket_holders']):
  raise UnprocessableEntity({'source': 'discount_code_id'}, 'Discount Usage Exceeded')

Lastly, the fifth check ensures that event id matches with given discount associated event

if discount_code.event.id != data['event'] and discount_code.user_for == TICKET:
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Invalid Discount Code")

Calculating Order Amount

The next important thing is to recalculate the order amount and it will calculated only if user is not the event admin

if not has_access('is_coorganizer', **view_kwargs):
  TicketingManager.calculate_update_amount(order)

API Response

The API response apart from general fields will provide you the payment-url depending upon the payment mode you selected.

  • Stripe : will give payment-url as stripe
  • Paypal: will provide the payment completing url in payment-url

This all explains the flow and requirements to create an order. Order API consists of many more things related with TIcketing Manager which works to create the payment url and apply discount count as well as calculate the total order amount.

Resources

  1. Stripe Payments API Docs
    https://stripe.com/docs/api
  2. Paypal Payments API docs
    https://developer.paypal.com/docs/api/
  3. Paypal Sandbox docs
    https://developer.paypal.com/docs/classic/lifecycle/ug_sandbox/

 

Continue ReadingUsing Order Endpoints in Open Event API Server

Ticket Ordering and Positioning (Front-end)

As discussed in my last blog about ticket ordering and positioning, in this blog we are gonna talk about how we implement the front-end part of re-arranging the tickets. We essentially do it using compute and methods of Vue.js. The functionality that is expected in the front-end is, the event organizer should be able to move the tickets Up or Down the order and save that position so that it gets displayed later in that very particular order.

Like I said above we use two main things of Vue.JS for this purpose – Compute and Methods.

Compute

We use this to get the sorted list of tickets based on the position key of the tickets and use this sorted list to display the tickets in the event editing wizard. Whenever you change the value of the position for a ticket, it automatically updates the list to sorted list again and hence the order of ticket in the front-end also updates. To add a compute function in Vue.JS, inside the new Vue() object creation, we add an attribute computed and inside that we put all the functions that we are gonna use. So in our case the function is sortedTickets . We make use of the sort function of lodash to sort the tickets array based on it’s position attribute.

Now while showing or looping over the tickets, we loop over sortedTickets  rather than the original ticket array.

Method

This method is called when the button is clicked to move it up or down. This makes the calculations to determine the values of the position of the two tickets which are being re-ordered in a single click. To add a method, we do it in a similar way like computed but using methods attribute instead. The methods we have written to move tickets up or down is moveTicket.

It has 3 parameters – ticket, index and direction. So when this function call is emitted, depending on the button, the direction is either “up” or “down” while the other two parameters are the ticket properties of the particular ticket. So in this function we check the direction and accordingly update the value of position for the tickets involved in the arranging. As we update the position here, because of the compute, the UI automatically updates to show the tickets in the new order.

Finally after all arrangement is done, the position is always saved in a hidden input field which is then passed as form data and is saved in the database so that the position value can be used in other pages for showing the ticket in order.

Show Ordered Ticket

In other pages, while showing ordered ticket, we already receive the ticket array in sorted format based on the position key. Hence, we don’t need to worry about it in the front-end and it automatically is shown in the sorted order.

Continue ReadingTicket Ordering and Positioning (Front-end)

Ticket Ordering or Positioning (back-end)

One of the many feature requests that we got for our open event organizer server or the eventyay website is ticket ordering. The event organizers wanted to show the tickets in a particular order in the website and wanted to control the ordering of the ticket. This was a common request by many and also an important enhancement. There were two main things to deal with when ticket ordering was concerned. Firstly, how do we store the position of the ticket in the set of tickets. Secondly, we needed to give an UI in the event creation/edit wizard to control the order or position of a ticket. In this blog, I will talk about how we store the position of the tickets in the backend and use it to show in our public page of the event.

Continue ReadingTicket Ordering or Positioning (back-end)

Implementing Payment and Tax System for Open-Event

So I implemented the payment system and tax system for the payment part in the ticketing system. The first step was making a list of available countries and currency options for the system. So I created and added the following list:

PAYMENT_COUNTRIES = {
    'United States',
    'Argentina',
    'Australia',
    'Austria',
    'Belgium',
    'Brazil',
    'Canada',
    'Cyprus',
    'Czech Republic',
    'Denmark',
    'Estonia',
    'Finland',
    'France',
    'Germany',
    'Greece',
    'Hong Kong',
    'Hungary',
    'Ireland',
    'Israel',
    'Italy',
    'Japan',
    'Latvia',
    'Lithuania',
    'Luxemborg',
    'Malaysia',
    'Malta',
    'Mexico',
    'Netherlands',
    'New Zealand',
    'Norway',
    'Philippines',
    'Poland',
    'Portugal',
    'Singapore',
    'Slovakia',
    'Slovenia',
    'Spain',
    'Sweden',
    'Switzerland',
    'Taiwan',
    'United Kingdom',
}

PAYMENT_CURRENCIES = {
    'ARS Argentine Peso $',
    'AUD Australian Dollars A$',
    'BRL Brazilian Real R$',
    'CAD Canadian Dollars C$',
    'CZK Czech Koruna Kč',
    'DKR Danish Krone Dkr',
    'EUR Euros €',
    'HKD Hong Kong Dollar HK$',
    'HUF Hungarian Forint Ft',
    'ILS Israeli Shekels ₪',
    'JPY Japanese Yen ¥',
    'MYR Malaysian Ringgits RM',
    'MXN Mexican Pesos Mex$',
    'NZD New Zealand Dollar NZ$',
    'NOK Norwegian Krone Nkr',
    'PHP Philippine Pesos ₱',
    'PLN Polish Zloty zł',
    'GBP Pounds Sterling £',
    'SGD Singapore Dollar SG$',
    'SEK Swedish Krona Skr',
    'CHF Swiss Franc Fr',
    'TWD Taiwan New Dollars NT$',
    'THB Thai baht ฿',
    'USD U.S. Dollars $',
}

This is the list of currently supported countries and currencies. Thus the user can choose from the above list in the following step on the first page of event creation:

1

If the user chooses a Paid ticket or a Donation ticket then he/she has to compulsorily choose a country and a currency for the event. Next in line is the system of payments – the way in which the organizer wants payments to be made for his/her event. They include the option of Online Payments and Offline Payments. The organizer has to tick the checkbox in order to enable the payment option. On enabling the PayPal and Stripe checkboxes he/she is represented with the following:

2

On enabling PayPal option the organizer has to enter the PayPal email and for Stripe he has to connect it with Stripe account.

And the final step is the addition of tax for the event. The organizer can choose whether he/she wants to enable tax for the event or not:

3

On choosing Yes he is presented with the tax form:

4

If the organizer wants to send invoices then on enabling invoices another form is displayed with details pertaining to Business Address, Registered Name etc….

5

Finally we have two options that whether we want to display the tax as separate fee or include in the price of tickets.

Continue ReadingImplementing Payment and Tax System for Open-Event

Ticketing System in Open-Event

So we implemented the ticketing system in the open-event. Basically we provide the user with two options – either add his/her own ticket url or use our own ticketing system. If the ticketing module is turned off then there is no option and the user has to add a Ticket URL.

1

2

Thus only Add Ticket URL is shown if the ticketing switch is turned off. However if the ticket switch is turned ON then we display our own ticketing system i.e. provide with an option to choose to the user.

3

Now the ticket feature can be either Free, Paid or by donation. If the ticket feature is free then just the normal ticketing details are entered by the user. However if Paid option is selected then a payment system is displayed to the user  where he/she has to choose the country and the currency in which the user will make the payment.

4

The user can pay through PayPal and we can also decide whether we want to add Tax to the event or not.

Continue ReadingTicketing System in Open-Event