Use payment method instead of token and PaymentIntent all over

This commit is contained in:
PCoder 2020-12-31 10:04:21 +05:30
parent 35cc9d4229
commit c3286a68a5
8 changed files with 245 additions and 121 deletions

View file

@ -187,7 +187,6 @@
window.enter_your_card_text = '{%trans "Enter your credit card number" %}'; window.enter_your_card_text = '{%trans "Enter your credit card number" %}';
(function () { (function () {
window.paymentIntentSecret = "{{payment_intent_secret}}";
window.stripeKey = "{{stripe_key}}"; window.stripeKey = "{{stripe_key}}";
window.current_lan = "{{LANGUAGE_CODE}}"; window.current_lan = "{{LANGUAGE_CODE}}";
})(); })();

View file

@ -2,6 +2,14 @@
{% load staticfiles bootstrap3 i18n custom_tags humanize %} {% load staticfiles bootstrap3 i18n custom_tags humanize %}
{% block content %} {% block content %}
<script>
{% if payment_intent_secret %}
console.log("payment_intent_secret");
window.paymentIntentSecret = "{{payment_intent_secret}}";
{% else %}
console.log("No payment_intent_secret");
{% endif %}
</script>
<div id="order-detail{{order.pk}}" class="order-detail-container"> <div id="order-detail{{order.pk}}" class="order-detail-container">
{% if messages %} {% if messages %}
<div class="alert alert-warning"> <div class="alert alert-warning">
@ -321,5 +329,10 @@
<script type="text/javascript"> <script type="text/javascript">
{% trans "Some problem encountered. Please try again later." as err_msg %} {% trans "Some problem encountered. Please try again later." as err_msg %}
var create_vm_error_message = '{{err_msg|safe}}'; var create_vm_error_message = '{{err_msg|safe}}';
var pm_id = '{{id_payment_method}}';
var error_url = '{{ error_msg.redirect }}';
var error_msg = '{{ error_msg.msg_body }}';
var error_title = '{{ error_msg.msg_title }}';
window.stripeKey = "{{stripe_key}}";
</script> </script>
{%endblock%} {%endblock%}

View file

@ -285,27 +285,12 @@ class PaymentOrderView(FormView):
product = GenericProduct.objects.get( product = GenericProduct.objects.get(
id=self.request.session['product_id'] 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( context.update({'generic_payment_form': ProductPaymentForm(
prefix='generic_payment_form', prefix='generic_payment_form',
initial={ initial={'product_name': product.product_name,
'product_name': product.product_name,
'amount': float(product.get_actual_price()), 'amount': float(product.get_actual_price()),
'recurring': product.product_is_subscription, 'recurring': product.product_is_subscription,
'description': product.product_description, 'description': product.product_description,
'payment_intent_secret': payment_intent.client_secret
}, },
product_id=product.id product_id=product.id
), }) ), })
@ -479,8 +464,9 @@ class PaymentOrderView(FormView):
context['generic_payment_form'] = generic_payment_form context['generic_payment_form'] = generic_payment_form
context['billing_address_form'] = address_form context['billing_address_form'] = address_form
return self.render_to_response(context) return self.render_to_response(context)
token = address_form.cleaned_data.get('token') id_payment_method = self.request.POST.get('id_payment_method',
if token is '': None)
if id_payment_method is None:
card_id = address_form.cleaned_data.get('card') card_id = address_form.cleaned_data.get('card')
logger.debug("token is empty and card_id is %s" % card_id) logger.debug("token is empty and card_id is %s" % card_id)
try: try:
@ -508,15 +494,16 @@ class PaymentOrderView(FormView):
) )
request.session['card_id'] = user_card_detail.id request.session['card_id'] = user_card_detail.id
else: else:
request.session['token'] = token request.session["id_payment_method"] = id_payment_method
logger.debug("token is %s" % token) logger.debug("id_payment_method is %s" % id_payment_method)
if request.user.is_authenticated(): if request.user.is_authenticated():
this_user = { this_user = {
'email': request.user.email, 'email': request.user.email,
'name': request.user.name 'name': request.user.name
} }
customer = StripeCustomer.get_or_create( customer = StripeCustomer.get_or_create(
email=this_user.get('email'), token=token email=this_user.get('email'),
id_payment_method=id_payment_method
) )
else: else:
user_email = address_form.cleaned_data.get('email') user_email = address_form.cleaned_data.get('email')
@ -539,7 +526,7 @@ class PaymentOrderView(FormView):
) )
customer = StripeCustomer.create_stripe_api_customer( customer = StripeCustomer.create_stripe_api_customer(
email=user_email, email=user_email,
token=token, token=id_payment_method,
customer_name=user_name) customer_name=user_name)
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
logger.debug( logger.debug(
@ -550,7 +537,7 @@ class PaymentOrderView(FormView):
) )
customer = StripeCustomer.create_stripe_api_customer( customer = StripeCustomer.create_stripe_api_customer(
email=user_email, email=user_email,
token=token, token=id_payment_method,
customer_name=user_name) customer_name=user_name)
billing_address = address_form.save() 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) if (('specs' not in request.session or 'user' not in request.session)
and 'generic_payment_type' not in request.session): and 'generic_payment_type' not in request.session):
return HttpResponseRedirect(reverse('datacenterlight:index')) return HttpResponseRedirect(reverse('datacenterlight:index'))
if 'token' in self.request.session: if 'id_payment_method' in self.request.session:
token = self.request.session['token'] payment_method = self.request.session['id_payment_method']
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_cards_details_from_token( card_details = stripe_utils.get_cards_details_from_payment_method(
token payment_method
) )
if not card_details.get('response_object'): if not card_details.get('response_object'):
return HttpResponseRedirect(reverse('hosting:payment')) return HttpResponseRedirect(reverse('hosting:payment'))
@ -635,6 +622,7 @@ class OrderConfirmationView(DetailView, FormView):
context['cc_brand'] = card_details_response['brand'] context['cc_brand'] = card_details_response['brand']
context['cc_exp_year'] = card_details_response['exp_year'] context['cc_exp_year'] = card_details_response['exp_year']
context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month']) context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month'])
context['id_payment_method'] = payment_method
else: else:
card_id = self.request.session.get('card_id') card_id = self.request.session.get('card_id')
card_detail = UserCardDetail.objects.get(id=card_id) card_detail = UserCardDetail.objects.get(id=card_id)
@ -718,6 +706,27 @@ class OrderConfirmationView(DetailView, FormView):
'form': UserHostingKeyForm(request=self.request), 'form': UserHostingKeyForm(request=self.request),
'keys': get_all_public_keys(self.request.user) '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({ context.update({
'site_url': reverse('datacenterlight:index'), 'site_url': reverse('datacenterlight:index'),
'page_header_text': _('Confirm Order'), 'page_header_text': _('Confirm Order'),
@ -725,6 +734,8 @@ class OrderConfirmationView(DetailView, FormView):
request.session.get('billing_address_data') request.session.get('billing_address_data')
), ),
'cms_integration': get_cms_integration('default'), '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) return render(request, self.template_name, context)
@ -765,9 +776,9 @@ class OrderConfirmationView(DetailView, FormView):
'generic_payment_details': generic_payment_details 'generic_payment_details': generic_payment_details
} }
if 'token' in request.session: if 'id_payment_method' in request.session:
card_details = stripe_utils.get_cards_details_from_token( card_details = stripe_utils.get_cards_details_from_payment_method(
request.session.get('token') request.session.get('id_payment_method')
) )
logger.debug( logger.debug(
"card_details=%s" % (card_details)) "card_details=%s" % (card_details))
@ -788,7 +799,22 @@ class OrderConfirmationView(DetailView, FormView):
) )
if not ucd: if not ucd:
acc_result = stripe_utils.associate_customer_card( 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:
msg = _(
'An error occurred while associating the card.'
' Details: {details}'.format(
details=acc_result['error']
)
)
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 set_as_default=True
) )
if acc_result['response_object'] is None: if acc_result['response_object'] is None:
@ -1334,9 +1360,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response,
clear_all_session_vars(real_request) clear_all_session_vars(real_request)
def show_error(msg, request): def get_error_response_dict(msg, request):
messages.add_message(request, messages.ERROR, msg,
extra_tags='failed_payment')
response = { response = {
'status': False, 'status': False,
'redirect': "{url}#{section}".format( 'redirect': "{url}#{section}".format(
@ -1356,4 +1380,10 @@ def show_error(msg, request):
' On close of this popup, you will be redirected back to' ' On close of this popup, you will be redirected back to'
' the payment page.')) ' 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))

View file

@ -84,6 +84,9 @@ $(document).ready(function () {
var hasCreditcard = window.hasCreditcard || false; var hasCreditcard = window.hasCreditcard || false;
if (!hasCreditcard && window.stripeKey) { if (!hasCreditcard && window.stripeKey) {
var stripe = Stripe(window.stripeKey); var stripe = Stripe(window.stripeKey);
if (window.pm_id) {
} else {
var element_style = { var element_style = {
fonts: [{ fonts: [{
family: 'lato-light', family: 'lato-light',
@ -147,6 +150,7 @@ $(document).ready(function () {
} }
}); });
} }
}
var submit_form_btn = $('#payment_button_with_creditcard'); var submit_form_btn = $('#payment_button_with_creditcard');
submit_form_btn.on('click', submit_payment); submit_form_btn.on('click', submit_payment);
@ -163,7 +167,7 @@ $(document).ready(function () {
if (parts.length === 2) return parts.pop().split(";").shift(); if (parts.length === 2) return parts.pop().split(";").shift();
} }
function submitBillingForm() { function submitBillingForm(pmId) {
var billing_form = $('#billing-form'); var billing_form = $('#billing-form');
var recurring_input = $('#id_generic_payment_form-recurring'); var recurring_input = $('#id_generic_payment_form-recurring');
billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />'); billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />');
@ -174,20 +178,46 @@ $(document).ready(function () {
billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />'); billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />');
} }
billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />'); billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />');
billing_form.append('<input type="hidden" name="id_payment_method" value="' + pmId + '" />');
billing_form.submit(); billing_form.submit();
} }
var $form_new = $('#payment-form-new'); var $form_new = $('#payment-form-new');
$form_new.submit(payWithPaymentIntent); $form_new.submit(payWithPaymentIntent);
window.result = "";
window.card = "";
function payWithPaymentIntent(e) { function payWithPaymentIntent(e) {
e.preventDefault(); 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, window.paymentIntentSecret,
{ {
payment_method: {card: cardNumberElement} payment_method: {card: cardNumberElement}
} }
).then(function(result) { ).then(function(result) {
window.result = result;
if (result.error) { if (result.error) {
// Display error.message in your UI. // Display error.message in your UI.
var errorElement = document.getElementById('card-errors'); var errorElement = document.getElementById('card-errors');
@ -198,7 +228,7 @@ $(document).ready(function () {
alert("Thanks for the order. Your product will be provisioned " + alert("Thanks for the order. Your product will be provisioned " +
"as soon as we receive the payment. Thank you."); "as soon as we receive the payment. Thank you.");
} }
}); }); */
} }
function payWithStripe_new(e) { function payWithStripe_new(e) {
e.preventDefault(); e.preventDefault();

View file

@ -92,6 +92,35 @@ $(document).ready(function() {
}); });
var create_vm_form = $('#virtual_machine_create_form'); 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 () { create_vm_form.submit(function () {
$('#btn-create-vm').prop('disabled', true); $('#btn-create-vm').prop('disabled', true);
$.ajax({ $.ajax({
@ -154,7 +183,7 @@ $(document).ready(function() {
} }
}); });
return false; return false;
}); });*/
$('#createvm-modal').on('hidden.bs.modal', function () { $('#createvm-modal').on('hidden.bs.modal', function () {
$(this).find('.modal-footer .btn').addClass('hide'); $(this).find('.modal-footer .btn').addClass('hide');
}); });

View file

@ -694,16 +694,17 @@ class SettingsView(LoginRequiredMixin, FormView):
msg = _("Billing address updated successfully") msg = _("Billing address updated successfully")
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
else: else:
token = form.cleaned_data.get('token') # TODO : Test this flow
id_payment_method = form.cleaned_data.get('id_payment_method')
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_cards_details_from_token( card_details = stripe_utils.get_cards_details_from_payment_method(
token id_payment_method
) )
if not card_details.get('response_object'): if not card_details.get('response_object'):
form.add_error("__all__", card_details.get('error')) form.add_error("__all__", card_details.get('error'))
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
stripe_customer = StripeCustomer.get_or_create( 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'] card = card_details['response_object']
if UserCardDetail.get_user_card_details(stripe_customer, card): if UserCardDetail.get_user_card_details(stripe_customer, card):
@ -711,7 +712,7 @@ class SettingsView(LoginRequiredMixin, FormView):
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
else: else:
acc_result = stripe_utils.associate_customer_card( 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: if acc_result['response_object'] is None:
msg = _( msg = _(
@ -1085,7 +1086,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
template, specs, stripe_customer_id, billing_address_data, template, specs, stripe_customer_id, billing_address_data,
vm_template_id, stripe_api_cus_id) 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( card_details = stripe_utils.get_cards_details_from_token(
request.session['token'] request.session['token']
) )
@ -1102,7 +1103,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
) )
if not ucd: if not ucd:
acc_result = stripe_utils.associate_customer_card( 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 set_as_default=True
) )
if acc_result['response_object'] is None: if acc_result['response_object'] is None:

View file

@ -296,7 +296,7 @@ class StripeCustomer(models.Model):
return None return None
@classmethod @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 Check if there is a registered stripe customer with that email
or create a new one or create a new one

View file

@ -83,12 +83,15 @@ class StripeUtils(object):
customer.save() customer.save()
@handleStripeError @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): set_as_default=False):
customer = stripe.Customer.retrieve(stripe_customer_id) 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: if set_as_default:
customer.default_source = card.id customer.invoice_settings.default_payment_method = id_payment_method
customer.save() customer.save()
return True return True
@ -100,6 +103,7 @@ class StripeUtils(object):
@handleStripeError @handleStripeError
def update_customer_card(self, customer_id, token): def update_customer_card(self, customer_id, token):
# TODO replace token with payment intent
customer = stripe.Customer.retrieve(customer_id) customer = stripe.Customer.retrieve(customer_id)
current_card_token = customer.default_source current_card_token = customer.default_source
customer.sources.retrieve(current_card_token).delete() customer.sources.retrieve(current_card_token).delete()
@ -188,6 +192,24 @@ class StripeUtils(object):
} }
return card_details 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): def check_customer(self, stripe_cus_api_id, user, token):
try: try:
customer = stripe.Customer.retrieve(stripe_cus_api_id) customer = stripe.Customer.retrieve(stripe_cus_api_id)
@ -207,11 +229,11 @@ class StripeUtils(object):
return customer return customer
@handleStripeError @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() == "": if name is None or name.strip() == "":
name = email name = email
customer = self.stripe.Customer.create( customer = self.stripe.Customer.create(
source=token, payment_method=id_payment_method,
description=name, description=name,
email=email email=email
) )
@ -494,15 +516,15 @@ class StripeUtils(object):
return tax_id_obj return tax_id_obj
@handleStripeError @handleStripeError
def get_payment_intent(self, amount): def get_payment_intent(self, amount, customer):
""" """ Create a stripe PaymentIntent of the given amount and return it
Adds VM metadata to a subscription
:param amount: the amount of payment_intent :param amount: the amount of payment_intent
:return: :return:
""" """
payment_intent_obj = stripe.PaymentIntent.create( payment_intent_obj = stripe.PaymentIntent.create(
amount=amount, amount=amount,
currency='chf' currency='chf',
customer=customer
) )
return payment_intent_obj return payment_intent_obj