Handle IncompleteSubscriptions in webhook
This commit is contained in:
		
					parent
					
						
							
								c4c918d591
							
						
					
				
			
			
				commit
				
					
						17c8f9ca18
					
				
			
		
					 3 changed files with 130 additions and 60 deletions
				
			
		| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
import stripe
 | 
					import stripe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.contrib import messages
 | 
					from django.contrib import messages
 | 
				
			||||||
| 
						 | 
					@ -19,9 +20,8 @@ from hosting.forms import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from hosting.models import (
 | 
					from hosting.models import (
 | 
				
			||||||
    HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey,
 | 
					    HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey,
 | 
				
			||||||
    StripeTaxRate)
 | 
					    StripeTaxRate, IncompleteSubscriptions)
 | 
				
			||||||
from membership.models import CustomUser, StripeCustomer
 | 
					from membership.models import CustomUser, StripeCustomer
 | 
				
			||||||
from opennebula_api.models import OpenNebulaManager
 | 
					 | 
				
			||||||
from opennebula_api.serializers import VMTemplateSerializer
 | 
					from opennebula_api.serializers import VMTemplateSerializer
 | 
				
			||||||
from utils.forms import (
 | 
					from utils.forms import (
 | 
				
			||||||
    BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
 | 
					    BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
 | 
				
			||||||
| 
						 | 
					@ -894,6 +894,53 @@ class OrderConfirmationView(DetailView, FormView):
 | 
				
			||||||
            latest_invoice = stripe.Invoice.retrieve(
 | 
					            latest_invoice = stripe.Invoice.retrieve(
 | 
				
			||||||
                stripe_subscription_obj.latest_invoice)
 | 
					                stripe_subscription_obj.latest_invoice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            new_user_hosting_key_id = None
 | 
				
			||||||
 | 
					            card_id = None
 | 
				
			||||||
 | 
					            generic_payment_type = None
 | 
				
			||||||
 | 
					            generic_payment_details = None
 | 
				
			||||||
 | 
					            if 'generic_payment_details' in request.session:
 | 
				
			||||||
 | 
					                generic_payment_details = request.session[
 | 
				
			||||||
 | 
					                    'generic_payment_details']
 | 
				
			||||||
 | 
					            if 'generic_payment_type' in request.session:
 | 
				
			||||||
 | 
					                generic_payment_type = request.session['generic_payment_type']
 | 
				
			||||||
 | 
					            if 'new_user_hosting_key_id' in self.request.session:
 | 
				
			||||||
 | 
					                new_user_hosting_key_id = request.session[
 | 
				
			||||||
 | 
					                    'new_user_hosting_key_id']
 | 
				
			||||||
 | 
					            if 'card_id' in request.session:
 | 
				
			||||||
 | 
					                card_id = request.session.get('card_id')
 | 
				
			||||||
 | 
					            req = {
 | 
				
			||||||
 | 
					                'scheme': self.request.scheme,
 | 
				
			||||||
 | 
					                'host': self.request.get_host(),
 | 
				
			||||||
 | 
					                'language': get_language(),
 | 
				
			||||||
 | 
					                'new_user_hosting_key_id': new_user_hosting_key_id,
 | 
				
			||||||
 | 
					                'card_id': card_id,
 | 
				
			||||||
 | 
					                'generic_payment_type': generic_payment_type,
 | 
				
			||||||
 | 
					                'generic_payment_details': generic_payment_details
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            subscription_status = ''
 | 
				
			||||||
 | 
					            if stripe_subscription_obj:
 | 
				
			||||||
 | 
					                subscription_status = stripe_subscription_obj.status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Store params so that they can be retrieved later
 | 
				
			||||||
 | 
					            IncompleteSubscriptions.objects.create(
 | 
				
			||||||
 | 
					                subscription_status=subscription_status,
 | 
				
			||||||
 | 
					                name=user.get('name'),
 | 
				
			||||||
 | 
					                email=user.get('email'),
 | 
				
			||||||
 | 
					                request=json.dumps(req),
 | 
				
			||||||
 | 
					                stripe_api_cus_id=stripe_api_cus_id,
 | 
				
			||||||
 | 
					                card_details_response=json.dumps(card_details_response),
 | 
				
			||||||
 | 
					                stripe_subscription_obj=json.dumps(stripe_subscription_obj) if stripe_customer_obj else '',
 | 
				
			||||||
 | 
					                stripe_onetime_charge=json.dumps(stripe_onetime_charge) if stripe_onetime_charge else '',
 | 
				
			||||||
 | 
					                gp_details=json.dumps(gp_details) if gp_details else '',
 | 
				
			||||||
 | 
					                specs=json.dumps(specs) if specs else '',
 | 
				
			||||||
 | 
					                vm_template_id=vm_template_id if vm_template_id else 0,
 | 
				
			||||||
 | 
					                template=json.dumps(template) if template else '',
 | 
				
			||||||
 | 
					                billing_address_data=json.dumps(
 | 
				
			||||||
 | 
					                    request.session.get('billing_address_data')
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Check if the subscription was approved and is active
 | 
					            # Check if the subscription was approved and is active
 | 
				
			||||||
            if (stripe_subscription_obj is None
 | 
					            if (stripe_subscription_obj is None
 | 
				
			||||||
                    or (stripe_subscription_obj.status != 'active'
 | 
					                    or (stripe_subscription_obj.status != 'active'
 | 
				
			||||||
| 
						 | 
					@ -953,6 +1000,7 @@ class OrderConfirmationView(DetailView, FormView):
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    clear_all_session_vars(request)
 | 
				
			||||||
                    return JsonResponse(context)
 | 
					                    return JsonResponse(context)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    logger.debug(
 | 
					                    logger.debug(
 | 
				
			||||||
| 
						 | 
					@ -962,28 +1010,6 @@ class OrderConfirmationView(DetailView, FormView):
 | 
				
			||||||
                        "requires_source_action")
 | 
					                        "requires_source_action")
 | 
				
			||||||
                    msg = subscription_result.get('error')
 | 
					                    msg = subscription_result.get('error')
 | 
				
			||||||
                    return show_error(msg, self.request)
 | 
					                    return show_error(msg, self.request)
 | 
				
			||||||
        new_user_hosting_key_id = None
 | 
					 | 
				
			||||||
        card_id = None
 | 
					 | 
				
			||||||
        generic_payment_type = None
 | 
					 | 
				
			||||||
        generic_payment_details = None
 | 
					 | 
				
			||||||
        if 'generic_payment_details' in request.session:
 | 
					 | 
				
			||||||
            generic_payment_details = request.session['generic_payment_details']
 | 
					 | 
				
			||||||
        if 'generic_payment_type' in request.session:
 | 
					 | 
				
			||||||
            generic_payment_type = request.session['generic_payment_type']
 | 
					 | 
				
			||||||
        if 'new_user_hosting_key_id' in self.request.session:
 | 
					 | 
				
			||||||
            new_user_hosting_key_id = request.session['new_user_hosting_key_id']
 | 
					 | 
				
			||||||
        if 'card_id' in request.session:
 | 
					 | 
				
			||||||
            card_id = request.session.get('card_id')
 | 
					 | 
				
			||||||
        req = {
 | 
					 | 
				
			||||||
            'scheme': self.request.scheme,
 | 
					 | 
				
			||||||
            'host': self.request.get_host(),
 | 
					 | 
				
			||||||
            'language': get_language(),
 | 
					 | 
				
			||||||
            'new_user_hosting_key_id': new_user_hosting_key_id,
 | 
					 | 
				
			||||||
            'card_id': card_id,
 | 
					 | 
				
			||||||
            'generic_payment_type': generic_payment_type,
 | 
					 | 
				
			||||||
            'generic_payment_details': generic_payment_details
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        do_create_vm(req, user, stripe_api_cus_id,
 | 
					        do_create_vm(req, user, stripe_api_cus_id,
 | 
				
			||||||
            card_details_response, stripe_subscription_obj,
 | 
					            card_details_response, stripe_subscription_obj,
 | 
				
			||||||
            stripe_onetime_charge, gp_details, specs, vm_template_id,
 | 
					            stripe_onetime_charge, gp_details, specs, vm_template_id,
 | 
				
			||||||
| 
						 | 
					@ -1191,14 +1217,14 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response,
 | 
				
			||||||
                      "We have just received a payment of CHF {amount:.2f}"
 | 
					                      "We have just received a payment of CHF {amount:.2f}"
 | 
				
			||||||
                      " from you.{recurring}\n\n"
 | 
					                      " from you.{recurring}\n\n"
 | 
				
			||||||
                      "Cheers,\nYour Data Center Light team".format(
 | 
					                      "Cheers,\nYour Data Center Light team".format(
 | 
				
			||||||
                name=user.get('name'),
 | 
					                            name=user.get('name'),
 | 
				
			||||||
                amount=gp_details['amount'],
 | 
					                            amount=gp_details['amount'],
 | 
				
			||||||
                recurring=(
 | 
					                            recurring=(
 | 
				
			||||||
                    recurring_text
 | 
					                                recurring_text
 | 
				
			||||||
                    if gp_details['recurring'] else ''
 | 
					                                if gp_details['recurring'] else ''
 | 
				
			||||||
                )
 | 
					                            )
 | 
				
			||||||
            )
 | 
					                        )
 | 
				
			||||||
            ),
 | 
					                    ),
 | 
				
			||||||
            'reply_to': ['info@ungleich.ch'],
 | 
					            'reply_to': ['info@ungleich.ch'],
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        send_plain_email_task.delay(email_data)
 | 
					        send_plain_email_task.delay(email_data)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -741,3 +741,22 @@ class StripeTaxRate(AssignPermissionsMixin, models.Model):
 | 
				
			||||||
    display_name = models.CharField(max_length=100)
 | 
					    display_name = models.CharField(max_length=100)
 | 
				
			||||||
    percentage = models.FloatField(default=0)
 | 
					    percentage = models.FloatField(default=0)
 | 
				
			||||||
    description = models.CharField(max_length=100)
 | 
					    description = models.CharField(max_length=100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IncompleteSubscriptions(AssignPermissionsMixin, models.Model):
 | 
				
			||||||
 | 
					    created_at = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
 | 
					    completed_at = models.DateTimeField()
 | 
				
			||||||
 | 
					    subscription_id = models.CharField(max_length=100)
 | 
				
			||||||
 | 
					    subscription_status = models.CharField(max_length=30)
 | 
				
			||||||
 | 
					    name = models.CharField(max_length=50)
 | 
				
			||||||
 | 
					    email = models.EmailField()
 | 
				
			||||||
 | 
					    request = models.TextField()
 | 
				
			||||||
 | 
					    stripe_api_cus_id = models.CharField(max_length=30)
 | 
				
			||||||
 | 
					    card_details_response = models.TextField()
 | 
				
			||||||
 | 
					    stripe_subscription_obj = models.TextField()
 | 
				
			||||||
 | 
					    stripe_onetime_charge = models.TextField()
 | 
				
			||||||
 | 
					    gp_details = models.TextField()
 | 
				
			||||||
 | 
					    specs = models.TextField()
 | 
				
			||||||
 | 
					    vm_template_id = models.PositiveIntegerField(default=0)
 | 
				
			||||||
 | 
					    template = models.TextField()
 | 
				
			||||||
 | 
					    billing_address_data = models.TextField()
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,8 @@
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
import stripe
 | 
					import stripe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Create your views here.
 | 
					# Create your views here.
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.http import HttpResponse
 | 
					from django.http import HttpResponse
 | 
				
			||||||
| 
						 | 
					@ -10,7 +11,7 @@ from django.views.decorators.http import require_POST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datacenterlight.views import do_create_vm
 | 
					from datacenterlight.views import do_create_vm
 | 
				
			||||||
from membership.models import StripeCustomer
 | 
					from membership.models import StripeCustomer
 | 
				
			||||||
from hosting.models import HostingOrder
 | 
					from hosting.models import IncompleteSubscriptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from utils.models import BillingAddress, UserBillingAddress
 | 
					from utils.models import BillingAddress, UserBillingAddress
 | 
				
			||||||
from utils.tasks import send_plain_email_task
 | 
					from utils.tasks import send_plain_email_task
 | 
				
			||||||
| 
						 | 
					@ -115,14 +116,16 @@ def handle_webhook(request):
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        send_plain_email_task.delay(email_data)
 | 
					        send_plain_email_task.delay(email_data)
 | 
				
			||||||
    elif event.type == 'invoice.paid':
 | 
					    elif event.type == 'invoice.paid':
 | 
				
			||||||
        #https://stripe.com/docs/billing/migration/strong-customer-authentication#scenario-1-handling-fulfillment
 | 
					        #More info:  https://stripe.com/docs/billing/migration/strong-customer-authentication#scenario-1-handling-fulfillment
 | 
				
			||||||
        invoice_obj = event.data.object
 | 
					        invoice_obj = event.data.object
 | 
				
			||||||
        logger.debug("Webhook Event: invoice.paid")
 | 
					        logger.debug("Webhook Event: invoice.paid")
 | 
				
			||||||
        logger.debug("invoice_obj %s " % str(invoice_obj))
 | 
					        logger.debug("invoice_obj %s " % str(invoice_obj))
 | 
				
			||||||
        logger.debug("invoice_obj.paid = %s %s" % (invoice_obj.paid, type(invoice_obj.paid)))
 | 
					        logger.debug("invoice_obj.paid = %s %s" % (invoice_obj.paid, type(invoice_obj.paid)))
 | 
				
			||||||
        logger.debug("invoice_obj.billing_reason = %s %s" % (invoice_obj.billing_reason, type(invoice_obj.billing_reason)))
 | 
					        logger.debug("invoice_obj.billing_reason = %s %s" % (invoice_obj.billing_reason, type(invoice_obj.billing_reason)))
 | 
				
			||||||
        # We should check for billing_reason == "subscription_create" but we check for "subscription_update"
 | 
					        # We should check for billing_reason == "subscription_create" but we
 | 
				
			||||||
        # because we are using older api. See https://stripe.com/docs/upgrades?since=2015-07-13
 | 
					        # check for "subscription_update"
 | 
				
			||||||
 | 
					        # because we are using older api.
 | 
				
			||||||
 | 
					        # See https://stripe.com/docs/upgrades?since=2015-07-13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The billing_reason attribute of the invoice object now can take the
 | 
					        # The billing_reason attribute of the invoice object now can take the
 | 
				
			||||||
        # value of subscription_create, indicating that it is the first
 | 
					        # value of subscription_create, indicating that it is the first
 | 
				
			||||||
| 
						 | 
					@ -130,32 +133,54 @@ def handle_webhook(request):
 | 
				
			||||||
        # billing_reason=subscription_create is represented as
 | 
					        # billing_reason=subscription_create is represented as
 | 
				
			||||||
        # subscription_update.
 | 
					        # subscription_update.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if invoice_obj.paid and invoice_obj.billing_reason == "subscription_update":
 | 
					        if (invoice_obj.paid and
 | 
				
			||||||
 | 
					                invoice_obj.billing_reason == "subscription_update"):
 | 
				
			||||||
            logger.debug("Start provisioning")
 | 
					            logger.debug("Start provisioning")
 | 
				
			||||||
            # get subscription id, order_id
 | 
					 | 
				
			||||||
            ho = None
 | 
					 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                ho = HostingOrder.objects.get(subscription_id=invoice_obj.subscription)
 | 
					                stripe_subscription_obj = stripe.Subscription.retrieve(
 | 
				
			||||||
 | 
					                    invoice_obj.subscription)
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    incomplete_sub = IncompleteSubscriptions.objects.get(
 | 
				
			||||||
 | 
					                        subscription_id=invoice_obj.subscription)
 | 
				
			||||||
 | 
					                    logger.debug("*******")
 | 
				
			||||||
 | 
					                    logger.debug(incomplete_sub)
 | 
				
			||||||
 | 
					                    logger.debug("*******")
 | 
				
			||||||
 | 
					                    do_create_vm(
 | 
				
			||||||
 | 
					                        request=incomplete_sub.request,
 | 
				
			||||||
 | 
					                        user={'name': incomplete_sub.name,
 | 
				
			||||||
 | 
					                              'email': incomplete_sub.email},
 | 
				
			||||||
 | 
					                        stripe_api_cus_id=incomplete_sub.stripe_api_cus_id,
 | 
				
			||||||
 | 
					                        card_details_response=json.loads(
 | 
				
			||||||
 | 
					                            incomplete_sub.card_details_response),
 | 
				
			||||||
 | 
					                        stripe_subscription_obj=json.loads(
 | 
				
			||||||
 | 
					                            stripe_subscription_obj),
 | 
				
			||||||
 | 
					                        stripe_onetime_charge=json.loads(
 | 
				
			||||||
 | 
					                            incomplete_sub.stripe_onetime_charge),
 | 
				
			||||||
 | 
					                        gp_details=json.loads(incomplete_sub.gp_details),
 | 
				
			||||||
 | 
					                        specs=json.loads(incomplete_sub.specs),
 | 
				
			||||||
 | 
					                        vm_template_id=incomplete_sub.vm_template_id,
 | 
				
			||||||
 | 
					                        template=json.loads(incomplete_sub.template)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                except (IncompleteSubscriptions.DoesNotExist,
 | 
				
			||||||
 | 
					                        IncompleteSubscriptions.MultipleObjectsReturned) as ex:
 | 
				
			||||||
 | 
					                    logger.error(str(ex))
 | 
				
			||||||
 | 
					                    # TODO Inform admin
 | 
				
			||||||
 | 
					                    email_data = {
 | 
				
			||||||
 | 
					                        'subject': "IncompleteSubscriptions error",
 | 
				
			||||||
 | 
					                        'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
				
			||||||
 | 
					                        'to': settings.DCL_ERROR_EMAILS_TO_LIST,
 | 
				
			||||||
 | 
					                        'body': "Response = %s" % str(ex),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    send_plain_email_task.delay(email_data)
 | 
				
			||||||
            except Exception as ex:
 | 
					            except Exception as ex:
 | 
				
			||||||
                logger.error(str(ex))
 | 
					                logger.error(str(ex))
 | 
				
			||||||
            if ho:
 | 
					                email_data = {
 | 
				
			||||||
                logger.debug("Create a VM for order %s" % str(ho))
 | 
					                    'subject': "invoice.paid Webhook error",
 | 
				
			||||||
                # TODO: fix the error below
 | 
					                    'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
				
			||||||
                try:
 | 
					                    'to': settings.DCL_ERROR_EMAILS_TO_LIST,
 | 
				
			||||||
                    user = {'name': ho.customer.user.name,
 | 
					                    'body': "Response = %s" % str(ex),
 | 
				
			||||||
                            'email': ho.customer.user.email}
 | 
					                }
 | 
				
			||||||
                    stripe_api_cus_id = ho.customer.stripe_id
 | 
					                send_plain_email_task.delay(email_data)
 | 
				
			||||||
                    stripe_subscription_obj = stripe.Subscription.retrieve(invoice_obj.subscription)
 | 
					 | 
				
			||||||
                    do_create_vm(request, user, stripe_api_cus_id,
 | 
					 | 
				
			||||||
                                 card_details_response,
 | 
					 | 
				
			||||||
                                 stripe_subscription_obj,
 | 
					 | 
				
			||||||
                                 stripe_onetime_charge, gp_details, specs,
 | 
					 | 
				
			||||||
                                 vm_template_id,
 | 
					 | 
				
			||||||
                                 template
 | 
					 | 
				
			||||||
                                 )
 | 
					 | 
				
			||||||
                except Exception as ex:
 | 
					 | 
				
			||||||
                    logger.error(str(ex))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        logger.error("Unhandled event : " + event.type)
 | 
					        logger.error("Unhandled event : " + event.type)
 | 
				
			||||||
    return HttpResponse(status=200)
 | 
					    return HttpResponse(status=200)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue