Implement Order Confirmation Feature in Eventyay

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.js
async 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) {
        this.notify.error(this.l10n.t('Only 5 resend actions are allowed in a minute'));
      }
      if (error.errors[0].detail) {
        this.notify.error(this.l10n.t(error.errors[0].detail));
      }
    }
  }

Using a simple post request, this was implemented on the frontend for sending the confirmation, but the additional work to be done was to handle the new error (429 status). The server throws the error but loader service hasn’t been configured yet to handle this error appropriately.

// app/services/loader.js
  if (!response.ok) {
    const defaultMessage = httpStatus[response.status];
    if (parsedResponse) {
      throw parsedResponse;
    }
    if (response.status === 429) {
      throw { status: 429, message: ‘TOO MANY REQUESTS’ };
    }
    throw new Error(
      getErrorMessage(
        response.statusText,
        defaultMessage
          ? `${response.status} – ${defaultMessage}`
          : `Could not make ${fetchOptions.type} request to ${fetchOptions.url}`
      )
    );
  }

The loader service has been modified in the following manner to accommodate the new error been thrown so that a more user friendly error could be shown on the controller level.

This was the whole mechanism which has been implemented for this particular problem. 

Resources

Related Work and Code Repository

Close Menu