Let’s Build: With Ruby on Rails – Marketplace App with Stripe Connect
Tags: webdev, rails, ruby, tutorial
Published: 2020-02-17 16:50:35

---
title: Let’s Build: With Ruby on Rails – Marketplace App with Stripe Connect
published: true
date: 2020-02-12 20:24:31 UTC
tags: Web Development,Rails,Ruby, tutorial
canonical_url: https://web-crunch.com/ruby-on-rails-marketplace-stripe-connect/
---

Welcome to another installment of my Let’s Build: With Ruby on Rails series. This series focuses on building a marketplace application using Stripe Connect. Users can sign up, purchase, and sell their own goods and services while the application takes a percentage of each transaction.

### Back My Idea

Our fictitious marketplace app is called Back My Idea. Back my Idea is an app similar to GoFundMe and Kickstarter where users can crowdsource/fund their way to building their next big idea. Entrepreneurs/Designers/Developers can pitch their concept of an app and ask for donations to help fund their process of building it.

[Download the source code](https://github.com/justalever/back-my-idea)

**Core Requirements:**

- A User must be logged in to manipulate any part of the platform.
- A User can have two roles (three if you count admin). A **maker** and a **backer** are those roles. Backers provide funding via payment forms (Stripe) to fund the Maker’s venture. The Maker can receive direct funding from Backers with an amount set aside for the app to take a cut. Any Backer can also be a Maker and vice versa.
- A Maker can post their project and save it as a draft or public. Once public, the project can be backed by the Backer.
- A User can’t back their own project directly but can edit content.
- All projects have an expiration date of 30 days.
- A Project can’t be edited by a Backer unless it’s their own. 
- A Project can be commented on by both a Maker and a Backer.
- A Project can have perks as stacked backing amounts. 
- Perks can be any number dictated by the Maker
- Each Perk represents a single transaction 

### Stack

- Ruby on Rails
- Stimulus JS
- Tailwind CSS

### Modeling:

- **User**
  - Roles: Admin, Maker, Backer
  - Username
  - Name
  - Email
  - Password
- **Project**
  - Title
  - Description
  - Donation Goal
  - Commentable Type
  - Commentable ID
  - User ID
- **Perk** – Relative to Projects but seperated (nested attributes?)
  - Title
  - Amount
  - Description
  - Amount Available
  - Project ID
- **Comment** – Polymorphic
  - Body

#### Part 1


{% youtube ehNX4JIHk10 %}

#### Part 2

{% youtube N9AYEHeaI58 %}

#### Part 3

{% youtube MSaYlK1xIJw %}


#### Part 4

{% youtube c33eaOtbgAU %}

#### Part 5

{% youtube 0VXUoXVbEII %}

#### Part 6

{% youtube hfJ9WsRyc2k %}

#### Part 7

{% youtube 8YWfLI-Cp7c %}

#### Part 8

{% youtube BL1ePRCgZmc %}

# Getting Started

This tutorial assumes you have some knowledge of creating apps with Ruby on Rails. If not, I strongly suggest checking out some of my other more beginner Let’s Builds. Each build gets a bit more challenging as you progress. Really stuck on how to use Ruby on Rails? I made a whole 90 video course to help you get “unstuck” at [https://hellorails.io](https://hellorails.io).

#### Tooling

I’ll be using Ruby, Rails, rbenv, VS Code, and iTerm to make this tutorial. You’re welcome to customize that tool stack to your heart’s content. I’m forgoing making this any more challenging or complicated than it needs to be.

To help even more with this (and to save a boatload of time on my end), I made a Rails application template I call [Kickoff Tailwind](https://github.com/justalever/kickoff_tailwind). You can use it as a starting point to get up and running fast with a new Rails app. The template leverages Devise, Tailwind CSS, Friendly Id, Sidekiq, and custom templates view from the GitHub repo. This works with Rails 5.2+. Check the [github repo](https://github.com/justalever/kickoff_tailwind) for more information.

#### Proposed Data Architecture

After a quick brainstorming session, I landed on what kind of data I might need I compiled a generalized list. This will most likely change as I progress. I prefer doing this upfront when I’m not touching code. It allows me to think in a different manner which ultimately means less trial and error later.

- **User – Model** _Free from Devise_
  - Roles: Admin, Maker, Backer
  - Username _(free from my kickoff tailwind template)_
  - Name _(free from my kickoff tailwind template)_
  - Email _(free from devise)_
  - Password _(free from Devise)_
- **Project**
  - Title – _string_
  - Description – _rich\_textarea (action text)_
  - Pledge Goal – _string_
  - Pledge Goal Ends At – date time
  - Commentable Type – _string_
  - Commentable ID – _bigint_
  - User ID – _integer_
- **Perk** – Relative to Projects but separated (nested attributes?)
  - Title – _string_
  - Amount – _decimal_
  - Description – _rich\_textarea (action text)_
  - Amount Available – _integer_
  - Project ID – _integer_
- **Comment** – Polymorphic
  - Body

# Creating projects

To kick things off I’ll generate our Project model scaffold with a title, donation goal and user association. We’ll add the description field later as an action text feature in Rails 6. We will also add polymorphic comments in the event you need comments elsewhere in the future.

```bash
$ rails g scaffold Project title:string donation_goal:decimal user:references

Running via Spring preloader in process 31754
      invoke active_record
      create db/migrate/20190829024529_create_projects.rb
      create app/models/project.rb
      invoke test_unit
      create test/models/project_test.rb
      create test/fixtures/projects.yml
      invoke resource_route
       route resources :projects
      invoke scaffold_controller
      create app/controllers/projects_controller.rb
      invoke erb
      create app/views/projects
      create app/views/projects/index.html.erb
      create app/views/projects/edit.html.erb
      create app/views/projects/show.html.erb
      create app/views/projects/new.html.erb
      create app/views/projects/_form.html.erb
      invoke test_unit
      create test/controllers/projects_controller_test.rb
      create test/system/projects_test.rb
      invoke helper
      create app/helpers/projects_helper.rb
      invoke test_unit
      invoke jbuilder
      create app/views/projects/index.json.jbuilder
      create app/views/projects/show.json.jbuilder
      create app/views/projects/_project.json.jbuilder
      invoke assets
      invoke scss
      create app/assets/stylesheets/projects.scss
      invoke scss
      create app/assets/stylesheets/scaffolds.scss

```

The command above generates a ton of files. I’ll delete the `scaffold.scss` file and the `project.scss` file that get generated since we don’t need either. This tutorial won’t be test driven. While I realize it’s important to practice TDD where possible the scope of this guide is to teach you to build out the idea using practical Rails concepts. Later on, tests can and should be a focus.

I’ll add a `description` field later via action text. Commenting will also come later!

Let’s migrate. Notice when you do a new file in `db/` is born called `schema.rb`.

```bash
$ rails db:migrate

== 20190829024529 CreateProjects: migrating ===================================
-- create_table(:projects)
   -> 0.0046s
== 20190829024529 CreateProjects: migrated (0.0049s) ==========================

```

Thanks to our `user:references` option we now have a `belongs_to :user` association within the `app/model/project.rb` file (within the Ruby class). This tells ActiveRecord how to associate our `User` model to our `Project` model. By using my [kickoff template](https://github.com/justalever/kickoff_tailwind) and Devise we got our `User` model for free. If you didn’t use those templates (it’s totally fine if not), you’ll need to generate a new user model and install Devise before going forward.

```ruby
# app/models/project.rb

class Project < ApplicationRecord
  belongs_to :user
end

```

We still need to declare a `has_many` association on the user model to make this all work.

```ruby
# app/models/user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :projects, dependent: :destroy # add this line
end

```

What’s happening here is that we expect a user to be able to create and associate themselves to many projects. Doing so happens on the `Project` model. When I ran `user:references` a new column was added to the database. If you check out that migration that was generated you’ll see the following:

```ruby
# db/migrate/20190829024529_create_projects.rb
class CreateProjects < ActiveRecord::Migration[6.0]
  def change
    create_table :projects do |t|
      t.string :title
      t.string :donation_goal
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end

```

The `t.references` method is a wrapper around some SQL that will generate an `user_id` column on the `projects` table in the database. Since we already migrated our new data types you can refer to the `schema.rb` file for a better visual of what was added. Remember not to edit this file directly.

```ruby
# db/schema.rb
ActiveRecord::Schema.define(version: 2019_08_29_024529) do
    ... # code omitted for clarity sake

  create_table "projects", force: :cascade do |t|
    t.string "title"
    t.string "donation_goal"
    t.integer "user_id", null: false # added thanks to user:references
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["user_id"], name: "index_projects_on_user_id" # added thanks to user:references
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.string "username"
    t.string "name"
    t.boolean "admin", default: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "projects", "users"
end

```

### Updating Routes

Since the focus of this app will be on projects it probably makes sense to make the project index path the root path of the app. This can be done by updating the `config/routes.rb` file to the following:

```ruby
# config/routes.rb

require 'sidekiq/web'

Rails.application.routes.draw do
  resources :projects
    authenticate :user, lambda { |u| u.admin? } do
      mount Sidekiq::Web => '/sidekiq'
    end

  devise_for :users
  root to: 'projects#index' # changed from 'home#index'
end

```

Now the root path should update to an ugly looking project table listing. Clicking `New Project` should direct you to `localhost:3000/projects/new` where a form awaits. You can remove the `User Id` field altogether.

### Some initial UI Love

I added some basic styles to make this look a little more presentable at this point. I chose a darker background for grins. Feel free to modify the styles in any way you please. If you’re following along and using my Kickoff Tailwind template the next section will apply to you directly.

```css
/* app/javascript/stylesheets/components/_forms.scss */
.input {
  @apply appearance-none block w-full bg-white text-gray-800 rounded py-3 px-4 leading-tight;

  &.input-with-border {
    @apply border;
  }
}

.input:focus {
  @apply outline-none bg-white;
}

.label {
  @apply block text-white font-bold mb-2;
}

.select {
  @apply appearance-none py-3 px-4 pr-8 block w-full bg-white text-gray-800
   rounded leading-tight border-2 border-transparent;
  -webkit-appearance: none;
}

.select:focus {
  @apply outline-none bg-white;
}

```

And the buttons get a little love as well.

```css
/* app/javascript/stylesheets/components/_buttons.scss */

/* Buttons */

.btn {
  @apply font-semibold text-sm py-2 px-4 rounded cursor-pointer no-underline inline-block;

  &.btn-sm {
    @apply text-xs py-1 px-3;
  }

  &.btn-lg {
    @apply text-base py-3 px-4;
  }

  &.btn-expanded {
    @apply block w-full text-center;
  }
}

.btn-default {
  @apply bg-blue-600 text-white;

  &:hover,
  &:focus {
    @apply bg-blue-500;
  }

  &.btn-outlined {
    @apply border border-blue-600 bg-transparent text-blue-600;

    &:hover,
    &:focus {
      @apply bg-blue-600 text-white;
    }
  }
}

.btn-white {
  @apply bg-white text-blue-800;

  &:hover,
  &focus {
    @apply bg-gray-300;
  }
}

.link {
  @apply no-underline text-white;

  &:hover,
  &:focus {
    @apply text-gray-100;
  }
}

```

I added a new file to tweak the heading colors `h1-h6` as well. This requires an import to our `application.scss` file.

```css
/* app/javascript/stylesheets/components/_typography.scss */
h1,
h2,
h3,
h4,
h5,
h6 {
  @apply text-white;
}


/* app/javscript/stylesheets/application.scss */

@tailwind base;
@tailwind components;

// Custom SCSS
@import "components/buttons";
@import "components/forms"; 
@import "components/typography"; /* Add this line */

@tailwind utilities;

```

Finally the main `application.html.erb` layout gets a slight tweak to the body class only:

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Back My Idea</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <meta name="viewport" content="width=device-width, initial-scale=1">
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

  </head>

 <body class="bg-blue-800 text-blue-100">
 <!-- a ton more code below: Find this in my kickoff tailwind rails application template (linked in this post) -->

```

### Project Forms and Index

With our new form classes adjusted to match the darker color scheme, we can update the templates directly to accommodate.

```html
<!-- app/views/projects/_form.html.erb-->

<%= form_with(model: project, local: true) do |form| %>
  <% if project.errors.any? %>
    <div id="error_explanation" class="bg-white p-6 rounded text-red-500 mb-5">
      <h2 class="text-red-500 font-bold"><%= pluralize(project.errors.count, "error") %> prohibited this project from being saved:</h2>

      <ul>
        <% project.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="mb-6">
    <%= form.label :title, class: "label" %>
    <%= form.text_field :title, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :donation_goal, class: "label" %>
    <%= form.text_field :donation_goal, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.submit class: "btn btn-default" %>
  </div>
<% end %>

```

Our scaffold for Projects currently shares this form partial in both `projects/new.html.erb` and `projects/edit.html.erb` so I’ll update those to make this all look a little nicer.

Here’s the `/new` template:

```html
<!-- app/views/projects/new.html.erb -->
<div class="max-w-lg m-auto">
  <h1 class="text-3xl font-bold mb-6">Create a Project</h1>
  <%= render 'form', project: @project %>
</div>

```

and the `/edit`:

```html
<!-- app/views/projects/edit.html.erb -->
<div class="max-w-lg m-auto">
  <h1 class="text-3xl font-bold mb-6">Edit Project</h1>
  <%= render 'form', project: @project %>
</div>

```

Note: Both the `/new` and `/edit` routes should definitely require a User to be logged in to view these. We’ll approach that issue coming up.

#### Handling Errors / Authentication

Trying to create a new Project results in an error since we removed the reference to the `user_id` that existed originally when scaffolding the Project resource. We can associate the user on the controller side of the equation to fix this problem. As it stands, you can’t create a new project.

In the real world, I need access to the user who is creating the project. I need this data so we can associate that user to the project going forward. Doing so means the user must be signed in before creating a new project. We can address this as well as the errors in our controller.

I’ll update the `projects_controller.rb` to the following as a result:

```ruby
# app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
  before_action :set_project, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:index, :show]

  def index
    @projects = Project.all
  end

  def show
  end

  def new
    @project = Project.new
  end

  def edit
  end

  def create
    @project = Project.new(project_params)
    @project.user_id = current_user.id

    respond_to do |format|
      if @project.save
        format.html { redirect_to @project, notice: 'Project was successfully created.' }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @project.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @project.update(project_params)
        format.html { redirect_to @project, notice: 'Project was successfully updated.' }
        format.json { render :show, status: :ok, location: @project }
      else
        format.html { render :edit }
        format.json { render json: @project.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @project.destroy
    respond_to do |format|
      format.html { redirect_to projects_url, notice: 'Project was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    def set_project
      @project = Project.find(params[:id])
    end

    def project_params
      params.require(:project).permit(:title, :donation_goal)
    end
end

```

As a result, the currently logged in user’s `id` attribute gets scoped to the project. Also be sure to adjust the `before_action` at the top of the class. We added a devise option called `authenticate_user!` which allows you to lock down any action in the class. In this case I’m white-listing `index` and `show` because anyone browsing the website will be able to see those without being logged in.

Try creating a new project now. You should be redirected to login. If so, and you haven’t already, create an account and create a new project. We can verify things worked correctly using our logs and/or Rails console.

```bash
Running via Spring preloader in process 45423
Loading development environment (Rails 6.0.0)
irb(main):001:0> Project.last
   (0.6ms) SELECT sqlite_version(*)
  Project Load (0.1ms) SELECT "projects".* FROM "projects" ORDER BY "projects"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<Project id: 1, title: "First Project", donation_goal: "$1000", user_id: 1, created_at: "2019-08-30 19:15:13", updated_at: "2019-08-30 19:15:13">

```

Notice when I type `Project.last` we get a project back. The `user_id` column has `1` as a value. Since there is only one account (at least on my machine) It’s safe to say our work here was a success.

### Project Index

The `index.html.erb` file displays all projects at this point. You will need to create some dummy projects to get any data to appear. I’ll make this a grid-layout with card components. Here’s the initial markup:

```html
<!-- app/views/projects/index.html.erb -->

<div class="flex flex-wrap items-start justify-start">
  <% @projects.each do |project| %>
    <div class="relative w-full p-6 border-2 border-blue-700 rounded-lg lg:w-1/4 lg:mr-8">
      <%= image_tag project.thumbnail.variant(resize_to_limit: [600, 400]), class: "rounded" if project.thumbnail.present? %>
      <h3 class="mb-2 text-2xl font-bold"><%= project.title %></h3>
      <div class="my-1"><%= truncate(strip_tags(project.description.to_s), length: 140) %></div>
      <p>Donation goal: <%= project.donation_goal %></p>
      <p class="text-sm italic opacity-75">Created by: <%= project.user.name %> </p>
      <%= link_to "View project", project, class: "btn btn-default inline-block text-center my-2" %>
      <% if author_of(project) %>
      <div class="absolute top-0 right-0 mt-2 mr-2">
        <%= link_to edit_project_path(project) do %>
          <svg class="w-6 h-6 text-white opacity-75 fill-current" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>edit</title><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></svg>
        <% end %>
      </div>
      <% end %>
    </div>
  <% end %>
</div>

```

If you look closely you’ll see a new helper called `author_of`. I added this to extract logic from the view. That logic now lives inside `app/helpers/application_helper.rb`

```ruby
# app/helpers/application_helper.rb

def author_of(resource)
  user_signed_in? && resource.user_id == current_user.id
end

```

I chose to put this in `application_helper.rb` because we’ll be using it pretty much everywhere. The helper checks first if a user is signed in and also if the object passed through has a `user_id` attribute that matches the `current_user` `id`. This essentially says, only the logged-in user who created this project should be able to edit it. We also have an `admin` attribute by default on the User model. It’s probably best to allow admins to edit this resource as well. We can create a new helper inside the `app/helpers/application_helper.rb` file as well.

```ruby
# app/helpers/application_helper.rb

def author_of(resource)
  user_signed_in? && resource.user_id == current_user.id
end

def admin?
  user_signed_in? && current_user.admin?
end

```

Logged in authors get a new edit icon which links to an edit path. The UI now looks like this:

### Extending Projects

Let’s tackle the description field for our project model. Traditionally you would likely add a `text` data type to a new column in the database. With the official launch of Rails 6 we now have access to [Action Text](https://edgeguides.rubyonrails.org/action_text_overview.html) which is a nice rich text editor designed to work with Ruby on Rails apps and more. We can install action text dependencies using a simple command:

```bash
$ rails action_text:install

```

This installs some dependencies and creates two new migrations. Action Text uses Active Storage so that is installed as well.

Next we want to migrate those migration files in:

```bash
$ rails db:migrate  

```

These essentially create separate tables for action text fields and active storage fields. Ultimately, this means we don’t need dedicated attributes on our other tables. Instead, we define what we need in our models and Rails handles the rest like magic.

Let’s update the `Project` model:

```ruby
# app/models/project.rb

class Project < ApplicationRecord
  belongs_to :user
  has_rich_text :description
end

```

The line `has_rich_text` denotes a specific association for action text. You can name this whatever you please. I chose `:description`.

With that added, save the file and head to the form partial. Now we can add the new text area to the form:

```html
<!-- app/views/projects/_form.html.erb-->

<%= form_with(model: project, local: true) do |form| %>
  <% if project.errors.any? %>
    <div id="error_explanation" class="bg-white p-6 rounded text-red-500 mb-5">
      <h2 class="text-red-500 font-bold"><%= pluralize(project.errors.count, "error") %> prohibited this project from being saved:</h2>

      <ul>
        <% project.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="mb-6">
    <%= form.label :title, class: "label" %>
    <%= form.text_field :title, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :donation_goal, class: "label" %>
    <%= form.text_field :donation_goal, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :description, class: "label" %>
    <%= form.rich_text_area :description, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.submit class: "btn btn-default" %>
  </div>
<% end %>
```

With this added we can check out the form. At the moment our CSS isn’t loading correctly. It’s also not matching our design. Instead of just using the defaults imported from the `trix` node package added during the installation I will customize the CSS completely.

Once Action Text gets installed it adds a `actiontext.scss` file to `app/assets/stylesheets/`. Our app’s CSS will live in `app/javascript/stylesheets`. I’ll search for the node module called trix and import the CSS file from it.

```css
/* app/javascript/stylesheets/actiontext.scss */

@import "trix/dist/trix.css";

trix-toolbar {
  .trix-button {
    @apply bg-white border-0;
  }

  .trix-button-group {
    border: 0;
  }

  .trix-button--icon-bold {
    @apply rounded-tl rounded-bl;
  }

  .trix-button--icon-redo {
    @apply rounded-tr rounded-br;
  }
}

.trix-button--icon-attach,
.trix-button-group-spacer,
.trix-button--icon-decrease-nesting-level,
.trix-button--icon-increase-nesting-level,
.trix-button--icon-code {
  display: none;
}

.trix-content {
  .attachment-gallery {
    > action-text-attachment,
    > .attachment {
      flex: 1 0 33%;
      padding: 0 0.5em;
      max-width: 33%;
    }

    &.attachment-gallery--2,
    &.attachment-gallery--4 {
      > action-text-attachment,
      > .attachment {
        flex-basis: 50%;
        max-width: 50%;
      }
    }
  }

  action-text-attachment {
    .attachment {
      padding: 0 !important;
      max-width: 100% !important;
    }
  }
}
```

We need to import it in our main `application.scss` file now:

```css
/* app/javascript/stylesheets/application.scss */

@tailwind base;
@tailwind components;

// Custom SCSS
@import "components/buttons";
@import "components/forms";
@import "components/actiontext";
@import "components/typography";

@tailwind utilities;

```

While everything looks to be working, we have a couple more tasks to handle. On the controller level we need to permit this new field. Head to `projects_controller.rb`

At the bottom of the file you should see a private method called `project_params`

```ruby
# app/controllers/projects_controller.rb
...
private
...

def project_params
  params.require(:project).permit(:title, :donation_goal)
end

```

We need to add `:description` to the `permit` method.

```ruby
# app/controllers/projects_controller.rb
...
private
...

def project_params
  params.require(:project).permit(:title, :donation_goal, :description)
end

```

This tells rails to white-list that new field thus letting data get saved to the data base. We can update our index view to verify the output:

```html
<!-- app/views/projects/index.html.erb-->
<div class="flex flex-wrap items-center justify-between">
  <% @projects.each do |project| %>
    <div class="border-2 border-blue-700 rounded-lg p-6 lg:w-1/4 w-full relative">
      <h3 class="font-bold text-2xl mb-2"><%= project.title %></h3>
      <div class="my-1"><%= truncate(strip_tags(project.description.to_s), length: 140) %></div>
      <!-- more code below omitted for brevity-->

```

Here we are displaying the `project.description`. Action Text comes back as HTML so we need a way to:

1. Sanitize that data (strip tags, html, etc..)
2. Truncate the newly sanitized data so it’s not very long on the index view. You can set a length property to do this.

### Adding Images

Let’s add support for thumbnail images and logos for each project. We can also add gravatar support for each author of a project. We’ll leverage active storage to add those thumbnails and a helper to add support for Gravatars.

Head to your `project.rb` model file. Within it I’ll append the following:

```ruby
# app/models/project.rb

class Project < ApplicationRecord
  belongs_to :user
  has_rich_text :description
  has_one_attached :thumbnail # add this line
end

```

This new line hooks into Rails active storage. We signify we only want one attachment by the `has_one` prefix. You can also add `has_many_attached` to denote many attachments.

Next, I’ll update the project form to include a file field as well as mark the form to accept multi-part data.

```html
<%= form_with(model: project, local: true, multipart: true) do |form| %>
  <% if project.errors.any? %>
    <div id="error_explanation" class="bg-white p-6 rounded text-red-500 mb-5">
      <h2 class="text-red-500 font-bold"><%= pluralize(project.errors.count, "error") %> prohibited this project from being saved:</h2>

      <ul>
        <% project.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="mb-6">
    <%= form.label :thumbnail, "Project thumbnail", class: "label" %>
    <%= form.file_field :thumbnail %>
  </div>

  <!-- code omitted for brevity -->

```

On the form options, I passed a `multipart: true` option. This tells the form we expect file data as a result. I also added the new `file_field` for the `:thumbnail` itself. We need to permit this field in our controller next.

Let’s try attaching a thumbnail: https://unsplash.com/photos/cXkrqY2wFyc

If you update an existing project or add a new one you might see some gnarly logging. This means our work did indeed prove worthy:

```bash
 ActiveStorage::Blob Create (1.0ms) INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["key", "txcaum3pyrdvejhw0km0ul2wib99"], ["filename", "kelly-sikkema-cXkrqY2wFyc-unsplash.jpg"], ["content_type", "image/jpeg"], ["metadata", "{\"identified\":true}"], ["byte_size", 82684], ["checksum", "rJPItH/glusRYq0Q5+iOSg=="], ["created_at", "2019-08-31 13:28:19.543763"]]
  ↳ app/controllers/projects_controller.rb:36:in `block in update'
  ActiveStorage::Attachment Create (0.4ms) INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?) [["name", "thumbnail"], ["record_type", "Project"], ["record_id", 1], ["blob_id", 1], ["created_at", "2019-08-31 13:28:19.546513"]]
  ↳ app/controllers/projects_controller.rb:36:in `block in update'
  Project Update (0.1ms) UPDATE "projects" SET "updated_at" = ? WHERE "projects"."id" = ? [["updated_at", "2019-08-31 13:28:19.547959"], ["id", 1]]
  ↳ app/controllers/projects_controller.rb:36:in `block in update'
   (3.0ms) commit transaction
  ↳ app/controllers/projects_controller.rb:36:in `block in update'
  Disk Storage (1.3ms) Uploaded file to key: txcaum3pyrdvejhw0km0ul2wib99 (checksum: rJPItH/glusRYq0Q5+iOSg==)
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: cdcf68f0-4d73-429c-9af5-7604645e8f40) to Sidekiq(active_storage_analysis) with arguments: #<GlobalID:0x00007fea5296a048 @uri=#<URI::GID gid://back-my-idea/ActiveStorage::Blob/1>>
Redirected to http://localhost:3000/projects/1

```

Heading back to our index we still can’t see the image yet. Let’s resolve that.

I’ll add this to our existing card markup:

```html
<%= image_tag project.thumbnail.variant(resize_to_limit: [600, 400]) %>

```

This still won’t work unfortunately. Active storage needs another dependency to work with image varients on the fly like I’m doing here. We can resolve that with a simple gem install

We need to uncomment the `image_processing` gem in the `Gemfile` and run `bundle install` following that.

```ruby
# Gemfile
# Use Active Storage variant
gem 'image_processing', '~> 1.2' # uncomment this line

```

Save that file and then run

```bash
$ bundle install

```

**If your server is running, restart it.**

Success!

### Comments

Rather than scaffolding the complete project logic yet we can start of other aspects of the application. Comments are in virtually any user-facing app. We might as well add them to projects. We can do so in a scalable way using polymorphism. That essentially means we can add comments to anything if necessary.

Generate the comment model and resources:

```bash
$ rails g model Comment commentable_type:string commentable_id:integer user:references body:text

      invoke active_record
      create db/migrate/20200123193236_create_comments.rb
      create app/models/comment.rb
      invoke test_unit
      create test/models/comment_test.rb
      create test/fixtures/comments.yml

```

Adding this model creates a new migration essentially creating a `comments` table in the database. We can extend it to be polymorphic in the model layer and also do a `has_many :through` type of association. The result is as follows:

```ruby
# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :commentable, polymorphic: true
end


# app/models/project.rb
class Project < ApplicationRecord
  belongs_to :user
  has_rich_text :description
  has_one_attached :thumbnail
  has_many :comments, as: :commentable
end

```

If we wanted to add comments to other models we totally could now thanks to how the association is set up and polymorphism.

### Comments controller

To get our comment response cycle in order we need to add a `comments_controller.rb` to the app next.

```bash
$ rails g controller comments create 

```

This creates both a controller and `create.html.erb` view along with some other files of which you can discard if you like.

We also need an instance of `commentable` that makes sense to namespace within projects. We can do this by generating a new controller inside a folder called `projects` all inside `app/controllers`

```bash
$ rails g controller projects/comments

```

That file contains the following:

```ruby
# app/controllers/projects/comments_controller.rb

class Projects::CommentsController < CommentsController
  before_action :set_commentable

  private

    def set_commentable
      @commentable = Project.find(params[:project_id])
    end
end

```

We are grabbing the instance of the project at hand and assigning it as `@commentable` in order to access it in the `comments_controller.rb`. You can repeat this concept for multiple resources if you have them. By this I mean you aren’t bound to just `Project` resources. Notice how the class inherits from `CommentsController` directly. This is intended!

Inside the comments controller I’ve added the following code:

```ruby
# app/controllers/comments_controller.rb

class CommentsController < ApplicationController
  before_action :authenticate_user!

  def create
    @comment = @commentable.comments.new comment_params
    @comment.user = current_user
    @comment.save
    redirect_to @commentable, notice: "Your comment was successfully posted."
  end

  private

    def comment_params
      params.require(:comment).permit(:body)
    end
end

```

We require the user to be signed in to comment on line 2. Inside the `create` action we create a `@comment` instance variable. It accesses the `@commentable` instance variable inherited from our controller within `app/controllers/projects/comments_controller.rb` file. We can create a new instance of that class and pass in the `comment_params` defined at the bottom of the file beneath the `private` declaration. Finally we assign the commenting user the `current_user` object and save. If all goes well we redirect back to the project or (@commentable) with a successful notice.

**A bit of clean up**

1. I deleted the folder `comments` that was generated within `app/views/projects`
2. A deleted the `create.html.erb` file inside `app/views/comments`
3. I created two new partials within `app/views/comments/` `_comments.html.erb` & `_form.html.erb`

In our project `show` view I’ve updated the markup a touch. Some dummy data is there which we’ll address later. We want to add the comment feed for now:

```html
<!-- app/views/projects/show.html.erb (WIP) -->
<div class="container relative p-6 mx-auto text-gray-900 bg-white rounded-lg lg:p-10">
  <div class="flex flex-wrap items-center justify-between w-full pb-4 mb-8 border-b-2 border-gray-200">
    <div class="flex flex-wrap items-start justify-between w-full pb-4 mb-4 border-b-2 border-gray-200">
      <div class="flex-1">
        <h1 class="text-3xl font-bold leading-none text-gray-800"><%= @project.title %></h1>
        <p class="text-sm italic text-gray-500">Created by <%= @project.user.name ||=
      @project.user.username %></p>
      </div>

      <% unless author_of(@project) %>
        <%= link_to "Back this idea", "#", class: "btn btn-default btn-lg lg:w-auto w-full lg:text-left text-center" %>
      <% end %>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none"><%# @project.pledged_amount %>1000</p>
      <p class="text-sm text-gray-500">pledged of <%= number_to_currency(@project.donation_goal) %></p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">200</p>
      <p class="text-sm text-gray-500">backers</p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">20</p>
      <p class="text-sm text-gray-500">days to go</p>
    </div>
  </div>

  <div class="flex flex-wrap items-start justify-between mb-6">
    <div class="w-full lg:w-3/5">
     <% if @project.thumbnail.present? %>
       <%= image_tag @project.thumbnail, class: "rounded" %>
       <% else %>
      <div class="flex items-center justify-center p-8 text-center bg-gray-100 rounded">
        <div class="">
          <p class="text-xs font-bold text-gray-600 uppercase">PROJECT</p>
          <h3 class="text-2xl text-black"><%= @project.title %></h3>
        </div>
      </div>
    <% end %>
    </div>
    <div class="w-full mt-6 lg:pl-10 lg:w-2/5 lg:mt-0">
      <p class="text-sm font-semibold text-gray-500 uppercase">Description</p>
      <%= @project.description %>
    </div>
  </div>

  <div class="w-full lg:w-3/5">
    <%= render "comments/comments", commentable: @project %>
    <%= render "comments/form", commentable: @project %>
  </div>

  <% if admin? || author_of(@project) %>
    <div class="absolute top-0 right-0 mt-4 mr-4">
      <%= link_to 'Edit', edit_project_path(@project), class: "btn btn-sm btn-outlined btn-default" %>
    </div>
  <% end %>
</div>

```

The comments partial (looping through each comment for display)

```html
<!-- app/views/comments/_comments.html.erb -->
<p class="text-sm font-semibold text-gray-500 uppercase">Comments</p>
<% commentable.comments.each do |comment| %>
  <%= comment.body %>
<% end %>

```

The comment form

```html
<!-- app/views/comments/_form.html.erb -->
<%= form_for [commentable, Comment.new] do |f| %>
  <div class="mb-6">
    <%= f.text_area :body, class: "input input-with-border", placeholder: "Add a comment", required: true %>
  </div>
  <%= f.submit class: "btn btn-default" %>
<% end %>

```

### Comment routing

It likely makes sense to make our comments nested within projects no? We actually can do this quite easily in `config/routes.rb`

```ruby
# config/routes.rb
require 'sidekiq/web'

Rails.application.routes.draw do
  authenticate :user, lambda { |u| u.admin? } do
    mount Sidekiq::Web => '/sidekiq'
  end

  resources :projects do
    resources :comments, module: :projects
  end

  devise_for :users
  root to: 'projects#index'
end

```

Within `resources :projects` I’ve added a block containing the new `resources:comments` line. We use the `module:` declaration to make `projects` not present in the `url`.

You’ll likely want to restart your server at this point

```bash
$ CTRL + C
$ rails db:migrate
$ rails server

```

You should now be able to create and render comments all on the project show view. If you’d like to be able to edit or delete comments you can extend the comments controller to have `update` , `edit` and `destroy` actions. I’ll be leaving this part out of this specific tutorial for now as I’ve covered it in other series.

### Extending Projects further

With comments out of the way, we can direct our attention back to projects. You’ll probably notice I don’t have a few requirements mapped out in the data layer just yet. We still need the following:

- A field to represent the current pledge amount. We’ll use this to determine how much is needed to match the pledge goal at any given moment.
- An expiration window for each project. Initially, this is capped at 30 days. We’ll need a `datetime` stamp in the database for this
- We need a way to count how many backers pledged each project. This can probably just be some logic we query for on the user layer. If a user backed an idea we can look up a charge relative to the project. 
- We need a status column to represent whether the project is active or past its pledge window of 30 days.

We can create a migration with those fields. We may add later if necessary.

```bash
$ rails g migration add_fields_to_projects current_donation_amount:integer expires_at:datetime status:string

```

That migration generates this file:

```ruby
class AddFieldsToProjects < ActiveRecord::Migration[6.0]
  def change
    add_column :projects, :current_donation_amount, :integer, default: 0
    add_column :projects, :expires_at, :datetime, default: DateTime.now + 30.days
    add_column :projects, :status, :string, default: "active"
  end
end

```

Simple enough right?

My thought here is to update the `current_donation_amount` each time a backer “backs” a project. We’ll default that column to `0` outright. We probably should have made the `donation_goal` column an integer as well but it’s not a huge deal for now. We will have to convert the string into an integer coming up.

For `expires_at` we can create a default DateTime of 30 days each time a new project is created. I’ll pass a default of the current time using `default: DateTime.now`.

The `status` of the project is either “active” or “inactive”. Active meaning within the 30-day expiration and inactive when it’s past that chunk of time.

```bash
$ rails db:migrate

```

Now each project has a status of “active”, expiration start time,

### Handling project expiration

There are a couple ways we can go about dynamically “expiring” a given project. Cron jobs and Active Jobs are probably the most common. Active Jobs seem more appealing to me in this case (mostly because I haven’t done a lot of cron job work) but you are free to choose your weapon of choice here.

What I want to do is automatically change the status of an active project to “inactive” once the `expires_at` DateTime meets present day. Doing this manually seems ridiculous so we can build a job that will be enqueued every time a new project is created. First we need a job:

```bash
$ rails generate job ExpireProject
Running via Spring preloader in process 47618
      invoke test_unit
      create test/jobs/expire_project_job_test.rb
      create app/jobs/expire_project_job.rb

```

Running that generation creates a couple files. The main one being the actual job class in mention:

```ruby
# app/jobs/expire_project_job.rb
class ExpireProjectJob < ApplicationJob
  queue_as :default

  def perform(*args)
    # Do something later
  end
end

```

We’ll add our logic within the perform method which is quite a simple change.

```ruby
# app/jobs/expire_project_job.rb
class ExpireProjectJob < ApplicationJob
  queue_as :default

  def perform(project)
    @project = project

    return if project_already_inactive?

    @project.status = "inactive"
    @project.save!
  end

  private

  def project_already_inactive?
    @project.status == "inactive"
  end
end

```

We’ll be passing in the project object directly when we initialize the Job. If the project already has a status of inactive we’ll just return. If it’s “active” we’ll update the status to be “inactive” and save the object. It’s probably a good idea to notify the author of the project that their project has expired. We can send a mailer to the author of the project just as well. That might look like the following:

```ruby
# app/jobs/expire_project_job.rb
class ExpireProjectJob < ApplicationJob
  queue_as :default

  def perform(project)
    @project = project

    return if project_already_inactive?

    @project.status = "inactive"
    @project.save!

    UserMailer.with(project: @project).project_expired_notice.deliver_later
  end

  private

  def project_already_inactive?
    @project.status == "inactive"
  end
end

```

Let’s make that mailer and method. We can do so from the command line (Have I told you how much I love Rails?)

```bash
$ rails g mailer User project_expired_notice

```

This generates a handful of files:

- `app/mailers/user_mailer.rb`
- `app/views/user_mailer/project_expired_notice.html.erb`
- `app/views/user_mailer/project_expired_notice.text.erb` – which I’ve deleted
- `test/mailers/previews/user_mailer_preview.rb`
- `test/mailers/user_mailer_test.rb`

Let’s look at the first file:

```ruby
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def project_expired_notice
    @project = params[:project]

    mail to: @project.user.email, subject: "Your project has expired"
  end
end

```

Here I’ve added some logic to send the notice of expiration to the user who created the project. Notice how we use the instance variable as passed down from the original background job. This variable extends all the way down to the view layer!

```html
<!-- app/views/user_mailer/project_expired_notice.html.erb -->
<h1>Hi <%= @project.user.name ||= @project.user.username %>,</h1>

<p>We wanted to inform you that the project <strong><%= @project.title %></strong> has met its expiration and is no longer active. <%= link_to "View your project", project_url(@project), target: "_blank" %>.</p>

```

Here we display a short message to the user and provide a call to action to view the project. Notice the URL helper is `project_url` instead of `project_path`. This is necessary for emails as we need absolute paths. People can check email from anywhere as you know.

We can then preview the email using the file `test/mailers/previews/user_mailer_preview.rb` and visiting `localhost:3000/rails/mailers`. You’ll likely see an error because we technically don’t have the right data we need. Here’s the file as it was generated.

```ruby
# test/mailers/previews/user_mailer_preview.rb

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/project_expired_notice
  def project_expired_notice
    UserMailer.project_expired_notice
  end

end

```

And after:

```ruby
# test/mailers/previews/user_mailer_preview.rb

class UserMailerPreview < ActionMailer::Preview
  def project_expired_notice
    UserMailer.with(project: Project.first).project_expired_notice
  end
end

```

I pass in the first Project in the database as a dummy project to view the mailer at `http://localhost:3000/rails/mailers/user_mailer/project_expired_notice`

The email doesn’t look real hot but it does render and contains the link and project information we are after. Pretty cool!

With our email in place, we need to talk about background jobs for a bit.

#### Configuring background jobs

Running jobs usually occur in the background. **If you’re using my kickoff\_tailwind template I already have my favorite background job configured tool called [Sidekiq](https://github.com/mperham/sidekiq)**.

If you’re not using my template then don’t fear setting up sidekiq is pretty painless. The gem uses an adapter that hooks into ActiveJob which is already part of Rails by default.

Be sure to install the gem first!

If you peek inside `config/application.rb` you should see the main configuration you need to get started. If not go ahead and install the gem and copy the code below:

```ruby
# config/application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module BackMyIdea
  class Application < Rails::Application
    config.active_job.queue_adapter = :sidekiq
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

```

The main line we need is `config.active_job.queue_adapter = :sidekiq`.

My kickoff\_tailwind template also adds some routing for Sidekiq as well. It’s assumed only admins can see a GUI interface at all times. Here is my `routes.rb` file so far:

```ruby
# config/routes.rb
require 'sidekiq/web'

Rails.application.routes.draw do
  authenticate :user, lambda { |u| u.admin? } do
    mount Sidekiq::Web => '/sidekiq'
  end

  resources :projects do
    resources :comments, module: :projects
  end

  devise_for :users
  root to: 'projects#index'
end

```

#### Testing it all out

We need to have sidekiq [running in the background](https://github.com/mperham/sidekiq/wiki/Active-Job#action-mailer). You can open a new terminal instance alongside your rails server instance and type. Not you might need to install Redis as well (`brew install redis`).

```bash
$ bundle exec sidekiq -q default -q mailers

```

With that running, we can finally trigger the job within our projects controller.

```ruby
# app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
...
 def create
    @project = Project.new(project_params)
    @project.user_id = current_user.id

    respond_to do |format|
      if @project.save
        ExpireProjectJob.set(wait_until: @project.expires_at).perform_later(@project)
        format.html { redirect_to @project, notice: 'Project was successfully created.' }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @project.errors, status: :unprocessable_entity }
      end
    end
  end
...
end

```

We added:

```ruby
ExpireProjectJob.set(wait_until: @project.expires_at).perform_later(@project)

```

This calls out the job we created prior and specifically waits until the project expires\_at column is met and then queues it up with Sidekiq.

Now you can create a new dummy project. Go ahead and do that now.

Now, I don’t expect you to wait 30 days to see if this worked lol. Intead we can check our Rails logs for starters:

```bash
[ActiveJob] Enqueued ExpireProjectJob (Job ID: 1fcc879f-139a-40c0-bf72-7e5b3dc1f0ef) to Sidekiq(default) with arguments: #<GlobalID:0x00007fd18f040700 @uri=#<URI::GID gid://back-my-idea/Project/3>>
Redirected to http://localhost:3000/projects/3

```

Good, the job gets enqueued successfully but we don’t know for sure if the code within the job is going to work as well. One way to gut-check it is to modify the initial wait time of the job to nothing. Let’s try that:

```ruby
ExpireProjectJob.perform_now(@project)

```

Note you can also do this from rails console if you like. You’d need to create a project manually though.

Create another project using the UI and see what happens.

I don’t have a way to see if the project is `inactive` or `active` except within rails console at this point so a quick way to check is:

```bash
$ rails c
Project.last
=> <Project id: 2, title: "test", donation_goal: 0.22222e5, user_id: 1, created_at: "2020-01-24 21:17:27", updated_at: "2020-01-24 21:17:27", current_donation_amount: 0, expires_at: "2020-02-22 22:51:44", status: "inactive">
irb(main):003:0>

```

Success! The status is `inactive`. Remember we set this column to be `active` by default when a new project is created.

Now we can modify the views to correlate with the status. It’s a pretty easy conditional of which I’ll extract into partials and a few helper methods:

```ruby
# app/models/project.rb

class Project < ApplicationRecord
...
  def active?
    status == "active"
  end

  def inactive
    status == "inactive"
  end
 end

```

And the main show view:

```html
<!-- app/views/projects/show.html.erb -->
<% if @project.active? %>
  <%= render "active_project", project: @project %>
<% else %>
  <%= render "inactive_project", project: @project %>
<% end %>

```

The active project view partial (has since been update a touch with stats):

```html
<div class="container relative p-6 mx-auto text-gray-900 bg-white rounded-lg lg:p-10">
  <div class="flex flex-wrap items-center justify-between w-full pb-4 mb-8 border-b-2 border-gray-200">
    <div class="flex flex-wrap items-start justify-between w-full pb-4 mb-4 border-b-2 border-gray-200">
      <div class="flex-1">
        <h1 class="text-3xl font-bold leading-none text-gray-800"><%= project.title %></h1>
        <p class="text-sm italic text-gray-500">Created by <%= project.user.name ||=
      project.user.username %></p>
      </div>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none"><%= number_to_currency(project.current_donation_amount) %>/mo</p>
      <p class="text-sm text-gray-500">pledged of <%= number_to_currency(project.donation_goal) %>/mo</p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">200</p>
      <p class="text-sm text-gray-500">backers</p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none"><%= distance_of_time_in_words_to_now(project.expires_at) %></p>
      <p class="text-sm text-gray-500">to go</p>
    </div>
  </div>

  <div class="flex flex-wrap items-start justify-between mb-6">
    <div class="w-full lg:w-3/5">
    <% if project.thumbnail.present? %>
      <%= image_tag project.thumbnail, class: "rounded" %>
    <% else %>
      <div class="flex items-center justify-center p-8 text-center bg-gray-100 rounded">
        <div class="">
          <p class="text-xs font-bold text-gray-600 uppercase">PROJECT</p>
          <h3 class="text-2xl text-black"><%= project.title %></h3>
        </div>
      </div>
    <% end %>
    </div>
    <div class="w-full mt-6 lg:pl-10 lg:w-2/5 lg:mt-0">
      <p class="text-sm font-semibold text-gray-500 uppercase">Description</p>
      <%= project.description %>
    </div>
  </div>

  <div class="w-full lg:w-3/5">
    <%= render "comments/comments", commentable: project %>
    <%= render "comments/form", commentable: project %>
  </div>

 <% if admin? || author_of(project) %>
    <div class="absolute top-0 right-0 mt-4 mr-4">
      <%= link_to 'Edit', edit_project_path(project), class: "btn btn-sm btn-outlined btn-default" %>
      <%= link_to 'Delee', project_path(project), method: :delete, class: "btn btn-sm btn-outlined btn-default", data: { confirm: "Are you sure?" } %>
    </div>
  <% end %>
</div>

```

And finally the inactive view with only subtle changes:

```html
<div class="container relative p-6 mx-auto text-gray-900 bg-white border-2 border-red-500 rounded-lg lg:p-10">
  <div class="flex flex-wrap items-center justify-between w-full pb-4 mb-8 border-b-2 border-gray-200">
    <div class="flex flex-wrap items-start justify-between w-full pb-4 mb-4 border-b-2 border-gray-200">
      <div class="flex-1">
        <span class="px-2 py-1 text-xs font-semibold text-white bg-red-500 rounded-lg">Inactive</span>

        <h1 class="mt-4 text-3xl font-bold leading-none text-gray-800"><%= project.title %></h1>
        <p class="text-sm italic text-gray-500">Created by <%= project.user.name ||=
      project.user.username %></p>
      </div>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none"><%= number_to_currency(project.current_donation_amount) %></p>
      <p class="text-sm text-gray-500">pledged of <%= number_to_currency(project.donation_goal) %></p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">200</p>
      <p class="text-sm text-gray-500">backers</p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">Expired</p>
      <p class="text-sm text-gray-500"><%= time_ago_in_words(@project.expires_at) %> ago</p>
    </div>
  </div>

  <div class="flex flex-wrap items-start justify-between mb-6">
    <div class="w-full lg:w-3/5">
    <% if project.thumbnail.present? %>
      <%= image_tag project.thumbnail, class: "rounded" %>
    <% else %>
      <div class="flex items-center justify-center p-8 text-center bg-gray-100 rounded">
        <div class="">
          <p class="text-xs font-bold text-gray-600 uppercase">PROJECT</p>
          <h3 class="text-2xl text-black"><%= project.title %></h3>
        </div>
      </div>
    <% end %>
    </div>
    <div class="w-full mt-6 lg:pl-10 lg:w-2/5 lg:mt-0">
      <p class="text-sm font-semibold text-gray-500 uppercase">Description</p>
      <%= project.description %>
    </div>
  </div>

  <div class="w-full lg:w-3/5">
    <%= render "comments/comments", commentable: project %>
    <p>Comments are closed for inactive projects</p>
  </div>

  <% if admin? || author_of(project) %>
    <div class="absolute top-0 right-0 mt-4 mr-4">
      <%= link_to 'Edit', edit_project_path(project), class: "btn btn-sm btn-outlined btn-default" %>
      <%= link_to 'Delee', project_path(project), method: :delete, class: "btn btn-sm btn-outlined btn-default", data: { confirm: "Are you sure?" } %>
    </div>
  <% end %>
</div>

```

If you head back to the projects index we still see all projects in view no matter what their status. It probably makes sense to only feed `active` projects through. We can add a scope for this in the model.

```ruby
# app/models/project.rb

class Project < ApplicationRecord
  ...
  scope :active, ->{ where(status: "active") }
  scope :inactive, ->{ where(status: "inactive") }
  ...
 end

```

Then in our controller update the `index` action to scope through the `active` scope.

```ruby
# app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
  before_action :set_project, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:index, :show]

  def index
    @projects = Project.active # swap from all to active
  end
  ...
end

```

Great now only “active” projects show up in our feed!

## Payment Setup with Stripe

##### Stripe connect oAuth Strategy

We need a couple of gems to make our lives easier working with Stripe. We’ll specifically be using [Stripe Connect](https://stripe.com/docs/connect) which is a way to be a platform in between all user payment transactions. The platform can earn a percentage per sale which makes marketplaces what they are!

```ruby
# Gemfile

gem 'stripe'
gem 'omniauth', '~> 1.9'
gem 'omniauth-stripe-connect'

```

Install those:

```bash
$ bundle install

```

Leveraging the [omniauth-stripe-connect](https://github.com/isaacsanders/omniauth-stripe-connect) gem we can easily hook into Devise with a little configuration. (If you’re using my template, Devise is already installed and configured. If not, you will need to install it yourself to get up to speed here).

# More Modeling

### Update the User model

We need to store some Stripe information for each user who decides to start a project. Doing so means a few new fields in the database.

```bash
$ rails g migration add_stripe_fields_to_users uid:string provider:string access_code:string publishable_key:string

```

These fields should be enough to create stripe accounts via stripe connect and start processing transactions once a user has authenticated via OAuth

Here’s the migration. We should be good to migrate! You’ll see where we use these fields coming up.

```ruby
# db/migrate/XXXX_add_stripe_fields_to_users.rb
class AddStripeFieldsToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :uid, :string
    add_column :users, :provider, :string
    add_column :users, :access_code, :string
    add_column :users, :publishable_key, :string
  end
end

```

```bash
$ rails db:migrate

```

## oAuth Flow

To connect with Stripe connect we need a way for both merchants and customers to distinguish themselves. Each user will need a corresponding stripe customer id that comes back during the oAuth redirection back to our app. We can hook into a handy gem that works great with Devise to help with this.

The gem is called `omniauth-stripe-connect` which you should have installed in a previous step.

### Declare a provider – Stripe Connect

To get the gem integrated with Devise we focus within `config/initializers/devise.rb`. We need to declare the new provider(you can have many i.e. Twitter, Facebook, Google, etc…). There will be tons of comments and settings within that file. I added the following at the end.

```ruby
# config/initializers/devise.rb
Devise.setup do |config|
  ...
  config.omniauth :stripe_connect, 
  Rails.application.credentials.dig(:stripe)[:connect_client_id], Rails.application.credentials.dig(:stripe)[:private_key],
  scope: 'read_write',
  stripe_landing: 'login'
end

```

This file points to keys we haven’t added to our app just yet. They can be called whatever you like.

To add the keys you’ll need to grab them from your Stripe account. For testing purposes, I recommend setting up a new testing account altogether. You should be able to find your connect `client_id` key within [dashboard.stripe.com/account/applications/settings](https://dashboard.stripe.com/account/applications/settings).

### Adding test keys to your app

If you’re new to Rails, encrypted credentials it’s worth googling a bit to understand why and how they work the way they do. Why this topic isn’t in the main Rails documentation is beside me. ‍Maybe I’ll create a pull request to add it.

At its simplest form Rails 5.2+ comes with a command we can run to decrypt/generate credential files that are YAML files.

```bash
$ rails credentials:edit

```

Running this command will probably throw an error like:

```bash
No $EDITOR to open file in. Assign one like this:

EDITOR="mate --wait" bin/rails credentials:edit

For editors that fork and exit immediately, it's important to pass a wait flag,
otherwise, the credentials will be saved immediately with no chance to edit.

```

This means we need to pass a code editor that will allow us to open a decrypted file that gets generated. I’m using VS code so I’ll pass the following:

```bash
$ EDITOR="code --wait" rails credentials:edit

```

This should open a new window in Visual Studio Code with some YAML code within. Here’s where you can add your keys. I believe you can add `export EDITOR="code --wait` to your `.bash_profile` or `.zshrc` file to make this happen automatically in the future.

The file that opens is considered your “production” credentials file. Meaning if you ship the code to a live server, Rails will look here assuming it’s in production mode. If you’d rather generate separate development and production keys you totally can. In fact, I’ll do just that for this tutorial. To do this you provide the proper environment you’re after with a flag.

```bash
$ EDITOR="code --wait" rails credentials:edit --environment=development

```

You can pass whatever environment you like there. Passing no environment means it’s the production environment by default.

I’ll pass the following:

```bash
$ EDITOR="code --wait" rails credentials:edit --environment=development

```

Inside the file that opens you’ll likely see some dummy yaml:

```yaml
# aws:
# access_key_id: 123
# secret_access_key: 345

```

We’ll follow the same formatting here and add our Stripe credentials (the test ones, not the live ones). Grab those from your account and put them here.

```yaml
stripe:
  connect_client_id: ca_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  publishable_key: pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  private_key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

```

As of 2020:

- You can find your stripe connect client id within your [Account settings](https://dashboard.stripe.com/account/applications/settings)
- 

You can find your publishable key and private key in the [developers area](https://dashboard.stripe.com/test/apikeys)

With those keys saved, closing the file will encrypt the code so it’s much more secure. When you create credential YAML files a `master.key` file is created of which you’ll need to share with only those you trust. They can use the key to be able to decrypt the initial credentials file. This applies to each environment. So in our case, we have a `development.key` that was generated within `config/credentials`.

This all seems complicated but I finally got used to it after a few uses.

### Configuring the Stripe gem

We installed the Stripe gem but haven’t really authorized it yet. Since we just added our credentials it’s an easy addition. You’ll create a new initializer called `stripe.rb` within `config/initializers/` next.

```ruby
# config/initializers/stripe.rb
Rails.configuration.stripe = {
  :publishable_key => Rails.application.credentials.dig(:stripe, :public_key),
  :secret_key => Rails.application.credentials.dig(:stripe, :private_key)
}
Stripe.api_key = Rails.application.credentials.dig(:stripe, :private_key)

```

#### Making the User model omniauthable

Luckily for us Devise has a plan for omniauth integration already in place. We just need to declare the provider we added in the previous step like so.

```ruby
# app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:stripe_connect]
  has_many :projects, dependent: :destroy
end

```

#### Customizing the omniauth callback

To add the necessary routing and controllers for Stripe to connect successfully to our app we need to provide a game plan with a new controller and route option.

Starting with the routes we extend our existing `devise_for` method by declaring a new controller for the omniauth\_callback controller explicitly.

```ruby
# config/routes.rb
# change this `devise_for` line to the following

devise_for :users, controllers: { omniauth_callbacks: "omniauth_callbacks" }

```

Here we tell Devise what controller to expect. Doing this means we need to create a new controller named `omniauth_callbacks_controller.rb` within `app/controllers`

Based on the request we can pluck parameters out and update attributes on a given user’s account. Right now we need to define what those are to make Stripe Connect function. We added some fields you might see below previously on the `users` database table. Here is where they come into play.

```ruby
# app/controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def stripe_connect
    auth_data = request.env["omniauth.auth"]
    @user = current_user
    if @user.persisted?
      @user.provider = auth_data.provider
      @user.uid = auth_data.uid
      @user.access_code = auth_data.credentials.token
      @user.publishable_key = auth_data.info.stripe_publishable_key
      @user.save

      sign_in_and_redirect @user, event: :authentication
      flash[:notice] = 'Stripe Account Created And Connected' if is_navigational_format?
    else
      session["devise.stripe_connect_data"] = request.env["omniauth.auth"]
      redirect_to root_path
    end
  end

  def failure
    redirect_to root_path
  end
end

```

This takes care of the request logic but we still need a place to point users who want to authenticate with Stripe Connect initially. Doing so means defining a dynamic URL based on a few parameters about our own Stripe Connect account. I’ll add a new helper to encapsulate this logic:

```ruby
# app/helpers/application_helper.rb
module ApplicationHelper
  def stripe_url
    "https://connect.stripe.com/oauth/authorize?response_type=code&client_id=#{Rails.application.credentials.dig(:stripe)[:connect_client_id]}&scope=read_write"
  end
end

```

and another to check if a user can receive payments altogether we will add to the User model:

```ruby
class User < ApplicationRecord
  ...
    def can_receive_payments?
    uid? && provider? && access_code? && publishable_key?
  end

end

```

This checks for each field’s presence as outlined by their names in the database.

### Adding the view layer

With this logic in place, we can start to mold how the flow of oAuth-ing users with Stripe Connect will take place.

To make our lives easier in the view layer I added a couple of helpers to extract some logic out:

```ruby
module ApplicationHelper
  ...
  def stripe_url
    "https://connect.stripe.com/oauth/authorize?response_type=code&client_id=#{Rails.application.credentials.dig(:stripe)[:connect_client_id]}&scope=read_write"
  end

  def stripe_connect_button # add this method
    link_to stripe_url, class: "btn-stripe-connect" do
      content_tag :span, "Connect with Stripe"
    end
  end
end

```

I used the view within the `devise` views folder called `registrations/edit.html.erb` as a home for the Stripe authentication button. The UI here is lackluster but it’s enough for you to build upon.

```html
<!-- app/views/devise/registration/edit.html.erb -->
<div class="container flex flex-wrap items-start justify-between mx-auto">
  <div class="w-full lg:w-1/2">
  <h2 class="pt-4 mb-8 text-4xl font-bold heading">Account</h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>

    <%= render "devise/shared/error_messages", resource: resource %>

    <div class="mb-6">
      <%= f.label :username, class:"label" %>
      <%= f.text_field :username, autofocus: true, class:"input" %>
    </div>

    <div class="mb-6">
      <%= f.label :name, class:"label" %>
      <%= f.text_field :name, class:"input" %>
    </div>

    <div class="mb-6">
      <%= f.label :email, class:"label" %>
      <%= f.email_field :email, autocomplete: "email", class:"input" %>
    </div>

    <div class="mb-6">
      <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
        <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
      <% end %>
    </div>

    <div class="mb-6">
      <%= f.label :password, class:"label" %>
      <%= f.password_field :password, autocomplete: "new-password", class:"input" %>
      <p class="pt-1 text-sm italic text-grey-dark"> <% if @minimum_password_length %>
        <%= @minimum_password_length %> characters minimum <% end %> (leave blank if you don't want to change it) </p>

    </div>

    <div class="mb-6">
      <%= f.label :password_confirmation, class: "label" %>
      <%= f.password_field :password_confirmation, autocomplete: "new-password", class: "input" %>
    </div>

    <div class="mb-6">
      <%= f.label :current_password, class: "label" %>
      <%= f.password_field :current_password, autocomplete: "current-password", class: "input" %>
      <p class="pt-2 text-sm italic text-grey-dark">(we need your current password to confirm your changes)</p>
    </div>

    <div class="mb-6">
      <%= f.submit "Update", class: "btn btn-default" %>
    </div>
    <% end %>

    <hr class="mt-6 mb-3 border" />

    <h3 class="mb-4 text-xl font-bold heading">Cancel my account</h3>

    <div class="flex items-center justify-between">
      <div class="flex-1"><p class="py-4">Unhappy?</p></div>

      <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: "btn btn-default" %>
    </div>
  </div>

<div class="w-full text-left lg:pl-16 lg:w-1/2">
  <div class="p-6 mt-10 text-gray-900 bg-white rounded shadow-lg">
    <% unless resource.can_receive_payments? %>
      <h4 class="mb-6 text-xl font-semibold leading-none text-gray-900">You wont be able to sell items until you register with Stripe!</h4>
      <%= stripe_button %>
    <% else %>
      <h4 class="mb-6 text-xl font-semibold leading-none text-gray-900">Successfully connected to Stripe ✅</h4>
    <% end %>
  </div>
</div>

```

I normally used a content\_for block here within my template but decided to customize this view a bit. At the bottom is the main area to pay attention to. Here we add a conditional around if a user already has connected with Stripe or not. If so we display a primitive success message.

To maintain branding I’ve added a Stripe-styled button that users of Stripe might identify with more than a generic alternative. This gives a bit more sense of trust in terms of user experience. Doing this requires some CSS of which I’ve added to our `_buttons.scss` partial (part of my kickoff\_tailwind) template.

```css
/* app/javascript/stylesheets/components/_buttons.scss */
.btn-stripe-connect {
  display: inline-block;
  margin-bottom: 1px;
  background-image: -webkit-linear-gradient(#28A0E5, #015E94);
  background-image: -moz-linear-gradient(#28A0E5, #015E94);
  background-image: -ms-linear-gradient(#28A0E5, #015E94);
  background-image: linear-gradient(#28A0E5, #015E94);
  -webkit-font-smoothing: antialiased;
  border: 0;
  padding: 1px;
  height: 30px;
  text-decoration: none;
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
  -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
  -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
  cursor: pointer;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.btn-stripe-connect span {
  display: block;
  position: relative;
  padding: 0 12px 0 44px;
  height: 30px;
  background: #1275FF;
  background-image: -webkit-linear-gradient(#7DC5EE, #008CDD 85%, #30A2E4);
  background-image: -moz-linear-gradient(#7DC5EE, #008CDD 85%, #30A2E4);
  background-image: -ms-linear-gradient(#7DC5EE, #008CDD 85%, #30A2E4);
  background-image: linear-gradient(#7DC5EE, #008CDD 85%, #30A2E4);
  font-size: 14px;
  line-height: 30px;
  color: white;
  font-weight: bold;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
  -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
  -moz-border-radius: 3px;
  -webkit-border-radius: 3px;
  border-radius: 3px;
}
.btn-stripe-connect span:before {
  content: '';
  display: block;
  position: absolute;
  left: 11px;
  top: 50%;
  width: 23px;
  height: 24px;
  margin-top: -12px;
  background-repeat: no-repeat;
  background-size: 23px 24px;
}
.btn-stripe-connect:active {
  background: #005D93;
}
.btn-stripe-connect:active span {
  color: #EEE;
  background: #008CDD;
  background-image: -webkit-linear-gradient(#008CDD, #008CDD 85%, #239ADF);
  background-image: -moz-linear-gradient(#008CDD, #008CDD 85%, #239ADF);
  background-image: -ms-linear-gradient(#008CDD, #008CDD 85%, #239ADF);
  background-image: linear-gradient(#008CDD, #008CDD 85%, #239ADF);
  -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1);
  -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1);
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1);
}
.btn-stripe-connect:active span:before {} .btn-stripe-connect.light-blue {
  background: #b5c3d8;
  background-image: -webkit-linear-gradient(#b5c3d8, #9cabc2);
  background-image: -moz-linear-gradient(#b5c3d8, #9cabc2);
  background-image: -ms-linear-gradient(#b5c3d8, #9cabc2);
  background-image: linear-gradient(#b5c3d8, #9cabc2);
  -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
  -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
}
.btn-stripe-connect.light-blue span {
  color: #556F88;
  text-shadow: 0 1px rgba(255, 255, 255, 0.8);
  background: #f0f5fa;
  background-image: -webkit-linear-gradient(#f0f5fa, #e4ecf5 85%, #e7eef6);
  background-image: -moz-linear-gradient(#f0f5fa, #e4ecf5 85%, #e7eef6);
  background-image: -ms-linear-gradient(#f0f5fa, #e4ecf5 85%, #e7eef6);
  background-image: linear-gradient(#f0f5fa, #e4ecf5 85%, #e7eef6);
  -moz-box-shadow: inset 0 1px 0 #fff;
  -webkit-box-shadow: inset 0 1px 0 #fff;
  box-shadow: inset 0 1px 0 #fff;
}
.btn-stripe-connect.light-blue:active {
  background: #9babc2;
}
.btn-stripe-connect.light-blue:active span {
  color: #556F88;
  text-shadow: 0 1px rgba(255, 255, 255, 0.8);
  background: #d7dee8;
  background-image: -webkit-linear-gradient(#d7dee8, #e7eef6);
  background-image: -moz-linear-gradient(#d7dee8, #e7eef6);
  background-image: -ms-linear-gradient(#d7dee8, #e7eef6);
  background-image: linear-gradient(#d7dee8, #e7eef6);
  -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.05);
  -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.05);
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.05);
}
.btn-stripe-connect.dark {
  background: #252525;
  background: rgba(0, 0, 0, 0.5) !important;
}
/* Images*/

.btn-stripe-connect span:before,
.btn-stripe-connect.blue span:before {
  background-image: url("data:image/png;base64,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");
}
.btn-stripe-connect.light-blue span:before {
  background-image: url("data:image/png;base64,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");
}
/* Retina support */

@media only screen and (-webkit-min-device-pixel-ratio: 1.5),
only screen and (min--moz-device-pixel-ratio: 1.5),
only screen and (min-device-pixel-ratio: 1.5) {
  .btn-stripe-connect span:before,
  .btn-stripe-connect.blue span:before {
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAwCAYAAABuZUjcAAAKRGlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/wRDtFgAAAIbklEQVRoBdVZa5BURxU+fZ9z57mzs7PvF4i7srAQSCifMVDERC0jYlzUlJalKeGPlCnL/NEfywpWacoiVZRVJIYfGjGUu5bxj5qHFSAYyQOBEsJzYSHDvnd2dp535j66PX1vNgsULDPs1cr2Vs+9e7v79NfnnnP663MJYwwWYxEWI2iOedEClxabxgkBwjEvOuA9PQOOlSw64JMr4vK8GidYYMcOES4tVSEAAZ8FAUqon1GiAJEEEG0CjFB8cTaxZUMAo1gEqQA0UABprAjPbrUwXnkesgqKP8CBk5vDIenrE+BKmwI+MawA1MbCkdV10cBDflXuVmSxQRbFkCAQZ9U2ZTaONyxKcyXDHjMs83ImV3rz6njmDRPMUZB80zAJOuvvsflkXpTP7DrWyeXcYCqk75AEieawrEoty1vrvlcV0ja3VQdb1rVUQVd9EFqqNIj5ZfDJooPBsCnohq2ldDMynC42XZnW7z09lu25lMxDMl34y0gyvTsBwyewc84Z4MEPpWIzF/MBcLLtNzJISmxZU+PmWETbtqGzfvVja5uguyF02+kCIEJUk6Ex4oMV9XP9ZnQT/nZ24it7XrtoJ5LZ7SjAM+Bg2+0ckAOcbBkQIaZFVzY1bGurjezYfn87PNQZ5+13ZaQRXMzH26Lg8ymfUokQdAR59INOc53GQ6q/Jiiua6oJ7+h9uAPua47cHeLrwHEmQRmTGLHV6x4v+JYwWsOFCGRDn6RKem1rPPrkN9Y0uqAXLN4VwCgjYGEE8rBgMAjwKsF9S9WgLa9qjYcf+Po9jXdlGrfC5Wj8Vg0Lf+ZENAFmpGB9TWTLhmUxUD1UDg/gtudRnK+a4RtkgqQyO+RT5LVrmiLgJcN19gcGNojUWriS5yRQm7pcBTc/vyCKdW1RrWwzOTiYhGf+dRUmcgZosgDVfgWaMCS2V2tO+OzG0MiVjdUwiFiYm9a7O4kJAoZEooV9H4T0O0ofODkKr5+6+nY6V3heVZQpv6ZWaz55qSJJnXjtUBW5pT7k8xeK5u+B0PQdBVbQgTLq9HbQYthyNVSmTT6A/nB0aGpF0K99+trY1F7TNI9PZGXkKUVRtYjGZCIOV1dHR4Ynz8FSLV8BrjK6uiAlpLcmco1ipmgpAaU8rfesboCuumBg31uJbx6+qH0uX9D/em0i85xFhaslKZKA8/82RtYDhd/1MkCuBnjxrLgKB0EQSb5oWO+9O1bZrsy3+Kc3dcH+b99b07NuyXe6P9r8z/am+C9lkuqCjo4qGGkQES76qJcuz/2GOlUoFuVsQS+98frlaSeq8Gkqqctrg7Dz853wwrfugUfXtj3W3tJ8oCletRUEXy1SCSSYHhdu41gFqILcZCrzwkvnJmE0U3JtHefiL7eS2l7th11f7IQ9j65aVh+r+nlzbd2TELJrHPLmIXZX3wyBX8MTQMm8PJ0u9Pe9chGQYy9omvXouHu/thJqI+Ef1sZDm0AMBmfPiQsSPDuY2zhWwSH5ISU5Pjm98x9nRo7+7JVBB3wl5nJz35Vo/z/esBQUVf2+QlkD9Aw42/Ts3Au7ushdAhQ5UzJoOjE+OrV9/1tDR7cNnIax7N2bDX9nm1bUQXdz9Rp/MLwRoqAtDOzcaO7rvDrAWW8vhcatWVNjF6cmJre9embkz1947h3YfXgIUgVzblQldxgFH0ZOr/qULwM15k4Zlci4Vd9ZU5ltY71oObHBnBFQBidmUk8kEsOP7Hntwqsb974NfS8PAh7LKoo23Hw+2R4FQcSzKlDPgFOEyf8kx3HW94kQ7xJgRRdAJG7CyIWxgiXNUN0+k5nJLN83k3n8D8eHN3+1ux5+8uBHIKiWt1G1Rn3IJkiUCcQzU3G0h9qWHMeJdoSrwtr9dl6I6DNjFwRRyxiKnStSqkPJPsGSmZ+mp1P9z2dzOy3Klj31yMdmX9S8V75APEsomMZwT9fz9i6vkW9AvEgQyqrBQM2Dq9rrD0gCgXfHA0jpjIRm2Zcw+3CR2tZl27SnMZFSZ1lWcRwZITeDckresAEXaoKwwBh7/WQubgTOQj5BVjdv7KiBJz7bztMNcHIk03JiONNyfiK/ntv2VMHAMx6BjpoA/Gj9Emdjul7W7e6TeQNDK9WJLRm361P5c1drEmAaymaYoXpfjZoiOk7FHWuh5dxEHmzLHiXM9oyTz9FawRZw65f5yyzXBMpd0JGhFKB5nSwRMVvumDv2cxm4m1f5X4AuWhRePDUOtqEPQJVVGfWcBz1ahmPlTlxzqaJLquYZU1HTvjcTMD6dOULM0n+g5nKposHzdWbo7FgEkDBviWlYx++53XtQ33kvDU8dHAJm6L8usdwEZn09S3qiPed5lcCSLUpI0eEA8620zLbDl6bh8T+egkI+/7Rl6kegcTSPst1QUKaM+brhrjnF2yUQJNxnrGMnR7KbTw5nYFVjyAl98w2+VdvVlA67Dw3BgROjAKa+yyrpz0BKTbJnez1NT6AKrrnA1bEi1av2v3xaiL90dnxL2Kc0rsXc4WpcQEc8AEtiGrRiejmK6WWeMDIxtVwwKExijB5KFuBYIg1cy8dx0dTQ/yQVc78yBXMIqJ5i/VvvkqHdSjXuM/THKy7w2LQJ6fpJms38QiHGvlzBt+RwJv2JQ2elbjyRtjIi1AIRMAsKPuQduHVzr2YW+kIBE5BTwOzzxLKOiMX8QVuWh00IpqD+S0WHtLlzefpLBOZo/IYvEqQPnTX5dxmy4xookqaCjRuT4mMi8g3bxs2KCkj3GFj4+QSzA0RkeskU8iCJeUiBDv09Jt8OPEV6k7DlP3gxxh/dAPymPh/Kf5d897dIOd9P7H8oEd4G1JV8wPGbRadx52sgLmrRAZ99EZ5+LZgV+v+4Llrg/wX6HRCxgvzAAwAAAABJRU5ErkJggg==");
  }
  .btn-stripe-connect.light-blue span:before {
    background-image: url("data:image/png;base64,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");
  }
}

```

You can grab this code straight from Stripe [here](https://stripe.com/newsroom/brand-assets).

### Add a redirect URI

Once the integration takes place on Stripe’s end we need a way to bounce users back to our app. Stripe offers this feature within your Connect settings dashboard. Make sure you’re viewing Test data and add the following URI within the Redirects area:

```bash
http://localhost:3000/users/auth/stripe_connect/callback

```

This URL exists in our app presently thanks to the gem we installed, devise, and the initial controller logic we set up before this.

While you are in your settings it makes sense to provide some Branding details as well to avoid confusion coming up. I’ll call our application “Back My Idea”.

![stripe-connect-branding](https://i0.wp.com/i.imgur.com/z12oBcf.png?ssl=1)

With the Stripe Connect button in place inside our view, we can click on it to land at a page that looks like below. I suggest creating a new user account in the app so you can tie it to a new account instead of your existing if you prefer. It’s not a huge deal if you don’t at first.

The view below assumes you’re signed out of any Stripe account upon visiting. If your account is the master Stripe account (the one containing the `client_id`) you will be prompted to log out or switch users.

![Connect With Stripe Prompt](https://i2.wp.com/i.imgur.com/Lpl5Jxv.png?ssl=1)

From here you could fill out the details of the giant form about your account but since we are in development mode, notice the call to action at the very top of the page. We can bypass the form altogether.

If you skip the form and all goes well you should be redirected back to the app with a success message in place! Sweeeeet!

Here are the console logs as proof. I omitted any keys just to be safe.

```bash
Started GET "/users/auth/stripe_connect/callback?scope=read_write&code=ac_XXXXXXXXXXXXXXXXXXXXXXXX" for 127.0.0.1 at 2020-01-28 16:26:39 -0600
I, [2020-01-28T16:26:39.694445 #32159] INFO -- omniauth: (stripe_connect) Callback phase initiated.
Processing by OmniauthCallbacksController#stripe_connect as HTML
  Parameters: {"scope"=>"read_write", "code"=>"ac_XXXXXXXXXXXXXXXXXXXX"}
  User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/omniauth_callbacks_controller.rb:4:in `stripe_connect'
   (0.1ms) begin transaction
  ↳ app/controllers/omniauth_callbacks_controller.rb:10:in `stripe_connect'
  User Update (0.4ms) UPDATE "users" SET "provider" = ?, "uid" = ?, "access_code" = ?, "publishable_key" = ?, "updated_at" = ? WHERE "users"."id" = ? [["provider", "stripe_connect"], ["uid", "acct_XXXXXXXXXXXXXX"], ["access_code", "sk_test_XXXXXXXXXXXXXXXXXXXXXXXX"], ["publishable_key", "pk_test_XXXXXXXXXXXXXXXXXXXXXXXX"], ["updated_at", "2020-01-28 22:26:41.139168"], ["id", 1]]
  ↳ app/controllers/omniauth_callbacks_controller.rb:10:in `stripe_connect'
   (1.0ms) commit transaction
  ↳ app/controllers/omniauth_callbacks_controller.rb:10:in `stripe_connect'
Redirected to http://localhost:3000/
Completed 302 Found in 7ms (ActiveRecord: 1.4ms | Allocations: 3826)

Started GET "/" for 127.0.0.1 at 2020-01-28 16:26:41 -0600
Processing by ProjectsController#index as HTML
  Rendering projects/index.html.erb within layouts/application

```

Navigating to your account settings you should see the updated messaging we added for connected accounts.

# Subscriptions/Backings

On the front-end, we need a way for projects to be backable. Doing so requires some form of subscription logic under the hood for each plan. We’ll add a way for a given user to configure what tiers they can offer and for how much (much like Patreon).

We’ll envoke the `nested_attributes` strategy for new projects that we’ll tie to a new model called `Perk`. Each `Perk` will have a price, title, description to start with. A user who hasn’t hosted the project can subscribe to any perk respectively.

### Create the Perk model

We’ll start with the following fields:

- `title`
- `amount`
- `description`
- `quantity` – Providing limited quantities affords higher purchases usually
- `project_id` – Each perk needs a parent Project to associate to

```bash
$ rails g model Perk title:string amount:decimal description:text quantity:integer project:references

```

Once that generates the new migration we need to amend it a tad. The `amount` column needs a few constraints and sane defaults since we are dealing with currency.

```ruby
class CreatePerks < ActiveRecord::Migration[6.0]
  def change
    create_table :perks do |t|
      t.decimal :amount, precision: 8, scale: 2, default: 0
      t.text :description
      t.integer :quantity
      t.references :project, null: false, foreign_key: true

      t.timestamps
    end
  end
end

```

- Precision refers to how many numbers to allow. In this case, it can be (999,999.99) but you’re welcome to tweak that.
- Scale refers to how many digits to the right of the decimal point should be present. For most currency that’s 2.

```bash
$ rails db:migrate

```

### Adding Perks to Project Model

We need to associate perks to projects as well as define the nested characteristics of the association. Doing so results in the following inside my `project.rb` file

```ruby
# app/models/project.rb
class Project < ApplicationRecord
  belongs_to :user
  has_rich_text :description
  has_one_attached :thumbnail
  has_many :comments, as: :commentable

  # add the two lines below
  has_many :perks, dependent: :destroy
  accepts_nested_attributes_for :perks, allow_destroy: true, reject_if: proc { |attr| attr['title'].blank? }
  ....

end

```

Here we say a Project can have multiple perks. The `dependent: :destroy` declaration means that if a project is deleted, the perks will be also.

The `accepts_nested_attributes_for` line signifies the ability for our perks to living within our project structure now. We need to extend our project forms to include the fields we added when generating the model. A perk can be destroyed independently via the `allow_destroy: true` declaration and finally, we validate a perk by not saving if the title field is blank on each new perk.

### The Perk Model

The hard work of the perk model is essentially already done thanks to the generator we ran. Rails added `belongs_to :project` automatically within the `perk.rb` file.

```ruby
# app/models/perk.rb
class Perk < ApplicationRecord
  belongs_to :project
end

```

### Whitelisting the new perk fields

On the Projects controller, we need to whitelist the new fields we just created in order to save any really. We can do so pretty easily:

```ruby
# app/controllers/projects_controller.rb

class ProjectsController < ApplicationController
  ...

  private
    ...
    def project_params
      params.require(:project).permit(:title, :donation_goal, :description, :thumbnail, perks_attributes: [:id, :_destroy, :title, :description, :amount, :quantity])
    end
end

```

Here I’ve added a new `perks_attributes` parameter which points to an array of additional fields. Notice the `:_destroy` method as well. That will allow us to delete individual perks as necessary.

### Updating the views

Our new project form looks like this currently:

```html
<!-- app/views/projects/_form.html.erb-->
<%= form_with(model: project, local: true, multipart: true) do |form| %>
  <% if project.errors.any? %>
    <div id="error_explanation" class="bg-white p-6 rounded text-red-500 mb-5">
      <h2 class="text-red-500 font-bold"><%= pluralize(project.errors.count, "error") %> prohibited this project from being saved:</h2>

      <ul>
        <% project.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="mb-6">
    <%= form.label :thumbnail, "Project thumbnail", class: "label" %>
    <%= form.file_field :thumbnail %>
  </div>

  <div class="mb-6">
    <%= form.label :title, class: "label" %>
    <%= form.text_field :title, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :donation_goal, class: "label" %>
    <%= form.text_field :donation_goal, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :description, class: "label" %>
    <%= form.rich_text_area :description, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.submit class: "btn btn-default" %>
  </div>
<% end %>

```

Nothing out of the ordinary right?

To make this a little more dynamic I’d like to reach for [Stimulus.js](https://dev.to/scottw/stimulus-2jio) which is a nice JavaScript library that doesn’t take over your entire front-end but rather sprinkles in more dynamic bits of JavaScript where you need them.

The general idea I’d like to introduce is added a new nested perk on click each time we need more than 1. JavaScript is rather necessary to help make this type of user experience more acceptable.

### Installing Stimulus JS

Because Rails already ships with Webpacker, we can install Stimulus quite easily.

```bash
$ bundle exec rails webpacker:install:stimulus

```

This command should do the heavy lifting we are after. Once it’s complete you should have a new `controllers` directory within `app/javascript` and a few additions to your `application.js` file.

We’ll only be using a bit of Stimulus but I encourage you to explore more. It’s a fantastic library that works really well with Rails.

Let’s create our first controller. I’ll delete the `hello_controller.js` that was created altogether.

```javascript
// app/javascript/controllers/nested_form_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = ["add_perk", "template"]

  add_association(event) {
    event.preventDefault()
    var content = this.templateTarget.innerHTML.replace(/TEMPLATE_RECORD/g, new Date().valueOf())
    this.add_perkTarget.insertAdjacentHTML('beforebegin', content)
  }

  remove_association(event) {
    event.preventDefault()
    let perk = event.target.closest(".nested-fields")
    perk.querySelector("input[name*='_destroy']").value = 1
    perk.style.display = 'none'
  }
}

```

Here we define a new controller and add some targets and methods. I won’t be going too deep on the ins and outs of Stimulus but this should be enough to get things rolling.

- Targets: refer to things you want to target…
- You can access targets using the `this` keyword in object notation. In our case that might be `this.add_perkTarget`. 

In our view partial we can extend the form like so:

```html
<!-- app/views/projects/_form.html.erb-->
<%= form_with(model: project, local: true, multipart: true) do |form| %>
  <% if project.errors.any? %>
    <div id="error_explanation" class="p-6 mb-5 text-red-500 bg-white rounded">
      <h2 class="font-bold text-red-500"><%= pluralize(project.errors.count, "error") %> prohibited this project from being saved:</h2>

      <ul>
        <% project.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="mb-6">
    <%= form.label :thumbnail, "Project thumbnail", class: "label" %>
    <%= form.file_field :thumbnail %>
  </div>

  <div class="mb-6">
    <%= form.label :title, class: "label" %>
    <%= form.text_field :title, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :donation_goal, class: "label" %>
    <%= form.text_field :donation_goal, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :description, class: "label" %>
    <%= form.rich_text_area :description, class: "input" %>
  </div>

  <div class="my-10">
    <h3 class="text-2xl">Perks</h3>

    <div data-controller="nested-form">
      <template data-target='nested-form.template'>
        <%= form.fields_for :perks, Perk.new, child_index: 'TEMPLATE_RECORD' do |perk| %>
          <%= render 'perk_fields', form: perk %>
        <% end %>
      </template>

      <%= form.fields_for :perks do |perk| %>
        <%= render 'perk_fields', form: perk %>
      <% end %>

      <div data-target="nested-form.add_perk">
        <%= link_to "Add Perk", "#", data: { action: "nested-form#add_association" }, class: "btn btn-white" %>
      </div>
    </div>
  </div>

  <div class="mb-6">
    <%= form.submit class: "btn btn-default" %>
  </div>
<% end %>

```

Our form now was a few more pieces of logic that use Stimulus + Rails to make it a bit more dynamic. You can create however many new Perks you want with a single click. I created a second partial to help render the subfields for perks

```html
<!-- app/views/projects/_perk_fields.html.erb -->
<div class="p-6 mb-4 border rounded nested-fields">

  <%= form.hidden_field :_destroy %>

  <div class="mb-6">
    <%= form.label :title %>
    <%= form.text_field :title, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :description %>
    <%= form.text_area :description, class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :amount %>
    <%= form.text_field :amount, placeholder: "0.00", class: "input" %>
  </div>

  <div class="mb-6">
    <%= form.label :quantity %>
    <%= form.text_field :quantity, placeholder: "10", class: "input" %>
  </div>

  <%= link_to "Remove", "#", data: { action: "click->nested-form#remove_association" }, class: "text-white underline text-sm" %>
</div>

```

If all goes smoothly you should be able to create a new project with perks and save it. We still need to render the project perks in the project show view so let’s update that next.

### Project Show view

The show view of a project can be either active or inactive based on the status we set up before. The main show template gets some simple code to account for this:

```html
<!--app/views/projects/show.html.erb-->
<% if @project.active? %>
  <%= render "active_project", project: @project %>
<% else %>
  <%= render "inactive_project", project: @project %>
<% end %>

```

Active projects will have more data since it’s what most users will interact with.

**The active view**

```html
<!-- app/views/projects/active_project.html.erb-->
<div class="container relative p-6 mx-auto text-gray-900 bg-white rounded-lg lg:p-10">
  <div class="flex flex-wrap items-center justify-between w-full pb-4 mb-8 border-b-2 border-gray-200">
    <div class="flex flex-wrap items-start justify-between w-full pb-4 mb-4 border-b-2 border-gray-200">
      <div class="flex-1">
        <h1 class="text-3xl font-bold leading-none text-gray-800"><%= project.title %></h1>
        <p class="text-sm italic text-gray-500">Created by <%= project.user.name ||=
      project.user.username %></p>
      </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none"><%= number_to_currency(project.current_donation_amount) %></p>
      <p class="text-sm text-gray-500">pledged of <%= number_to_currency(project.donation_goal) %></p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">200</p>
      <p class="text-sm text-gray-500">backers</p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none"><%= distance_of_time_in_words_to_now(project.expires_at) %></p>
      <p class="text-sm text-gray-500">to go</p>
    </div>
  </div>

  <div class="flex flex-wrap items-start justify-between w-full mb-6">
    <div class="w-full lg:w-3/5">
    <% if project.thumbnail.present? %>
      <%= image_tag project.thumbnail, class: "rounded" %>
    <% else %>
      <div class="flex items-center justify-center p-8 text-center bg-gray-100 rounded">
        <div class="">
          <p class="text-xs font-bold text-gray-600 uppercase">PROJECT</p>
          <h3 class="text-2xl text-black"><%= project.title %></h3>
        </div>
      </div>
    <% end %>
      <div class="my-6">
        <%= render "comments/comments", commentable: project %>
        <%= render "comments/form", commentable: project %>
      </div>
    </div>

    <div class="w-full mt-6 lg:pl-10 lg:w-2/5 lg:mt-0">
      <div class="mb-6">
        <p class="text-sm font-semibold text-gray-500 uppercase">Description</p>
        <%= project.description %>
      </div>

      <h3 class="text-2xl text-gray-900">Back this idea</h3>

      <% project.perks.each do |perk| %>
        <div class="p-4 mb-6 bg-gray-100 border rounded">
          <h4 class="text-lg font-bold text-center text-gray-900"><%= perk.title %></h4>
          <p class="pb-2 mb-2 text-sm italic text-center border-b"><%= perk.quantity %> left</p>
          <div class="py-2 text-gray-700">
            <%= simple_format perk.description %>
          </div>

          <% if user_signed_in? && perk.project.user_id == current_user.id %>
            <em class="block text-sm text-center">Sorry, You can't back your own idea</em>
          <% else %>
          <%= link_to "Back this idea for #{number_to_currency(perk.amount)} /mo", new_subscription_path(amount: perk.amount, project: project), class: "btn btn-default btn-expanded" %>
          <% end %>
        </div>
      <% end %>
    </div>
  </div>

 <% if admin? || author_of(project) %>
    <div class="absolute top-0 right-0 mt-4 mr-4">
      <%= link_to 'Edit', edit_project_path(project), class: "btn btn-sm btn-outlined btn-default" %>
      <%= link_to 'Delete', project_path(project), method: :delete, class: "btn btn-sm btn-outlined btn-default", data: { confirm: "Are you sure?" } %>
    </div>
  <% end %>
</div>

```

For now we have the UI for what we need with regards to perks and the project. There are a few areas to improve upon but we’ll focus on those in a bit.

**The inactive view:**

We won’t render any way to back the project and show obvious signs of inactivity.

```html
<!-- app/views/projects/inactive_project.html.erb-->
<div class="container relative p-6 mx-auto text-gray-900 bg-white border-2 border-red-500 rounded-lg lg:p-10">
  <div class="flex flex-wrap items-center justify-between w-full pb-4 mb-8 border-b-2 border-gray-200">
    <div class="flex flex-wrap items-start justify-between w-full pb-4 mb-4 border-b-2 border-gray-200">
      <div class="flex-1">
        <span class="px-2 py-1 text-xs font-semibold text-white bg-red-500 rounded-lg">Inactive</span>

        <h1 class="mt-4 text-3xl font-bold leading-none text-gray-800"><%= project.title %></h1>
        <p class="text-sm italic text-gray-500">Created by <%= project.user.name ||=
      project.user.username %></p>
      </div>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none"><%= number_to_currency(project.current_donation_amount) %></p>
      <p class="text-sm text-gray-500">pledged of <%= number_to_currency(project.donation_goal) %></p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">200</p>
      <p class="text-sm text-gray-500">backers</p>
    </div>

    <div class="w-full mb-4 lg:w-1/5 lg:mb-0">
      <p class="m-0 text-xl font-semibold leading-none">Expired</p>
      <p class="text-sm text-gray-500"><%= time_ago_in_words(@project.expires_at) %> ago</p>
    </div>
  </div>

  <div class="flex flex-wrap items-start justify-between mb-6">
    <div class="w-full lg:w-3/5">
    <% if project.thumbnail.present? %>
      <%= image_tag project.thumbnail, class: "rounded" %>
    <% else %>
      <div class="flex items-center justify-center p-8 text-center bg-gray-100 rounded">
        <div class="">
          <p class="text-xs font-bold text-gray-600 uppercase">PROJECT</p>
          <h3 class="text-2xl text-black"><%= project.title %></h3>
        </div>
      </div>
    <% end %>
    <div class="my-6">
      <%= render "comments/comments", commentable: project %>
      <p>Comments are closed for inactive projects</p>
    </div>
    </div>
    <div class="w-full mt-6 lg:pl-10 lg:w-2/5 lg:mt-0">
      <p class="text-sm font-semibold text-gray-500 uppercase">Description</p>
      <%= project.description %>
    </div>
  </div>

  <% if admin? || author_of(project) %>
    <div class="absolute top-0 right-0 mt-4 mr-4">
      <%= link_to 'Edit', edit_project_path(project), class: "btn btn-sm btn-outlined btn-default" %>
      <%= link_to 'Delee', project_path(project), method: :delete, class: "btn btn-sm btn-outlined btn-default", data: { confirm: "Are you sure?" } %>
    </div>
  <% end %>
</div>

```

# Perk subscriptions

Adding subscriptions to each Perk should come pretty standard but with Stripe Connect there are a few more steps and concerns.

### Subscription Controller and routes

We need a way to pass some requests with the necessary data to make our charges work as planned. It makes sense to silo that into a subscription controller with routes.

```ruby
# config/routes.rb

resource :subscription # add this anywhere

```

You may need to reboot your server for these changes to take effect.

We can add a views folder at `app/views/subscriptions` next. Inside we need a few views.

```bash
back-my-idea/app/views/subscriptions
.
├── _form.html.erb
├── new.html.erb
└── show.html.erb

```

The controller is where a lot of the magic happens. Here we need access to the current user, the project data, and the client data (Back My Idea)

With the Pay gem, we can create subscriptions quite easily. From each new payment form, we will pass parameters through for the values we need to create the initial charge and start the subscription.

Here’s the controller at its current state. We need a way to grab information passed through the request upon clicking a “Back this project” button in our views.

```ruby
# app/controllers/subscriptions_controller.rb
class SubscriptionsController < ApplicationController
  def new
    @project = Project.find(params[:project])
  end

  def create
  end

  def destroy
  end
end

```

In the request we have access to the project via params thanks to passing them through to each Perk’s button:

```html
<!-- app/views/projects/_active_project.html.erb-->

<!-- omitted code--->

<h3 class="text-2xl text-gray-900">Back this idea</h3>

<% project.perks.each do |perk| %>
  <div class="p-4 mb-6 bg-gray-100 border rounded">
    <h4 class="text-lg font-bold text-center text-gray-900"><%= perk.title %></h4>
    <p class="pb-2 mb-2 text-sm italic text-center border-b"><%= perk.quantity %> left</p>
    <div class="py-2 text-gray-700">
      <%= simple_format perk.description %>
    </div>
    <%= link_to "Back this idea for #{number_to_currency(perk.amount)} /mo", new_subscription_path(amount: perk.amount, project: project), class: "btn btn-default btn-expanded" %>
  </div>
<% end %>

<!-- omitted code--->

```

With a link\_to method, we can pass information through via the URL so this could equate to something like:

```bash
http://localhost:3000/subscription/new?amount=10.0&project=4

```

From that, we can grab it in the controller and do whatever. Pretty nifty!

Let’s first add our form to the subscription views so we have somewhere to start. Inside `app/views/subscriptions/new.html.erb` I’ll add;

```html
<div class="w-1/2 mx-auto">
  <h3 class="mb-2 text-2xl font-bold text-center">You're about to back <em><%= @project.title %> </em></h3>

  <div class="p-6 border rounded">
    <%= render "form" %>
  </div>
</div>

```

And inside the form partial:

```html
<!-- app/views/subscriptions/_form.html.erb-->
<%= form_with model: current_user, url: subscription_url, method: :post, html: { id: "payment-form" } do |form| %>
  <div>
    <label for="card-element">
      Credit or debit card
    </label>
    <div id="card-element">
      <!-- A Stripe Element will be inserted here. -->
    </div>

    <!-- Used to display Element errors. -->
    <div id="card-errors" role="alert"></div>
  </div>

  <button>Submit Payment</button>
<% end %>

```

We’ll enhance this in a bit but first, we need to tie some JavaScript to it

## JavaScript

There is still a need for capturing Stripe tokens on the frontend and handing them off to the server so we’ll implement Stripe elements as well. Let’s start there:

First, we need to add the JS library to the app. I’ll add it in the application layout file

```html
<!-- app/views/layouts/application.html.erb-->
<!DOCTYPE html>
<html>
  <head>
    <title>Back My Idea</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'https://js.stripe.com/v3/' %>

    <!-- tons more code -->

```

Next, we need some JavaScript to prevent the default form from submitting and tokenizing the charge. We’ll also need to pass the given users’ publishable key through. I’ll create a new `stripe.js` file as a catch-all place to work our magic. This could be better optimized as a Stimulus Controller or more reusable javascript component but I’m not entirely worried about that at this stage.

I’ll approach this in an Object-Oriented way where we can create an ES6 class to allow for the given Project owner’s publishable Stripe key to be passed in as a parameter.

```javascript
class StripeCharges {
  constructor({ form, key }) {
    this.form = form;
    this.key = key;
    this.stripe = Stripe(this.key)
  }

  initialize() {
    this.mountCard()
  }

  mountCard() {
    const elements = this.stripe.elements();

    const style = {
      base: {
        color: "#32325D",
        fontWeight: 500,
        fontSize: "16px",
        fontSmoothing: "antialiased",

        "::placeholder": {
          color: "#CFD7DF"
        },
        invalid: {
          color: "#E25950"
        }
      },
    };

    const card = elements.create('card', { style });
    if (card) {
      card.mount('#card-element');
      this.generateToken(card);
    }
  }

  generateToken(card) {
    let self = this;
    this.form.addEventListener('submit', async (event) => {
      event.preventDefault();

      const { token, error } = await self.stripe.createToken(card);

      if (error) {
        const errorElement = document.getElementById('card-errors');
        errorElement.textContent = error.message;
      } else {
        this.tokenHandler(token);
      }
    });
  }

  tokenHandler(token) {
    let self = this;
    const hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('type', 'hidden');
    hiddenInput.setAttribute('name', 'stripeToken');
    hiddenInput.setAttribute('value', token.id);
    this.form.appendChild(hiddenInput);

    ["brand", "last4", "exp_month", "exp_year"].forEach(function (field) {
      self.addCardField(token, field);
    });
    this.form.submit();
  }

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

// Kick it all off
document.addEventListener("turbolinks:load", () => {
  const form = document.querySelector('#payment-form')
  if (form) {
    const charge = new StripeCharges({
      form: form,
      key: form.dataset.stripeKey
    });
    charge.initialize()
  }
})

```

Inside `application.js` I’ll import this file:

```javascript
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

require("trix")
require("@rails/actiontext")

import "stylesheets/application"
import "controllers"
import "components/stripe";

```

Finally, in the views I’ll add updates to our form partial and new view.

```html
<!-- app/views/subscriptions/new.html.erb-->
<%= form_with model: current_user, url: subscription_url, method: :post, html: { id: "payment-form", class: "stripe-form" }, data: { stripe_key: project.user.publishable_key } do |form| %>
  <div>
    <label for="card-element" class="label">
      Credit or debit card
    </label>

    <div id="card-element">
      <!-- A Stripe Element will be inserted here. -->
    </div>

    <!-- Used to display Element errors. -->
    <div id="card-errors" role="alert" class="text-sm text-red-400"></div>
  </div>

  <button>Back <%= number_to_currency(params[:amount]) %> /mo toward <em><%= project.title %></em></button>
<% end %>

```

This should mount the form using the user’s publishable key on file who owns the project. We’ll be adding fields to this form to send data over the wire that we need such as the amount and customer id.

I’ve added some styles to make it look more presentable as well:

```css
/* app/javascript/stylesheets/components/_forms.scss */

// Stripe form
.stripe-form {
  @apply bg-white rounded-lg block p-6;

  * {
    @apply font-sans text-base font-normal;
  }

  label {
    @apply text-gray-900;
  }

  .card-only {
    @apply block;
  }

  .StripeElement {
    @apply border px-3 py-3 rounded-lg;
  }

  input, button {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    outline: none;
    border-style: none;
    color: #fff;
  }

  input:-webkit-autofill {
    transition: background-color 100000000s;
    -webkit-animation: 1ms void-animation-out;
  }

  input {
    -webkit-animation: 1ms void-animation-out;
  }

  input::-webkit-input-placeholder {
    @apply text-gray-400;
  }

  input::-moz-placeholder {
    @apply text-gray-400;
  }

  input:-ms-input-placeholder {
    @apply text-gray-400;
  }

  button {
    @apply bg-indigo-500 rounded mt-4 text-white cursor-pointer w-full block h-10;
  }

  button:active {
    @apply bg-indigo-600;
  }

  .error {
    svg {
      .base {
        fill: #e25950;
      }

      .glyph {
        fill: #f6f9fc;
      }
    }

    .success {
      .icon .border {
        stroke: #ffc7ee;
      }

      .icon .checkmark {
        @apply bg-indigo-500;
      }

      .title {
        color: #32325d;
      }

      .message {
        color: #8898aa;
      }

      .reset path {
        @apply bg-indigo-500;
      }
    }
  }
}

```

Up until this point I had some demo content in my app. I’d rather start clean from here on out so I’ll reset the database and also delete the accounts on Stripe that were created prior within the Stripe Connect test data area.

```bash
$ rails db:reset

```

### Making sure a user who creates a project has Stripe keys

A nice way to force a user to authenticate with Stripe is to use some conditional logic when they sign up and go to create a new Project. I’ve modified our `projects/new.html.erb` view a tad to render the Stripe button right in line.

```html
<!-- app/projects/new.html.erb-->
<div class="max-w-lg m-auto">
 <% if current_user.can_receive_payments? %>
    <h1 class="mb-6 text-3xl font-bold">Create a Project</h1>
    <%= render 'form', project: @project %>
  <% else %>
  <div class="p-6 bg-white rounded-lg">
    <h1 class="text-3xl font-bold text-gray-900">Authorize Stripe</h1>
    <p class="mb-6 text-gray-900">Before you can configure a new project we need you to connect to Stripe</p>
    <%= stripe_connect_button %>
  </div>
  <% end %>
</div>

```

### Signing in / Sign up fixups

You may notice a new “Sign in with Stripe Connect” link when visiting the sign-up or sign in path. We don’t really want this to be present for Stripe connect so a condition should get the job done. Devise has a partial called `_links.html.erb` you can amend. Look for the block below and add the `unless provider == :stripe_connect` logic.

```html
<!- app/views/devise/_links.html.erb-->
<!-- .. more code here.. -->
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), class: "link link-grey block py-2" unless provider == :stripe_connect %>
  <% end -%>
<% end -%>

```

## Controller Logic

Our front-end is behaving as expected but we still need the serverside component to submit the charge to Stripe successfully. On the `POST` request we’ll hone in the the `create` method in the `subscriptions_controller.rb` file to complete the circle.

In order to use subscriptions with Stripe Connect we also are met with a requirement of creating plans dynamically via Stripe based on the tiers on each given project.

Doing this requires a bit of logic behind the scenes that will generate each tier’s plan dynamically via the Stripe API. I’ll reach for another Job to do as such so we an queue these up in a more performant manner.

```bash
$ rails g job create_perk_plans

```

This creates a new job file. Inside I’ve added the following logic:

```ruby
class CreatePerkPlansJob < ApplicationJob
  queue_as :default

  def perform(project)
    key = project.user.access_code
    Stripe.api_key = key

    project.perks.each do |perk|
      Stripe::Plan.create({
        id: "#{perk.title.parameterize}-perk_#{perk.id}",
        amount: (perk.amount.to_r * 100).to_i,
        currency: 'usd',
        interval: 'month',
        product: { name: perk.title },
        nickname: perk.title.parameterize
      })
    end
  end
end

```

This job sets up the Stripe API to use the user who is creating the project’s information. When a new plan is created we pass in each perk’s info inside an each loop. We need a unique ID we can use later to retrieve the plan during subscription setup.

To get this to kick off we need to call the class in our projects controller.

```ruby
# app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
  ...
  def create
    @project = Project.new(project_params)
    @project.user_id = current_user.id

    respond_to do |format|
      if @project.save
        if @project.perks.any? && current_user.can_receive_payments?
          CreatePerkPlansJob.perform_now(@project) # you can also perform later if you like
        end
        ExpireProjectJob.set(wait_until: @project.expires_at).perform_later(@project)
        format.html { redirect_to @project, notice: 'Project was successfully created.' }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @project.errors, status: :unprocessable_entity }
      end
    end
  end
  ...
end

```

In the controller I added a new conditional that makes sure a project has perks (it should) and the current user can actually receive payments. Considering they got this far they should already be able to accept payments but I wanted to be extra sure.

#### Testing it out yourself

If you authenticate a new Stripe account in the app, create a new project with a perk or two, and then head to your Stripe dashboard, you should hopefully see a new product with pricing and other information.

### Subscribing users

Each user will need a customer id associated it for Stripe to reference. This will either be used or added at the time of charge. We need to add a column to our users table in the database as a result. I’ll also add a subscribed boolean property to signify if a user is indeed subscribed.

```bash
$ rails g migration add_subscription_fields_to_users stripe_id:string stripe_subscription_id:string subscribed:boolean current_plan:string card_last4:string card_exp_month:string card_exp_year:string card_type:string
$ rails db:migrate

```

Now that we can dynamically add plans for users we are ready to initialize subscriptions. Doing this will happen inside the `subscriptions_controller.rb`. We need a few values which will be parameters passed through the request. It look like we still need a plan id based on the perk a given user wants to support. We can update the perk button parameters to include it.

```html
<!-- app/views/projects/_active_project.html.erb-->
<!-- ... more code... -->
<h3 class="text-2xl text-gray-900">Back this idea</h3>

<% project.perks.each do |perk| %>
    <div class="p-4 mb-6 bg-gray-100 border rounded">
      <h4 class="text-lg font-bold text-center text-gray-900"><%= perk.title %></h4>
      <p class="pb-2 mb-2 text-sm italic text-center border-b"><%= perk.quantity %> left</p>
      <div class="py-2 text-gray-700">
        <%= simple_format perk.description %>
      </div>

      <% if user_signed_in? && perk.project.user_id == current_user.id %>
        <em class="block text-sm text-center">Sorry, You can't back your own idea</em>
      <% else %>
      <%= link_to "Back this idea for #{number_to_currency(perk.amount)} /mo", new_subscription_path(amount: perk.amount, project: project, plan: "#{perk.title.parameterize}-perk_#{perk.id}"), class: "btn btn-default btn-expanded" %>
      <% end %>
    </div>
  <% end %>
 <!-- ... more code... -->

```

The `link_to` got a new parameter passed called `plan` with the value of `perk.title.parameterize` which will match the nickname we gave each Stripe plan when a new project was created. Remember we ran that job in the background to perform those tasks dynamically.

### Subscription logic

I’d love to be able to use the Pay here but it’s not quite clear from [the docs](https://stripe.com/docs/connect/subscriptions#create-subscriptions) if Stripe Connect is supported. To play it safe we’ll refer to the [Stripe docs instead](https://stripe.com/docs/connect/subscriptions#create-subscriptions).

```ruby
class SubscriptionsController < ApplicationController
  before_action :authenticate_user!

  def new
    @project = Project.find(params[:project])
  end

  # Reference:
  # https://stripe.com/docs/connect/subscriptions
  def create
    @project = Project.find(params[:project])
    key = @project.user.access_code
    Stripe.api_key = key

    plan_id = params[:plan]
    plan = Stripe::Plan.retrieve(plan_id)
    token = params[:stripeToken]

    customer = if current_user.stripe_id?
                Stripe::Customer.retrieve(current_user.stripe_id)
              else
                Stripe::Customer.create(email: current_user.email, source: token)
              end

    subscription = Stripe::Subscription.create({
      customer: customer,
      items: [
        {
          plan: plan
        }
      ],
      expand: ["latest_invoice.payment_intent"],
      application_fee_percent: 10,
    }, stripe_acccount: key)

    options = {
      stripe_id: customer.id,
      subscribed: true,
    }

    options.merge!(
      card_last4: params[:user][:card_last4],
      card_exp_month: params[:user][:card_exp_month],
      card_exp_year: params[:user][:card_exp_year],
      card_type: params[:user][:card_brand]
    )

    current_user.update(options)

    redirect_to root_path, notice: "Your subscription was setup successfully!"
  end

  def destroy
  end
end

```

Here is where I landed so far. We need a few variables coming from different places.

- Our own internal API key is set app-wide so we can access the `Stripe` class without problems. 
- We also need the API key of the given project author. 
- The `plan` is derived from the perk titles and dynamic Stripe plans that should already be created at this point. We’ll need to add a hidden field which adds this value during the `POST` request in the parameters.
- The `token` comes from the front-end during form submission. The Stripe JavaScript we added handles this. 

We first check if a customer exists in our database, if so we use their customer ID to create the subscription rather than creating a whole new customer each time. If a customer doesn’t exist we create one dynamically using the Stripe API. We pass the user’s email and the token.

Finally the subscription requires the customer id, plan type, and stripe account key of the project author. Our platform once a cut of each recurring monthly fee which in this case is 10%.

Let’s add that hidden fields to the form so we have access to them during form submission.

```html
<!-- app/views/subscriptions/_form.html.erb-->
<%= form_with model: current_user, url: subscription_url, method: :post, html: { id: "payment-form", class: "stripe-form" }, data: { stripe_key: project.user.publishable_key } do |form| %>
  <div>
    <label for="card-element" class="label">
      Credit or debit card
    </label>

    <div id="card-element">
      <!-- A Stripe Element will be inserted here. -->
    </div>

    <!-- Used to display Element errors. -->
    <div id="card-errors" role="alert" class="text-sm text-red-400"></div>
  </div>

  <input type="hidden" name="plan" value="<%= params[:plan] %>">
  <input type="hidden" name="project" value="<%= params[:project] %>">

  <button>Back <%= number_to_currency(params[:amount]) %> /mo toward <em><%= project.title %></em></button>
<% end %>

```

### Test drive!

Let’s see if we get things to work.

My console outputs what I’m after for another user

```bash
<User card_type: "Visa", id: 2, email: "jsmitty@gmail.com", username: "jsmitty", name: "John Smith", admin: false, created_at: "2020-01-30 20:46:18", updated_at: "2020-01-30 20:50:58", uid: nil, provider: nil, access_code: nil, publishable_key: nil, stripe_id: "cus_Ge21Ymdr4iI9q5", subscribed: true, current_plan: "perky-perk_1", card_last4: "4242", card_exp_month: "4", card_exp_year: "2024">
irb(main):006:0>

```

This user is merely a subscriber. They haven’t authenticated with Stripe Connect nor posted a project hence the nil values.

We can verify in Stripe the charge. This Stripe account is the user who created the project.

![Stripe charge screenshot](https://i2.wp.com/i.imgur.com/GbV0o43.png?ssl=1)

In a new private browsing window, I opened my main Connect Account and head to the `Collected Fees` area. We defined 10% per month charge so our fee works out to $2.00 in this case.

![connect account collected fees](https://i0.wp.com/i.imgur.com/K3qmUeX.png?ssl=1)

Success!

### Destroying Subscriptions

It’s definitely a requirement to give users the ability to cancel a subscription. We can do this from the account area and reuse the subscription controller to factor in that logic. We’ll essentially be undoing what we did on the `create` method.

```ruby
class SubscriptionsController < ApplicationController
  ...
  def destroy
    subscription_to_remove = params[:id]
    customer = Stripe::Customer.retrieve(current_user.stripe_id)
    customer.subscriptions.retrieve(subscription_to_remove).delete
    current_user.subscribed = false

    redirect_to root_path, notice: "Your subscription has been canceled."
  end
end

```

The main logic is in place here since we have access to the stripe customer id now. We can retrieve their subscription, cancel their plan and update their records to be not subscribed.

Let’s render a user’s current subscription and a link to cancel in their account settings. See the bottom of the code snippet for the newest addition.

```html
<!-- app/view/devise/registrations/edit.html.erb-->
<div class="container flex flex-wrap items-start justify-between mx-auto">
  <div class="w-full lg:w-1/2">
  <h2 class="pt-4 mb-8 text-4xl font-bold heading">Account</h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>

    <%= render "devise/shared/error_messages", resource: resource %>

    <div class="mb-6">
      <%= f.label :username, class:"label" %>
      <%= f.text_field :username, autofocus: true, class:"input" %>
    </div>

    <div class="mb-6">
      <%= f.label :name, class:"label" %>
      <%= f.text_field :name, class:"input" %>
    </div>

    <div class="mb-6">
      <%= f.label :email, class:"label" %>
      <%= f.email_field :email, autocomplete: "email", class:"input" %>
    </div>

    <div class="mb-6">
      <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
        <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
      <% end %>
    </div>

    <div class="mb-6">
      <%= f.label :password, class:"label" %>
      <%= f.password_field :password, autocomplete: "new-password", class:"input" %>
      <p class="pt-1 text-sm italic text-grey-dark"> <% if @minimum_password_length %>
        <%= @minimum_password_length %> characters minimum <% end %> (leave blank if you don't want to change it) </p>

    </div>

    <div class="mb-6">
      <%= f.label :password_confirmation, class: "label" %>
      <%= f.password_field :password_confirmation, autocomplete: "new-password", class: "input" %>
    </div>

    <div class="mb-6">
      <%= f.label :current_password, class: "label" %>
      <%= f.password_field :current_password, autocomplete: "current-password", class: "input" %>
      <p class="pt-2 text-sm italic text-grey-dark">(we need your current password to confirm your changes)</p>
    </div>

    <div class="mb-6">
      <%= f.submit "Update", class: "btn btn-default" %>
    </div>
    <% end %>

    <hr class="mt-6 mb-3 border" />

    <h3 class="mb-4 text-xl font-bold heading">Cancel my account</h3>

    <div class="flex items-center justify-between">
      <div class="flex-1"><p class="py-4">Unhappy?</p></div>

      <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: "btn btn-default" %>
    </div>
  </div>
<div class="w-full text-left lg:pl-16 lg:w-1/2">
  <div class="p-6 mt-10 text-gray-900 bg-white rounded shadow-lg">
    <% unless resource.can_receive_payments? %>
      <h4 class="mb-6 text-xl font-semibold leading-none text-gray-900">You wont be able to sell items until you register with Stripe!</h4>
      <%= stripe_connect_button %>
    <% else %>
      <h4 class="mb-6 text-xl font-semibold leading-none text-gray-900">Successfully connected to Stripe ✅</h4>
    <% end %>
  </div>

  <% if resource.subscribed? %>
   <% customer = Stripe::Customer.retrieve(current_user.stripe_id) %>
    <% if customer.subscriptions.any? %>
    <div class="p-6 mt-10 text-gray-900 bg-white rounded shadow-lg">
      <h4 class="font-bold text-gray-900">Active subscriptions</h4>
      <ul>
        <% customer.subscriptions.list.data.each do |sub| %>
        <li class="flex items-center justify-between py-4 border-b">
          <div><%= sub.plan.nickname %></div>
          <%= link_to "Cancel Subscription", subscription_path(id: sub.id), method: :delete, data: { confirm: "Are you sure?" } %>
        </li>
        <% end %>
      </ul>
    </div>
    <% end %>
   <% end %>
</div>

```

Yes, this is crappy code. We’re doing too much logic in the view but it’s only to prove the functionality. I highly recommend moving some of this to a controller. We’re essentially querying the Stripe API for the current user’s subscription list based on their `stripe_id`. In doing so we can link to our new `destroy` action passing the subscription id as a parameter through to the controller to round out the subscription cancellation request.

### Project Clean up

We have some housekeeping to do with regards to analytics for projects as new subscribers back new ideas. Our backing count is completely static at this point. We need to add a field and make that dynamic for each new subscriber.

```bash
$ rails g migration add_backings_count_to_projects backings_count:integer

```

We’ll set the default to 0

```ruby
class AddBackingsCountToProjects < ActiveRecord::Migration[6.0]
  def change
    add_column :projects, :backings_count, :integer, default: 0
  end
end
```

```bash
$ rails db:migrate
```

Inside the Subscription controller, we can update counts pretty quickly once a new subscription occurs.

```ruby
# app/controllers/subscriptions_controller.rb
...

# Update project attributes
project_updates = {
  backings_count: @project.backings_count.next,
  current_donation_amount: @project.current_donation_amount + (plan.amount/100).to_i,
}
@project.update(project_updates)
redirect_to root_path, notice: "Your subscription was setup successfully!"
....

```

That gets us 1 backer and adds the monthly amount to the monthly backed goal.

We can update the views as a result in both `_active_project.html.erb` and `inactive_project.html.erb`.

```html
<!-- app/views/
<!-- more code -->
<div class="w-full mb-4 lg:w-1/5 lg:mb-0">
  <p class="m-0 text-xl font-semibold leading-none"><%= project.backings_count %></p>
  <p class="text-sm text-gray-500">backers</p>
</div>
<!-- more code -->

```

### Keep users from subscribing twice

Right now we don’t have a great way to tell if a user has subscribed to a specific perk. There’s likely a much better way to track this but I’m going to rely on a simple array for now. This is much easier using Postgresql so I’m actually going to scrap our data so far in favor of switching to Postgresql (you’d need to at some point to deploy this anywhere anyways).

```bash
$ rails db:system:change --to=postgresql

```

If you get a warning about overwriting a `database.yml` file just type `Y` to continue. This adds the `pg` gem inside your `Gemfile` and updates your `config/database.yml` file. Doing this erases all your data so we’ll need to create some more. Before we do I want to add a field on the users table that will store an array of values which will be the plans the user is subscribed to.

```bash
$ rails g migration add_perk_subscriptions_to_users perk_subscriptions:text

```

Then we will modify that file:

```ruby
class AddPerkSubscriptionsToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :perk_subscriptions, :text, array:true, default: []
  end
end
```

```bash
$ rails db:create 
$ rails db:migrate

```

### Subscription perk\_subscriptions logic

We need to append to the `perk_subscriptions` array once a user has purchased a plan. Doing so can happen in the subscriptions controller

```ruby
class SubscriptionsController < ApplicationController
  ...
  options = {
      stripe_id: customer.id,
      subscribed: true,
    }

    options.merge!(
      card_last4: params[:user][:card_last4],
      card_exp_month: params[:user][:card_exp_month],
      card_exp_year: params[:user][:card_exp_year],
      card_type: params[:user][:card_brand]
    )

    current_user.perk_subscriptions << plan_id # add this line
    current_user.update(options)
  ...

end

```

We add the identifier to the array. My logs come back like this via rails console to confirm it worked!

```bash
irb(main):001:0> User.last
  User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 2, email: "jsmitty@gmail.com", username: "jsmitty", name: "John Smith", admin: false, created_at: "2020-01-31 19:35:13", updated_at: "2020-01-31 19:41:28", uid: nil, provider: nil, access_code: nil, publishable_key: nil, stripe_id: "cus_GeO35Z7cUaFle0", subscribed: true, card_last4: "4242", card_exp_month: "4", card_exp_year: "2024", card_type: "Visa", perk_subscriptions: ["totes-cool-perk-perk_1"]>

```

Note the `perk_subscriptions` column now contains an entry within the array!

Why do we need this? Well, we don’t want the same user subscribing to the same perk twice unless they unsubscribed first. In our view we can add a helper to query for the perk identifier. If it finds it in the array we won’t display the button to subscribe.

```html
<!-- app/views/projects/_active_project.html.erb-->
<!-- a bunch more code-->

<% project.perks.each do |perk| %>
  <div class="p-4 mb-6 bg-gray-100 border rounded">
    <h4 class="text-lg font-bold text-center text-gray-900"><%= perk.title %></h4>
    <p class="pb-2 mb-2 text-sm italic text-center border-b"><%= perk.quantity %> left</p>
    <div class="py-2 text-gray-700">
      <%= simple_format perk.description %>
    </div>

    <% if user_signed_in? && perk.project.user_id == current_user.id %>
      <em class="block text-sm text-center">Sorry, You can't back your own idea</em>
    <% else %>
     <% if purchased_perk(perk) %>
        <p class="text-sm">You're already subscribed to this perk. <%= link_to "Manage your subscriptions in your account settings", edit_user_registration_path, class: "text-blue-500 underline" %>.</p>
      <% else %>
       <%= link_to "Back this idea for #{number_to_currency(perk.amount)} /mo", new_subscription_path(amount: perk.amount, project: project, plan: "#{perk.title.parameterize}-perk_#{perk.id}"), class: "btn btn-default btn-expanded" %>
      <% end %>
    <% end %>
  </div>
<% end %>

<!-- a bunch more code-->

```

I extracted logic into a helper here called `purchased_perk(perk)`. That gets extracted to our `projects_helper.rb` file.

```ruby
module ProjectsHelper
  def purchased_perk(perk)
    user_signed_in? && current_user.perk_subscriptions.include?("#{perk.title.parameterize}-perk_#{perk.id}")
  end
end

```

Here’s what I ended up with. We pass the same identifier to the `include?` method which is a godsend from Ruby to look up array values.

### What if a subscription is removed?

We need to do the reverse in the `destroy` method inside the `subscriptions_controller.rb` file. Let’s address that now.

```ruby
def destroy
    subscription_to_remove = params[:id]
    plan_to_remove = params[:plan_id] # add this line
    customer = Stripe::Customer.retrieve(current_user.stripe_id)
    customer.subscriptions.retrieve(subscription_to_remove).delete
    current_user.subscribed = false
    current_user.perk_subscriptions.delete(plan_to_remove) # add this line
    current_user.save
    redirect_to root_path, notice: "Your subscription has been cancelled."
  end

```

We need access to the plan id so we’ll need to update the edit account view as a result. That’s a pretty easy change:

```html
<!-- app/views/devise/registrations/edit.html.erb-->
<!-- more code -->
<% if resource.subscribed? %>
   <% customer = Stripe::Customer.retrieve(current_user.stripe_id) %>
    <% if customer.subscriptions.any? %>
    <div class="p-6 mt-10 text-gray-900 bg-white rounded shadow-lg">
      <h4 class="font-bold text-gray-900">Active subscriptions</h4>
      <ul>
        <% customer.subscriptions.list.data.each do |sub| %>
        <li class="flex items-center justify-between py-4 border-b">
          <div><%= sub.plan.nickname %></div>
          <%= link_to "Cancel Subscription", subscription_path(id: sub.id, plan_id: sub.plan.id), method: :delete, data: { confirm: "Are you sure?" } %>
        </li>
        <% end %>
      </ul>
    </div>
    <% end %>
   <% end %>
<!-- more code -->

```

Notice we now pass the `plan_id` parameter through the “Cancel subscription” link. That gets passed to the controller and then on to Stripe. Cool!

Now when you subscribe to a perk as a customer you should see a notice instead of the subscribe button once a plan is active. If you unsubscribe that button then returns.

### Closing thoughts and where to take things from here

There are plenty of improvements to be made to this application but I hope it gave you a broader look at using Stripe for a marketplace solution. Stripe Connect is very powerful and honestly quite easy to integrate. You’ll need two sides to a marketplace with your own platform in the middle. From there the possibilities are kind of endless.

This app does the following:

- Provides a place for ideas to be backed
- Any user can connect their Stripe account and start a new project
- Any user can back a project
- Authenticated users can comment on projects
- Projects auto-expire after their 30-day limit thanks to background jobs
- Perks are dynamically added to projects and new Stripe subscription plans are added dynamically once a new project is authored.

What could we enhance?

- Tests, Tests, Tests! (I ran out of time so I didn’t focus on tests)
- Have a defined Subscription model for keeping track of customers and subscriptions in-app. 
- Hook into Stripe’s webhooks to get real-time updates when a User’s stripe merchant account needs more information or has something that needs to be updated.
- Add transactional emails around changes/subscriptions/stripe events. 
- More dynamic backing states. You might notice once a customer unsubscribes our metrics don’t revert. This isn’t a huge deal but is something that should probably be addressed.
- The UI sucks but it works

The post [Let’s Build: With Ruby on Rails – Marketplace App with Stripe Connect](https://web-crunch.com/ruby-on-rails-marketplace-stripe-connect/) appeared first on [Web-Crunch](https://web-crunch.com).