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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MercuryBanking
2
4
  module Formatters
3
5
  # Formatter for table output
@@ -5,7 +7,7 @@ module MercuryBanking
5
7
  # Display accounts in a table format
6
8
  def display_accounts_table(accounts)
7
9
  return puts "No accounts found." if accounts.empty?
8
-
10
+
9
11
  rows = accounts.map do |a|
10
12
  [
11
13
  a["name"],
@@ -16,23 +18,23 @@ module MercuryBanking
16
18
  a["status"].capitalize
17
19
  ]
18
20
  end
19
-
21
+
20
22
  table = ::Terminal::Table.new(
21
23
  headings: ['Account Name', 'Account Number', 'Type', 'Current Balance', 'Available Balance', 'Status'],
22
24
  rows: rows
23
25
  )
24
-
26
+
25
27
  puts table
26
28
  end
27
29
 
28
30
  # Display recipients in a table format
29
31
  def display_recipients_table(recipients)
30
32
  return puts "No recipients found." if recipients.empty?
31
-
33
+
32
34
  rows = recipients.map do |r|
33
35
  account_info = r["electronicRoutingInfo"] || {}
34
36
  address_info = r["address"] || {}
35
-
37
+
36
38
  [
37
39
  r["id"],
38
40
  r["name"],
@@ -42,61 +44,61 @@ module MercuryBanking
42
44
  r["status"].capitalize
43
45
  ]
44
46
  end
45
-
47
+
46
48
  table = ::Terminal::Table.new(
47
49
  headings: ['ID', 'Name', 'Routing #', 'Account #', 'City', 'Status'],
48
50
  rows: rows
49
51
  )
50
-
52
+
51
53
  puts table
52
54
  end
53
-
55
+
54
56
  # Display transactions in a table format
55
57
  def display_transactions_table(transactions, show_account_name = false)
56
58
  return puts "No transactions found." if transactions.empty?
57
-
58
- headings = ['Date', 'Amount', 'Type']
59
+
60
+ headings = %w[Date Amount Type]
59
61
  headings.insert(0, 'Account') if show_account_name
60
- headings.concat(['Description', 'Status'])
61
-
62
+ headings.push("Description", "Status")
63
+
62
64
  rows = transactions.map do |t|
63
65
  date = t["postedAt"] ? Time.parse(t["postedAt"]).strftime("%Y-%m-%d") : "Pending"
64
66
  amount = format("$%.2f", t["amount"].abs)
65
- direction = t["amount"] < 0 ? "DEBIT" : "CREDIT"
67
+ direction = (t["amount"]).negative? ? "DEBIT" : "CREDIT"
66
68
  description = t["bankDescription"] || t["externalMemo"] || "N/A"
67
69
  description = description.length > 50 ? "#{description[0..47]}..." : description
68
70
  status = t["status"].capitalize
69
-
71
+
70
72
  row = [date, amount, direction, description, status]
71
73
  row.insert(0, t["accountName"]) if show_account_name
72
74
  row
73
75
  end
74
-
76
+
75
77
  table = ::Terminal::Table.new(
76
78
  headings: headings,
77
79
  rows: rows
78
80
  )
79
-
81
+
80
82
  puts table
81
83
  end
82
-
84
+
83
85
  # Display a single transaction's details
84
86
  def display_transaction_details(transaction)
85
87
  return puts "Transaction not found." unless transaction
86
-
88
+
87
89
  # Format dates
88
90
  created_at = transaction["createdAt"] ? Time.parse(transaction["createdAt"]).strftime("%Y-%m-%d %H:%M:%S") : "N/A"
89
91
  posted_at = transaction["postedAt"] ? Time.parse(transaction["postedAt"]).strftime("%Y-%m-%d %H:%M:%S") : "Pending"
90
92
  estimated_delivery = transaction["estimatedDeliveryDate"] ? Time.parse(transaction["estimatedDeliveryDate"]).strftime("%Y-%m-%d") : "N/A"
91
-
93
+
92
94
  # Format amount
93
95
  amount = transaction["amount"].to_f
94
96
  amount_str = format("$%.2f", amount.abs)
95
- direction = amount < 0 ? "DEBIT" : "CREDIT"
96
-
97
+ direction = amount.negative? ? "DEBIT" : "CREDIT"
98
+
97
99
  # Get counterparty info
98
- counterparty = transaction["counterpartyId"] ? "ID: #{transaction["counterpartyId"]}" : "N/A"
99
-
100
+ counterparty = transaction["counterpartyId"] ? "ID: #{transaction['counterpartyId']}" : "N/A"
101
+
100
102
  # Create a table for the transaction details
101
103
  details = [
102
104
  ["Transaction ID", transaction["id"]],
@@ -110,24 +112,24 @@ module MercuryBanking
110
112
  ["External Memo", transaction["externalMemo"] || "N/A"],
111
113
  ["Counterparty", counterparty]
112
114
  ]
113
-
115
+
114
116
  table = ::Terminal::Table.new(
115
117
  title: "Transaction Details",
116
118
  rows: details
117
119
  )
118
-
120
+
119
121
  puts table
120
122
  end
121
-
123
+
122
124
  private
123
-
125
+
124
126
  # Mask account number for security
125
127
  def mask_account_number(account_number)
126
128
  return "N/A" unless account_number
127
-
129
+
128
130
  # Show only the last 4 digits
129
- "••••#{account_number[-4..-1]}" if account_number.length >= 4
131
+ "••••#{account_number[-4..]}" if account_number.length >= 4
130
132
  end
131
133
  end
132
134
  end
133
- end
135
+ end
@@ -1,35 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MercuryBanking
4
+ # Multi-account management class
5
+ # Handles operations across multiple Mercury accounts
2
6
  class Multi
3
7
  def initialize(keys = [])
4
8
  @banks = {}
5
9
  @apis = {}
6
10
  @keys = keys
7
-
11
+
8
12
  @keys.each do |api_key|
9
13
  next if api_key.empty?
14
+
10
15
  mercury = MercuryBanking::API.new(api_key)
11
-
16
+
12
17
  begin
13
18
  accounts = mercury.accounts
14
19
  rescue StandardError => e
15
20
  puts e
16
21
  next
17
22
  end
18
-
23
+
19
24
  if accounts.nil?
20
25
  puts "*" * 80
21
26
  puts "* #{api_key} returned no accounts"
22
27
  puts "*" * 80
23
28
  next
24
29
  end
25
-
30
+
26
31
  checking = find_all_checking_accounts(accounts)
27
32
 
28
33
  checking.each do |ac|
29
34
  identifier = ac['name']
30
35
  @banks[identifier] = ac
31
36
  end
32
-
37
+
33
38
  @identifier = accounts.first["name"]
34
39
  count_checking_accounts(accounts)
35
40
  @apis[@identifier] = mercury
@@ -49,27 +54,29 @@ module MercuryBanking
49
54
  table = Terminal::Table.new rows: @banks.collect { |name, vals|
50
55
  [name, vals["availableBalance"], vals["status"]]
51
56
  }
52
- print table.to_s + "\n"
57
+ print "#{table}\n"
53
58
  end
54
59
 
55
60
  def ensure_recipient(from:, to:, email:, address:, city:, region:, postal_code:, country:)
56
61
  raise "Target bank account not found: #{to}" unless @banks[to]
62
+
57
63
  mercury_source = @apis[from]
58
64
  target = mercury_source.find_recipient(name: @banks[to]["name"])
59
-
65
+
60
66
  if target.nil?
61
- target = add_target_account_to_recipient_list(mercury_source, to, email, address, city, region, postal_code, country)
67
+ target = add_target_account_to_recipient_list(mercury_source, to, email, address, city, region, postal_code,
68
+ country)
62
69
  end
63
70
  target
64
71
  end
65
72
 
66
73
  def add_target_account_to_recipient_list(mercury_source, to, email, address, city, region, postal_code, country)
67
- puts "Adding recipient: #{@banks[to]["name"]}"
74
+ puts "Adding recipient: #{@banks[to]['name']}"
68
75
  rec = MercuryBanking::Recipient.new(
69
- name: @banks[to]["name"],
70
- address: address,
71
- email: email,
72
- account_number: @banks[to]["accountNumber"],
76
+ name: @banks[to]["name"],
77
+ address: address,
78
+ email: email,
79
+ account_number: @banks[to]["accountNumber"],
73
80
  routing_number: @banks[to]["routingNumber"],
74
81
  city: city,
75
82
  region: region,
@@ -83,53 +90,52 @@ module MercuryBanking
83
90
  def ensure_account(account)
84
91
  found = @banks.keys.grep(/#{account}/i)
85
92
  raise "Account: #{account} not found." if found.empty?
86
- raise "Account: #{account} matched multiple #{found.join(",")}." if found.size > 1
93
+ raise "Account: #{account} matched multiple #{found.join(',')}." if found.size > 1
94
+
87
95
  found.first
88
96
  end
89
97
 
90
- def transfer(from:, to:, amount:, note:, wait: false, email:, address:, city:, region:, postal_code:, country:)
98
+ def transfer(from:, to:, amount:, note:, email:, address:, city:, region:, postal_code:, country:, wait: false)
91
99
  puts "#{from} -> #{to} (#{amount})"
92
100
  from_account = ensure_account(from)
93
101
  to_account = ensure_account(to)
94
-
102
+
95
103
  # Create a recipient with "to" information in "from"'s account:
96
104
  recipient = ensure_recipient(
97
- from: from_account,
98
- to: to_account,
99
- email: email,
100
- address: address,
101
- city: city,
102
- region: region,
103
- postal_code: postal_code,
105
+ from: from_account,
106
+ to: to_account,
107
+ email: email,
108
+ address: address,
109
+ city: city,
110
+ region: region,
111
+ postal_code: postal_code,
104
112
  country: country
105
113
  )
106
-
107
- puts "From account #{@banks[from_account]["id"]} -> Recipient #{recipient["id"]} (account: #{@banks[to_account]["id"]})"
114
+
115
+ puts "From account #{@banks[from_account]['id']} -> Recipient #{recipient['id']} (account: #{@banks[to_account]['id']})"
108
116
  transfer = @apis[from_account].transfer(
109
- recipient_id: recipient["id"],
110
- amount: amount,
111
- account_id: @banks[from_account]["id"],
112
- note: note,
117
+ recipient_id: recipient["id"],
118
+ amount: amount,
119
+ account_id: @banks[from_account]["id"],
120
+ note: note,
113
121
  external: "Ex #{note}"
114
122
  )
115
-
123
+
116
124
  if wait
117
125
  while transfer["status"] != "sent"
118
- puts "Waiting on #{transfer["id"]} (#{transfer["status"]}) to complete."
126
+ puts "Waiting on #{transfer['id']} (#{transfer['status']}) to complete."
119
127
  sleep 10
120
128
  transfer = @apis[from_account].transaction(@banks[from_account]["id"], transfer["id"])
121
- if transfer["status"] == "failed"
122
- puts transfer
123
- end
129
+ puts transfer if transfer["status"] == "failed"
124
130
  end
125
131
  end
126
-
132
+
127
133
  write_log(from_account, to_account, amount, note, transfer)
128
134
  transfer
129
135
  end
130
136
 
131
137
  def write_log(from, to, amount, note, transfer)
132
- File.write("log", "\"#{from}\", \"#{to}\", \"#{amount}\", \"#{note}\", \"#{transfer["status"]}\"\n")
138
+ File.write("log", "\"#{from}\", \"#{to}\", \"#{amount}\", \"#{note}\", \"#{transfer['status']}\"\n")
133
139
  end
134
140
  end
135
- end
141
+ end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MercuryBanking
4
+ # Recipient class for Mercury Banking API
2
5
  class Recipient
3
- attr_accessor :name
6
+ attr_accessor :name, :address, :city, :region, :postal_code, :country, :email
4
7
 
5
8
  def initialize(name:, email:, account_number:, routing_number:, address:, city:, region:, postal_code:, country:)
6
9
  @name = name
@@ -12,18 +15,23 @@ module MercuryBanking
12
15
  postalCode: postal_code,
13
16
  country: country
14
17
  }
15
- @emails = [ email ]
16
- @paymentMethod = "electronic"
17
- @electronicRoutingInfo = {
18
- accountNumber: account_number,
19
- routingNumber: routing_number,
18
+ @emails = [email]
19
+ @payment_method = "electronic"
20
+ @electronic_routing_info = {
21
+ accountNumber: account_number,
22
+ routingNumber: routing_number,
20
23
  electronicAccountType: "businessChecking",
21
- address: @address
24
+ address: @address
22
25
  }
23
26
  end
24
27
 
25
28
  def json
26
- { name: @name, address: @address, emails: @emails, paymentMethod: @paymentMethod, electronicRoutingInfo: @electronicRoutingInfo }
29
+ { name: @name, address: @address, emails: @emails, paymentMethod: @payment_method,
30
+ electronicRoutingInfo: @electronic_routing_info }
31
+ end
32
+
33
+ def to_json(*_args)
34
+ json.to_json
27
35
  end
28
36
  end
29
- end
37
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'fileutils'
3
5
 
@@ -5,100 +7,97 @@ module MercuryBanking
5
7
  # Class to manage reconciliation status for transactions
6
8
  class Reconciliation
7
9
  RECONCILIATION_DIR = File.join(Dir.home, '.mercury-banking', 'reconciliation')
8
-
10
+
9
11
  def initialize
10
12
  # Ensure reconciliation directory exists
11
13
  FileUtils.mkdir_p(RECONCILIATION_DIR)
12
14
  end
13
-
15
+
14
16
  # Get the reconciliation file path for an account
15
17
  def reconciliation_file(account_id)
16
18
  File.join(RECONCILIATION_DIR, "#{account_id}.json")
17
19
  end
18
-
20
+
19
21
  # Load reconciled transactions for an account
20
22
  def load_reconciled_transactions(account_id)
21
23
  file_path = reconciliation_file(account_id)
22
-
23
- if File.exist?(file_path)
24
- begin
25
- data = JSON.parse(File.read(file_path))
26
- # Handle both old format (array of transaction IDs) and new format (hash with dates)
27
- if data.is_a?(Array)
28
- # Convert old format to new format
29
- new_data = {}
30
- data.each do |transaction_id|
31
- new_data[transaction_id] = nil
32
- end
33
- return new_data
34
- else
35
- return data
36
- end
37
- rescue JSON::ParserError
38
- # If the file is corrupted, return an empty hash
39
- return {}
24
+
25
+ return {} unless File.exist?(file_path)
26
+
27
+ begin
28
+ data = JSON.parse(File.read(file_path))
29
+ # Handle both old format (array of transaction IDs) and new format (hash with dates)
30
+ return data unless data.is_a?(Array)
31
+
32
+ # Convert old format to new format
33
+ new_data = {}
34
+ data.each do |transaction_id|
35
+ new_data[transaction_id] = nil
40
36
  end
41
- else
42
- # If the file doesn't exist, return an empty hash
43
- return {}
37
+ new_data
38
+ rescue JSON::ParserError
39
+ # If the file is corrupted, return an empty hash
40
+ {}
44
41
  end
42
+
43
+ # If the file doesn't exist, return an empty hash
45
44
  end
46
-
45
+
47
46
  # Save reconciled transactions for an account
48
47
  def save_reconciled_transactions(account_id, transactions)
49
48
  file_path = reconciliation_file(account_id)
50
49
  File.write(file_path, JSON.pretty_generate(transactions))
51
50
  end
52
-
51
+
53
52
  # Mark a transaction as reconciled
54
53
  def mark_reconciled(account_id, transaction_id)
55
54
  transactions = load_reconciled_transactions(account_id)
56
-
55
+
57
56
  # Add the transaction ID if it's not already in the list
58
- if transactions.key?(transaction_id)
59
- return false # Already reconciled
60
- else
61
- transactions[transaction_id] = Time.now.strftime('%Y-%m-%d')
62
- save_reconciled_transactions(account_id, transactions)
63
- return true # Successfully reconciled
64
- end
57
+ return false if transactions.key?(transaction_id)
58
+
59
+ # Already reconciled
60
+
61
+ transactions[transaction_id] = Time.now.strftime('%Y-%m-%d')
62
+ save_reconciled_transactions(account_id, transactions)
63
+ true # Successfully reconciled
65
64
  end
66
-
65
+
67
66
  # Mark a transaction as unreconciled
68
67
  def mark_unreconciled(account_id, transaction_id)
69
68
  transactions = load_reconciled_transactions(account_id)
70
-
69
+
71
70
  # Remove the transaction ID if it's in the list
72
- if transactions.key?(transaction_id)
73
- transactions.delete(transaction_id)
74
- save_reconciled_transactions(account_id, transactions)
75
- return true # Successfully unreconciled
76
- else
77
- return false # Not reconciled to begin with
78
- end
71
+ return false unless transactions.key?(transaction_id)
72
+
73
+ transactions.delete(transaction_id)
74
+ save_reconciled_transactions(account_id, transactions)
75
+ true # Successfully unreconciled
76
+
77
+ # Not reconciled to begin with
79
78
  end
80
-
79
+
81
80
  # Check if a transaction is reconciled
82
81
  def reconciled?(account_id, transaction_id)
83
82
  transactions = load_reconciled_transactions(account_id)
84
83
  transactions.key?(transaction_id)
85
84
  end
86
-
85
+
87
86
  # Get all reconciled transaction IDs for an account
88
87
  def get_reconciled_transactions(account_id)
89
88
  load_reconciled_transactions(account_id).keys
90
89
  end
91
-
90
+
92
91
  # Get the reconciliation date for a transaction
93
92
  def get_reconciliation_date(account_id, transaction_id)
94
93
  transactions = load_reconciled_transactions(account_id)
95
94
  transactions[transaction_id]
96
95
  end
97
-
96
+
98
97
  # Get reconciliation status for all transactions
99
98
  def get_reconciliation_status(account_id, transactions)
100
99
  reconciled_data = load_reconciled_transactions(account_id)
101
-
100
+
102
101
  transactions.map do |t|
103
102
  {
104
103
  transaction_id: t["id"],
@@ -110,23 +109,23 @@ module MercuryBanking
110
109
  }
111
110
  end
112
111
  end
113
-
112
+
114
113
  # Get reconciliation summary
115
114
  def get_reconciliation_summary(account_id, transactions)
116
115
  reconciled_data = load_reconciled_transactions(account_id)
117
-
116
+
118
117
  total_transactions = transactions.size
119
118
  reconciled_count = transactions.count { |t| reconciled_data.key?(t["id"]) }
120
119
  unreconciled_count = total_transactions - reconciled_count
121
-
120
+
122
121
  reconciled_amount = transactions
123
- .select { |t| reconciled_data.key?(t["id"]) }
124
- .sum { |t| t["amount"].to_f }
125
-
122
+ .select { |t| reconciled_data.key?(t["id"]) }
123
+ .sum { |t| t["amount"].to_f }
124
+
126
125
  unreconciled_amount = transactions
127
- .reject { |t| reconciled_data.key?(t["id"]) }
128
- .sum { |t| t["amount"].to_f }
129
-
126
+ .reject { |t| reconciled_data.key?(t["id"]) }
127
+ .sum { |t| t["amount"].to_f }
128
+
130
129
  {
131
130
  total_transactions: total_transactions,
132
131
  reconciled_count: reconciled_count,
@@ -136,4 +135,4 @@ module MercuryBanking
136
135
  }
137
136
  end
138
137
  end
139
- end
138
+ end