cubits 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e681c887f584d39661aeadad10c1e297abad0ab
4
- data.tar.gz: 1cc90230a6bfae073d6ec6ba23a314fcf518f4f6
3
+ metadata.gz: d337170a2395378547f050001109b90f2387bb58
4
+ data.tar.gz: 8b505f0edeb55b9a5c1a518f8dad387a9e167f42
5
5
  SHA512:
6
- metadata.gz: ef1a5b930c12ab8dc276eeb8a56552c2a616fd88f773b1d0711cb2318d1a4ff496d1ea4b9a8d63c759a7241a61b582b2354c184f13305b40181ebb4d2b53323d
7
- data.tar.gz: acc7922ad9ed67ccba6a1c5525cc0459525dc75a8c7531a07b62032d4893063bfcba361e3f4c38b862ab396b14a39bb609eb966fcccb39f665015d3ebb7cd75b
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
 
@@ -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
- @connection = Connection.new(params)
28
+ @connections ||= {}
29
+ @connections[params[:key]] = Connection.new(params)
27
30
  end
28
31
 
29
- #
30
32
  # Returns configured Connection object
31
33
  #
32
- def self.connection
33
- @connection || fail('Cubits connection is not configured')
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
- @connection = nil
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
@@ -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
@@ -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 = OpenSSL::HMAC.hexdigest('sha512', @secret, msg)
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
@@ -24,4 +24,11 @@ module Cubits
24
24
 
25
25
  class InternalServerError < ServerError
26
26
  end
27
+
28
+ # Cubits client errors
29
+ class InvalidSignature < StandardError
30
+ end
31
+
32
+ class InsecureCallback < StandardError
33
+ end
27
34
  end # module Cubits
@@ -1,6 +1,6 @@
1
1
  module Cubits
2
2
  class Invoice < Resource
3
3
  path '/api/v1/invoices'
4
- expose_methods :create, :find, :reload
4
+ expose_methods :create, :find, :reload, :from_callback
5
5
  end # class Invoice
6
6
  end
@@ -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 #{self.class.name}" unless @path
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
@@ -1,3 +1,3 @@
1
1
  module Cubits
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -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.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-03-25 00:00:00.000000000 Z
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