From 38801abed78fac2df7029fcfe67001e5d67f167c Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 22 Apr 2016 08:36:38 -0500 Subject: [PATCH 1/2] Added View to render payment page, Added Payment and summary forms, Added Payment.js library to request stripe token , Added jQuery validator for handling payment form errors --- dynamicweb/settings/base.py | 1 + hosting/mixins.py | 20 +++ hosting/static/hosting/css/landing-page.css | 6 +- hosting/static/hosting/css/payment.css | 33 +++++ hosting/static/hosting/js/payment.js | 110 ++++++++++++++ hosting/static/hosting/js/pricing.js | 2 +- hosting/templates/hosting/base_short.html | 135 ++++++++++++++++++ .../templates/hosting/includes/_header.html | 1 - .../templates/hosting/includes/_pricing.html | 14 +- hosting/templates/hosting/login.html | 40 +++--- hosting/templates/hosting/payment.html | 90 ++++++++++++ hosting/urls.py | 4 +- hosting/views.py | 95 +++++++----- 13 files changed, 480 insertions(+), 71 deletions(-) create mode 100644 hosting/mixins.py create mode 100644 hosting/static/hosting/css/payment.css create mode 100644 hosting/static/hosting/js/payment.js create mode 100644 hosting/templates/hosting/base_short.html create mode 100644 hosting/templates/hosting/payment.html diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 39902025..9b4c2f75 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -30,6 +30,7 @@ dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR)) SITE_ID = 1 APP_ROOT_ENDPOINT = "/" +APPEND_SLASH=True LOGIN_URL = None LOGOUT_URL = None diff --git a/hosting/mixins.py b/hosting/mixins.py new file mode 100644 index 00000000..7104e157 --- /dev/null +++ b/hosting/mixins.py @@ -0,0 +1,20 @@ +from django.shortcuts import redirect +from django.core.urlresolvers import reverse + + +class ProcessVMSelectionMixin(object): + + def post(self, request, *args, **kwargs): + vm_specs = { + 'cores': request.POST.get('cores'), + 'memory': request.POST.get('memory'), + 'disk_size': request.POST.get('disk_space'), + 'hosting_company': request.POST.get('hosting_company'), + 'hosting_company_name': request.POST.get('hosting_company_name'), + 'final_price': request.POST.get('final_price') + } + request.session['vm_specs'] = vm_specs + if not request.user.is_authenticated(): + request.session['vm_specs'] = vm_specs + return redirect(reverse('hosting:login')) + return redirect(reverse('hosting:payment')) diff --git a/hosting/static/hosting/css/landing-page.css b/hosting/static/hosting/css/landing-page.css index 24be9dec..6698c5ec 100644 --- a/hosting/static/hosting/css/landing-page.css +++ b/hosting/static/hosting/css/landing-page.css @@ -31,8 +31,7 @@ h6 { } .intro-header { - height: 85%; - padding-top: 10%; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ + padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ padding-bottom: 50px; text-align: center; color: #f8f8f8; @@ -48,8 +47,7 @@ h6 { background-size: cover; } .intro-header-2 { - height: 85%; - padding-top: 100px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ + padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ padding-bottom: 50px; text-align: center; color: #f8f8f8; diff --git a/hosting/static/hosting/css/payment.css b/hosting/static/hosting/css/payment.css new file mode 100644 index 00000000..9b16f8b3 --- /dev/null +++ b/hosting/static/hosting/css/payment.css @@ -0,0 +1,33 @@ +.creditcard-box {padding-top:17%; padding-bottom: 11%;} +.creditcard-box .panel-title {display: inline;font-weight: bold; font-size:17px;} +.creditcard-box .checkbox.pull-right { margin: 0; } +.creditcard-box .pl-ziro { padding-left: 0px; } +.creditcard-box .form-control.error { + border-color: red; + outline: 0; + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(255,0,0,0.6); +} +.creditcard-box label.error { + font-weight: bold; + color: red; + padding: 2px 8px; + margin-top: 2px; +} +.creditcard-box .payment-errors { + font-weight: bold; + color: red; + padding: 2px 8px; + margin-top: 2px; +} + +.summary-box { + + padding-top:17%; + padding-bottom: 11%; +} + +.summary-box .content { + + padding-top: 15px; + +} \ No newline at end of file diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js new file mode 100644 index 00000000..2e187201 --- /dev/null +++ b/hosting/static/hosting/js/payment.js @@ -0,0 +1,110 @@ +$( document ).ready(function() { + + 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 = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh'; // Replace with your API publishable key + Stripe.setPublishableKey(PublishableKey); + Stripe.card.createToken($form, function stripeResponseHandler(status, response) { + console.log + 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; + console.log(token); + // AJAX + $.post('/account/stripe_card_token', { + token: token + }) + // Assign handlers immediately after making the request, + .done(function(data, textStatus, jqXHR) { + $form.find('[type=submit]').html('Payment successful ').prop('disabled', true); + }) + .fail(function(jqXHR, textStatus, errorThrown) { + $form.find('[type=submit]').html('There was a problem').removeClass('success').addClass('error'); + /* Show Stripe errors on the form */ + $form.find('.payment-errors').text('Try refreshing the page and trying again.'); + $form.find('.payment-errors').closest('.row').show(); + }); + } + }); + } + + /* 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/hosting/static/hosting/js/pricing.js b/hosting/static/hosting/js/pricing.js index 64e80638..7ff24518 100644 --- a/hosting/static/hosting/js/pricing.js +++ b/hosting/static/hosting/js/pricing.js @@ -8,7 +8,7 @@ $( document ).ready(function() { function calculate_price(vm_type){ var ID_SELECTOR = "#"; - var CURRENCY = "$"; + var CURRENCY = "CHF"; var final_price_selector = ID_SELECTOR.concat(vm_type.concat('-final-price')); var core_selector = ID_SELECTOR.concat(vm_type.concat('-cores')); var memory_selector = ID_SELECTOR.concat(vm_type.concat('-memory')); diff --git a/hosting/templates/hosting/base_short.html b/hosting/templates/hosting/base_short.html new file mode 100644 index 00000000..7aa1e702 --- /dev/null +++ b/hosting/templates/hosting/base_short.html @@ -0,0 +1,135 @@ +{% load staticfiles bootstrap3%} + + + + + + + + + + + + Payment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% block content %} + {% endblock %} + + + + + + + + + + + + + + + + + + + + + + diff --git a/hosting/templates/hosting/includes/_header.html b/hosting/templates/hosting/includes/_header.html index 03e3e5c2..a75e797c 100644 --- a/hosting/templates/hosting/includes/_header.html +++ b/hosting/templates/hosting/includes/_header.html @@ -1,5 +1,4 @@ {% load staticfiles %} -
diff --git a/hosting/templates/hosting/includes/_pricing.html b/hosting/templates/hosting/includes/_pricing.html index 628a65f8..2483a849 100644 --- a/hosting/templates/hosting/includes/_pricing.html +++ b/hosting/templates/hosting/includes/_pricing.html @@ -80,7 +80,10 @@ {% endfor %} {% for vm in vm_types %}
-
+ + {% csrf_token %} + +
  • @@ -91,7 +94,7 @@
    - {% with ''|center:10 as range %} {% for _ in range %} @@ -106,7 +109,7 @@
    - {% with ''|center:50 as range %} {% for _ in range %} @@ -120,12 +123,13 @@
  • - + GiB
  • -

    {{vm.default_price|floatformat}}$

    + +

    {{vm.default_price|floatformat}}CHF

    per month
  • diff --git a/hosting/templates/hosting/login.html b/hosting/templates/hosting/login.html index 04def39f..da8a378b 100644 --- a/hosting/templates/hosting/login.html +++ b/hosting/templates/hosting/login.html @@ -36,7 +36,7 @@ -