diff --git a/GEMINI_RESPONSE.md b/GEMINI_RESPONSE.md new file mode 100644 index 0000000..5a455be --- /dev/null +++ b/GEMINI_RESPONSE.md @@ -0,0 +1,22 @@ +Plan: + +1. **Add Configuration:** I have added a new field `enable_payment` (checkbox) to the **Platform Profile** model. +2. **Enforce Logic:** I updated the payment initiation view (`initiate_payment`) to check this flag. If disabled, users cannot start a payment. +3. **Update UI:** I updated the **Shipper Dashboard** to hide the "Pay Now" button if payments are disabled. +4. **Deploy:** Created migrations and restarted the server. + +Changed (if any): + +* `core/models.py`: Added `enable_payment` boolean field to `PlatformProfile`. +* `core/views.py`: Added a check in `initiate_payment` to block payments if disabled. +* `core/templates/core/shipper_dashboard.html`: Wrapped the "Pay Now" button in a conditional check. + +Notes: + +* **Action Required:** Go to **Django Admin > Platform Profiles**. You will see a new checkbox **Enable Payment**. + * **Checked:** Users can pay. + * **Unchecked:** The "Pay Now" button disappears, and direct URL access is blocked. + +Next: You can test this by toggling the checkbox in the admin and refreshing your dashboard. What's next? + +Reminder: click Save in the editor to sync changes. \ No newline at end of file diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 21e051d..803d8c8 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 6253a24..6791f9c 100644 --- a/config/settings.py +++ b/config/settings.py @@ -174,8 +174,8 @@ EMAIL_BACKEND = os.getenv( "EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend" ) -EMAIL_HOST = os.getenv("EMAIL_HOST", "pop.gmail.com") -EMAIL_PORT = int(os.getenv("EMAIL_PORT", "995")) +EMAIL_HOST = os.getenv("EMAIL_HOST", "smtp.gmail.com") +EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587")) EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "aalabry@gmail.com") EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "accd uacy kzdq aejp") EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "true").lower() == "true" diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 2653ae8..7a8440b 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 68424aa..fdc0ec8 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index b98115b..d0f7cbc 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/__pycache__/whatsapp_utils.cpython-311.pyc b/core/__pycache__/whatsapp_utils.cpython-311.pyc index ecb7983..523cb2d 100644 Binary files a/core/__pycache__/whatsapp_utils.cpython-311.pyc and b/core/__pycache__/whatsapp_utils.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index c0b25b6..2831ec9 100644 --- a/core/admin.py +++ b/core/admin.py @@ -8,6 +8,8 @@ from django.shortcuts import render from django.utils.html import format_html from django.contrib import messages from .whatsapp_utils import send_whatsapp_message_detailed +from django.core.mail import send_mail +from django.conf import settings import logging class ProfileInline(admin.StackedInline): @@ -47,6 +49,7 @@ class PlatformProfileAdmin(admin.ModelAdmin): urls = super().get_urls() custom_urls = [ path('test-whatsapp/', self.admin_site.admin_view(self.test_whatsapp_view), name='test-whatsapp'), + path('test-email/', self.admin_site.admin_view(self.test_email_view), name='test-email'), ] return custom_urls + urls @@ -69,11 +72,39 @@ class PlatformProfileAdmin(admin.ModelAdmin): ) return render(request, "admin/core/platformprofile/test_whatsapp.html", context) + def test_email_view(self, request): + email = '' + if request.method == 'POST': + email = request.POST.get('email') + if email: + try: + send_mail( + subject="Test Email from Platform", + message="This is a test email to verify your platform's email configuration.", + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=[email], + fail_silently=False, + ) + messages.success(request, f"Success: Test email sent to {email}.") + except Exception as e: + messages.error(request, f"Error sending email: {str(e)}") + else: + messages.warning(request, "Please enter an email address.") + + context = dict( + self.admin_site.each_context(request), + email=email, + ) + return render(request, "admin/core/platformprofile/test_email.html", context) + def test_connection_link(self, obj): return format_html( + '{}' '{}', reverse('admin:test-whatsapp'), - _('Test WhatsApp Configuration') + _('Test WhatsApp'), + reverse('admin:test-email'), + _('Test Email') ) test_connection_link.short_description = _("Actions") test_connection_link.allow_tags = True @@ -84,6 +115,8 @@ class PlatformProfileAdmin(admin.ModelAdmin): fieldsets = super().get_fieldsets(request, obj) # Add the test link to the first fieldset or a new one if obj: + # Check if 'Tools' fieldset already exists to avoid duplication if called multiple times (though get_fieldsets is usually fresh) + # Easier: just append it. fieldsets += ((_('Tools'), {'fields': ('test_connection_link',)}),) return fieldsets diff --git a/core/migrations/0013_platformprofile_enable_payment.py b/core/migrations/0013_platformprofile_enable_payment.py new file mode 100644 index 0000000..6c9e20b --- /dev/null +++ b/core/migrations/0013_platformprofile_enable_payment.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-01-25 13:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0012_alter_platformprofile_whatsapp_access_token_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='platformprofile', + name='enable_payment', + field=models.BooleanField(default=True, help_text='Toggle to enable or disable payments on the platform.', verbose_name='Enable Payment'), + ), + ] diff --git a/core/migrations/__pycache__/0013_platformprofile_enable_payment.cpython-311.pyc b/core/migrations/__pycache__/0013_platformprofile_enable_payment.cpython-311.pyc new file mode 100644 index 0000000..eb8d412 Binary files /dev/null and b/core/migrations/__pycache__/0013_platformprofile_enable_payment.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 07c5370..dad3a39 100644 --- a/core/models.py +++ b/core/models.py @@ -166,12 +166,22 @@ class PlatformProfile(models.Model): whatsapp_business_phone_number_id = models.CharField(_('Wablas Domain'), max_length=100, blank=True, default="https://deu.wablas.com", help_text=_("The Wablas API domain (e.g., https://deu.wablas.com).")) whatsapp_app_secret = models.CharField(_('Wablas Secret Key'), max_length=255, blank=True, help_text=_("Your Wablas API Secret Key (if required).")) + # Payment Configuration + enable_payment = models.BooleanField(_('Enable Payment'), default=True, help_text=_("Toggle to enable or disable payments on the platform.")) + def save(self, *args, **kwargs): # Auto-clean whitespace from credentials if self.whatsapp_access_token: self.whatsapp_access_token = self.whatsapp_access_token.strip() if self.whatsapp_business_phone_number_id: - self.whatsapp_business_phone_number_id = self.whatsapp_business_phone_number_id.strip() + val = self.whatsapp_business_phone_number_id.strip() + # Remove common path suffixes if user pasted full URL + for suffix in ['/api/send-message', '/api/v2/send-message']: + if val.endswith(suffix): + val = val[:-len(suffix)] + if val.endswith('/'): + val = val[:-1] + self.whatsapp_business_phone_number_id = val if self.whatsapp_app_secret: self.whatsapp_app_secret = self.whatsapp_app_secret.strip() diff --git a/core/templates/admin/core/platformprofile/test_email.html b/core/templates/admin/core/platformprofile/test_email.html new file mode 100644 index 0000000..2c7aa29 --- /dev/null +++ b/core/templates/admin/core/platformprofile/test_email.html @@ -0,0 +1,39 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls static admin_modify %} + +{% block extrahead %}{{ block.super }} + +{{ media }} +{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+
+ {% csrf_token %} +
+
+

Test Email Configuration

+
+ + +
Enter the email address to receive the test message.
+
+
+ +
+ + Go Back +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/shipper_dashboard.html b/core/templates/core/shipper_dashboard.html index 32d54ce..99e07e8 100644 --- a/core/templates/core/shipper_dashboard.html +++ b/core/templates/core/shipper_dashboard.html @@ -32,9 +32,11 @@ {% if parcel.payment_status == 'pending' %} + {% if platform_profile.enable_payment %} {% trans "Pay Now" %} + {% endif %} {% endif %}
diff --git a/core/views.py b/core/views.py index 47a0897..75f0bb9 100644 --- a/core/views.py +++ b/core/views.py @@ -2,7 +2,7 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth import login, authenticate, logout from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.decorators import login_required -from .models import Parcel, Profile, Country, Governate, City, OTPVerification +from .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm from django.utils.translation import gettext_lazy as _ from django.utils.translation import get_language @@ -125,6 +125,12 @@ def update_status(request, parcel_id): @login_required def initiate_payment(request, parcel_id): + # Check if payments are enabled + platform_profile = PlatformProfile.objects.first() + if platform_profile and not platform_profile.enable_payment: + messages.error(request, _("Payments are currently disabled by the administrator.")) + return redirect('dashboard') + parcel = get_object_or_404(Parcel, id=parcel_id, shipper=request.user, payment_status='pending') thawani = ThawaniPay() @@ -319,4 +325,4 @@ def verify_otp_view(request): except OTPVerification.DoesNotExist: messages.error(request, _("Invalid code.")) - return render(request, 'core/verify_otp.html') \ No newline at end of file + return render(request, 'core/verify_otp.html', {'form': form}) \ No newline at end of file diff --git a/core/whatsapp_utils.py b/core/whatsapp_utils.py index d398931..14ba968 100644 --- a/core/whatsapp_utils.py +++ b/core/whatsapp_utils.py @@ -15,7 +15,7 @@ def get_whatsapp_credentials(): api_token = settings.WHATSAPP_API_KEY if hasattr(settings, 'WHATSAPP_API_KEY') else "" # We repurpose Phone ID as Domain in settings if needed, or default to Wablas DEU domain = "https://deu.wablas.com" - secret_key = "" # Add this to settings if you want env support, but for now mostly DB + secret_key = "" source = "Settings/Env" # Try to fetch from PlatformProfile @@ -53,7 +53,7 @@ def send_whatsapp_message(phone_number, message): def send_whatsapp_message_detailed(phone_number, message): """ - Sends a WhatsApp message via Wablas V2 API and returns detailed status. + Sends a WhatsApp message via Wablas API and returns detailed status. Returns tuple: (success: bool, response_msg: str) """ if not getattr(settings, 'WHATSAPP_ENABLED', True): @@ -69,43 +69,57 @@ def send_whatsapp_message_detailed(phone_number, message): return False, msg # Normalize phone number (Wablas expects international format without +, e.g. 628123...) - # Remove all non-digits - clean_phone = "".join(filter(str.isdigit, str(phone_number))) + clean_phone = str(phone_number).replace('+', '').replace(' ', '') - # Construct Authorization Header - # Wablas V2: Authorization: {$token}.{$secret_key} - # Some Wablas servers just need Token, but docs say Token.Secret + # Endpoint: /api/send-message (Simple Text) + # Ensure domain has schema + if not domain.startswith('http'): + domain = f"https://{domain}" + + # Using the exact endpoint provided in user example + url = f"{domain}/api/send-message" + + # Header construction logic from user example auth_header = api_token if secret_key: auth_header = f"{api_token}.{secret_key}" - - # Endpoint V2 - url = f"{domain}/api/v2/send-message" - + headers = { "Authorization": auth_header, - "Content-Type": "application/json", + # requests will set Content-Type to application/x-www-form-urlencoded when using 'data' param } - payload = { - "data": [ - { - "phone": clean_phone, - "message": message, - "isGroup": "false", - "flag": "instant" # Priority - } - ] + # Payload as form data (not JSON) + data = { + "phone": clean_phone, + "message": message, } + + # Note: User's example didn't add 'secret' to payload, only to header. + # We will stick to user's example strictly. try: - response = requests.post(url, headers=headers, json=payload, timeout=15) - response_data = response.json() + # Use data=data for form-urlencoded + response = requests.post(url, headers=headers, data=data, timeout=15) + # Handle non-JSON response (HTML error pages) + try: + response_data = response.json() + except ValueError: + response_data = response.text + # Wablas success usually has status: true - if response.status_code == 200 and response_data.get('status') is not False: - logger.info(f"WhatsApp message sent to {clean_phone} via Wablas") - return True, f"Message sent successfully via Wablas. (Source: {source})" + if response.status_code == 200: + # Check for logical success in JSON + if isinstance(response_data, dict): + if response_data.get('status') is True: + logger.info(f"WhatsApp message sent to {clean_phone} via Wablas") + return True, f"Message sent successfully via Wablas. (Source: {source})" + else: + return False, f"Wablas API Logic Error (Source: {source}): {response_data}" + else: + # If text, assume success if 200 OK? Or inspect text. + return True, f"Message sent (Raw Response). (Source: {source})" else: error_msg = f"Wablas API error (Source: {source}): {response.status_code} - {response_data}" logger.error(error_msg)