adding disable payments
This commit is contained in:
parent
df3c7ad9f5
commit
ce3101780f
22
GEMINI_RESPONSE.md
Normal file
22
GEMINI_RESPONSE.md
Normal file
@ -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.
|
||||||
Binary file not shown.
@ -174,8 +174,8 @@ EMAIL_BACKEND = os.getenv(
|
|||||||
"EMAIL_BACKEND",
|
"EMAIL_BACKEND",
|
||||||
"django.core.mail.backends.smtp.EmailBackend"
|
"django.core.mail.backends.smtp.EmailBackend"
|
||||||
)
|
)
|
||||||
EMAIL_HOST = os.getenv("EMAIL_HOST", "pop.gmail.com")
|
EMAIL_HOST = os.getenv("EMAIL_HOST", "smtp.gmail.com")
|
||||||
EMAIL_PORT = int(os.getenv("EMAIL_PORT", "995"))
|
EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587"))
|
||||||
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "aalabry@gmail.com")
|
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "aalabry@gmail.com")
|
||||||
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "accd uacy kzdq aejp")
|
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "accd uacy kzdq aejp")
|
||||||
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "true").lower() == "true"
|
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "true").lower() == "true"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -8,6 +8,8 @@ from django.shortcuts import render
|
|||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from .whatsapp_utils import send_whatsapp_message_detailed
|
from .whatsapp_utils import send_whatsapp_message_detailed
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.conf import settings
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
class ProfileInline(admin.StackedInline):
|
class ProfileInline(admin.StackedInline):
|
||||||
@ -47,6 +49,7 @@ class PlatformProfileAdmin(admin.ModelAdmin):
|
|||||||
urls = super().get_urls()
|
urls = super().get_urls()
|
||||||
custom_urls = [
|
custom_urls = [
|
||||||
path('test-whatsapp/', self.admin_site.admin_view(self.test_whatsapp_view), name='test-whatsapp'),
|
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
|
return custom_urls + urls
|
||||||
|
|
||||||
@ -69,11 +72,39 @@ class PlatformProfileAdmin(admin.ModelAdmin):
|
|||||||
)
|
)
|
||||||
return render(request, "admin/core/platformprofile/test_whatsapp.html", context)
|
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):
|
def test_connection_link(self, obj):
|
||||||
return format_html(
|
return format_html(
|
||||||
|
'<a class="button" href="{}" style="margin-right: 10px;">{}</a>'
|
||||||
'<a class="button" href="{}">{}</a>',
|
'<a class="button" href="{}">{}</a>',
|
||||||
reverse('admin:test-whatsapp'),
|
reverse('admin:test-whatsapp'),
|
||||||
_('Test WhatsApp Configuration')
|
_('Test WhatsApp'),
|
||||||
|
reverse('admin:test-email'),
|
||||||
|
_('Test Email')
|
||||||
)
|
)
|
||||||
test_connection_link.short_description = _("Actions")
|
test_connection_link.short_description = _("Actions")
|
||||||
test_connection_link.allow_tags = True
|
test_connection_link.allow_tags = True
|
||||||
@ -84,6 +115,8 @@ class PlatformProfileAdmin(admin.ModelAdmin):
|
|||||||
fieldsets = super().get_fieldsets(request, obj)
|
fieldsets = super().get_fieldsets(request, obj)
|
||||||
# Add the test link to the first fieldset or a new one
|
# Add the test link to the first fieldset or a new one
|
||||||
if obj:
|
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',)}),)
|
fieldsets += ((_('Tools'), {'fields': ('test_connection_link',)}),)
|
||||||
return fieldsets
|
return fieldsets
|
||||||
|
|
||||||
|
|||||||
18
core/migrations/0013_platformprofile_enable_payment.py
Normal file
18
core/migrations/0013_platformprofile_enable_payment.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -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_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)."))
|
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):
|
def save(self, *args, **kwargs):
|
||||||
# Auto-clean whitespace from credentials
|
# Auto-clean whitespace from credentials
|
||||||
if self.whatsapp_access_token:
|
if self.whatsapp_access_token:
|
||||||
self.whatsapp_access_token = self.whatsapp_access_token.strip()
|
self.whatsapp_access_token = self.whatsapp_access_token.strip()
|
||||||
if self.whatsapp_business_phone_number_id:
|
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:
|
if self.whatsapp_app_secret:
|
||||||
self.whatsapp_app_secret = self.whatsapp_app_secret.strip()
|
self.whatsapp_app_secret = self.whatsapp_app_secret.strip()
|
||||||
|
|
||||||
|
|||||||
39
core/templates/admin/core/platformprofile/test_email.html
Normal file
39
core/templates/admin/core/platformprofile/test_email.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
{% load i18n admin_urls static admin_modify %}
|
||||||
|
|
||||||
|
{% block extrahead %}{{ block.super }}
|
||||||
|
<script src="{% url 'admin:jsi18n' %}"></script>
|
||||||
|
{{ media }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
|
||||||
|
› <a href="{% url 'admin:app_list' app_label='core' %}">Core</a>
|
||||||
|
› <a href="{% url 'admin:core_platformprofile_changelist' %}">Platform Profiles</a>
|
||||||
|
› Test Email Configuration
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="content-main">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div>
|
||||||
|
<fieldset class="module aligned">
|
||||||
|
<h2>Test Email Configuration</h2>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="id_email" class="required">Target Email Address:</label>
|
||||||
|
<input type="email" name="email" id="id_email" value="{{ email }}" required size="40">
|
||||||
|
<div class="help">Enter the email address to receive the test message.</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="submit-row">
|
||||||
|
<input type="submit" value="Send Test Email" class="default" name="_save">
|
||||||
|
<a href="../" class="button closelink" style="margin-left: 10px;">Go Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -32,9 +32,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if parcel.payment_status == 'pending' %}
|
{% if parcel.payment_status == 'pending' %}
|
||||||
|
{% if platform_profile.enable_payment %}
|
||||||
<a href="{% url 'initiate_payment' parcel.id %}" class="btn btn-sm btn-outline-primary w-100 mb-3">
|
<a href="{% url 'initiate_payment' parcel.id %}" class="btn btn-sm btn-outline-primary w-100 mb-3">
|
||||||
<i class="fas fa-credit-card me-1"></i> {% trans "Pay Now" %}
|
<i class="fas fa-credit-card me-1"></i> {% trans "Pay Now" %}
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@ -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 import login, authenticate, logout
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.contrib.auth.decorators import login_required
|
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 .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
@ -125,6 +125,12 @@ def update_status(request, parcel_id):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def initiate_payment(request, parcel_id):
|
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')
|
parcel = get_object_or_404(Parcel, id=parcel_id, shipper=request.user, payment_status='pending')
|
||||||
|
|
||||||
thawani = ThawaniPay()
|
thawani = ThawaniPay()
|
||||||
@ -319,4 +325,4 @@ def verify_otp_view(request):
|
|||||||
except OTPVerification.DoesNotExist:
|
except OTPVerification.DoesNotExist:
|
||||||
messages.error(request, _("Invalid code."))
|
messages.error(request, _("Invalid code."))
|
||||||
|
|
||||||
return render(request, 'core/verify_otp.html')
|
return render(request, 'core/verify_otp.html', {'form': form})
|
||||||
@ -15,7 +15,7 @@ def get_whatsapp_credentials():
|
|||||||
api_token = settings.WHATSAPP_API_KEY if hasattr(settings, 'WHATSAPP_API_KEY') else ""
|
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
|
# We repurpose Phone ID as Domain in settings if needed, or default to Wablas DEU
|
||||||
domain = "https://deu.wablas.com"
|
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"
|
source = "Settings/Env"
|
||||||
|
|
||||||
# Try to fetch from PlatformProfile
|
# Try to fetch from PlatformProfile
|
||||||
@ -53,7 +53,7 @@ def send_whatsapp_message(phone_number, message):
|
|||||||
|
|
||||||
def send_whatsapp_message_detailed(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)
|
Returns tuple: (success: bool, response_msg: str)
|
||||||
"""
|
"""
|
||||||
if not getattr(settings, 'WHATSAPP_ENABLED', True):
|
if not getattr(settings, 'WHATSAPP_ENABLED', True):
|
||||||
@ -69,43 +69,57 @@ def send_whatsapp_message_detailed(phone_number, message):
|
|||||||
return False, msg
|
return False, msg
|
||||||
|
|
||||||
# Normalize phone number (Wablas expects international format without +, e.g. 628123...)
|
# Normalize phone number (Wablas expects international format without +, e.g. 628123...)
|
||||||
# Remove all non-digits
|
clean_phone = str(phone_number).replace('+', '').replace(' ', '')
|
||||||
clean_phone = "".join(filter(str.isdigit, str(phone_number)))
|
|
||||||
|
|
||||||
# Construct Authorization Header
|
# Endpoint: /api/send-message (Simple Text)
|
||||||
# Wablas V2: Authorization: {$token}.{$secret_key}
|
# Ensure domain has schema
|
||||||
# Some Wablas servers just need Token, but docs say Token.Secret
|
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
|
auth_header = api_token
|
||||||
if secret_key:
|
if secret_key:
|
||||||
auth_header = f"{api_token}.{secret_key}"
|
auth_header = f"{api_token}.{secret_key}"
|
||||||
|
|
||||||
# Endpoint V2
|
|
||||||
url = f"{domain}/api/v2/send-message"
|
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": auth_header,
|
"Authorization": auth_header,
|
||||||
"Content-Type": "application/json",
|
# requests will set Content-Type to application/x-www-form-urlencoded when using 'data' param
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = {
|
# Payload as form data (not JSON)
|
||||||
"data": [
|
data = {
|
||||||
{
|
"phone": clean_phone,
|
||||||
"phone": clean_phone,
|
"message": message,
|
||||||
"message": message,
|
|
||||||
"isGroup": "false",
|
|
||||||
"flag": "instant" # Priority
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Note: User's example didn't add 'secret' to payload, only to header.
|
||||||
|
# We will stick to user's example strictly.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=payload, timeout=15)
|
# Use data=data for form-urlencoded
|
||||||
response_data = response.json()
|
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
|
# Wablas success usually has status: true
|
||||||
if response.status_code == 200 and response_data.get('status') is not False:
|
if response.status_code == 200:
|
||||||
logger.info(f"WhatsApp message sent to {clean_phone} via Wablas")
|
# Check for logical success in JSON
|
||||||
return True, f"Message sent successfully via Wablas. (Source: {source})"
|
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:
|
else:
|
||||||
error_msg = f"Wablas API error (Source: {source}): {response.status_code} - {response_data}"
|
error_msg = f"Wablas API error (Source: {source}): {response.status_code} - {response_data}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user