2

New-ish Ruby Rails programmer here, please help me learn. I am having a difficult time creating a subscription in Stripe. It is an app where schools will be registering to. I already created a plan in Stripe with an ID called, 'reach' and I am able to create a Stripe Customer Token, but not a Subscription.

On my registration form (in views), I have a hidden_field_tag with the plan name as 'reach' which is passed through the URL, params. I also have a hidden field in the form of the stripeToken.

I have a class called SchoolRegistration and the code underneath is here:

attr_accessor :stripeToken
attr_accessor :plan

def save_with_subscription

 if valid?
  customer = Stripe::Customer.create(description: email, plan: plan, source: stripeToken)
   self.stripe_customer_token = customer.id
   save!

 end
end

What I discovered recently is the <%= hidden_field_tag :plan, params[:plan] %> in my views is NOT saving to my database. I can see it on my console when I hit submit, but it never gets saved to the database. How can I save that in the database?

Controller:

    class SchoolRegistrationsController < ApplicationController

      def new
        @register = SchoolRegistration.new
      end

      def create
        @register = SchoolRegistration.new(register_params)


        if @register.save_with_subscription
          flash[:success] = "Congratulations!  You have registered your school!
          redirect_to new_user_registration_path

        else
          flash[:danger] = @register.errors.full_messages.join(", ")
          redirect_to new_registration_path
        end
      end

      private
        def register_params
params.require(:school_registration).permit(:name_of_person_completing_form, :email, :role_in_school, :school_name, :grade_levels, :street_name, :city, :state, :zip_code)
        end
    end

params.require is indented in my code...not sure why it wouldn't indent here.

JavaScript:

/* global $ Stripe */
//Document ready.
$(document).on('turbolinks:load', function(){

  //Set Stripe public key.
  var stripe = Stripe($('meta[name="stripe-key"]').attr('content'));
  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',
      color: "#32325d",
    }
  };

  // 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 = '';
    }
  });

  var form = document.getElementById('payment-form');
  form.addEventListener('submit', function(event) {
    event.preventDefault();

    stripe.createToken(card).then(function(result) {
      if (result.error) {
        // Inform the customer that 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);

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

I know it is probably obvious, I am just having a difficult time and I did check out the documentation. Please help me learn and much thanks to all of you! Let me know if you need more info or code - using Rails 5.

fool-dev
  • 7,671
  • 9
  • 40
  • 54
pat_w14
  • 31
  • 6
  • Put your `payment` related all code like `model` `controller` and `JavaScript` – fool-dev Jan 10 '18 at 04:01
  • Can you provide more information? What's the exact issue you're seeing? Is there an error or an exception when you try to create the customer object? – Ywain Jan 10 '18 at 12:35
  • Thank you both for responding! @fool I believe all the code should be there to check out. @Ywain What I discovered recently is my `hidden_field_tag :plan, :params[:plan]` is not hitting the database and everything else is (new-bie question) how can I make sure that hidden_field_tag gets saved? I can see it in my console when I hit submit on the form. – pat_w14 Jan 10 '18 at 14:52

2 Answers2

1

create the subscription by associating the plan with the customer id witch you get when creating customer on stripe

Stripe::Subscription.create(
  :customer => "cus_4fdAW5ftNQow1a",
  :items => [
    {
      :plan => "basic-monthly",
    },
  ],
)

For more information https://stripe.com/docs/subscriptions/quickstart

Ajay sharma
  • 41
  • 1
  • 8
  • Thank you for your help! Would you place this code in the model file? – pat_w14 Jan 10 '18 at 18:32
  • Also, from my form, the `<%= hidden_field_tag :plan, params[:plan] %>` is not saving to the database. What do I have to write in my controller to save the hidden field tag part to the database? Instance or local variable? – pat_w14 Jan 10 '18 at 18:39
0

Ok, I'm trying to give a full stripe implementation solution, you follow this step by step, all code is tested and go to the live site for testing here the site

This example only Stripe payment

Add this on view/layouts/application.html.erb

<%= javascript_include_tag "https://js.stripe.com/v2/" %>
just above
<%= javascript_include_tag "application" %>

Create environment variable with Stripe keys

STRIPE_TEST_PUBLISHABLE_KEY: pk_test_xxxxxxxxxx
STRIPE_TEST_SECRET_KEY: sk_test_xxxxxxxxxxxxx

On the registration file, add the code below to the top of the file:

<script language="Javascript">
    Stripe.setPublishableKey("<%= ENV['STRIPE_TEST_PUBLISHABLE_KEY'] %>");
</script>

And add this class on your form cc_form

Create a model for payment with references

rails g model Payment email:string token:string school_registration:references

Will generate a file under db like belo

class CreatePayments < ActiveRecord::Migration[5.0]
  def change
    create_table :payments do |t|
      t.string :email
      t.string :token
      t.references :school_registration, foreign_key: true

      t.timestamps
    end
  end
end

Then

rake db:migrate
#=> model/SchoolRegistration.rb
#=> add these two lines
has_one :payment
accepts_nested_attributes_for :payment

On the payment.rb

    attr_accessor :card_number, :card_cvv, :card_expires_month, :card_expires_year
belongs_to :school_registration

def self.month_options
    Date::MONTHNAMES.compact.each_with_index.map { |name, i| ["#{i+1} - #{name}", i+1]}
end

def self.year_options
    (Date.today.year..(Date.today.year+10)).to_a
end

def process_payment
    customer = Stripe::Customer.create email: email, card: token

    Stripe::Charge.create customer: customer.id, amount: 1000, description: 'Premium', currency: 'usd' 
   #=> 1000 means 1000 cents that means 10 dollars
end

Now on your form

<%= fields_for( :payment ) do |p| %>
    <div class="row col-md-12">
        <div class="form-group col-md-4 no-left-padding">
            <%= p.label :card_number, "Card Number", data: {stripe: "label"} %>
            <%= p.text_field :card_number, class: "form-control", required: true, data: {stripe: 'number'} %>
        </div>

        <div class="form-group col-md-2">
            <%= p.label :card_cvv, "Card CVV", data: {stripe: "label"} %>
            <%= p.text_field :card_cvv, class: "form-control", required: true, data: {stripe: 'cvv'} %>
        </div>

        <div class="form-group col-md-6">
            <div class="col-md-12">
                <%= p.label :card_expires, "Caed Expires", data: {stripe: "label" } %>
            </div>

            <div class="col-md-3">
                <%= p.select :card_expires_month, options_for_select(Payment.month_options),
                                                    { include_blank: 'Month' },
                                                    "data-stripe" => "exp-month",
                                                    class: "form-control", required: true %>
            </div>

            <div class="col-md-3">
                <%= p.select :card_expires_year, options_for_select(Payment.year_options.push),
                                                { include_blank: 'Year' },
                                                class: "form-control",
                                                data: { stripe: "exp-year" }, required: true %>
            </div>
        </div>
    </div>
<% end %>

And now create JS file under javascripts folder named like stripe.js

$(document).ready(function() {

    var show_error, stripeResponseHandler, submitHandler;

    submitHandler = function (event) {

        var $form = $(event.target);
        $form.find("input[type=submit]").prop("disabled", true);
        //If Stripe was initialized correctly this will create a token using the credit card info
        if(Stripe){
            Stripe.card.createToken($form, stripeResponseHandler);
        } else {
            show_error("Failed to load credit card processing functionality. Please reload this page in your browser.")
        }
        return false;
    };

    $(".cc_form").on('submit', submitHandler);
    stripeResponseHandler = function (status, response) {
        var token, $form;
        $form = $('.cc_form');
        if (response.error) {
            console.log(response.error.message);
            show_error(response.error.message);
            $form.find("input[type=submit]").prop("disabled", false);
        } else {
            token = response.id;
            $form.append($("<input type=\"hidden\" name=\"payment[token]\" />").val(token));
            $("[data-stripe=number]").remove();
            $("[data-stripe=cvv]").remove();
            $("[data-stripe=exp-year]").remove();
            $("[data-stripe=exp-month]").remove();
            $("[data-stripe=label]").remove();
            $form.get(0).submit();
        }
        return false;
    };
    show_error = function (message) {
        if($("#flash-messages").size() < 1){
            $('div.container.main div:first').prepend("<div id='flash-messages'></div>")
        }
        $("#flash-messages").html('<div class="alert alert-warning"><a class="close" data-dismiss="alert">×</a><div id="flash_alert">' + message + '</div></div>');
        $('.alert').delay(5000).fadeOut(3000);
        return false;
    };
});

And finally, go to controller and add those lines

if @register.save
   @payment = Payment.new({email: params["school_registration"]["email"],
                token: params[:payment]["token"], school_registration_id: @register.id
            })
   flash[:error] = "Please check registration errors" unless @payment.valid?
   begin
      @payment.process_payment
      @payment.save
   rescue Exception => e
      flash[:error] = e.message
      @register.destroy
      render :new and return #=> :new means your registration form
   end
else
    #=> Code 
end

This is actually one-time subscription and Stripe basic implementation if you implement this carefully and succeed you can whatever which you need.

And for more go to Rails Checkout Guide

Hope to help

fool-dev
  • 7,671
  • 9
  • 40
  • 54