gocardless_pro 1.1.0 → 2.0.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -4
  3. data/lib/gocardless_pro.rb +1 -0
  4. data/lib/gocardless_pro/api_service.rb +2 -0
  5. data/lib/gocardless_pro/client.rb +4 -3
  6. data/lib/gocardless_pro/error/invalid_state_error.rb +17 -0
  7. data/lib/gocardless_pro/middlewares/raise_gocardless_errors.rb +50 -0
  8. data/lib/gocardless_pro/request.rb +38 -1
  9. data/lib/gocardless_pro/resources/creditor.rb +2 -2
  10. data/lib/gocardless_pro/resources/creditor_bank_account.rb +11 -11
  11. data/lib/gocardless_pro/resources/customer_bank_account.rb +2 -2
  12. data/lib/gocardless_pro/resources/event.rb +2 -2
  13. data/lib/gocardless_pro/resources/mandate.rb +2 -2
  14. data/lib/gocardless_pro/resources/payment.rb +7 -7
  15. data/lib/gocardless_pro/resources/payout.rb +7 -6
  16. data/lib/gocardless_pro/resources/redirect_flow.rb +2 -2
  17. data/lib/gocardless_pro/resources/refund.rb +2 -2
  18. data/lib/gocardless_pro/resources/subscription.rb +2 -2
  19. data/lib/gocardless_pro/response.rb +2 -54
  20. data/lib/gocardless_pro/services/bank_details_lookups_service.rb +3 -0
  21. data/lib/gocardless_pro/services/creditor_bank_accounts_service.rb +31 -2
  22. data/lib/gocardless_pro/services/creditors_service.rb +21 -1
  23. data/lib/gocardless_pro/services/customer_bank_accounts_service.rb +34 -2
  24. data/lib/gocardless_pro/services/customers_service.rb +21 -1
  25. data/lib/gocardless_pro/services/events_service.rb +5 -0
  26. data/lib/gocardless_pro/services/mandate_pdfs_service.rb +3 -0
  27. data/lib/gocardless_pro/services/mandates_service.rb +47 -3
  28. data/lib/gocardless_pro/services/payments_service.rb +47 -3
  29. data/lib/gocardless_pro/services/payouts_service.rb +5 -0
  30. data/lib/gocardless_pro/services/redirect_flows_service.rb +28 -2
  31. data/lib/gocardless_pro/services/refunds_service.rb +21 -1
  32. data/lib/gocardless_pro/services/subscriptions_service.rb +34 -2
  33. data/lib/gocardless_pro/version.rb +1 -1
  34. data/spec/api_service_spec.rb +106 -0
  35. data/spec/middlewares/raise_gocardless_errors_spec.rb +98 -0
  36. data/spec/resources/bank_details_lookup_spec.rb +102 -19
  37. data/spec/resources/creditor_bank_account_spec.rb +416 -40
  38. data/spec/resources/creditor_spec.rb +414 -53
  39. data/spec/resources/customer_bank_account_spec.rb +452 -40
  40. data/spec/resources/customer_spec.rb +457 -45
  41. data/spec/resources/event_spec.rb +171 -72
  42. data/spec/resources/mandate_pdf_spec.rb +100 -17
  43. data/spec/resources/mandate_spec.rb +501 -44
  44. data/spec/resources/payment_spec.rb +531 -48
  45. data/spec/resources/payout_spec.rb +189 -45
  46. data/spec/resources/redirect_flow_spec.rb +277 -43
  47. data/spec/resources/refund_spec.rb +349 -34
  48. data/spec/resources/subscription_spec.rb +531 -53
  49. data/spec/response_spec.rb +12 -79
  50. data/spec/services/bank_details_lookups_service_spec.rb +67 -2
  51. data/spec/services/creditor_bank_accounts_service_spec.rb +309 -31
  52. data/spec/services/creditors_service_spec.rb +343 -33
  53. data/spec/services/customer_bank_accounts_service_spec.rb +335 -32
  54. data/spec/services/customers_service_spec.rb +364 -36
  55. data/spec/services/events_service_spec.rb +185 -24
  56. data/spec/services/mandate_pdfs_service_spec.rb +66 -2
  57. data/spec/services/mandates_service_spec.rb +341 -33
  58. data/spec/services/payments_service_spec.rb +355 -35
  59. data/spec/services/payouts_service_spec.rb +206 -26
  60. data/spec/services/redirect_flows_service_spec.rb +137 -7
  61. data/spec/services/refunds_service_spec.rb +301 -27
  62. data/spec/services/subscriptions_service_spec.rb +377 -38
  63. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f9bf930f0861c969e5518b00a10e9751691b4505
4
- data.tar.gz: 3ca0cd94ef9b840cacf9e5aa3ddd7fb843404ed3
3
+ metadata.gz: b558be54b18a323263272b8629c8f7f57c5b034b
4
+ data.tar.gz: b7f920a9a2c6861183603d02bd19c803c245108f
5
5
  SHA512:
6
- metadata.gz: a86b8b85b3e57c6b199d8dbea5591bb3a393f339e2e506a939a82a2bdc22cb8df5361f1f4e4288604b29d3debdfde3b2a7ebb1d8895c640a456e4d795c3b5477
7
- data.tar.gz: 2bf60b407e6fdc07d2e582274197bff176b03f9fc4d30f25591943ab0479121ab9ea388c56c931d0bfe6d14aa5276d1e7e983faaa06fd212ed900ca84d83e4b3
6
+ metadata.gz: df5272b94fa8d97a0945fcc393e69a97558ce9dd4701c54049e6d4e66aea4b0521e3c3f2dc5783a3aebedb6f19a791e85fe40f016487e41726688f3341a72ac4
7
+ data.tar.gz: 42cc33ed47c7e8fa42f23d7e1b3e99bc7f19e7de0891adb82b9c62de102945982ceec33e7a2c5e111c9eabc9268c4a772d1d221bbd8938b4030f168c82a6ba9f
data/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  A Ruby client for the GoCardless API. For full details of the GoCardless API, see the [API docs](https://developer.gocardless.com/pro/).
4
4
 
5
- [![Gem Version](https://badge.fury.io/rb/statesman.png)](http://badge.fury.io/rb/gocardless_pro)
5
+ [![Gem Version](https://badge.fury.io/rb/gocardless_pro.svg)](http://badge.fury.io/rb/gocardless_pro)
6
6
  [![Build Status](https://travis-ci.org/gocardless/gocardless-pro-ruby.svg?branch=master)](https://travis-ci.org/gocardless/gocardless-pro-ruby)
7
7
 
8
8
  - ["Getting started" guide](https://developer.gocardless.com/getting-started/api/introduction/?lang=ruby) with copy and paste Ruby code samples
9
- - [API Reference](https://developer.gocardless.com/api-reference/2015-07-06)
9
+ - [API Reference](https://developer.gocardless.com/api-reference)
10
10
 
11
11
  ## Usage Examples
12
12
 
@@ -115,6 +115,11 @@ For POST and PUT requests you need to pass in the body in under the `params` key
115
115
  )
116
116
  ```
117
117
 
118
+ When creating a resource, the library will automatically include a randomly-generated
119
+ [idempotency key](https://developer.gocardless.com/api-reference/#making-requests-idempotency-keys)
120
+ - this means that if a request appears to fail but is in fact successful (for example due
121
+ to a timeout), you will not end up creating multiple duplicates of the resource.
122
+
118
123
  If any parameters are required they come first:
119
124
 
120
125
  ```rb
@@ -125,7 +130,7 @@ If any parameters are required they come first:
125
130
 
126
131
  Custom headers can be provided for a POST request under the `headers` key.
127
132
 
128
- The most common use of a custom header would be to set an [idempotency key](https://developer.gocardless.com/pro/#making-requests-idempotency-keys) when making a request:
133
+ The most common use of a custom header would be to set a custom [idempotency key](https://developer.gocardless.com/pro/#making-requests-idempotency-keys) when making a request:
129
134
 
130
135
  ```rb
131
136
  @client.customers.create(
@@ -142,7 +147,7 @@ The most common use of a custom header would be to set an [idempotency key](http
142
147
 
143
148
  ### Handling failures
144
149
 
145
- When the API returns an error, the client will raise a corresponding one. There are four classes of error which could be thrown, allof which subclass `GoCardlessPro::Error`:
150
+ When the API returns an error, the client will raise a corresponding one. There are four classes of error which could be thrown, all of which subclass `GoCardlessPro::Error`:
146
151
 
147
152
  - `GoCardlessPro::GoCardlessError`
148
153
  - `GoCardlessPro::InvalidApiUsageError`
@@ -151,6 +156,8 @@ When the API returns an error, the client will raise a corresponding one. There
151
156
 
152
157
  These errors are fully documented in the [API documentation](https://developer.gocardless.com/api-reference/#overview-errors).
153
158
 
159
+ When the API returns an `invalid_state` error due to an `idempotent_creation_conflict`, where possible, the library will automatically retrieve the existing record which was created using the idempotency key.
160
+
154
161
  All errors have the following methods to facilitate access to information in the API response:
155
162
 
156
163
  - `#documentation_url`
@@ -160,6 +167,8 @@ All errors have the following methods to facilitate access to information in the
160
167
  - `#request_id`
161
168
  - `#errors`
162
169
 
170
+ If a timeout occurs, and the request being made is idempotent, the library will automatically retry the request up to 3 times before giving up and raising a `Faraday::TimeoutError` error.
171
+
163
172
  ### Using the OAuth API
164
173
 
165
174
  The API includes [OAuth](https://developer.gocardless.com/pro/2015-07-06/#guides-oauth) functionality, which allows you to work with other users' GoCardless accounts. Once a user approves you, you can use the GoCardless API on their behalf and receive their webhooks.
@@ -24,6 +24,7 @@ end
24
24
 
25
25
  require_relative 'gocardless_pro/api_service'
26
26
  require_relative 'gocardless_pro/list_response'
27
+ require_relative 'gocardless_pro/middlewares/raise_gocardless_errors'
27
28
  require_relative 'gocardless_pro/error'
28
29
  require_relative 'gocardless_pro/error/validation_error'
29
30
  require_relative 'gocardless_pro/error/gocardless_error'
@@ -23,6 +23,8 @@ module GoCardlessPro
23
23
  connection_options = options[:connection_options]
24
24
 
25
25
  @connection = Faraday.new(root_url, connection_options) do |faraday|
26
+ faraday.response :raise_gocardless_errors
27
+
26
28
  faraday.adapter(*http_adapter)
27
29
  end
28
30
 
@@ -116,7 +116,9 @@ module GoCardlessPro
116
116
  default_headers: {
117
117
  'GoCardless-Version' => '2015-07-06',
118
118
  'User-Agent' => "#{user_agent}",
119
- 'Content-Type' => 'application/json'
119
+ 'Content-Type' => 'application/json',
120
+ 'GoCardless-Client-Library' => 'gocardless-pro-ruby',
121
+ 'GoCardless-Client-Version' => '2.0.0'
120
122
  }
121
123
  }
122
124
  end
@@ -124,8 +126,7 @@ module GoCardlessPro
124
126
  def user_agent
125
127
  @user_agent ||=
126
128
  begin
127
- gem_name = 'gocardless_pro'
128
- gem_info = "#{gem_name}"
129
+ gem_info = 'gocardless-pro-ruby'
129
130
  gem_info += "/v#{GoCardlessPro::VERSION}" if defined?(GoCardlessPro::VERSION)
130
131
 
131
132
  ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
@@ -1,5 +1,22 @@
1
1
  module GoCardlessPro
2
2
  # Thrown when the API returns an invalid state error
3
3
  class InvalidStateError < Error
4
+ IDEMPOTENT_CREATION_CONFLICT = 'idempotent_creation_conflict'.freeze
5
+ CONFLICTING_RESOURCE_ID = 'conflicting_resource_id'.freeze
6
+
7
+ def idempotent_creation_conflict?
8
+ !idempotent_creation_conflict_error.nil?
9
+ end
10
+
11
+ def conflicting_resource_id
12
+ return unless idempotent_creation_conflict?
13
+ idempotent_creation_conflict_error['links'][CONFLICTING_RESOURCE_ID]
14
+ end
15
+
16
+ private
17
+
18
+ def idempotent_creation_conflict_error
19
+ errors.find { |error| error['reason'] == IDEMPOTENT_CREATION_CONFLICT }
20
+ end
4
21
  end
5
22
  end
@@ -0,0 +1,50 @@
1
+ module GoCardlessPro
2
+ module Middlewares
3
+ class RaiseGoCardlessErrors < Faraday::Response::Middleware
4
+ API_ERROR_STATUSES = 501..599
5
+ CLIENT_ERROR_STATUSES = 400..500
6
+
7
+ def on_complete(env)
8
+ if !json?(env) || API_ERROR_STATUSES.include?(env.status)
9
+ fail ApiError, generate_error_data(env)
10
+ end
11
+
12
+ if CLIENT_ERROR_STATUSES.include?(env.status)
13
+ json_body ||= JSON.parse(env.body) unless env.body.empty?
14
+ error_type = json_body['error']['type']
15
+ fail(error_class_for_type(error_type), json_body['error'])
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def error_class_for_type(type)
22
+ {
23
+ validation_failed: GoCardlessPro::ValidationError,
24
+ gocardless: GoCardlessPro::GoCardlessError,
25
+ invalid_api_usage: GoCardlessPro::InvalidApiUsageError,
26
+ invalid_state: GoCardlessPro::InvalidStateError
27
+ }.fetch(type.to_sym)
28
+ end
29
+
30
+ def generate_error_data(env)
31
+ {
32
+ 'message' => "Something went wrong with this request\n" \
33
+ "code: #{env.status}\n" \
34
+ "headers: #{env.response_headers}\n" \
35
+ "body: #{env.body}",
36
+ 'code' => env.status
37
+ }
38
+ end
39
+
40
+ def json?(env)
41
+ content_type = env.response_headers['Content-Type'] ||
42
+ env.response_headers['content-type'] || ''
43
+
44
+ content_type.include?('application/json')
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ Faraday::Response.register_middleware raise_gocardless_errors: GoCardlessPro::Middlewares::RaiseGoCardlessErrors
@@ -1,6 +1,11 @@
1
+ require 'securerandom'
2
+
1
3
  module GoCardlessPro
2
4
  # A class that wraps an API request
3
5
  class Request
6
+ MAX_NETWORK_RETRIES = 3
7
+ RETRY_DELAY = 0.5
8
+
4
9
  # Initialize a request class, which makes calls to the API
5
10
  # @param connection
6
11
  # @param method [Symbol] the method to make the request with
@@ -13,6 +18,7 @@ module GoCardlessPro
13
18
  @path = path
14
19
  @headers = options.delete(:headers) || {}
15
20
  @envelope_name = options.delete(:envelope_key)
21
+ @retry_failures = options.delete(:retry_failures) { true }
16
22
  @given_options = options
17
23
 
18
24
  @request_body = request_body
@@ -21,11 +27,34 @@ module GoCardlessPro
21
27
  @request_body = @request_body.to_json
22
28
  @headers['Content-Type'] ||= 'application/json'
23
29
  end
30
+
31
+ @headers['Idempotency-Key'] ||= SecureRandom.uuid if @method == :post
24
32
  end
25
33
 
26
34
  # Make the request and wrap it in a Response object
27
35
  def request
28
- Response.new(make_request)
36
+ if @retry_failures
37
+ with_retries { Response.new(make_request) }
38
+ else
39
+ Response.new(make_request)
40
+ end
41
+ end
42
+
43
+ def with_retries
44
+ requests_attempted = 0
45
+ total_requests_allowed = MAX_NETWORK_RETRIES + 1
46
+
47
+ begin
48
+ yield
49
+ rescue => exception
50
+ if requests_attempted < total_requests_allowed && should_retry?(exception)
51
+ requests_attempted += 1
52
+ sleep(RETRY_DELAY)
53
+ retry
54
+ else
55
+ raise exception
56
+ end
57
+ end
29
58
  end
30
59
 
31
60
  # Make the API request
@@ -57,5 +86,13 @@ module GoCardlessPro
57
86
  {}
58
87
  end
59
88
  end
89
+
90
+ private
91
+
92
+ def should_retry?(exception)
93
+ return true if exception.is_a?(Faraday::TimeoutError)
94
+ return true if exception.is_a?(Faraday::ConnectionFailed)
95
+ return true if exception.is_a?(GoCardlessPro::ApiError)
96
+ end
60
97
  end
61
98
  end
@@ -61,7 +61,7 @@ module GoCardlessPro
61
61
 
62
62
  # Return the links that the resource has
63
63
  def links
64
- @links_links ||= Links.new(@links)
64
+ @creditor_links ||= Links.new(@links)
65
65
  end
66
66
 
67
67
  # Provides the creditor resource as a hash of all its readable attributes
@@ -71,7 +71,7 @@ module GoCardlessPro
71
71
 
72
72
  class Links
73
73
  def initialize(links)
74
- @links = links
74
+ @links = links || {}
75
75
  end
76
76
 
77
77
  def default_eur_payout_account
@@ -12,15 +12,15 @@ module GoCardlessPro
12
12
  # Represents an instance of a creditor_bank_account resource returned from the API
13
13
 
14
14
  # Creditor Bank Accounts hold the bank details of a
15
- # [creditor](#whitelabel-partner-endpoints-creditors). These are the bank
16
- # accounts which your [payouts](#core-endpoints-payouts) will be sent to.
17
-
18
- # #
19
- # Note that creditor bank accounts must be unique, and so you will
20
- # encounter a `bank_account_exists` error if you try to create a duplicate
21
- # bank account. You may wish to handle this by updating the existing record
22
- # instead, the ID of which will be provided as
23
- # `links[creditor_bank_account]` in the error response.
15
+ # [creditor](#core-endpoints-creditors). These are the bank accounts which
16
+ # your [payouts](#core-endpoints-payouts) will be sent to.
17
+ #
18
+ # Note
19
+ # that creditor bank accounts must be unique, and so you will encounter a
20
+ # `bank_account_exists` error if you try to create a duplicate bank account.
21
+ # You may wish to handle this by updating the existing record instead, the
22
+ # ID of which will be provided as `links[creditor_bank_account]` in the
23
+ # error response.
24
24
  class CreditorBankAccount
25
25
  attr_reader :account_holder_name
26
26
  attr_reader :account_number_ending
@@ -56,7 +56,7 @@ module GoCardlessPro
56
56
 
57
57
  # Return the links that the resource has
58
58
  def links
59
- @links_links ||= Links.new(@links)
59
+ @creditor_bank_account_links ||= Links.new(@links)
60
60
  end
61
61
 
62
62
  # Provides the creditor_bank_account resource as a hash of all its readable attributes
@@ -66,7 +66,7 @@ module GoCardlessPro
66
66
 
67
67
  class Links
68
68
  def initialize(links)
69
- @links = links
69
+ @links = links || {}
70
70
  end
71
71
 
72
72
  def creditor
@@ -57,7 +57,7 @@ module GoCardlessPro
57
57
 
58
58
  # Return the links that the resource has
59
59
  def links
60
- @links_links ||= Links.new(@links)
60
+ @customer_bank_account_links ||= Links.new(@links)
61
61
  end
62
62
 
63
63
  # Provides the customer_bank_account resource as a hash of all its readable attributes
@@ -67,7 +67,7 @@ module GoCardlessPro
67
67
 
68
68
  class Links
69
69
  def initialize(links)
70
- @links = links
70
+ @links = links || {}
71
71
  end
72
72
 
73
73
  def customer
@@ -43,7 +43,7 @@ module GoCardlessPro
43
43
 
44
44
  # Return the links that the resource has
45
45
  def links
46
- @links_links ||= Links.new(@links)
46
+ @event_links ||= Links.new(@links)
47
47
  end
48
48
 
49
49
  # Provides the event resource as a hash of all its readable attributes
@@ -53,7 +53,7 @@ module GoCardlessPro
53
53
 
54
54
  class Links
55
55
  def initialize(links)
56
- @links = links
56
+ @links = links || {}
57
57
  end
58
58
 
59
59
  def mandate
@@ -50,7 +50,7 @@ module GoCardlessPro
50
50
 
51
51
  # Return the links that the resource has
52
52
  def links
53
- @links_links ||= Links.new(@links)
53
+ @mandate_links ||= Links.new(@links)
54
54
  end
55
55
 
56
56
  # Provides the mandate resource as a hash of all its readable attributes
@@ -60,7 +60,7 @@ module GoCardlessPro
60
60
 
61
61
  class Links
62
62
  def initialize(links)
63
- @links = links
63
+ @links = links || {}
64
64
  end
65
65
 
66
66
  def creditor
@@ -13,12 +13,12 @@ module GoCardlessPro
13
13
 
14
14
  # Payment objects represent payments from a
15
15
  # [customer](#core-endpoints-customers) to a
16
- # [creditor](#whitelabel-partner-endpoints-creditors), taken against a
17
- # Direct Debit [mandate](#core-endpoints-mandates).
16
+ # [creditor](#core-endpoints-creditors), taken against a Direct Debit
17
+ # [mandate](#core-endpoints-mandates).
18
18
  #
19
- # GoCardless
20
- # will notify you via a [webhook](#appendix-webhooks) whenever the state of
21
- # a payment changes.
19
+ # GoCardless will notify
20
+ # you via a [webhook](#appendix-webhooks) whenever the state of a payment
21
+ # changes.
22
22
  class Payment
23
23
  attr_reader :amount
24
24
  attr_reader :amount_refunded
@@ -56,7 +56,7 @@ module GoCardlessPro
56
56
 
57
57
  # Return the links that the resource has
58
58
  def links
59
- @links_links ||= Links.new(@links)
59
+ @payment_links ||= Links.new(@links)
60
60
  end
61
61
 
62
62
  # Provides the payment resource as a hash of all its readable attributes
@@ -66,7 +66,7 @@ module GoCardlessPro
66
66
 
67
67
  class Links
68
68
  def initialize(links)
69
- @links = links
69
+ @links = links || {}
70
70
  end
71
71
 
72
72
  def creditor
@@ -12,10 +12,9 @@ module GoCardlessPro
12
12
  # Represents an instance of a payout resource returned from the API
13
13
 
14
14
  # Payouts represent transfers from GoCardless to a
15
- # [creditor](#whitelabel-partner-endpoints-creditors). Each payout contains
16
- # the funds collected from one or many [payments](#core-endpoints-payments).
17
- # Payouts are created automatically after a payment has been successfully
18
- # collected.
15
+ # [creditor](#core-endpoints-creditors). Each payout contains the funds
16
+ # collected from one or many [payments](#core-endpoints-payments). Payouts
17
+ # are created automatically after a payment has been successfully collected.
19
18
  class Payout
20
19
  attr_reader :amount
21
20
  attr_reader :arrival_date
@@ -23,6 +22,7 @@ module GoCardlessPro
23
22
  attr_reader :currency
24
23
  attr_reader :deducted_fees
25
24
  attr_reader :id
25
+ attr_reader :payout_type
26
26
  attr_reader :reference
27
27
  attr_reader :status
28
28
 
@@ -38,6 +38,7 @@ module GoCardlessPro
38
38
  @deducted_fees = object['deducted_fees']
39
39
  @id = object['id']
40
40
  @links = object['links']
41
+ @payout_type = object['payout_type']
41
42
  @reference = object['reference']
42
43
  @status = object['status']
43
44
  @response = response
@@ -49,7 +50,7 @@ module GoCardlessPro
49
50
 
50
51
  # Return the links that the resource has
51
52
  def links
52
- @links_links ||= Links.new(@links)
53
+ @payout_links ||= Links.new(@links)
53
54
  end
54
55
 
55
56
  # Provides the payout resource as a hash of all its readable attributes
@@ -59,7 +60,7 @@ module GoCardlessPro
59
60
 
60
61
  class Links
61
62
  def initialize(links)
62
- @links = links
63
+ @links = links || {}
63
64
  end
64
65
 
65
66
  def creditor