mercury_banking 0.5.38 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +46 -2
- data/CHANGELOG.md +16 -0
- data/Gemfile +13 -12
- data/Gemfile.lock +1 -1
- data/Rakefile +3 -1
- data/bin/console +1 -0
- data/bin/mercury +2 -1
- data/lib/mercury_banking/api.rb +26 -23
- data/lib/mercury_banking/cli/accounts.rb +24 -24
- data/lib/mercury_banking/cli/base.rb +48 -26
- data/lib/mercury_banking/cli/financials.rb +177 -252
- data/lib/mercury_banking/cli/reconciliation.rb +284 -371
- data/lib/mercury_banking/cli/reports.rb +82 -74
- data/lib/mercury_banking/cli/transactions.rb +60 -62
- data/lib/mercury_banking/cli.rb +56 -51
- data/lib/mercury_banking/formatters/export_formatter.rb +99 -97
- data/lib/mercury_banking/formatters/table_formatter.rb +32 -30
- data/lib/mercury_banking/multi.rb +43 -37
- data/lib/mercury_banking/recipient.rb +17 -9
- data/lib/mercury_banking/reconciliation.rb +57 -58
- data/lib/mercury_banking/reports/balance_sheet.rb +210 -218
- data/lib/mercury_banking/reports/reconciliation.rb +114 -100
- data/lib/mercury_banking/utils/command_utils.rb +3 -1
- data/lib/mercury_banking/version.rb +3 -1
- data/lib/mercury_banking.rb +2 -3
- data/mercury_banking.gemspec +15 -12
- metadata +40 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c388af3fd079eacd9a2ba3fbc78a137ecb57352bd7deda55bcebbb372149126
|
4
|
+
data.tar.gz: 5665853a81cb64eb76128d70a60a3d830b732fa9ae9096ab2f6bae2d5ea858fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8161c1c7afb40e33bb6dd29dbd843d01b969616be1ef4ed45983c2ea65dc2047344ca204e3043085351f8da86822e11ab015d56c4fa077400dd0982047186f75
|
7
|
+
data.tar.gz: 97f6fa41b322f94b53a4b69ee9255ceeb9083f3ec5934f35fc1b3952166aa9b0e16767a6460c688d0d7e4cd9aa6f43d65b1eeb3bac0eedb9f519c6679c11aab9
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion:
|
2
|
+
TargetRubyVersion: 3.0
|
3
3
|
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
4
5
|
|
5
6
|
# Disable the rule that changes $? to $CHILD_STATUS
|
6
7
|
Style/GlobalVars:
|
@@ -14,4 +15,47 @@ Style/SpecialGlobalVars:
|
|
14
15
|
|
15
16
|
# Allow slightly longer methods since we've already done significant refactoring
|
16
17
|
Metrics/MethodLength:
|
17
|
-
Max:
|
18
|
+
Max: 50 # Default is 10, we're increasing it to 50
|
19
|
+
Exclude:
|
20
|
+
- 'spec/**/*'
|
21
|
+
|
22
|
+
# Disable enforcement of single quotes over double quotes
|
23
|
+
Style/StringLiterals:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
# Increase line length limit
|
27
|
+
Layout/LineLength:
|
28
|
+
Max: 150
|
29
|
+
Exclude:
|
30
|
+
- 'spec/**/*'
|
31
|
+
|
32
|
+
# Increase block length limit for specs and complex methods
|
33
|
+
Metrics/BlockLength:
|
34
|
+
Max: 100
|
35
|
+
Exclude:
|
36
|
+
- 'spec/**/*'
|
37
|
+
|
38
|
+
# Increase class and module length limits
|
39
|
+
Metrics/ClassLength:
|
40
|
+
Max: 150
|
41
|
+
|
42
|
+
Metrics/ModuleLength:
|
43
|
+
Max: 500
|
44
|
+
|
45
|
+
# Increase complexity limits
|
46
|
+
Metrics/AbcSize:
|
47
|
+
Max: 75
|
48
|
+
|
49
|
+
Metrics/CyclomaticComplexity:
|
50
|
+
Max: 30
|
51
|
+
|
52
|
+
Metrics/PerceivedComplexity:
|
53
|
+
Max: 30
|
54
|
+
|
55
|
+
# Increase parameter list limit
|
56
|
+
Metrics/ParameterLists:
|
57
|
+
Max: 12
|
58
|
+
|
59
|
+
# Allow optional boolean parameters
|
60
|
+
Style/OptionalBooleanParameter:
|
61
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [0.7.0] - 2025-03-11
|
4
|
+
### Changed
|
5
|
+
- Simplified gem functionality to focus on core features
|
6
|
+
- Removed balance sheet generation and only kept balance checking functionality
|
7
|
+
- Removed all reconciliation functionality (reconcile, reconcile_all, reconciliation_status, unreconcile)
|
8
|
+
- Added new `balancecheck` command that checks ledger balances against Mercury account balances
|
9
|
+
- Reduced dependencies by removing unnecessary modules and imports
|
10
|
+
|
11
|
+
## [0.6.0] - 2025-03-10
|
12
|
+
### Changed
|
13
|
+
- Major code refactoring to improve code quality and maintainability
|
14
|
+
- Extracted helper methods into separate modules
|
15
|
+
- Reduced method complexity and size
|
16
|
+
- Fixed various RuboCop issues
|
17
|
+
- Improved code organization by separating concerns
|
18
|
+
|
3
19
|
## [0.5.34] - 2025-03-10
|
4
20
|
### Fixed
|
5
21
|
- Added missing `thor` gem dependency to fix LoadError when running CLI commands
|
data/Gemfile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source 'https://rubygems.org'
|
2
4
|
|
3
5
|
ruby '3.3.5'
|
@@ -5,22 +7,21 @@ ruby '3.3.5'
|
|
5
7
|
# Specify your gem's dependencies in mercury_banking.gemspec
|
6
8
|
gemspec
|
7
9
|
|
8
|
-
gem 'dotenv'
|
9
10
|
gem 'activesupport'
|
10
|
-
gem '
|
11
|
-
gem '
|
12
|
-
gem '
|
13
|
-
gem '
|
14
|
-
gem '
|
11
|
+
gem 'base64'
|
12
|
+
gem 'csv'
|
13
|
+
gem 'dead_end', require: false # Helps find syntax errors
|
14
|
+
gem 'debride', require: false # Finds unused code
|
15
|
+
gem 'dotenv'
|
15
16
|
gem 'lockbox'
|
17
|
+
gem 'pry'
|
16
18
|
gem 'rake', '~> 13.0'
|
17
19
|
gem 'rspec', '~> 3.0'
|
18
20
|
gem 'rubocop', require: false
|
19
21
|
gem 'rubocop-performance', require: false
|
20
|
-
gem 'rubocop-rspec', require: false
|
21
22
|
gem 'rubocop-rake', require: false
|
22
|
-
gem '
|
23
|
-
gem '
|
24
|
-
gem '
|
25
|
-
gem '
|
26
|
-
|
23
|
+
gem 'rubocop-rspec', require: false
|
24
|
+
gem 'symmetric-encryption'
|
25
|
+
gem 'terminal-table'
|
26
|
+
gem 'thor'
|
27
|
+
gem 'webmock', '~> 3.18'
|
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/bin/mercury
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'thor'
|
4
5
|
require 'mercury_banking'
|
@@ -14,4 +15,4 @@ require 'mercury_banking/cli'
|
|
14
15
|
require 'symmetric-encryption'
|
15
16
|
|
16
17
|
# Start the CLI
|
17
|
-
MercuryBanking::CLI::Main.start(ARGV)
|
18
|
+
MercuryBanking::CLI::Main.start(ARGV)
|
data/lib/mercury_banking/api.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MercuryBanking
|
4
|
+
# API client for Mercury Banking
|
5
|
+
# Handles all API requests to the Mercury Banking API
|
2
6
|
class API
|
3
7
|
def initialize(secret)
|
4
8
|
@api_key = secret
|
@@ -40,10 +44,8 @@ module MercuryBanking
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def validate_body(body, path)
|
43
|
-
if body["errors"]
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
+
raise "#{@api_key} access to #{path} errored with #{body['errors']['message']}" if body["errors"]
|
48
|
+
|
47
49
|
body
|
48
50
|
end
|
49
51
|
|
@@ -86,6 +88,7 @@ module MercuryBanking
|
|
86
88
|
def find_account_by_number(account_number)
|
87
89
|
account = accounts.find { |a| a["accountNumber"] == account_number.to_s }
|
88
90
|
raise "Account with number #{account_number} not found" unless account
|
91
|
+
|
89
92
|
account
|
90
93
|
end
|
91
94
|
|
@@ -93,37 +96,35 @@ module MercuryBanking
|
|
93
96
|
account = get_account(account_id)
|
94
97
|
account['currentBalance']
|
95
98
|
end
|
96
|
-
|
99
|
+
|
97
100
|
# /account/:id/transactions
|
98
101
|
def get_transactions(account_id, start_date = nil)
|
99
102
|
path = "account/#{account_id}/transactions"
|
100
103
|
path += "?start=#{start_date}" if start_date
|
101
104
|
get(path)["transactions"]
|
102
105
|
end
|
103
|
-
|
106
|
+
|
104
107
|
# Get transactions from all accounts
|
105
108
|
def get_all_transactions(start_date = nil)
|
106
109
|
all_transactions = []
|
107
|
-
|
110
|
+
|
108
111
|
accounts.each do |account|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
transaction["accountNumber"] = account["accountNumber"]
|
116
|
-
end
|
117
|
-
all_transactions.concat(account_transactions)
|
118
|
-
rescue StandardError => e
|
119
|
-
puts "Warning: Could not fetch transactions for account #{account["name"]}: #{e.message}" unless ENV['MERCURY_SILENT']
|
112
|
+
account_transactions = get_transactions(account["id"], start_date)
|
113
|
+
# Add account information to each transaction
|
114
|
+
account_transactions.each do |transaction|
|
115
|
+
transaction["accountName"] = account["name"]
|
116
|
+
transaction["accountId"] = account["id"]
|
117
|
+
transaction["accountNumber"] = account["accountNumber"]
|
120
118
|
end
|
119
|
+
all_transactions.concat(account_transactions)
|
120
|
+
rescue StandardError => e
|
121
|
+
puts "Warning: Could not fetch transactions for account #{account['name']}: #{e.message}" unless ENV['MERCURY_SILENT']
|
121
122
|
end
|
122
|
-
|
123
|
+
|
123
124
|
# Sort all transactions by date (newest first)
|
124
125
|
all_transactions.sort_by { |t| t["createdAt"] || "" }.reverse
|
125
126
|
end
|
126
|
-
|
127
|
+
|
127
128
|
# /account/:id/transactions/:id
|
128
129
|
def transaction(account_id, transaction_id)
|
129
130
|
path = "account/#{account_id}/transaction/#{transaction_id}"
|
@@ -158,10 +159,12 @@ module MercuryBanking
|
|
158
159
|
post(rec.json, 'recipients')
|
159
160
|
new_recipient = find_recipient(name: name)
|
160
161
|
raise "Couldn't add account #{name}" unless new_recipient
|
162
|
+
|
161
163
|
puts "Successfully added #{new_recipient['name']}" if new_recipient
|
162
164
|
end
|
163
165
|
|
164
|
-
def update_recipient(name:, address:, email:, city:, region:, postal_code:, country:, account_number:,
|
166
|
+
def update_recipient(name:, address:, email:, city:, region:, postal_code:, country:, account_number:,
|
167
|
+
routing_number:, recipient_id:)
|
165
168
|
rec = MercuryBanking::Recipient.new(
|
166
169
|
name: name,
|
167
170
|
address: address,
|
@@ -179,7 +182,7 @@ module MercuryBanking
|
|
179
182
|
def transfer(recipient_id:, amount:, account_id:, note: nil, external: nil)
|
180
183
|
payload = { recipientId: recipient_id, amount: amount, paymentMethod: 'ach',
|
181
184
|
note: note, externalMemo: external, idempotencyKey: "#{recipient_id}-#{amount}-#{note}-#{external}" }
|
182
|
-
|
185
|
+
post(payload, "account/#{account_id}/transactions")
|
183
186
|
end
|
184
187
|
end
|
185
|
-
end
|
188
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MercuryBanking
|
2
4
|
module CLI
|
3
5
|
# Module for account-related commands
|
@@ -6,51 +8,49 @@ module MercuryBanking
|
|
6
8
|
def self.included(base)
|
7
9
|
base.class_eval do
|
8
10
|
desc 'accounts', 'Display all accounts'
|
9
|
-
method_option :format, type: :string, default: 'table', enum: [
|
11
|
+
method_option :format, type: :string, default: 'table', enum: %w[table json],
|
12
|
+
desc: 'Output format (table or json)'
|
10
13
|
def accounts
|
11
14
|
with_api_client do |client|
|
12
15
|
accounts = client.accounts
|
13
|
-
|
16
|
+
|
14
17
|
if options[:json] || options[:format] == 'json'
|
15
18
|
puts JSON.pretty_generate(accounts)
|
16
19
|
else
|
17
20
|
display_accounts_table(accounts)
|
18
21
|
end
|
19
|
-
|
22
|
+
|
20
23
|
puts "You have #{accounts.count} account/s" unless options[:json]
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
|
-
desc 'balance ACCOUNT_ID_OR_NUMBER',
|
27
|
+
desc 'balance ACCOUNT_ID_OR_NUMBER',
|
28
|
+
"Display the current balance of an account (using account number from accounts table)"
|
25
29
|
def balance(account_identifier)
|
26
30
|
with_api_client do |client|
|
27
31
|
# Determine if we're dealing with an account ID or account number
|
28
32
|
account_id = nil
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# If not found by number, assume it's an ID
|
35
|
-
account_id = account_identifier
|
36
|
-
account = client.get_account(account_id)
|
37
|
-
end
|
38
|
-
else
|
33
|
+
begin
|
34
|
+
account = client.find_account_by_number(account_identifier)
|
35
|
+
account_id = account['id']
|
36
|
+
rescue StandardError
|
37
|
+
# If not found by number, assume it's an ID
|
39
38
|
account_id = account_identifier
|
40
|
-
account = client.get_account(account_id)
|
41
39
|
end
|
42
|
-
|
40
|
+
|
41
|
+
account = client.get_account(account_id)
|
42
|
+
|
43
43
|
balance = client.balance(account_id)
|
44
|
-
|
44
|
+
|
45
45
|
if options[:json]
|
46
46
|
puts JSON.pretty_generate({
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
'account_id' => account_id,
|
48
|
+
'account_number' => account['accountNumber'],
|
49
|
+
'name' => account['name'],
|
50
|
+
'balance' => balance
|
51
|
+
})
|
52
52
|
else
|
53
|
-
puts "#{account['name']} (#{account['accountNumber']}): $#{format(
|
53
|
+
puts "#{account['name']} (#{account['accountNumber']}): $#{format('%.2f', balance)}"
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -58,4 +58,4 @@ module MercuryBanking
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
|
-
end
|
61
|
+
end
|
@@ -1,3 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'symmetric-encryption'
|
5
|
+
require 'json'
|
6
|
+
require 'base64'
|
7
|
+
require 'fileutils'
|
8
|
+
|
1
9
|
module MercuryBanking
|
2
10
|
module CLI
|
3
11
|
# Base module for CLI functionality
|
@@ -6,7 +14,7 @@ module MercuryBanking
|
|
6
14
|
def with_api_client
|
7
15
|
api_key = get_api_key
|
8
16
|
raise "API key not found. Please run 'mercury set_key' first." unless api_key
|
9
|
-
|
17
|
+
|
10
18
|
client = MercuryBanking::API.new(api_key)
|
11
19
|
yield client
|
12
20
|
rescue StandardError => e
|
@@ -18,41 +26,55 @@ module MercuryBanking
|
|
18
26
|
exit 1
|
19
27
|
end
|
20
28
|
|
21
|
-
# Get
|
29
|
+
# Get API key by decrypting the stored encrypted key
|
22
30
|
def get_api_key
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
config_dir = File.join(Dir.home, '.mercury-banking')
|
32
|
+
key_config_path = File.join(config_dir, 'key_config.json')
|
33
|
+
api_key_path = File.join(config_dir, 'api_key.enc')
|
34
|
+
|
35
|
+
unless File.exist?(key_config_path) && File.exist?(api_key_path)
|
36
|
+
return nil
|
28
37
|
end
|
38
|
+
|
39
|
+
# Load the cipher configuration
|
40
|
+
cipher_config = JSON.parse(File.read(key_config_path))
|
41
|
+
|
42
|
+
# Recreate the cipher
|
43
|
+
cipher = SymmetricEncryption::Cipher.new(
|
44
|
+
key: Base64.strict_decode64(cipher_config['key']),
|
45
|
+
iv: Base64.strict_decode64(cipher_config['iv']),
|
46
|
+
cipher_name: cipher_config['cipher_name']
|
47
|
+
)
|
48
|
+
|
49
|
+
# Set the cipher as the primary one
|
50
|
+
SymmetricEncryption.cipher = cipher
|
29
51
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
rescue => e
|
46
|
-
puts "Error decrypting API key: #{e.message}"
|
52
|
+
# Load and decrypt the API key
|
53
|
+
encrypted_key = File.read(api_key_path)
|
54
|
+
cipher.decrypt(encrypted_key)
|
55
|
+
rescue StandardError => e
|
56
|
+
puts "Error decrypting API key: #{e.message}" unless options[:json]
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get the API key from the configuration file (legacy method)
|
61
|
+
def api_key
|
62
|
+
config_path = File.join(Dir.home, '.mercury', 'config')
|
63
|
+
key_path = File.join(Dir.home, '.mercury', 'key')
|
64
|
+
|
65
|
+
unless File.exist?(config_path) && File.exist?(key_path)
|
66
|
+
puts "Error: Mercury API key not found. Please run 'mercury configure' first."
|
47
67
|
exit 1
|
48
68
|
end
|
69
|
+
|
70
|
+
File.read(key_path).strip
|
49
71
|
end
|
50
72
|
|
51
73
|
# Log transfer details
|
52
74
|
def log_transfer(from, to, amount, note, status)
|
53
75
|
log_dir = File.join(Dir.home, '.mercury-banking', 'logs')
|
54
76
|
FileUtils.mkdir_p(log_dir)
|
55
|
-
|
77
|
+
|
56
78
|
log_file = File.join(log_dir, 'transfers.log')
|
57
79
|
File.open(log_file, 'a') do |f|
|
58
80
|
f.puts "#{Time.now.iso8601},\"#{from}\",\"#{to}\",\"#{amount}\",\"#{note}\",\"#{status}\""
|
@@ -65,4 +87,4 @@ module MercuryBanking
|
|
65
87
|
end
|
66
88
|
end
|
67
89
|
end
|
68
|
-
end
|
90
|
+
end
|