Merge pull request #50 from levivm/feature/vm_pricing
Handled stripe payment errors , Added payment invoice
This commit is contained in:
		
				commit
				
					
						9502580030
					
				
			
		
					 5 changed files with 202 additions and 16 deletions
				
			
		
							
								
								
									
										79
									
								
								hosting/templates/hosting/invoice.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								hosting/templates/hosting/invoice.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					{% extends "hosting/base_short.html" %}
 | 
				
			||||||
 | 
					{% load staticfiles bootstrap3 %}
 | 
				
			||||||
 | 
					{% block content %} 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style type="text/css">
 | 
				
			||||||
 | 
					.invoice-title h2, .invoice-title h3 {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table > tbody > tr > .no-line {
 | 
				
			||||||
 | 
					    border-top: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table > thead > tr > .no-line {
 | 
				
			||||||
 | 
					    border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table > tbody > tr > .thick-line {
 | 
				
			||||||
 | 
					    border-top: 2px solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="container  payment-container">
 | 
				
			||||||
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					        <div class="col-xs-8 col-xs-offset-2">
 | 
				
			||||||
 | 
					    		<div class="invoice-title">
 | 
				
			||||||
 | 
					    			<h2>Invoice</h2><h3 class="pull-right">Order # {{order.id}}</h3>
 | 
				
			||||||
 | 
					    		</div>
 | 
				
			||||||
 | 
					    		<hr>
 | 
				
			||||||
 | 
					    		<div class="row">
 | 
				
			||||||
 | 
					    			<div class="col-xs-6">
 | 
				
			||||||
 | 
					    				<address>
 | 
				
			||||||
 | 
					                    <h3><b>Billed To:</b></h3>
 | 
				
			||||||
 | 
					    					John Smith<br>
 | 
				
			||||||
 | 
					    					1234 Main<br>
 | 
				
			||||||
 | 
					    					Apt. 4B<br>
 | 
				
			||||||
 | 
					    					Springfield, ST 54321
 | 
				
			||||||
 | 
					    				</address>
 | 
				
			||||||
 | 
					    			</div>
 | 
				
			||||||
 | 
					                <div class="col-xs-6 text-right">
 | 
				
			||||||
 | 
					                    <address>
 | 
				
			||||||
 | 
					                        <strong>Order Date:</strong><br>
 | 
				
			||||||
 | 
					                        {{order.created_at}}<br><br>
 | 
				
			||||||
 | 
					                    </address>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					    		</div>
 | 
				
			||||||
 | 
					    		<div class="row">
 | 
				
			||||||
 | 
					    			<div class="col-xs-6">
 | 
				
			||||||
 | 
					    				<address>
 | 
				
			||||||
 | 
					    					<strong>Payment Method:</strong><br>
 | 
				
			||||||
 | 
					    					{{brand}} ending **** {{last4}}<br>
 | 
				
			||||||
 | 
					    					{{user.email}}
 | 
				
			||||||
 | 
					    				</address>
 | 
				
			||||||
 | 
					    			</div>
 | 
				
			||||||
 | 
					    		</div>
 | 
				
			||||||
 | 
					    	</div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					        <div class="col-md-8 col-md-offset-2">
 | 
				
			||||||
 | 
					            <h3><b>Order summary</b></h3>
 | 
				
			||||||
 | 
					            <hr>
 | 
				
			||||||
 | 
					            <div class="content">
 | 
				
			||||||
 | 
					                <p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.hosting_company_name}}</span></p>
 | 
				
			||||||
 | 
					                <hr>
 | 
				
			||||||
 | 
					                <p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p>
 | 
				
			||||||
 | 
					                <hr>
 | 
				
			||||||
 | 
					                <p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
 | 
				
			||||||
 | 
					                <hr>
 | 
				
			||||||
 | 
					                <p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p>
 | 
				
			||||||
 | 
					                <hr>
 | 
				
			||||||
 | 
					                <h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{%endblock%}
 | 
				
			||||||
| 
						 | 
					@ -64,6 +64,17 @@
 | 
				
			||||||
	                                <p class="payment-errors"></p>
 | 
						                                <p class="payment-errors"></p>
 | 
				
			||||||
	                            </div>
 | 
						                            </div>
 | 
				
			||||||
	                        </div>
 | 
						                        </div>
 | 
				
			||||||
 | 
						                        {% if paymentError %}
 | 
				
			||||||
 | 
						                        <div class="row">
 | 
				
			||||||
 | 
						                            <div class="col-xs-12">
 | 
				
			||||||
 | 
						                            	<p>
 | 
				
			||||||
 | 
						                             	{% bootstrap_alert paymentError alert_type='danger' %}
 | 
				
			||||||
 | 
						                             	</p>
 | 
				
			||||||
 | 
						                            </div>
 | 
				
			||||||
 | 
						                        </div>
 | 
				
			||||||
 | 
						                        {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                           
 | 
				
			||||||
	                    </form>
 | 
						                    </form>
 | 
				
			||||||
	                </div>
 | 
						                </div>
 | 
				
			||||||
	            </div>
 | 
						            </div>
 | 
				
			||||||
| 
						 | 
					@ -88,6 +99,7 @@
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</form>
 | 
									</form>
 | 
				
			||||||
 | 
									
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
	        
 | 
						        
 | 
				
			||||||
	             
 | 
						             
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,8 @@
 | 
				
			||||||
from django.conf.urls import url
 | 
					from django.conf.urls import url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
 | 
					from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
 | 
				
			||||||
                    NodeJSHostingView, LoginView, SignupView, IndexView
 | 
					                    NodeJSHostingView, LoginView, SignupView, IndexView, \
 | 
				
			||||||
 | 
					                    InvoiceVMView 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urlpatterns = [
 | 
					urlpatterns = [
 | 
				
			||||||
    url(r'index/?$', IndexView.as_view(), name='index'),
 | 
					    url(r'index/?$', IndexView.as_view(), name='index'),
 | 
				
			||||||
| 
						 | 
					@ -11,4 +12,5 @@ urlpatterns = [
 | 
				
			||||||
    url(r'login/?$', LoginView.as_view(),  name='login'),
 | 
					    url(r'login/?$', LoginView.as_view(),  name='login'),
 | 
				
			||||||
    url(r'signup/?$', SignupView.as_view(), name='signup'),
 | 
					    url(r'signup/?$', SignupView.as_view(), name='signup'),
 | 
				
			||||||
    url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
 | 
					    url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
 | 
				
			||||||
 | 
					    url(r'invoice/?$', InvoiceVMView.as_view(), name='invoice'),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ from membership.forms import PaymentForm
 | 
				
			||||||
from membership.models import CustomUser, StripeCustomer
 | 
					from membership.models import CustomUser, StripeCustomer
 | 
				
			||||||
from utils.stripe_utils import StripeUtils
 | 
					from utils.stripe_utils import StripeUtils
 | 
				
			||||||
from utils.forms import BillingAddressForm
 | 
					from utils.forms import BillingAddressForm
 | 
				
			||||||
 | 
					from utils.models import BillingAddress
 | 
				
			||||||
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
 | 
					from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
 | 
				
			||||||
from .forms import HostingUserSignupForm, HostingUserLoginForm
 | 
					from .forms import HostingUserSignupForm, HostingUserLoginForm
 | 
				
			||||||
from .mixins import ProcessVMSelectionMixin
 | 
					from .mixins import ProcessVMSelectionMixin
 | 
				
			||||||
| 
						 | 
					@ -155,7 +156,7 @@ class PaymentVMView(FormView):
 | 
				
			||||||
        form = self.get_form()
 | 
					        form = self.get_form()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if form.is_valid():
 | 
					        if form.is_valid():
 | 
				
			||||||
 | 
					            context = self.get_context_data()
 | 
				
			||||||
            specifications = request.session.get('vm_specs')
 | 
					            specifications = request.session.get('vm_specs')
 | 
				
			||||||
            vm_type = specifications.get('hosting_company')
 | 
					            vm_type = specifications.get('hosting_company')
 | 
				
			||||||
            vm = VirtualMachineType.objects.get(hosting_company=vm_type)
 | 
					            vm = VirtualMachineType.objects.get(hosting_company=vm_type)
 | 
				
			||||||
| 
						 | 
					@ -185,20 +186,63 @@ class PaymentVMView(FormView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Make stripe charge to a customer
 | 
					            # Make stripe charge to a customer
 | 
				
			||||||
            stripe_utils = StripeUtils()
 | 
					            stripe_utils = StripeUtils()
 | 
				
			||||||
            charge = stripe_utils.make_charge(amount=final_price,
 | 
					            charge_response = stripe_utils.make_charge(amount=final_price,
 | 
				
			||||||
                                              customer=customer.stripe_id)
 | 
					                                              customer=customer.stripe_id)
 | 
				
			||||||
            order.set_stripe_charge(charge)
 | 
					            charge = charge_response.get('response_object')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not charge.paid:
 | 
					            # Check if the payment was approved 
 | 
				
			||||||
                # raise an error 
 | 
					            if not charge:
 | 
				
			||||||
                pass
 | 
					                context.update({
 | 
				
			||||||
 | 
					                    'paymentError': charge_response.get('error'),
 | 
				
			||||||
 | 
					                    'form':form
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                return render(request, self.template_name, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            charge = charge_response.get('response_object')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Associate an order with a stripe payment
 | 
				
			||||||
 | 
					            order.set_stripe_charge(charge)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # If the Stripe payment was successed, set order status approved
 | 
					            # If the Stripe payment was successed, set order status approved
 | 
				
			||||||
            order.set_approved()
 | 
					            order.set_approved()
 | 
				
			||||||
            # order.charge = 
 | 
					            request.session.update({
 | 
				
			||||||
 | 
					                'charge':charge,
 | 
				
			||||||
            # Billing Address should be store here
 | 
					                'order':order.id,
 | 
				
			||||||
 | 
					                'billing_address':billing_address.id
 | 
				
			||||||
            return HttpResponseRedirect(reverse('hosting:payment'))
 | 
					            })
 | 
				
			||||||
 | 
					            return HttpResponseRedirect(reverse('hosting:invoice'))
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return self.form_invalid(form)
 | 
					            return self.form_invalid(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvoiceVMView(View):
 | 
				
			||||||
 | 
					    template_name = "hosting/invoice.html"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
 | 
					        charge = self.request.session.get('charge')
 | 
				
			||||||
 | 
					        order_id = self.request.session.get('order')
 | 
				
			||||||
 | 
					        billing_address_id = self.request.session.get('billing_address')
 | 
				
			||||||
 | 
					        last4 = charge.get('source').get('last4')
 | 
				
			||||||
 | 
					        brand = charge.get('source').get('brand')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        order = get_object_or_404(HostingOrder, pk=order_id)
 | 
				
			||||||
 | 
					        billing_address = get_object_or_404(BillingAddress, pk=billing_address_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not charge:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = {
 | 
				
			||||||
 | 
					            'last4': last4,
 | 
				
			||||||
 | 
					            'brand': brand,
 | 
				
			||||||
 | 
					            'order': order,
 | 
				
			||||||
 | 
					            'billing_address': billing_address,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = self.get_context_data()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return render(request, self.template_name, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,54 @@ import stripe
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def handleStripeError(f):
 | 
				
			||||||
 | 
					        def handleProblems(*args, **kwargs):
 | 
				
			||||||
 | 
					            response = {
 | 
				
			||||||
 | 
					                'paid': False,
 | 
				
			||||||
 | 
					                'response_object': None,
 | 
				
			||||||
 | 
					                'error': None
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            common_message = "Currently its not possible to make payments."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                response_object = f(*args, **kwargs)
 | 
				
			||||||
 | 
					                response  = {
 | 
				
			||||||
 | 
					                    'response_object': response_object,
 | 
				
			||||||
 | 
					                    'error': None
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					            except stripe.error.CardError as e:
 | 
				
			||||||
 | 
					                # Since it's a decline, stripe.error.CardError will be caught
 | 
				
			||||||
 | 
					                body = e.json_body
 | 
				
			||||||
 | 
					                err = body['error']
 | 
				
			||||||
 | 
					                response.update({'error':err['message']})
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					            except stripe.error.RateLimitError as e:
 | 
				
			||||||
 | 
					                response.update({'error':"Too many requests made to the API too quickly"})
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					            except stripe.error.InvalidRequestError as e:
 | 
				
			||||||
 | 
					                response.update({'error':"Invalid parameters"})
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					            except stripe.error.AuthenticationError as e:
 | 
				
			||||||
 | 
					                # Authentication with Stripe's API failed
 | 
				
			||||||
 | 
					                # (maybe you changed API keys recently)
 | 
				
			||||||
 | 
					                response.update({'error':common_message})
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					            except stripe.error.APIConnectionError as e:
 | 
				
			||||||
 | 
					                response.update({'error':common_message})
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					            except stripe.error.StripeError as e:
 | 
				
			||||||
 | 
					                # maybe send email
 | 
				
			||||||
 | 
					                response.update({'error':common_message})
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                # maybe send email
 | 
				
			||||||
 | 
					                response.update({'error':common_message})
 | 
				
			||||||
 | 
					                return response
 | 
				
			||||||
 | 
					        return handleProblems
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StripeUtils(object):
 | 
					class StripeUtils(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CURRENCY = 'chf'
 | 
					    CURRENCY = 'chf'
 | 
				
			||||||
| 
						 | 
					@ -12,18 +60,18 @@ class StripeUtils(object):
 | 
				
			||||||
        self.stripe = stripe
 | 
					        self.stripe = stripe
 | 
				
			||||||
        self.stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
 | 
					        self.stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @handleStripeError
 | 
				
			||||||
    def create_customer(self, token, email):
 | 
					    def create_customer(self, token, email):
 | 
				
			||||||
        stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
 | 
					        customer = self.stripe.Customer.create(
 | 
				
			||||||
        customer = stripe.Customer.create(
 | 
					 | 
				
			||||||
              source=token,
 | 
					              source=token,
 | 
				
			||||||
              description='description for testing',
 | 
					              description='description for testing',
 | 
				
			||||||
              email=email
 | 
					              email=email
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return customer
 | 
					        return customer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @handleStripeError
 | 
				
			||||||
    def make_charge(self, amount=None, customer=None):
 | 
					    def make_charge(self, amount=None, customer=None):
 | 
				
			||||||
        amount = int(amount * 100)  # stripe amount unit, in cents
 | 
					        amount = int(amount * 100)  # stripe amount unit, in cents
 | 
				
			||||||
 | 
					 | 
				
			||||||
        charge = self.stripe.Charge.create(
 | 
					        charge = self.stripe.Charge.create(
 | 
				
			||||||
          amount=amount,  # in cents
 | 
					          amount=amount,  # in cents
 | 
				
			||||||
          currency=self.CURRENCY,
 | 
					          currency=self.CURRENCY,
 | 
				
			||||||
| 
						 | 
					@ -31,6 +79,7 @@ class StripeUtils(object):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return charge
 | 
					        return charge
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @handleStripeError
 | 
				
			||||||
    def create_plan(self, amount, name, id):
 | 
					    def create_plan(self, amount, name, id):
 | 
				
			||||||
        self.stripe.Plan.create(
 | 
					        self.stripe.Plan.create(
 | 
				
			||||||
          amount=amount,
 | 
					          amount=amount,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue