title: Django/Stripe: idempotent requests can only be used with the same parameters
body: <p>I am using Stripe in my Django application. I have the following test case: <code>incorrect_cvc</code> leads to an card_error. Now when correcting the CVC and using <code>4242 4242 4242 4242</code> what I except is a successful charge. However what I get pack is the following error message:</p>
  
  <blockquote>
    <p>Request req_auTSTGSGoUVNUa: Keys for idempotent requests can only be
    used with the same parameters they were first used with. Try using a
    key other than 'k1qjchgqjw' if you meant to execute a different
    request.</p>
  </blockquote>
  
  <p>I am not aware of which <code>parameters</code> I changed. But I think it's not the idea that the checkout process basically doesn't work anymore after an <code>card_error</code>. Does anyone understand which parameters I "changed" that leads to this error message?</p>
  
  <pre><code>def checkout_page(request):
  """
  * Check if session and ReservedItem exist.
  * Generate order_item dict for every ReservedItem entry, that belongs
    to order_reference.
  If request.method is 'POST':
      * Check if ticket reservation is still valid.
      * Create entries in models OrderItem, Order &amp; ReservedItem.
  """
  session_order_reference = request.session.get('order_reference')
  if request.session.get('order_reference'):
      reserved_items = ReservedItem.objects.filter(
          order_reference=session_order_reference
      )
      if not reserved_items:
          return redirect('website:index')
  else:
      return redirect('website:index')
  
  taxes_dict = {}
  total_gross = total_tax_amount = 0
  order_items_list = []
  for item in reserved_items:
      event = item.ticket.event
      timestamp_of_reservation = item.created
      total_gross += item.subtotal
      order_item = {
          'ticket': item.ticket,
          'ticket_name': item.ticket.name,
          'quantity': item.quantity,
          'subtotal': item.subtotal,
          'type': OrderType.ORDER,
      }
      total_tax_amount += add_tax(
          item=item,
          taxes_dict=taxes_dict,
          order_item=order_item,
      )
      order_items_list.append(dict(order_item))
  total_net = total_gross - total_tax_amount  # TODO Marc: Calculate in add_vat func?
  
  if request.method == 'POST':
  
      # TODO Marc: Should live in forms.py or just models?
      reservation_expired_redirect = check_if_reservation_expired(
          request=request,
          timestamp_of_reservation=timestamp_of_reservation,
          organizer=event.organizer.slug,
          event=event.slug,
      )
      if reservation_expired_redirect:
          return reservation_expired_redirect
  
      # TODO Marc: Should live in forms.py or just models?
      ticket_is_on_sale = check_if_ticket_is_on_sale(
          order_items_list=order_items_list,
          request=request,
          organizer=event.organizer.slug,
          event=event.slug,
      )
      if ticket_is_on_sale:
          return ticket_is_on_sale
  
      billing = BillingForm(request.POST, prefix='billing')
      order = OrderForm(request.POST, prefix='order')
      if order.is_valid() and billing.is_valid():
  
          # Charge via Stripe
          stripe.api_key = "ABC" # TODO Marc: Change to env
          token = request.POST.get('stripeToken')
          # https://stripe.com/docs/api#error_handling
  
          paid = False
          try:
              # Compare with transactions &gt; models copy.py &gt; class ChargeManager(models.Manager):
              # Use Stripe's library to make requests...
              total_gross_amount_in_smallest_unit = smallest_currency_unit(total_gross, 'eur') #TODO Marc: Replace eur
              charge = stripe.Charge.create(
                  amount=total_gross_amount_in_smallest_unit, # TODO Marc &gt; https://stripe.com/docs/currencies#zero-decimal
                  application_fee=100, # TODO Marc: Which currency?
                  currency='eur',  # TODO Marc
                  source=token,
                  stripe_account="ABC",  # TODO Marc: Replace with organizer stripe account
                  idempotency_key=session_order_reference,
              )
  
              new_charge_obj = Charge.objects.create(
                  amount=charge.amount,
                  charge_id=charge.id,
                  livemode=charge.livemode,
                  paid=charge.paid,
                  refunded=charge.refunded,
                  currency=charge.currency,
                  failure_code=charge.failure_code,
                  failure_message=charge.failure_message,
                  fraud_details=charge.fraud_details,
                  outcome=charge.outcome,
                  status=charge.status,
                  application_fee=charge.application_fee,
                  captured=charge.captured,
                  created=charge.created,
                  # TODO Marc: Add refunds:
                  # amount_refunded=charge.amount_refunded,
                  # etc.
              )
              application_fee = stripe.ApplicationFee.retrieve(charge.application_fee)
              Fee.objects.create(
                  fee_id=application_fee.id,
                  livemode=application_fee.livemode,
                  currency=application_fee.currency,
                  amount=application_fee.amount,
                  charge=new_charge_obj,
                  # TODO Marc: Add refunds
              )
              paid = new_charge_obj.paid
          except stripe.error.CardError as e:
              # Since it's a decline, stripe.error.CardError will be caught
              body = e.json_body
              err = body.get('error', {})
  
              messages.add_message(
                  request,
                  messages.ERROR,
                  err.get('message')
              )
  
              # return redirect(
              #     'orders:order-list',
              #     order_reference=new_order.order_reference,
              #     access_key=new_order.access_key,
              # )
  
              # print("Type is: %s") % err.get('type')
              # print("Code is: %s") % err.get('code')
              # # param is '' in this case
              # print("Param is: %s") % err.get('param')
              # print("Message is: %s") % err.get('message')
          except stripe.error.RateLimitError as e:
              # Too many requests made to the API too quickly
              pass
          except stripe.error.InvalidRequestError as e:
              # Invalid parameters were supplied to Stripe's API
              pass
          except stripe.error.AuthenticationError as e:
              # Authentication with Stripe's API failed
              # (maybe you changed API keys recently)
              pass
          except stripe.error.APIConnectionError as e:
              # Network communication with Stripe failed
              pass
          except stripe.error.StripeError as e:
              # Display a very generic error to the user, and maybe send
              # yourself an email
              pass
          except Exception as e:
              # Something else happened, completely unrelated to Stripe
              pass
          if paid:
              # Create new attendee
              i = 1
              attendee_list = []
              for item in reserved_items:
                  for _ in range(item.quantity):  # noqa
                      new_attendee_dict = {
                          'event': item.ticket.event,
                          'ticket': item.ticket,
                          'ticket_name': item.ticket.name,
                          'ticket_reference': session_order_reference + "-" + str(i),
                          'ticket_code': get_random_string(length=10),
                      }
                      i += 1
                      attendee_list.append(dict(new_attendee_dict))
  
              # Create new order
              new_order_dict = {
                  'total_gross': total_gross,
                  'total_tax': total_tax_amount,
                  'total_net': total_net,
                  'total_gross_converted': total_gross,  # TODO Marc
                  'event': event,
                  'order_reference': session_order_reference,
                  'status': OrderStatus.PENDING,
                  'access_key': get_random_string(length=10),
              }
  
              new_order = order.save(commit=False)
              [setattr(new_order, k, v) for k, v in new_order_dict.items()]
              new_order.save()
  
              # Create order items
              for item in order_items_list:
                  OrderItem.objects.create(order=new_order, **item)
  
              # Create attendees
              for item in attendee_list:
                  Attendee.objects.create(order=new_order, **item)
  
              # Create billing profile
              billing_profile = billing.save(commit=False)
              billing_profile.order = new_order
              billing_profile.save()
  
              # Delete order_reference session
              del request.session['order_reference']
  
              return redirect(
                  'orders:order-list',
                  order_reference=new_order.order_reference,
                  access_key=new_order.access_key,
              )
  else:
      billing = BillingForm(prefix='billing')
      order = OrderForm(prefix='order')
  
  context = {
      'reserved_items': reserved_items,
      'taxes': taxes_dict,
      'total_net': total_net,
      'total_gross': total_gross,
      'currency': event.currency,
      'order': order,
      'billing': billing,
  }
  
  return render(request, 'checkout/checkout.html', context)
  </code></pre>