Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add Cashfree Payment Provider #2545

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions app/controllers/webhooks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ def stripe
head(:ok)
end

def cashfree
result = PaymentProviders::CashfreeService.new.handle_incoming_webhook(
organization_id: params[:organization_id],
code: params[:code].presence,
body: request.body.read,
timestamp: request.headers['X-Cashfree-Timestamp'],
signature: request.headers['X-Cashfree-Signature']
)

unless result.success?
if result.error.is_a?(BaseService::ServiceFailure) && result.error.code == 'webhook_error'
return head(:bad_request)
end

result.raise_if_error!
end

head(:ok)
end

def gocardless
result = PaymentProviders::GocardlessService.new.handle_incoming_webhook(
organization_id: params[:organization_id],
Expand Down
20 changes: 20 additions & 0 deletions app/graphql/mutations/payment_providers/cashfree/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Mutations
module PaymentProviders
module Cashfree
class Base < BaseMutation
include AuthenticableApiUser
include RequiredOrganization

def resolve(**args)
result = ::PaymentProviders::CashfreeService
.new(context[:current_user])
.create_or_update(**args.merge(organization: current_organization))

result.success? ? result.cashfree_provider : result_error(result)
end
end
end
end
end
18 changes: 18 additions & 0 deletions app/graphql/mutations/payment_providers/cashfree/create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Mutations
module PaymentProviders
module Cashfree
class Create < Base
REQUIRED_PERMISSION = 'organization:integrations:create'

graphql_name 'AddCashfreePaymentProvider'
description 'Add or update Cashfree payment provider'

input_object_class Types::PaymentProviders::CashfreeInput

type Types::PaymentProviders::Cashfree
end
end
end
end
18 changes: 18 additions & 0 deletions app/graphql/mutations/payment_providers/cashfree/update.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Mutations
module PaymentProviders
module Cashfree
class Update < Base
REQUIRED_PERMISSION = 'organization:integrations:update'

graphql_name 'UpdateCashfreePaymentProvider'
description 'Update Cashfree payment provider'

input_object_class Types::PaymentProviders::UpdateInput

type Types::PaymentProviders::Cashfree
end
end
end
end
2 changes: 2 additions & 0 deletions app/graphql/resolvers/payment_providers_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def provider_type(type)
PaymentProviders::StripeProvider.to_s
when 'gocardless'
PaymentProviders::GocardlessProvider.to_s
when 'cashfree'
PaymentProviders::CashfreeProvider.to_s
else
raise(NotImplementedError)
end
Expand Down
2 changes: 2 additions & 0 deletions app/graphql/types/customers/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ def provider_customer
object.stripe_customer
when :gocardless
object.gocardless_customer
when :cashfree
object.cashfree_customer
when :adyen
object.adyen_customer
end
Expand Down
2 changes: 2 additions & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ class MutationType < Types::BaseObject
field :update_add_on, mutation: Mutations::AddOns::Update

field :add_adyen_payment_provider, mutation: Mutations::PaymentProviders::Adyen::Create
field :add_cashfree_payment_provider, mutation: Mutations::PaymentProviders::Cashfree::Create
field :add_gocardless_payment_provider, mutation: Mutations::PaymentProviders::Gocardless::Create
field :add_stripe_payment_provider, mutation: Mutations::PaymentProviders::Stripe::Create

field :update_adyen_payment_provider, mutation: Mutations::PaymentProviders::Adyen::Update
field :update_cashfree_payment_provider, mutation: Mutations::PaymentProviders::Cashfree::Update
field :update_gocardless_payment_provider, mutation: Mutations::PaymentProviders::Gocardless::Update
field :update_stripe_payment_provider, mutation: Mutations::PaymentProviders::Stripe::Update

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class CurrentOrganizationType < BaseOrganizationType
field :taxes, [Types::Taxes::Object], resolver: Resolvers::TaxesResolver, permission: 'organization:taxes:view'

field :adyen_payment_providers, [Types::PaymentProviders::Adyen], permission: 'organization:integrations:view'
field :cashfree_payment_providers, [Types::PaymentProviders::Cashfree], permission: 'organization:integrations:view'
field :gocardless_payment_providers, [Types::PaymentProviders::Gocardless], permission: 'organization:integrations:view'
field :stripe_payment_providers, [Types::PaymentProviders::Stripe], permission: 'organization:integrations:view'

Expand Down
17 changes: 17 additions & 0 deletions app/graphql/types/payment_providers/cashfree.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Types
module PaymentProviders
class Cashfree < Types::BaseObject
graphql_name 'CashfreeProvider'

field :code, String, null: false
field :id, ID, null: false
field :name, String, null: false

field :client_id, String, null: true, permission: 'organization:integrations:view'
field :client_secret, String, null: true, permission: 'organization:integrations:view'
field :success_redirect_url, String, null: true, permission: 'organization:integrations:view'
end
end
end
15 changes: 15 additions & 0 deletions app/graphql/types/payment_providers/cashfree_input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Types
module PaymentProviders
class CashfreeInput < BaseInputObject
description 'Cashfree input arguments'

argument :client_id, String, required: true
argument :client_secret, String, required: true
argument :code, String, required: true
argument :name, String, required: true
argument :success_redirect_url, String, required: false
end
end
end
5 changes: 4 additions & 1 deletion app/graphql/types/payment_providers/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class Object < Types::BaseUnion

possible_types Types::PaymentProviders::Adyen,
Types::PaymentProviders::Gocardless,
Types::PaymentProviders::Stripe
Types::PaymentProviders::Stripe,
Types::PaymentProviders::Cashfree

def self.resolve_type(object, _context)
case object.class.to_s
Expand All @@ -17,6 +18,8 @@ def self.resolve_type(object, _context)
Types::PaymentProviders::Stripe
when 'PaymentProviders::GocardlessProvider'
Types::PaymentProviders::Gocardless
when 'PaymentProviders::CashfreeProvider'
Types::PaymentProviders::Cashfree
else
raise "Unexpected Payment provider type: #{object.inspect}"
end
Expand Down
16 changes: 16 additions & 0 deletions app/jobs/invoices/payments/cashfree_create_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Invoices
module Payments
class CashfreeCreateJob < ApplicationJob
queue_as 'providers'

unique :until_executed

def perform(invoice)
result = Invoices::Payments::CashfreeService.new(invoice).create
result.raise_if_error!
end
end
end
end
14 changes: 14 additions & 0 deletions app/jobs/payment_providers/cashfree/handle_event_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module PaymentProviders
module Cashfree
class HandleEventJob < ApplicationJob
queue_as 'providers'

def perform(event_json:)
result = PaymentProviders::CashfreeService.new.handle_event(event_json:)
result.raise_if_error!
end
end
end
end
5 changes: 4 additions & 1 deletion app/models/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ class Customer < ApplicationRecord

has_one :stripe_customer, class_name: 'PaymentProviderCustomers::StripeCustomer'
has_one :gocardless_customer, class_name: 'PaymentProviderCustomers::GocardlessCustomer'
has_one :cashfree_customer, class_name: 'PaymentProviderCustomers::CashfreeCustomer'
has_one :adyen_customer, class_name: 'PaymentProviderCustomers::AdyenCustomer'
has_one :netsuite_customer, class_name: 'IntegrationCustomers::NetsuiteCustomer'
has_one :anrok_customer, class_name: 'IntegrationCustomers::AnrokCustomer'
has_one :xero_customer, class_name: 'IntegrationCustomers::XeroCustomer'

PAYMENT_PROVIDERS = %w[stripe gocardless adyen].freeze
PAYMENT_PROVIDERS = %w[stripe gocardless cashfree adyen].freeze

default_scope -> { kept }
sequenced scope: ->(customer) { customer.organization.customers.with_discarded },
Expand Down Expand Up @@ -121,6 +122,8 @@ def provider_customer
stripe_customer
when :gocardless
gocardless_customer
when :cashfree
cashfree_customer
when :adyen
adyen_customer
end
Expand Down
3 changes: 3 additions & 0 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Organization < ApplicationRecord

has_many :stripe_payment_providers, class_name: 'PaymentProviders::StripeProvider'
has_many :gocardless_payment_providers, class_name: 'PaymentProviders::GocardlessProvider'
has_many :cashfree_payment_providers, class_name: 'PaymentProviders::CashfreeProvider'
has_many :adyen_payment_providers, class_name: 'PaymentProviders::AdyenProvider'

has_many :netsuite_integrations, class_name: 'Integrations::NetsuiteIntegration'
Expand Down Expand Up @@ -106,6 +107,8 @@ def payment_provider(provider)
stripe_payment_provider
when 'gocardless'
gocardless_payment_provider
when 'cashfree'
cashfree_payment_provider
when 'adyen'
adyen_payment_provider
end
Expand Down
31 changes: 31 additions & 0 deletions app/models/payment_provider_customers/cashfree_customer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module PaymentProviderCustomers
class CashfreeCustomer < BaseCustomer
end
end

# == Schema Information
#
# Table name: payment_provider_customers
#
# id :uuid not null, primary key
# settings :jsonb not null
# type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# customer_id :uuid not null
# payment_provider_id :uuid
# provider_customer_id :string
#
# Indexes
#
# index_payment_provider_customers_on_customer_id_and_type (customer_id,type) UNIQUE
# index_payment_provider_customers_on_payment_provider_id (payment_provider_id)
# index_payment_provider_customers_on_provider_customer_id (provider_customer_id)
#
# Foreign Keys
#
# fk_rails_... (customer_id => customers.id)
# fk_rails_... (payment_provider_id => payment_providers.id)
#
39 changes: 39 additions & 0 deletions app/models/payment_providers/cashfree_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module PaymentProviders
class CashfreeProvider < BaseProvider
SUCCESS_REDIRECT_URL = 'https://cashfree.com/'
API_VERSION = "2023-08-01"
BASE_URL = (Rails.env.production? ? 'https://api.cashfree.com/pg/links' : 'https://sandbox.cashfree.com/pg/links')

validates :client_id, presence: true
validates :client_secret, presence: true
validates :success_redirect_url, url: true, allow_nil: true, length: {maximum: 1024}

secrets_accessors :client_id, :client_secret
end
end

# == Schema Information
#
# Table name: payment_providers
#
# id :uuid not null, primary key
# code :string not null
# name :string not null
# secrets :string
# settings :jsonb not null
# type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# organization_id :uuid not null
#
# Indexes
#
# index_payment_providers_on_code_and_organization_id (code,organization_id) UNIQUE
# index_payment_providers_on_organization_id (organization_id)
#
# Foreign Keys
#
# fk_rails_... (organization_id => organizations.id)
#
3 changes: 3 additions & 0 deletions app/serializers/v1/customer_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ def billing_configuration
when :gocardless
configuration[:provider_customer_id] = model.gocardless_customer&.provider_customer_id
configuration.merge!(model.gocardless_customer&.settings || {})
when :cashfree
configuration[:provider_customer_id] = model.cashfree_customer&.provider_customer_id
configuration.merge!(model.cashfree_customer&.settings || {})
when :adyen
configuration[:provider_customer_id] = model.adyen_customer&.provider_customer_id
configuration.merge!(model.adyen_customer&.settings || {})
Expand Down
4 changes: 3 additions & 1 deletion app/services/customers/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def handle_api_billing_configuration(customer, params, new_customer)

if billing.key?(:payment_provider)
customer.payment_provider = nil
if %w[stripe gocardless adyen].include?(billing[:payment_provider])
if %w[stripe gocardless cashfree adyen].include?(billing[:payment_provider])
customer.payment_provider = billing[:payment_provider]
end
end
Expand All @@ -304,6 +304,8 @@ def create_or_update_provider_customer(customer, billing_configuration = {})
PaymentProviderCustomers::StripeCustomer
when 'gocardless'
PaymentProviderCustomers::GocardlessCustomer
when 'cashfree'
PaymentProviderCustomers::CashfreeCustomer
when 'adyen'
PaymentProviderCustomers::AdyenCustomer
end
Expand Down
18 changes: 18 additions & 0 deletions app/services/customers/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ def create_or_update_provider_customer(customer, payment_provider, billing_confi
return unless handle_provider_customer

update_gocardless_customer(customer, billing_configuration)
when 'cashfree'
handle_provider_customer ||= customer.cashfree_customer&.provider_customer_id.present?

return unless handle_provider_customer

update_cashfree_customer(customer, billing_configuration)
when 'adyen'
handle_provider_customer ||= customer.adyen_customer&.provider_customer_id.present?

Expand Down Expand Up @@ -219,6 +225,18 @@ def update_gocardless_customer(customer, billing_configuration)
customer.gocardless_customer&.reload
end

def update_cashfree_customer(customer, billing_configuration)
create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: PaymentProviderCustomers::CashfreeCustomer,
payment_provider_id: payment_provider(customer)&.id,
params: billing_configuration
)
create_result.raise_if_error!

# NOTE: Create service is modifying an other instance of the provider customer
customer.cashfree_customer&.reload
end

def update_adyen_customer(customer, billing_configuration)
create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: PaymentProviderCustomers::AdyenCustomer,
Expand Down
Loading
Loading