44from django .contrib import messages
55from django .urls import reverse
66from django .http import HttpResponse , HttpResponseRedirect , Http404
7- from django .shortcuts import redirect , get_object_or_404
7+ from django .shortcuts import redirect
88from django .views .decorators .csrf import csrf_exempt
99from django .views .generic import TemplateView , FormView , View
1010from finance .models import Payment
@@ -71,6 +71,27 @@ def post(self, request, *args, **kwargs):
7171 "and amount %f" %
7272 (order_to_pay .id , amount_to_pay ), user = order_to_pay .user )
7373
74+ # Extra context for debugging before starting external call
75+ try :
76+ has_succeeded_payment = order_to_pay .payments .filter (succeeded = True ).exists ()
77+ except Exception :
78+ has_succeeded_payment = False
79+ log_event (
80+ event = (
81+ "[DEBUG] Payment pre-checks for order %s" % order_to_pay .id
82+ ),
83+ user = order_to_pay .user ,
84+ extra = (
85+ "finalized=%s, paid=%s, succeeded_payment_exists=%s, round_is_open=%s"
86+ % (
87+ order_to_pay .finalized ,
88+ order_to_pay .paid ,
89+ has_succeeded_payment ,
90+ order_to_pay .order_round .is_open ,
91+ )
92+ ),
93+ )
94+
7495 if amount_to_pay == 0 :
7596 log_event (
7697 event = "Payment for order %d not necessary because order total "
@@ -84,8 +105,8 @@ def post(self, request, *args, **kwargs):
84105 order_to_pay .complete_after_payment ()
85106 return redirect (reverse ('order_summary' , args = (order_to_pay .pk ,)))
86107
87- # Sanity checks. If one of these fails, it's very likely that someone
88- # is tampering.
108+ # Sanity checks. If one of these fails, it's very likely that someone
109+ # is tampering.
89110 assert order_to_pay .user == request .user
90111 assert order_to_pay .finalized is True
91112 assert order_to_pay .paid is False
@@ -103,18 +124,55 @@ def post(self, request, *args, **kwargs):
103124 return redirect (reverse ('finish_order' , args = (order_to_pay .id ,)))
104125
105126 # Start the payment
106- results = self .create_payment (
107- amount = float (amount_to_pay ),
108- description = "VOKO Utrecht %d"
109- % order_to_pay .id ,
110- order_id = order_to_pay .id ,
127+ try :
128+ results = self .create_payment (
129+ amount = float (amount_to_pay ),
130+ description = "VOKO Utrecht %d" % order_to_pay .id ,
131+ order_id = order_to_pay .id ,
132+ )
133+ try :
134+ mollie_id = results ["id" ]
135+ except Exception :
136+ mollie_id = getattr (results , "id" , None )
137+ checkout_url = getattr (results , "checkout_url" , None )
138+ status = getattr (results , "status" , None )
139+ log_event (
140+ event = ("[DEBUG] Mollie payment created for order %s" ) % order_to_pay .id ,
141+ user = order_to_pay .user ,
142+ extra = ("mollie_id=%s, status=%s, checkout_url=%s" ) % (mollie_id , status , checkout_url ),
111143 )
144+ except Exception as e :
145+ # Log and surface a friendly error to the user
146+ log_event (
147+ event = (
148+ "[ERROR] Failed to create Mollie payment for order %s: %s"
149+ ) % (order_to_pay .id , e ),
150+ user = order_to_pay .user ,
151+ )
152+ messages .error (request , "Het aanmaken van de betaling is mislukt. Probeer het later opnieuw." )
153+ # Un-freeze order so user can retry
154+ order_to_pay .finalized = False
155+ order_to_pay .save ()
156+ return redirect (reverse ('finish_order' , args = (order_to_pay .id ,)))
112157
113- Payment .objects .create (amount = amount_to_pay ,
114- order = order_to_pay ,
115- mollie_id = results ["id" ])
158+ Payment .objects .create (
159+ amount = amount_to_pay ,
160+ order = order_to_pay ,
161+ mollie_id = mollie_id ,
162+ )
163+ log_event (
164+ event = (
165+ "[DEBUG] Stored local Payment for order %s with mollie_id=%s"
166+ ) % (order_to_pay .id , mollie_id ),
167+ user = order_to_pay .user ,
168+ )
116169
117170 redirect_url = results .checkout_url
171+ log_event (
172+ event = ("[DEBUG] Redirecting user to Mollie checkout for order %s" ) % (order_to_pay .id ),
173+ user = order_to_pay .user ,
174+ extra = ("checkout_url=%s" ) % redirect_url ,
175+ )
118176 return redirect (redirect_url )
119177
120178 def _message_payment_unnecessary (self ):
@@ -141,13 +199,29 @@ def get_context_data(self, **kwargs):
141199
142200 order_id = self .request .GET .get ('order' )
143201
202+ log_event (
203+ event = "[DEBUG] ConfirmTransactionView called" ,
204+ user = self .request .user ,
205+ extra = ("user_id=%s, order_id=%s" ) % (self .request .user .id , order_id ),
206+ )
207+
144208 # Payment may already be confirmed by the webhook
145209 try :
146210 order = Order .objects .get (id = order_id , user = self .request .user )
147211 except Order .DoesNotExist :
212+ log_event (
213+ event = ("[WARN] ConfirmTransactionView: Order not found" ),
214+ user = self .request .user ,
215+ extra = ("user_id=%s, order_id=%s" ) % (self .request .user .id , order_id ),
216+ )
148217 raise Http404
149218
150219 if order .paid is True :
220+ log_event (
221+ event = ("[DEBUG] ConfirmTransactionView: order already paid" ),
222+ user = order .user ,
223+ extra = ("order_id=%s" ) % order .id ,
224+ )
151225 context ['payment_succeeded' ] = True
152226 return context
153227
@@ -156,10 +230,35 @@ def get_context_data(self, **kwargs):
156230 .order_by ('id' ).last ()
157231
158232 if payment is None :
233+ log_event (
234+ event = ("[WARN] ConfirmTransactionView: No pending Payment found" ),
235+ user = order .user ,
236+ extra = ("order_id=%s" ) % order_id ,
237+ )
159238 raise Http404
160239
161- mollie_payment = self .get_payment (payment .mollie_id )
240+ log_event (
241+ event = ("[DEBUG] ConfirmTransactionView: Fetching Mollie payment status" ),
242+ user = order .user ,
243+ extra = ("mollie_id=%s, order_id=%s" ) % (payment .mollie_id , order_id ),
244+ )
245+ try :
246+ mollie_payment = self .get_payment (payment .mollie_id )
247+ except Exception as e :
248+ log_event (
249+ event = ("[ERROR] ConfirmTransactionView: Failed to fetch Mollie payment" ),
250+ user = order .user ,
251+ extra = ("mollie_id=%s, order_id=%s, error=%s" ) % (payment .mollie_id , order_id , e ),
252+ )
253+ context ['payment_succeeded' ] = False
254+ return context
162255 success = mollie_payment .is_paid ()
256+ status = getattr (mollie_payment , 'status' , None )
257+ log_event (
258+ event = ("[DEBUG] ConfirmTransactionView: Mollie payment status fetched" ),
259+ user = order .user ,
260+ extra = ("mollie_id=%s, status=%s, is_paid=%s" ) % (payment .mollie_id , status , success ),
261+ )
163262
164263 if success :
165264 if (payment .order .finalized is True
@@ -182,9 +281,11 @@ def get_context_data(self, **kwargs):
182281 user = payment .order .user )
183282
184283 else :
185- log_event (event = "Payment %s for order %s and amount %f failed" %
186- (payment .id , payment .order .id , payment .amount ),
187- user = payment .order .user )
284+ log_event (
285+ event = ("Payment failed in confirm view" ),
286+ user = payment .order .user ,
287+ extra = ("payment_id=%s, order_id=%s, amount=%s, status=%s" ) % (payment .id , payment .order .id , payment .amount , status ),
288+ )
188289
189290 context ['payment_succeeded' ] = success
190291 return context
@@ -198,15 +299,45 @@ def dispatch(self, request, *args, **kwargs):
198299 def post (self , request , * args , ** kwargs ):
199300 mollie_id = request .POST .get ('id' )
200301
201- payment = get_object_or_404 (Payment , mollie_id = mollie_id )
202- mollie_payment = self .get_payment (payment .mollie_id )
302+ if not mollie_id :
303+ log_event (event = "[WARN] Webhook called without Mollie id" , user = None )
304+ return HttpResponse ("" , status = 404 )
305+
306+ remote = request .META .get ('HTTP_X_FORWARDED_FOR' ) or request .META .get ('REMOTE_ADDR' )
307+ log_event (
308+ event = ("[DEBUG] Webhook received" ),
309+ user = None ,
310+ extra = ("mollie_id=%s, remote=%s" ) % (mollie_id , remote ),
311+ )
312+
313+ try :
314+ payment = Payment .objects .get (mollie_id = mollie_id )
315+ except Payment .DoesNotExist :
316+ log_event (event = ("[WARN] Webhook for unknown mollie_id" ), user = None , extra = ("mollie_id=%s" ) % mollie_id )
317+ return HttpResponse ("unknown id" , status = 404 )
318+
319+ try :
320+ mollie_payment = self .get_payment (payment .mollie_id )
321+ except Exception as e :
322+ log_event (
323+ event = ("[ERROR] Webhook: Failed to fetch Mollie payment" ),
324+ user = payment .order .user ,
325+ extra = ("mollie_id=%s, order_id=%s, error=%s" ) % (payment .mollie_id , payment .order .id , e ),
326+ )
327+ return HttpResponse ("error" , status = 500 )
203328 success = mollie_payment .is_paid ()
329+ status = getattr (mollie_payment , 'status' , None )
330+ log_event (
331+ event = ("[DEBUG] Webhook Mollie status fetched" ),
332+ user = payment .order .user ,
333+ extra = ("mollie_id=%s, status=%s, is_paid=%s, order_id=%s" ) % (payment .mollie_id , status , success , payment .order .id ),
334+ )
204335
205336 if not success :
206337 log_event (
207338 event = "Payment %s for order %s and amount %f "
208- "failed via callback" %
209- (payment .id , payment .order .id , payment .amount ),
339+ "failed via callback (Mollie status=%s) " %
340+ (payment .id , payment .order .id , payment .amount , status ),
210341 user = payment .order .user )
211342 return HttpResponse ("" )
212343
0 commit comments