pactas_itero 0.2.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.hound.yml +14 -0
  4. data/.rubocop.yml +2 -0
  5. data/.ruby-style.yml +268 -0
  6. data/.travis.yml +13 -0
  7. data/CHANGELOG.md +46 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +33 -0
  11. data/Rakefile +16 -0
  12. data/lib/pactas_itero/api/contracts.rb +30 -0
  13. data/lib/pactas_itero/api/customers.rb +20 -0
  14. data/lib/pactas_itero/api/invoices.rb +25 -0
  15. data/lib/pactas_itero/api/oauth.rb +12 -0
  16. data/lib/pactas_itero/api/orders.rb +19 -0
  17. data/lib/pactas_itero/api/rated_items.rb +16 -0
  18. data/lib/pactas_itero/api.rb +18 -0
  19. data/lib/pactas_itero/client.rb +108 -0
  20. data/lib/pactas_itero/configurable.rb +59 -0
  21. data/lib/pactas_itero/default.rb +77 -0
  22. data/lib/pactas_itero/error.rb +217 -0
  23. data/lib/pactas_itero/ext/hash/camelize_keys.rb +32 -0
  24. data/lib/pactas_itero/response/raise_error.rb +21 -0
  25. data/lib/pactas_itero/version.rb +3 -0
  26. data/lib/pactas_itero.rb +31 -0
  27. data/pactas_itero.gemspec +32 -0
  28. data/spec/fixtures/bearer_token.json +5 -0
  29. data/spec/fixtures/commit_order_response.json +50 -0
  30. data/spec/fixtures/contract.json +46 -0
  31. data/spec/fixtures/contract_cancellation_preview_response.json +73 -0
  32. data/spec/fixtures/contracts.json +48 -0
  33. data/spec/fixtures/create_order_response.json +17 -0
  34. data/spec/fixtures/create_rated_item.json +8 -0
  35. data/spec/fixtures/customer.json +23 -0
  36. data/spec/fixtures/customers.json +40 -0
  37. data/spec/fixtures/invoice.json +22 -0
  38. data/spec/fixtures/invoices.json +46 -0
  39. data/spec/fixtures/order.json +15 -0
  40. data/spec/pactas_itero/api/contracts_spec.rb +118 -0
  41. data/spec/pactas_itero/api/customers_spec.rb +188 -0
  42. data/spec/pactas_itero/api/invoices_spec.rb +87 -0
  43. data/spec/pactas_itero/api/oauth_spec.rb +40 -0
  44. data/spec/pactas_itero/api/orders_spec.rb +102 -0
  45. data/spec/pactas_itero/api/rated_items_spec.rb +74 -0
  46. data/spec/pactas_itero/client_spec.rb +316 -0
  47. data/spec/pactas_itero_spec.rb +32 -0
  48. data/spec/spec_helper.rb +100 -0
  49. metadata +218 -0
@@ -0,0 +1,108 @@
1
+ require 'base64'
2
+ require 'rash'
3
+ require 'pactas_itero/configurable'
4
+ require 'pactas_itero/api'
5
+
6
+ module PactasItero
7
+ class Client
8
+ include PactasItero::Configurable
9
+ include PactasItero::Api
10
+
11
+ attr_accessor :bearer_token
12
+
13
+ def initialize(options = {})
14
+ PactasItero::Configurable.keys.each do |key|
15
+ instance_variable_set(:"@#{key}", options[key] || PactasItero.instance_variable_get(:"@#{key}"))
16
+ end
17
+ end
18
+
19
+ def get(url, options = {})
20
+ request :get, url, options
21
+ end
22
+
23
+ def post(url, options = {})
24
+ request :post, url, options
25
+ end
26
+
27
+ def put(url, options = {})
28
+ request :put, url, options
29
+ end
30
+
31
+ def patch(url, options = {})
32
+ request :patch, url, options
33
+ end
34
+
35
+ def delete(url, options = {})
36
+ request :delete, url, options
37
+ end
38
+
39
+ def head(url, options = {})
40
+ request :head, url, options
41
+ end
42
+
43
+ def bearer_token?
44
+ !!bearer_token
45
+ end
46
+
47
+ private
48
+
49
+ def connection
50
+ @connection ||= Faraday.new(api_endpoint, connection_options)
51
+ end
52
+
53
+ def request(method, path, params = {})
54
+ headers = params.delete(:headers) || {}
55
+ if accept = params.delete(:accept)
56
+ headers[:accept] = accept
57
+ end
58
+
59
+ bearer_token_request = params.delete(:bearer_token_request)
60
+
61
+ if bearer_token_request
62
+ headers[:accept] = '*/*'
63
+ headers[:authorization] = bearer_token_credentials_auth_header
64
+ headers[:content_type] = 'application/x-www-form-urlencoded; charset=UTF-8'
65
+ else
66
+ headers[:authorization] = auth_header
67
+ end
68
+
69
+ response = connection.send(method.to_sym, path, params) {
70
+ |request| request.headers.update(headers)
71
+ }.env
72
+ response.body
73
+ end
74
+
75
+ def connection_options
76
+ @connection_options ||= {
77
+ :builder => middleware,
78
+ :headers => {
79
+ :accept => default_media_type,
80
+ :user_agent => user_agent,
81
+ },
82
+ :request => {
83
+ :open_timeout => 10,
84
+ :timeout => 30,
85
+ },
86
+ }
87
+ end
88
+
89
+ def bearer_token_credentials_auth_header
90
+ basic_auth_token = Base64.strict_encode64("#{@client_id}:#{@client_secret}")
91
+ "Basic #{basic_auth_token}"
92
+ end
93
+
94
+ def bearer_auth_header
95
+ token = if bearer_token.respond_to?(:access_token)
96
+ bearer_token.access_token
97
+ else
98
+ bearer_token
99
+ end
100
+ "Bearer #{token}"
101
+ end
102
+
103
+ def auth_header
104
+ @bearer_token = token unless bearer_token?
105
+ bearer_auth_header
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,59 @@
1
+ module PactasItero
2
+
3
+ module Configurable
4
+ attr_accessor :bearer_token, :client_id, :client_secret, :user_agent,
5
+ :default_media_type, :middleware, :production
6
+ attr_writer :api_endpoint
7
+ class << self
8
+
9
+ # List of configurable keys for PactasItero::Client
10
+ def keys
11
+ @keys ||= [
12
+ :bearer_token,
13
+ :api_endpoint,
14
+ :client_id,
15
+ :client_secret,
16
+ :user_agent,
17
+ :default_media_type,
18
+ :middleware,
19
+ :production
20
+ ]
21
+ end
22
+ end
23
+
24
+ # Set configuration options using a block
25
+ def configure
26
+ yield self
27
+ end
28
+
29
+ # Reset configuration options to default values
30
+ def reset!
31
+ PactasItero::Configurable.keys.each do |key|
32
+ send(:"#{key}=", PactasItero::Default.options[key])
33
+ end
34
+ self
35
+ end
36
+ alias setup reset!
37
+
38
+ def api_endpoint
39
+ endpoint = @api_endpoint ||
40
+ production && production_api_endpoint ||
41
+ sandbox_api_endpoint
42
+ File.join(endpoint, "")
43
+ end
44
+
45
+ def sandbox_api_endpoint
46
+ PactasItero::Default.sandbox_api_endpoint
47
+ end
48
+
49
+ def production_api_endpoint
50
+ PactasItero::Default.production_api_endpoint
51
+ end
52
+
53
+ private
54
+
55
+ def options
56
+ Hash[PactasItero::Configurable.keys.map{|key| [key, send(:"#{key}")]}]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,77 @@
1
+ require 'pactas_itero/response/raise_error'
2
+ require 'pactas_itero/version'
3
+ require 'faraday_middleware'
4
+
5
+ module PactasItero
6
+
7
+ # Default configuration options for {Client}
8
+ module Default
9
+
10
+ SANDBOX_API_ENDPOINT = "https://sandbox.billwerk.com".freeze
11
+
12
+ PRODUCTION_API_ENDPOINT = "https://itero.pactas.com".freeze
13
+
14
+ PRODUCTION = false
15
+
16
+ USER_AGENT = "Pactas.Itero Ruby Gem #{PactasItero::VERSION}".freeze
17
+
18
+ MEDIA_TYPE = "application/json"
19
+
20
+ MIDDLEWARE = Faraday::RackBuilder.new do |builder|
21
+ builder.request :json
22
+ builder.use PactasItero::Response::RaiseError
23
+ builder.response :rashify
24
+ builder.request :url_encoded
25
+ builder.response :json, :content_type => /\bjson$/
26
+
27
+ builder.adapter Faraday.default_adapter
28
+ end
29
+
30
+ class << self
31
+
32
+ def options
33
+ Hash[PactasItero::Configurable.keys.map{|key| [key, send(key)]}]
34
+ end
35
+
36
+ def api_endpoint
37
+ ENV['PACTAS_ITERO_ENDPOINT']
38
+ end
39
+
40
+ def sandbox_api_endpoint
41
+ SANDBOX_API_ENDPOINT
42
+ end
43
+
44
+ def production_api_endpoint
45
+ PRODUCTION_API_ENDPOINT
46
+ end
47
+
48
+ def production
49
+ PRODUCTION
50
+ end
51
+
52
+ def client_id
53
+ ENV['PACTAS_ITERO_CLIENT_ID']
54
+ end
55
+
56
+ def client_secret
57
+ ENV['PACTAS_ITERO_CLIENT_SECRET']
58
+ end
59
+
60
+ def bearer_token
61
+ ENV['PACTAS_ITERO_BEARER_TOKEN']
62
+ end
63
+
64
+ def default_media_type
65
+ ENV['PACTAS_ITERO_CLIENT_DEFAULT_MEDIA_TYPE'] || MEDIA_TYPE
66
+ end
67
+
68
+ def middleware
69
+ MIDDLEWARE
70
+ end
71
+
72
+ def user_agent
73
+ ENV['PACTAS_ITERO_USER_AGENT'] || USER_AGENT
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,217 @@
1
+ module PactasItero
2
+ # Custom error class for rescuing from all Pactas errors
3
+ class Error < StandardError
4
+
5
+ # Returns the appropriate PactasItero::Error sublcass based
6
+ # on status and response message
7
+ #
8
+ # @param [Hash] response HTTP response
9
+ # @return [PactasItero::Error]
10
+ def self.from_response(response)
11
+ status = response[:status].to_i
12
+ body = response[:body].to_s
13
+ headers = response[:response_headers]
14
+
15
+ if klass = case status
16
+ when 400 then PactasItero::BadRequest
17
+ when 401 then error_for_401(headers)
18
+ when 403 then error_for_403(body)
19
+ when 404 then PactasItero::NotFound
20
+ when 406 then PactasItero::NotAcceptable
21
+ when 409 then PactasItero::Conflict
22
+ when 415 then PactasItero::UnsupportedMediaType
23
+ when 422 then PactasItero::UnprocessableEntity
24
+ when 400..499 then PactasItero::ClientError
25
+ when 500 then PactasItero::InternalServerError
26
+ when 501 then PactasItero::NotImplemented
27
+ when 502 then PactasItero::BadGateway
28
+ when 503 then PactasItero::ServiceUnavailable
29
+ when 500..599 then PactasItero::ServerError
30
+ end
31
+ klass.new(response)
32
+ end
33
+ end
34
+
35
+ def initialize(response=nil)
36
+ @response = response
37
+ super(build_error_message)
38
+ end
39
+
40
+ # Returns most appropriate error for 401 HTTP status code
41
+ # @private
42
+ def self.error_for_401(headers)
43
+ if PactasItero::OneTimePasswordRequired.required_header(headers)
44
+ PactasItero::OneTimePasswordRequired
45
+ else
46
+ PactasItero::Unauthorized
47
+ end
48
+ end
49
+
50
+ # Returns most appropriate error for 403 HTTP status code
51
+ # @private
52
+ def self.error_for_403(body)
53
+ if body =~ /rate limit exceeded/i
54
+ PactasItero::TooManyRequests
55
+ elsif body =~ /login attempts exceeded/i
56
+ PactasItero::TooManyLoginAttempts
57
+ else
58
+ PactasItero::Forbidden
59
+ end
60
+ end
61
+
62
+ # Array of validation errors
63
+ # @return [Array<Hash>] Error info
64
+ def errors
65
+ if data && data.is_a?(Hash)
66
+ data[:errors] || []
67
+ else
68
+ []
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def data
75
+ @data ||=
76
+ if (body = @response[:body]) && !body.empty?
77
+ if body.is_a?(String) &&
78
+ @response[:response_headers] &&
79
+ @response[:response_headers][:content_type] =~ /json/
80
+
81
+ Sawyer::Agent.serializer.decode(body)
82
+ else
83
+ body
84
+ end
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ def response_message
91
+ case data
92
+ when Hash
93
+ data[:Message]
94
+ when String
95
+ data
96
+ end
97
+ end
98
+
99
+ def response_error
100
+ "Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
101
+ end
102
+
103
+ def response_error_summary
104
+ return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?
105
+
106
+ summary = "\nError summary:\n"
107
+ summary << data[:errors].map do |hash|
108
+ hash.map { |k,v| " #{k}: #{v}" }
109
+ end.join("\n")
110
+
111
+ summary
112
+ end
113
+
114
+ def build_error_message
115
+ return nil if @response.nil?
116
+
117
+ message = "#{@response[:method].to_s.upcase} "
118
+ message << redact_url(@response[:url].to_s) + ": "
119
+ message << "#{@response[:status]} - "
120
+ message << "#{response_message}" unless response_message.nil?
121
+ message << "#{response_error}" unless response_error.nil?
122
+ message << "#{response_error_summary}" unless response_error_summary.nil?
123
+ message
124
+ end
125
+
126
+ def redact_url(url_string)
127
+ %w[client_secret access_token].each do |token|
128
+ url_string.gsub!(/#{token}=\S+/, "#{token}=(redacted)") if url_string.include? token
129
+ end
130
+ url_string
131
+ end
132
+ end
133
+
134
+ # Raised on errors in the 400-499 range
135
+ class ClientError < Error; end
136
+
137
+ # Raised when Pactas returns a 400 HTTP status code
138
+ class BadRequest < ClientError; end
139
+
140
+ # Raised when Pactas returns a 401 HTTP status code
141
+ class Unauthorized < ClientError; end
142
+
143
+ # Raised when Pactas returns a 401 HTTP status code
144
+ # and headers include "X-Pactas-OTP"
145
+ class OneTimePasswordRequired < ClientError
146
+ #@private
147
+ OTP_DELIVERY_PATTERN = /required; (\w+)/i
148
+
149
+ #@private
150
+ def self.required_header(headers)
151
+ OTP_DELIVERY_PATTERN.match headers['X-Pactas-OTP'].to_s
152
+ end
153
+
154
+ # Delivery method for the user's OTP
155
+ #
156
+ # @return [String]
157
+ def password_delivery
158
+ @password_delivery ||= delivery_method_from_header
159
+ end
160
+
161
+ private
162
+
163
+ def delivery_method_from_header
164
+ if match = self.class.required_header(@response[:response_headers])
165
+ match[1]
166
+ end
167
+ end
168
+ end
169
+
170
+ # Raised when Pactas returns a 403 HTTP status code
171
+ class Forbidden < ClientError; end
172
+
173
+ # Raised when Pactas returns a 403 HTTP status code
174
+ # and body matches 'rate limit exceeded'
175
+ class TooManyRequests < Forbidden; end
176
+
177
+ # Raised when Pactas returns a 403 HTTP status code
178
+ # and body matches 'login attempts exceeded'
179
+ class TooManyLoginAttempts < Forbidden; end
180
+
181
+ # Raised when Pactas returns a 404 HTTP status code
182
+ class NotFound < ClientError; end
183
+
184
+ # Raised when Pactas returns a 406 HTTP status code
185
+ class NotAcceptable < ClientError; end
186
+
187
+ # Raised when Pactas returns a 409 HTTP status code
188
+ class Conflict < ClientError; end
189
+
190
+ # Raised when Pactas returns a 414 HTTP status code
191
+ class UnsupportedMediaType < ClientError; end
192
+
193
+ # Raised when Pactas returns a 422 HTTP status code
194
+ class UnprocessableEntity < ClientError; end
195
+
196
+ # Raised on errors in the 500-599 range
197
+ class ServerError < Error; end
198
+
199
+ # Raised when Pactas returns a 500 HTTP status code
200
+ class InternalServerError < ServerError; end
201
+
202
+ # Raised when Pactas returns a 501 HTTP status code
203
+ class NotImplemented < ServerError; end
204
+
205
+ # Raised when Pactas returns a 502 HTTP status code
206
+ class BadGateway < ServerError; end
207
+
208
+ # Raised when Pactas returns a 503 HTTP status code
209
+ class ServiceUnavailable < ServerError; end
210
+
211
+ # Raised when client fails to provide valid Content-Type
212
+ class MissingContentType < ArgumentError; end
213
+
214
+ # Raised when a method requires an application client_id
215
+ # and secret but none is provided
216
+ class ApplicationCredentialsRequired < StandardError; end
217
+ end
@@ -0,0 +1,32 @@
1
+ class Hash
2
+
3
+ def camelize_keys(value = self)
4
+ case value
5
+ when Array
6
+ value.map { |v| camelize_keys(v) }
7
+ when Hash
8
+ Hash[value.map { |k, v| [camelize_key(k), camelize_keys(v)] }]
9
+ else
10
+ value
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def camelize_key(key)
17
+ if key.is_a? Symbol
18
+ camelize(key.to_s).to_sym
19
+ elsif key.is_a? String
20
+ camelize(key)
21
+ else
22
+ key
23
+ end
24
+ end
25
+
26
+ # copied from ActiveSupport
27
+ def camelize(term)
28
+ string = term.to_s
29
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
30
+ string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ require 'faraday'
2
+ require 'pactas_itero/error'
3
+
4
+ module PactasItero
5
+ # Faraday response middleware
6
+ module Response
7
+
8
+ # This class raises an PactasItero-flavored exception based
9
+ # HTTP status codes returned by the API
10
+ class RaiseError < Faraday::Response::Middleware
11
+
12
+ private
13
+
14
+ def on_complete(response)
15
+ if error = PactasItero::Error.from_response(response)
16
+ raise error
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module PactasItero
2
+ VERSION = "0.2.0".freeze
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'pactas_itero/client'
2
+ require 'pactas_itero/default'
3
+
4
+ module PactasItero
5
+
6
+ class << self
7
+ include PactasItero::Configurable
8
+
9
+ # API client based on configured options {Configurable}
10
+ #
11
+ # @return [PactasItero::Client] API wrapper
12
+ def client
13
+ @client = PactasItero::Client.new(options)
14
+ @client
15
+ end
16
+
17
+ # @private
18
+ def respond_to_missing?(method_name, include_private=false); client.respond_to?(method_name, include_private); end if RUBY_VERSION >= "1.9"
19
+ # @private
20
+ def respond_to?(method_name, include_private=false); client.respond_to?(method_name, include_private) || super; end if RUBY_VERSION < "1.9"
21
+
22
+ private
23
+
24
+ def method_missing(method_name, *args, &block)
25
+ return super unless client.respond_to?(method_name)
26
+ client.send(method_name, *args, &block)
27
+ end
28
+ end
29
+ end
30
+
31
+ PactasItero.setup
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pactas_itero/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'pactas_itero'
8
+ spec.version = PactasItero::VERSION
9
+ spec.authors = ['Simon Fröhler']
10
+ spec.email = "simon@shipcloud.io"
11
+ spec.summary = %q{pactas_itero provides a client mapping for accessing
12
+ the Pactas Itero API.}
13
+ spec.description = %q{pactas_itero provides a client mapping for accessing
14
+ the Pactas Itero API, making it easy to post your data to, adn read your
15
+ data from your Pactas account.}
16
+ spec.homepage = 'https://github.com/webionate/pactas_itero'
17
+ spec.license = 'MIT'
18
+
19
+ spec.files = `git ls-files`.split("\n")
20
+ spec.test_files = `git ls-files -- spec/*`.split("\n")
21
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency('faraday_middleware', '~> 0.9.1')
25
+ spec.add_dependency('rash', '~> 0.4.0')
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.7'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency "rspec", '~> 3.0'
30
+ spec.add_development_dependency("simplecov", "~> 0")
31
+ spec.add_development_dependency("webmock", "~> 1.18", ">= 1.18.0")
32
+ end
@@ -0,0 +1,5 @@
1
+ {
2
+ "access_token": "top_secret_access_token",
3
+ "expires": 0,
4
+ "token_type": "bearer"
5
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "Id": "5370e5ab9e40071fd01e01e0",
3
+ "LastBillingDate": "2014-05-22T13:33:30.9510000Z",
4
+ "NextBillingDate": "2014-06-22T13:33:30.9510000Z",
5
+ "PlanId": "525bf8eb9e40073a58590fd4",
6
+ "CustomerId": "5370e5ab9e40071fd01e01df",
7
+ "LifecycleStatus": "Active",
8
+ "CustomerName": "asd asd",
9
+ "Phases": [{
10
+ "Type": "Normal",
11
+ "StartDate": "2014-05-12T15:15:55.8410000Z",
12
+ "PlanVariantId": "5256be5c9e4007398ce610f0",
13
+ "PlanId": "5256be2a9e4007398ce610ef"
14
+ }, {
15
+ "Type": "Inactive",
16
+ "StartDate": "2014-05-13T22:00:00.0000000Z"
17
+ }, {
18
+ "Type": "Normal",
19
+ "StartDate": "2014-05-22T13:22:41.4510000Z",
20
+ "PlanVariantId": "5256be5c9e4007398ce610f0",
21
+ "PlanId": "5256be2a9e4007398ce610ef"
22
+ }, {
23
+ "Type": "Normal",
24
+ "StartDate": "2014-05-22T13:33:30.9510000Z",
25
+ "PlanVariantId": "525bf9089e40073a58590fd5",
26
+ "PlanId": "525bf8eb9e40073a58590fd4"
27
+ }],
28
+ "Balance": 0,
29
+ "Currency": "EUR",
30
+ "PlanGroupId": "5256be1b9e4007398ce610ee",
31
+ "PaymentBearer": {
32
+ "Code": "41212312",
33
+ "Holder": "asdasd asdad",
34
+ "Country": "de",
35
+ "Account": "*****3123",
36
+ "Type": "BankAccount"
37
+ },
38
+ "PaymentProvider": "Paymill",
39
+ "EscalationSuspended": false,
40
+ "RecurringPaymentsPaused": false,
41
+ "CurrentPhase": {
42
+ "Type": "Normal",
43
+ "StartDate": "2014-05-22T13:33:30.9510000Z",
44
+ "PlanVariantId": "525bf9089e40073a58590fd5",
45
+ "PlanId": "525bf8eb9e40073a58590fd4"
46
+ },
47
+ "StartDate": "2014-05-12T15:15:55.8410000Z",
48
+ "BilledUntil": "2014-06-22T13:33:30.9510000Z",
49
+ "PlanVariantId": "525bf9089e40073a58590fd5"
50
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "Balance": 0,
3
+ "BilledUntil": "2014-05-23T13:12:47.0760000Z",
4
+ "Currency": "EUR",
5
+ "CurrentPhase": {
6
+ "PlanId": "53567a9e1d8dd00df056564d",
7
+ "PlanVariantId": "53567b431d8dd00df056564f",
8
+ "StartDate": "2014-04-23T13:12:47.0760000Z",
9
+ "Type": "Normal"
10
+ },
11
+ "CustomerId": "5357bc4f1d8dd00fa0db6c31",
12
+ "CustomerName": "Max Mustermann",
13
+ "EndDate": "2014-05-23T13:12:47.0760000Z",
14
+ "EscalationSuspended": false,
15
+ "Id": "5357bc4f1d8dd00fa0db6c32",
16
+ "LastBillingDate": "2014-04-23T13:12:47.0760000Z",
17
+ "LifecycleStatus": "Active",
18
+ "NextBillingDate": "2014-05-23T13:12:47.0760000Z",
19
+ "PaymentBearer": {
20
+ "CardType": "visa",
21
+ "Country": "DE",
22
+ "ExpiryMonth": 12,
23
+ "ExpiryYear": 2017,
24
+ "Holder": "Marcellus Wallace",
25
+ "Last4": "1111",
26
+ "Type": "CreditCard"
27
+ },
28
+ "PaymentProvider": "Paymill",
29
+ "Phases": [
30
+ {
31
+ "PlanId": "53567a9e1d8dd00df056564d",
32
+ "PlanVariantId": "53567b431d8dd00df056564f",
33
+ "StartDate": "2014-04-23T13:12:47.0760000Z",
34
+ "Type": "Normal"
35
+ },
36
+ {
37
+ "StartDate": "2014-05-23T13:12:47.0760000Z",
38
+ "Type": "Inactive"
39
+ }
40
+ ],
41
+ "PlanGroupId": "53567a731d8dd00df056564a",
42
+ "PlanId": "53567a9e1d8dd00df056564d",
43
+ "PlanVariantId": "53567b431d8dd00df056564f",
44
+ "RecurringPaymentsPaused": false,
45
+ "StartDate": "2014-04-23T13:12:47.0760000Z"
46
+ }