Skip to main content

Stripe Subscriptions: Duplicate Customers

Rails • Asked by Nick McNeany


Hey Eeveryone,

I'm creating a subscription site using Stripe v3. For the most part everything is working fine, but I noticed in my Stripe Customer dashboard there are duplicate customers (for all customers). The first customer does not have a card associated with it, but the duplicate customer does.

I noticed in my logs that it is performing an update to the member two times right in a row, but I can't figure out what's causing this to happen. Pasted logs at the very bottom.

Here's all my stuff

I know it's a lot to look through, but I really appreciate any help!

Member Model

def stripe_customer
        if stripe_id?
                Stripe::Customer.retrieve(stripe_id)
        else
                stripe_customer = Stripe::Customer.create(email: email)
                update(stripe_id: stripe_customer.id)
                stripe_customer
        end
end

Memberships Controller

before_action :authenticate_member!

def new
end

def create
        customer = current_member.stripe_customer

    begin
            subscription = customer.subscriptions.create(
                    source: params[:stripeToken],
                    plan:   params[:plan]
            )

            current_member.assign_attributes(stripe_subscription_id: subscription.id, expires_at: nil)
            current_member.assign_attributes(
                    card_brand:     params[:card_brand],
                    card_last4:     params[:card_last4],
                    card_exp_month: params[:card_exp_month],
                    card_exp_year:  params[:card_exp_year]
            ) if params[:card_last4]
            current_member.save

            flash.notice = "BooYah!!! Thanks for signing up!"
            redirect_to edit_member_registration_path(current_member)
    rescue Stripe::CardError => e
            flash.alert = e.message
            render action: :new
    end
end

def show
end

Stripe Elements

document.addEventListener("turbolinks:load", function() {
    var public_key = document.querySelector("meta[name='stripe-public-key']").content;
    var stripe = Stripe(public_key);
    var elements = stripe.elements();

    // Custom styling can be passed to options when creating an Element.
    var style = {
        base: {
            // Add your base input styles here. For example:
            fontSize: '16px',
            lineHeight: '24px'
        }
    };

    // Create an instance of the card Element
    var card = elements.create('card', {style: style});

    // Add an instance of the card Element into the `card-element` <div>
    card.mount('#card-element');

    card.addEventListener('change', function(event) {
        var displayError = document.getElementById('card-errors');
            if (event.error) {
            displayError.textContent = event.error.message;
        } else {
            displayError.textContent = '';
        }
    });

    // Create a token or display an error when the form is submitted.
    var form = document.getElementById('payment-form');
        form.addEventListener('submit', function(event) {
        event.preventDefault();

        stripe.createToken(card).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
                stripeTokenHandler(result.token);
            }
        });
    });
});

function stripeTokenHandler(token) {
    // Insert the token ID into the form so it gets submitted to the server
    var form = document.getElementById('payment-form');
    var hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('type', 'hidden');
    hiddenInput.setAttribute('name', 'stripeToken');
    hiddenInput.setAttribute('value', token.id);
    form.appendChild(hiddenInput);

    ["brand", "exp_month", "exp_year", "last4"].forEach(function(field) {
        addFieldToForm(form, token, field);
    });

    // Submit the form
    form.submit();
}

function addFieldToForm(form, token, field) {
    var hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('type', 'hidden');
    hiddenInput.setAttribute('name', "card_" + field);
    hiddenInput.setAttribute('value', token.card[field]);
    form.appendChild(hiddenInput);
}

Logs

Started POST "/membership" for 127.0.0.1 at 2017-09-03 14:04:15 -0500
Processing by MembershipsController#create as JS
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"tF7nwOkJPALmvzrH5UVC4TgxolKBYH8pRQYkVpK8tSFG8ADR4dQnJmWHYBYSuQq2pI8OdvtXvqgIIoRQ3/fIXA==", "plan"=>"annual"}

  Member Load (0.6ms)  SELECT  "members".* FROM "members" WHERE "members"."id" = $1 ORDER BY "members"."id" ASC LIMIT $2  [["id", 15], ["LIMIT", 1]]
Started POST "/membership" for 127.0.0.1 at 2017-09-03 14:04:16 -0500
Processing by MembershipsController#create as HTML

  Parameters: {"utf8"=>"✓", "authenticity_token"=>"tF7nwOkJPALmvzrH5UVC4TgxolKBYH8pRQYkVpK8tSFG8ADR4dQnJmWHYBYSuQq2pI8OdvtXvqgIIoRQ3/fIXA==", "plan"=>"annual", "stripeToken"=>"tok_1Ay3OGFjHiphw0iAWDy0deos", "card_brand"=>"Visa", "card_exp_month"=>"12", "card_exp_year"=>"2023", "card_last4"=>"0002"}
  Member Load (0.6ms)  SELECT  "members".* FROM "members" WHERE "members"."id" = $1 ORDER BY "members"."id" ASC LIMIT $2  [["id", 15], ["LIMIT", 1]]

    |==This where I think the duplication is coming from. But I can't figure out why or what's causing it
    ==================================================================================
   (0.2ms)  BEGIN
  SQL (0.7ms)  UPDATE "members" SET "stripe_id" = $1, "updated_at" = $2 WHERE "members"."id" = $3  [["stripe_id", "cus_BKipFeUFJtHX6t"], ["updated_at", "2017-09-03 19:04:17.125942"], ["id", 15]]
   (6.6ms)  COMMIT
   (0.2ms)  BEGIN
  SQL (0.6ms)  UPDATE "members" SET "stripe_id" = $1, "updated_at" = $2 WHERE "members"."id" = $3  [["stripe_id", "cus_BKipmnHyvuuvVH"], ["updated_at", "2017-09-03 19:04:17.252095"], ["id", 15]]
   (0.5ms)  COMMIT
        ==================================================================================

Completed 500 Internal Server Error in 1995ms (ActiveRecord: 2.0ms)

Stripe::InvalidRequestError (This customer has no attached payment source):

app/controllers/concerns/memberships_controller.rb:11:in `create'
  Rendering memberships/new.html.erb within layouts/application
  Rendered memberships/new.html.erb within layouts/application (0.9ms)
  Rendered shared/_flash.html.erb (0.8ms)
Completed 200 OK in 1625ms (Views: 59.6ms | ActiveRecord: 8.2ms)

Also, I'm not sure why I'm seeing these two lines in my log:

Completed 500 Internal Server Error in 1995ms (ActiveRecord: 2.0ms)

Stripe::InvalidRequestError (This customer has no attached payment source):

Again, I know this is a lot to go through, but I really appreciate any help anyone can offer!

Please let me know if anyone needs any additional info!

Thanks!

Nick

Gravatar Chris Oliver commented on : Mod Staff

Hey Nick,

That's sure weird! I think what it looks like is happening is that your Javascript is submitting the form twice.

You can tell because the first POST to /memberships hasn't finished before the second one starts. And that's why the logs are mixed together and you have those two UPDATEs on the user. They're processing almost at exactly the same time.

You should do some debugging on your Javascript to figure out why it's executing twice and that should fix the problem.

Thanks Chris,

I was getting some erros from rails-ujs, so I swapped if for jquery-ujs since I'm using jquery-rails and that seems to have fixed the dupicate customer problem.

I'm still getting some Stripe releated errors that I'm looking into now. I may endup opening another thread with more detail, but in short I keep getting an error saying (paraphrased) :

#card-element can't be found and to make sure it's on the page before calling .mount

#card-element is definitely on my form view, so I'm assuming it has to do with the order of my js being loaded or turbolinks or both.

Has anyone experienced this? I'm using Rails 5.1.x.

Thanks!

Gravatar Chris Oliver commented on : Mod Staff

The #card-element error comes from you trying to run this on every page regardless if the field is on the page. You'll want to add some JS to check if the element exists, and if it does not, return.

document.addEventListener("turbolinks:load", function() {
  if (!document.querySelector("#card-element")) {
      return
    }

    // rest of your code
}

Thanks, Chris! You the man!

Hey Everyone,

Just wanted to give a quick update. So, I was still getting some really weird behavior like, duplicate customers and resubscribing was creating 4 new subscriptions!

I was using a form_with tag for the payment forms. I changed that to a form_tag and now everything is working as expected.

Not sure why the form_with was causing all this disaray, maybe I was using it wrong... I don't know. I do know changing it to a form_tag fixed it.

Has anyone dealt with this before or have any idea what was going on with the form_with helper?

Thanks!

Gravatar Chris Oliver commented on : Mod Staff

Did you have local: true on the form_with?

Ummm... No, no I did not have local: true on the form_with.

Man!!! How did I miss that!!!

By default form_with attaches the data-remote attribute submitting the form via an XMLHTTPRequest in the background if an Unobtrusive JavaScript driver, like rails-ujs, is used. See the :local option for more. source: http://edgeapi.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-formwith

Thanks, Chris! You really are the man!! Appreciate all your help!!!

Gravatar Chris Oliver commented on : Mod Staff

I wonder if that got triggered along with the JS for Stripe. May need to update my screencasts to mention that if I didn't cover that properly.

Can't we just have nice things that work out of the box?? 😜

Haha, I know, right!!!

Actually, in the Stripe Elements screencast you use a form_tag because you're using Rails 5.1.0. You attempt to use the form_with, realize you can't and then move on with the form_tag.

Login or create an account to join the conversation.