diff --git a/app/models/credit_note.rb b/app/models/credit_note.rb index ce08d9edba7..61fc9e3f950 100644 --- a/app/models/credit_note.rb +++ b/app/models/credit_note.rb @@ -129,6 +129,18 @@ def sub_total_excluding_taxes_amount_cents end alias_method :sub_total_excluding_taxes_amount_currency, :currency + def precise_total + items.sum(&:precise_amount_cents) - precise_coupons_adjustment_amount_cents + precise_taxes_amount_cents + end + + def taxes_rounding_adjustment + taxes_amount_cents - precise_taxes_amount_cents + end + + def rounding_adjustment + total_amount_cents - precise_total + end + private def ensure_number diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 0cf64b3ac1a..e59c2e38e1b 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -273,7 +273,7 @@ def creditable_amount_cents fee_rate = fee.creditable_amount_cents.fdiv(fees_total_creditable) prorated_credit_amount = credit_adjustement * fee_rate (fee.creditable_amount_cents - prorated_credit_amount) * (fee.taxes_rate || 0) - end.fdiv(100).round + end.fdiv(100).round # BECAUSE OF THIS ROUND the returned value is not precise fees_total_creditable - credit_adjustement + vat end diff --git a/app/services/credit_notes/create_service.rb b/app/services/credit_notes/create_service.rb index bd171078b1e..1a31cc9e167 100644 --- a/app/services/credit_notes/create_service.rb +++ b/app/services/credit_notes/create_service.rb @@ -205,10 +205,24 @@ def compute_amounts_and_taxes credit_note.precise_coupons_adjustment_amount_cents = taxes_result.coupons_adjustment_amount_cents credit_note.coupons_adjustment_amount_cents = taxes_result.coupons_adjustment_amount_cents.round credit_note.precise_taxes_amount_cents = taxes_result.taxes_amount_cents - credit_note.taxes_amount_cents = taxes_result.taxes_amount_cents.round + adjust_credit_note_tax_rounding if credit_note_for_all_remaining_amount? + + credit_note.taxes_amount_cents = credit_note.precise_taxes_amount_cents.round credit_note.taxes_rate = taxes_result.taxes_rate taxes_result.applied_taxes.each { |applied_tax| credit_note.applied_taxes << applied_tax } end + + def credit_note_for_all_remaining_amount? + credit_note.invoice.creditable_amount_cents == 0 + end + + def adjust_credit_note_tax_rounding + credit_note.precise_taxes_amount_cents -= all_rounding_tax_adjustments + end + + def all_rounding_tax_adjustments + credit_note.invoice.credit_notes.sum(&:taxes_rounding_adjustment) + end end end diff --git a/app/services/credit_notes/estimate_service.rb b/app/services/credit_notes/estimate_service.rb index cae724c8496..cd37812b9ad 100644 --- a/app/services/credit_notes/estimate_service.rb +++ b/app/services/credit_notes/estimate_service.rb @@ -58,6 +58,10 @@ def validate_items end end + def valid_credit_note? + CreditNotes::ValidateService.new(result, item: credit_note).valid? + end + def valid_item?(item) CreditNotes::ValidateItemService.new(result, item:).valid? end @@ -71,7 +75,9 @@ def compute_amounts_and_taxes credit_note.precise_coupons_adjustment_amount_cents = taxes_result.coupons_adjustment_amount_cents credit_note.coupons_adjustment_amount_cents = taxes_result.coupons_adjustment_amount_cents.round credit_note.precise_taxes_amount_cents = taxes_result.taxes_amount_cents - credit_note.taxes_amount_cents = taxes_result.taxes_amount_cents.round + adjust_credit_note_tax_precise_rounding if credit_note_for_all_remaining_amount? + + credit_note.taxes_amount_cents = credit_note.precise_taxes_amount_cents.round credit_note.taxes_rate = taxes_result.taxes_rate taxes_result.applied_taxes.each { |applied_tax| credit_note.applied_taxes << applied_tax } @@ -79,13 +85,25 @@ def compute_amounts_and_taxes credit_note.credit_amount_cents = ( credit_note.items.sum(&:amount_cents) - taxes_result.coupons_adjustment_amount_cents + - taxes_result.taxes_amount_cents + credit_note.precise_taxes_amount_cents ).round compute_refundable_amount credit_note.total_amount_cents = credit_note.credit_amount_cents end + def credit_note_for_all_remaining_amount? + credit_note.items.sum(&:precise_amount_cents) == credit_note.invoice.fees.sum(&:creditable_amount_cents) + end + + def adjust_credit_note_tax_precise_rounding + credit_note.precise_taxes_amount_cents -= all_rounding_tax_adjustments + end + + def all_rounding_tax_adjustments + credit_note.invoice.credit_notes.sum(&:taxes_rounding_adjustment) + end + def compute_refundable_amount credit_note.refund_amount_cents = credit_note.credit_amount_cents diff --git a/app/services/credit_notes/validate_item_service.rb b/app/services/credit_notes/validate_item_service.rb index 11895643cff..cd0bb62f871 100644 --- a/app/services/credit_notes/validate_item_service.rb +++ b/app/services/credit_notes/validate_item_service.rb @@ -7,7 +7,6 @@ def valid? valid_item_amount? valid_individual_amount? - valid_global_amount? if errors? result.validation_failure!(errors:) diff --git a/spec/models/credit_note_spec.rb b/spec/models/credit_note_spec.rb index 9ad7395c2e7..b1224154aaa 100644 --- a/spec/models/credit_note_spec.rb +++ b/spec/models/credit_note_spec.rb @@ -3,7 +3,12 @@ require 'rails_helper' RSpec.describe CreditNote, type: :model do - subject(:credit_note) { create(:credit_note) } + subject(:credit_note) do + create :credit_note, credit_amount_cents: 11000, total_amount_cents: 11000, taxes_amount_cents: 1000, + taxes_rate: 10.0, precise_taxes_amount_cents: 1000 + end + + let(:item) { create(:credit_note_item, credit_note:, precise_amount_cents: 10000, amount_cents: 1000) } it_behaves_like 'paper_trail traceable' @@ -243,10 +248,35 @@ end end - describe ' #sub_total_excluding_taxes_amount_cents' do - it 'returs the total amount without the taxes' do - expect(credit_note.sub_total_excluding_taxes_amount_cents) - .to eq(credit_note.items.sum(&:precise_amount_cents) - credit_note.precise_coupons_adjustment_amount_cents) + context 'when calculating depends on related items' do + before do + item + credit_note.reload + end + + describe '#sub_total_excluding_taxes_amount_cents' do + it 'returs the total amount without the taxes' do + expect(credit_note.sub_total_excluding_taxes_amount_cents) + .to eq(credit_note.items.sum(&:precise_amount_cents) - credit_note.precise_coupons_adjustment_amount_cents) + end + end + + describe '#precise_total' do + it 'returns the total precise amount including precise taxes' do + expect(credit_note.precise_total).to eq(11000) + end + end + end + + describe '#taxes_rounding_adjustment' do + it 'returns the difference between taxes and precise taxes' do + expect(credit_note.taxes_rounding_adjustment).to eq(0) + end + end + + describe '#rounding_adjustment' do + it 'returns the difference between credit note total and credit note precise total' do + expect(credit_note.taxes_rounding_adjustment).to eq(0) end end @@ -328,4 +358,36 @@ end end end + + context 'when taxes are not precise' do + subject(:credit_note) do + create :credit_note, credit_amount_cents: 8200, total_amount_cents: 8200, taxes_amount_cents: 1367, + taxes_rate: 20.0, precise_taxes_amount_cents: 1366.6 + end + + let(:item) { create(:credit_note_item, credit_note:, precise_amount_cents: 6833, amount_cents: 6833) } + + before do + item + credit_note.reload + end + + describe '#precise_total' do + it 'returns the total precise amount including precise taxes' do + expect(credit_note.precise_total).to eq(8199.6) + end + end + + describe '#taxes_rounding_adjustment' do + it 'returns the difference between taxes and precise taxes' do + expect(credit_note.taxes_rounding_adjustment).to eq(0.4) + end + end + + describe '#rounding_adjustment' do + it 'returns the difference between credit note total and credit note precise total' do + expect(credit_note.taxes_rounding_adjustment).to eq(0.4) + end + end + end end diff --git a/spec/scenarios/credit_note_spec.rb b/spec/scenarios/credit_note_spec.rb index 4c8d51fe027..b4fda2cc26b 100644 --- a/spec/scenarios/credit_note_spec.rb +++ b/spec/scenarios/credit_note_spec.rb @@ -336,4 +336,547 @@ end end end + + context 'when creating credit note with possible rounding issues' do + context 'when creating credit notes for small items with taxes, so sum of items with their taxes is bigger than invoice total amount' do + let(:tax) { create(:tax, organization:, rate: 20) } + + context 'when two similar items are refunded separately' do + let(:add_ons) { create_list(:add_on, 2, organization:, amount_cents: 68_33) } + + it 'solves the rounding issue' do + # create a one off invoice with two addons and small amounts as feed + create_one_off_invoice(customer, add_ons) + # invoice amount should be with taxes calculated on items sum: + invoice = customer.invoices.order(:created_at).last + expect(invoice.total_amount_cents).to eq(163_99) + expect(invoice.taxes_amount_cents).to eq(27_33) + fees = invoice.fees + invoice.update(payment_status: 'succeeded') + + # estimate and create credit notes for first item - full refund; the taxes are rounded to higher number + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: fees[0].id, + amount_cents: 68_33 + } + ] + ) + + # Estimate the credit notes amount on one fee rounds the taxes to higher number + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 13_67, + precise_taxes_amount_cents: "1366.6", + sub_total_excluding_taxes_amount_cents: 6833, + max_creditable_amount_cents: 8200, + max_refundable_amount_cents: 8200, + taxes_rate: 20.0 + ) + + # Emit a credit note on only one fee + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 0, + refund_amount_cents: 82_00, + items: [ + { + fee_id: fees[0].id, + amount_cents: 68_33 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + refund_amount_cents: 82_00, + total_amount_cents: 82_00, + taxes_amount_cents: 13_67, + precise_taxes_amount_cents: 1366.6 + ) + expect(credit_note.precise_total).to eq(8199.6) + expect(credit_note.taxes_rounding_adjustment).to eq(0.4) + + # when issuing second credit note, it should be rounded to lower number + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: fees[1].id, + amount_cents: 68_33 + } + ] + ) + + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 13_66, + precise_taxes_amount_cents: "1366.2", + sub_total_excluding_taxes_amount_cents: 6833, + max_creditable_amount_cents: 8199, + max_refundable_amount_cents: 8199, + taxes_rate: 20.0 + ) + + # Emit a credit note on only one fee + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 0, + refund_amount_cents: 81_99, + items: [ + { + fee_id: fees[1].id, + amount_cents: 68_33 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + refund_amount_cents: 8199, + total_amount_cents: 8199, + taxes_amount_cents: 13_66, + precise_taxes_amount_cents: 1366.2 + ) + expect(credit_note.precise_total).to eq(8199.2) + expect(credit_note.taxes_rounding_adjustment).to eq(-0.2) + end + end + + context 'when four items are refunded separately, some whole, some in parts' do + let(:add_ons) { create_list(:add_on, 4, organization:, amount_cents: 68_33) } + + it 'solves the rounding issue' do + # create a one off invoice with two addons and small amounts as feed + create_one_off_invoice(customer, add_ons) + # invoice amount should be with taxes calculated on items sum: + invoice = customer.invoices.order(:created_at).last + expect(invoice.total_amount_cents).to eq(327_98) + expect(invoice.taxes_amount_cents).to eq(54_66) + fees = invoice.fees + invoice.update(payment_status: 'succeeded') + + # estimate and create credit notes for first three items - full refund; the taxes are rounded to higher number + 3.times do |i| + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: fees[i].id, + amount_cents: 68_33 + } + ] + ) + + # Estimate the credit notes amount on one fee rounds the taxes to higher number + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 13_67, + precise_taxes_amount_cents: "1366.6", + sub_total_excluding_taxes_amount_cents: 6833, + max_creditable_amount_cents: 8200, + max_refundable_amount_cents: 8200, + taxes_rate: 20.0 + ) + + # Emit a credit note on only one fee + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 0, + refund_amount_cents: 82_00, + items: [ + { + fee_id: fees[i].id, + amount_cents: 68_33 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + refund_amount_cents: 82_00, + total_amount_cents: 82_00, + taxes_amount_cents: 13_67, + precise_taxes_amount_cents: 1366.6 + ) + expect(credit_note.precise_total).to eq(8199.6) + expect(credit_note.taxes_rounding_adjustment).to eq(0.4) + end + # this value is wrong because of all rounding because if we subtract issued credit notes from the invoice, it + # will result in 327_98 - 82_00 * 3 = 81_98 + expect(invoice.creditable_amount_cents).to eq(8200) + + # split last refundable item into three chunks, first's taxes are rounded to lower number + # next two are rounded to higher number + # cn_1 => 13.67, cn2 => 22.33, cn3 => 32.33 + # CN1 + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: fees[3].id, + amount_cents: 13_67 + } + ] + ) + + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 273, + precise_taxes_amount_cents: "273.4", + sub_total_excluding_taxes_amount_cents: 1367, + max_creditable_amount_cents: 1640, + max_refundable_amount_cents: 1640, + taxes_rate: 20.0 + ) + + # Emit a credit note on only one fee + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 0, + refund_amount_cents: 1640, + items: [ + { + fee_id: fees[3].id, + amount_cents: 1367 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + refund_amount_cents: 1640, + total_amount_cents: 1640, + taxes_amount_cents: 273, + precise_taxes_amount_cents: 273.4 + ) + expect(credit_note.precise_total).to eq(1640.4) + expect(credit_note.taxes_rounding_adjustment).to eq(-0.4) + # real remaining: 81_98 - 16_40 = 65_58 + expect(invoice.creditable_amount_cents).to eq(6559) + + # cn_1 => 13.67, cn2 => 22.33, cn3 => 32.33 + # CN2 + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: fees[3].id, + amount_cents: 22_33 + } + ] + ) + + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 447, + precise_taxes_amount_cents: "446.6", + sub_total_excluding_taxes_amount_cents: 2233, + max_creditable_amount_cents: 2680, + max_refundable_amount_cents: 2680, + taxes_rate: 20.0 + ) + + # Emit a credit note on only one fee + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 0, + refund_amount_cents: 2680, + items: [ + { + fee_id: fees[3].id, + amount_cents: 2233 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + refund_amount_cents: 2680, + total_amount_cents: 2680, + taxes_amount_cents: 447, + precise_taxes_amount_cents: 446.6 + ) + expect(credit_note.precise_total).to eq(2679.6) + expect(credit_note.taxes_rounding_adjustment).to eq(0.4) + # real remaining: 65_58 - 26_80 = 38_78 + expect(invoice.creditable_amount_cents).to eq(3880) + + # cn_1 => 13.67, cn2 => 22.33, cn3 => 32.33 + # CN3 + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: fees[3].id, + amount_cents: 32_33 + } + ] + ) + + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 645, + precise_taxes_amount_cents: "645.4", + sub_total_excluding_taxes_amount_cents: 3233, + max_creditable_amount_cents: 3878, + max_refundable_amount_cents: 3878, + taxes_rate: 20.0 + ) + + # Emit a credit note on only one fee + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 0, + refund_amount_cents: 3878, + items: [ + { + fee_id: fees[3].id, + amount_cents: 3233 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + refund_amount_cents: 3878, + total_amount_cents: 3878, + taxes_amount_cents: 645, + precise_taxes_amount_cents: 645.4 + ) + expect(credit_note.precise_total).to eq(3878.4) + expect(credit_note.taxes_rounding_adjustment).to eq(-0.4) + expect(invoice.creditable_amount_cents).to eq(0) + end + end + end + + context 'when creating credit note with small items and applied coupons' do + let(:tax) { create(:tax, organization:, rate: 20) } + let(:plan_tax) { create(:tax, organization:, name: 'Plan Tax', rate: 20, applied_to_organization: false) } + let(:plan) do + create( + :plan, + organization:, + interval: :monthly, + amount_cents: 1_999, + pay_in_advance: false + ) + end + + let(:charge1) do + create( + :standard_charge, + plan:, + min_amount_cents: 6833 + ) + end + + let(:charge2) do + create( + :standard_charge, + plan:, + min_amount_cents: 200_33 + ) + end + + let(:coupon) do + create( + :coupon, + organization:, + amount_cents: 10_00, + expiration: :no_expiration, + coupon_type: :fixed_amount, + frequency: :forever, + limited_plans: false, + reusable: true + ) + end + + before do + charge1 + charge2 + end + + it 'calculates all roundings' do + # Creates two subscriptions + travel_to(DateTime.new(2022, 12, 19, 12)) do + create_subscription( + external_customer_id: customer.external_id, + external_id: "#{customer.external_id}_1", + plan_code: plan.code, + billing_time: :anniversary + ) + end + + # Apply a coupon twice to the customer + travel_to(DateTime.new(2023, 8, 29)) do + apply_coupon( + external_customer_id: customer.external_id, + coupon_code: coupon.code, + amount_cents: 10_00 + ) + end + + # Bill subscription on an anniversary date + travel_to(DateTime.new(2023, 10, 19)) do + Subscriptions::BillingService.call + perform_all_enqueued_jobs + end + + invoice = customer.invoices.order(created_at: :desc).first + # fees sum = 19_99 + 68_33 + 200_33 = 288_65 + # applied coupon - 10_00 + # subtotal before taxes - 278_65 + # taxes = 5573 + expect(invoice.total_amount_cents).to eq(334_38) + + # issue a CN for the full subscription fee - 19_99 before taxes and coupons + subscription_fee = invoice.fees.find(&:subscription?) + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: subscription_fee.id, + amount_cents: 19_99 + } + ] + ) + + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 386, + precise_taxes_amount_cents: "386.0", + sub_total_excluding_taxes_amount_cents: 1930, + max_creditable_amount_cents: 2316, + coupons_adjustment_amount_cents: 69, + taxes_rate: 20.0 + ) + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 23_16, + items: [ + { + fee_id: subscription_fee.id, + amount_cents: 19_99 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + credit_amount_cents: 2316, + total_amount_cents: 2316, + taxes_amount_cents: 386, + precise_taxes_amount_cents: 386.0, + precise_coupons_adjustment_amount_cents: 69.25342 + ) + expect(credit_note.precise_total).to eq(2315.74658) + expect(credit_note.taxes_rounding_adjustment).to eq(0) + # real remaining: 334_38 - 23_16 = 311_22 + expect(invoice.creditable_amount_cents).to eq(31122.253421098216) + + # issue a CN for the full first charge - 68_33 before taxes and coupons + first_charge = invoice.fees.find { |fee| fee.amount_cents == 68_33 } + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: first_charge.id, + amount_cents: 68_33 + } + ] + ) + + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 1319, + precise_taxes_amount_cents: "1319.2", + sub_total_excluding_taxes_amount_cents: 6596, + max_creditable_amount_cents: 7915, + coupons_adjustment_amount_cents: 237, + taxes_rate: 20.0 + ) + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 7915, + items: [ + { + fee_id: first_charge.id, + amount_cents: 6833 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + credit_amount_cents: 7915, + total_amount_cents: 7915, + taxes_amount_cents: 1319, + precise_taxes_amount_cents: 1319.2, + precise_coupons_adjustment_amount_cents: 236.72267 + ) + expect(credit_note.precise_total).to eq(7915.47733) + expect(credit_note.taxes_rounding_adjustment).to eq(-0.2) + # real remaining: 311_22 - 79_15 = 232_07 + expect(invoice.creditable_amount_cents).to eq(23206.97609561753) + + # issue a CN for the full last charge - 200_33 before taxes and coupons + last_charge = invoice.fees.find { |fee| fee.amount_cents == 200_33 } + estimate_credit_note( + invoice_id: invoice.id, + items: [ + { + fee_id: last_charge.id, + amount_cents: 200_33 + } + ] + ) + + estimate = json[:estimated_credit_note] + expect(estimate).to include( + taxes_amount_cents: 3868, + precise_taxes_amount_cents: "3868.0", + sub_total_excluding_taxes_amount_cents: 19339, + max_creditable_amount_cents: 23207, + coupons_adjustment_amount_cents: 694, + taxes_rate: 20.0 + ) + create_credit_note( + invoice_id: invoice.id, + reason: :other, + credit_amount_cents: 23207, + items: [ + { + fee_id: last_charge.id, + amount_cents: 200_33 + } + ] + ) + + credit_note = invoice.credit_notes.order(:created_at).last + expect(credit_note).to have_attributes( + credit_amount_cents: 23207, + total_amount_cents: 23207, + taxes_amount_cents: 3868, + precise_taxes_amount_cents: 3868.0, + precise_coupons_adjustment_amount_cents: 694.0239 + ) + expect(credit_note.precise_total).to eq(23206.9761) + expect(credit_note.taxes_rounding_adjustment).to eq(0) + # real remaining: 232_07 - 23_207 = 0 + expect(invoice.creditable_amount_cents).to eq(0) + end + end + end end diff --git a/spec/services/credit_notes/validate_item_service_spec.rb b/spec/services/credit_notes/validate_item_service_spec.rb index 7aea3019729..1aeea95c14d 100644 --- a/spec/services/credit_notes/validate_item_service_spec.rb +++ b/spec/services/credit_notes/validate_item_service_spec.rb @@ -95,20 +95,5 @@ end end end - - context 'when reaching invoice creditable amount' do - before do - create(:credit_note, invoice:, total_amount_cents: 99) - end - - it 'fails the validation' do - aggregate_failures do - expect(validator).not_to be_valid - - expect(result.error).to be_a(BaseService::ValidationFailure) - expect(result.error.messages[:amount_cents]).to eq(['higher_than_remaining_invoice_amount']) - end - end - end end end diff --git a/spec/support/scenarios_helper.rb b/spec/support/scenarios_helper.rb index 6e304980957..29147e7a3cb 100644 --- a/spec/support/scenarios_helper.rb +++ b/spec/support/scenarios_helper.rb @@ -69,6 +69,29 @@ def update_invoice(invoice, params) put_with_token(organization, "/api/v1/invoices/#{invoice.id}", {invoice: params}) end + def create_one_off_invoice(customer, addons) + create_invoice_params = { + external_customer_id: customer.external_id, + currency: "EUR", + fees: [], + timestamp: Time.zone.now.to_i + } + addons.each do |fee| + fee_addon_params = { + add_on_id: fee.id, + add_on_code: fee.code, + name: fee.name, + units: 1, + unit_amount_cents: fee.amount_cents, + tax_codes: [ + tax.code + ] + } + create_invoice_params[:fees].push(fee_addon_params) + end + post_with_token(organization, "/api/v1/invoices", {invoice: create_invoice_params}) + end + ### Coupons def create_coupon(params)