Repository: fossasia/open-event-server

Issue:
Paid tickets can be received without actually paying
**Describe the bug**
Under certain circumstances, the payment checkout is skipped, and paid tickets are issued without charging money

**To Reproduce**
Environment: backend 1.3.0 + frontend 1.1.0.
Browsers used: Safari and Chrome on OS/X

Steps to reproduce the behaviour:
1. Set-up Stripe in the system settings as a payment gateway, with a test set of client-id and keys
2. Create an event with 1 or more tiers of paid tickets, and with Stripe as the only means of payment. _Don't connect it to your Stripe account_, however.
3. Purchase a ticket for the event, and go to checkout. A ticket will be issued without taking the user to the payment page.

**Expected behaviour**
Give the user an error. e.g. that the payment processor couldn't be reached.

**Additional detail:**
You're very welcome to register on my staging server and reproduce it there:
https://open-event-server.casapu.pro/ or deploy a compatible environment from: https://github.com/yitzikc/open-event-integration




Discussion:
Issue-Label Bot is automatically applying the label `bug` to this issue, with a confidence of 0.99. Please mark this comment with :thumbsup: or :thumbsdown: to give our bot feedback! 

 Links: [app homepage](https://github.com/marketplace/issue-label-bot), [dashboard](https://mlbot.net/data/fossasia/open-event-server) and [code](https://github.com/hamelsmu/MLapp) for this bot.
I explored the issue and its possible fixes. I found few things -
- We should force stripe authorization if we select it as a possible payment method. Currently, it is not there
- We already have a function to check if we have a payment method enabled if we have a paid ticket. It just needs to include stripe authorization.
- FE also needs a few changes. Tickets are saved before stripe authorization in FE, which needs to be changed to be able to check for stripe authorization while creating a ticket.

@iamareebjamal should I start with this approach?
Thanks for the quick response! I believe the issue could be broader and affect PayPal as well, although I haven't finalised the PayPal setup so I'm not sure about that.
@shreyanshdwivedi This should be mostly handled on server without any complex check. Before issuing any paid ticket, simply check the payment mode and use the API stripe/paypal to confirm that the payment has actually been made. 
@yitzikc can you please add a gif or screenshot of the page you are finally getting to
I can later (tonight your time). In the meantime you're very much invited to try this use-case on my staging site, at the link I provided in the ticket.
Thanks. Have checked it out on your site. Saw your error
Will work on the fix right away

Resolving patch:
diff --git a/app/api/orders.py b/app/api/orders.py
--- a/app/api/orders.py
+++ b/app/api/orders.py
@@ -54,6 +54,14 @@ def check_event_user_ticket_holders(order, data, element):
                                      "You cannot update {} of an order".format(element))
 
 
+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
+    elif mode == 'paypal':
+        return (order.paid_via == 'paypal') and order.transaction_id
+
+
 class OrdersListPost(ResourceList):
     """
     OrderListPost class for OrderSchema
@@ -348,6 +356,18 @@ def before_update_object(self, order, data, view_kwargs):
             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 UnprocessableEntity({'pointer': '/data/attributes/payment-mode'},
+                                      "payment-mode cannot be free for order with amount > 0")
+        elif data.get('status') == 'completed' and data.get('payment_mode') == 'stripe' and \
+                not is_payment_valid(order, 'stripe'):
+            raise UnprocessableEntity({'pointer': '/data/attributes/payment-mode'},
+                                      "insufficient data to verify stripe payment")
+        elif data.get('status') == 'completed' and data.get('payment_mode') == 'paypal' and \
+                not is_payment_valid(order, 'paypal'):
+            raise UnprocessableEntity({'pointer': '/data/attributes/payment-mode'},
+                                      "insufficient data to verify paypal payment")
+
     def after_update_object(self, order, data, view_kwargs):
         """
         :param order:


Test patch: