Skip to content

Commit

Permalink
feat(revshare): Create customers with partner as account_type (#3065)
Browse files Browse the repository at this point in the history
## Roadmap

👉 https://getlago.canny.io/feature-requests/p/calculate-revenue-share

 ## Context

Current problem: companies with **partners** selling for them cannot
have a **revenue share** system in Lago.

We want to propose **self-billing** into Lago, a billing arrangement
where the **customer** creates and issues the invoice on **behalf** of
the **supplier** for goods or services received.

 ## Description

define `revenue_share` premium integration for organizations

Create customers with account_type partner when revenue share premium
integration is enabled.

GraphQL and API v1 accepts and returns customer account_type.
  • Loading branch information
ancorcruz authored Jan 17, 2025
1 parent 39214d2 commit d7e2a27
Show file tree
Hide file tree
Showing 19 changed files with 333 additions and 75 deletions.
1 change: 1 addition & 0 deletions app/controllers/api/v1/customers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def checkout_url

def create_params
params.require(:customer).permit(
:account_type,
:external_id,
:name,
:firstname,
Expand Down
13 changes: 13 additions & 0 deletions app/graphql/types/customers/account_type_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Types
module Customers
class AccountTypeEnum < Types::BaseEnum
graphql_name "CustomerAccountTypeEnum"

Customer::ACCOUNT_TYPES.keys.each do |type|
value type
end
end
end
end
1 change: 1 addition & 0 deletions app/graphql/types/customers/create_customer_input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Customers
class CreateCustomerInput < BaseInputObject
description 'Create Customer input arguments'

argument :account_type, Types::Customers::AccountTypeEnum, required: false
argument :address_line1, String, required: false
argument :address_line2, String, required: false
argument :city, String, required: false
Expand Down
1 change: 1 addition & 0 deletions app/graphql/types/customers/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Object < Types::BaseObject

field :id, ID, null: false

field :account_type, Types::Customers::AccountTypeEnum, null: false
field :customer_type, Types::Customers::CustomerTypeEnum
field :display_name, String, null: false
field :external_id, String, null: false
Expand Down
7 changes: 7 additions & 0 deletions app/models/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ class Customer < ApplicationRecord
individual: 'individual'
}.freeze

ACCOUNT_TYPES = {
customer: 'customer',
partner: 'partner'
}.freeze

attribute :finalize_zero_amount_invoice, :integer
enum finalize_zero_amount_invoice: FINALIZE_ZERO_AMOUNT_INVOICE_OPTIONS, _prefix: :finalize_zero_amount_invoice
attribute :customer_type, :string
enum customer_type: CUSTOMER_TYPES, _prefix: :customer_type
attribute :account_type, :string
enum account_type: ACCOUNT_TYPES, _suffix: :account

before_save :ensure_slug

Expand Down
6 changes: 5 additions & 1 deletion app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Organization < ApplicationRecord
].freeze

INTEGRATIONS = %w[
netsuite okta anrok xero progressive_billing hubspot auto_dunning revenue_analytics salesforce api_permissions
netsuite okta anrok xero progressive_billing hubspot auto_dunning revenue_analytics salesforce api_permissions revenue_share
].freeze
PREMIUM_INTEGRATIONS = INTEGRATIONS - %w[anrok]

Expand Down Expand Up @@ -140,6 +140,10 @@ def auto_dunning_enabled?
License.premium? && premium_integrations.include?("auto_dunning")
end

def revenue_share_enabled?
License.premium? && premium_integrations.include?("revenue_share")
end

def reset_customers_last_dunning_campaign_attempt
customers
.falling_back_to_default_dunning_campaign
Expand Down
1 change: 1 addition & 0 deletions app/serializers/v1/customer_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def serialize
payload = {
lago_id: model.id,
external_id: model.external_id,
account_type: model.account_type,
name: model.name,
firstname: model.firstname,
lastname: model.lastname,
Expand Down
11 changes: 11 additions & 0 deletions app/services/customers/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ def create_from_api(organization:, params:)
customer.firstname = params[:firstname] if params.key?(:firstname)
customer.lastname = params[:lastname] if params.key?(:lastname)
customer.customer_type = params[:customer_type] if params.key?(:customer_type)

if customer.organization.revenue_share_enabled?
customer.account_type = params[:account_type] if params.key?(:account_type)
customer.exclude_from_dunning_campaign = customer.partner_account?
end

if params.key?(:tax_identification_number)
customer.tax_identification_number = params[:tax_identification_number]
end
Expand Down Expand Up @@ -169,6 +175,11 @@ def create(**args)
customer_type: args[:customer_type]
)

if customer&.organization&.revenue_share_enabled?
customer.account_type = args[:account_type] if args.key?(:account_type)
customer.exclude_from_dunning_campaign = customer.partner_account?
end

if args.key?(:finalize_zero_amount_invoice)
customer.finalize_zero_amount_invoice = args[:finalize_zero_amount_invoice]
end
Expand Down
10 changes: 10 additions & 0 deletions schema.graphql

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

63 changes: 63 additions & 0 deletions schema.json

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

9 changes: 9 additions & 0 deletions spec/graphql/types/customers/account_type_enum_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 Types::Customers::AccountTypeEnum do
it "enumerizes the correct values" do
expect(described_class.values.keys).to match_array(%w[customer partner])
end
end
1 change: 1 addition & 0 deletions spec/graphql/types/customers/create_customer_input_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
RSpec.describe Types::Customers::CreateCustomerInput do
subject { described_class }

it { is_expected.to accept_argument(:account_type).of_type(Types::Customers::AccountTypeEnum) }
it { is_expected.to accept_argument(:address_line1).of_type('String') }
it { is_expected.to accept_argument(:address_line2).of_type('String') }
it { is_expected.to accept_argument(:city).of_type('String') }
Expand Down
1 change: 1 addition & 0 deletions spec/graphql/types/customers/object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

it { is_expected.to have_field(:id).of_type('ID!') }

it { is_expected.to have_field(:account_type).of_type("CustomerAccountTypeEnum!") }
it { is_expected.to have_field(:customer_type).of_type(Types::Customers::CustomerTypeEnum) }
it { is_expected.to have_field(:display_name).of_type('String!') }
it { is_expected.to have_field(:external_id).of_type('String!') }
Expand Down
30 changes: 30 additions & 0 deletions spec/models/customer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,36 @@
end
end

describe "account_type enum" do
subject(:customer) { build_stubbed(:customer, account_type:) }

context "when account_type is customer" do
let(:account_type) { "customer" }

it "identifies the customer as a customer" do
expect(customer.account_type).to eq("customer")
expect(customer).to be_customer_account
end
end

context "when account_type is partner" do
let(:account_type) { "partner" }

it "identifies the customer as partner" do
expect(customer.account_type).to eq("partner")
expect(customer).to be_partner_account
end
end

context "when account_type is nil" do
subject(:customer) { build(:customer) }

it "defaults to customer for existing customers" do
expect(customer.account_type).to eq "customer"
end
end
end

describe 'preferred_document_locale' do
subject(:customer) do
described_class.new(
Expand Down
20 changes: 4 additions & 16 deletions spec/models/organization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,23 +222,11 @@
end

describe "#auto_dunning_enabled?" do
subject(:auto_dunning_enabled?) { organization.auto_dunning_enabled? }

it { is_expected.to eq(false) }

context "when premium features are enabled" do
around { |test| lago_premium!(&test) }

it { is_expected.to eq(false) }

context "with auto_dunning integration is enabled" do
let(:organization) do
described_class.new(premium_integrations: ["auto_dunning"])
end
it_behaves_like "organization premium feature", "auto_dunning"
end

it { is_expected.to eq(true) }
end
end
describe "#revenue_share_enabled?" do
it_behaves_like "organization premium feature", "revenue_share"
end

describe "#reset_customers_last_dunning_campaign_attempt" do
Expand Down
Loading

0 comments on commit d7e2a27

Please sign in to comment.