From c3286a68a522eb3719a04bbb85491c1620571099 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 10:04:21 +0530 Subject: [PATCH] Use payment method instead of token and PaymentIntent all over --- .../datacenterlight/landing_payment.html | 1 - .../datacenterlight/order_detail.html | 13 ++ datacenterlight/views.py | 108 ++++++++----- hosting/static/hosting/js/payment.js | 150 +++++++++++------- .../hosting/js/virtual_machine_detail.js | 31 +++- hosting/views.py | 15 +- membership/models.py | 2 +- utils/stripe_utils.py | 46 ++++-- 8 files changed, 245 insertions(+), 121 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index f112f0d9..66a0e63f 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -187,7 +187,6 @@ window.enter_your_card_text = '{%trans "Enter your credit card number" %}'; (function () { - window.paymentIntentSecret = "{{payment_intent_secret}}"; window.stripeKey = "{{stripe_key}}"; window.current_lan = "{{LANGUAGE_CODE}}"; })(); diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index d8eb4934..1914acb8 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -2,6 +2,14 @@ {% load staticfiles bootstrap3 i18n custom_tags humanize %} {% block content %} +
{% if messages %}
@@ -321,5 +329,10 @@ {%endblock%} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5d12a878..f50cf422 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -285,28 +285,13 @@ class PaymentOrderView(FormView): product = GenericProduct.objects.get( id=self.request.session['product_id'] ) - # TODO get the correct price of the product from order - # confirmation - stripe_utils = StripeUtils() - payment_intent_response = stripe_utils.get_payment_intent( - int(product.get_actual_price() * 100) - ) - payment_intent = payment_intent_response.get('response_object') - if not payment_intent: - logger.error("Could not create payment_intent %s" % - str(payment_intent_response)) - else: - logger.debug("payment_intent.client_secret = %s" % - str(payment_intent.client_secret)) context.update({'generic_payment_form': ProductPaymentForm( prefix='generic_payment_form', - initial={ - 'product_name': product.product_name, - 'amount': float(product.get_actual_price()), - 'recurring': product.product_is_subscription, - 'description': product.product_description, - 'payment_intent_secret': payment_intent.client_secret - }, + initial={'product_name': product.product_name, + 'amount': float(product.get_actual_price()), + 'recurring': product.product_is_subscription, + 'description': product.product_description, + }, product_id=product.id ), }) else: @@ -479,8 +464,9 @@ class PaymentOrderView(FormView): context['generic_payment_form'] = generic_payment_form context['billing_address_form'] = address_form return self.render_to_response(context) - token = address_form.cleaned_data.get('token') - if token is '': + id_payment_method = self.request.POST.get('id_payment_method', + None) + if id_payment_method is None: card_id = address_form.cleaned_data.get('card') logger.debug("token is empty and card_id is %s" % card_id) try: @@ -508,15 +494,16 @@ class PaymentOrderView(FormView): ) request.session['card_id'] = user_card_detail.id else: - request.session['token'] = token - logger.debug("token is %s" % token) + request.session["id_payment_method"] = id_payment_method + logger.debug("id_payment_method is %s" % id_payment_method) if request.user.is_authenticated(): this_user = { 'email': request.user.email, 'name': request.user.name } customer = StripeCustomer.get_or_create( - email=this_user.get('email'), token=token + email=this_user.get('email'), + id_payment_method=id_payment_method ) else: user_email = address_form.cleaned_data.get('email') @@ -539,7 +526,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - token=token, + token=id_payment_method, customer_name=user_name) except CustomUser.DoesNotExist: logger.debug( @@ -550,7 +537,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - token=token, + token=id_payment_method, customer_name=user_name) billing_address = address_form.save() @@ -622,11 +609,11 @@ class OrderConfirmationView(DetailView, FormView): if (('specs' not in request.session or 'user' not in request.session) and 'generic_payment_type' not in request.session): return HttpResponseRedirect(reverse('datacenterlight:index')) - if 'token' in self.request.session: - token = self.request.session['token'] + if 'id_payment_method' in self.request.session: + payment_method = self.request.session['id_payment_method'] stripe_utils = StripeUtils() - card_details = stripe_utils.get_cards_details_from_token( - token + card_details = stripe_utils.get_cards_details_from_payment_method( + payment_method ) if not card_details.get('response_object'): return HttpResponseRedirect(reverse('hosting:payment')) @@ -635,6 +622,7 @@ class OrderConfirmationView(DetailView, FormView): context['cc_brand'] = card_details_response['brand'] context['cc_exp_year'] = card_details_response['exp_year'] context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month']) + context['id_payment_method'] = payment_method else: card_id = self.request.session.get('card_id') card_detail = UserCardDetail.objects.get(id=card_id) @@ -718,6 +706,27 @@ class OrderConfirmationView(DetailView, FormView): 'form': UserHostingKeyForm(request=self.request), 'keys': get_all_public_keys(self.request.user) }) + + # Obtain PaymentIntent so that we can initiate and charge/subscribe + # the customer + stripe_utils = StripeUtils() + payment_intent_response = stripe_utils.get_payment_intent( + int(request.session['generic_payment_details']['amount'] * + 100), + customer=request.session['customer'] + ) + payment_intent = payment_intent_response.get( + 'response_object') + if not payment_intent: + logger.error("Could not create payment_intent %s" % + str(payment_intent_response)) + else: + logger.debug("payment_intent.client_secret = %s" % + str(payment_intent.client_secret)) + context.update({ + 'payment_intent_secret': payment_intent.client_secret + }) + context.update({ 'site_url': reverse('datacenterlight:index'), 'page_header_text': _('Confirm Order'), @@ -725,6 +734,8 @@ class OrderConfirmationView(DetailView, FormView): request.session.get('billing_address_data') ), 'cms_integration': get_cms_integration('default'), + 'error_msg': get_error_response_dict("Error", request), + 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, }) return render(request, self.template_name, context) @@ -765,9 +776,9 @@ class OrderConfirmationView(DetailView, FormView): 'generic_payment_details': generic_payment_details } - if 'token' in request.session: - card_details = stripe_utils.get_cards_details_from_token( - request.session.get('token') + if 'id_payment_method' in request.session: + card_details = stripe_utils.get_cards_details_from_payment_method( + request.session.get('id_payment_method') ) logger.debug( "card_details=%s" % (card_details)) @@ -788,7 +799,7 @@ class OrderConfirmationView(DetailView, FormView): ) if not ucd: acc_result = stripe_utils.associate_customer_card( - stripe_api_cus_id, request.session['token'], + stripe_api_cus_id, request.session['id_payment_method'], set_as_default=True ) if acc_result['response_object'] is None: @@ -799,6 +810,21 @@ class OrderConfirmationView(DetailView, FormView): ) ) return show_error(msg, self.request) + else: + # Associate PaymentMethod with the stripe customer + # and set it as the default source + acc_result = stripe_utils.associate_customer_card( + stripe_api_cus_id, request.session['id_payment_method'], + set_as_default=True + ) + if acc_result['response_object'] is None: + msg = _( + 'An error occurred while associating the card.' + ' Details: {details}'.format( + details=acc_result['error'] + ) + ) + return show_error(msg, self.request) elif 'card_id' in request.session: card_id = request.session.get('card_id') user_card_detail = UserCardDetail.objects.get(id=card_id) @@ -1334,9 +1360,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, clear_all_session_vars(real_request) -def show_error(msg, request): - messages.add_message(request, messages.ERROR, msg, - extra_tags='failed_payment') +def get_error_response_dict(msg, request): response = { 'status': False, 'redirect': "{url}#{section}".format( @@ -1356,4 +1380,10 @@ def show_error(msg, request): ' On close of this popup, you will be redirected back to' ' the payment page.')) } - return JsonResponse(response) + return response + + +def show_error(msg, request): + messages.add_message(request, messages.ERROR, msg, + extra_tags='failed_payment') + return JsonResponse(get_error_response_dict(msg,request)) diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index 933e15df..a2e2717a 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -84,68 +84,72 @@ $(document).ready(function () { var hasCreditcard = window.hasCreditcard || false; if (!hasCreditcard && window.stripeKey) { var stripe = Stripe(window.stripeKey); - var element_style = { - fonts: [{ - family: 'lato-light', - src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")' - }, { - family: 'lato-regular', - src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Regular.woff) format("woff2")' - } - ], - locale: window.current_lan - }; - var elements = stripe.elements(element_style); - var credit_card_text_style = { - base: { - iconColor: '#666EE8', - color: '#31325F', - lineHeight: '25px', - fontWeight: 300, - fontFamily: "'lato-light', sans-serif", - fontSize: '14px', - '::placeholder': { - color: '#777' + if (window.pm_id) { + + } else { + var element_style = { + fonts: [{ + family: 'lato-light', + src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")' + }, { + family: 'lato-regular', + src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Regular.woff) format("woff2")' } - }, - invalid: { - iconColor: '#eb4d5c', - color: '#eb4d5c', - lineHeight: '25px', - fontWeight: 300, - fontFamily: "'lato-regular', sans-serif", - fontSize: '14px', - '::placeholder': { + ], + locale: window.current_lan + }; + var elements = stripe.elements(element_style); + var credit_card_text_style = { + base: { + iconColor: '#666EE8', + color: '#31325F', + lineHeight: '25px', + fontWeight: 300, + fontFamily: "'lato-light', sans-serif", + fontSize: '14px', + '::placeholder': { + color: '#777' + } + }, + invalid: { + iconColor: '#eb4d5c', color: '#eb4d5c', - fontWeight: 400 + lineHeight: '25px', + fontWeight: 300, + fontFamily: "'lato-regular', sans-serif", + fontSize: '14px', + '::placeholder': { + color: '#eb4d5c', + fontWeight: 400 + } } - } - }; + }; - var enter_ccard_text = "Enter your credit card number"; - if (typeof window.enter_your_card_text !== 'undefined') { - enter_ccard_text = window.enter_your_card_text; + var enter_ccard_text = "Enter your credit card number"; + if (typeof window.enter_your_card_text !== 'undefined') { + enter_ccard_text = window.enter_your_card_text; + } + var cardNumberElement = elements.create('cardNumber', { + style: credit_card_text_style, + placeholder: enter_ccard_text + }); + cardNumberElement.mount('#card-number-element'); + + var cardExpiryElement = elements.create('cardExpiry', { + style: credit_card_text_style + }); + cardExpiryElement.mount('#card-expiry-element'); + + var cardCvcElement = elements.create('cardCvc', { + style: credit_card_text_style + }); + cardCvcElement.mount('#card-cvc-element'); + cardNumberElement.on('change', function (event) { + if (event.brand) { + setBrandIcon(event.brand); + } + }); } - var cardNumberElement = elements.create('cardNumber', { - style: credit_card_text_style, - placeholder: enter_ccard_text - }); - cardNumberElement.mount('#card-number-element'); - - var cardExpiryElement = elements.create('cardExpiry', { - style: credit_card_text_style - }); - cardExpiryElement.mount('#card-expiry-element'); - - var cardCvcElement = elements.create('cardCvc', { - style: credit_card_text_style - }); - cardCvcElement.mount('#card-cvc-element'); - cardNumberElement.on('change', function (event) { - if (event.brand) { - setBrandIcon(event.brand); - } - }); } var submit_form_btn = $('#payment_button_with_creditcard'); @@ -163,7 +167,7 @@ $(document).ready(function () { if (parts.length === 2) return parts.pop().split(";").shift(); } - function submitBillingForm() { + function submitBillingForm(pmId) { var billing_form = $('#billing-form'); var recurring_input = $('#id_generic_payment_form-recurring'); billing_form.append(''); @@ -174,20 +178,46 @@ $(document).ready(function () { billing_form.append(''); } billing_form.append(''); + billing_form.append(''); billing_form.submit(); } var $form_new = $('#payment-form-new'); $form_new.submit(payWithPaymentIntent); + window.result = ""; + window.card = ""; function payWithPaymentIntent(e) { e.preventDefault(); - stripe.confirmCardPayment( + function stripePMHandler(paymentMethod) { + // Insert the token ID into the form so it gets submitted to the server + console.log(paymentMethod); + $('#id_payment_method').val(paymentMethod.id); + submitBillingForm(paymentMethod.id); + } + stripe.createPaymentMethod({ + type: 'card', + card: cardNumberElement, + }) + .then(function(result) { + // Handle result.error or result.paymentMethod + window.result = result; + if(result.error) { + var errorElement = document.getElementById('card-errors'); + errorElement.textContent = result.error.message; + } else { + console.log("created paymentMethod " + result.paymentMethod.id); + stripePMHandler(result.paymentMethod); + } + }); + window.card = cardNumberElement; + /* stripe.confirmCardPayment( window.paymentIntentSecret, { payment_method: {card: cardNumberElement} } ).then(function(result) { + window.result = result; if (result.error) { // Display error.message in your UI. var errorElement = document.getElementById('card-errors'); @@ -198,7 +228,7 @@ $(document).ready(function () { alert("Thanks for the order. Your product will be provisioned " + "as soon as we receive the payment. Thank you."); } - }); + }); */ } function payWithStripe_new(e) { e.preventDefault(); diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index dec8e680..e1bfd3a8 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -92,6 +92,35 @@ $(document).ready(function() { }); var create_vm_form = $('#virtual_machine_create_form'); + create_vm_form.submit(placeOrderPaymentIntent); + + function placeOrderPaymentIntent(e) { + e.preventDefault(); + var stripe = Stripe(window.stripeKey); + stripe.confirmCardPayment( + window.paymentIntentSecret, + { + payment_method: window.pm_id + } + ).then(function(result) { + window.result = result; + if (result.error) { + // Display error.message in your UI. + var errorElement = document.getElementById('card-errors'); + errorElement.textContent = result.error.message; + } else { + // The payment has succeeded + // Display a success message + alert("Thanks for the order. Your product will be provisioned " + + "as soon as we receive the payment. Thank you."); + modal_btn.attr('href', err).removeClass('hide'); + fa_icon.attr('class', 'checkmark'); + $('#createvm-modal-title').text(data.success.msg_title); + $('#createvm-modal-body').html(data.success.msg_body); + } + }); + } + /* create_vm_form.submit(function () { $('#btn-create-vm').prop('disabled', true); $.ajax({ @@ -154,7 +183,7 @@ $(document).ready(function() { } }); return false; - }); + });*/ $('#createvm-modal').on('hidden.bs.modal', function () { $(this).find('.modal-footer .btn').addClass('hide'); }); diff --git a/hosting/views.py b/hosting/views.py index cc038d12..f18a22b7 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -694,16 +694,17 @@ class SettingsView(LoginRequiredMixin, FormView): msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) else: - token = form.cleaned_data.get('token') + # TODO : Test this flow + id_payment_method = form.cleaned_data.get('id_payment_method') stripe_utils = StripeUtils() - card_details = stripe_utils.get_cards_details_from_token( - token + card_details = stripe_utils.get_cards_details_from_payment_method( + id_payment_method ) if not card_details.get('response_object'): form.add_error("__all__", card_details.get('error')) return self.render_to_response(self.get_context_data()) stripe_customer = StripeCustomer.get_or_create( - email=request.user.email, token=token + email=request.user.email, id_payment_method=id_payment_method ) card = card_details['response_object'] if UserCardDetail.get_user_card_details(stripe_customer, card): @@ -711,7 +712,7 @@ class SettingsView(LoginRequiredMixin, FormView): messages.add_message(request, messages.ERROR, msg) else: acc_result = stripe_utils.associate_customer_card( - request.user.stripecustomer.stripe_id, token + request.user.stripecustomer.stripe_id, id_payment_method ) if acc_result['response_object'] is None: msg = _( @@ -1085,7 +1086,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): template, specs, stripe_customer_id, billing_address_data, vm_template_id, stripe_api_cus_id) ) - if 'token' in self.request.session: + if 'id_payment_method' in self.request.session: card_details = stripe_utils.get_cards_details_from_token( request.session['token'] ) @@ -1102,7 +1103,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): ) if not ucd: acc_result = stripe_utils.associate_customer_card( - stripe_api_cus_id, request.session['token'], + stripe_api_cus_id, request.session['id_payment_method'], set_as_default=True ) if acc_result['response_object'] is None: diff --git a/membership/models.py b/membership/models.py index 703b4800..079b60e0 100644 --- a/membership/models.py +++ b/membership/models.py @@ -296,7 +296,7 @@ class StripeCustomer(models.Model): return None @classmethod - def get_or_create(cls, email=None, token=None): + def get_or_create(cls, email=None, token=None, id_payment_method=None): """ Check if there is a registered stripe customer with that email or create a new one diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index bf731508..a4cc2c6a 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -83,12 +83,15 @@ class StripeUtils(object): customer.save() @handleStripeError - def associate_customer_card(self, stripe_customer_id, token, + def associate_customer_card(self, stripe_customer_id, id_payment_method, set_as_default=False): customer = stripe.Customer.retrieve(stripe_customer_id) - card = customer.sources.create(source=token) + stripe.PaymentMethod.attach( + id_payment_method, + customer=stripe_customer_id, + ) if set_as_default: - customer.default_source = card.id + customer.invoice_settings.default_payment_method = id_payment_method customer.save() return True @@ -100,6 +103,7 @@ class StripeUtils(object): @handleStripeError def update_customer_card(self, customer_id, token): + # TODO replace token with payment intent customer = stripe.Customer.retrieve(customer_id) current_card_token = customer.default_source customer.sources.retrieve(current_card_token).delete() @@ -188,6 +192,24 @@ class StripeUtils(object): } return card_details + @handleStripeError + def get_cards_details_from_payment_method(self, payment_method_id): + payment_method = stripe.PaymentMethod.retrieve(payment_method_id) + # payment_method does not always seem to have a card with id + # if that is the case, fallback to payment_method_id for card_id + card_id = payment_method_id + if hasattr(payment_method.card, 'id'): + card_id = payment_method.card.id + card_details = { + 'last4': payment_method.card.last4, + 'brand': payment_method.card.brand, + 'exp_month': payment_method.card.exp_month, + 'exp_year': payment_method.card.exp_year, + 'fingerprint': payment_method.card.fingerprint, + 'card_id': card_id + } + return card_details + def check_customer(self, stripe_cus_api_id, user, token): try: customer = stripe.Customer.retrieve(stripe_cus_api_id) @@ -207,11 +229,11 @@ class StripeUtils(object): return customer @handleStripeError - def create_customer(self, token, email, name=None): + def create_customer(self, id_payment_method, email, name=None): if name is None or name.strip() == "": name = email customer = self.stripe.Customer.create( - source=token, + payment_method=id_payment_method, description=name, email=email ) @@ -494,19 +516,19 @@ class StripeUtils(object): return tax_id_obj @handleStripeError - def get_payment_intent(self, amount): - """ - Adds VM metadata to a subscription - :param amount: the amount of payment_intent - :return: + def get_payment_intent(self, amount, customer): + """ Create a stripe PaymentIntent of the given amount and return it + :param amount: the amount of payment_intent + :return: """ payment_intent_obj = stripe.PaymentIntent.create( amount=amount, - currency='chf' + currency='chf', + customer=customer ) return payment_intent_obj def compare_vat_numbers(self, vat1, vat2): _vat1 = vat1.replace(" ", "").replace(".", "").replace("-","") _vat2 = vat2.replace(" ", "").replace(".", "").replace("-","") - return True if _vat1 == _vat2 else False \ No newline at end of file + return True if _vat1 == _vat2 else False