bill_forward 1.2014.296 → 1.2015.183

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -27
  3. data/.idea/compiler.xml +23 -23
  4. data/.idea/copyright/profiles_settings.xml +2 -2
  5. data/.idea/encodings.xml +5 -5
  6. data/.idea/inspectionProfiles/Project_Default.xml +10 -10
  7. data/.idea/inspectionProfiles/profiles_settings.xml +6 -6
  8. data/.idea/misc.xml +23 -23
  9. data/.idea/modules.xml +9 -9
  10. data/.idea/scopes/scope_settings.xml +4 -4
  11. data/.idea/vcs.xml +7 -7
  12. data/.rspec +1 -1
  13. data/Gemfile +13 -9
  14. data/LICENSE.md +22 -22
  15. data/README.md +285 -227
  16. data/Rakefile +73 -73
  17. data/bill_forward.gemspec +28 -29
  18. data/bill_forward.iml +28 -28
  19. data/lib/bill_forward/billing_entity.rb +295 -262
  20. data/lib/bill_forward/client.rb +350 -355
  21. data/lib/bill_forward/entities/account.rb +18 -18
  22. data/lib/bill_forward/entities/amendments/issue_invoice_amendment.rb +10 -0
  23. data/lib/bill_forward/entities/amendments/product_rate_plan_migration_amendment.rb +18 -0
  24. data/lib/bill_forward/entities/api_configuration.rb +12 -0
  25. data/lib/bill_forward/entities/invoice.rb +29 -3
  26. data/lib/bill_forward/entities/invoice_parts/taxation_link.rb +5 -5
  27. data/lib/bill_forward/entities/organisation.rb +36 -36
  28. data/lib/bill_forward/entities/payment_method.rb +4 -4
  29. data/lib/bill_forward/entities/payment_method_subscription_link.rb +5 -5
  30. data/lib/bill_forward/entities/pricing_component.rb +21 -21
  31. data/lib/bill_forward/entities/pricing_component_tier.rb +5 -5
  32. data/lib/bill_forward/entities/pricing_component_value.rb +5 -5
  33. data/lib/bill_forward/entities/pricing_component_value_migration_amendment_mapping.rb +31 -0
  34. data/lib/bill_forward/entities/product.rb +5 -5
  35. data/lib/bill_forward/entities/product_rate_plan.rb +43 -19
  36. data/lib/bill_forward/entities/profile.rb +14 -14
  37. data/lib/bill_forward/entities/refund.rb +5 -0
  38. data/lib/bill_forward/entities/role.rb +3 -3
  39. data/lib/bill_forward/entities/stripe_ach_token.rb +9 -0
  40. data/lib/bill_forward/entities/subscription.rb +83 -53
  41. data/lib/bill_forward/entities/subscription_charge.rb +14 -0
  42. data/lib/bill_forward/insertable_entity.rb +21 -31
  43. data/lib/bill_forward/mutable_entity.rb +26 -46
  44. data/lib/bill_forward/resource_path.rb +10 -10
  45. data/lib/bill_forward/type_check.rb +20 -20
  46. data/lib/bill_forward/version.rb +4 -4
  47. data/lib/bill_forward.rb +17 -17
  48. data/scratch/Gemfile +9 -0
  49. data/scratch/scratch.example.rb +8 -0
  50. data/spec/component/account_spec.rb +199 -199
  51. data/spec/component/billing_entity_spec.rb +152 -152
  52. data/spec/functional/account_spec.rb +24 -24
  53. data/spec/functional/bad_citizen/account_spec.rb +102 -102
  54. data/spec/functional/bad_citizen/credit_note_spec.rb +14 -13
  55. data/spec/functional/bad_citizen/payment_method_spec.rb +1 -1
  56. data/spec/functional/bad_citizen/product_rate_plan_migration_amendment_spec.rb +379 -0
  57. data/spec/functional/bad_citizen/product_rate_plan_spec.rb +103 -47
  58. data/spec/functional/bad_citizen/product_spec.rb +16 -9
  59. data/spec/functional/bad_citizen/situational/authorize_net_token_spec.rb +4 -0
  60. data/spec/functional/bad_citizen/situational/malordered_entity_spec.rb +1 -1
  61. data/spec/functional/bad_citizen/situational/payment_method_spec.rb +6 -1
  62. data/spec/functional/bad_citizen/situational/subscription_chargeable_spec.rb +96 -65
  63. data/spec/functional/bad_citizen/subscription_spec.rb +99 -60
  64. data/spec/functional/bad_citizen/subscription_with_credit_spec.rb +121 -91
  65. data/spec/functional/bad_citizen/unit_of_measure_spec.rb +14 -7
  66. data/spec/functional/client_spec.rb +23 -23
  67. data/spec/functional/organisation_spec.rb +27 -27
  68. data/spec/setup_test_constants.rb +72 -72
  69. data/spec/spec_helper.rb +10 -10
  70. data/spec/syntax/account_spec.rb +23 -23
  71. data/spec/syntax/billing_entity_spec.rb +92 -92
  72. data/tools/RSpec hardcoded.sublime-build +16 -0
  73. data/tools/RSpec.sublime-build +13 -0
  74. data/tools/Ruby legacy.sublime-build +7 -0
  75. data/tools/local_bundle_build.sh +8 -0
  76. data/tools/local_bundle_install.sh +9 -0
  77. metadata +30 -85
@@ -1,263 +1,296 @@
1
- module BillForward
2
- class BillingEntity
3
- # legacy Ruby gives us this 'id' chuff. we kinda need it back.
4
- undef id if defined? id
5
- attr_accessor :_client
6
-
7
- def initialize(state_params = nil, client = nil)
8
- raise AbstractInstantiateError.new('This abstract class cannot be instantiated!') if self.class == MutableEntity
9
-
10
- client = self.class.singleton_client if client.nil?
11
- state_params = {} if state_params.nil?
12
-
13
- TypeCheck.verifyObj(Client, client, 'client')
14
- TypeCheck.verifyObj(Hash, state_params, 'state_params')
15
-
16
- @_registered_entities = Hash.new
17
- @_registered_entity_arrays = Hash.new
18
-
19
- @_client = client
20
- # initiate with empty state params
21
- # use indifferent hash so 'id' and :id are the same
22
- @_state_params = HashWithIndifferentAccess.new
23
- # legacy Ruby gives us this 'id' chuff. we kinda need it back.
24
- @_state_params.instance_eval { undef id if defined? id }
25
- # populate state params now
26
- unserialize_all state_params
27
- end
28
-
29
- class << self
30
- attr_accessor :resource_path
31
-
32
- def get_by_id(id, query_params = {}, customClient = nil)
33
- client = customClient
34
- client = singleton_client if client.nil?
35
-
36
- raise ArgumentError.new("id cannot be nil") if id.nil?
37
- TypeCheck.verifyObj(Hash, query_params, 'query_params')
38
-
39
- route = resource_path.path
40
- endpoint = ''
41
- url_full = "#{route}/#{endpoint}#{id}"
42
-
43
- response = client.get_first(url_full, query_params)
44
-
45
- # maybe use build_entity here for consistency
46
- self.new(response, client)
47
- end
48
-
49
- def get_all(query_params = {}, customClient = nil)
50
- client = customClient
51
- client = singleton_client if client.nil?
52
-
53
- TypeCheck.verifyObj(Hash, query_params, 'query_params')
54
-
55
- route = resource_path.path
56
- endpoint = ''
57
- url_full = "#{route}/#{endpoint}"
58
-
59
- response = client.get(url_full, query_params)
60
- results = response["results"]
61
-
62
- # maybe use build_entity_array here for consistency
63
- entity_array = Array.new
64
- # maybe it's an empty array, but that's okay too.
65
- results.each do |value|
66
- entity = self.new(value, client)
67
- entity_array.push(entity)
68
- end
69
- entity_array
70
- end
71
-
72
- def singleton_client
73
- Client.default_client
74
- end
75
- end
76
-
77
- def method_missing(method_id, *arguments, &block)
78
- # no call to super; our criteria is all keys.
79
- #setter
80
- if /^(\w+)=$/ =~ method_id.to_s
81
- return set_state_param($1, arguments.first)
82
- end
83
- #getter
84
- get_state_param(method_id.to_s)
85
- end
86
-
87
- def [](key)
88
- method_missing(key)
89
- end
90
-
91
- def []=(key, value)
92
- set_key = key.to_s+'='
93
- method_missing(set_key, value)
94
- end
95
-
96
- def to_ordered_hash
97
- ordered_hash = hash_with_type_at_top(@_state_params)
98
- ordered_hash
99
- end
100
-
101
- def to_json(*a)
102
- ordered_hash = to_ordered_hash
103
- ordered_hash.to_json
104
- # @_state_params.to_json
105
- end
106
-
107
- def to_unordered_hash
108
- json_string = to_json
109
- JSON.parse(json_string)
110
- end
111
-
112
- def to_s
113
- parsed = to_unordered_hash
114
- JSON.pretty_generate(parsed)
115
- end
116
-
117
- def serialize
118
- to_json
119
- end
120
-
121
- protected
122
- def hash_with_type_at_top(hash)
123
- new_hash = OrderedHashWithDotAccess.new
124
-
125
- # API presently requires '@type' (if present) to be first key in JSON
126
- if hash.has_key? '@type'
127
- # insert existing @type as first element in ordered hash
128
- new_hash['@type'] = hash.with_indifferent_access['@type']
129
- end
130
-
131
- # add key-value pairs excepting '@type' back in
132
- # no, we don't care about the order of these.
133
- hash.with_indifferent_access.reject {|key, value| key == '@type'}.each do |key, value|
134
- new_hash[key] = value
135
- end
136
-
137
- return new_hash
138
- end
139
-
140
- def set_state_param(key, value)
141
- @_state_params[key] = value
142
- get_state_param(key)
143
- end
144
-
145
- def get_state_param(key)
146
- @_state_params[key]
147
- end
148
-
149
- def unserialize_all(hash)
150
- TypeCheck.verifyObj(Hash, hash, 'hash')
151
-
152
- hash.each do |key, value|
153
- unserialized = unserialize_one value
154
- set_state_param(key, unserialized)
155
- end
156
- end
157
-
158
- def unserialize_hash(hash)
159
- TypeCheck.verifyObj(Hash, hash, 'hash')
160
-
161
- # API presently requires '@type' (if present) to be first key in JSON
162
- hash = hash_with_type_at_top(hash)
163
-
164
- hash.each do |key, value|
165
- # recurse down, so that all nested hashes get same treatment
166
- unserialized = unserialize_one value
167
-
168
- # replace with unserialized version
169
- hash[key] = unserialized
170
- end
171
-
172
- hash
173
- end
174
-
175
- def unserialize_array(array)
176
- TypeCheck.verifyObj(Array, array, 'array')
177
-
178
- array.each_with_index do |value, index|
179
- # recurse down, so that all nested hashes get same treatment
180
- unserialized = unserialize_one value
181
-
182
- # replace with unserialized version
183
- array[index] = unserialized
184
- end
185
-
186
- array
187
- end
188
-
189
- def unserialize_one(value)
190
- if value.is_a? Hash
191
- value = unserialize_hash(value)
192
- elsif value.is_a? Array
193
- value = unserialize_array(value)
194
- end
195
- value
196
- end
197
-
198
- def unserialize_entity(key, entity_class, hash)
199
- # ensure that the provided entity class derives from BillingEntity
200
- TypeCheck.verifyClass(BillingEntity, entity_class, 'entity_class')
201
- TypeCheck.verifyObj(Hash, hash, 'hash')
202
-
203
- # register the entity as one that requires bespoke serialization
204
- @_registered_entities[key] = entity_class
205
- # if key exists in the provided hash, add it to current entity's model
206
- if hash.has_key? key
207
- entity = build_entity(entity_class, hash[key])
208
- set_state_param(key, entity)
209
- end
210
- end
211
-
212
- def unserialize_array_of_entities(key, entity_class, hash)
213
- # ensure that the provided entity class derives from BillingEntity
214
- TypeCheck.verifyClass(BillingEntity, entity_class, 'entity_class')
215
- TypeCheck.verifyObj(Hash, hash, 'hash')
216
-
217
- # register the array of entities as one that requires bespoke serialization
218
- @_registered_entity_arrays[key] = entity_class
219
- # if key exists in the provided hash, add it to current entity's model
220
- if hash.has_key? key
221
- entities = build_entity_array(entity_class, hash[key])
222
- set_state_param(key, entities)
223
- end
224
- end
225
-
226
- def build_entity_array(entity_class, entity_hashes)
227
- TypeCheck.verifyObj(Array, entity_hashes, 'entity_hashes')
228
-
229
- entity_array = Array.new
230
- # maybe it's an empty array, but that's okay too.
231
- entity_hashes.each do |value|
232
- new_entity = build_entity(entity_class, value)
233
- entity_array.push(new_entity)
234
- end
235
- entity_array
236
- end
237
-
238
- def build_entity(entity_class, entity)
239
- if entity.is_a? Hash
240
- # either we are given a serialized entity
241
- # we must unserialize it
242
-
243
- # this entity should the same client as we do
244
- client = @_client
245
-
246
- new_entity = entity_class.new(entity, client)
247
- elsif entity.is_a? entity_class
248
- # or we are given an already-constructed entity
249
- # just return it as-is
250
-
251
- # for consistency we might want to set this entity to use the same client as us. Let's not for now.
252
- new_entity = entity
253
- else
254
- expectedClassName = entity_class.name
255
- actualClassName = entity.class.name
256
- raise TypeError.new("Expected instance of either: 'Hash' or '#{expectedClassName}' at argument 'entity'. "+
257
- "Instead received: '#{actualClassName}'")
258
- end
259
-
260
- new_entity
261
- end
262
- end
1
+ module BillForward
2
+ class BillingEntity
3
+ # legacy Ruby gives us this 'id' chuff. we kinda need it back.
4
+ undef id if defined? id
5
+ attr_accessor :_client
6
+
7
+ def initialize(state_params = nil, client = nil)
8
+ raise AbstractInstantiateError.new('This abstract class cannot be instantiated!') if self.class == MutableEntity
9
+
10
+ client = self.class.singleton_client if client.nil?
11
+ state_params = {} if state_params.nil?
12
+
13
+ TypeCheck.verifyObj(Client, client, 'client')
14
+ TypeCheck.verifyObj(Hash, state_params, 'state_params')
15
+
16
+ @_registered_entities = Hash.new
17
+ @_registered_entity_arrays = Hash.new
18
+
19
+ @_client = client
20
+ # initiate with empty state params
21
+ # use indifferent hash so 'id' and :id are the same
22
+ @_state_params = HashWithIndifferentAccess.new
23
+ # legacy Ruby gives us this 'id' chuff. we kinda need it back.
24
+ @_state_params.instance_eval { undef id if defined? id }
25
+ # populate state params now
26
+ unserialize_all state_params
27
+ end
28
+
29
+ @@payload_verbs = ['post', 'put']
30
+ @@no_payload_verbs = ['get', 'delete']
31
+ @@all_verbs = @@payload_verbs + @@no_payload_verbs
32
+
33
+ class << self
34
+ attr_accessor :resource_path
35
+
36
+ def singleton_client
37
+ Client.default_client
38
+ end
39
+
40
+ def build_entity_array(entity_hashes)
41
+ TypeCheck.verifyObj(Array, entity_hashes, 'entity_hashes')
42
+
43
+ entity_hashes.map do |hash|
44
+ self.build_entity(hash)
45
+ end
46
+ end
47
+
48
+ def build_entity(entity)
49
+ if entity.is_a? Hash
50
+ # either we are given a serialized entity
51
+ # we must unserialize it
52
+
53
+ # this entity should the same client as we do
54
+ client = @_client
55
+
56
+ return self.new(entity, client)
57
+ end
58
+ if entity.is_a? self
59
+ # or we are given an already-constructed entity
60
+ # just return it as-is
61
+
62
+ # for consistency we might want to set this entity to use the same client as us. Let's not for now.
63
+ return entity
64
+ end
65
+ if
66
+ expectedClassName = self.name
67
+ actualClassName = entity.class.name
68
+ raise TypeError.new("Expected instance of either: 'Hash' or '#{expectedClassName}' at argument 'entity'. "+
69
+ "Instead received: '#{actualClassName}'")
70
+ end
71
+
72
+ new_entity
73
+ end
74
+
75
+ def request_ambiguous(*args)
76
+ plurality = args.shift
77
+ verb = args.shift
78
+ endpoint = args.shift
79
+
80
+ payload = nil;
81
+ haspayload = @@payload_verbs.include?(verb)
82
+ if (haspayload)
83
+ payload_typed = args.shift
84
+ payload = payload_typed.serialize
85
+ end
86
+
87
+ query_params = args.shift
88
+ custom_client = args.shift
89
+
90
+ client = client.nil? \
91
+ ? singleton_client \
92
+ : custom_client
93
+
94
+ route = resource_path.path
95
+ url_full = "#{route}/#{endpoint}"
96
+ method = "#{verb}_#{plurality}"
97
+
98
+ arguments = [url_full, query_params]
99
+ arguments.insert(1, payload) if haspayload
100
+
101
+ # puts verb
102
+
103
+ client.send(method.intern, *arguments)
104
+ end
105
+
106
+ def request_many_heterotyped(*args)
107
+ response_type = args.shift
108
+ arguments = ['many']+args
109
+ results = self.send(:request_ambiguous, *arguments)
110
+ response_type.build_entity_array(results)
111
+ end
112
+
113
+ def request_first_heterotyped(*args)
114
+ response_type = args.shift
115
+ arguments = ['first']+args
116
+ result = self.send(:request_ambiguous, *arguments)
117
+ response_type.build_entity(result)
118
+ end
119
+
120
+ ['first', 'many'].each do |method|
121
+ define_method("request_#{method}".intern) do |*args|
122
+ arguments = [self]+args
123
+ self.send("request_#{method}_heterotyped".intern, *arguments)
124
+ end
125
+ end
126
+
127
+ def get_by_id(id, query_params = {}, custom_client = nil)
128
+ raise ArgumentError.new("id cannot be nil") if id.nil?
129
+
130
+ endpoint = sprintf('%s',
131
+ ERB::Util.url_encode(id)
132
+ )
133
+
134
+ self.request_first('get', endpoint, query_params, custom_client)
135
+ end
136
+
137
+ def get_all(query_params = {}, custom_client = nil)
138
+ self.request_many('get', '', query_params, custom_client)
139
+ end
140
+ end
141
+
142
+ def method_missing(method_id, *arguments, &block)
143
+ # no call to super; our criteria is all keys.
144
+ #setter
145
+ if /^(\w+)=$/ =~ method_id.to_s
146
+ return set_state_param($1, arguments.first)
147
+ end
148
+ #getter
149
+ get_state_param(method_id.to_s)
150
+ end
151
+
152
+ def [](key)
153
+ method_missing(key)
154
+ end
155
+
156
+ def []=(key, value)
157
+ set_key = key.to_s+'='
158
+ method_missing(set_key, value)
159
+ end
160
+
161
+ def to_ordered_hash
162
+ ordered_hash = hash_with_type_at_top(@_state_params)
163
+ ordered_hash
164
+ end
165
+
166
+ def to_json(*a)
167
+ ordered_hash = to_ordered_hash
168
+ ordered_hash.to_json
169
+ # @_state_params.to_json
170
+ end
171
+
172
+ def to_unordered_hash
173
+ json_string = to_json
174
+ JSON.parse(json_string)
175
+ end
176
+
177
+ def to_s
178
+ parsed = to_unordered_hash
179
+ JSON.pretty_generate(parsed)
180
+ end
181
+
182
+ def serialize
183
+ to_json
184
+ end
185
+
186
+ protected
187
+ def hash_with_type_at_top(hash)
188
+ new_hash = OrderedHashWithDotAccess.new
189
+
190
+ # API presently requires '@type' (if present) to be first key in JSON
191
+ if hash.has_key? '@type'
192
+ # insert existing @type as first element in ordered hash
193
+ new_hash['@type'] = hash.with_indifferent_access['@type']
194
+ end
195
+
196
+ # add key-value pairs excepting '@type' back in
197
+ # no, we don't care about the order of these.
198
+ hash.with_indifferent_access.reject {|key, value| key == '@type'}.each do |key, value|
199
+ new_hash[key] = value
200
+ end
201
+
202
+ return new_hash
203
+ end
204
+
205
+ def set_state_param(key, value)
206
+ @_state_params[key] = value
207
+ get_state_param(key)
208
+ end
209
+
210
+ def get_state_param(key)
211
+ @_state_params[key]
212
+ end
213
+
214
+ def unserialize_all(hash)
215
+ TypeCheck.verifyObj(Hash, hash, 'hash')
216
+
217
+ hash.each do |key, value|
218
+ unserialized = unserialize_one value
219
+ set_state_param(key, unserialized)
220
+ end
221
+ end
222
+
223
+ def unserialize_hash(hash)
224
+ TypeCheck.verifyObj(Hash, hash, 'hash')
225
+
226
+ # API presently requires '@type' (if present) to be first key in JSON
227
+ hash = hash_with_type_at_top(hash)
228
+
229
+ hash.each do |key, value|
230
+ # recurse down, so that all nested hashes get same treatment
231
+ unserialized = unserialize_one value
232
+
233
+ # replace with unserialized version
234
+ hash[key] = unserialized
235
+ end
236
+
237
+ hash
238
+ end
239
+
240
+ def unserialize_array(array)
241
+ TypeCheck.verifyObj(Array, array, 'array')
242
+
243
+ array.each_with_index do |value, index|
244
+ # recurse down, so that all nested hashes get same treatment
245
+ unserialized = unserialize_one value
246
+
247
+ # replace with unserialized version
248
+ array[index] = unserialized
249
+ end
250
+
251
+ array
252
+ end
253
+
254
+ def unserialize_one(value)
255
+ if value.is_a? Hash
256
+ return unserialize_hash(value)
257
+ end
258
+ if value.is_a? Array
259
+ return unserialize_array(value)
260
+ end
261
+ value
262
+ end
263
+
264
+ def unserialize_entity(key, entity_class, hash)
265
+ # ensure that the provided entity class derives from BillingEntity
266
+ TypeCheck.verifyClass(BillingEntity, entity_class, 'entity_class')
267
+ TypeCheck.verifyObj(Hash, hash, 'hash')
268
+
269
+ # register the entity as one that requires bespoke serialization
270
+ @_registered_entities[key] = entity_class
271
+ # if key exists in the provided hash, add it to current entity's model
272
+ if hash.has_key? key
273
+ val = hash[key]
274
+ if (val.nil?)
275
+ return
276
+ end
277
+ entity = entity_class.build_entity(val)
278
+ set_state_param(key, entity)
279
+ end
280
+ end
281
+
282
+ def unserialize_array_of_entities(key, entity_class, hash)
283
+ # ensure that the provided entity class derives from BillingEntity
284
+ TypeCheck.verifyClass(BillingEntity, entity_class, 'entity_class')
285
+ TypeCheck.verifyObj(Hash, hash, 'hash')
286
+
287
+ # register the array of entities as one that requires bespoke serialization
288
+ @_registered_entity_arrays[key] = entity_class
289
+ # if key exists in the provided hash, add it to current entity's model
290
+ if hash.has_key? key
291
+ entities = entity_class.build_entity_array(hash[key])
292
+ set_state_param(key, entities)
293
+ end
294
+ end
295
+ end
263
296
  end