cubits 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +10 -0
- data/README.md +136 -0
- data/lib/cubits.rb +18 -5
- data/lib/cubits/callback.rb +68 -0
- data/lib/cubits/channel.rb +8 -1
- data/lib/cubits/connection.rb +11 -1
- data/lib/cubits/errors.rb +7 -0
- data/lib/cubits/invoice.rb +1 -1
- data/lib/cubits/resource.rb +52 -1
- data/lib/cubits/resource_collection.rb +69 -0
- data/lib/cubits/version.rb +1 -1
- data/spec/lib/cubits/callback_spec.rb +82 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d337170a2395378547f050001109b90f2387bb58
|
4
|
+
data.tar.gz: 8b505f0edeb55b9a5c1a518f8dad387a9e167f42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2c5625fe8ae836a0fb521e2b423eed6af3eca3c95d44988b8497bb2710201ea0a176ef81cabd2155c3ff04deda7f164d11fab2dc4d8f81b6a9b41de373dbc88
|
7
|
+
data.tar.gz: 9327d05bb549d9478ea3d03112fc98d4dfa9cde73c3f1dc83e9fab0e054e15bea76bc098f21cc9c3291330645ab5cb3b45b99884586418b164f900d96088d31b
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# 0.5.0
|
2
|
+
|
3
|
+
* Added Cubits::Channel::Tx collection implementation.
|
4
|
+
Use `channel.txs.all` to retrieve all channel transactions,
|
5
|
+
`channel.txs.find()` to find a single transaction.
|
6
|
+
|
7
|
+
* Added Cubits::Callback module for callback signature verification
|
8
|
+
and resource instantiation.
|
9
|
+
Cubits::Invoice, Cubits::Channel, Cubits::Channel::Tx expose .from_callback() method.
|
10
|
+
|
1
11
|
# 0.4.0
|
2
12
|
|
3
13
|
Enforcing strict server SSL certificate checks, to prevent MitM attacks
|
data/README.md
CHANGED
@@ -145,6 +145,49 @@ channel.update(reference: "CHAN_192357")
|
|
145
145
|
channel.reference # => "CHAN_192357"
|
146
146
|
```
|
147
147
|
|
148
|
+
#### #txs
|
149
|
+
|
150
|
+
Returns channel transactions as a collection of `Cubits::Channel::Tx` objects.
|
151
|
+
|
152
|
+
The collection exposes methods `.all` and `.find()`:
|
153
|
+
|
154
|
+
Listing all channel transactions:
|
155
|
+
```ruby
|
156
|
+
channel = Cubits::Channel.find("d17ad6c96f83162a2764ecd4739d7ab2")
|
157
|
+
|
158
|
+
channel.txs.all # => [{"tx_ref_code"=>"YYWZN", ...}, ...]
|
159
|
+
```
|
160
|
+
|
161
|
+
Retrieving a channel transaction with given *tx_ref_code*:
|
162
|
+
```ruby
|
163
|
+
channel = Cubits::Channel.find("d17ad6c96f83162a2764ecd4739d7ab2")
|
164
|
+
tx = channel.txs.find("YYWZN")
|
165
|
+
tx.class # => Cubits::Channel::Tx
|
166
|
+
|
167
|
+
tx # => {"tx_ref_code"=>"YYWZN", "state"=>"pending", ... }
|
168
|
+
```
|
169
|
+
|
170
|
+
### Cubits::Channel::Tx
|
171
|
+
|
172
|
+
This resource represents a merchant channel transaction. An instance of `Cubits::Channel::Tx` should be obtained from a `channel.txs` collection or instantiated
|
173
|
+
from a callback.
|
174
|
+
|
175
|
+
#### #channel
|
176
|
+
|
177
|
+
Returns the `Cubits::Channel` object, owning this transaction:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
tx = Cubits::Channel::Tx.from_callback(
|
181
|
+
cubits_callback_id: 'ABCDEFGH',
|
182
|
+
cubits_key: '7287ba0902...',
|
183
|
+
cubits_signature: '7d89c35c2...',
|
184
|
+
body: '{"tx_ref_code": "YYWZN", "state": "pending", ...}'
|
185
|
+
)
|
186
|
+
|
187
|
+
tx.channel # => {"receiver_currency"=>"EUR", "name"=>nil, ...}
|
188
|
+
tx.channel.class # => Cubits::Channel
|
189
|
+
```
|
190
|
+
|
148
191
|
## Accounts
|
149
192
|
|
150
193
|
Your Cubits accounts are represented by the `Cubits::Account` class, which is a descendant of [Hashie::Mash](https://github.com/intridea/hashie#mash), so it's a Hash with a method-like access to its elements:
|
@@ -256,5 +299,98 @@ Cubits.send_money amount: '1.5000000', address: '3BnYBqPnGtRz2cfcnhxFKy3JswU3biM
|
|
256
299
|
|
257
300
|
On success `.send_money` creates a transaction and returns its reference code.
|
258
301
|
|
302
|
+
## Callbacks
|
303
|
+
|
304
|
+
Cubits Merchant API provides an authentication mechanism for callback requests it posts to the merchant specified URL's. That way merchants can be sure, that the data posted within a callback comes from a trusted source.
|
305
|
+
|
306
|
+
The callback authentication is described in detail in the [Cubits Help Center](https://cubits.com/help) Developer's section.
|
307
|
+
|
308
|
+
The `cubits` ruby gem provides a `Cubits::Callback` class for easy and straightforward callback verification and parsing.
|
309
|
+
|
310
|
+
### .from_params()
|
311
|
+
|
312
|
+
Provided all relevant headers and body are extracted from the callback request, `from_params()` method validates the signature, parses request body and returns passed data wrapped in a given `Cubits::Resource`-based class object.
|
313
|
+
|
314
|
+
Signature of the callback is verified using key+secret pair, passed to `Cubits.configure(...)` beforehand.
|
315
|
+
|
316
|
+
#### Parameters
|
317
|
+
name | type | description
|
318
|
+
---------------------|---------|---------------------
|
319
|
+
cubits_callback_id | String | Value of the CUBITS_CALLBACK_ID header
|
320
|
+
cubits_key | String | Value of the CUBITS_KEY header
|
321
|
+
cubits_signature | String | Value of the CUBITS_SIGNATURE header
|
322
|
+
body | String | Callback request body as a String
|
323
|
+
resource_class | Resource, nil | (optional) subclass of `Cubits::Resource` (e.g. `Cubits::Invoice`). If specified, an object of that class is instantiated and initialized with the parsed request body. (default: nil)
|
324
|
+
allow_insecure | Boolean | (optional) Allow insecure, unsigned callbacks (default: false)
|
325
|
+
|
326
|
+
#### Returns
|
327
|
+
|
328
|
+
An instance of a given `resource_class` or a `Hash`.
|
329
|
+
|
330
|
+
#### Errors
|
331
|
+
|
332
|
+
`Cubits::InvalidSignature` is raised if either `cubits_key` passed with the callback
|
333
|
+
does not match the preconfigured API key, or the `cubits_signature` does not match
|
334
|
+
the signature calculated from a preconfigured API key+secret pair.
|
335
|
+
|
336
|
+
`Cubits::InsecureCallback` is raised if the callback is unsigned, and `allow_insecure` option is *false*.
|
337
|
+
|
338
|
+
#### Examples
|
339
|
+
|
340
|
+
Validate and parse callback into a plain `Hash`:
|
341
|
+
```ruby
|
342
|
+
data = Cubits::Callback.from_params(
|
343
|
+
cubits_callback_id: 'ABCDEFGH',
|
344
|
+
cubits_key: '7287ba0902...',
|
345
|
+
cubits_signature: '7d89c35c2...',
|
346
|
+
body: '{"attr1": 123, "attr2": "hello"}'
|
347
|
+
)
|
348
|
+
|
349
|
+
data # => { 'attr1' => 123, 'attr2' => 'hello' }
|
350
|
+
data.class # => Hash
|
351
|
+
```
|
352
|
+
|
353
|
+
Validate and parse callback into a `Cubits::Invoice` object:
|
354
|
+
```ruby
|
355
|
+
invoice = Cubits::Callback.from_params(
|
356
|
+
cubits_callback_id: 'ABCDEFGH',
|
357
|
+
cubits_key: '7287ba0902...',
|
358
|
+
cubits_signature: '7d89c35c2...',
|
359
|
+
body: '{"attr1": 123, "attr2": "hello"}',
|
360
|
+
resource_class: Cubits::Invoice
|
361
|
+
)
|
362
|
+
|
363
|
+
invoice # => { 'attr1' => 123, 'attr2' => 'hello' }
|
364
|
+
invoice.class # => Cubits::Invoice
|
365
|
+
```
|
366
|
+
|
367
|
+
Process an insecure, unsigned callback:
|
368
|
+
```ruby
|
369
|
+
data = Cubits::Callback.from_params(
|
370
|
+
cubits_callback_id: 'ABCDEFGH',
|
371
|
+
body: '{"attr1": 123, "attr2": "hello"}',
|
372
|
+
allow_insecure: true
|
373
|
+
)
|
374
|
+
|
375
|
+
data # => { 'attr1' => 123, 'attr2' => 'hello' }
|
376
|
+
```
|
377
|
+
|
378
|
+
### Cubits::Resource.from_callback()
|
379
|
+
|
380
|
+
`Cubits::Invoice`, `Cubits::Channel` and `Cubits::Channel::Tx` expose
|
381
|
+
a helper method: `.from_callback()` which can be used to validate callback and instantiate a resource object in one go:
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
invoice = Cubits::Invoice.from_callback(
|
385
|
+
cubits_callback_id: 'ABCDEFGH',
|
386
|
+
cubits_key: '7287ba0902...',
|
387
|
+
cubits_signature: '7d89c35c2...',
|
388
|
+
body: '{"attr1": 123, "attr2": "hello"}'
|
389
|
+
)
|
390
|
+
|
391
|
+
invoice # => { 'attr1' => 123, 'attr2' => 'hello' }
|
392
|
+
invoice.class # => Cubits::Invoice
|
393
|
+
```
|
394
|
+
|
259
395
|
----
|
260
396
|
|
data/lib/cubits.rb
CHANGED
@@ -5,10 +5,12 @@ require 'cubits/connection'
|
|
5
5
|
require 'cubits/errors'
|
6
6
|
require 'cubits/helpers'
|
7
7
|
require 'cubits/resource'
|
8
|
+
require 'cubits/resource_collection'
|
8
9
|
require 'cubits/invoice'
|
9
10
|
require 'cubits/account'
|
10
11
|
require 'cubits/quote'
|
11
12
|
require 'cubits/channel'
|
13
|
+
require 'cubits/callback'
|
12
14
|
|
13
15
|
module Cubits
|
14
16
|
extend Cubits::Helpers
|
@@ -23,14 +25,24 @@ module Cubits
|
|
23
25
|
# @param params[:secret] [String] API secret obtained from Cubits
|
24
26
|
#
|
25
27
|
def self.configure(params = {})
|
26
|
-
@
|
28
|
+
@connections ||= {}
|
29
|
+
@connections[params[:key]] = Connection.new(params)
|
27
30
|
end
|
28
31
|
|
29
|
-
#
|
30
32
|
# Returns configured Connection object
|
31
33
|
#
|
32
|
-
|
33
|
-
|
34
|
+
# @param key [String] (optional) Cubits API key of the configured connection
|
35
|
+
#
|
36
|
+
# @return [Connection] Connection object matching the requested Cubits API key or first
|
37
|
+
# configured connection
|
38
|
+
#
|
39
|
+
def self.connection(key = nil)
|
40
|
+
@connections ||= {}
|
41
|
+
c = key ? @connections[key] : @connections.values.first
|
42
|
+
unless c
|
43
|
+
fail ConnectionError, "Cubits connection is not configured for key #{key || '(default)'}"
|
44
|
+
end
|
45
|
+
c
|
34
46
|
end
|
35
47
|
|
36
48
|
# Returns current Logger object
|
@@ -60,10 +72,11 @@ module Cubits
|
|
60
72
|
fail ArgumentError, 'URI is expected as new_base_url' unless new_base_url.is_a?(URI)
|
61
73
|
@base_url = new_base_url
|
62
74
|
end
|
75
|
+
|
63
76
|
# Resets all internal states
|
64
77
|
#
|
65
78
|
def self.reset
|
66
|
-
@
|
79
|
+
@connections = {}
|
67
80
|
@logger = nil
|
68
81
|
end
|
69
82
|
end # module Cubits
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Cubits
|
2
|
+
class Callback
|
3
|
+
#
|
4
|
+
# Processes callback request parsed into separate params
|
5
|
+
# and instantiates a resource object on success.
|
6
|
+
#
|
7
|
+
# @param params [Hash]
|
8
|
+
# @param params[:cubits_callback_id] [String] Value of the CUBITS_CALLBACK_ID header
|
9
|
+
# @param params[:cubits_key] [String] Value of the CUBITS_KEY header
|
10
|
+
# @param params[:cubits_signature] [String] Value of the CUBITS_SIGNATURE header
|
11
|
+
# @param params[:body] [String] Request body
|
12
|
+
# @param params[:resource_class] [Resource,nil] (optional) Instantiate a Resource based object (default: nil)
|
13
|
+
# and initialize it with parsed request body. If not specified, returns parsed body as a plain Hash
|
14
|
+
# @param params[:allow_insecure] [Boolean] (optional) Allow insecure, unsigned callbacks (default: false)
|
15
|
+
#
|
16
|
+
# @return [Resource,Hash]
|
17
|
+
#
|
18
|
+
# @raise [InvalidSignature]
|
19
|
+
# @raise [InsecureCallback]
|
20
|
+
#
|
21
|
+
def self.from_params(params = {})
|
22
|
+
result = from_params_to_hash(params)
|
23
|
+
params[:resource_class] ? params[:resource_class].new(result) : result
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def self.from_params_to_hash(params)
|
29
|
+
validate_params!(params)
|
30
|
+
if params[:cubits_signature] && !params[:cubits_signature].empty?
|
31
|
+
validate_signature!(params)
|
32
|
+
elsif !params[:allow_insecure]
|
33
|
+
fail InsecureCallback, 'Refusing to process an unsigned callback for security reasons'
|
34
|
+
end
|
35
|
+
JSON.parse(params[:body])
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.validate_params!(params)
|
39
|
+
unless params[:cubits_callback_id].is_a?(String)
|
40
|
+
fail ArgumentError, 'String is expected as :cubits_callback_id'
|
41
|
+
end
|
42
|
+
if params[:cubits_key] && !params[:cubits_key].is_a?(String)
|
43
|
+
fail ArgumentError, 'String is expected as :cubits_key'
|
44
|
+
end
|
45
|
+
if params[:cubits_signature] && !params[:cubits_signature].is_a?(String)
|
46
|
+
fail ArgumentError, 'String is expected as :cubits_signature'
|
47
|
+
end
|
48
|
+
fail ArgumentError, 'String is expected as :body' unless params[:body].is_a?(String)
|
49
|
+
if params[:resource_class]
|
50
|
+
unless params[:resource_class].is_a?(Class) && params[:resource_class] < Resource
|
51
|
+
fail ArgumentError, 'Resource based class is expected as :resource_class'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.validate_signature!(params)
|
58
|
+
connection = Cubits.connection(params[:cubits_key])
|
59
|
+
msg = params[:cubits_callback_id] + OpenSSL::Digest::SHA256.hexdigest(params[:body])
|
60
|
+
unless connection.sign_message(msg) == params[:cubits_signature]
|
61
|
+
fail InvalidSignature, 'Callback signature is invalid'
|
62
|
+
end
|
63
|
+
true
|
64
|
+
rescue ConnectionError => e
|
65
|
+
raise InvalidSignature, e.message
|
66
|
+
end
|
67
|
+
end # class Callback
|
68
|
+
end
|
data/lib/cubits/channel.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
module Cubits
|
2
2
|
class Channel < Resource
|
3
3
|
path '/api/v1/channels'
|
4
|
-
expose_methods :create, :find, :reload, :update
|
4
|
+
expose_methods :create, :find, :reload, :update, :from_callback
|
5
|
+
|
6
|
+
has_many :txs, class_name: 'Channel::Tx'
|
7
|
+
|
8
|
+
class Tx < Resource
|
9
|
+
expose_methods :from_callback
|
10
|
+
belongs_to :channel
|
11
|
+
end
|
5
12
|
end # class Channel
|
6
13
|
end
|
data/lib/cubits/connection.rb
CHANGED
@@ -38,6 +38,16 @@ module Cubits
|
|
38
38
|
request(:post, path, encoded_data)
|
39
39
|
end
|
40
40
|
|
41
|
+
# Signs the message with preconfigured secret
|
42
|
+
#
|
43
|
+
# @param msg [String]
|
44
|
+
#
|
45
|
+
# @return [String] Calculated signature
|
46
|
+
#
|
47
|
+
def sign_message(msg)
|
48
|
+
OpenSSL::HMAC.hexdigest('sha512', @secret, msg)
|
49
|
+
end
|
50
|
+
|
41
51
|
private
|
42
52
|
|
43
53
|
# Sends a request to the API
|
@@ -119,7 +129,7 @@ module Cubits
|
|
119
129
|
#
|
120
130
|
def sign_request(path, nonce, request_data)
|
121
131
|
msg = path + nonce.to_s + OpenSSL::Digest::SHA256.hexdigest(request_data)
|
122
|
-
signature =
|
132
|
+
signature = sign_message(msg)
|
123
133
|
Cubits.logger.debug 'sign_request: ' \
|
124
134
|
"path=#{path} nonce=#{nonce} request_data=#{request_data} msg=#{msg} signature=#{signature}"
|
125
135
|
signature
|
data/lib/cubits/errors.rb
CHANGED
data/lib/cubits/invoice.rb
CHANGED
data/lib/cubits/resource.rb
CHANGED
@@ -30,7 +30,7 @@ module Cubits
|
|
30
30
|
# Returns API path to resource
|
31
31
|
#
|
32
32
|
def self.path_to(resource_or_id = nil)
|
33
|
-
fail ArgumentError, "Resource path is not set for #{
|
33
|
+
fail ArgumentError, "Resource path is not set for #{name}" unless @path
|
34
34
|
if resource_or_id.is_a?(Resource)
|
35
35
|
"#{@path}/#{resource_or_id.id}"
|
36
36
|
elsif resource_or_id
|
@@ -40,6 +40,35 @@ module Cubits
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
# Associations
|
44
|
+
def self.has_many(association_name, params = {})
|
45
|
+
define_method(association_name) do
|
46
|
+
association = instance_variable_get("@#{association_name}")
|
47
|
+
return association if association
|
48
|
+
class_name = params[:class_name] || association_name.to_s.capitalize.sub(/s$/, '')
|
49
|
+
resource = Cubits.const_get(class_name) || fail("Failed to find class #{class_name}")
|
50
|
+
association = ResourceCollection.new(
|
51
|
+
path: self.class.path_to(id) + '/' + association_name.to_s,
|
52
|
+
resource: resource,
|
53
|
+
expose_methods: params[:expose_methods] || [:find, :all]
|
54
|
+
)
|
55
|
+
instance_variable_set("@#{association_name}", association)
|
56
|
+
association
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.belongs_to(association_name, params = {})
|
61
|
+
define_method(association_name) do
|
62
|
+
association_id = send :"#{association_name}_id"
|
63
|
+
unless association_id
|
64
|
+
fail ArgumentError, "No #{association_name}_id attribute is defined for #{self}"
|
65
|
+
end
|
66
|
+
class_name = params[:class_name] || association_name.to_s.capitalize
|
67
|
+
resource = Cubits.const_get(class_name) || fail("Failed to find class #{class_name}")
|
68
|
+
resource.find(association_id)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
43
72
|
# Loads collection of resources
|
44
73
|
#
|
45
74
|
# @return [Array<Resource>]
|
@@ -64,6 +93,28 @@ module Cubits
|
|
64
93
|
nil
|
65
94
|
end
|
66
95
|
|
96
|
+
# Processes callback request parsed into separate params
|
97
|
+
# and instantiates a resource object on success.
|
98
|
+
#
|
99
|
+
# @param params [Hash]
|
100
|
+
# @param params[:cubits_callback_id] [String] Value of the CUBITS_CALLBACK_ID header
|
101
|
+
# @param params[:cubits_key] [String] Value of the CUBITS_KEY header
|
102
|
+
# @param params[:cubits_signature] [String] Value of the CUBITS_SIGNATURE header
|
103
|
+
# @param params[:body] [String] Request body
|
104
|
+
# @param params[:allow_insecure] [Boolean] (optional) Allow insecure, unsigned callbacks (default: false)
|
105
|
+
#
|
106
|
+
# @return [Resource,Hash]
|
107
|
+
#
|
108
|
+
# @raise [InvalidSignature]
|
109
|
+
# @raise [InsecureCallback]
|
110
|
+
#
|
111
|
+
def self.from_callback(params)
|
112
|
+
unless exposed_method?(:from_callback)
|
113
|
+
fail NoMethodError, "Resource #{name} does not expose .from_callback"
|
114
|
+
end
|
115
|
+
Cubits::Callback.from_params(params.merge(resource_class: self))
|
116
|
+
end
|
117
|
+
|
67
118
|
# Reloads resource
|
68
119
|
#
|
69
120
|
def reload
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Cubits
|
2
|
+
class ResourceCollection
|
3
|
+
attr_reader :path, :resource
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
@path = params[:path]
|
7
|
+
@resource = params[:resource]
|
8
|
+
@exposed_methods = params[:expose_methods]
|
9
|
+
end
|
10
|
+
|
11
|
+
# By convention collection name for the resource is the last part of the path
|
12
|
+
#
|
13
|
+
def collection_name
|
14
|
+
path.split('/').last
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return true if the method is exposed by this resource
|
18
|
+
#
|
19
|
+
def exposed_method?(method_name)
|
20
|
+
(@exposed_methods || []).include?(method_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# Loads collection of resources
|
25
|
+
#
|
26
|
+
# @return [Array<Resource>]
|
27
|
+
#
|
28
|
+
def all
|
29
|
+
fail NoMethodError, "Resource #{name} does not expose .all" unless exposed_method?(:all)
|
30
|
+
Cubits.connection.get(path_to, per_page: 1000)[collection_name].map { |r| resource.new r }
|
31
|
+
rescue NotFound
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Loads resource
|
36
|
+
#
|
37
|
+
# @param id [String]
|
38
|
+
#
|
39
|
+
# @return nil if resource is not found
|
40
|
+
#
|
41
|
+
def find(id)
|
42
|
+
fail NoMethodError, "Resource #{name} does not expose .find" unless exposed_method?(:find)
|
43
|
+
resource.new Cubits.connection.get(path_to(id))
|
44
|
+
rescue NotFound
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns API path to resource
|
49
|
+
#
|
50
|
+
def path_to(resource_or_id = nil)
|
51
|
+
fail ArgumentError, "Resource path is not set for #{self}" unless path
|
52
|
+
if resource_or_id.is_a?(Resource)
|
53
|
+
"#{path}/#{resource_or_id.id}"
|
54
|
+
elsif resource_or_id
|
55
|
+
"#{path}/#{resource_or_id}"
|
56
|
+
else
|
57
|
+
path
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def name
|
62
|
+
"Collection of #{resource.name}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
"<#{self.class.name}:<#{resource.name}>:#{path}>"
|
67
|
+
end
|
68
|
+
end # class ResourceCollection
|
69
|
+
end # module Cubits
|
data/lib/cubits/version.rb
CHANGED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cubits::Callback do
|
4
|
+
let(:key) { '7287ba0902461025b01d5b99e4679018' }
|
5
|
+
let(:secret) { '93yJJ8LBDe3zNSewHBdX1XIQDjCMDIn0EKNnXrd3kfzL72fvLz99uKnXFLYuCfkt' }
|
6
|
+
let!(:configured_connection) do
|
7
|
+
Cubits.configure(key: key, secret: secret)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:cubits_key) { '7287ba0902461025b01d5b99e4679018' }
|
11
|
+
let(:cubits_callback_id) { 'ABCDEFGH' }
|
12
|
+
let(:cubits_signature) { '7d89c35c2e0840867f63b77ea575050db21a134b674d4a38f1e255518efb5b81383442cd9a888dca86dfe3e43a0769525088aac3efed3102a6b14bd1446f14a1' }
|
13
|
+
let(:body) { '{"attr1": 123, "attr2": "hello"}' }
|
14
|
+
let(:resource_class) { Cubits::Invoice }
|
15
|
+
let(:allow_insecure) { nil }
|
16
|
+
|
17
|
+
context '.from_params(),' do
|
18
|
+
let(:params) do
|
19
|
+
{
|
20
|
+
cubits_key: cubits_key,
|
21
|
+
cubits_callback_id: cubits_callback_id,
|
22
|
+
cubits_signature: cubits_signature,
|
23
|
+
body: body,
|
24
|
+
resource_class: resource_class,
|
25
|
+
allow_insecure: allow_insecure
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { described_class.from_params(params) }
|
30
|
+
|
31
|
+
it 'raises no errors for valid params' do
|
32
|
+
expect { subject }.to_not raise_error
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns a Resource instance' do
|
36
|
+
expect(subject).to be_a Cubits::Resource
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'uses the request body to initialize the Resource instance' do
|
40
|
+
expect(subject.attr1).to eq 123
|
41
|
+
expect(subject.attr2).to eq 'hello'
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when callback is NOT signed,' do
|
45
|
+
let(:cubits_key) { nil }
|
46
|
+
let(:cubits_signature) { nil }
|
47
|
+
|
48
|
+
it 'refuses to process callback' do
|
49
|
+
expect { subject }.to raise_error(Cubits::InsecureCallback)
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'and insecure callbacks are explicitly allowed,' do
|
53
|
+
let(:allow_insecure) { true }
|
54
|
+
it 'processes the unsigned callback' do
|
55
|
+
expect { subject }.to_not raise_error
|
56
|
+
end
|
57
|
+
end # and insecure callbacks are explicitly allowed
|
58
|
+
end # when callback is NOT signed
|
59
|
+
|
60
|
+
context 'when callback has an invalid signature,' do
|
61
|
+
let(:cubits_signature) { 'blablabla' }
|
62
|
+
it 'refuses to process callback' do
|
63
|
+
expect { subject }.to raise_error(Cubits::InvalidSignature)
|
64
|
+
end
|
65
|
+
end # when callback has an invalid signature
|
66
|
+
|
67
|
+
context 'when callback has an invalid API key,' do
|
68
|
+
let(:cubits_key) { 'blablabla' }
|
69
|
+
it 'refuses to process callback' do
|
70
|
+
expect { subject }.to raise_error(Cubits::InvalidSignature)
|
71
|
+
end
|
72
|
+
end # when callback has an invalid API key
|
73
|
+
|
74
|
+
context 'when no :resource_class is specified,' do
|
75
|
+
let(:resource_class) { nil }
|
76
|
+
it 'returns a Hash' do
|
77
|
+
expect(subject).to be_a Hash
|
78
|
+
expect(subject).to eq({ 'attr1' => 123, 'attr2' => 'hello' })
|
79
|
+
end
|
80
|
+
end # when no :resource_class is specified
|
81
|
+
end # .from_params()
|
82
|
+
end # describe Cubits::Connection
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cubits
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Kukushkin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -116,6 +116,7 @@ files:
|
|
116
116
|
- cubits.gemspec
|
117
117
|
- lib/cubits.rb
|
118
118
|
- lib/cubits/account.rb
|
119
|
+
- lib/cubits/callback.rb
|
119
120
|
- lib/cubits/channel.rb
|
120
121
|
- lib/cubits/connection.rb
|
121
122
|
- lib/cubits/errors.rb
|
@@ -123,8 +124,10 @@ files:
|
|
123
124
|
- lib/cubits/invoice.rb
|
124
125
|
- lib/cubits/quote.rb
|
125
126
|
- lib/cubits/resource.rb
|
127
|
+
- lib/cubits/resource_collection.rb
|
126
128
|
- lib/cubits/version.rb
|
127
129
|
- spec/lib/cubits/account_spec.rb
|
130
|
+
- spec/lib/cubits/callback_spec.rb
|
128
131
|
- spec/lib/cubits/channel_spec.rb
|
129
132
|
- spec/lib/cubits/connection_spec.rb
|
130
133
|
- spec/lib/cubits/helpers_spec.rb
|
@@ -158,6 +161,7 @@ specification_version: 4
|
|
158
161
|
summary: Ruby client for Cubits Merchant API
|
159
162
|
test_files:
|
160
163
|
- spec/lib/cubits/account_spec.rb
|
164
|
+
- spec/lib/cubits/callback_spec.rb
|
161
165
|
- spec/lib/cubits/channel_spec.rb
|
162
166
|
- spec/lib/cubits/connection_spec.rb
|
163
167
|
- spec/lib/cubits/helpers_spec.rb
|