Ask A Question

Notifications

You’re not receiving notifications from this thread.

Stripe Subscriptions: Duplicate Customers

Nick McNeany asked in Rails

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

Reply

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.

Reply

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!

Reply

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
}
Reply

Thanks, Chris! You the man!

Reply

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!

Reply

Did you have local: true on the form_with?

Reply

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!!!

Reply

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?? 😜

Reply

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.

Reply

I ran into this issue when going through the same course recently on Rails 5.2. local: true definitely fixed it. Thanks guys!

Reply

Chris, I'm hoping you will consider updating the Stripe series. Thank you.

Reply

Thomas, the Stripe series is new a separate course from GoRails: https://courses.gorails.com/payments-with-rails-master-class

It covers the latest Stripe with a shopping cart example for one-time payments, Stripe Billing subscriptions. Soon it will have Braintree + PayPal examples in the course as well.

Reply

Yes, I know it's a separate course, which I think is kind of unfair to us long-time subscribers, because there are some problems with the instructions that subscribers have access to. One of them dogged me for a couple of days until the folks at Stripe pointed out that using Bootstrap to decorate the Elements mount would cause it to not be visible on the page. And the other issues as pointed out in this thread. You mention yourself that these things get out of date quickly. Thank you.

Reply

Minor bits and pieces can get out of date but to be honest, Stripe's documentation is really good and their engineering team is great about helping users fix issues like you've had. I don't think it's unreasonable for Chris to have separate Master Classes. I've been a GoRails subscriber for a while and I purchased the course and have used it on a few projects now.

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 87,400+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.