Danial Bagheri
Danial Bagheri

Reputation: 126

Stripe subscription creates duplicate payment on Django/Python

I've created a stripe subscription using Django/Stripe SDK and since the software is in Europe it used the new SCA (Strong Customer Authentication). So first I attach the source to customers and then I try to subscribe them, it works but for certain customers I am seeing duplicated payments on Stripe control panel.

I've contacted Stripe and their customer support recommended the following but I couldn't figure it out:

I see what you mean when you say the customers are being charged more than
once. The issue boils down to the API call requesting them to be added to a subscription is being sent more than once to Stripe from your server.

To solve this on your end, you will need to make sure your system is sending a request only once. I would recommend checking your server to see where the requests are coming from. It may be an easy solve on that end once this is determined.


@login_required
def PaymentView(request):
    profile = Profile.objects.filter(user=request.user).first()
    try:
        address = profile.address.get(address_type="home")
    except:
        address = None
    user_membership = get_user_membership(request)
    try:
        selected_membership = get_selected_membership(request)
    except:
        return redirect(reverse("membership:select"))
    publishKey = settings.STRIPE_PUBLISHABLE_KEY
    if request.method == "POST":
        # try:
        source = request.POST.get('stripeSource', "")
        amend = request.POST.get('amend', '')

        '''
        First we need to add the source for the customer
        '''
        if amend == "true":
            customer = stripe.Customer.modify(
                user_membership.stripe_customer_id,
                source=source,
            )
            customer.save()
        else:
            customer = stripe.Customer.retrieve(
                user_membership.stripe_customer_id)

            try:
                customer.source = source  # 4242424242424242
                customer.save()
            except:
                pass

        '''
        Now we can create the subscription using only the customer as we don't need to pass their
        credit card source anymore
        '''

        stripe_subscription = stripe.Subscription.create(
            customer=user_membership.stripe_customer_id,
            items=[
                {"plan": selected_membership.stripe_plan_id},
            ],
            # billing="charge_automatically", #billing is depricated
            collection_method="charge_automatically",
            expand=['latest_invoice.payment_intent'],
            # idempotency_key='FXZMav7BbtEui1Z3',
        )
        # subscription = djstripe.models.Subscription.sync_from_stripe_data(
        #     stripe_subscription
        # )

        if stripe_subscription.status == "active":
            return redirect(reverse('membership:update-transactions',
                                    kwargs={
                                        'subscription_id': stripe_subscription.id
                                    }))
        elif stripe_subscription.status == "incomplete":
            payment_intent = stripe_subscription.latest_invoice.payment_intent
            if payment_intent.status == "requires_action":
                messages.info(
                    request, "Your bank requires extra authentication")
                context = {
                    "client_secret": payment_intent.client_secret,
                    "STRIPE_PUBLIC_KEY": settings.STRIPE_PUBLISHABLE_KEY,
                    "subscription_id": stripe_subscription.id
                }
                return render(request, "membership/3d-secure-checkout.html", context)
            elif payment_intent.status == "requires_payment_method":
                messages.warning(
                    request, "Your card has been failed or declined, Please try again")
                context = {
                    'publishKey': publishKey,
                    'selected_membership': selected_membership,
                    'client_secret': client_secret,
                    'address': address,
                    'profile': profile,
                    'amend': "true"
                }
                return render(request, "membership/membership_payment.html", context)
            else:
                messages.info(
                    request, "Something went wrong. Please report to the website admin.")

    context = {
        'publishKey': publishKey,
        'selected_membership': selected_membership,
        # 'client_secret': client_secret,
        'address': address,
        'profile': profile,
        'amend': "false"
    }

    return render(request, "membership/membership_payment.html", context)


@login_required
def updateTransactionRecords(request, subscription_id):
    user_membership = get_user_membership(request)
    selected_membership = get_selected_membership(request)
    user_membership.membership = selected_membership
    user_membership.save()
    sub, created = Subscription.objects.get_or_create(
        user_membership=user_membership)
    sub.stripe_subscription_id = subscription_id
    sub.active = True
    email_content, e = EmailTemplate.objects.get_or_create(
        email_tag='paid_subscription_success')
    recepient = request.user.email
    sender = settings.EMAIL_HOST_USER
    send_mail(email_content.email_subject, email_content.email_body,
              sender, [recepient])
    sub.save()

    try:
        del request.session['selected_membership_type']
    except:
        pass

    messages.info(request, 'Successfully created {} membership'.format(
        selected_membership))
    return redirect(reverse('membership:select'))


@login_required
def successful_membership(request):
    selected_membership = request.session.get('selected_membership', "Free")

    return render(request, 'membership/purchase_membership_success.html', context={"membership": selected_membership})


Here is part of my js:

<script src="https://js.stripe.com/v3/"></script>
  <!-- script for the stripe form -->
  <script type="text/javascript">
  // Create a Stripe client.
    var stripe = Stripe("{{ publishKey }}");

    // Create an instance of Elements.
    var elements = stripe.elements();
    var cardButton = document.getElementById("card-button");
    var cardholderName = document.getElementById("cardholder-name");
    var cardElement = elements.create("card");
    cardElement.mount("#card-element");
    var line1 = document.getElementById("line1");
    var line2 = document.getElementById("line2");
    var city = document.getElementById("city");
    var country = document.getElementById("country");
    var postalCode = document.getElementById("postal_code");
    var email = document.getElementById("email");
    var ownerInfo = {
      owner: {
        name: cardholderName.value,
        address: {
          line1: line1.value,
          line2: line2.value,
          city: city.value,
          postal_code: postalCode.value,
          country: country.value
        },
        email: email.value
      }
    };
    // Add an instance of the card Element into the `card-element` <div>.
    // Handle real-time validation errors from the card Element.
    cardElement.addEventListener("change", function (event) {
      var displayError = document.getElementById("card-errors");
      if (event.error) {
        displayError.textContent = event.error.message;
      } else {
        displayError.textContent = "";
      }
    });
    // Handle form submission.
    var form = document.getElementById("payment-form");
    form.addEventListener("submit", function (event) {
      event.preventDefault();
      stripe.createSource(cardElement, ownerInfo).then(function (result) {
        if (result.error) {
          // Inform the user if there was an error.
          var errorElement = document.getElementById("card-errors");
          errorElement.textContent = result.error.message;
        } else {
          // Send the token to your server.
          setTimeout(stripeSourceHandler(result.source), 3000);
          //stripeSourceHandler(result.source);
        }
      });
    });
    // Submit the form with the source ID.
    function stripeSourceHandler(source) {
      var form = document.getElementById("payment-form");
      var hiddenInput = document.createElement("input");
      hiddenInput.setAttribute("type", "hidden");
      hiddenInput.setAttribute("name", "stripeSource");
      hiddenInput.setAttribute("value", source.id);
      form.appendChild(hiddenInput);
      // Insert the source ID into the form so it gets submitted to the server
      // Submit the form
      form.submit();
    }
  </script>

Upvotes: 3

Views: 1230

Answers (1)

Danial Bagheri
Danial Bagheri

Reputation: 126

I have fixed the issue by checking if the user has any current active subscriptions before I subscribe them, I am not sure why it was happening in the first place but I presume it was due to internet speed or disconnection on mobile devices, so javascript was sending the request but before it redirects the user, it would have lost it's connection.

Here is how I fixed it with stripe SDK:

customer = stripe.Customer.retrieve(user_membership.stripe_customer_id)
        if customer.subscriptions.total_count > 0:
            for i in customer.subscriptions.data:
                if i.plan['id'] == selected_membership.stripe_plan_id and i.plan['active'] == True:
                    messages.info(
                        request, "Your have already subscribed to this plan")
                    return redirect(reverse('membership:update-transactions',
                                            kwargs={
                                                'subscription_id': i.id
                                            }))
                else:
                    pass  # Maybe we can check if users have a subscription that needs renewing
        else:

If anyone have a cleaner code please share it with me. 😊

Upvotes: 1

Related Questions