mercury_banking 0.5.34
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 +7 -0
- data/.env_test +1 -0
- data/.gitignore +24 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +90 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +140 -0
- data/LICENSE +21 -0
- data/LINTING_REPORT.md +118 -0
- data/README.md +244 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/mercury +17 -0
- data/bin/setup +8 -0
- data/lib/mercury_banking/api.rb +184 -0
- data/lib/mercury_banking/cli/accounts.rb +61 -0
- data/lib/mercury_banking/cli/base.rb +68 -0
- data/lib/mercury_banking/cli/financials.rb +302 -0
- data/lib/mercury_banking/cli/reconciliation.rb +406 -0
- data/lib/mercury_banking/cli/reports.rb +265 -0
- data/lib/mercury_banking/cli/transactions.rb +222 -0
- data/lib/mercury_banking/cli.rb +209 -0
- data/lib/mercury_banking/formatters/export_formatter.rb +306 -0
- data/lib/mercury_banking/formatters/table_formatter.rb +133 -0
- data/lib/mercury_banking/multi.rb +135 -0
- data/lib/mercury_banking/recipient.rb +29 -0
- data/lib/mercury_banking/reconciliation.rb +139 -0
- data/lib/mercury_banking/reports/balance_sheet.rb +586 -0
- data/lib/mercury_banking/reports/reconciliation.rb +307 -0
- data/lib/mercury_banking/utils/command_utils.rb +18 -0
- data/lib/mercury_banking/version.rb +3 -0
- data/lib/mercury_banking.rb +19 -0
- data/mercury_banking.gemspec +37 -0
- metadata +183 -0
data/README.md
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
# MercuryBanking
|
2
|
+
|
3
|
+
Client library for talking to [Mercury API](https://docs.mercury.com/reference).
|
4
|
+
|
5
|
+
This gem comes in handy when you want to access all of your bank accounts and their transactions histories or make payments to existing recipients in your Mercury account.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'mercury_banking'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install mercury_banking
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
First initialize client:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'mercury_banking'
|
29
|
+
|
30
|
+
# For single account
|
31
|
+
client = Mercury::API.new("YOUR_API_KEY")
|
32
|
+
|
33
|
+
# For multiple accounts
|
34
|
+
multi_client = Mercury::Multi.new(['YOUR_API_KEY'])
|
35
|
+
```
|
36
|
+
|
37
|
+
Then call the API
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
client.accounts
|
41
|
+
|
42
|
+
multi_client.balances
|
43
|
+
```
|
44
|
+
|
45
|
+
### CLI Usage
|
46
|
+
|
47
|
+
The gem also provides a command-line interface for interacting with Mercury Banking:
|
48
|
+
|
49
|
+
```bash
|
50
|
+
# Display all accounts
|
51
|
+
mercury accounts
|
52
|
+
|
53
|
+
# List transactions for an account
|
54
|
+
mercury transactions ACCOUNT_ID_OR_NUMBER
|
55
|
+
|
56
|
+
# Download all transactions as CSV files
|
57
|
+
mercury transactions_download
|
58
|
+
|
59
|
+
# Download transactions with custom options
|
60
|
+
mercury transactions_download --output_dir=my_transactions --start_date=2023-01-01 --end_date=2023-12-31
|
61
|
+
|
62
|
+
# Download transactions in beancount format
|
63
|
+
mercury transactions_download --format=beancount
|
64
|
+
|
65
|
+
# Download transactions in both CSV and beancount formats
|
66
|
+
mercury transactions_download --format=both
|
67
|
+
|
68
|
+
# Generate a balance sheet report
|
69
|
+
mercury financials balancesheet
|
70
|
+
|
71
|
+
# Generate a balance sheet report with custom options
|
72
|
+
mercury financials balancesheet --format=beancount --start=2023-01-01 --end=2023-12-31 --verbose
|
73
|
+
|
74
|
+
# Generate an income statement report
|
75
|
+
mercury financials incomestatement
|
76
|
+
```
|
77
|
+
|
78
|
+
The `transactions_download` command will download all Mercury transactions in the format:
|
79
|
+
```
|
80
|
+
transactions/YEAR-MO-Mercury-ACCOUNT-ID.csv
|
81
|
+
```
|
82
|
+
|
83
|
+
For example: `transactions/2023-01-Mercury-123456789.csv`
|
84
|
+
|
85
|
+
When using the `--format=beancount` option, it will create files in the format:
|
86
|
+
```
|
87
|
+
transactions/YEAR-MO-Mercury-ACCOUNT-ID.beancount
|
88
|
+
```
|
89
|
+
|
90
|
+
### API Usage
|
91
|
+
|
92
|
+
For transferring money to a _registered_ recipient:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
client.transfer(
|
96
|
+
recipient_id: RECIPIENT_ID,
|
97
|
+
amount: 100000, # amount should be in USD
|
98
|
+
account_id: ACCOUNT_ID,
|
99
|
+
note: "TEST"
|
100
|
+
)
|
101
|
+
```
|
102
|
+
|
103
|
+
json response:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
{
|
107
|
+
"amount": 1000000,
|
108
|
+
"bankDescription": null,
|
109
|
+
"counterpartyId": "dd486506-d66b-11e9-8d8a-0b2e6a550f30",
|
110
|
+
"counterpartyName": "Fake P. Erson",
|
111
|
+
"counterpartyNickname": null,
|
112
|
+
"createdAt": "2019-09-13T21:17:15.974788Z",
|
113
|
+
"dashboardLink": "https://mercury.com/transactions/dd4896c0-d66b-11e9-8d8a-cf31cc71dda0",
|
114
|
+
"details": {
|
115
|
+
"address": null,
|
116
|
+
"domesticWireRoutingInfo": null,
|
117
|
+
"electronicRoutingInfo": null,
|
118
|
+
"internationalWireRoutingInfo": null
|
119
|
+
},
|
120
|
+
"estimatedDeliveryDate": "2019-09-13T21:17:17.387585Z",
|
121
|
+
"failedAt": null,
|
122
|
+
"id": "dd4896c0-d66b-11e9-8d8a-cf31cc71dda0",
|
123
|
+
"kind": "externalTransfer",
|
124
|
+
"note": null,
|
125
|
+
"externalMemo": null,
|
126
|
+
"postedAt": "2019-09-13T21:17:17.387585Z",
|
127
|
+
"reasonForFailure": null,
|
128
|
+
"status": "sent",
|
129
|
+
"feeId": null
|
130
|
+
},
|
131
|
+
```
|
132
|
+
|
133
|
+
For transferring between two Mercury accounts:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
multi_client.transfer(
|
137
|
+
from: 'ACCOUNT_NAME_1',
|
138
|
+
to: 'ACCOUNT_NAME_2',
|
139
|
+
amount: 100000, # amount should be in USD
|
140
|
+
note: 'test',
|
141
|
+
email: 'test@gmail.com',
|
142
|
+
address: 'test address',
|
143
|
+
city: 'Minato',
|
144
|
+
region: 'Tokyo', # Either a two-letter US state code like "CA" for California or a free-form identification of a particular region worldwide
|
145
|
+
postal_code: '1110000',
|
146
|
+
country: 'JP' # ISO3166Alpha2 Ex. US, JP, etc.
|
147
|
+
)
|
148
|
+
```
|
149
|
+
|
150
|
+
json_response:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
{
|
154
|
+
"amount": 1000000,
|
155
|
+
"bankDescription": null,
|
156
|
+
"counterpartyId": "dd486506-d66b-11e9-8d8a-0b2e6a550f30",
|
157
|
+
"counterpartyName": "Fake P. Erson",
|
158
|
+
"counterpartyNickname": null,
|
159
|
+
"createdAt": "2019-09-13T21:17:15.974788Z",
|
160
|
+
"dashboardLink": "https://mercury.com/transactions/dd4896c0-d66b-11e9-8d8a-cf31cc71dda0",
|
161
|
+
"details": {
|
162
|
+
"address": null,
|
163
|
+
"domesticWireRoutingInfo": null,
|
164
|
+
"electronicRoutingInfo": null,
|
165
|
+
"internationalWireRoutingInfo": null
|
166
|
+
},
|
167
|
+
"estimatedDeliveryDate": "2019-09-13T21:17:17.387585Z",
|
168
|
+
"failedAt": null,
|
169
|
+
"id": "dd4896c0-d66b-11e9-8d8a-cf31cc71dda0",
|
170
|
+
"kind": "externalTransfer",
|
171
|
+
"note": null,
|
172
|
+
"externalMemo": null,
|
173
|
+
"postedAt": "2019-09-13T21:17:17.387585Z",
|
174
|
+
"reasonForFailure": null,
|
175
|
+
"status": "sent",
|
176
|
+
"feeId": null
|
177
|
+
},
|
178
|
+
```
|
179
|
+
|
180
|
+
## Development
|
181
|
+
|
182
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
183
|
+
|
184
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
185
|
+
|
186
|
+
## Test
|
187
|
+
|
188
|
+
We use [RSpec](http://rspec.info/) for testing. To run test use the following command:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
rspec spec
|
192
|
+
```
|
193
|
+
|
194
|
+
or
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
rake spec
|
198
|
+
```
|
199
|
+
|
200
|
+
### Testing
|
201
|
+
|
202
|
+
For running the test suite:
|
203
|
+
|
204
|
+
1. Run `bundle install`
|
205
|
+
2. Run `bundle exec rspec` to run the automated tests
|
206
|
+
|
207
|
+
For manual testing:
|
208
|
+
|
209
|
+
1. Run `bundle install`
|
210
|
+
2. Rename `.env_test` to `.env`
|
211
|
+
3. Inside the `.env` file, add your Mercury API key
|
212
|
+
4. Use the CLI commands to interact with the Mercury API
|
213
|
+
|
214
|
+
## ToDo
|
215
|
+
|
216
|
+
- To cover all endpoints
|
217
|
+
- Throw useful errors
|
218
|
+
- Encryption
|
219
|
+
|
220
|
+
## Contributing
|
221
|
+
|
222
|
+
1. Fork it
|
223
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
224
|
+
3. Commit your changes (`git commit -am "Add new feature"`).
|
225
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
226
|
+
5. Create a new Pull Request.
|
227
|
+
|
228
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/XenonIO/mercury-banking. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/XenonIO/mercury-banking/blob/master/CODE_OF_CONDUCT.md).
|
229
|
+
|
230
|
+
## Release Notes
|
231
|
+
|
232
|
+
- v1.0.0: Initial major release.
|
233
|
+
|
234
|
+
## Reference
|
235
|
+
|
236
|
+
See [Mercury docs](https://docs.mercury.com/reference) for the complete list of endpoints.
|
237
|
+
|
238
|
+
## License
|
239
|
+
|
240
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
241
|
+
|
242
|
+
## Code of Conduct
|
243
|
+
|
244
|
+
Everyone interacting in the MercuryBanking project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/XenonIO/mercury-banking/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mercury_banking"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/mercury
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'mercury_banking'
|
5
|
+
require 'dotenv/load'
|
6
|
+
require 'json'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'base64'
|
9
|
+
require 'terminal-table'
|
10
|
+
require 'time'
|
11
|
+
require 'logger' # Explicitly require logger
|
12
|
+
require 'securerandom'
|
13
|
+
require 'mercury_banking/cli'
|
14
|
+
require 'symmetric-encryption'
|
15
|
+
|
16
|
+
# Start the CLI
|
17
|
+
MercuryBanking::CLI::Main.start(ARGV)
|
data/bin/setup
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
module MercuryBanking
|
2
|
+
class API
|
3
|
+
def initialize(secret)
|
4
|
+
@api_key = secret
|
5
|
+
end
|
6
|
+
|
7
|
+
def get(path)
|
8
|
+
check_if_the_path_is_valid(path)
|
9
|
+
response = send_get_request_to_api(path)
|
10
|
+
body = parse_json(response)
|
11
|
+
validate_body(body, path)
|
12
|
+
body
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_if_the_path_is_valid(path)
|
16
|
+
if path.nil? || path.empty?
|
17
|
+
raise 'Path cannot be empty'
|
18
|
+
elsif path[0] == '/'
|
19
|
+
raise "Path should not start with a /."
|
20
|
+
else
|
21
|
+
path
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_get_request_to_api(path)
|
26
|
+
base_url = "https://backend.mercury.com/api/v1/"
|
27
|
+
url = URI.join(base_url, path)
|
28
|
+
|
29
|
+
http = Net::HTTP.new(url.host, url.port)
|
30
|
+
http.use_ssl = true
|
31
|
+
|
32
|
+
request = Net::HTTP::Get.new(url)
|
33
|
+
request.basic_auth @api_key, ''
|
34
|
+
|
35
|
+
http.request(request)
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_json(response)
|
39
|
+
JSON.parse(response.read_body)
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate_body(body, path)
|
43
|
+
if body["errors"]
|
44
|
+
raise "#{@api_key} access to #{path} errored with #{body["errors"]["message"]}"
|
45
|
+
end
|
46
|
+
|
47
|
+
body
|
48
|
+
end
|
49
|
+
|
50
|
+
def post(object, path)
|
51
|
+
check_if_the_path_is_valid(path)
|
52
|
+
response = send_post_request_to_api(object, path)
|
53
|
+
body = parse_json(response)
|
54
|
+
validate_body(body, path)
|
55
|
+
body
|
56
|
+
end
|
57
|
+
|
58
|
+
def send_post_request_to_api(object, path)
|
59
|
+
base_url = "https://backend.mercury.com/api/v1/"
|
60
|
+
url = URI.join(base_url, path)
|
61
|
+
|
62
|
+
http = Net::HTTP.new(url.host, url.port)
|
63
|
+
http.use_ssl = true
|
64
|
+
|
65
|
+
request = Net::HTTP::Post.new(url)
|
66
|
+
request["accept"] = 'application/json'
|
67
|
+
request["content-type"] = 'application/json'
|
68
|
+
request.body = object.to_json
|
69
|
+
|
70
|
+
request.basic_auth @api_key, ''
|
71
|
+
|
72
|
+
http.request(request)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Userland API
|
76
|
+
def accounts
|
77
|
+
get("accounts")["accounts"]
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_account(account_id)
|
81
|
+
path = "account/#{account_id}"
|
82
|
+
get(path)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Find account by account number
|
86
|
+
def find_account_by_number(account_number)
|
87
|
+
account = accounts.find { |a| a["accountNumber"] == account_number.to_s }
|
88
|
+
raise "Account with number #{account_number} not found" unless account
|
89
|
+
account
|
90
|
+
end
|
91
|
+
|
92
|
+
def balance(account_id)
|
93
|
+
account = get_account(account_id)
|
94
|
+
account['currentBalance']
|
95
|
+
end
|
96
|
+
|
97
|
+
# /account/:id/transactions
|
98
|
+
def get_transactions(account_id, start_date = nil)
|
99
|
+
path = "account/#{account_id}/transactions"
|
100
|
+
path += "?start=#{start_date}" if start_date
|
101
|
+
get(path)["transactions"]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Get transactions from all accounts
|
105
|
+
def get_all_transactions(start_date = nil)
|
106
|
+
all_transactions = []
|
107
|
+
|
108
|
+
accounts.each do |account|
|
109
|
+
begin
|
110
|
+
account_transactions = get_transactions(account["id"], start_date)
|
111
|
+
# Add account information to each transaction
|
112
|
+
account_transactions.each do |transaction|
|
113
|
+
transaction["accountName"] = account["name"]
|
114
|
+
transaction["accountId"] = account["id"]
|
115
|
+
end
|
116
|
+
all_transactions.concat(account_transactions)
|
117
|
+
rescue StandardError => e
|
118
|
+
puts "Warning: Could not fetch transactions for account #{account["name"]}: #{e.message}" unless ENV['MERCURY_SILENT']
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Sort all transactions by date (newest first)
|
123
|
+
all_transactions.sort_by { |t| t["createdAt"] || "" }.reverse
|
124
|
+
end
|
125
|
+
|
126
|
+
# /account/:id/transactions/:id
|
127
|
+
def transaction(account_id, transaction_id)
|
128
|
+
path = "account/#{account_id}/transaction/#{transaction_id}"
|
129
|
+
get(path)
|
130
|
+
end
|
131
|
+
|
132
|
+
def recipients
|
133
|
+
get("recipients")["recipients"]
|
134
|
+
end
|
135
|
+
|
136
|
+
def find_recipient(name:)
|
137
|
+
recipients.find { |r| r["name"] == name && r['status'] != 'deleted' }
|
138
|
+
end
|
139
|
+
|
140
|
+
def get_recipient(recipient_id)
|
141
|
+
path = "recipient/#{recipient_id}"
|
142
|
+
get(path)
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_recipient(name:, address:, email:, city:, region:, postal_code:, country:, account_number:, routing_number:)
|
146
|
+
rec = MercuryBanking::Recipient.new(
|
147
|
+
name: name,
|
148
|
+
address: address,
|
149
|
+
email: email,
|
150
|
+
city: city,
|
151
|
+
region: region,
|
152
|
+
postal_code: postal_code,
|
153
|
+
country: country,
|
154
|
+
account_number: account_number,
|
155
|
+
routing_number: routing_number
|
156
|
+
)
|
157
|
+
post(rec.json, 'recipients')
|
158
|
+
new_recipient = find_recipient(name: name)
|
159
|
+
raise "Couldn't add account #{name}" unless new_recipient
|
160
|
+
puts "Successfully added #{new_recipient['name']}" if new_recipient
|
161
|
+
end
|
162
|
+
|
163
|
+
def update_recipient(name:, address:, email:, city:, region:, postal_code:, country:, account_number:, routing_number:, recipient_id:)
|
164
|
+
rec = MercuryBanking::Recipient.new(
|
165
|
+
name: name,
|
166
|
+
address: address,
|
167
|
+
email: email,
|
168
|
+
city: city,
|
169
|
+
region: region,
|
170
|
+
postal_code: postal_code,
|
171
|
+
country: country,
|
172
|
+
account_number: account_number,
|
173
|
+
routing_number: routing_number
|
174
|
+
)
|
175
|
+
post(rec.json, "recipient/#{recipient_id}")
|
176
|
+
end
|
177
|
+
|
178
|
+
def transfer(recipient_id:, amount:, account_id:, note: nil, external: nil)
|
179
|
+
payload = { recipientId: recipient_id, amount: amount, paymentMethod: 'ach',
|
180
|
+
note: note, externalMemo: external, idempotencyKey: "#{recipient_id}-#{amount}-#{note}-#{external}" }
|
181
|
+
self.post(payload, "account/#{account_id}/transactions")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MercuryBanking
|
2
|
+
module CLI
|
3
|
+
# Module for account-related commands
|
4
|
+
module Accounts
|
5
|
+
# Add account-related commands to the CLI class
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
desc 'accounts', 'Display all accounts'
|
9
|
+
method_option :format, type: :string, default: 'table', enum: ['table', 'json'], desc: 'Output format (table or json)'
|
10
|
+
def accounts
|
11
|
+
with_api_client do |client|
|
12
|
+
accounts = client.accounts
|
13
|
+
|
14
|
+
if options[:json] || options[:format] == 'json'
|
15
|
+
puts JSON.pretty_generate(accounts)
|
16
|
+
else
|
17
|
+
display_accounts_table(accounts)
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "You have #{accounts.count} account/s" unless options[:json]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'balance ACCOUNT_ID_OR_NUMBER', "Display the current balance of an account (using account number from accounts table)"
|
25
|
+
def balance(account_identifier)
|
26
|
+
with_api_client do |client|
|
27
|
+
# Determine if we're dealing with an account ID or account number
|
28
|
+
account_id = nil
|
29
|
+
if account_identifier.match?(/^\d+$/) && !account_identifier.include?('-')
|
30
|
+
begin
|
31
|
+
account = client.find_account_by_number(account_identifier)
|
32
|
+
account_id = account["id"]
|
33
|
+
rescue => e
|
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
|
39
|
+
account_id = account_identifier
|
40
|
+
account = client.get_account(account_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
balance = client.balance(account_id)
|
44
|
+
|
45
|
+
if options[:json]
|
46
|
+
puts JSON.pretty_generate({
|
47
|
+
'account_id' => account_id,
|
48
|
+
'account_number' => account['accountNumber'],
|
49
|
+
'name' => account['name'],
|
50
|
+
'balance' => balance
|
51
|
+
})
|
52
|
+
else
|
53
|
+
puts "#{account['name']} (#{account['accountNumber']}): $#{format("%.2f", balance)}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module MercuryBanking
|
2
|
+
module CLI
|
3
|
+
# Base module for CLI functionality
|
4
|
+
module Base
|
5
|
+
# Get API client with error handling
|
6
|
+
def with_api_client
|
7
|
+
api_key = get_api_key
|
8
|
+
raise "API key not found. Please run 'mercury set_key' first." unless api_key
|
9
|
+
|
10
|
+
client = MercuryBanking::API.new(api_key)
|
11
|
+
yield client
|
12
|
+
rescue StandardError => e
|
13
|
+
if options[:json]
|
14
|
+
puts JSON.pretty_generate({ 'error' => e.message })
|
15
|
+
else
|
16
|
+
puts "Error: #{e.message}"
|
17
|
+
end
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get the API key from the encrypted storage
|
22
|
+
def get_api_key
|
23
|
+
config_path = File.join(Dir.home, '.mercury-banking', 'api_key.enc')
|
24
|
+
key_path = File.join(Dir.home, '.mercury-banking', 'key_config.json')
|
25
|
+
|
26
|
+
unless File.exist?(config_path) && File.exist?(key_path)
|
27
|
+
raise "API key not found. Please run 'mercury set_key' first."
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
key_config = JSON.parse(File.read(key_path))
|
32
|
+
|
33
|
+
# Initialize the SymmetricEncryption with the loaded cipher
|
34
|
+
cipher = SymmetricEncryption::Cipher.new(
|
35
|
+
key: Base64.strict_decode64(key_config['key']),
|
36
|
+
iv: Base64.strict_decode64(key_config['iv']),
|
37
|
+
cipher_name: key_config['cipher_name'] || 'aes-256-cbc'
|
38
|
+
)
|
39
|
+
|
40
|
+
# Set the cipher as the primary one
|
41
|
+
SymmetricEncryption.cipher = cipher
|
42
|
+
|
43
|
+
encrypted_key = File.read(config_path)
|
44
|
+
cipher.decrypt(encrypted_key)
|
45
|
+
rescue => e
|
46
|
+
puts "Error decrypting API key: #{e.message}"
|
47
|
+
exit 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Log transfer details
|
52
|
+
def log_transfer(from, to, amount, note, status)
|
53
|
+
log_dir = File.join(Dir.home, '.mercury-banking', 'logs')
|
54
|
+
FileUtils.mkdir_p(log_dir)
|
55
|
+
|
56
|
+
log_file = File.join(log_dir, 'transfers.log')
|
57
|
+
File.open(log_file, 'a') do |f|
|
58
|
+
f.puts "#{Time.now.iso8601},\"#{from}\",\"#{to}\",\"#{amount}\",\"#{note}\",\"#{status}\""
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Check if a command exists in the system
|
63
|
+
def command_exists?(command)
|
64
|
+
system("which #{command} > /dev/null 2>&1")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|