vantiv_lite 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +18 -0
- data/README.md +223 -0
- data/lib/vantiv_lite/config.rb +98 -0
- data/lib/vantiv_lite/request.rb +92 -0
- data/lib/vantiv_lite/response.rb +72 -0
- data/lib/vantiv_lite/version.rb +12 -0
- data/lib/vantiv_lite/xml/nokogiri.rb +46 -0
- data/lib/vantiv_lite/xml/ox.rb +45 -0
- data/lib/vantiv_lite/xml/parser.rb +27 -0
- data/lib/vantiv_lite/xml/rexml.rb +55 -0
- data/lib/vantiv_lite/xml/serializer.rb +41 -0
- data/lib/vantiv_lite/xml.rb +36 -0
- data/lib/vantiv_lite.rb +30 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5fc133b7d98fa13b255fa742c638a61b4967ab37
|
4
|
+
data.tar.gz: 667a4b905d9937d14f62abaefa30967dd9dd428f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: db7cea41c6b32da8c25c827de18fa7f407b933dbfce1efd26e43f8da7da8cd86f6bb2e85d9e5b1e0f6b3a6631ad3045f4b1bbac9ffc32d570fc32c9042e57f6d
|
7
|
+
data.tar.gz: 78930183f9f3581eb86dc160c77439af517ba4c549a79a8659a7250d94a6ef792c4779f2e98110086de9d249a46a90fa1c5693b0739d6d791d9c0adedbdf489b
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2018 Joshua Hansen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# VantivLite
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
This gem provides a simple interface for interacting with Vantiv's (WorldPay) LitleOnline eCommerce API. There's no real modeling. It's just a convenient way to configure your environment, send requests, and receive responses in hashes without all the mess of dealing with XML. As such, it should work with any API request available.
|
6
|
+
|
7
|
+
### Why not just use [LitleOnline](https://github.com/Vantiv/litle-sdk-for-ruby), the official SDK?
|
8
|
+
|
9
|
+
There are a number of reasons for creating this gem, despite the fact that an official Ruby implementation already exists. LitleOnline's code not particularly idiomatic Ruby. It's unlikely the developers are experienced Rubyists. That wouldn't be a show-stopper in and of itself, but the dependencies imposed by the gem are downright painful:
|
10
|
+
|
11
|
+
1. [`libxml-ruby`](https://github.com/xml4r/libxml-ruby): A large library that requires an even larger native extension. Given the nature and size of the XML being generated, this is overkill unless you're already using `libxml-ruby` (and odds are you're not).
|
12
|
+
2. [`xml-object`](https://github.com/burke/xml-object): In addition to being abandoned at this point, it has a dependency of [`ActiveSupport`](https://github.com/rails/rails/tree/master/activesupport)
|
13
|
+
|
14
|
+
There are no requirements outside the standard library for this gem, although you can optionally use [Nokogiri](https://github.com/sparklemotion/nokogiri) or [Ox](https://github.com/ohler55/ox) for parsing/serializing XML.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Pretty standard gem stuff.
|
19
|
+
|
20
|
+
$ gem install vantiv_lite
|
21
|
+
|
22
|
+
When using [Bundler](https://bundler.io) or requiring this library in general, it's important to note that it will attempt to load it's XML add-ons by default If Nokogiri or Ox is already defined, it will use them in that order. Otherwise, it will use the default of REXML. The only consideration is that REXML will get required if neither optional library is already required.
|
23
|
+
|
24
|
+
So, ensure your XML libs load first or just manually load it after the fact.
|
25
|
+
|
26
|
+
## Configuration
|
27
|
+
|
28
|
+
Out of the proverbial box, this should Just Work™ with Vantiv's (WorldPay) test environment using version 8.22 of the API. Obviously, when you go to production, that's probably not ideal. There are a number of ways to configure this gem.
|
29
|
+
|
30
|
+
If you're integrating into a system that's using a single configuration---pretty common to just process credit cards for your institution---you can use a global configuration set either programmatically or via environment variables:
|
31
|
+
|
32
|
+
**Note:** It's unlikely you're using version 8.22. In fact, versions can be a sticky issue with this API. Ensure you're using the version that has been assigned to you! You don't want to do all your testing in the default 8.22 only to find out you're on the latest when you move to `prelive`.
|
33
|
+
|
34
|
+
### Programmatically
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
VantivLite.configure do
|
38
|
+
env 'sandbox'
|
39
|
+
merchant_id 'default'
|
40
|
+
password 'sandbox'
|
41
|
+
proxy_url 'http://user:passsword@proxy.internal:8888'
|
42
|
+
report_group 'Default Report Group'
|
43
|
+
username 'sandbox'
|
44
|
+
version '8.22'
|
45
|
+
xml_lib 'REXML'
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
Note: All values displayed above are the default values, with the exception of `proxy_url` which is `nil` by default. (There's a good chance you'll need to set `proxy_url` in `prelive` and `postlive` environments since they are IP-whitelisted.)
|
50
|
+
|
51
|
+
### `ENV`
|
52
|
+
|
53
|
+
Prefix any configuration option with `vantiv_` and it will be automatically set:
|
54
|
+
|
55
|
+
* `ENV['vantiv_env']`
|
56
|
+
* `ENV['vantiv_merchant_id']`
|
57
|
+
* `ENV['vantiv_password']`
|
58
|
+
* `ENV['vantiv_proxy_url']`
|
59
|
+
* `ENV['vantiv_report_group']`
|
60
|
+
* `ENV['vantiv_username']`
|
61
|
+
* `ENV['vantiv_version']`
|
62
|
+
* `ENV['vantiv_xml_lib']`
|
63
|
+
|
64
|
+
You can return the configuration set by the environment with `VantivLite.env_config` which might be useful in situations where you want multiple configurations modified from a default set by the environment.
|
65
|
+
|
66
|
+
### Multiple Configurations
|
67
|
+
|
68
|
+
If you're building a platform that allows multiple clients to plug into Vantiv's API with their own credentials, IDs, or whatever else you'll need to use different configuration options for each. This is done by creating `VantivLite::Config` objects and injecting them into requests. (The global default is used by default in new requests.)
|
69
|
+
|
70
|
+
This can be done with an existing config:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
config = VantivLite.env_config
|
74
|
+
new_config = config.with(username: 'user', password: 'password2')
|
75
|
+
```
|
76
|
+
|
77
|
+
Or by just creating a new one:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
# With a hash:
|
81
|
+
|
82
|
+
config = VantivLite::Config.new(env: 'prelive' version: '11.1')
|
83
|
+
|
84
|
+
# With DSL:
|
85
|
+
|
86
|
+
config = VantivLite::Config.build
|
87
|
+
proxy_url 'http://proxy.internal:8888'
|
88
|
+
xml_lib 'Ox'
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
## Making Requests
|
93
|
+
|
94
|
+
A basic request can be made using `VantivLite.request`. This uses the global config and request objects:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
params = {
|
98
|
+
register_token_request: {
|
99
|
+
order_id: '50',
|
100
|
+
account_number: '4457119922390123'
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
response = VantivLite.request(params) # => #<VantivLite::Response>
|
105
|
+
```
|
106
|
+
|
107
|
+
This will return a `VantivLite::Response` which itself operates much like a hash:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
response.dig(:register_token_response, :litle_token) # => "1111222233330123"
|
111
|
+
```
|
112
|
+
|
113
|
+
For many simple transactions the `*_request` and `*_response` keys get a little tedious. So, this can be abreviated to the following:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
params = {
|
117
|
+
order_id: '50',
|
118
|
+
account_number: '4457119922390123'
|
119
|
+
}
|
120
|
+
|
121
|
+
response = VantivLite.register_token(params).dig(:litle_token) # => "1111222233330123"
|
122
|
+
```
|
123
|
+
|
124
|
+
There are shortcuts for the requests:
|
125
|
+
|
126
|
+
* `auth_reversal`
|
127
|
+
* `authorization`
|
128
|
+
* `capture`
|
129
|
+
* `credit`
|
130
|
+
* `register_token`
|
131
|
+
* `sale`
|
132
|
+
* `void`
|
133
|
+
|
134
|
+
Note that the only transformation that is done is underscoring and symbolizing keys. No other mapping is done.
|
135
|
+
|
136
|
+
### Requests With Multiple Configurations
|
137
|
+
|
138
|
+
`VantiveLite.request` (and the various convenience versions) simply uses `Vantiv.default_request` which is just an instance of `VantivLite::Request`. The request object itself can be used similarly with the methods by invoking `#call`:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
params = {
|
142
|
+
register_token_request: {
|
143
|
+
order_id: '50',
|
144
|
+
account_number: '4457119922390123'
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
response = VantivLite::Request.new(custom_config).(params) # => #<VantivLite::Response>
|
149
|
+
|
150
|
+
# Shortcut methods also work:
|
151
|
+
|
152
|
+
params = {
|
153
|
+
order_id: '50',
|
154
|
+
account_number: '4457119922390123'
|
155
|
+
}
|
156
|
+
|
157
|
+
response = VantivLite::Request.new(custom_config).register_token(params)
|
158
|
+
```
|
159
|
+
|
160
|
+
### Elements and Attributes
|
161
|
+
|
162
|
+
Obviously, XML doesn't map nice and neat to a hash and vice-versa. However, Vantiv's API doesn't make heavy use of attributes so, on serialization, certain keys are serialized into attributes and return hashes just merge everything together. For example, if you wanted to set an `id` attribute, you would do the following:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
params = {
|
166
|
+
register_token_request: {
|
167
|
+
id: 'abcdef',
|
168
|
+
order_id: '50',
|
169
|
+
account_number: '4457119922390123'
|
170
|
+
}
|
171
|
+
}
|
172
|
+
```
|
173
|
+
|
174
|
+
See `VantivLite::XML::Serializer` for a list of defaults.
|
175
|
+
|
176
|
+
### Order Matters
|
177
|
+
|
178
|
+
This library does not use any sort of models and the mapping done is purely to rename keys into something more "Ruby-ish." Unfortunately, the API XML XSDs from Vantiv are relatively picky. Your hash keys should be in the same order they appear in the documentation.
|
179
|
+
|
180
|
+
If you get an error like this:
|
181
|
+
|
182
|
+
```
|
183
|
+
VantivLite::Response::ServerError: Error validating xml data against the schema cvc-complex-type.2.4.a: Invalid content was found starting with element 'orderId'. One of '{"http://www.litle.com/schema":cardValidationNum}' is expected.
|
184
|
+
```
|
185
|
+
|
186
|
+
It probably means your keys are out of order.
|
187
|
+
|
188
|
+
Yes, this could be solved by consuming the XSDs and validating requests or modeling every single object. However, the overhead probably isn't worth it and can require version specific changes. Developers are just going to wrap these lower level calls in their own objects anyway where order can be enforced as necessary.
|
189
|
+
|
190
|
+
## Vantiv Environments
|
191
|
+
|
192
|
+
Valid environments are:
|
193
|
+
|
194
|
+
* `sandbox`
|
195
|
+
* `prelive`
|
196
|
+
* `postlive`
|
197
|
+
|
198
|
+
This configures the API url. At the moment, that's really all it does.
|
199
|
+
|
200
|
+
## Contributing
|
201
|
+
|
202
|
+
### Issue Guidelines
|
203
|
+
|
204
|
+
GitHub issues are for bugs, not support. As of right now, there is no official support for this gem. You can try reaching out to the author, [Joshua Hansen](mailto:joshua@epicbanality.com?subject=VantiveLite) if you're really stuck, but there's a pretty high chance that won't go anywhere at the moment or you'll get a response like this:
|
205
|
+
|
206
|
+
> Hi. I'm super busy. It's nothing personal. Check the README first if you haven't already. If you don 't find your answer there, it's time to start reading the source. Have fun! Let me know if I screwed something up.
|
207
|
+
|
208
|
+
### Pull Request Guidelines
|
209
|
+
|
210
|
+
* Include tests with your PRs.
|
211
|
+
* Run `rubocop` to ensure your style fits with the rest of the project.
|
212
|
+
|
213
|
+
### Code of Conduct
|
214
|
+
|
215
|
+
Be nice. After all, this is free code. I have a day job.
|
216
|
+
|
217
|
+
## License
|
218
|
+
|
219
|
+
See [`LICENSE.txt`](LICENSE.txt).
|
220
|
+
|
221
|
+
## What if I stop maintaining this?
|
222
|
+
|
223
|
+
The codebase isn't huge. If you opt to rely on this code and I die/get bored/find enlightenment you should be able to maintain it. Sadly, that's the only guarantee at the moment!
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module VantivLite
|
7
|
+
class Config
|
8
|
+
InvalidEnvironment = Class.new(StandardError)
|
9
|
+
|
10
|
+
ENVS = {
|
11
|
+
'sandbox' => URI('https://www.testvantivcnp.com/sandbox/communicator/online'),
|
12
|
+
'prelive' => URI('https://payments.vantivprelive.com/vap/communicator/online'),
|
13
|
+
'postlive' => URI('https://payments.vantivcnp.com/vap/communicator/online')
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
OPTS = %i[env merchant_id password proxy_url report_group username version xml_lib].freeze
|
17
|
+
|
18
|
+
class Builder
|
19
|
+
def self.call(&blk)
|
20
|
+
new.(&blk)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@opts = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
OPTS.each { |o| define_method(o) { |val| @opts[o] = val.to_s } }
|
28
|
+
|
29
|
+
def call(&blk)
|
30
|
+
instance_eval(&blk) if block_given?
|
31
|
+
Config.new(@opts)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.build(&blk)
|
36
|
+
Config::Builder.(&blk)
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :proxy_uri, :sandbox, :uri
|
40
|
+
alias sandbox? sandbox
|
41
|
+
|
42
|
+
def initialize(**opts)
|
43
|
+
@opts = opts.each_with_object({}) { |(k, v), h| OPTS.include?(k = k.to_sym) && h[k] = v.to_s }
|
44
|
+
defaults!
|
45
|
+
load_xml_lib
|
46
|
+
env_valid?
|
47
|
+
proxy_uri!
|
48
|
+
@uri = ENVS[@opts[:env]]
|
49
|
+
end
|
50
|
+
|
51
|
+
def opts
|
52
|
+
@opts.dup
|
53
|
+
end
|
54
|
+
|
55
|
+
def proxy_args
|
56
|
+
@proxy_uri ? [@proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password] : []
|
57
|
+
end
|
58
|
+
|
59
|
+
def with(**opts)
|
60
|
+
new(@opts.merge(opts))
|
61
|
+
end
|
62
|
+
|
63
|
+
OPTS.each { |o| define_method(o) { @opts[o] } }
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def default_xml_lib
|
68
|
+
return 'Ox' if defined?(::Ox)
|
69
|
+
return 'Nokogiri' if defined?(::Nokogiri)
|
70
|
+
'REXML'
|
71
|
+
end
|
72
|
+
|
73
|
+
def defaults!
|
74
|
+
@opts[:env] ||= 'sandbox'
|
75
|
+
@opts[:report_group] ||= 'Default Report Group'
|
76
|
+
@opts[:version] ||= '8.22'
|
77
|
+
@opts[:xml_lib] ||= default_xml_lib
|
78
|
+
return unless (@sandbox = (opts[:env] == 'sandbox'))
|
79
|
+
@opts[:merchant_id] ||= 'default'
|
80
|
+
@opts[:password] ||= 'sandbox'
|
81
|
+
@opts[:username] ||= 'sandbox'
|
82
|
+
end
|
83
|
+
|
84
|
+
def env_valid?
|
85
|
+
raise InvalidEnvironment, %(:env must be set to one of: "#{ENVS.keys.join('", "')}") unless
|
86
|
+
ENVS.key?(@opts[:env])
|
87
|
+
end
|
88
|
+
|
89
|
+
def load_xml_lib
|
90
|
+
require "vantiv_lite/xml/#{@opts[:xml_lib].downcase}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def proxy_uri!
|
94
|
+
return unless (url = @opts['proxy_url'] || ENV['HTTP_PROXY'] || ENV['http_proxy'])
|
95
|
+
@proxy_uri = URI(url)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'vantiv_lite/response'
|
5
|
+
require 'vantiv_lite/xml'
|
6
|
+
|
7
|
+
module VantivLite
|
8
|
+
TRANSACTIONS = %w[
|
9
|
+
auth_reversal
|
10
|
+
authorization
|
11
|
+
capture
|
12
|
+
credit
|
13
|
+
register_token
|
14
|
+
sale
|
15
|
+
void
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
class Request
|
19
|
+
InvalidConfig = Class.new(StandardError)
|
20
|
+
ResponseError = Class.new(StandardError)
|
21
|
+
|
22
|
+
attr_reader :config, :http, :serializer
|
23
|
+
|
24
|
+
def initialize(config = VantivLite.default_config, http: nil, serializer: nil)
|
25
|
+
raise InvalidConfig, 'invalid or missing config' unless config.is_a?(Config)
|
26
|
+
@config = config
|
27
|
+
@http = http || _http
|
28
|
+
@parser = XML.parser_with(config.xml_lib)
|
29
|
+
@serializer = serializer || XML.serializer_with(config.xml_lib)
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(request_hash, *dig_keys)
|
33
|
+
Response.new(post(serializer.(format_request(request_hash))), *dig_keys, parser: @parser)
|
34
|
+
end
|
35
|
+
|
36
|
+
def post(xml)
|
37
|
+
http.start { |h| h.request(post_request(xml)) }
|
38
|
+
end
|
39
|
+
|
40
|
+
TRANSACTIONS.each do |t|
|
41
|
+
define_method(t) { |hash| call({ :"#{t}_request" => hash }, :"#{t}_response") }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def _http
|
47
|
+
Net::HTTP.new(config.uri.host, config.uri.port, *config.proxy_args).tap do |h|
|
48
|
+
h.use_ssl = true if config.uri.scheme == 'https'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_attributes_with(hash)
|
53
|
+
hash['id'] ||= SecureRandom.hex(12)
|
54
|
+
hash['reportGroup'] ||= config.report_group
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def format_request(request_hash)
|
59
|
+
{
|
60
|
+
'litleOnlineRequest' => {
|
61
|
+
'xmlns' => 'http://www.litle.com/schema',
|
62
|
+
'version' => config.version,
|
63
|
+
'merchantId' => config.merchant_id,
|
64
|
+
'authentication' => { 'user' => config.username, 'password' => config.password }
|
65
|
+
}.merge(insert_default_attributes(lower_camelize_keys(request_hash)))
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def insert_default_attributes(request_hash)
|
70
|
+
request_hash.each_with_object({}) do |(k, obj), h|
|
71
|
+
h[k] = XML.hash_or_array(obj) { |o| default_attributes_with(o) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def lower_camelize_keys(hash)
|
76
|
+
XML.transform_keys(hash) do |k|
|
77
|
+
if k.is_a?(String)
|
78
|
+
k
|
79
|
+
else
|
80
|
+
k.to_s.gsub(/_[a-z]/i) { |m| m[1].upcase }.tap { |s| s[0] = s[0].downcase }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def post_request(xml)
|
86
|
+
Net::HTTP::Post.new(config.uri.path).tap do |r|
|
87
|
+
r['Content-Type'] ||= 'text/xml; charset=UTF-8'
|
88
|
+
r.body = xml
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'vantiv_lite/xml'
|
4
|
+
|
5
|
+
module VantivLite
|
6
|
+
class Response
|
7
|
+
ServerError = Class.new(StandardError)
|
8
|
+
ROOT_KEY = :litle_online_response
|
9
|
+
|
10
|
+
module Refinements
|
11
|
+
[Array, Hash].each do |klass|
|
12
|
+
next if klass.public_instance_methods.include?(:dig)
|
13
|
+
refine klass do
|
14
|
+
def dig(key, *next_keys)
|
15
|
+
Dig.(self, key, *next_keys)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
using Refinements
|
22
|
+
|
23
|
+
Dig = lambda do |obj, key, *next_keys|
|
24
|
+
begin
|
25
|
+
next_obj = obj[key]
|
26
|
+
next_obj.nil? || next_keys.empty? ? next_obj : next_obj.dig(*next_keys)
|
27
|
+
rescue NoMethodError
|
28
|
+
raise TypeError, "#{next_obj.class.name} does not have #dig method"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
include Enumerable
|
33
|
+
|
34
|
+
attr_reader :to_h
|
35
|
+
alias to_hash to_h
|
36
|
+
|
37
|
+
def initialize(http_response, *dig_keys, parser:)
|
38
|
+
http_ok?(http_response)
|
39
|
+
@to_h = response_hash_with(parser.(http_response.body), dig_keys)
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](key)
|
43
|
+
@to_h[key]
|
44
|
+
end
|
45
|
+
|
46
|
+
def dig(key, *next_keys)
|
47
|
+
Dig.(@to_h, key, *next_keys)
|
48
|
+
end
|
49
|
+
|
50
|
+
def each(*args, &blk)
|
51
|
+
@to_h.each(*args, &blk)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def http_ok?(http_response)
|
57
|
+
raise ServerError, "server responded with #{http_response.code} instead of 200" unless
|
58
|
+
http_response.code == '200'
|
59
|
+
end
|
60
|
+
|
61
|
+
def response_hash_with(response_hash, dig_keys)
|
62
|
+
response_hash = underscore_symbolize_keys(response_hash)
|
63
|
+
raise ServerError, "missing root :#{ROOT_KEY}" unless (root_hash = response_hash[ROOT_KEY])
|
64
|
+
raise ServerError, root_hash[:message] unless root_hash[:response] == '0'
|
65
|
+
dig_keys.any? ? root_hash.dig(*dig_keys) : root_hash
|
66
|
+
end
|
67
|
+
|
68
|
+
def underscore_symbolize_keys(hash)
|
69
|
+
XML.transform_keys(hash) { |k| k.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }.to_sym }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'vantiv_lite/xml'
|
5
|
+
|
6
|
+
module VantivLite
|
7
|
+
module XML
|
8
|
+
module Nokogiri
|
9
|
+
class Parser
|
10
|
+
include XML::Parser
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def root(xml)
|
15
|
+
::Nokogiri::XML(xml) { |c| c.options = ::Nokogiri::XML::ParseOptions::NOBLANKS }.root
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_with!(element)
|
19
|
+
return element.text if element.attributes.empty? && element.elements.empty?
|
20
|
+
element.to_h.merge(hash_with(*element.elements))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Serializer
|
25
|
+
include XML::Serializer
|
26
|
+
|
27
|
+
def call(hash)
|
28
|
+
::Nokogiri::XML::Document.new.tap { |d| add_xml_elements!(d, hash) }.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def attributes_or_elements!(parent, key, value)
|
34
|
+
return parent[key] = text_with(value) if attributes.include?(key)
|
35
|
+
e = ::Nokogiri::XML::Element.new(key, parent)
|
36
|
+
parent.add_child(e)
|
37
|
+
add_xml_elements!(e, value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert_text!(element, text)
|
41
|
+
element.add_child(text_with(text))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'ox'
|
4
|
+
require 'vantiv_lite/xml'
|
5
|
+
|
6
|
+
module VantivLite
|
7
|
+
module XML
|
8
|
+
module Ox
|
9
|
+
class Parser
|
10
|
+
include XML::Parser
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def root(xml)
|
15
|
+
::Ox.load(xml, symbolize_keys: false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_with!(node)
|
19
|
+
node.text ? node.text : node.attributes.merge(hash_with(*node.nodes))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Serializer
|
24
|
+
include XML::Serializer
|
25
|
+
|
26
|
+
def call(hash)
|
27
|
+
::Ox.dump(add_xml_elements!(::Ox::Document.new(version: '1.0'), hash))
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def attributes_or_elements!(parent, key, value)
|
33
|
+
return parent[key] = text_with(value) if attributes.include?(key)
|
34
|
+
e = ::Ox::Element.new(key)
|
35
|
+
parent << e
|
36
|
+
add_xml_elements!(e, value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def insert_text!(node, text)
|
40
|
+
node << text_with(text)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module VantivLite
|
4
|
+
module XML
|
5
|
+
module Parser
|
6
|
+
def call(xml)
|
7
|
+
hash_with(root(xml))
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def hash_with(*nodes)
|
13
|
+
nodes.each_with_object({}) do |n, h|
|
14
|
+
inject_or_merge!(h, n.name, value_with!(n))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def inject_or_merge!(hash, key, value)
|
19
|
+
if hash.key?(key)
|
20
|
+
cv = hash[key]
|
21
|
+
value = cv.is_a?(Array) ? cv.push(value) : [cv, value]
|
22
|
+
end
|
23
|
+
hash[key] = value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'vantiv_lite/xml'
|
5
|
+
|
6
|
+
module VantivLite
|
7
|
+
module XML
|
8
|
+
module REXML
|
9
|
+
class Parser
|
10
|
+
include XML::Parser
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def attribute_hash(element)
|
15
|
+
element.attributes.each_with_object({}) { |(k, v), h| h[k] = v.to_s }
|
16
|
+
end
|
17
|
+
|
18
|
+
def root(xml)
|
19
|
+
::REXML::Document.new(xml).root
|
20
|
+
end
|
21
|
+
|
22
|
+
def value_with!(element)
|
23
|
+
children = element.elements.to_a
|
24
|
+
return element.text if element.attributes.empty? && children.empty?
|
25
|
+
attribute_hash(element).merge(hash_with(*children))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Serializer
|
30
|
+
include XML::Serializer
|
31
|
+
|
32
|
+
def call(hash)
|
33
|
+
# NOTE: This forces attributes to be delimited with double quotes. Despite the fact that
|
34
|
+
# the Litle API returns single-quoted responses, requests with single-quoted attributes
|
35
|
+
# will yield an internal server error.
|
36
|
+
::REXML::Document.new(nil, attribute_quote: :quote).tap do |d|
|
37
|
+
d.add(::REXML::XMLDecl.new)
|
38
|
+
add_xml_elements!(d, hash)
|
39
|
+
end.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def attributes_or_elements!(parent, key, value)
|
45
|
+
return parent.add_attribute(key, text_with(value)) if attributes.include?(key)
|
46
|
+
add_xml_elements!(::REXML::Element.new(key, parent, parent.context), value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def insert_text!(element, text)
|
50
|
+
element.add_text(text_with(text))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module VantivLite
|
4
|
+
module XML
|
5
|
+
module Serializer
|
6
|
+
ATTRIBUTES = %w[id merchantId reportGroup version xmlns].freeze
|
7
|
+
|
8
|
+
@@type_coercions = {}
|
9
|
+
def self.coerce(type, obj = nil, &blk)
|
10
|
+
raise TypeError, '`type` must be a `Class`' unless type.is_a?(Class)
|
11
|
+
obj ||= blk
|
12
|
+
raise TypeError, '`obj` must respond to `call`' unless obj.respond_to?(:call)
|
13
|
+
@@type_coercions[type] = obj
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :attributes
|
17
|
+
|
18
|
+
def initialize(attributes: ATTRIBUTES)
|
19
|
+
@attributes = attributes
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def add_xml_elements!(parent, obj)
|
25
|
+
case obj
|
26
|
+
when Hash
|
27
|
+
obj.each { |k, v| attributes_or_elements!(parent, k, v) }
|
28
|
+
when Array
|
29
|
+
obj.each { |v| add_xml_elements!(parent, v) }
|
30
|
+
else
|
31
|
+
insert_text!(parent, obj)
|
32
|
+
end
|
33
|
+
parent
|
34
|
+
end
|
35
|
+
|
36
|
+
def text_with(obj)
|
37
|
+
(callable = @@type_coercions[obj.class]) ? callable.call(obj) : obj.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'vantiv_lite/xml/parser'
|
4
|
+
require 'vantiv_lite/xml/serializer'
|
5
|
+
|
6
|
+
module VantivLite
|
7
|
+
module XML
|
8
|
+
class << self
|
9
|
+
def hash_or_array(obj)
|
10
|
+
case obj
|
11
|
+
when Hash
|
12
|
+
yield(obj)
|
13
|
+
when Array
|
14
|
+
obj.map { |o| yield(o) }
|
15
|
+
else
|
16
|
+
obj
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parser_with(name)
|
21
|
+
const_get(name)::Parser.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def serializer_with(name, attributes: Serializer::ATTRIBUTES)
|
25
|
+
const_get(name)::Serializer.new(attributes: attributes)
|
26
|
+
end
|
27
|
+
|
28
|
+
def transform_keys(hash, &blk)
|
29
|
+
return hash unless hash.is_a?(Hash)
|
30
|
+
hash.each_with_object({}) do |(k, obj), h|
|
31
|
+
h[yield(k)] = XML.hash_or_array(obj) { |o| transform_keys(o, &blk) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/vantiv_lite.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'vantiv_lite/config'
|
4
|
+
require 'vantiv_lite/request'
|
5
|
+
|
6
|
+
module VantivLite
|
7
|
+
class << self
|
8
|
+
attr_reader :default_config, :default_request
|
9
|
+
|
10
|
+
def configure(config = env_config, &blk)
|
11
|
+
@default_config = block_given? ? Config.build(&blk) : Config.new(config)
|
12
|
+
@default_request = Request.new(@default_config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def env_config
|
16
|
+
Config::OPTS.each_with_object({}) do |k, h|
|
17
|
+
env_key = "vaniv_#{k}"
|
18
|
+
h[k] = ENV[env_key] if ENV.key?(env_key)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def request(request_hash)
|
23
|
+
default_request.(request_hash)
|
24
|
+
end
|
25
|
+
alias call request
|
26
|
+
|
27
|
+
TRANSACTIONS.each { |t| define_method(t) { |hash| default_request.public_send(t, hash) } }
|
28
|
+
end
|
29
|
+
configure
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vantiv_lite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joshua
|
8
|
+
- Hansen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-05-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.16'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.16'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: minitest
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '5.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '5.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: nokogiri
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.8'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.8'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: ox
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2.9'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.9'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rake
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '10.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '10.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rubocop
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.56'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.56'
|
98
|
+
description: A Simplified Vanitiv/WorldPay (LitleOnline) API Library
|
99
|
+
email:
|
100
|
+
- joshua@epicbanality.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- lib/vantiv_lite.rb
|
108
|
+
- lib/vantiv_lite/config.rb
|
109
|
+
- lib/vantiv_lite/request.rb
|
110
|
+
- lib/vantiv_lite/response.rb
|
111
|
+
- lib/vantiv_lite/version.rb
|
112
|
+
- lib/vantiv_lite/xml.rb
|
113
|
+
- lib/vantiv_lite/xml/nokogiri.rb
|
114
|
+
- lib/vantiv_lite/xml/ox.rb
|
115
|
+
- lib/vantiv_lite/xml/parser.rb
|
116
|
+
- lib/vantiv_lite/xml/rexml.rb
|
117
|
+
- lib/vantiv_lite/xml/serializer.rb
|
118
|
+
homepage: https://github.com/binarypaladin/vantiv_lite
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 2.2.0
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.4.5.4
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: A Simplified Vanitiv/WorldPay (LitleOnline) API Library
|
142
|
+
test_files: []
|