fossasia/open-event-orga-server

View on GitHub
app/api/orders.py

Summary

Maintainability
F
3 days
Test Coverage
import json
import logging
import time

import omise
import requests
from flask import Blueprint, current_app, jsonify, redirect, request, url_for
from flask_jwt_extended import current_user
from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
from marshmallow_jsonapi import fields
from marshmallow_jsonapi.flask import Schema

from app.api.bootstrap import api
from app.api.data_layers.ChargesLayer import ChargesLayer
from app.api.helpers.custom_forms import validate_custom_form_constraints_request
from app.api.helpers.db import safe_query, safe_query_by_id, safe_query_kwargs, save_to_db
from app.api.helpers.errors import (
    BadRequestError,
    ConflictError,
    ForbiddenError,
    UnprocessableEntityError,
)
from app.api.helpers.files import make_frontend_url
from app.api.helpers.mail import send_order_cancel_email
from app.api.helpers.notification import notify_ticket_cancel
from app.api.helpers.order import (
    create_onsite_attendees_for_order,
    delete_related_attendees_for_order,
    on_order_completed,
)
from app.api.helpers.payment import (
    AliPayPaymentsManager,
    OmisePaymentsManager,
    PayPalPaymentsManager,
    PaytmPaymentsManager,
)
from app.api.helpers.permission_manager import has_access
from app.api.helpers.permissions import jwt_required
from app.api.helpers.query import event_query
from app.api.helpers.ticketing import validate_discount_code, validate_ticket_holders
from app.api.helpers.utilities import dasherize, require_relationship
from app.api.schema.attendees import AttendeeSchema
from app.api.schema.orders import OrderSchema
from app.models import db
from app.models.order import Order, OrderTicket, get_updatable_fields
from app.models.ticket_holder import TicketHolder
from app.models.user import User
from app.settings import get_settings

order_misc_routes = Blueprint('order_misc', __name__, url_prefix='/v1')
alipay_blueprint = Blueprint('alipay_blueprint', __name__, url_prefix='/v1/alipay')
stripe_blueprint = Blueprint('stripe_blueprint', __name__, url_prefix='/v1/stripe')


def check_event_user_ticket_holders(order, data, element):
    if element in ['event', 'user'] and data[element] != str(
        getattr(order, element, None).id
    ):
        raise ForbiddenError(
            {'pointer': f'data/{element}'},
            f"You cannot update {element} of an order",
        )
    if element == 'ticket_holders':
        ticket_holders = []
        for ticket_holder in order.ticket_holders:
            ticket_holders.append(str(ticket_holder.id))
        if data[element] != ticket_holders and element not in get_updatable_fields():
            raise ForbiddenError(
                {'pointer': f'data/{element}'},
                f"You cannot update {element} of an order",
            )


def is_payment_valid(order, mode):
    if mode == 'stripe':
        return (
            (order.paid_via == 'stripe')
            and order.brand
            and order.transaction_id
            and order.exp_year
            and order.last4
            and order.exp_month
        )
    if mode == 'paypal':
        return (order.paid_via == 'paypal') and order.transaction_id


def check_billing_info(data):
    if (
        data.get('amount')
        and data.get('amount') > 0
        and not data.get('is_billing_enabled')
    ):
        raise UnprocessableEntityError(
            {'pointer': '/data/attributes/is_billing_enabled'},
            "Billing information is mandatory for this order",
        )
    if data.get('is_billing_enabled') and not (
        data.get('company')
        and data.get('address')
        and data.get('city')
        and data.get('zipcode')
        and data.get('country')
    ):
        raise UnprocessableEntityError(
            {'pointer': '/data/attributes/is_billing_enabled'},
            "Billing information incomplete",
        )


def save_order(order):
    order_tickets = {}
    for holder in order.ticket_holders:
        save_to_db(holder)
        if not order_tickets.get(holder.ticket_id):
            order_tickets[holder.ticket_id] = 1
        else:
            order_tickets[holder.ticket_id] += 1

    order.user = current_user

    for ticket in order_tickets:
        od = OrderTicket(
            order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket]
        )
        save_to_db(od)

    order.quantity = order.tickets_count
    save_to_db(order)

    on_order_completed(order)


def validate_attendees(ticket_holders):
    free_ticket_quantity = 0

    for ticket_holder in validate_ticket_holders(ticket_holders):
        if ticket_holder.ticket.type == 'free':
            free_ticket_quantity += 1

    if not current_app.config['ALLOW_UNVERIFIED_FREE_ORDERS'] and (
        not current_user.is_verified and free_ticket_quantity == len(ticket_holders)
    ):
        raise ForbiddenError(
            {'pointer': '/data/relationships/user', 'code': 'unverified-user'},
            "Unverified user cannot place free orders",
        )


class OrdersListPost(ResourceList):
    """
    OrderListPost class for OrderSchema
    """

    def before_post(self, args, kwargs, data=None):
        """
        before post method to check for required relationships and permissions
        :param args:
        :param kwargs:
        :param data:
        :return:
        """
        require_relationship(['event'], data)

        # Create on site attendees.
        if request.args.get('onsite', False):
            create_onsite_attendees_for_order(data)
        elif data.get('on_site_tickets'):
            del data['on_site_tickets']
        require_relationship(['ticket_holders'], data)

        # Ensuring that default status is always initializing, unless the user is event co-organizer
        if not has_access('is_coorganizer', event_id=data['event']):
            data['status'] = 'initializing'

    def before_create_object(self, data, view_kwargs):
        """
        before create object method for OrderListPost Class
        :param data:
        :param view_kwargs:
        :return:
        """
        validate_attendees(data['ticket_holders'])

        if data.get('cancel_note'):
            del data['cancel_note']

        if data.get('payment_mode') != 'free' and not data.get('amount'):
            raise ConflictError(
                {'pointer': '/data/attributes/amount'},
                "Amount cannot be null for a paid order",
            )

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

    def after_create_object(self, order, data, view_kwargs):
        """
        after create object method for OrderListPost Class
        :param order: Object created from mashmallow_jsonapi
        :param data:
        :param view_kwargs:
        :return:
        """
        save_order(order)

        data['user_id'] = current_user.id

    methods = [
        'POST',
    ]
    decorators = (jwt_required,)
    schema = OrderSchema
    data_layer = {
        'session': db.session,
        'model': Order,
        'methods': {
            'before_create_object': before_create_object,
            'after_create_object': after_create_object,
        },
    }


class OrdersList(ResourceList):
    """
    OrderList class for OrderSchema
    """

    def query(self, view_kwargs):
        query_ = self.session.query(Order)
        if view_kwargs.get('user_id'):
            # orders under a user
            user = safe_query_kwargs(User, view_kwargs, 'user_id')
            if not has_access('is_user_itself', user_id=user.id):
                raise ForbiddenError({'source': ''}, 'Access Forbidden')
            query_ = query_.join(TicketHolder).filter(TicketHolder.email == user.email)
        else:
            # orders under an event
            query_ = event_query(query_, view_kwargs, restrict=True)

        return query_

    decorators = (jwt_required,)
    methods = [
        'GET',
    ]
    schema = OrderSchema
    data_layer = {'session': db.session, 'model': Order, 'methods': {'query': query}}


class OrderDetail(ResourceDetail):
    """
    OrderDetail class for OrderSchema
    """

    def before_get_object(self, view_kwargs):
        """
        before get method to get the resource id for fetching details
        :param view_kwargs:
        :return:
        """
        if view_kwargs.get('attendee_id'):
            attendee = safe_query_kwargs(TicketHolder, view_kwargs, 'attendee_id')
            view_kwargs['id'] = attendee.order.id
        if view_kwargs.get('order_identifier'):
            order = safe_query_kwargs(
                Order, view_kwargs, 'order_identifier', 'identifier'
            )
            view_kwargs['id'] = order.id
        elif view_kwargs.get('id'):
            order = safe_query_by_id(Order, view_kwargs['id'])

        if not (
            has_access(
                'is_coorganizer_or_user_itself',
                event_id=order.event_id,
                user_id=order.user_id,
            )
            or order.is_attendee(current_user)
        ):
            raise ForbiddenError(
                {'source': ''}, 'You can only access your orders or your event\'s orders'
            )

    def before_update_object(self, order, data, view_kwargs):
        """
        before update object method of order details
        1. admin can update all the fields.
        2. event organizer
            a. own orders: he/she can update selected fields.
            b. other's orders: can only update the status that too when the order mode is free. No refund system.
        3. order user can update selected fields of his/her order when the status is initializing.
        The selected fields mentioned above can be taken from get_updatable_fields method from order model.
        :param order:
        :param data:
        :param view_kwargs:
        :return:
        """
        if (
            data.get('status') in ['pending', 'placed', 'completed']
            and order.event.is_ticket_form_enabled
        ):
            attendees = order.ticket_holders
            for attendee in attendees:
                validate_custom_form_constraints_request(
                    'attendee', AttendeeSchema, attendee, {}
                )

        if data.get('amount') and (
            data.get('is_billing_enabled') or order.event.is_billing_info_mandatory
        ):
            check_billing_info(data)
        if (not has_access('is_coorganizer', event_id=order.event_id)) and (
            not current_user.id == order.user_id
        ):
            raise ForbiddenError({'pointer': ''}, "Access Forbidden")

        relationships = ['event', 'ticket_holders', 'user']
        if has_access('is_coorganizer_but_not_admin', event_id=order.event_id):
            if current_user.id == order.user_id:
                # Order created from the tickets tab.
                for element in data:
                    if data[element]:
                        if (
                            element not in relationships
                            and data[element] != getattr(order, element, None)
                            and element not in get_updatable_fields()
                        ):
                            raise ForbiddenError(
                                {'pointer': f'data/{element}'},
                                f"You cannot update {element} of an order",
                            )
                        check_event_user_ticket_holders(order, data, element)

            else:
                # Order created from the public pages.
                for element in data:
                    if data[element]:
                        if element not in relationships and data[element] != getattr(
                            order, element, None
                        ):
                            if element != 'status':
                                raise ForbiddenError(
                                    {'pointer': f'data/{element}'},
                                    f"You cannot update {element} of an order",
                                )
                            if (
                                element == 'status'
                                and order.amount
                                and order.status == 'completed'
                            ):
                                # Since we don't have a refund system.
                                raise ForbiddenError(
                                    {'pointer': 'data/status'},
                                    "You cannot update the status of a completed paid order",
                                )
                            if element == 'status' and order.status == 'cancelled':
                                # Since the tickets have been unlocked and we can't revert it.
                                raise ForbiddenError(
                                    {'pointer': 'data/status'},
                                    "You cannot update the status of a cancelled order",
                                )
                        else:
                            check_event_user_ticket_holders(order, data, element)

        elif current_user.id == order.user_id:
            status = data.get('status')
            if (
                order.status != Order.Status.INITIALIZING
                and order.status != Order.Status.PENDING
                and status != Order.Status.CANCELLED
            ):
                raise ForbiddenError(
                    {'pointer': ''},
                    "You cannot update a non-initialized or non-pending order",
                )
            if status == Order.Status.CANCELLED:
                # If this is a cancellation request, we revert all PATCH changes except status = cancelled
                data.clear()
                data['status'] = Order.Status.CANCELLED
            for element in data:
                if data[element]:
                    if (
                        element == 'is_billing_enabled'
                        and order.status == 'completed'
                        and data[element] != getattr(order, element, None)
                    ):
                        raise ForbiddenError(
                            {'pointer': f'data/{element}'},
                            f"You cannot update {element} of a completed order",
                        )
                    if (
                        element not in relationships
                        and data[element] != getattr(order, element, None)
                        and element not in get_updatable_fields()
                    ):
                        raise ForbiddenError(
                            {'pointer': f'data/{element}'},
                            f"You cannot update {element} of an order",
                        )
                    check_event_user_ticket_holders(order, data, element)

        if has_access('is_organizer', event_id=order.event_id) and 'order_notes' in data:
            if order.order_notes and data['order_notes'] not in order.order_notes.split(
                ","
            ):
                data['order_notes'] = '{},{}'.format(
                    order.order_notes, data['order_notes']
                )

        if data.get('payment_mode') == 'free' and data.get('amount') > 0:
            raise UnprocessableEntityError(
                {'pointer': '/data/attributes/payment-mode'},
                "payment-mode cannot be free for order with amount > 0",
            )
        if (
            data.get('status') == 'completed'
            and data.get('payment_mode') == 'stripe'
            and not is_payment_valid(order, 'stripe')
        ):
            raise UnprocessableEntityError(
                {'pointer': '/data/attributes/payment-mode'},
                "insufficient data to verify stripe payment",
            )
        if (
            data.get('status') == 'completed'
            and data.get('payment_mode') == 'paypal'
            and not is_payment_valid(order, 'paypal')
        ):
            raise UnprocessableEntityError(
                {'pointer': '/data/attributes/payment-mode'},
                "insufficient data to verify paypal payment",
            )

    def after_update_object(self, order, data, view_kwargs):
        """
        :param order:
        :param data:
        :param view_kwargs:
        :return:
        """

        if order.status == 'cancelled':
            send_order_cancel_email(order)
            notify_ticket_cancel(order, current_user)

            # delete the attendees so that the tickets are unlocked.
            delete_related_attendees_for_order(order)

        elif order.status == 'completed' or order.status == 'placed':
            on_order_completed(order)

    # This is to ensure that the permissions manager runs and hence changes the kwarg from order identifier to id.
    decorators = (
        jwt_required,
        api.has_permission('auth_required', methods="PATCH", model=Order),
    )
    schema = OrderSchema
    data_layer = {
        'session': db.session,
        'model': Order,
        'methods': {
            'before_update_object': before_update_object,
            'before_get_object': before_get_object,
            'after_update_object': after_update_object,
        },
    }


class OrderRelationship(ResourceRelationship):
    """
    Order relationship
    """

    def before_get(self, args, kwargs):
        """
        before get method to get the resource id for fetching details
        :param view_kwargs:
        :return:
        """
        if kwargs.get('order_identifier'):
            order = safe_query_kwargs(Order, kwargs, 'order_identifier', 'identifier')
            kwargs['id'] = order.id
        elif kwargs.get('id'):
            order = safe_query_by_id(Order, kwargs['id'])

        if not has_access(
            'is_coorganizer', event_id=order.event_id, user_id=order.user_id
        ):
            raise ForbiddenError(
                {'source': ''}, 'You can only access your orders or your event\'s orders'
            )

    decorators = (jwt_required,)
    schema = OrderSchema
    data_layer = {'session': db.session, 'model': Order}


class ChargeSchema(Schema):
    """
    ChargeSchema
    """

    class Meta:
        """
        Meta class for ChargeSchema
        """

        type_ = 'charge'
        inflect = dasherize
        self_view = 'v1.charge_list'
        self_view_kwargs = {'order_identifier': '<id>'}

    id = fields.Str(dump_only=True)
    stripe = fields.Str(load_only=True, allow_none=True)
    paypal_payer_id = fields.Str(load_only=True, allow_none=True)
    paypal_payment_id = fields.Str(load_only=True, allow_none=True)
    status = fields.Boolean(dump_only=True)
    message = fields.Str(dump_only=True)


class ChargeList(ResourceList):
    """
    ChargeList ResourceList for ChargesLayer class
    """

    methods = [
        'POST',
    ]
    schema = ChargeSchema

    data_layer = {'class': ChargesLayer, 'session': db.session, 'model': Order}

    decorators = (jwt_required,)


@order_misc_routes.route(
    '/orders/<string:order_identifier>/create-paypal-payment', methods=['POST']
)
@jwt_required
def create_paypal_payment(order_identifier):
    """
    Create a paypal payment.
    :return: The payment id of the created payment.
    """
    try:
        return_url = request.json['data']['attributes']['return-url']
        cancel_url = request.json['data']['attributes']['cancel-url']
    except TypeError:
        raise BadRequestError({'source': ''}, 'Bad Request Error')

    order = safe_query(Order, 'identifier', order_identifier, 'identifier')
    status, response = PayPalPaymentsManager.create_payment(order, return_url, cancel_url)

    if status:
        return jsonify(status=True, payment_id=response)
    return jsonify(status=False, error=response)


@order_misc_routes.route(
    '/orders/<string:order_identifier>/verify-mobile-paypal-payment', methods=['POST']
)
@jwt_required
def verify_mobile_paypal_payment(order_identifier):
    """
    Verify paypal payment made on mobile client
    :return: The status of order verification
    """
    try:
        payment_id = request.json['data']['attributes']['payment-id']
    except TypeError:
        raise BadRequestError({'source': ''}, 'Bad Request Error')
    order = safe_query(Order, 'identifier', order_identifier, 'identifier')
    status, error = PayPalPaymentsManager.verify_payment(payment_id, order)
    return jsonify(status=status, error=error)


@alipay_blueprint.route(
    '/create_source/<string:order_identifier>', methods=['GET', 'POST']
)
@jwt_required
def create_source(order_identifier):
    """
    Create a source object for alipay payments.
    :param order_identifier:
    :return: The alipay redirection link.
    """
    try:
        order = safe_query(Order, 'identifier', order_identifier, 'identifier')
        source_object = AliPayPaymentsManager.create_source(
            amount=int(order.amount),
            currency='usd',
            redirect_return_uri=url_for(
                'alipay_blueprint.alipay_return_uri',
                order_identifier=order.identifier,
                _external=True,
            ),
        )
        order.order_notes = source_object.id
        save_to_db(order)
        return jsonify(link=source_object.redirect['url'])
    except TypeError:
        raise BadRequestError({'source': ''}, 'Source creation error')


@alipay_blueprint.route(
    '/alipay_return_uri/<string:order_identifier>', methods=['GET', 'POST']
)
def alipay_return_uri(order_identifier):
    """
    Charge Object creation & Order finalization for Alipay payments.
    :param order_identifier:
    :return: JSON response of the payment status.
    """
    try:
        charge_response = AliPayPaymentsManager.charge_source(order_identifier)
        if charge_response.status == 'succeeded':
            order = safe_query(Order, 'identifier', order_identifier, 'identifier')
            order.status = 'completed'
            save_to_db(order)
            return redirect(make_frontend_url(f'/orders/{order_identifier}/view'))
        return jsonify(status=False, error='Charge object failure')
    except TypeError:
        return jsonify(status=False, error='Source object status error')


@order_misc_routes.route(
    '/orders/<string:order_identifier>/omise-checkout', methods=['POST', 'GET']
)
@jwt_required
def omise_checkout(order_identifier):
    """
    Charging the user and returning payment response for Omise Gateway
    :param order_identifier:
    :return: JSON response of the payment status.
    """
    token = request.form.get('omiseToken')
    order = safe_query(Order, 'identifier', order_identifier, 'identifier')
    order.status = 'completed'
    save_to_db(order)
    try:
        charge = OmisePaymentsManager.charge_payment(order_identifier, token)
    except omise.errors.BaseError as e:
        logging.exception(
            f"""OmiseError: {repr(e)}.  See https://www.omise.co/api-errors"""
        )
        return jsonify(status=False, error=f"Omise Failure Message: {str(e)}")
    except Exception:
        logging.exception('Error while charging omise')
    if charge.failure_code is not None:
        logging.warning(
            "Omise Failure Message: {} ({})".format(
                charge.failure_message, charge.failure_code
            )
        )
        return jsonify(
            status=False,
            error="Omise Failure Message: {} ({})".format(
                charge.failure_message, charge.failure_code
            ),
        )
    logging.info(f"Successful charge: {charge.id}.  Order ID: {order_identifier}")

    return redirect(make_frontend_url(f'orders/{order_identifier}/view'))


@order_misc_routes.route(
    '/orders/<string:order_identifier>/paytm/initiate-transaction',
    methods=['POST', 'GET'],
)
@jwt_required
def initiate_transaction(order_identifier):
    """
    Initiating a PayTM transaction to obtain the txn token
    :param order_identifier:
    :return: JSON response containing the signature & txn token
    """
    order = safe_query(Order, 'identifier', order_identifier, 'identifier')
    paytm_mode = get_settings()['paytm_mode']
    paytm_params = {}
    # body parameters
    paytm_params["body"] = {
        "requestType": "Payment",
        "mid": (
            get_settings()['paytm_sandbox_merchant']
            if paytm_mode == 'test'
            else get_settings()['paytm_live_merchant']
        ),
        "websiteName": "eventyay",
        "orderId": order_identifier,
        "callbackUrl": "",
        "txnAmount": {
            "value": order.amount,
            "currency": "INR",
        },
        "userInfo": {
            "custId": order.user.id,
        },
    }
    checksum = PaytmPaymentsManager.generate_checksum(paytm_params)
    # head parameters
    paytm_params["head"] = {"signature": checksum}
    post_data = json.dumps(paytm_params)
    if paytm_mode == 'test':
        url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={}&orderId={}".format(
            get_settings()['paytm_sandbox_merchant'], order_identifier
        )
    else:
        url = "https://securegw.paytm.in/theia/api/v1/initiateTransaction?mid={}&orderId={}".format(
            get_settings()['paytm_live_merchant'], order_identifier
        )
    response = requests.post(
        url, data=post_data, headers={"Content-type": "application/json"}
    )
    return response.json()


@order_misc_routes.route(
    '/orders/<string:order_identifier>/paytm/fetch-payment-options/<string:txn_token>'
)
@jwt_required
def fetch_payment_options(order_identifier, txn_token):
    paytm_mode = get_settings()['paytm_mode']
    if paytm_mode == 'test':
        url = "https://securegw-stage.paytm.in/theia/api/v1/fetchPaymentOptions?mid={}&orderId={}".format(
            get_settings()['paytm_sandbox_merchant'], order_identifier
        )
    else:
        url = "https://securegw.paytm.in/theia/api/v1/fetchPaymentOptions?mid={}&orderId={}".format(
            get_settings()['paytm_live_merchant'], order_identifier
        )
    head = {
        "clientId": "C11",
        "version": "v1",
        "requestTimestamp": str(int(time.time())),
        "channelId": "WEB",
        "txnToken": txn_token,
    }
    response = PaytmPaymentsManager.hit_paytm_endpoint(url=url, head=head)
    return response


@order_misc_routes.route(
    '/orders/<string:order_identifier>/paytm/send_otp/<string:txn_token>',
    methods=['POST'],
)
@jwt_required
def send_otp(order_identifier, txn_token):
    paytm_mode = get_settings()['paytm_mode']
    if paytm_mode == 'test':
        url = "https://securegw-stage.paytm.in/theia/api/v1/login/sendOtp?mid={}&orderId={}".format(
            get_settings()['paytm_sandbox_merchant'], order_identifier
        )
    else:
        url = "https://securegw.paytm.in/theia/api/v1/login/sendOtp?mid={}&orderId={}".format(
            get_settings()['paytm_live_merchant'], order_identifier
        )

    head = {
        "clientId": "C11",
        "version": "v1",
        "requestTimestamp": str(int(time.time())),
        "channelId": "WEB",
        "txnToken": txn_token,
    }
    body = {"mobileNumber": request.json['data']['phone']}
    response = PaytmPaymentsManager.hit_paytm_endpoint(url=url, head=head, body=body)
    return response


@order_misc_routes.route(
    '/orders/<string:order_identifier>/paytm/validate_otp/<string:txn_token>',
    methods=['POST'],
)
def validate_otp(order_identifier, txn_token):
    paytm_mode = get_settings()['paytm_mode']
    if paytm_mode == 'test':
        url = "https://securegw-stage.paytm.in/theia/api/v1/login/validateOtp?mid={}&orderId={}".format(
            get_settings()['paytm_sandbox_merchant'], order_identifier
        )
    else:
        url = "https://securegw.paytm.in/theia/api/v1/login/validateOtp?mid={}&orderId={}".format(
            get_settings()['paytm_live_merchant'], order_identifier
        )
    head = {
        "clientId": "C11",
        "version": "v1",
        "requestTimestamp": str(int(time.time())),
        "channelId": "WEB",
        "txnToken": txn_token,
    }
    body = {"otp": request.json['data']['otp']}
    response = PaytmPaymentsManager.hit_paytm_endpoint(url=url, head=head, body=body)
    return response


@order_misc_routes.route(
    '/orders/<string:order_identifier>/paytm/process_transaction/<string:txn_token>'
)
@jwt_required
def process_transaction(order_identifier, txn_token):
    paytm_mode = get_settings()['paytm_mode']
    merchant_id = (
        get_settings()['paytm_sandbox_merchant']
        if paytm_mode == 'test'
        else get_settings()['paytm_live_merchant']
    )

    if paytm_mode == 'test':
        url = "https://securegw-stage.paytm.in/theia/api/v1/processTransaction?mid={}&orderId={}".format(
            get_settings()['paytm_sandbox_merchant'], order_identifier
        )
    else:
        url = "https://securegw.paytm.in/theia/api/v1/processTransaction?mid={}&orderId={}".format(
            get_settings()['paytm_live_merchant'], order_identifier
        )

    head = {
        "version": "v1",
        "requestTimestamp": str(int(time.time())),
        "channelId": "WEB",
        "txnToken": txn_token,
    }

    body = {
        "requestType": "NATIVE",
        "mid": merchant_id,
        "orderId": order_identifier,
        "paymentMode": "BALANCE",
    }

    response = PaytmPaymentsManager.hit_paytm_endpoint(url=url, head=head, body=body)
    return response


@order_misc_routes.route(
    '/orders/<string:order_identifier>/paytm/transaction-status', methods=['GET']
)
def get_transaction_status(order_identifier):
    paytm_params = {}
    paytm_checksum_params = {}
    url = ""
    paytm_mode = get_settings()['paytm_mode']
    merchant_id = (
        get_settings()['paytm_sandbox_merchant']
        if paytm_mode == 'test'
        else get_settings()['paytm_live_merchant']
    )
    paytm_checksum_params["body"] = {"mid": merchant_id, "orderId": order_identifier}
    checksum = PaytmPaymentsManager.generate_checksum(paytm_checksum_params)

    paytm_params["MID"] = merchant_id
    paytm_params["ORDERID"] = order_identifier
    paytm_params["CHECKSUMHASH"] = checksum
    post_data = json.dumps(paytm_params)

    if paytm_mode == 'test':
        url = "https://securegw-stage.paytm.in/order/status"
    else:
        url = "https://securegw.paytm.in/order/status"
    response = requests.post(
        url, data=post_data, headers={"Content-type": "application/json"}
    ).json()
    return response