From ca8b1627d774e9ffc725c377e7dfeacd08f23c4a Mon Sep 17 00:00:00 2001
From: Shivam Chahar
Date: Mon, 25 Apr 2022 17:48:45 +0530
Subject: [PATCH] Add stripe checkout for invoices
---
.env.example | 4 ++
.../invoices/payments_controller.rb | 37 ++++++++++++
app/models/client.rb | 17 ++++++
app/models/invoice.rb | 4 ++
app/services/application_service.rb | 7 +++
app/services/invoice_payment/checkout.rb | 59 +++++++++++++++++++
app/views/invoice_mailer/invoice.html.erb | 2 +
app/views/invoice_mailer/invoice.text.erb | 2 +
app/views/invoices/payments/cancel.html.erb | 21 +++++++
app/views/invoices/payments/success.html.erb | 17 ++++++
config/initializers/stripe.rb | 3 +
config/routes.rb | 9 +++
...20220425074402_add_stripe_id_to_clients.rb | 7 +++
db/schema.rb | 5 +-
spec/requests/invoices/payments_spec.rb | 9 +++
15 files changed, 201 insertions(+), 2 deletions(-)
create mode 100644 app/controllers/invoices/payments_controller.rb
create mode 100644 app/services/application_service.rb
create mode 100644 app/services/invoice_payment/checkout.rb
create mode 100644 app/views/invoices/payments/cancel.html.erb
create mode 100644 app/views/invoices/payments/success.html.erb
create mode 100644 config/initializers/stripe.rb
create mode 100644 db/migrate/20220425074402_add_stripe_id_to_clients.rb
create mode 100644 spec/requests/invoices/payments_spec.rb
diff --git a/.env.example b/.env.example
index d18b1dabc5..a19f5d3d00 100644
--- a/.env.example
+++ b/.env.example
@@ -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"
diff --git a/app/controllers/invoices/payments_controller.rb b/app/controllers/invoices/payments_controller.rb
new file mode 100644
index 0000000000..306adb50dc
--- /dev/null
+++ b/app/controllers/invoices/payments_controller.rb
@@ -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
diff --git a/app/models/client.rb b/app/models/client.rb
index 4110173de4..ec21b4922b 100644
--- a/app/models/client.rb
+++ b/app/models/client.rb
@@ -12,6 +12,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# discarded_at :datetime
+# stripe_id :string
#
# Indexes
#
@@ -89,6 +90,22 @@ def client_detail(time_frame = "week")
}
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
diff --git a/app/models/invoice.rb b/app/models/invoice.rb
index 78a282bc64..0d1c3ad13c 100644
--- a/app/models/invoice.rb
+++ b/app/models/invoice.rb
@@ -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
diff --git a/app/services/application_service.rb b/app/services/application_service.rb
new file mode 100644
index 0000000000..b9a1299cfd
--- /dev/null
+++ b/app/services/application_service.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class ApplicationService
+ def self.process(*args, &block)
+ new(*args, &block).process
+ end
+end
diff --git a/app/services/invoice_payment/checkout.rb b/app/services/invoice_payment/checkout.rb
new file mode 100644
index 0000000000..8fd90b87cf
--- /dev/null
+++ b/app/services/invoice_payment/checkout.rb
@@ -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
diff --git a/app/views/invoice_mailer/invoice.html.erb b/app/views/invoice_mailer/invoice.html.erb
index 78b2eaf0b8..75c2caffb0 100644
--- a/app/views/invoice_mailer/invoice.html.erb
+++ b/app/views/invoice_mailer/invoice.html.erb
@@ -14,6 +14,8 @@
The due date is <%= @invoice.due_date %>.
+ You can pay for the invoice here: <%= link_to nil, new_invoice_payment_url(@invoice), target: "_blank", rel: "nofollow" %>
+
Thanks!