back end dev
Rails Store
Building a Rails Webshop from Scratch

Rails Store + Shopping Cart

A rails store with shopping cart, stripe checkout, and social oauth. Confirmation emails also. The goal is to easily combine this bit with other snippets.

Setup

rails new <app name> --database=postgresql

Postgres

Using the custom multiple database postgres docker image:

docker run -id --name rails-db -e POSTGRES_MULTIPLE_DATABASES="railyard-dev","railyard-test","railyard-prod" -e POSTGRES_USER=<database user> -e POSTGRES_PASSWORD=<password> -p 5434:5432 db:latest

Rails

Config

Needs:

  • Stripe API key

Initial setup:

#/.env
PUBLISHABLE_KEY=
SECRET_KEY=

#Gemfile

gem 'devise'
gem 'omniauth-github'
gem 'omniauth-twitter'
gem 'dotenv'
gem 'activerecord-session_store'
gem 'mailgun-ruby'
gem 'stripe'
gem 'jquery-rails'
#/config/database.yml

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  timeout: 5000
  username: worker
  password: S0itg0es1
  host: 127.0.0.1
  port: 5434

development:
  <<: *default
  database: railyard-dev

test:
  <<: *default
  database: railyard-test

production:
  <<: *default
  database: railyard-prod

Stripe

rails g controller charges
#/config/initializers/stripe.rb
Rails.configuration.stripe = {
  :publishable_key => ENV['PUBLISHABLE_KEY'],
  :secret_key      => ENV['SECRET_KEY']
}

Stripe.api_key = Rails.configuration.stripe[:secret_key]

Controllers:

class ChargesController < ApplicationController
    before_action :authenticate_user!

    def new
    end

    def create
      # Amount in cents
      @amount = params[:amount]
      # puts "creating a carge"

      customer = Stripe::Customer.create(
        :email => params[:stripeEmail],
        :source  => params[:stripeToken],
        # :metadata    => {'name' => params[:nameField], 'address' => params[:addressField], 'username' => current_user.email }
      )

      puts "creating charge"
      puts params

      charge = Stripe::Charge.create(
        :customer    => customer.id,
        :amount      => @amount,
        :description => "a test",
        :currency    => 'usd',
        :metadata    => {'order_id' => '6735'}
      )

    if charge["paid"] == true
    #Save customer to the db
        # redirect_to root_path
    end

    rescue Stripe::CardError => e
      flash[:error] = e.message
      puts "ERRROR ERROR"
      # redirect_to new_charge_path
    end
end

Views:

<%= form_tag charges_path do %>

  <article>
    <% if flash[:error].present? %>
      <div id="error_explanation">
        <p><%= flash[:error] %></p>
      </div>
    <% end %>
    <label class="amount">
      <span>
        <strong>Price:</strong>
        $<%= @product.price %>
        <button id="purchaseButton">Purchase</button>
      </span>
    </label>
  </article>



  <article>
    <%= hidden_field_tag(:stripeToken) %>
  </article>


  <script src="https://checkout.stripe.com/checkout.js"></script>
  <script>
  var handler = StripeCheckout.configure({
    key: "<%= Rails.configuration.stripe[:publishable_key] %>",
    image: 'https://stripe.com/assets/img/documentation/checkout/marketplace.png',
    locale: 'auto',
    token: function(token) {
        // $('input#stripeToken').val(token.id);
        // $('form').submit();
        console.log(JSON.stringify(token));
        var tokenInput = $("<input type=hidden name=stripeToken />").val(token.id);
          var emailInput = $("<input type=hidden name=stripeEmail />").val(token.email);
        var amount = $("<input type=hidden name=amount />").val(parseFloat(<%= @product.price * 100 %>));
        //   // document.getElementById("chargeForm").submit();
          $("form").append(tokenInput).append(emailInput).append(amount).submit();
      // You can access the token ID with `token.id`.
      // Get the token ID to your server-side code for use.
    }
  });

  // console.log();



  document.getElementById('purchaseButton').addEventListener('click', function(e) {
      e.preventDefault();
    // Open Checkout with further options:
    handler.open({
      name: "<%= @product.name %>",
      description: "<%= @product.description %>",
      amount: parseFloat(<%= @product.price * 100 %>)
      // zipCode: true,
      // billingAddress: true

    });

  });

  // Close Checkout on page navigation:
  window.addEventListener('popstate', function() {
    handler.close();
  });
  </script>

  <% end %>

Shopping Cart

Migrations:

rails g model Cart
class CreateCartItems < ActiveRecord::Migration[5.2]
  def change
    create_table :cart_items do |t|
      t.integer :quantity
      t.integer :cart_id
      t.product :integer
      t.timestamps
    end
  end
end

Models:

#carts
class Cart < ApplicationRecord
  has_one :user
  has_many :cart_items

  def add_item(product_id)
    puts "adding item"
    self.cart_items.create(:product => product_id, :quantity => 1)

    # if product
    #   puts "we have a product"
    #   puts product[:description]
    #   # increase the quantity of product in cart
    #   # product.quantity + 1
    #   # @cart = Cart.find(session[:cart_id])
    #   self.cart_items.create(:product => product)
    #   save
    # else
    #   # product does not exist in cart
    #   # @cart.cart_items.create(product)
    # end
    save
  end
end

#cart_items
class CartItem < ApplicationRecord
  belongs_to :cart, {:optional => true}
end

Controllers:

class ApplicationController < ActionController::Base
  def current_cart
    puts "getting card_id"
    puts session[:cart_id]
    if session[:cart_id]
      @current_cart ||= Cart.where(id: session[:cart_id])
    end
    if session[:cart_id].nil?
      @current_cart = Cart.create!
      session[:cart_id] = @current_cart.id
    end
    @current_cart
  end
end

class CartsController < ApplicationController
  before_action :set_cart, only: [:show, :edit, :update, :destroy]

  # GET /carts
  # GET /carts.json
  def index
    if session[:cart_id]
      @carts = current_cart
      redirect_to '/carts/' + session[:cart_id].to_s
    else
      @carts = Cart.all
    end
  end


  def add_to_cart
    puts "add to cart"

    if session[:cart_id]
      @cart = Cart.find(session[:cart_id])
      puts "cart exists"
      @cart.add_item(params[:product_id])
    else
      puts "cart doesnt exist"
      @cart = Cart.new()

      respond_to do |format|
        if @cart.save
          puts "create cart"
          puts @cart.id
          session[:cart_id] = @cart.id
          @cart.add_item(params[:product_id])
          format.html { redirect_to @cart, notice: 'Cart was successfully created.' }
          format.json { render :show, status: :created, location: @cart }
        else
          format.html { render :new }
          format.json { render json: @cart.errors, status: :unprocessable_entity }
        end
      end
    end
  end


  # GET /carts/1
  # GET /carts/1.json
  def show
    if @cart.cart_items
      # if @cart.cart_items.product
        @totals = Hash.new(0)
        @output = Hash.new(0)
        productArr = @cart.cart_items.pluck(:product)
        searchArr = productArr.uniq

        productArr.each do |v|
          @totals[v] += 1
        end


        @products = Product.where(id: searchArr)

        @products.each do |product|
          num = @totals[product.id]

          @output = {
            :product => product,
            :quantity => num
          }
        end

        puts @output
      # end
    end
  end

  # # POST /carts
  # # POST /carts.json
  def create
    @cart = Cart.new(cart_params)

    respond_to do |format|
      if @cart.save
        puts "create cart"
        session[:cart_id] = @cart.id
        format.html { redirect_to @cart, notice: 'Cart was successfully created.' }
        format.json { render :show, status: :created, location: @cart }
      else
        format.html { render :new }
        format.json { render json: @cart.errors, status: :unprocessable_entity }
      end
    end
  end


end