Skip to content

Commit

Permalink
WIP: Refactor Payment Intent into a Model
Browse files Browse the repository at this point in the history
  • Loading branch information
kigster committed May 13, 2024
1 parent ab72d2d commit d273791
Show file tree
Hide file tree
Showing 20 changed files with 460 additions and 89 deletions.
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ GEM
railties (>= 6.0.0)
stringio (3.1.0)
stripe (11.4.0)
sucker_punch (3.2.0)
concurrent-ruby (~> 1.0)
temple (0.10.3)
thor (1.3.1)
tilt (2.3.0)
Expand Down Expand Up @@ -519,6 +521,7 @@ DEPENDENCIES
simplecov
stimulus-rails
stripe (~> 11.3)
sucker_punch
timecop
timeout
turbo-rails
Expand Down
117 changes: 117 additions & 0 deletions app/classes/fnf/payments_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# frozen_string_literal: true

module FnF
class PaymentsService
attr_accessor :ticket_request, :payment, :stripe_payment_intent, :user

def initialize(ticket_request:)
self.ticket_request = ticket_request
raise ArgumentError, 'PaymentsService: ticket_request can not be nil' if ticket_request.nil?

self.payment = ticket_request.payment || ticket_request.build_payment
self.stripe_payment_intent = payment.stripe_payment_intent || payment.build_stripe_payment_intent
end

# Stripe Payment Intent
# https://docs.stripe.com/api/payment_intents/object
def create_payment_intent(amount)
Stripe::PaymentIntent.create({
amount: payment.amount,
currency: 'usd',
automatic_payment_methods: { enabled: true },
description: "#{ticket_request.total}#{ticket_request.event.name} Tickets",
metadata: {
ticket_request_id: ticket_request.id,
ticket_request_user_id: ticket_request.user_id,
event_id: ticket_request.event.id,
event_name: ticket_request.event.name
}
})
end

# @description Returns the Stripe Payment Intent
# @see https://docs.stripe.com/api/payment_intents/object
# @example
# {
# "id": "pi_3MtwBwLkdIwHu7ix28a3tqPa",
# "object": "payment_intent",
# "amount": 2000,
# "amount_capturable": 0,
# "amount_details": {
# "tip": {}
# },
# "amount_received": 0,
# "application": null,
# "application_fee_amount": null,
# "automatic_payment_methods": {
# "enabled": true
# },
# "canceled_at": null,
# "cancellation_reason": null,
# "capture_method": "automatic",
# "client_secret": "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhfZluoGH",
# "confirmation_method": "automatic",
# "created": 1680800504,
# "currency": "usd",
# "customer": null,
# "description": null,
# "invoice": null,
# "last_payment_error": null,
# "latest_charge": null,
# "livemode": false,
# "metadata": {},
# "next_action": null,
# "on_behalf_of": null,
# "payment_method": null,
# "payment_method_options": {
# "card": {
# "installments": null,
# "mandate_options": null,
# "network": null,
# "request_three_d_secure": "automatic"
# },
# "link": {
# "persistent_token": null
# }
# },
# "payment_method_types": [
# "card",
# "link"
# ],
# "processing": null,
# "receipt_email": null,
# "review": null,
# "setup_future_usage": null,
# "shipping": null,
# "source": null,
# "statement_descriptor": null,
# "statement_descriptor_suffix": null,
# "status": "requires_payment_method",
# "transfer_data": null,
# "transfer_group": null
# }
def stripe_payment_intent(amount)
intent = if stripe_payment_intent_id
Stripe::PaymentIntent.retrieve(stripe_payment_intent_id)
end

if intent
return intent if intent
end

Stripe::PaymentIntent.create({
amount:,
currency: 'usd',
automatic_payment_methods: { enabled: true },
description: "#{ticket_request.event.name} Tickets",
metadata: {
ticket_request_id: ticket_request.id,
ticket_request_user_id: ticket_request.user_id,
event_id: ticket_request.event.id,
event_name: ticket_request.event.name
}
})
end

end
end
17 changes: 13 additions & 4 deletions app/controllers/payments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,28 @@ class PaymentsController < ApplicationController
before_action :set_event
before_action :set_ticket_request
before_action :set_payment
before_action :validate_payment, except: [:confirm]
before_action :validate_payment

def show
Rails.logger.debug { "#show() => @ticket_request = #{@ticket_request&.inspect} params: #{params}" }
self
end

# @description
# Rendered whenever a +TicketRequest+ is ready to be paid for (i.e. has been approved).
# This renders the new.html.haml view, and initializes Stimulus +checkout_controller.js+
# which uses StripeJS API to render the credit card collection form. In order to render
# the form, the StripeJS calls via Ajax the POST #create action on this controller, to
# initialize the +PaymentIntent+ object, and return the +clientSecret+ to the front-end.
# After the user enters their card number, and clicks Submit, Stripe API will handle the
# credit card errors. Once the payment goes through, however, StripeJS will redirect the
# user to the GET #confirm action, which must update the payment as 'paid'
#
def new
Rails.logger.debug { "#new() => @ticket_request = #{@ticket_request&.inspect}" }
initialize_payment
end

# Creates new Payment
# Create Payment Intent and save PaymentIntentId in Payment
def create
Rails.logger.debug { "#create() => @ticket_request = #{@ticket_request&.inspect} params: #{params}" }

Expand Down Expand Up @@ -120,7 +128,7 @@ def set_payment
end
end

# initialize payment and save stripe payment intent
# @description Initialize the @payment and save the PaymentIntent by calling StripeAPI
def save_payment_intent
initialize_payment
return redirect_to root_path unless @payment.present? && @payment.can_view?(current_user)
Expand All @@ -130,6 +138,7 @@ def save_payment_intent
end
end

# @description Either fetches from the database @payment instance or builds one in memory
def initialize_payment
@stripe_publishable_api_key ||= Rails.configuration.stripe[:publishable_api_key]

Expand Down
1 change: 1 addition & 0 deletions app/controllers/ticket_requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def edit
@user = @ticket_request.user
end

# @description
# rubocop: disable Metrics/AbcSize
def create
unless @event.ticket_sales_open?
Expand Down
56 changes: 45 additions & 11 deletions app/helpers/payments_helper.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
# frozen_string_literal: true

# @description
# Calculates the extra amount to charge based off of Stripe's fees
module PaymentsHelper
# Calculates the extra amount to charge based off of Stripe's fees so that the
# full original amount is sent to the event organizer.
STRIPE_RATE = BigDecimal('0.029', 10) # 2.9% per transaction
STRIPE_TRANSACTION_FEE = BigDecimal('0.30', 10) # +30 cents per transaction

def extra_amount_to_charge(_original_amount)
# XXX: For now, disable passing fees on to user
# extra = (original_amount * STRIPE_RATE + STRIPE_TRANSACTION_FEE) / (1 - STRIPE_RATE)
# extra_cents = (extra * 100).ceil # Round up to the nearest cent
# BigDecimal.new(extra_cents, 10) / 100
0
# 2.9% per transaction
STRIPE_RATE = BigDecimal('0.029', 10)

# +30 cents per transaction
STRIPE_TRANSACTION_FEE = BigDecimal('0.30', 10)

class << self
attr_accessor :extra_fees_enabled, :stripe_rate, :stripe_transaction_fee

def configure
self.extra_fees_enabled = false
self.stripe_rate = STRIPE_RATE
self.stripe_transaction_fee = STRIPE_TRANSACTION_FEE

# override in a block
yield(self)
end

def disable!
self.extra_fees_enabled = false
end
end

# @description
# For now, we disable passing fees on to user.

attr_accessor :extra_charge_amount

# @description
# Can be used to tack on additional fees to the user.
def extra_amount_to_charge(original_amount_cents = nil)
unless PaymentsHelper.extra_fees_enabled
return self.extra_charge_amount = 0
end

if original_amount_cents.nil? && respond_to?(:amount)
original_amount_cents = amount
end

extra = ((original_amount_cents * STRIPE_RATE) + STRIPE_TRANSACTION_FEE) / (1 - STRIPE_RATE)
self.extra_charge_amount = extra.ceil # Round up to the nearest cent
end
end

PaymentsHelper.disable!
Loading

0 comments on commit d273791

Please sign in to comment.