diff --git a/dynamicweb/settings/local.py b/dynamicweb/settings/local.py deleted file mode 100644 index 799df594..00000000 --- a/dynamicweb/settings/local.py +++ /dev/null @@ -1,21 +0,0 @@ -from .base import * -REGISTRATION_MESSAGE['message'] = REGISTRATION_MESSAGE['message'].format(host='dynamicweb-development.ungleich.ch',slug='{slug}') -ALLOWED_HOSTS = [ - "*" - ] - -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'unique-snowflake' - } -} - -MIDDLEWARE_CLASSES+=("debug_toolbar.middleware.DebugToolbarMiddleware",) - -INSTALLED_APPS+=( - 'django_extensions', - 'debug_toolbar' - ) diff --git a/hosting/admin.py b/hosting/admin.py index 4d1489ce..c4bff6e0 100644 --- a/hosting/admin.py +++ b/hosting/admin.py @@ -3,7 +3,6 @@ from django.utils.html import format_html from django.core.urlresolvers import reverse from utils.mailer import BaseEmail -from utils.stripe_utils import StripeUtils from .forms import HostingOrderAdminForm from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index dd6c64d4..c431f7ce 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -56,7 +56,7 @@ $( document ).ready(function() { //set token on a hidden input $('#id_token').val(token); - $('#billing-form').submit(); + $('#donation-form').submit(); } }); } diff --git a/nosystemd/admin.py b/nosystemd/admin.py index 8c38f3f3..b07cea8c 100644 --- a/nosystemd/admin.py +++ b/nosystemd/admin.py @@ -1,3 +1,22 @@ from django.contrib import admin +from django.utils.html import format_html +from django.core.urlresolvers import reverse + +from .models import Donation + # Register your models here. + + +class DonationAdmin(admin.ModelAdmin): + + list_display = ('id', 'donation', 'donator') + search_fields = ['id', 'donator__user__email'] + + def user(self, obj): + email = obj.customer.user.email + user_url = reverse("admin:membership_customuser_change", args=[obj.customer.user.id]) + return format_html("{email}", url=user_url, email=email) + + +admin.site.register(Donation, DonationAdmin) diff --git a/nosystemd/forms.py b/nosystemd/forms.py index 464c1424..8aa3eb35 100644 --- a/nosystemd/forms.py +++ b/nosystemd/forms.py @@ -1,7 +1,11 @@ from django import forms +from django.utils.translation import ugettext_lazy as _ -from utils.forms import LoginFormMixin, SignupFormMixin +from utils.forms import LoginFormMixin, SignupFormMixin, BillingAddressForm +from utils.models import BillingAddress + +from .models import Donation, DonatorStatus class LoginForm(LoginFormMixin): @@ -10,5 +14,38 @@ class LoginForm(LoginFormMixin): class SignupForm(SignupFormMixin): - confirm_password = forms.CharField(widget=forms.EmailInput()) + confirm_password = forms.CharField(widget=forms.PasswordInput()) password = forms.CharField(widget=forms.PasswordInput()) + + +class DonationForm(forms.ModelForm): + + class Meta: + model = Donation + fields = ['donation', 'donator', 'billing_address', + 'last4', 'cc_brand', 'stripe_charge_id'] + + def save(self, commit=True): + instance = super(DonationForm, self).save(commit=False) + + if commit: + DonatorStatus.create(self.cleaned_data['donator'].user) + instance.save() + + return instance + + +class DonationBillingForm(BillingAddressForm): + token = forms.CharField(widget=forms.HiddenInput()) + donation_amount = forms.FloatField(widget=forms.TextInput(attrs={'placeholder': 'Amount'})) + + class Meta: + model = BillingAddress + fields = ['donation_amount', 'street_address', 'city', 'postal_code', 'country'] + labels = { + 'amount': _('Amount'), + 'street_address': _('Street Address'), + 'city': _('City'), + 'postal_code': _('Postal Code'), + 'Country': _('Country'), + } diff --git a/nosystemd/management/__init__.py b/nosystemd/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nosystemd/management/commands/__init__.py b/nosystemd/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nosystemd/management/commands/make_donation_charges.py b/nosystemd/management/commands/make_donation_charges.py new file mode 100644 index 00000000..210b04be --- /dev/null +++ b/nosystemd/management/commands/make_donation_charges.py @@ -0,0 +1,51 @@ +from django.core.management.base import BaseCommand + +from nosystem.models import DonatorStatus, Donation +from datetime import datetime + +from utils.stripe_utils import StripeUtils +from .forms import DonationForm + + +class Command(BaseCommand): + help = 'Make the monthly stripe charge to all donators' + + def handle(self, *args, **options): + donators = DonatorStatus.objects.filter(status=DonatorStatus.ACTIVE) + current_month = datetime.now().month + current_year = datetime.now().year + for donator in donators: + current_month_donation = Donation.objects.get(created_at__month=current_month, + created_at__year=current_year) + if not current_month_donation: + last_donation = Donation.objects.filter(donator=donator).last() + + donation_amount = last_donation.donation + # Make stripe charge to a customer + stripe_utils = StripeUtils() + stripe_utils.CURRENCY = 'usd' + charge_response = stripe_utils.make_charge(amount=donation_amount, + customer=donator.stripe_id) + charge = charge_response.get('response_object') + + # Check if the payment was approved + if not charge: + # TODO save error + context = { + 'paymentError': charge_response.get('error'), + } + print(context) + # Create a donation + charge = charge_response.get('response_object') + donation_data = { + 'cc_brand': charge.source.brand, + 'stripe_charge_id': charge.id, + 'last4': charge.source.last4, + 'billing_address': last_donation.billing_address.id, + 'donator': donator.id, + 'donation': donation_amount + } + donation_form = DonationForm(donation_data) + if donation_form.is_valid(): + donation = donation_form.save() + print(donation) diff --git a/nosystemd/migrations/0001_initial.py b/nosystemd/migrations/0001_initial.py new file mode 100644 index 00000000..8fa8bb71 --- /dev/null +++ b/nosystemd/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-07-22 06:51 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('utils', '0002_billingaddress'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Donation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('donation', models.FloatField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('last4', models.CharField(max_length=4)), + ('cc_brand', models.CharField(max_length=10)), + ('stripe_charge_id', models.CharField(max_length=100, null=True)), + ('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress')), + ('donator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/nosystemd/migrations/0002_auto_20160723_1848.py b/nosystemd/migrations/0002_auto_20160723_1848.py new file mode 100644 index 00000000..b4585533 --- /dev/null +++ b/nosystemd/migrations/0002_auto_20160723_1848.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-07-23 18:48 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('nosystemd', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='donation', + name='donator', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer'), + ), + ] diff --git a/nosystemd/migrations/0003_donatorstatus.py b/nosystemd/migrations/0003_donatorstatus.py new file mode 100644 index 00000000..6054c5da --- /dev/null +++ b/nosystemd/migrations/0003_donatorstatus.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-07-26 01:53 +from __future__ import unicode_literals + +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), + ('nosystemd', '0002_auto_20160723_1848'), + ] + + operations = [ + migrations.CreateModel( + name='DonatorStatus', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('active', 'Active'), ('canceled', 'Canceled')], max_length=10)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/nosystemd/models.py b/nosystemd/models.py index 8400b34f..45c3948a 100644 --- a/nosystemd/models.py +++ b/nosystemd/models.py @@ -1,15 +1,28 @@ from django.db import models -from membership.models import CustomUser +from membership.models import StripeCustomer, CustomUser from utils.models import BillingAddress -# Create your models here. +class DonatorStatus(models.Model): + ACTIVE = 'active' + CANCELED = 'canceled' + + STATUS_CHOICES = ( + (ACTIVE, 'Active'), + (CANCELED, 'Canceled') + ) + user = models.OneToOneField(CustomUser) + status = models.CharField(choices=STATUS_CHOICES, max_length=10, default=ACTIVE) + + @classmethod + def create(cls, user): + cls.objects.get_or_create(user=user) class Donation(models.Model): donation = models.FloatField() - donator = models.ForeignKey(CustomUser) + donator = models.ForeignKey(StripeCustomer) created_at = models.DateTimeField(auto_now_add=True) billing_address = models.ForeignKey(BillingAddress) last4 = models.CharField(max_length=4) @@ -20,3 +33,9 @@ class Donation(models.Model): def create(cls, data): obj = cls.objects.create(**data) return obj + + def set_stripe_charge(self, stripe_charge): + self.stripe_charge_id = stripe_charge.id + self.last4 = stripe_charge.source.last4 + self.cc_brand = stripe_charge.source.brand + self.save diff --git a/nosystemd/static/nosystemd/js/donation.js b/nosystemd/static/nosystemd/js/donation.js new file mode 100644 index 00000000..c431f7ce --- /dev/null +++ b/nosystemd/static/nosystemd/js/donation.js @@ -0,0 +1,124 @@ +$( document ).ready(function() { + + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { + // Only send the token to relative URLs i.e. locally. + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + } + } + }); + + + var $form = $('#payment-form'); + $form.submit(payWithStripe); + + /* If you're using Stripe for payments */ + function payWithStripe(e) { + e.preventDefault(); + + /* Visual feedback */ + $form.find('[type=submit]').html('Validating '); + + var PublishableKey = window.stripeKey; + Stripe.setPublishableKey(PublishableKey); + Stripe.card.createToken($form, function stripeResponseHandler(status, response) { + if (response.error) { + /* Visual feedback */ + $form.find('[type=submit]').html('Try again'); + /* Show Stripe errors on the form */ + $form.find('.payment-errors').text(response.error.message); + $form.find('.payment-errors').closest('.row').show(); + } else { + /* Visual feedback */ + $form.find('[type=submit]').html('Processing '); + /* Hide Stripe errors on the form */ + $form.find('.payment-errors').closest('.row').hide(); + $form.find('.payment-errors').text(""); + // response contains id and card, which contains additional card details + var token = response.id; + // AJAX + + //set token on a hidden input + $('#id_token').val(token); + $('#donation-form').submit(); + } + }); + } + + /* Form validation */ + $.validator.addMethod("month", function(value, element) { + return this.optional(element) || /^(01|02|03|04|05|06|07|08|09|10|11|12)$/.test(value); + }, "Please specify a valid 2-digit month."); + + $.validator.addMethod("year", function(value, element) { + return this.optional(element) || /^[0-9]{2}$/.test(value); + }, "Please specify a valid 2-digit year."); + + validator = $form.validate({ + rules: { + cardNumber: { + required: true, + creditcard: true, + digits: true + }, + expMonth: { + required: true, + month: true + }, + expYear: { + required: true, + year: true + }, + cvCode: { + required: true, + digits: true + } + }, + highlight: function(element) { + $(element).closest('.form-control').removeClass('success').addClass('error'); + }, + unhighlight: function(element) { + $(element).closest('.form-control').removeClass('error').addClass('success'); + }, + errorPlacement: function(error, element) { + $(element).closest('.form-group').append(error); + } + }); + + paymentFormReady = function() { + if ($form.find('[name=cardNumber]').hasClass("success") && + $form.find('[name=expMonth]').hasClass("success") && + $form.find('[name=expYear]').hasClass("success") && + $form.find('[name=cvCode]').val().length > 1) { + return true; + } else { + return false; + } + } + + $form.find('[type=submit]').prop('disabled', true); + var readyInterval = setInterval(function() { + if (paymentFormReady()) { + $form.find('[type=submit]').prop('disabled', false); + clearInterval(readyInterval); + } + }, 250); + +}); + diff --git a/nosystemd/templates/nosystemd/base.html b/nosystemd/templates/nosystemd/base.html index a2041fee..b9691186 100644 --- a/nosystemd/templates/nosystemd/base.html +++ b/nosystemd/templates/nosystemd/base.html @@ -1,4 +1,4 @@ -{% load static bootstrap3 %} +{% load static bootstrap3 i18n %} @@ -59,9 +59,20 @@