diff --git a/accounts/views.py b/accounts/views.py index 25887e4..f935ecb 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,3 +1,6 @@ +from datetime import datetime +from django.utils import timezone +from datetime import datetime, timedelta, date from django.conf import settings from django.shortcuts import render, redirect, get_object_or_404, reverse from django.core.mail import EmailMessage @@ -19,7 +22,7 @@ from .tokens import account_activation_token from .forms import SignUpForm, CreateStaffForm, UpdateStaffForm from .models import Profile -from membership.models import Business, BusinessTeamMember, Subscription +from membership.models import Business, BusinessTeamMember, Subscription, Plan User = get_user_model() @@ -35,9 +38,9 @@ def get_success_url(self): if url: return url elif self.request.user.is_admin: - return reverse('/') + return reverse('membership') elif self.request.user.is_manager or self.request.user.is_staff: - return reverse('dashboard') + return reverse('membership') else: return f'/admin/' @@ -60,9 +63,17 @@ def signup(request): #create BusinessTeamMember business_team = BusinessTeamMember.objects.get_or_create(business=business, user=user) - #business_team = BusinessTeamMember(user=user) - print('business_team:', business_team) - #business_team.save() + + #create free subscription + ends_time = timezone.now() + timedelta(days=14) + + subscription = Subscription.objects.get_or_create( + plan_id= 1, + business=business, + start_time = timezone.now(), + ends_time = ends_time, + is_active = True, + ) # send confirmation email token = account_activation_token.make_token(user) diff --git a/config/settings.py b/config/settings.py index 39d1fb4..da2a5f8 100644 --- a/config/settings.py +++ b/config/settings.py @@ -1,16 +1,7 @@ -""" -Django settings for config project. - -Generated by 'django-admin startproject' using Django 3.0.7. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.0/ref/settings/ -""" - import os +from decouple import config +from unipath import Path +from dj_database_url import parse as db_url # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -20,12 +11,19 @@ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'wsfsfgfdsf' +SECRET_KEY = config('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = config('DEBUG', cast=bool) -ALLOWED_HOSTS = [] +if not DEBUG: + ALLOWED_HOSTS = [] + SECURE_SSL_REDIRECT=True + SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTO', 'https') + SESSION_COOKIE_SECURE=True + SESSION_EXPIRE_AT_BROWSER_CLOSE=True +else: + ALLOWED_HOST = [] # Application definition @@ -79,12 +77,29 @@ # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), +if not DEBUG: + DATABASES = { + 'default': { + # 'ENGINE': 'django.db.backends.mysql', + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': config('DB_NAME'), + 'USER': config('DB_USER'), + 'PASSWORD': config('DB_PASSWORD'), + 'HOST': config('DB_HOST'), + 'PORT': config('DB_PORT', cast=int), + # 'OPTIONS': { + # 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" + # }, + } + } + +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } } -} # Password validation @@ -120,10 +135,18 @@ USE_TZ = True -#Email Configurations For Development -EMAIL_HOST_USER = 'noreply@example.com' -EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' -EMAIL_FILE_PATH = os.path.join(BASE_DIR, 'sent_mails') +#Email Configurations +if not DEBUG: + EMAIL_BACKEND = config('EMAIL_BACKEND') + EMAIL_HOST = config('EMAIL_HOST') + EMAIL_HOST_USER = config('EMAIL_HOST_USER') + EMAIL_PORT = config('PORT', cast=int) + EMAIL_USE_SSL = config('PORT', cast=bool) + EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD') +else: + EMAIL_HOST_USER = 'noreply@example.com' + EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' + EMAIL_FILE_PATH = os.path.join(BASE_DIR, 'sent_mails') AUTH_USER_MODEL = 'accounts.CustomUser' @@ -139,10 +162,21 @@ STATIC_URL = '/static/' MEDIA_URL = '/media/' -STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),] -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +if not DEBUG: + STATICFILES_DIRS = [ + BASE_DIR+"/static",] -#M-PESA PAYMENT CONFIGURATIONS -PUBLIC_KEY ='MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArv9yxA69XQKBo24BaF/D+fvlqmGdYjqLQ5WtNBb5tquqGvAvG3WMFETVUSow/LizQalxj2ElMVrUmzu5mGGkxK08bWEXF7a1DEvtVJs6nppIlFJc2SnrU14AOrIrB28ogm58JjAl5BOQawOXD5dfSk7MaAA82pVHoIqEu0FxA8BOKU+RGTihRU+ptw1j4bsAJYiPbSX6i71gfPvwHPYamM0bfI4CmlsUUR3KvCG24rB6FNPcRBhM3jDuv8ae2kC33w9hEq8qNB55uw51vK7hyXoAa+U7IqP1y6nBdlN25gkxEA8yrsl1678cspeXr+3ciRyqoRgj9RD/ONbJhhxFvt1cLBh+qwK2eqISfBb06eRnNeC71oBokDm3zyCnkOtMDGl7IvnMfZfEPFCfg5QgJVk1msPpRvQxmEsrX9MQRyFVzgy2CWNIb7c+jPapyrNwoUbANlN8adU1m6yOuoX7F49x+OjiG2se0EJ6nafeKUXw/+hiJZvELUYgzKUtMAZVTNZfT8jjb58j8GVtuS+6TM2AutbejaCV84ZK58E2CRJqhmjQibEUO6KPdD7oTlEkFy52Y1uOOBXgYpqMzufNPmfdqqqSM4dU70PO8ogyKGiLAIxCetMjjm6FCMEA3Kc8K0Ig7/XtFm9By6VxTJK1Mg36TlHaZKP6VzVLXMtesJECAwEAAQ==' + MEDIA_ROOT = '' + STATIC_ROOT = '' +else: + STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),] + MEDIA_ROOT = os.path.join(BASE_DIR, 'media') -API_KEY = 'e0dlIJMW6qrit7c9XpP1olfhqgHQ05sw' \ No newline at end of file + +#M-PESA PAYMENT CONFIGURATIONS +if not DEBUG: + PUBLIC_KEY = config('PUBLIC_KEY') + API_KEY = config('API_KEY') +else: + PUBLIC_KEY = config('PUBLIC_KEY') + API_KEY = config('API_KEY') \ No newline at end of file diff --git a/env-example b/env-example new file mode 100644 index 0000000..4b2cddc --- /dev/null +++ b/env-example @@ -0,0 +1,25 @@ +DEBUG=True + +SECRET_KEY=aaaaa + +PUBLIC_KEY= + +API_KEY= + + +# Credentials for database + +DB_NAME=aaa +DB_USER=aaa +DB_HOST=localhost +DB_PASSWORD=aaaa +DB_PORT=5432 + +#Email Credentials + +EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +EMAIL_HOST=aaaa +EMAIL_HOST_USER=aaaa +EMAIL_PORT=465 +EMAIL_USE_SSL=True +EMAIL_HOST_PASSWORD=aaaa \ No newline at end of file diff --git a/membership/migrations/0007_auto_20201017_0947.py b/membership/migrations/0007_auto_20201017_0947.py new file mode 100644 index 0000000..9c9f4c0 --- /dev/null +++ b/membership/migrations/0007_auto_20201017_0947.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-10-17 06:47 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0006_auto_20201015_2336'), + ] + + operations = [ + migrations.RenameField( + model_name='payment', + old_name='subscription', + new_name='subscription_id', + ), + ] diff --git a/membership/migrations/0008_auto_20201017_1020.py b/membership/migrations/0008_auto_20201017_1020.py new file mode 100644 index 0000000..79a35a0 --- /dev/null +++ b/membership/migrations/0008_auto_20201017_1020.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1 on 2020-10-17 07:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0007_auto_20201017_0947'), + ] + + operations = [ + migrations.RemoveField( + model_name='payment', + name='subscription_id', + ), + migrations.AddField( + model_name='payment', + name='subscription', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='membership.subscription'), + ), + ] diff --git a/membership/migrations/0009_auto_20201017_1046.py b/membership/migrations/0009_auto_20201017_1046.py new file mode 100644 index 0000000..b890031 --- /dev/null +++ b/membership/migrations/0009_auto_20201017_1046.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1 on 2020-10-17 07:46 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('membership', '0008_auto_20201017_1020'), + ] + + operations = [ + migrations.RemoveField( + model_name='payment', + name='subscription', + ), + migrations.AddField( + model_name='payment', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/membership/migrations/0010_auto_20201017_1046.py b/membership/migrations/0010_auto_20201017_1046.py new file mode 100644 index 0000000..f1939da --- /dev/null +++ b/membership/migrations/0010_auto_20201017_1046.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1 on 2020-10-17 07:46 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('membership', '0009_auto_20201017_1046'), + ] + + operations = [ + migrations.AlterField( + model_name='payment', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounts.customuser'), + ), + ] diff --git a/membership/migrations/0011_auto_20201017_1047.py b/membership/migrations/0011_auto_20201017_1047.py new file mode 100644 index 0000000..f533f0a --- /dev/null +++ b/membership/migrations/0011_auto_20201017_1047.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1 on 2020-10-17 07:47 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('membership', '0010_auto_20201017_1046'), + ] + + operations = [ + migrations.AlterField( + model_name='payment', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/membership/migrations/0012_auto_20201017_1253.py b/membership/migrations/0012_auto_20201017_1253.py new file mode 100644 index 0000000..40dc296 --- /dev/null +++ b/membership/migrations/0012_auto_20201017_1253.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1 on 2020-10-17 09:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0011_auto_20201017_1047'), + ] + + operations = [ + migrations.RemoveField( + model_name='business', + name='reference_no', + ), + migrations.AddField( + model_name='payment', + name='reference_no', + field=models.CharField(blank=True, max_length=150, null=True), + ), + ] diff --git a/membership/models.py b/membership/models.py index 94d4950..ada6cee 100644 --- a/membership/models.py +++ b/membership/models.py @@ -11,7 +11,6 @@ class Business(models.Model): user = models.OneToOneField(User, on_delete=models.PROTECT) name = models.CharField(max_length=100) location = models.CharField(max_length=100) - reference_no = models.CharField(max_length=150, blank=True, null=True) #payment gateway def __str__(self): return self.user.email @@ -56,12 +55,13 @@ def __str__(self): class Payment(models.Model): created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True) - subscription = models.OneToOneField(Subscription, on_delete=models.PROTECT) + user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True) phone = models.CharField(max_length=12, verbose_name='Enter your M-PESA mobile number', help_text='example. 255700000000') transactionID = models.CharField(max_length=100, blank=True, null=True) conversationID = models.CharField(max_length=100, blank=True, null=True) + reference_no = models.CharField(max_length=150, blank=True, null=True) #payment gateway def __str__(self): - return self.subscription.plan.price + return self.user.business.plan diff --git a/membership/signals.py b/membership/signals.py index 6a42e61..d6f3219 100644 --- a/membership/signals.py +++ b/membership/signals.py @@ -1,12 +1,12 @@ -from django.db.models.signals import post_save -from django.dispatch import receiver -from .models import Business +# from django.db.models.signals import post_save +# from django.dispatch import receiver +# from .models import Business -@receiver(post_save, sender=Business) -def business_reference_no(sender, instance, created, **kwargs): - if created: - reference_no = str(instance.id) + str(instance.created_at.hour) + str(instance.created_at.minute) + str(instance.created_at.year)[-2:] +# @receiver(post_save, sender=Business) +# def business_reference_no(sender, instance, created, **kwargs): +# if created: +# reference_no = str(instance.id) + str(instance.created_at.hour) + str(instance.created_at.minute) + str(instance.created_at.year)[-2:] - Business.objects.filter(pk=instance.pk).update(reference_no=reference_no) - print("reference_no:", reference_no) \ No newline at end of file +# Business.objects.filter(pk=instance.pk).update(reference_no=reference_no) +# print("reference_no:", reference_no) \ No newline at end of file diff --git a/membership/templates/membership/payment.html b/membership/templates/membership/payment.html index bd42521..0623536 100644 --- a/membership/templates/membership/payment.html +++ b/membership/templates/membership/payment.html @@ -36,7 +36,7 @@
-

Your Order ID: {{subscription.id}}

+

Ref no: {{reference_no}}

Please keep your phone in hand. Once you click "Pay" a request to comfirm your M-PESA PIN sent to your phone. diff --git a/membership/views.py b/membership/views.py index 3441fef..eff24bc 100644 --- a/membership/views.py +++ b/membership/views.py @@ -1,12 +1,14 @@ -import datetime +from datetime import datetime from django.utils import timezone from django.shortcuts import render -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse from django.views.generic import TemplateView, ListView from django.contrib import messages from .models import Plan, Subscription, Business from .forms import PaymentForm +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.contrib.auth.decorators import login_required #Vodacom mpesa intergrations from django.conf import settings @@ -34,7 +36,7 @@ def get_selected_plan(request): return None -class PricingPage(ListView): +class PricingPage(LoginRequiredMixin, ListView): template_name = 'membership/pricing_page.html' model = Plan @@ -66,15 +68,21 @@ def post(self, request, *args, **kwargs): return HttpResponseRedirect(reverse('membership:payment')) - +@login_required def paymentView(request): selected_plan = get_selected_plan(request) plans = Plan.objects.get(name=selected_plan) + business = Business.objects.get(user=request.user) - reference_no = business.reference_no + + current_subscription = get_user_plan(request) + + reference_no = str(request.user.id) + str(current_subscription.id) + datetime.now().strftime('%Y%m%d%H%M%S') + print('reference_no:', reference_no) + if request.method == 'POST': form = PaymentForm(request.POST) if form.is_valid(): @@ -174,7 +182,8 @@ def paymentView(request): print(result.body) if result.body['output_ResponseCode'] == 'INS-0': - + + #update/downgrade subscriptions ends_time = timezone.now() + timedelta(days=plans.duration_days) Subscription.objects.filter(business=request.user.business).update( plan=plans.id, @@ -182,21 +191,38 @@ def paymentView(request): ends_time = ends_time, paid_status = True, ) - subscription = Subscription.objects.filter(business=request.user.business) #save transactionID,transactionID payment = form.save(commit=False) - payment.subscription = subscription + payment.user_id = request.user.id payment.transactionID = result.body['output_TransactionID'] payment.conversationID = result.body['output_ConversationID'] + payment.reference_no = reference_no payment.save() return HttpResponse('Your Payment was Successfully sent!') + elif result.body['output_ResponseCode'] == 'INS-1': + messages.add_message(request, messages.ERROR, 'Internal Error') + + elif result.body['output_ResponseCode'] == 'INS-6': + messages.add_message(request, messages.ERROR, 'Transaction Failed') + + elif result.body['output_ResponseCode'] == 'INS-9': + messages.add_message(request, messages.ERROR, 'Request timeout') + + elif result.body['output_ResponseCode'] == 'INS-10': + messages.add_message(request, messages.ERROR, 'Duplicate Transaction') + + elif result.body['output_ResponseCode'] == 'INS-2006': + messages.add_message(request, messages.ERROR, 'Insufficient balance') + + else: + messages.add_message(request, messages.ERROR, 'Configuration Error, contact with support team') else: form = PaymentForm() - context = {'form':form, 'plans':plans} + context = {'form':form, 'plans':plans, 'reference_no':reference_no} return render(request, 'membership/payment.html', context) diff --git a/requirements.txt b/requirements.txt index e1b910e..09f0aa1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,8 @@ certifi==2018.1.18 chardet==3.0.4 idna==2.6 pycryptodome==3.9.8 -requests==2.20.0 -urllib3==1.25.10 \ No newline at end of file +requests==2.18.4 +urllib3==1.25.10 +django-decouple==2.1 +unipath==1.1 +dj-database-url==0.5.0