From 17c8f9ca18f813652433cf4eaab9f354da4118f0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 10:59:21 +0530 Subject: [PATCH] Handle IncompleteSubscriptions in webhook --- datacenterlight/views.py | 92 ++++++++++++++++++++++++++-------------- hosting/models.py | 19 +++++++++ webhook/views.py | 79 ++++++++++++++++++++++------------ 3 files changed, 130 insertions(+), 60 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 00f8a555..3eb77412 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,6 +1,7 @@ import logging - +import json import stripe + from django import forms from django.conf import settings from django.contrib import messages @@ -19,9 +20,8 @@ from hosting.forms import ( ) from hosting.models import ( HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey, - StripeTaxRate) + StripeTaxRate, IncompleteSubscriptions) from membership.models import CustomUser, StripeCustomer -from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VMTemplateSerializer from utils.forms import ( BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, @@ -894,6 +894,53 @@ class OrderConfirmationView(DetailView, FormView): latest_invoice = stripe.Invoice.retrieve( 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 if (stripe_subscription_obj is None or (stripe_subscription_obj.status != 'active' @@ -953,6 +1000,7 @@ class OrderConfirmationView(DetailView, FormView): ) } } + clear_all_session_vars(request) return JsonResponse(context) else: logger.debug( @@ -962,28 +1010,6 @@ class OrderConfirmationView(DetailView, FormView): "requires_source_action") msg = subscription_result.get('error') 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, card_details_response, stripe_subscription_obj, 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}" " from you.{recurring}\n\n" "Cheers,\nYour Data Center Light team".format( - name=user.get('name'), - amount=gp_details['amount'], - recurring=( - recurring_text - if gp_details['recurring'] else '' - ) - ) - ), + name=user.get('name'), + amount=gp_details['amount'], + recurring=( + recurring_text + if gp_details['recurring'] else '' + ) + ) + ), 'reply_to': ['info@ungleich.ch'], } send_plain_email_task.delay(email_data) diff --git a/hosting/models.py b/hosting/models.py index 1061cf54..0554f2e9 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -741,3 +741,22 @@ class StripeTaxRate(AssignPermissionsMixin, models.Model): display_name = models.CharField(max_length=100) percentage = models.FloatField(default=0) 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() \ No newline at end of file diff --git a/webhook/views.py b/webhook/views.py index 04d19b63..5f8c85b0 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -1,7 +1,8 @@ import datetime import logging - +import json import stripe + # Create your views here. from django.conf import settings 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 membership.models import StripeCustomer -from hosting.models import HostingOrder +from hosting.models import IncompleteSubscriptions from utils.models import BillingAddress, UserBillingAddress from utils.tasks import send_plain_email_task @@ -115,14 +116,16 @@ def handle_webhook(request): } send_plain_email_task.delay(email_data) 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 logger.debug("Webhook Event: invoice.paid") 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.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" - # because we are using older api. See https://stripe.com/docs/upgrades?since=2015-07-13 + # We should check for billing_reason == "subscription_create" but we + # 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 # 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 # 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") - # get subscription id, order_id - ho = None 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: logger.error(str(ex)) - if ho: - logger.debug("Create a VM for order %s" % str(ho)) - # TODO: fix the error below - try: - user = {'name': ho.customer.user.name, - 'email': ho.customer.user.email} - stripe_api_cus_id = ho.customer.stripe_id - 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)) - + email_data = { + 'subject': "invoice.paid Webhook 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) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200)