Skip to content

Commit 03ba7d4

Browse files
tobiascadeebveldkamp
authored andcommitted
Enhance payment processing with additional logging and error handling in CreateTransactionView and ConfirmTransactionView
1 parent 204e17d commit 03ba7d4

File tree

1 file changed

+150
-19
lines changed

1 file changed

+150
-19
lines changed

webapp/finance/views.py

Lines changed: 150 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.contrib import messages
55
from django.urls import reverse
66
from django.http import HttpResponse, HttpResponseRedirect, Http404
7-
from django.shortcuts import redirect, get_object_or_404
7+
from django.shortcuts import redirect
88
from django.views.decorators.csrf import csrf_exempt
99
from django.views.generic import TemplateView, FormView, View
1010
from 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

Comments
 (0)