Skip to content

Commit

Permalink
Add stripe checkout for invoices (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
shivamsinghchahar authored Apr 26, 2022
1 parent a7c7912 commit 644dea8
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ NEW_RELIC_LICENSE_KEY='replace with newrelic licence key'

# Redis url
REDIS_URL='redis://127.0.0.1:6379/12'

# Stripe
STRIPE_PUBLISHABLE_KEY="pk_test_NOgckL4BT40aggiIRMiU8O2g00yhQxp1yS"
STRIPE_SECRET_KEY="sk_test_0upT8snNIjPXvOteHwRKhOHK00dGraUDu5"
37 changes: 37 additions & 0 deletions app/controllers/invoices/payments_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

class Invoices::PaymentsController < ApplicationController
skip_before_action :authenticate_user!
skip_after_action :verify_authorized
before_action :load_invoice
before_action :ensure_invoice_unpaid, only: [:new]

def new
session = @invoice.create_checkout_session!(
success_url: success_invoice_payments_url(@invoice),
cancel_url: cancel_invoice_payments_url(@invoice)
)

redirect_to session.url, allow_other_host: true
end

def success
@invoice.paid!
end

def cancel
render
end

private

def load_invoice
@invoice = Invoice.includes(client: :company).find(params[:invoice_id])
end

def ensure_invoice_unpaid
if @invoice.paid?
redirect_to success_invoice_payments_url(@invoice.id)
end
end
end
17 changes: 17 additions & 0 deletions app/models/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# discarded_at :datetime
# stripe_id :string
#
# Indexes
#
Expand Down Expand Up @@ -103,6 +104,22 @@ def client_overdue_and_outstanding_calculation
}
end

def register_on_stripe!
self.transaction do
customer = Stripe::Customer.create(
{
email:,
name:,
phone:,
metadata: {
platform_id: id
}
})

update!(stripe_id: customer.id)
end
end

private

def discard_projects
Expand Down
4 changes: 4 additions & 0 deletions app/models/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,8 @@ def update_timesheet_entry_status!
timesheet_entry_ids = invoice_line_items.pluck(:timesheet_entry_id)
TimesheetEntry.where(id: timesheet_entry_ids).update!(bill_status: :billed)
end

def create_checkout_session!(success_url:, cancel_url:)
InvoicePayment::Checkout.process(invoice: self, success_url:, cancel_url:)
end
end
7 changes: 7 additions & 0 deletions app/services/application_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class ApplicationService
def self.process(*args, &block)
new(*args, &block).process
end
end
59 changes: 59 additions & 0 deletions app/services/invoice_payment/checkout.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

module InvoicePayment
class Checkout < ApplicationService
def initialize(params)
@invoice = params[:invoice]
@company = invoice.client.company
@client = invoice.client
@success_url = params[:success_url]
@cancel_url = params[:cancel_url]
end

def process
Invoice.transaction do
ensure_client_registered!
checkout!
end
end

private

attr_reader :invoice, :company, :client, :success_url, :cancel_url

def ensure_client_registered!
return if client.stripe_id?

client.register_on_stripe!
end

def description
"Invoice from #{company.name} for #{currency} #{invoice.amount} due on #{invoice.due_date}"
end

def currency
company.base_currency
end

def checkout!
Stripe::Checkout::Session.create(
{
line_items: [{
price_data: {
currency: company.base_currency.downcase,
product_data: {
name: invoice.invoice_number,
description:
},
unit_amount: invoice.amount.to_i
},
quantity: 1
}],
mode: "payment",
customer: client.reload.stripe_id,
success_url:,
cancel_url:
})
end
end
end
2 changes: 2 additions & 0 deletions app/views/invoice_mailer/invoice.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
The due date is <%= @invoice.due_date %>.
</p>

<p>You can pay for the invoice here: <%= link_to nil, new_invoice_payment_url(@invoice), target: "_blank", rel: "nofollow" %></p>

<p>Thanks!</p>
</body>
</html>
2 changes: 2 additions & 0 deletions app/views/invoice_mailer/invoice.text.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ Hi, <%= @invoice.client_name %>
You have an invoice - <%= @invoice.invoice_number %> with amount <%= @invoice.amount %>
due date is: <%= @invoice.due_date %>.

You can pay for the invoice here: <%= new_invoice_payment_url(@invoice) %>

Thanks!
Miru
21 changes: 21 additions & 0 deletions app/views/invoices/payments/cancel.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="flex flex-col min-h-full pt-16 pb-12 my-auto">
<main class="flex flex-col self-center justify-center flex-grow w-full px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="flex justify-center flex-shrink-0">
<svg class="w-auto h-16 text-red-400 bg-red-200 rounded-full shadow-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
</div>

<div class="py-16">
<div class="text-center">
<p class="text-sm font-semibold tracking-wide uppercase text-miru-han-purple-600">Invoice #<%= @invoice.invoice_number %></p>
<h1 class="mt-2 text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl">Payment was cancelled.</h1>
<p class="mt-2 text-base text-gray-500">Didn't cancel the payment?</p>
<div class="mt-6 group">
<%= link_to "Try again", new_invoice_payment_url(@invoice), class: "text-base font-medium text-miru-han-purple-600 group-hover:text-miru-han-purple-400", target: "_blank", rel: "nofollow" %>
<span aria-hidden="true" class="text-base font-medium text-miru-han-purple-600 group-hover:text-miru-han-purple-400"> &rarr;</span>
</div>
</div>
</div>
</main>
</div>
17 changes: 17 additions & 0 deletions app/views/invoices/payments/success.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="flex flex-col min-h-full pt-16 pb-12 my-auto">
<main class="flex flex-col self-center justify-center flex-grow w-full px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="flex justify-center flex-shrink-0">
<svg class="w-auto h-16 text-green-400 bg-green-200 rounded-full shadow-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>

<div class="py-16">
<div class="text-center">
<p class="text-sm font-semibold tracking-wide text-indigo-600 uppercase">Invoice #<%= @invoice.invoice_number %></p>
<h1 class="mt-2 text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl">Payment was successful. 🎉</h1>
<p class="mt-2 text-base text-gray-500">We have received your payment.</p>
</div>
</div>
</main>
</div>
3 changes: 3 additions & 0 deletions config/initializers/stripe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

Stripe.api_key = ENV["STRIPE_SECRET_KEY"]
9 changes: 9 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ def draw(routes_name)
resources :invoices, only: [:show]
resources :workspaces, only: [:update]

resources :invoices, only: [], module: :invoices do
resources :payments, only: [:new] do
collection do
get :success
get :cancel
end
end
end

get "clients/*path", to: "clients#index", via: :all
get "clients", to: "clients#index"

Expand Down
7 changes: 7 additions & 0 deletions db/migrate/20220425074402_add_stripe_id_to_clients.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddStripeIdToClients < ActiveRecord::Migration[7.0]
def change
add_column :clients, :stripe_id, :string, default: nil
end
end
5 changes: 3 additions & 2 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions spec/requests/invoices/payments_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "Invoices::Payments", type: :request do
describe "GET /index" do
pending "add some examples (or delete) #{__FILE__}"
end
end

0 comments on commit 644dea8

Please sign in to comment.