pactas_itero 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }