Skip to content

Commit

Permalink
feat: separate fees from payments
Browse files Browse the repository at this point in the history
  • Loading branch information
adamcooke committed Oct 20, 2020
1 parent cf2be53 commit 2588a46
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 173 deletions.
29 changes: 8 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ export.invoice_contact_name = 'Generic Customer'
# the export. It's important to ensure that you categorise the tax
# element on this as that forms a key part of the export.
export.add_invoice do |invoice|
# For logging & reporting purposes, you can add the invoice
# ID and/or number here. This won't actually be exported to Xero but
# may be useful later for debugging purposes.
invoice.id = 'inv_abcdef12345'
invoice.number = 'INV-2020'

# Specify the country code for this invoice
invoice.country = 'GB'

Expand All @@ -69,36 +63,29 @@ end
# Next, add all the payments that have been received on this day
# to the export.
export.add_payment do |payment|
# As with invoices, this isn't exported but useful for logging
# and debugging.
payment.id = 'pay_abcdef12345'

# Specify the amount of money that was received on this day
payment.amount = 240.0

# Specify the bank account code that the money was deposited into
payment.bank_account = '010'
end

# Specify the amount of fees that have been deducted for this payment.
# If fees are deducted by a separate invoice by your payment provider
# you should not specify these here.
payment.fees = 2.30
# Next add any payment fees that have been charged to us and thus deducted
# from the amount we will be receiving into this bank account. Use a
# category to split up fees on the bank transaction line.
export.add_fee do |fee|
fee.category = 'General fees'
fee.amount = 2.30
fee.bank_account = '010'
end

# Next, do exactly the same as above with refunds really.
export.add_refund do |refund|
# The refund identifier for logging/debugging
refund.id = 'ref_abcde12345'

# Specify the amount of the refund
refund.amount = 10.0

# Specify the bank account that the refund was taken from
refund.bank_account = '010'

# If any fees were charged or refunded to you for this refund, you
# can specify them here. Refunded fees should be entered negatively.
refund.fees = -0.50
end

# When you've added all your data to this export, you can go ahead and
Expand Down
44 changes: 26 additions & 18 deletions lib/xero_exporter/executor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def execute_all
create_credit_note_payment
add_payments
add_refunds
add_fees
true
end

Expand Down Expand Up @@ -105,33 +106,38 @@ def create_credit_note
#
# @return [void]
def add_payments
@proposal.payments.each do |bank_account, amounts|
run_task "add_payments_#{bank_account}_transfer" do
transfer = add_bank_transfer(@export.receivables_account, bank_account, amounts[:amount])
@proposal.payments.each do |bank_account, amount|
run_task "add_payments_#{bank_account}" do
transfer = add_bank_transfer(@export.receivables_account, bank_account, amount)
current_state[:transfer_id] = transfer['BankTransferID']
end

run_task "add_payments_#{bank_account}_fee" do
if fee = add_fee_transaction(bank_account, amounts[:fees])
current_state[:fee_transaction_id] = fee['BankTransactionID']
end
end
end
end

# Create refunds for all payments in the export
#
# @return [void]
def add_refunds
@proposal.refunds.each do |bank_account, amounts|
run_task "add_refunds_#{bank_account}_transfer" do
transfer = add_bank_transfer(bank_account, @export.receivables_account, amounts[:amount])
current_state[:transfer_id] = transfer['BankTransferID']
@proposal.refunds.each do |bank_account, amount|
run_task "add_refunds_#{bank_account}" do
if transfer = add_bank_transfer(bank_account, @export.receivables_account, amount)
current_state[:transfer_id] = transfer['BankTransferID']
end
end
end
end

run_task "add_refunds_#{bank_account}_fee" do
if fee = add_fee_transaction(bank_account, amounts[:fees])
current_state[:fee_transaction_id] = fee['BankTransactionID']
# Create payments for all payments in the export
#
# @return [void]
def add_fees
@proposal.fees.each do |bank_account, amounts_by_category|
amounts_by_category.each do |category, amount|
category_for_key = category&.downcase&.gsub(' ', '_') || 'none'
run_task "add_fees_#{bank_account}_#{category_for_key}" do
if transaction = add_fee_transaction(bank_account, amount, category)
current_state[:transaction_id] = transaction['BankTransactionID']
end
end
end
end
Expand Down Expand Up @@ -263,9 +269,11 @@ def tracking_options
# @param bank_account [String]
# @param amount [Float]
# @return [void]
def add_fee_transaction(bank_account, amount)
def add_fee_transaction(bank_account, amount, description = nil)
return if amount.zero?

logger.debug "Creating fee transaction for #{amount} from #{bank_account} (#{description})"

@api.post('BankTransactions', {
'Type' => amount.negative? ? 'RECEIVE' : 'SPEND',
'Contact' => {
Expand All @@ -277,7 +285,7 @@ def add_fee_transaction(bank_account, amount)
'Reference' => @export.reference,
'LineItems' => [
{
'Description' => 'Fees',
'Description' => description || 'Fees',
'UnitAmount' => amount.abs,
'AccountCode' => @export.fee_accounts[bank_account] || '404',
'Tracking' => tracking_options
Expand Down
10 changes: 10 additions & 0 deletions lib/xero_exporter/export.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'xero_exporter/invoice'
require 'xero_exporter/payment'
require 'xero_exporter/refund'
require 'xero_exporter/fee'
require 'xero_exporter/executor'

module XeroExporter
Expand All @@ -21,6 +22,7 @@ class Export
attr_reader :invoices
attr_reader :payments
attr_reader :refunds
attr_reader :fees

attr_reader :payment_providers
attr_reader :account_names
Expand All @@ -33,6 +35,7 @@ def initialize
@invoices = []
@payments = []
@refunds = []
@fees = []
@payment_providers = {}
@fee_accounts = {}
@account_names = {}
Expand Down Expand Up @@ -72,6 +75,13 @@ def add_refund
refund
end

def add_fee
fee = Fee.new
yield fee if block_given?
@fees << fee
fee
end

def execute(api, **options)
executor = Executor.new(self, api, **options)
yield executor if block_given?
Expand Down
11 changes: 11 additions & 0 deletions lib/xero_exporter/fee.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module XeroExporter
class Fee

attr_accessor :amount
attr_accessor :category
attr_accessor :bank_account

end
end
1 change: 0 additions & 1 deletion lib/xero_exporter/payment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class Payment
attr_accessor :id
attr_accessor :bank_account
attr_accessor :amount
attr_accessor :fees

end
end
20 changes: 14 additions & 6 deletions lib/xero_exporter/proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ def credit_note_lines
#
# @return [Hash]
def payments
export_payments = Hash.new { |h, k| h[k] = { amount: 0.0, fees: 0.0 } }
export_payments = Hash.new(0.0)
@export.payments.each do |payment|
key = payment.bank_account
export_payments[key][:amount] += payment.amount || 0.0
export_payments[key][:fees] += payment.fees || 0.0
export_payments[key] += payment.amount || 0.0
end
export_payments
end
Expand All @@ -40,15 +39,24 @@ def payments
#
# @return [Hash]
def refunds
export_refunds = Hash.new { |h, k| h[k] = { amount: 0.0, fees: 0.0 } }
export_refunds = Hash.new(0.0)
@export.refunds.each do |refund|
key = refund.bank_account
export_refunds[key][:amount] += refund.amount || 0.0
export_refunds[key][:fees] += refund.fees || 0.0
export_refunds[key] += refund.amount || 0.0
end
export_refunds
end

# Return the fees grouped by bank account & category
#
# @return [Hash]
def fees
initial_hash = Hash.new { |h, k| h[k] = Hash.new(0.0) }
@export.fees.each_with_object(initial_hash) do |fee, hash|
hash[fee.bank_account][fee.category] += fee.amount || 0.0
end
end

# Return the text to go with an invoice line
#
# @return [String]
Expand Down
1 change: 0 additions & 1 deletion lib/xero_exporter/refund.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class Refund
attr_accessor :id
attr_accessor :bank_account
attr_accessor :amount
attr_accessor :fees

end
end
99 changes: 99 additions & 0 deletions spec/specs/xero_exporter/executor/add_fees_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

require 'spec_helper'

describe XeroExporter::Executor do
context '#add_fees' do
it 'should create bank transactions' do
executor, state = create_executor do |e, api|
e.add_fee do |fee|
fee.amount = 1.50
fee.category = 'Bank Fees'
fee.bank_account = '010'
end

e.add_fee do |fee|
fee.amount = 5.0
fee.category = 'Fraud Fees'
fee.bank_account = '010'
end

e.add_fee do |fee|
fee.amount = 1.50
fee.category = 'Bank Fees'
fee.bank_account = '011'
end

# We'll want to get the ID for the contact so we'll return a contact
# in this result.
expect(api).to receive(:get).with('Contacts', anything).at_least(:once) do |_, params|
case params[:where]
when /Stripe/
{ 'Contacts' => [{ 'ContactID' => 'stripe' }] }
when /Generic/
{ 'Contacts' => [{ 'ContactID' => 'generic' }] }
else
{ 'Contacts' => [{ 'ContactID' => 'other' }] }
end
end

# We'll expect that the invoice will be posted to the Xero API.
invoked_times = 0
expect(api).to receive(:post).with('BankTransactions', anything).exactly(3).times do |_, params|
expect(params['Date']).to eq '2020-10-02'
expect(params['Type']).to eq 'SPEND'
case invoked_times
when 0
expect(params['BankAccount']['Code']).to eq '010'
expect(params['Contact']['ContactID']).to eq 'stripe'
expect(params['LineItems'][0]['Description']).to eq 'Bank Fees'
expect(params['LineItems'][0]['UnitAmount']).to eq 1.5
expect(params['LineItems'][0]['AccountCode']).to eq '010.404'
when 1
expect(params['BankAccount']['Code']).to eq '010'
expect(params['Contact']['ContactID']).to eq 'stripe'
expect(params['LineItems'][0]['Description']).to eq 'Fraud Fees'
expect(params['LineItems'][0]['UnitAmount']).to eq 5.0
expect(params['LineItems'][0]['AccountCode']).to eq '010.404'
when 2
expect(params['BankAccount']['Code']).to eq '011'
expect(params['Contact']['ContactID']).to eq 'generic'
expect(params['LineItems'][0]['Description']).to eq 'Bank Fees'
expect(params['LineItems'][0]['UnitAmount']).to eq 1.5
expect(params['LineItems'][0]['AccountCode']).to eq '404'
end
invoked_times += 1
{
'BankTransactions' => [{
'BankTransactionID' =>
"transaction-for-#{params['BankAccount']['Code']}-#{params['LineItems'][0]['Description']}"
}]
}
end
end

executor.add_fees
expect(state[:add_fees_010_bank_fees][:state]).to eq 'complete'
expect(state[:add_fees_010_bank_fees][:transaction_id]).to eq 'transaction-for-010-Bank Fees'

expect(state[:add_fees_010_fraud_fees][:state]).to eq 'complete'
expect(state[:add_fees_010_fraud_fees][:transaction_id]).to eq 'transaction-for-010-Fraud Fees'

expect(state[:add_fees_011_bank_fees][:state]).to eq 'complete'
expect(state[:add_fees_011_bank_fees][:transaction_id]).to eq 'transaction-for-011-Bank Fees'

expect(@logger_string_io.string).to include 'Running add_fees_010_bank_fees task'
expect(@logger_string_io.string).to include 'Running add_fees_010_fraud_fees task'
expect(@logger_string_io.string).to include 'Running add_fees_011_bank_fees task'
expect(@logger_string_io.string).to include 'Creating fee transaction for 1.5 from 010 (Bank Fees)'
expect(@logger_string_io.string).to include 'Creating fee transaction for 5.0 from 010 (Fraud Fees)'
expect(@logger_string_io.string).to include 'Creating fee transaction for 1.5 from 011 (Bank Fees)'
end

it "won't run when there are no payments" do
executor, state = create_executor
executor.add_payments
expect(state).to be_empty
end
end
end
Loading

0 comments on commit 2588a46

Please sign in to comment.