title: Heroku adds an extra "/checkout" in the URL when completing order with Stripe using Django
body: <p>I have a small ecommerce project hosted on Heroku that I've built using Django, when clicking &quot;complete order&quot; on the checkout page I should be taken to the template &quot;checkout_success&quot; but instead I get stuck on the same page due to the URL trying to find a path with an extra &quot;/checkout&quot;. I've posted some screenshots below to further explain.</p>
  <p><strong>Checkout app URLS.py:</strong></p>
  <pre><code>from django.urls import path
  from . import views
  from .webhooks import webhook
  
  urlpatterns = [
      path('', views.checkout, name='checkout'),
      path('checkout_success/&lt;order_number&gt;',
           views.checkout_success, name='checkout_success'),
      path('cache_checkout_data/', views.cache_checkout_data,
           name='cache_checkout_data'),
      path('wh/', webhook, name='webhook'),
  ]
  
  </code></pre>
  <p><strong>Checkout apps view for checkout:</strong></p>
  <pre><code>def checkout(request):
      stripe_public_key = settings.STRIPE_PUBLIC_KEY
      stripe_secret_key = settings.STRIPE_SECRET_KEY
  
      if request.method == 'POST':
          bag = request.session.get('bag', {})
  
          form_data = {
              'full_name': request.POST['full_name'],
              'email': request.POST['email'],
              'phone_number': request.POST['phone_number'],
              'country': request.POST['country'],
              'postcode': request.POST['postcode'],
              'town_or_city': request.POST['town_or_city'],
              'street_address1': request.POST['street_address1'],
              'street_address2': request.POST['street_address2'],
              'county': request.POST['county'],
          }
          order_form = OrderForm(form_data)
          if order_form.is_valid():
              order = order_form.save(commit=False)
              pid = request.POST.get('client_secret').split('_secret')[0]
              order.stripe_pid = pid
              order.original_bag = json.dumps(bag)
              order.save()
              for item_id, item_data in bag.items():
                  try:
                      product = Product.objects.get(id=item_id)
                      if isinstance(item_data, int):
                          order_line_item = OrderLineItem(
                              order=order,
                              product=product,
                              quantity=item_data,
                          )
                          order_line_item.save()
                      else:
                          for size, quantity in item_data['items_by_size'].items():
                              order_line_item = OrderLineItem(
                                  order=order,
                                  product=product,
                                  quantity=quantity,
                                  product_size=size,
                              )
                              order_line_item.save()
                  except Product.DoesNotExist:
                      messages.error(request, (
                          &quot;One of the products in your bag wasn't found in our database. &quot;
                          &quot;Please call us for assistance!&quot;)
                      )
                      order.delete()
                      return redirect(reverse('view_bag'))
  
              request.session['save_info'] = 'save-info' in request.POST
              return redirect(reverse('checkout_success', args=[order.order_number]))
          else:
              messages.error(request, 'There was an error with your form. \
                  Please double check your information.')
      else:
          bag = request.session.get('bag', {})
          if not bag:
              messages.error(
                  request, &quot;There's nothing in your bag at the moment&quot;)
              return redirect(reverse('products'))
  
          current_bag = bag_contents(request)
          total = current_bag['grand_total']
          stripe_total = round(total * 100)
          stripe.api_key = settings.STRIPE_SECRET_KEY
          intent = stripe.PaymentIntent.create(
              amount=stripe_total,
              currency=settings.STRIPE_CURRENCY,
          )
  
          if request.user.is_authenticated:
              try:
                  profile = UserProfile.objects.get(user=request.user)
                  order_form = OrderForm(initial={
                      'full_name': profile.user.get_full_name(),
                      'email': profile.user.email,
                      'phone_number': profile.default_phone_number,
                      'country': profile.default_country,
                      'postcode': profile.default_postcode,
                      'town_or_city': profile.default_town_or_city,
                      'street_address1': profile.default_street_address1,
                      'street_address2': profile.default_street_address2,
                      'county': profile.default_county,
                  })
              except UserProfile.DoesNotExist:
                  order_form = OrderForm()
          else:
              order_form = OrderForm()
  
      if not stripe_public_key:
          messages.warning(request, 'Stripe public key is missing. \
              Did you forget to set it in your environment?')
  
      template = 'checkout/checkout.html'
      context = {
          'order_form': order_form,
          'stripe_public_key': settings.STRIPE_PUBLIC_KEY,
          'client_secret': intent.client_secret,
      }
  
      return render(request, template, context)
  </code></pre>
  <p><strong>Checkout apps views.py for successful checkout:</strong></p>
  <pre><code>def checkout_success(request, order_number):
      &quot;&quot;&quot;
      Handle successful checkouts
      &quot;&quot;&quot;
      save_info = request.session.get('save_info')
      order = get_object_or_404(Order, order_number=order_number)
  
      if request.user.is_authenticated:
          profile = UserProfile.objects.get(user=request.user)
          # Attach the user's profile to the order
          order.user_profile = profile
          order.save()
  
          # Save the user's info
          if save_info:
              profile_data = {
                  'default_phone_number': order.phone_number,
                  'default_country': order.country,
                  'default_postcode': order.postcode,
                  'default_town_or_city': order.town_or_city,
                  'default_street_address1': order.street_address1,
                  'default_street_address2': order.street_address2,
                  'default_county': order.county,
              }
              user_profile_form = UserProfileForm(profile_data, instance=profile)
              if user_profile_form.is_valid():
                  user_profile_form.save()
  
      messages.success(request, f'Order successfully processed! \
          Your order number is {order_number}. A confirmation \
          email will be sent to {order.email}.')
  
      if 'bag' in request.session:
          del request.session['bag']
  
      template = 'checkout/checkout_success.html'
      context = {
          'order': order,
      }
  
      return render(request, template, context)
  </code></pre>
  <p><strong>checkout template:</strong></p>
  <pre><code>            &lt;form action=&quot;{% url 'checkout' %}&quot; method=&quot;POST&quot; id=&quot;payment-form&quot;&gt;
                  {% csrf_token %}
                  &lt;fieldset class=&quot;rounded px-3 mb-5&quot;&gt;
                      &lt;legend class=&quot;fieldset-label small text-black px-2 w-auto&quot;&gt;Details&lt;/legend&gt;
                      {{ order_form.full_name | as_crispy_field }}
                      {{ order_form.email | as_crispy_field }}
                  &lt;/fieldset&gt;
                  &lt;fieldset class=&quot;rounded px-3 mb-5&quot;&gt;
                      &lt;legend class=&quot;fieldset-label small text-black px-2 w-auto&quot;&gt;Delivery&lt;/legend&gt;
                      {{ order_form.phone_number | as_crispy_field }}
                      {{ order_form.street_address1 | as_crispy_field }}
                      {{ order_form.street_address2 | as_crispy_field }}
                      {{ order_form.town_or_city | as_crispy_field }}
                      {{ order_form.county | as_crispy_field }}
                      {{ order_form.postcode | as_crispy_field }}
                      {{ order_form.country | as_crispy_field }}
                      &lt;div class=&quot;form-check form-check-inline float-right mr-0&quot;&gt;
                          {% if user.is_authenticated %}
                          &lt;label class=&quot;form-check-label&quot; for=&quot;id-save-info&quot;&gt;Save this delivery information to my
                              profile&lt;/label&gt;
                          &lt;input class=&quot;form-check-input ml-2 mr-0&quot; type=&quot;checkbox&quot; id=&quot;id-save-info&quot; name=&quot;save-info&quot;
                              checked&gt;
                          {% else %}
                          &lt;label class=&quot;form-check-label&quot; for=&quot;id-save-info&quot;&gt;
                              &lt;a class=&quot;text-info&quot; href=&quot;{% url 'account_signup' %}&quot;&gt;Create an account&lt;/a&gt; or
                              &lt;a class=&quot;text-info&quot; href=&quot;{% url 'account_login' %}&quot;&gt;login&lt;/a&gt; to save this information
                          &lt;/label&gt;
                          {% endif %}
                      &lt;/div&gt;
                  &lt;/fieldset&gt;
                  &lt;fieldset class=&quot;px-3&quot;&gt;
                      &lt;legend class=&quot;fieldset-label small text-black px-2 w-auto&quot;&gt;Payment&lt;/legend&gt;
                      &lt;!-- A Stripe card element will go here --&gt;
                      &lt;div class=&quot;mb-3&quot; id=&quot;card-element&quot;&gt;&lt;/div&gt;
  
                      &lt;!-- Used to display form errors --&gt;
                      &lt;div class=&quot;mb-3 text-danger&quot; id=&quot;card-errors&quot; role=&quot;alert&quot;&gt;&lt;/div&gt;
                      &lt;input type=&quot;hidden&quot; value=&quot;{{ client_secret }}&quot; name=&quot;client_secret&quot;&gt;
                  &lt;/fieldset&gt;
  
                  &lt;div class=&quot;submit-button text-right mt-5 mb-2&quot;&gt;
                      &lt;a href=&quot;{% url 'view_bag' %}&quot; class=&quot;btn btn-outline-black rounded-0&quot;&gt;
                          &lt;span class=&quot;icon&quot;&gt;
                              &lt;i class=&quot;fas fa-chevron-left&quot;&gt;&lt;/i&gt;
                          &lt;/span&gt;
                          &lt;span class=&quot;font-weight-bold&quot;&gt;Adjust Bag&lt;/span&gt;
                      &lt;/a&gt;
                      &lt;button id=&quot;submit-button&quot; class=&quot;btn btn-black rounded-0&quot;&gt;
                          &lt;span class=&quot;font-weight-bold&quot;&gt;Complete Order&lt;/span&gt;
                          &lt;span class=&quot;icon&quot;&gt;
                              &lt;i class=&quot;fas fa-lock&quot;&gt;&lt;/i&gt;
                          &lt;/span&gt;
                      &lt;/button&gt;
                      &lt;p class=&quot;small text-danger my-0&quot;&gt;
                          &lt;span class=&quot;icon&quot;&gt;
                              &lt;i class=&quot;fas fa-exclamation-circle&quot;&gt;&lt;/i&gt;
                          &lt;/span&gt;
                          &lt;span&gt;Your card will be charged &lt;strong&gt;${{ grand_total|floatformat:2 }}&lt;/strong&gt;&lt;/span&gt;
                      &lt;/p&gt;
                  &lt;/div&gt;
              &lt;/form&gt;
  
  </code></pre>
  <p><strong>JS for Stripe payment</strong></p>
  <pre><code>// Handle form submit
  var form = document.getElementById('payment-form');
  
  form.addEventListener('submit', function (ev) {
      ev.preventDefault();
      card.update({
          'disabled': true
      });
      $('#submit-button').attr('disabled', true);
      $('#payment-form').fadeToggle(100);
      $('#loading-overlay').fadeToggle(100);
  
      var saveInfo = Boolean($('#id-save-info').attr('checked'));
      // From using {% csrf_token %} in the form
      var csrfToken = $('input[name=&quot;csrfmiddlewaretoken&quot;]').val();
      var postData = {
          'csrfmiddlewaretoken': csrfToken,
          'client_secret': clientSecret,
          'save_info': saveInfo,
      };
      var url = '/checkout/cache_checkout_data/';
  
      $.post(url, postData).done(function () {
          stripe.confirmCardPayment(clientSecret, {
              payment_method: {
                  card: card,
                  billing_details: {
                      name: $.trim(form.full_name.value),
                      phone: $.trim(form.phone_number.value),
                      email: $.trim(form.email.value),
                      address: {
                          line1: $.trim(form.street_address1.value),
                          line2: $.trim(form.street_address2.value),
                          city: $.trim(form.town_or_city.value),
                          country: $.trim(form.country.value),
                          state: $.trim(form.county.value),
                      }
                  }
              },
              shipping: {
                  name: $.trim(form.full_name.value),
                  phone: $.trim(form.phone_number.value),
                  address: {
                      line1: $.trim(form.street_address1.value),
                      line2: $.trim(form.street_address2.value),
                      city: $.trim(form.town_or_city.value),
                      country: $.trim(form.country.value),
                      postal_code: $.trim(form.postcode.value),
                      state: $.trim(form.county.value),
                  }
              },
          }).then(function (result) {
              if (result.error) {
                  var errorDiv = document.getElementById('card-errors');
                  var html = `
                      &lt;span class=&quot;icon&quot; role=&quot;alert&quot;&gt;
                      &lt;i class=&quot;fas fa-times&quot;&gt;&lt;/i&gt;
                      &lt;/span&gt;
                      &lt;span&gt;${result.error.message}&lt;/span&gt;`;
                  $(errorDiv).html(html);
                  $('#payment-form').fadeToggle(100);
                  $('#loading-overlay').fadeToggle(100);
                  card.update({
                      'disabled': false
                  });
                  $('#submit-button').attr('disabled', false);
              } else {
                  if (result.paymentIntent.status === 'succeeded') {
                      form.submit();
                  }
              }
          });
      }).fail(function () {
          // just reload the page, the error will be in django messages
          location.reload();
      })
  
  </code></pre>
  <p><strong>Heroku Logs Error:</strong></p>
  <p>**<em>notice the extra &quot;/checkout&quot; in the POST URL.</em><br><br>
  <a href="https://i.stack.imgur.com/9SmBS.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/9SmBS.png" alt="enter image description here" /></a></p>
  <p><a href="https://i.stack.imgur.com/BA9i8.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/BA9i8.png" alt="enter image description here" /></a></p>
  <p>Same in the browser console URL</p>
  <p>I've tried an array things from adding and taking away &quot;/&quot; in my settings.py file and other URL paths. I'm sure this is something im overlooking but i've been stuck with this bug for days now. If anyone could help that would be great! And if any additional info is needed i'll be happy to provide. Thanks!</p>