Skip to content

Commit 1834548

Browse files
committed
Improve payment flow in event registrations
1 parent 9e9ee67 commit 1834548

File tree

7 files changed

+515
-28
lines changed

7 files changed

+515
-28
lines changed

src/donations/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .forms import DonationForm
55
from .models import DonationConfig, DonationTier, DonationReceived
66
from temple_web.myconfig import PaymentGatewayConfig
7-
from .upi_gateway import create_order, check_order_status
7+
from utils.payment.upi_gateway import create_order, check_order_status
88
from uuid import uuid4
99
from datetime import date
1010
from utils.adirect import adirect

src/haps/admin.py

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class EventAdmin(admin.ModelAdmin):
5353

5454
@admin.register(EventRegistration)
5555
class EventRegistrationAdmin(admin.ModelAdmin):
56-
search_fields = ["event__name", "user__email", "order_id"]
56+
search_fields = ["event__name", "user__email", "order_id", "client_txn_id"]
5757
list_display = [
5858
"registration_number",
5959
"event",
@@ -62,17 +62,93 @@ class EventRegistrationAdmin(admin.ModelAdmin):
6262
"datetime",
6363
"amount",
6464
"payment_status",
65-
"order_id"
65+
"order_id",
66+
"payment_date_time",
6667
]
6768
list_filter = ["event__name", "payment_status"]
68-
readonly_fields = ["datetime", "form_responses"]
69+
readonly_fields = [
70+
"datetime",
71+
"form_responses",
72+
"payment_status",
73+
"order_id",
74+
"client_txn_id",
75+
"payment_date_time",
76+
"payment_data",
77+
"get_payment_details"
78+
]
79+
80+
fieldsets = [
81+
(None, {
82+
'fields': [
83+
"event",
84+
"user",
85+
"datetime",
86+
"amount",
87+
"form_responses",
88+
]
89+
}),
90+
('Payment Information', {
91+
'fields': [
92+
"payment_status",
93+
"order_id",
94+
"client_txn_id",
95+
"payment_date_time",
96+
],
97+
'classes': ['collapse']
98+
}),
99+
('Payment Diagnostic Data', {
100+
'fields': [
101+
"get_payment_details",
102+
],
103+
'classes': ['collapse'],
104+
'description': 'Detailed payment transaction data from UPI gateway'
105+
})
106+
]
69107

70108
def has_add_permission(self, request):
71-
return False # Registrations should only be created through the website
109+
return False
72110

73111
def get_queryset(self, request):
74112
return super().get_queryset(request).select_related('event', 'user')
75113

76114
def registration_number(self, obj):
77-
return f"#{obj.id}"
115+
return obj.registration_number
78116
registration_number.short_description = "Registration No."
117+
118+
def get_payment_details(self, obj):
119+
if not obj.payment_data:
120+
return "No payment data available"
121+
122+
# Format payment data for display
123+
details = []
124+
if obj.payment_data.get('customer_vpa'):
125+
details.append(f"Customer UPI: {obj.payment_data['customer_vpa']}")
126+
if obj.payment_data.get('upi_txn_id'):
127+
details.append(f"UPI Transaction ID: {obj.payment_data['upi_txn_id']}")
128+
if obj.payment_data.get('status'):
129+
details.append(f"Status: {obj.payment_data['status']}")
130+
if obj.payment_data.get('remark'):
131+
details.append(f"Remark: {obj.payment_data['remark']}")
132+
if obj.payment_data.get('txnAt'):
133+
details.append(f"Transaction Time: {obj.payment_data['txnAt']}")
134+
135+
# Merchant details
136+
merchant = obj.payment_data.get('merchant', {})
137+
if merchant:
138+
details.append("Merchant Details:")
139+
if merchant.get('name'):
140+
details.append(f" - Name: {merchant['name']}")
141+
if merchant.get('upi_id'):
142+
details.append(f" - UPI ID: {merchant['upi_id']}")
143+
144+
# User defined fields
145+
for i in range(1, 4):
146+
udf = obj.payment_data.get(f'udf{i}')
147+
if udf:
148+
details.append(f"UDF{i}: {udf}")
149+
150+
if obj.payment_data.get('createdAt'):
151+
details.append(f"Created At: {obj.payment_data['createdAt']}")
152+
153+
return "\n".join(details)
154+
get_payment_details.short_description = "Payment Details"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Generated by Django 4.2.1 on 2024-12-29 19:34
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("haps", "0005_event_login_required_event_registration_fee_and_more"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="eventregistration",
14+
name="client_txn_id",
15+
field=models.CharField(
16+
blank=True, db_index=True, max_length=128, null=True, unique=True
17+
),
18+
),
19+
migrations.AddField(
20+
model_name="eventregistration",
21+
name="payment_data",
22+
field=models.JSONField(
23+
default=dict,
24+
help_text="\n Stores payment-related data from UPI gateway including:\n - customer_vpa: Customer's UPI ID\n - upi_txn_id: UPI transaction ID\n - status: Detailed payment status\n - remark: Payment remarks/failure reason\n - txnAt: Transaction timestamp\n - merchant: Merchant details (name, upi_id)\n - udf1, udf2, udf3: User defined fields\n - redirect_url: Payment redirect URL\n - createdAt: Order creation time\n ",
25+
),
26+
),
27+
migrations.AddField(
28+
model_name="eventregistration",
29+
name="payment_date_time",
30+
field=models.DateTimeField(blank=True, null=True),
31+
),
32+
]

src/haps/models.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,39 @@ class EventRegistration(models.Model):
7575
datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True)
7676
form_responses = models.JSONField(default=dict)
7777
amount = models.PositiveIntegerField(null=True, blank=True)
78+
79+
# Payment related fields
7880
payment_status = models.CharField(
7981
max_length=10,
8082
choices=PAYMENT_STATUS_CHOICES,
8183
default='pending'
8284
)
8385
order_id = models.CharField(max_length=100, blank=True, null=True)
86+
client_txn_id = models.CharField(max_length=128, unique=True, db_index=True, null=True, blank=True)
87+
payment_date_time = models.DateTimeField(null=True, blank=True)
88+
89+
# Payment diagnostic data from UPI gateway
90+
payment_data = models.JSONField(default=dict, help_text="""
91+
Stores payment-related data from UPI gateway including:
92+
- customer_vpa: Customer's UPI ID
93+
- upi_txn_id: UPI transaction ID
94+
- status: Detailed payment status
95+
- remark: Payment remarks/failure reason
96+
- txnAt: Transaction timestamp
97+
- merchant: Merchant details (name, upi_id)
98+
- udf1, udf2, udf3: User defined fields
99+
- redirect_url: Payment redirect URL
100+
- createdAt: Order creation time
101+
""")
102+
103+
def __str__(self):
104+
return f"Registration #{self.id} - {self.event.name}"
105+
106+
@property
107+
def registration_number(self):
108+
"""Returns a formatted registration number."""
109+
return f"#{self.id}"
110+
84111

85112
def user_name(self):
86113
if self.user:
@@ -99,4 +126,4 @@ def user_profile_link(self):
99126

100127
def __str__(self) -> str:
101128
user_str = str(self.user) if self.user else self.form_responses.get('name', 'Anonymous')
102-
return f"{user_str} % {self.event}"
129+
return f"{user_str} % {self.event}"
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
{% extends "commons.html" %}
2+
3+
{% block title %} {{ event.name }} - Registration Failed {% endblock title %}
4+
{% load haps_extras %}
5+
6+
{% block content %}
7+
<div class="bg-gray-100 min-h-screen py-8">
8+
<div class="bg-white p-6 md:mx-auto max-w-2xl rounded-lg shadow">
9+
<div class="text-red-600 w-16 h-16 mx-auto my-6">
10+
<i class="fa-regular fa-circle-xmark fa-3x"></i>
11+
</div>
12+
13+
<div class="text-center">
14+
<h3 class="md:text-2xl text-base text-gray-900 font-semibold text-center">Registration Failed!</h3>
15+
<p class="text-gray-600 my-2">Your registration for the event is incomplete due to payment failure.</p>
16+
<p class="text-gray-600">Registration #{{registration.id}}</p>
17+
{% if remark %}
18+
<p class="text-red-600 mt-2">{{remark}}</p>
19+
{% endif %}
20+
</div>
21+
22+
<div class="my-8">
23+
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
24+
<tbody>
25+
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
26+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
27+
Event
28+
</th>
29+
<td class="px-6 py-4">
30+
{{event.name}}
31+
</td>
32+
</tr>
33+
34+
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
35+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
36+
Name
37+
</th>
38+
<td class="px-6 py-4">
39+
{% if registration.user %}
40+
{{registration.user.get_full_name|default:"Anonymous User"}}
41+
{% else %}
42+
{{registration.form_responses.name|default:"Anonymous User"}}
43+
{% endif %}
44+
</td>
45+
</tr>
46+
47+
{% if registration.user %}
48+
<tr class="bg-white border-b dark:bg-gray-800">
49+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
50+
Email
51+
</th>
52+
<td class="px-6 py-4">
53+
{{registration.user.email}}
54+
</td>
55+
</tr>
56+
{% endif %}
57+
58+
{% if not registration.user %}
59+
<tr class="bg-white border-b dark:bg-gray-800">
60+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
61+
WhatsApp
62+
</th>
63+
<td class="px-6 py-4">
64+
{{registration.form_responses.whatsapp_number}}
65+
</td>
66+
</tr>
67+
{% endif %}
68+
69+
<tr class="bg-white border-b dark:bg-gray-800">
70+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
71+
Date
72+
</th>
73+
<td class="px-6 py-4">
74+
{{event.start_time}}
75+
</td>
76+
</tr>
77+
78+
<tr class="bg-white border-b dark:bg-gray-800">
79+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
80+
Venue
81+
</th>
82+
<td class="px-6 py-4">
83+
{{event.venue}}
84+
</td>
85+
</tr>
86+
87+
{% if registration.datetime %}
88+
<tr class="bg-white border-b dark:bg-gray-800">
89+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
90+
Registration Time
91+
</th>
92+
<td class="px-6 py-4">
93+
{{registration.datetime}}
94+
</td>
95+
</tr>
96+
{% endif %}
97+
98+
<tr class="bg-white border-b dark:bg-gray-800">
99+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
100+
Registration Fee
101+
</th>
102+
<td class="px-6 py-4">
103+
₹{{event.registration_fee}}
104+
<span class="ml-2 text-xs px-2 py-1 rounded-full bg-red-100 text-red-800">
105+
Payment Failed
106+
</span>
107+
</td>
108+
</tr>
109+
110+
{% for field in event.form_fields.all %}
111+
<tr class="bg-white border-b dark:bg-gray-800">
112+
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
113+
{{field.field_label}}
114+
</th>
115+
<td class="px-6 py-4">
116+
{{registration.form_responses|get_item:field.field_label}}
117+
</td>
118+
</tr>
119+
{% endfor %}
120+
</tbody>
121+
</table>
122+
</div>
123+
124+
<div class="flex justify-center space-x-4">
125+
<a href="{% url 'haps:retry_payment' registration_id=registration.id %}"
126+
class="text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800">
127+
Retry Payment
128+
</a>
129+
130+
<a href="{% url 'haps:event_item' slug=event.slug %}"
131+
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
132+
Back to Event
133+
</a>
134+
</div>
135+
</div>
136+
</div>
137+
{% endblock content %}

src/haps/urls.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
urlpatterns = [
88
path("", views.haps_list, name="events"),
9-
path("<slug>", views.haps_item, name="event_item"),
10-
path("<slug>/register", views.register_for_event, name="event_register"),
9+
path("<slug:slug>", views.haps_item, name="event_item"),
10+
path("<slug:slug>/register", views.register_for_event, name="register"),
11+
path("registration/<int:registration_id>/failure", views.register_failure, name="register_failure"),
12+
path("registration/<int:registration_id>/success", views.register_success, name="register_success"),
13+
path("registration/<int:registration_id>/pay", views.initiate_payment, name="initiate_payment"),
14+
path("registration/<int:registration_id>/retry", views.retry_payment, name="retry_payment"),
15+
path("payment/callback", views.payment_callback, name="payment_callback"),
1116
]

0 commit comments

Comments
 (0)