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%} | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| {% extends "hosting/base_short.html" %} | {% extends "hosting/base_short.html" %} | ||||||
| {% load staticfiles bootstrap3 %} | {% load staticfiles bootstrap3 %} | ||||||
| {%block content %}  | {% block content %}  | ||||||
| <!-- Credit card form --> | <!-- Credit card form --> | ||||||
| <div> | <div> | ||||||
| 	<div class="container payment-container"> | 	<div class="container payment-container"> | ||||||
|  | @ -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