Handled stripe payment errors , Added invoice template, Added view to handle invoice data
This commit is contained in:
		
					parent
					
						
							
								bf334a38d4
							
						
					
				
			
			
				commit
				
					
						46ab364184
					
				
			
		
					 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%}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{% extends "hosting/base_short.html" %}
 | 
			
		||||
{% load staticfiles bootstrap3 %}
 | 
			
		||||
{%block content %} 
 | 
			
		||||
{% block content %} 
 | 
			
		||||
<!-- Credit card form -->
 | 
			
		||||
<div>
 | 
			
		||||
	<div class="container payment-container">
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +64,17 @@
 | 
			
		|||
	                                <p class="payment-errors"></p>
 | 
			
		||||
	                            </div>
 | 
			
		||||
	                        </div>
 | 
			
		||||
	                        {% if paymentError %}
 | 
			
		||||
	                        <div class="row">
 | 
			
		||||
	                            <div class="col-xs-12">
 | 
			
		||||
	                            	<p>
 | 
			
		||||
	                             	{% bootstrap_alert paymentError alert_type='danger' %}
 | 
			
		||||
	                             	</p>
 | 
			
		||||
	                            </div>
 | 
			
		||||
	                        </div>
 | 
			
		||||
	                        {% endif %}
 | 
			
		||||
 | 
			
		||||
                           
 | 
			
		||||
	                    </form>
 | 
			
		||||
	                </div>
 | 
			
		||||
	            </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +99,7 @@
 | 
			
		|||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</form>
 | 
			
		||||
				
 | 
			
		||||
			</div>
 | 
			
		||||
	        
 | 
			
		||||
	             
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
from django.conf.urls import url
 | 
			
		||||
 | 
			
		||||
from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
 | 
			
		||||
                    NodeJSHostingView, LoginView, SignupView, IndexView
 | 
			
		||||
                    NodeJSHostingView, LoginView, SignupView, IndexView, \
 | 
			
		||||
                    InvoiceVMView 
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'index/?$', IndexView.as_view(), name='index'),
 | 
			
		||||
| 
						 | 
				
			
			@ -11,4 +12,5 @@ urlpatterns = [
 | 
			
		|||
    url(r'login/?$', LoginView.as_view(),  name='login'),
 | 
			
		||||
    url(r'signup/?$', SignupView.as_view(), name='signup'),
 | 
			
		||||
    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 utils.stripe_utils import StripeUtils
 | 
			
		||||
from utils.forms import BillingAddressForm
 | 
			
		||||
from utils.models import BillingAddress
 | 
			
		||||
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
 | 
			
		||||
from .forms import HostingUserSignupForm, HostingUserLoginForm
 | 
			
		||||
from .mixins import ProcessVMSelectionMixin
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +156,7 @@ class PaymentVMView(FormView):
 | 
			
		|||
        form = self.get_form()
 | 
			
		||||
 | 
			
		||||
        if form.is_valid():
 | 
			
		||||
 | 
			
		||||
            context = self.get_context_data()
 | 
			
		||||
            specifications = request.session.get('vm_specs')
 | 
			
		||||
            vm_type = specifications.get('hosting_company')
 | 
			
		||||
            vm = VirtualMachineType.objects.get(hosting_company=vm_type)
 | 
			
		||||
| 
						 | 
				
			
			@ -185,20 +186,63 @@ class PaymentVMView(FormView):
 | 
			
		|||
 | 
			
		||||
            # Make stripe charge to a customer
 | 
			
		||||
            stripe_utils = StripeUtils()
 | 
			
		||||
            charge = stripe_utils.make_charge(amount=final_price,
 | 
			
		||||
            charge_response = stripe_utils.make_charge(amount=final_price,
 | 
			
		||||
                                              customer=customer.stripe_id)
 | 
			
		||||
            order.set_stripe_charge(charge)
 | 
			
		||||
            charge = charge_response.get('response_object')
 | 
			
		||||
 | 
			
		||||
            if not charge.paid:
 | 
			
		||||
                # raise an error 
 | 
			
		||||
                pass
 | 
			
		||||
            # Check if the payment was approved 
 | 
			
		||||
            if not charge:
 | 
			
		||||
                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
 | 
			
		||||
            order.set_approved()
 | 
			
		||||
            # order.charge = 
 | 
			
		||||
 | 
			
		||||
            # Billing Address should be store here
 | 
			
		||||
 | 
			
		||||
            return HttpResponseRedirect(reverse('hosting:payment'))
 | 
			
		||||
            request.session.update({
 | 
			
		||||
                'charge':charge,
 | 
			
		||||
                'order':order.id,
 | 
			
		||||
                'billing_address':billing_address.id
 | 
			
		||||
            })
 | 
			
		||||
            return HttpResponseRedirect(reverse('hosting:invoice'))
 | 
			
		||||
        else:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
 | 
			
		||||
    CURRENCY = 'chf'
 | 
			
		||||
| 
						 | 
				
			
			@ -12,18 +60,18 @@ class StripeUtils(object):
 | 
			
		|||
        self.stripe = stripe
 | 
			
		||||
        self.stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
 | 
			
		||||
 | 
			
		||||
    @handleStripeError
 | 
			
		||||
    def create_customer(self, token, email):
 | 
			
		||||
        stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
 | 
			
		||||
        customer = stripe.Customer.create(
 | 
			
		||||
        customer = self.stripe.Customer.create(
 | 
			
		||||
              source=token,
 | 
			
		||||
              description='description for testing',
 | 
			
		||||
              email=email
 | 
			
		||||
        )
 | 
			
		||||
        return customer
 | 
			
		||||
 | 
			
		||||
    @handleStripeError
 | 
			
		||||
    def make_charge(self, amount=None, customer=None):
 | 
			
		||||
        amount = int(amount * 100)  # stripe amount unit, in cents
 | 
			
		||||
 | 
			
		||||
        charge = self.stripe.Charge.create(
 | 
			
		||||
          amount=amount,  # in cents
 | 
			
		||||
          currency=self.CURRENCY,
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +79,7 @@ class StripeUtils(object):
 | 
			
		|||
        )
 | 
			
		||||
        return charge
 | 
			
		||||
 | 
			
		||||
    @handleStripeError
 | 
			
		||||
    def create_plan(self, amount, name, id):
 | 
			
		||||
        self.stripe.Plan.create(
 | 
			
		||||
          amount=amount,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue