Skip to content
Open
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
10 changes: 8 additions & 2 deletions app/controllers/api/v1/billing_entities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Api
module V1
class BillingEntitiesController < Api::BaseController
include RawPaymentTermParams

def index
render(
json: ::CollectionSerializer.new(
Expand Down Expand Up @@ -67,7 +69,7 @@ def update
private

def create_params
params.require(:billing_entity).permit(
permitted = params.require(:billing_entity).permit(
:code,
:name,
:einvoicing,
Expand Down Expand Up @@ -99,10 +101,12 @@ def create_params
:document_locale
]
)

with_raw_payment_term(permitted, params[:billing_entity])
end

def update_params
params.require(:billing_entity).permit(
permitted = params.require(:billing_entity).permit(
:name,
:einvoicing,
:email,
Expand Down Expand Up @@ -135,6 +139,8 @@ def update_params
tax_codes: [],
invoice_custom_section_codes: []
)

with_raw_payment_term(permitted, params[:billing_entity])
end

def resource_name
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/api/v1/customers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Api
module V1
class CustomersController < Api::BaseController
include RawPaymentTermParams

def create
result = ::Customers::UpsertFromApiService.call(
organization: current_organization,
Expand Down Expand Up @@ -121,7 +123,7 @@ def checkout_url
private

def create_params
params.expect(customer: [
permitted = params.expect(customer: [
:account_type,
:external_id,
:name,
Expand Down Expand Up @@ -191,6 +193,8 @@ def create_params
tax_codes: [],
invoice_custom_section_codes: []
])

with_raw_payment_term(permitted, params[:customer])
end

def render_customer(customer)
Expand Down
19 changes: 19 additions & 0 deletions app/controllers/concerns/raw_payment_term_params.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module RawPaymentTermParams
private

# Strong params drop null and wrong-type values before validation.
# As a solution, this method copies raw values into the permitted params,
# so the PaymentTerms::ValidateService receives every value that the client sent.
def with_raw_payment_term(permitted, raw)
%i[payment_term net_payment_term].each do |key|
next unless raw.respond_to?(:key?) && raw.key?(key)

permitted[key] = raw[key]
permitted[key].permit! if permitted[key].is_a?(ActionController::Parameters)
end

permitted
end
end
1 change: 1 addition & 0 deletions app/serializers/v1/billing_entity_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def serialize
legal_number: model.legal_number,
timezone: model.timezone,
net_payment_term: model.net_payment_term,
payment_term: model.payment_term,
email_settings: model.email_settings,
document_numbering: model.document_numbering,
document_number_prefix: model.document_number_prefix,
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 @@ -33,6 +33,7 @@ def serialize
timezone: model.timezone,
applicable_timezone: model.applicable_timezone,
net_payment_term: model.net_payment_term,
payment_term: model.payment_term,
external_salesforce_id: model.external_salesforce_id,
finalize_zero_amount_invoice: model.finalize_zero_amount_invoice,
billing_configuration:,
Expand Down
1 change: 1 addition & 0 deletions app/serializers/v1/invoice_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def serialize
issuing_date: model.issuing_date&.iso8601,
payment_due_date: model.payment_due_date&.iso8601,
net_payment_term: model.net_payment_term,
payment_term: model.snapshotted_payment_term&.to_h,
invoice_type: model.invoice_type,
status: model.status,
payment_status: model.payment_status,
Expand Down
5 changes: 4 additions & 1 deletion app/services/billing_entities/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def initialize(organization:, params:)
def call
return result.forbidden_failure! unless organization.can_create_billing_entity?

unless PaymentTerms::ValidateService.new(result, payment_term: params[:payment_term], net_payment_term: params[:net_payment_term]).valid?
return result
end

ActiveRecord::Base.transaction do
billing_entity.assign_attributes(create_attributes)
billing_entity.id = params[:id] if params[:id]
Expand Down Expand Up @@ -77,7 +81,6 @@ def create_attributes
legal_name
legal_number
name
net_payment_term
phone
state
tax_identification_number
Expand Down
4 changes: 4 additions & 0 deletions app/services/billing_entities/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def initialize(billing_entity:, params:)
def call
return result.not_found_failure!(resource: "billing_entity") unless billing_entity

unless PaymentTerms::ValidateService.new(result, payment_term: params[:payment_term], net_payment_term: params[:net_payment_term]).valid?
return result
end

original_attributes = billing_entity.attributes
old_tax_codes = billing_entity.taxes.pluck(:code)

Expand Down
5 changes: 4 additions & 1 deletion app/services/customers/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ def call
)
end

unless PaymentTerms::ValidateService.new(result, payment_term: args[:payment_term], net_payment_term: args[:net_payment_term]).valid?
return result
end

customer = billing_entity.customers.new(
organization_id: organization.id,
external_id: args[:external_id],
Expand All @@ -56,7 +60,6 @@ def call
logo_url: args[:logo_url],
legal_name: args[:legal_name],
legal_number: args[:legal_number],
net_payment_term: args[:net_payment_term],
external_salesforce_id: args[:external_salesforce_id],
payment_provider: args[:payment_provider],
payment_provider_code: args[:payment_provider_code],
Expand Down
4 changes: 4 additions & 0 deletions app/services/customers/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def call
)
end

unless PaymentTerms::ValidateService.new(result, payment_term: args[:payment_term], net_payment_term: args[:net_payment_term]).valid?
return result
end

old_payment_provider = customer.payment_provider
old_provider_customer = customer.provider_customer
original_tax_values = customer.slice(:tax_identification_number, :zipcode, :country).symbolize_keys
Expand Down
5 changes: 4 additions & 1 deletion app/services/customers/upsert_from_api_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def call
)
end

unless PaymentTerms::ValidateService.new(result, payment_term: params[:payment_term], net_payment_term: params[:net_payment_term]).valid?
return result
end

ActiveRecord::Base.transaction do
original_tax_values = customer.slice(:tax_identification_number, :zipcode, :country).symbolize_keys

Expand Down Expand Up @@ -71,7 +75,6 @@ def call
customer.logo_url = params[:logo_url] if params.key?(:logo_url)
customer.legal_name = params[:legal_name] if params.key?(:legal_name)
customer.legal_number = params[:legal_number] if params.key?(:legal_number)
customer.net_payment_term = params[:net_payment_term] if params.key?(:net_payment_term)
customer.external_salesforce_id = params[:external_salesforce_id] if params.key?(:external_salesforce_id)
customer.finalize_zero_amount_invoice = params[:finalize_zero_amount_invoice] || "inherit" if params.key?(:finalize_zero_amount_invoice)
customer.firstname = params[:firstname] if params.key?(:firstname)
Expand Down
8 changes: 7 additions & 1 deletion app/services/payment_terms/validate_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ def valid_payment_term?
valid_month_offset?
end

# Normalized to an indifferent-access Hash so API input arrives the same
# whether it comes as ActionController::Parameters or a plain hash.
def payment_term
args[:payment_term]
return @payment_term if defined?(@payment_term)

raw = args[:payment_term]
raw = raw.to_unsafe_h if raw.is_a?(ActionController::Parameters)
@payment_term = raw.is_a?(Hash) ? raw.with_indifferent_access : raw
end

def term_type
Expand Down
91 changes: 91 additions & 0 deletions spec/requests/api/v1/billing_entities_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,97 @@
before { subject }
end

context "when updating a structured payment_term" do
let(:update_params) do
{billing_entity: {payment_term: {term_type: "net_end_of_month", days: 10}}}
end

it "writes the term and returns it with a null alias" do
subject

expect(response).to be_successful
expect(json[:billing_entity][:payment_term]).to eq(term_type: "net_end_of_month", days: 10)
expect(json[:billing_entity][:net_payment_term]).to be_nil
end
end

context "when updating net_payment_term with a digit string" do
let(:update_params) { {billing_entity: {net_payment_term: "45"}} }

it "fails with invalid_format" do
subject

expect(response).to have_http_status(:unprocessable_entity)
expect(json[:error_details][:net_payment_term]).to eq(["invalid_format"])
end
end

context "when updating net_payment_term with a decimal" do
let(:update_params) { {billing_entity: {net_payment_term: 30.5}} }

it "fails with invalid_format" do
subject

expect(response).to have_http_status(:unprocessable_entity)
expect(json[:error_details][:net_payment_term]).to eq(["invalid_format"])
end
end

context "when updating net_payment_term with a negative value" do
let(:update_params) { {billing_entity: {net_payment_term: -1}} }

it "fails with the legacy value_is_out_of_range code" do
subject

expect(response).to have_http_status(:unprocessable_entity)
expect(json[:error_details][:net_payment_term]).to eq(["value_is_out_of_range"])
end
end

context "when the payment_term shape is invalid" do
let(:update_params) do
{billing_entity: {payment_term: {term_type: "net"}}}
end

it "returns a validation error" do
subject

expect(response).to have_http_status(:unprocessable_entity)
expect(json[:error_details][:payment_term]).to eq(["invalid_days"])
end
end

context "when the payment_term is not a hash" do
let(:update_params) do
{billing_entity: {payment_term: "net 30"}}
end

it "returns a validation error instead of silently dropping the value" do
subject

expect(response).to have_http_status(:unprocessable_entity)
expect(json[:error_details][:payment_term]).to eq(["invalid_format"])
end
end

context "when the payment_term is null" do
let(:update_params) do
{billing_entity: {payment_term: nil}}
end

before do
billing_entity1.update!(payment_term: {"term_type" => "net", "days" => 30}, net_payment_term: 30)
end

it "clears the term" do
subject

expect(response).to be_successful
expect(json[:billing_entity][:payment_term]).to be_nil
expect(json[:billing_entity][:net_payment_term]).to be_nil
end
end

context "when updating the applicable invoice custom sections" do
let(:update_params) do
{
Expand Down
Loading
Loading