ruby_paypal_nvp 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2fe00c7ddd7983e5511a992b53f1d9c53bed4807
4
+ data.tar.gz: 094c8c4e580882e4a39c0643fbb131e0211aa17e
5
+ SHA512:
6
+ metadata.gz: 3d307941fa46451f7905d40a64a2d73cc3cad7cea855ada278b07341e973057a3487c9a50474ea2a4f0bcb80f1a29c627b4d19026d6632d2de75c10cbac01555
7
+ data.tar.gz: f89320bd20446303bf864c5b961208b901e29a8f1c5849fc9788f0d09a5dc9f55e02efa5c89d1954a812251d3b80ac74a715cd36d2e711ece6c474b1329a8e2a
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ bin/debug_console
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,17 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'ruby_paypal_nvp.gemspec'
4
+ - 'spec/**/*'
5
+ TargetRubyVersion: 2.3
6
+
7
+ Style/FrozenStringLiteralComment:
8
+ Enabled: false
9
+
10
+ Metrics/LineLength:
11
+ Max: 89
12
+
13
+ Documentation:
14
+ Enabled: false
15
+
16
+ Naming/VariableName:
17
+ Enabled: false
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.16.0.pre.2
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at andrej@antas.cz. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ruby_paypal_nvp.gemspec
6
+ gemspec
@@ -0,0 +1,84 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby_paypal_nvp (0.1.0)
5
+ activesupport (~> 5.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (5.1.4)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (~> 0.7)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
15
+ addressable (2.5.2)
16
+ public_suffix (>= 2.0.2, < 4.0)
17
+ ast (2.3.0)
18
+ coderay (1.1.2)
19
+ concurrent-ruby (1.0.5)
20
+ crack (0.4.3)
21
+ safe_yaml (~> 1.0.0)
22
+ diff-lcs (1.3)
23
+ hashdiff (0.3.7)
24
+ i18n (0.9.0)
25
+ concurrent-ruby (~> 1.0)
26
+ method_source (0.9.0)
27
+ minitest (5.10.3)
28
+ parallel (1.12.1)
29
+ parser (2.4.0.2)
30
+ ast (~> 2.3)
31
+ powerpack (0.1.1)
32
+ pry (0.11.2)
33
+ coderay (~> 1.1.0)
34
+ method_source (~> 0.9.0)
35
+ public_suffix (3.0.1)
36
+ rainbow (3.0.0)
37
+ rake (10.5.0)
38
+ rspec (3.5.0)
39
+ rspec-core (~> 3.5.0)
40
+ rspec-expectations (~> 3.5.0)
41
+ rspec-mocks (~> 3.5.0)
42
+ rspec-core (3.5.4)
43
+ rspec-support (~> 3.5.0)
44
+ rspec-expectations (3.5.0)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.5.0)
47
+ rspec-mocks (3.5.0)
48
+ diff-lcs (>= 1.2.0, < 2.0)
49
+ rspec-support (~> 3.5.0)
50
+ rspec-support (3.5.0)
51
+ rubocop (0.52.0)
52
+ parallel (~> 1.10)
53
+ parser (>= 2.4.0.2, < 3.0)
54
+ powerpack (~> 0.1)
55
+ rainbow (>= 2.2.2, < 4.0)
56
+ ruby-progressbar (~> 1.7)
57
+ unicode-display_width (~> 1.0, >= 1.0.1)
58
+ ruby-progressbar (1.9.0)
59
+ safe_yaml (1.0.4)
60
+ thread_safe (0.3.6)
61
+ tzinfo (1.2.3)
62
+ thread_safe (~> 0.1)
63
+ unicode-display_width (1.3.0)
64
+ vcr (2.9.3)
65
+ webmock (3.1.1)
66
+ addressable (>= 2.3.6)
67
+ crack (>= 0.3.2)
68
+ hashdiff
69
+
70
+ PLATFORMS
71
+ ruby
72
+
73
+ DEPENDENCIES
74
+ bundler (~> 1.16.a)
75
+ pry (~> 0.11)
76
+ rake (~> 10.0)
77
+ rspec (~> 3.0)
78
+ rubocop
79
+ ruby_paypal_nvp!
80
+ vcr (~> 2.2)
81
+ webmock
82
+
83
+ BUNDLED WITH
84
+ 1.16.0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Andrej Antas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,212 @@
1
+ # RubyPaypalNvp
2
+
3
+ Missing paypal account statements.
4
+
5
+ As per communication with PayPal this is apart from PDF and CSV reports
6
+ prefered way of downloading e.x. monthly statements if you have too much and
7
+ need to automate the process...
8
+
9
+ This gem provides you with simple access to specific subject account statement.
10
+
11
+ ## Requirements
12
+
13
+ - PayPal account of course
14
+ - Paypal credentials for NVP API can be found under:
15
+ - Login on http://paypal.com/
16
+ - Go to your Profile and settings (upper right corner menu)
17
+ - Click on My selling tools
18
+ - Under first part - Selling online - click on Update API access
19
+ - At the bottom look up NVP/SOAP APIintegration (Classic)
20
+ - Click Manage API credentials there
21
+ - Finally after a while on this page is information needed for you to access
22
+ NVP API data
23
+ - If you take care of multiple account data like we do, They need to give
24
+ you access like this:
25
+ - Login on http://paypal.com/
26
+ - Click Profile“ → „Profile and settings“
27
+ - In the menu choose „My selling tools“ and then „API access“ and click on Update
28
+ - Choose the first option and click on „Grant API permission“
29
+ - On the next page „Add a new third party“
30
+ - In the new window fill in ID given to you (is the one from previous part) and click „Lookup“
31
+ - When the ID was filled in correctly a new menu with permissions will appear („Available Permissions“).
32
+ - Choose the following options:
33
+ - Obtain your PayPal account balance.
34
+ - Obtain information about a single transaction.
35
+ - Search your transactions for items that match specific criteria and display the results.
36
+ - Confirm the selection by clicking „Add“.
37
+
38
+ And after these couple days of setup you are all good to go :)
39
+
40
+ Important to note here is that in first part is your account and setup and
41
+ stuff...
42
+
43
+ Second part is you customer (in our case) who wants you to lookup data for them
44
+ and they use your id that you need to send them (is always in email like form
45
+ without @ in it)
46
+
47
+ Then the `subject` used in search for statements is their email through which
48
+ they granted API access to you...
49
+
50
+ ## Installation
51
+
52
+ Add this line to your application's Gemfile:
53
+
54
+ ```ruby
55
+ gem 'ruby_paypal_nvp'
56
+ ```
57
+
58
+ And then execute:
59
+
60
+ $ bundle
61
+
62
+ Or install it yourself as:
63
+
64
+ $ gem install ruby_paypal_nvp
65
+
66
+ ## Usage
67
+
68
+ For the time being this gem provides you with Stamtement download for desired
69
+ period.
70
+
71
+ The weird PayPal pagin and all things considered proven to be correct input for
72
+ accountants to work with.
73
+
74
+ first step is configuration (either as initializer or if you have it in single
75
+ script or so):
76
+
77
+ ```ruby
78
+ RubyPaypalNvp.configure do |config|
79
+ config.user = 'YOUR_USER_HERE' # watch out may differ according to setup for sandbox
80
+ config.password = 'YOUR_PWD_HERE'
81
+ config.signature = 'YOUR_SIGNATURE_HERE'
82
+ config.api_url = 'https://api-3t.paypal.com/nvp' # or put 'https://api-3t.andbox.aypal.com/nvp' for sandbox env
83
+ end
84
+ ```
85
+
86
+ Then you are ready to query PayPal
87
+
88
+ We provide you with Statement class:
89
+
90
+ ```
91
+ RubyPaypalNvp::Statement
92
+ ```
93
+
94
+ Provides search method `where`:
95
+
96
+ ```
97
+ RubyPaypalNvp::Statement.where(
98
+ date_from: Time.zone.parse('1.9.2017'),
99
+ date_to: Time.zone.parse('1.9.2017'),
100
+ subject: 'info@uol.cz',
101
+ currency: 'CZK',
102
+ transaction_class: 'BalanceAffecting'
103
+ )
104
+ ```
105
+
106
+ Where
107
+ - `date_from` beginning of interval to search for (beggining of the day - disregards time)
108
+ - `date_to` end of interval to search for (end of the day - disregards time)
109
+ - Dates and times in particular are converted to utc
110
+ - `subject` is account email you are looking through (as per description in requirements)
111
+ - `currency` is as name says currency of desired account (defaults to `CZK`)
112
+ - `transaction_class` is per description found in docs (here)[https://developer.paypal.com/docs/classic/api/merchant/TransactionSearch_API_Operation_NVP/] defaults to `BalanceAffecting`
113
+ - most problematic since multiple options you should be able to put the as `All` or `Received` result in `500` from PayPal which we are not able to identify (talking here about accounts with 1k+ transactions monthly)
114
+ - default setup is provide to be stable and correct for accounting
115
+
116
+ Response you get from search is in form of Object containing:
117
+
118
+ ```ruby
119
+ #<RubyPaypalNvp::Model::Statement:0x007fe4360ac450
120
+ @amount_sum=1000.00,
121
+ @currency_code="CZK",
122
+ @end_date="2017-09-01T21:59:59Z",
123
+ @fee_amount_sum=10.00,
124
+ @items=
125
+ [#<RubyPaypalNvp::Model::Item:0x007fe43679bf90
126
+ @amount=1000.00,
127
+ @currency_code="CZK",
128
+ @email="test@uol.cz",
129
+ @fee_amount=10.00,
130
+ @name="Martin Tester",
131
+ @net_amount=1010.00,
132
+ @status="Completed",
133
+ @timestamp="2017-09-01T12:48:08Z",
134
+ @timezone="GMT",
135
+ @transaction_id="9XXXXXXXXXXXXXXXH",
136
+ @type="Refund">],
137
+ @items_count=4,
138
+ @net_amount_sum=1010.00,
139
+ @start_date="2017-08-31T22:00:00Z",
140
+ @subject="info@uol.cz",
141
+ @timestamp="2017-11-02T10:45:07Z">
142
+ ```
143
+
144
+ You can see all the sums and all items which represent all movements on the account in given time period there.
145
+
146
+ Also `RubyPaypalNvp::Model::Statemnt` result class from query provides you method `#generate_csv` which will create CSV from the data for inspection:
147
+
148
+ ```
149
+ result = RubyPaypalNvp::Statement.where(
150
+ date_from: Time.zone.parse('1.9.2017'),
151
+ date_to: Time.zone.parse('30.9.2017'),
152
+ subject: 'info@uol.cz',
153
+ currency: 'CZK',
154
+ transaction_class: 'BalanceAffecting'
155
+ )
156
+ result.generate_csv('location_to_save.csv')
157
+ ```
158
+
159
+ Another thing provided is Balance fetching, where you can ask for balance of
160
+ given account at point in time when asking it... you cannot really ask for
161
+ balance at given point in time, because they simply do not provide it...
162
+
163
+ For this purpose you have:
164
+
165
+ ```
166
+ RubyPaypalNvp::Balance
167
+ ```
168
+
169
+ it provides you again with simple `.where` method to ask for data:
170
+
171
+ ```
172
+ result = RubyPaypalNvp::Balance
173
+ .where(subject: 'info@uol.cz', currency: 'CZK')
174
+ ```
175
+
176
+ Where
177
+ - `subject` is account email you are looking through (as per description in requirements)
178
+ - `currency` is as name says currency of desired account (defaults to `CZK`)
179
+
180
+ Result provides speak for itself:
181
+
182
+ ```
183
+ #<RubyPaypalNvp::Model::Balance:0x007fd8f0a17f90
184
+ @currency_code="CZK",
185
+ @opening_balance=58221.0,
186
+ @subject="info@uol.cz",
187
+ @timestamp="2017-12-06 10:00:14 +0100">
188
+ ```
189
+
190
+
191
+ ## Development
192
+
193
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
194
+
195
+ 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).
196
+
197
+ ## Contributing
198
+
199
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby_paypal_nvp. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
200
+
201
+ ## TODO
202
+
203
+ Pending things that will be done shortly:
204
+ - Rspec
205
+
206
+ ## License
207
+
208
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
209
+
210
+ ## Code of Conduct
211
+
212
+ Everyone interacting in the RubyPaypalNvp project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/ruby_paypal_nvp/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'ruby_paypal_nvp'
5
+ require 'pry'
6
+
7
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,32 @@
1
+ require 'active_support/all'
2
+ require 'net/http'
3
+
4
+ require 'ruby_paypal_nvp/version'
5
+ require 'ruby_paypal_nvp/configuration'
6
+
7
+ require 'ruby_paypal_nvp/utils/errors'
8
+
9
+ require 'ruby_paypal_nvp/fetcher/base'
10
+ require 'ruby_paypal_nvp/fetcher/balance'
11
+ require 'ruby_paypal_nvp/fetcher/statement'
12
+
13
+ require 'ruby_paypal_nvp/model/statement'
14
+ require 'ruby_paypal_nvp/model/balance'
15
+ require 'ruby_paypal_nvp/model/item'
16
+
17
+ require 'ruby_paypal_nvp/statement'
18
+ require 'ruby_paypal_nvp/balance'
19
+
20
+ module RubyPaypalNvp
21
+ def self.configuration
22
+ @configuration ||= Configuration.new
23
+ end
24
+
25
+ def self.reset
26
+ @configuration = Configuration.new
27
+ end
28
+
29
+ def self.configure
30
+ yield(configuration)
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module RubyPaypalNvp
2
+ class Balance
3
+ def self.where(options)
4
+ ::RubyPaypalNvp::Fetcher::Balance.call(options)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ module RubyPaypalNvp
2
+ class Configuration
3
+ attr_writer :version, :user, :password, :signature, :subject, :api_url
4
+
5
+ def initialize
6
+ Time.zone = 'Prague'
7
+ @version = nil
8
+ @user = nil
9
+ @password = nil
10
+ @signature = nil
11
+ @subject = nil
12
+ end
13
+
14
+ def version
15
+ return '204.0' unless @version
16
+ @version
17
+ end
18
+
19
+ def user
20
+ raise ConfigNotSet, 'user' unless @user
21
+ @user
22
+ end
23
+
24
+ def password
25
+ raise ConfigNotSet, 'password' unless @password
26
+ @password
27
+ end
28
+
29
+ def signature
30
+ raise ConfigNotSet, 'signature' unless @signature
31
+ @signature
32
+ end
33
+
34
+ def subject
35
+ raise ConfigNotSet, 'subject' unless @subject
36
+ @subject
37
+ end
38
+
39
+ def api_url
40
+ raise ConfigNotSet, 'api_url' unless @api_url
41
+ @api_url
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ module RubyPaypalNvp
2
+ module Fetcher
3
+ class Balance < Base
4
+ RESPONSE_PARAMS = %w[L_AMT L_CURRENCYCODE].freeze
5
+
6
+ private
7
+
8
+ def process_loaded_data(result)
9
+ result = filter_current_currency(result)
10
+ return if result[:values].blank?
11
+ ::RubyPaypalNvp::Model::Balance.new(result)
12
+ end
13
+
14
+ def filter_current_currency(result)
15
+ currency_code = result[:meta]['currency_code']
16
+ balance = result[:values].select do |_key, value|
17
+ { key: value } if value.values.include?(currency_code)
18
+ end
19
+ result[:values] = balance.values.first
20
+ result
21
+ end
22
+
23
+ def load_response
24
+ response = load_api_response(request_options)
25
+ raise response['L_LONGMESSAGE0'] if response['ACK'] == 'Failure'
26
+ parse(response, increment: 0)
27
+ result = result_with_meta(timestamp: Time.zone.parse(response['TIMESTAMP']).to_s)
28
+ @resulting_hash = default_hash
29
+ result
30
+ end
31
+
32
+ def request_options
33
+ super.merge! returnallcurrencies: 1
34
+ end
35
+
36
+ def api_method
37
+ 'GetBalance'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,114 @@
1
+ ##
2
+ # Base for fetching data from Paypal NVP API
3
+ # See docs: https://developer.paypal.com/docs/classic/api/
4
+ ##
5
+ module RubyPaypalNvp
6
+ module Fetcher
7
+ class Base
8
+ ##
9
+ # Input options:
10
+ # date_from
11
+ # date_to
12
+ # currency
13
+ # subject
14
+ # transaction_class
15
+ #
16
+ # rubocop:disable Metrics/AbcSize
17
+ def initialize(opts)
18
+ @start_date = opts.fetch(:date_from, Time.zone.now).beginning_of_day.utc.iso8601
19
+ @end_date = opts.fetch(:date_to, Time.zone.now).end_of_day.utc.iso8601
20
+ @currency = opts.fetch(:currency, 'CZK')
21
+ @subject = opts[:subject] || RubyPaypalNvp.configuration.subject
22
+ @transaction_class = opts.fetch(:transaction_class, 'BalanceAffecting')
23
+ @resulting_hash = default_hash
24
+ end
25
+ # rubocop:enable Metrics/AbcSize
26
+
27
+ def call
28
+ result = load_response
29
+ process_loaded_data(result)
30
+ rescue NoMethodError
31
+ raise "Error processing #{self.class} for #{@subject}"
32
+ end
33
+
34
+ def self.call(options)
35
+ new(options).call
36
+ end
37
+
38
+ def load_api_response(options)
39
+ uri = URI(RubyPaypalNvp.configuration.api_url)
40
+ req = Net::HTTP::Post.new(uri)
41
+ req.set_form_data(options)
42
+ res = Net::HTTP.start(uri.hostname, uri.port,
43
+ use_ssl: uri.scheme == 'https') do |http|
44
+ http.open_timeout = 6000
45
+ http.read_timeout = 6000
46
+ http.request(req)
47
+ end
48
+ pretty_json(res.body)
49
+ end
50
+
51
+ private
52
+
53
+ # rubocop:disable Metrics/AbcSize
54
+ # rubocop:disable Metrics/MethodLength
55
+ def parse(body, options = {})
56
+ @resulting_hash[:values].tap do |parsed_response|
57
+ param_type = self.class::RESPONSE_PARAMS.first
58
+ all_for_params_type = body.select { |k, _v| k.start_with?(param_type) }
59
+ all_for_params_type.each_with_index do |(key, value), index|
60
+ real_index = index + options[:increment]
61
+ parsed_response[real_index] ||= {}
62
+ parsed_response[real_index] = parsed_response[real_index]
63
+ .merge(param_type.to_s => value)
64
+ key = key.gsub(param_type, '')
65
+ self.class::RESPONSE_PARAMS.drop(1).each do |attribute_name|
66
+ pair = { attribute_name => body["#{attribute_name}#{key}"] }
67
+ parsed_response[real_index] = parsed_response[real_index].merge pair
68
+ end
69
+ end
70
+ end
71
+ end
72
+ # rubocop:enable Metrics/AbcSize
73
+ # rubocop:enable Metrics/MethodLength
74
+
75
+ # Implement in each child to save/send data
76
+ def process_loaded_data(_result)
77
+ true
78
+ end
79
+
80
+ def result_with_meta(options = {})
81
+ @resulting_hash[:meta]['timestamp'] = options[:timestamp]
82
+ @resulting_hash[:meta]['start_date'] = @start_date
83
+ @resulting_hash[:meta]['end_date'] = @end_date
84
+ @resulting_hash[:meta]['subject'] = @subject
85
+ @resulting_hash[:meta]['currency_code'] = @currency
86
+ @resulting_hash
87
+ end
88
+
89
+ def request_options(_options = {})
90
+ {
91
+ method: api_method,
92
+ version: RubyPaypalNvp.configuration.version,
93
+ user: RubyPaypalNvp.configuration.user,
94
+ pwd: RubyPaypalNvp.configuration.password,
95
+ signature: RubyPaypalNvp.configuration.signature,
96
+ subject: @subject
97
+ }
98
+ end
99
+
100
+ # Needs to be specific in each child
101
+ def api_method
102
+ ''
103
+ end
104
+
105
+ def pretty_json(body)
106
+ Hash[URI.decode_www_form(body)]
107
+ end
108
+
109
+ def default_hash
110
+ { meta: {}, values: {} }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,66 @@
1
+ module RubyPaypalNvp
2
+ module Fetcher
3
+ class Statement < Base
4
+ RESPONSE_PARAMS = %w[L_TIMESTAMP L_TIMEZONE L_TYPE L_EMAIL L_NAME
5
+ L_TRANSACTIONID L_STATUS L_AMT L_CURRENCYCODE
6
+ L_FEEAMT L_NETAMT].freeze
7
+
8
+ private
9
+
10
+ def process_loaded_data(result)
11
+ ::RubyPaypalNvp::Model::Statement.new(result)
12
+ end
13
+
14
+ # rubocop:disable Metrics/AbcSize
15
+ # rubocop:disable Metrics/MethodLength
16
+ def load_response
17
+ @ack = 'Failure'
18
+ while @ack != 'Success'
19
+ enddate = moved_end ? Time.parse(moved_end).utc.iso8601 : nil
20
+ options = request_options(enddate: enddate)
21
+ response = load_api_response(options)
22
+ parse(response, increment: increment)
23
+
24
+ @ack = response['ACK']
25
+ raise response['L_LONGMESSAGE0'] if @ack == 'Failure'
26
+ end
27
+ result = result_with_meta(timestamp: response['TIMESTAMP'])
28
+ @resulting_hash = default_hash
29
+ result[:values] = result[:values].values
30
+ result
31
+ end
32
+ # rubocop:enable Metrics/AbcSize
33
+ # rubocop:enable Metrics/MethodLength
34
+
35
+ # rubocop:disable Style/GuardClause
36
+ def moved_end
37
+ if @resulting_hash[:values].keys.last
38
+ @resulting_hash[:values].values.last['L_TIMESTAMP']
39
+ end
40
+ end
41
+ # rubocop:enable Style/GuardClause
42
+
43
+ def increment
44
+ if @resulting_hash[:values].keys.last
45
+ (@resulting_hash[:values].keys.last + 1)
46
+ else
47
+ 0
48
+ end
49
+ end
50
+
51
+ def request_options(options = {})
52
+ additional = {
53
+ startdate: @start_date,
54
+ enddate: options[:enddate] || @end_date,
55
+ transactionclass: @transaction_class,
56
+ currencycode: @currency
57
+ }
58
+ super.merge! additional
59
+ end
60
+
61
+ def api_method
62
+ 'TransactionSearch'
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,14 @@
1
+ module RubyPaypalNvp
2
+ module Model
3
+ class Balance
4
+ attr_accessor :currency_code, :subject, :timestamp, :opening_balance
5
+
6
+ def initialize(result)
7
+ @currency_code = result[:meta]['currency_code']
8
+ @subject = result[:meta]['subject']
9
+ @timestamp = result[:meta]['timestamp']
10
+ @opening_balance = result[:values]['L_AMT'].to_f
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,39 @@
1
+ module RubyPaypalNvp
2
+ module Model
3
+ class Item
4
+ attr_accessor :timestamp, :timezone, :type, :email, :name,
5
+ :transaction_id, :status, :amount, :currency_code, :fee_amount,
6
+ :net_amount
7
+
8
+ # rubocop:disable Metrics/AbcSize
9
+ # rubocop:disable Metrics/CyclomaticComplexity
10
+ # rubocop:disable Metrics/PerceivedComplexity
11
+ # rubocop:disable Metrics/MethodLength
12
+ def initialize(hash = {})
13
+ @timestamp = hash['L_TIMESTAMP'] || nil
14
+ @timezone = hash['L_TIMEZONE'] || nil
15
+ @type = hash['L_TYPE'] || nil
16
+ @email = hash['L_EMAIL'] || nil
17
+ @name = hash['L_NAME'] || nil
18
+ @transaction_id = hash['L_TRANSACTIONID'] || nil
19
+ @status = hash['L_STATUS'] || nil
20
+ @amount = (hash['L_AMT'] || nil).to_f
21
+ @currency_code = hash['L_CURRENCYCODE'] || nil
22
+ @fee_amount = (hash['L_FEEAMT'] || nil).to_f
23
+ @net_amount = (hash['L_NETAMT'] || nil).to_f
24
+ end
25
+ # rubocop:enable Metrics/AbcSize
26
+ # rubocop:enable Metrics/CyclomaticComplexity
27
+ # rubocop:enable Metrics/PerceivedComplexity
28
+ # rubocop:enable Metrics/MethodLength
29
+
30
+ def self.attributes
31
+ new.instance_variable_names.map { |a| a.delete('@') }
32
+ end
33
+
34
+ def to_csv
35
+ instance_variables.map { |k| instance_variable_get(k) }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,81 @@
1
+ require 'csv'
2
+
3
+ module RubyPaypalNvp
4
+ module Model
5
+ class Statement
6
+ attr_accessor :timestamp, :start_date, :end_date, :subject,
7
+ :currency_code, :items, :amount_sum, :fee_amount_sum,
8
+ :net_amount_sum, :items_count
9
+
10
+ IGNORED_STATUSES = %w[Cleared Placed Removed].freeze
11
+
12
+ # rubocop:disable Metrics/AbcSize
13
+ # rubocop:disable Metrics/MethodLength
14
+ def initialize(result)
15
+ @timestamp = result[:meta]['timestamp']
16
+ @start_date = result[:meta]['start_date']
17
+ @end_date = result[:meta]['end_date']
18
+ @subject = result[:meta]['subject']
19
+ @currency_code = result[:meta]['currency_code']
20
+ @items = result[:values].map do |value|
21
+ Item.new(value) unless IGNORED_STATUSES.include?(value['L_STATUS'])
22
+ end.compact.uniq(&:transaction_id)
23
+ @items_count = @items.count
24
+ @amount_sum = @items.sum(&:amount)
25
+ @fee_amount_sum = @items.sum(&:fee_amount)
26
+ @net_amount_sum = @items.sum(&:net_amount)
27
+ @raw = result
28
+ end
29
+ # rubocop:enable Metrics/AbcSize
30
+ # rubocop:enable Metrics/MethodLength
31
+
32
+ # rubocop:disable Metrics/AbcSize
33
+ # rubocop:disable Metrics/MethodLength
34
+ def generate_csv(filename = nil)
35
+ raise 'Missing filename/path' unless filename
36
+ ::CSV.open(filename, 'w') do |csv|
37
+ csv << ['HEADER']
38
+ csv << ['', 'timestamp', @timestamp]
39
+ csv << ['', 'start_date', @start_date]
40
+ csv << ['', 'end_date', @end_date]
41
+ csv << ['', 'subject', @subject]
42
+ csv << ['', 'currency_code', @currency_code]
43
+ csv << ['', 'items_count', @items_count]
44
+ csv << ['', 'amount_sum', @amount_sum]
45
+ csv << ['', 'fee_amount_sum', @fee_amount_sum]
46
+ csv << ['', 'net_amount_sum', @net_amount_sum]
47
+ csv << ['ITEMS']
48
+ csv << RubyPaypalNvp::Model::Item.attributes
49
+ @items.each do |item|
50
+ csv << item.to_csv
51
+ end
52
+ end
53
+ end
54
+ # rubocop:enable Metrics/AbcSize
55
+ # rubocop:enable Metrics/MethodLength
56
+
57
+ def to_json
58
+ raw_items.to_json
59
+ end
60
+
61
+ def to_iis_json
62
+ {
63
+ 'meta': {
64
+ 'timestamp': @timestamp,
65
+ 'start_date': @start_date,
66
+ 'end_date': @end_date,
67
+ 'subject': @subject,
68
+ 'currency_code': @currency_code
69
+ },
70
+ 'values': raw_items
71
+ }.to_json
72
+ end
73
+
74
+ private
75
+
76
+ def raw_items
77
+ @raw[:values].compact.uniq { |i| i['L_TRANSACTIONID'] }
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,7 @@
1
+ module RubyPaypalNvp
2
+ class Statement
3
+ def self.where(options)
4
+ ::RubyPaypalNvp::Fetcher::Statement.call(options)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module RubyPaypalNvp
2
+ class Error < StandardError; end
3
+
4
+ class ConfigNotSet < Error
5
+ def initialize(param)
6
+ msg = "Please check if you set #{param}, seems it is missing."
7
+ super(msg)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module RubyPaypalNvp
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,39 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'ruby_paypal_nvp/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ruby_paypal_nvp'
7
+ spec.version = RubyPaypalNvp::VERSION
8
+ spec.authors = ['Andrej Antas']
9
+ spec.email = ['andrej@antas.cz']
10
+
11
+ spec.summary = 'Ruby PayPal NVP wrapper'
12
+ spec.description = 'Ruby wrapper for PayPal NVP API since there was none'
13
+ spec.homepage = 'https://github.com/redrick/ruby_paypal_nvp'
14
+ spec.license = 'MIT'
15
+
16
+ if spec.respond_to?(:metadata)
17
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
18
+ else
19
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
20
+ 'public gem pushes.'
21
+ end
22
+
23
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
24
+ f.match(%r{^(test|spec|features)/})
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_runtime_dependency 'activesupport', '~> 5.1'
31
+
32
+ spec.add_development_dependency 'bundler', '~> 1.16.a'
33
+ spec.add_development_dependency 'pry', '~> 0.11'
34
+ spec.add_development_dependency 'rake', '~> 10.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.0'
36
+ spec.add_development_dependency 'rubocop'
37
+ spec.add_development_dependency 'vcr', '~> 2.2'
38
+ spec.add_development_dependency 'webmock'
39
+ end
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_paypal_nvp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrej Antas
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.16.a
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.16.a
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: vcr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Ruby wrapper for PayPal NVP API since there was none
126
+ email:
127
+ - andrej@antas.cz
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - ".rubocop.yml"
135
+ - ".travis.yml"
136
+ - CODE_OF_CONDUCT.md
137
+ - Gemfile
138
+ - Gemfile.lock
139
+ - LICENSE.txt
140
+ - README.md
141
+ - Rakefile
142
+ - bin/console
143
+ - bin/setup
144
+ - lib/ruby_paypal_nvp.rb
145
+ - lib/ruby_paypal_nvp/balance.rb
146
+ - lib/ruby_paypal_nvp/configuration.rb
147
+ - lib/ruby_paypal_nvp/fetcher/balance.rb
148
+ - lib/ruby_paypal_nvp/fetcher/base.rb
149
+ - lib/ruby_paypal_nvp/fetcher/statement.rb
150
+ - lib/ruby_paypal_nvp/model/balance.rb
151
+ - lib/ruby_paypal_nvp/model/item.rb
152
+ - lib/ruby_paypal_nvp/model/statement.rb
153
+ - lib/ruby_paypal_nvp/statement.rb
154
+ - lib/ruby_paypal_nvp/utils/errors.rb
155
+ - lib/ruby_paypal_nvp/version.rb
156
+ - ruby_paypal_nvp.gemspec
157
+ homepage: https://github.com/redrick/ruby_paypal_nvp
158
+ licenses:
159
+ - MIT
160
+ metadata:
161
+ allowed_push_host: https://rubygems.org/
162
+ post_install_message:
163
+ rdoc_options: []
164
+ require_paths:
165
+ - lib
166
+ required_ruby_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ requirements: []
177
+ rubyforge_project:
178
+ rubygems_version: 2.6.8
179
+ signing_key:
180
+ specification_version: 4
181
+ summary: Ruby PayPal NVP wrapper
182
+ test_files: []