gocardless-pro 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +132 -0
  6. data/circle.yml +18 -0
  7. data/demo.rb +10 -0
  8. data/gocardless-pro.gemspec +27 -0
  9. data/lib/gocardless-pro.rb +243 -0
  10. data/lib/gocardless-pro/api_service.rb +57 -0
  11. data/lib/gocardless-pro/error.rb +42 -0
  12. data/lib/gocardless-pro/error/gocardless_error.rb +5 -0
  13. data/lib/gocardless-pro/error/invalid_api_usage_error.rb +5 -0
  14. data/lib/gocardless-pro/error/invalid_state_error.rb +5 -0
  15. data/lib/gocardless-pro/error/validation_error.rb +5 -0
  16. data/lib/gocardless-pro/list_response.rb +34 -0
  17. data/lib/gocardless-pro/paginator.rb +37 -0
  18. data/lib/gocardless-pro/request.rb +69 -0
  19. data/lib/gocardless-pro/resources/api_key.rb +62 -0
  20. data/lib/gocardless-pro/resources/creditor.rb +83 -0
  21. data/lib/gocardless-pro/resources/creditor_bank_account.rb +78 -0
  22. data/lib/gocardless-pro/resources/customer.rb +72 -0
  23. data/lib/gocardless-pro/resources/customer_bank_account.rb +80 -0
  24. data/lib/gocardless-pro/resources/event.rb +75 -0
  25. data/lib/gocardless-pro/resources/helper.rb +29 -0
  26. data/lib/gocardless-pro/resources/mandate.rb +70 -0
  27. data/lib/gocardless-pro/resources/payment.rb +86 -0
  28. data/lib/gocardless-pro/resources/payout.rb +66 -0
  29. data/lib/gocardless-pro/resources/publishable_api_key.rb +51 -0
  30. data/lib/gocardless-pro/resources/redirect_flow.rb +104 -0
  31. data/lib/gocardless-pro/resources/refund.rb +70 -0
  32. data/lib/gocardless-pro/resources/role.rb +101 -0
  33. data/lib/gocardless-pro/resources/subscription.rb +152 -0
  34. data/lib/gocardless-pro/resources/user.rb +60 -0
  35. data/lib/gocardless-pro/response.rb +77 -0
  36. data/lib/gocardless-pro/services/api_key_service.rb +130 -0
  37. data/lib/gocardless-pro/services/base_service.rb +29 -0
  38. data/lib/gocardless-pro/services/creditor_bank_account_service.rb +122 -0
  39. data/lib/gocardless-pro/services/creditor_service.rb +112 -0
  40. data/lib/gocardless-pro/services/customer_bank_account_service.rb +153 -0
  41. data/lib/gocardless-pro/services/customer_service.rb +112 -0
  42. data/lib/gocardless-pro/services/event_service.rb +80 -0
  43. data/lib/gocardless-pro/services/helper_service.rb +97 -0
  44. data/lib/gocardless-pro/services/mandate_service.rb +170 -0
  45. data/lib/gocardless-pro/services/payment_service.rb +164 -0
  46. data/lib/gocardless-pro/services/payout_service.rb +80 -0
  47. data/lib/gocardless-pro/services/publishable_api_key_service.rb +130 -0
  48. data/lib/gocardless-pro/services/redirect_flow_service.rb +96 -0
  49. data/lib/gocardless-pro/services/refund_service.rb +126 -0
  50. data/lib/gocardless-pro/services/role_service.rb +127 -0
  51. data/lib/gocardless-pro/services/subscription_service.rb +133 -0
  52. data/lib/gocardless-pro/services/user_service.rb +148 -0
  53. data/lib/gocardless-pro/version.rb +8 -0
  54. data/spec/api_service_spec.rb +69 -0
  55. data/spec/client_spec.rb +29 -0
  56. data/spec/error_spec.rb +44 -0
  57. data/spec/resources/api_key_spec.rb +85 -0
  58. data/spec/resources/creditor_bank_account_spec.rb +109 -0
  59. data/spec/resources/creditor_spec.rb +125 -0
  60. data/spec/resources/customer_bank_account_spec.rb +109 -0
  61. data/spec/resources/customer_spec.rb +127 -0
  62. data/spec/resources/event_spec.rb +113 -0
  63. data/spec/resources/helper_spec.rb +23 -0
  64. data/spec/resources/mandate_spec.rb +97 -0
  65. data/spec/resources/payment_spec.rb +129 -0
  66. data/spec/resources/payout_spec.rb +89 -0
  67. data/spec/resources/publishable_api_key_spec.rb +63 -0
  68. data/spec/resources/redirect_flow_spec.rb +97 -0
  69. data/spec/resources/refund_spec.rb +77 -0
  70. data/spec/resources/role_spec.rb +63 -0
  71. data/spec/resources/subscription_spec.rb +157 -0
  72. data/spec/resources/user_spec.rb +85 -0
  73. data/spec/response_spec.rb +79 -0
  74. data/spec/services/api_key_service_spec.rb +362 -0
  75. data/spec/services/creditor_bank_account_service_spec.rb +365 -0
  76. data/spec/services/creditor_service_spec.rb +339 -0
  77. data/spec/services/customer_bank_account_service_spec.rb +404 -0
  78. data/spec/services/customer_service_spec.rb +365 -0
  79. data/spec/services/event_service_spec.rb +172 -0
  80. data/spec/services/helper_service_spec.rb +123 -0
  81. data/spec/services/mandate_service_spec.rb +449 -0
  82. data/spec/services/payment_service_spec.rb +497 -0
  83. data/spec/services/payout_service_spec.rb +172 -0
  84. data/spec/services/publishable_api_key_service_spec.rb +336 -0
  85. data/spec/services/redirect_flow_service_spec.rb +208 -0
  86. data/spec/services/refund_service_spec.rb +279 -0
  87. data/spec/services/role_service_spec.rb +336 -0
  88. data/spec/services/subscription_service_spec.rb +488 -0
  89. data/spec/services/user_service_spec.rb +433 -0
  90. data/spec/spec_helper.rb +91 -0
  91. metadata +255 -0
@@ -0,0 +1,164 @@
1
+ require_relative './base_service'
2
+
3
+ # encoding: utf-8
4
+ #
5
+ # WARNING: Do not edit by hand, this file was generated by Crank:
6
+ #
7
+ # https://github.com/gocardless/crank
8
+
9
+ module GoCardless
10
+ module Services
11
+ # Service for making requests to the Payment endpoints
12
+ class PaymentService < BaseService
13
+ # <a name="mandate_is_inactive"></a>Creates a new payment object.
14
+ #
15
+ # This
16
+ # fails with a `mandate_is_inactive` error if the linked
17
+ # [mandate](https://developer.gocardless.com/pro/#api-endpoints-mandates) is
18
+ # cancelled. Payments can be created against `pending_submission` mandates, but
19
+ # they will not be submitted until the mandate becomes active.
20
+ # Example URL: /payments
21
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
22
+ # Else, they will be the body of the request.
23
+ def create(options = {}, custom_headers = {})
24
+ path = '/payments'
25
+ new_options = {}
26
+ new_options[envelope_key] = options
27
+ options = new_options
28
+ response = make_request(:post, path, options, custom_headers)
29
+
30
+ Resources::Payment.new(unenvelope_body(response.body))
31
+ end
32
+
33
+ # Returns a
34
+ # [cursor-paginated](https://developer.gocardless.com/pro/#overview-cursor-pagination)
35
+ # list of your payments.
36
+ # Example URL: /payments
37
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
38
+ # Else, they will be the body of the request.
39
+ def list(options = {}, custom_headers = {})
40
+ path = '/payments'
41
+
42
+ response = make_request(:get, path, options, custom_headers)
43
+ ListResponse.new(
44
+ raw_response: response,
45
+ unenveloped_body: unenvelope_body(response.body),
46
+ resource_class: Resources::Payment
47
+ )
48
+ end
49
+
50
+ # Get a lazily enumerated list of all the items returned. This is simmilar to the `list` method but will paginate for you automatically.
51
+ #
52
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
53
+ # Otherwise they will be the body of the request.
54
+ def all(options = {})
55
+ Paginator.new(
56
+ service: self,
57
+ path: '/payments',
58
+ options: options
59
+ ).enumerator
60
+ end
61
+
62
+ # Retrieves the details of a single existing payment.
63
+ # Example URL: /payments/:identity
64
+ #
65
+ # @param identity # Unique identifier, beginning with "PM"
66
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
67
+ # Else, they will be the body of the request.
68
+ def get(identity, options = {}, custom_headers = {})
69
+ path = sub_url('/payments/:identity', 'identity' => identity)
70
+
71
+ response = make_request(:get, path, options, custom_headers)
72
+
73
+ Resources::Payment.new(unenvelope_body(response.body))
74
+ end
75
+
76
+ # Updates a payment object. This accepts only the metadata parameter.
77
+ # Example URL: /payments/:identity
78
+ #
79
+ # @param identity # Unique identifier, beginning with "PM"
80
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
81
+ # Else, they will be the body of the request.
82
+ def update(identity, options = {}, custom_headers = {})
83
+ path = sub_url('/payments/:identity', 'identity' => identity)
84
+
85
+ new_options = {}
86
+ new_options[envelope_key] = options
87
+ options = new_options
88
+ response = make_request(:put, path, options, custom_headers)
89
+
90
+ Resources::Payment.new(unenvelope_body(response.body))
91
+ end
92
+
93
+ # Cancels the payment if it has not already been submitted to the banks. Any
94
+ # metadata supplied to this endpoint will be stored on the payment cancellation
95
+ # event it causes.
96
+ #
97
+ # This will fail with a `cancellation_failed` error unless
98
+ # the payment's status is `pending_submission`.
99
+ # Example URL: /payments/:identity/actions/cancel
100
+ #
101
+ # @param identity # Unique identifier, beginning with "PM"
102
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
103
+ # Else, they will be the body of the request.
104
+ def cancel(identity, options = {}, custom_headers = {})
105
+ path = sub_url('/payments/:identity/actions/cancel', 'identity' => identity)
106
+
107
+ new_options = {}
108
+ new_options['data'] = options
109
+ options = new_options
110
+ response = make_request(:post, path, options, custom_headers)
111
+
112
+ Resources::Payment.new(unenvelope_body(response.body))
113
+ end
114
+
115
+ # <a name="retry_failed"></a>Retries a failed payment if the underlying mandate
116
+ # is active. You will receive a `resubmission_requested` webhook, but after that
117
+ # retrying the payment follows the same process as its initial creation, so you
118
+ # will receive a `submitted` webhook, followed by a `confirmed` or `failed`
119
+ # event. Any metadata supplied to this endpoint will be stored against the
120
+ # payment submission event it causes.
121
+ #
122
+ # This will return a `retry_failed`
123
+ # error if the payment has not failed.
124
+ # Example URL: /payments/:identity/actions/retry
125
+ #
126
+ # @param identity # Unique identifier, beginning with "PM"
127
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
128
+ # Else, they will be the body of the request.
129
+ def retry(identity, options = {}, custom_headers = {})
130
+ path = sub_url('/payments/:identity/actions/retry', 'identity' => identity)
131
+
132
+ new_options = {}
133
+ new_options['data'] = options
134
+ options = new_options
135
+ response = make_request(:post, path, options, custom_headers)
136
+
137
+ Resources::Payment.new(unenvelope_body(response.body))
138
+ end
139
+
140
+ # Unenvelope the response of the body using the service's `envelope_key`
141
+ #
142
+ # @param body [Hash]
143
+ def unenvelope_body(body)
144
+ body[envelope_key] || body['data']
145
+ end
146
+
147
+ private
148
+
149
+ # return the key which API responses will envelope data under
150
+ def envelope_key
151
+ 'payments'
152
+ end
153
+
154
+ # take a URL with placeholder params and substitute them out for the acutal value
155
+ # @param url [String] the URL with placeholders in
156
+ # @param param_map [Hash] a hash of placeholders and their actual values
157
+ def sub_url(url, param_map)
158
+ param_map.reduce(url) do |new_url, (param, value)|
159
+ new_url.gsub(":#{param}", value)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,80 @@
1
+ require_relative './base_service'
2
+
3
+ # encoding: utf-8
4
+ #
5
+ # WARNING: Do not edit by hand, this file was generated by Crank:
6
+ #
7
+ # https://github.com/gocardless/crank
8
+
9
+ module GoCardless
10
+ module Services
11
+ # Service for making requests to the Payout endpoints
12
+ class PayoutService < BaseService
13
+ # Returns a
14
+ # [cursor-paginated](https://developer.gocardless.com/pro/#overview-cursor-pagination)
15
+ # list of your payouts.
16
+ # Example URL: /payouts
17
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
18
+ # Else, they will be the body of the request.
19
+ def list(options = {}, custom_headers = {})
20
+ path = '/payouts'
21
+
22
+ response = make_request(:get, path, options, custom_headers)
23
+ ListResponse.new(
24
+ raw_response: response,
25
+ unenveloped_body: unenvelope_body(response.body),
26
+ resource_class: Resources::Payout
27
+ )
28
+ end
29
+
30
+ # Get a lazily enumerated list of all the items returned. This is simmilar to the `list` method but will paginate for you automatically.
31
+ #
32
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
33
+ # Otherwise they will be the body of the request.
34
+ def all(options = {})
35
+ Paginator.new(
36
+ service: self,
37
+ path: '/payouts',
38
+ options: options
39
+ ).enumerator
40
+ end
41
+
42
+ # Retrieves the details of a single payout.
43
+ # Example URL: /payouts/:identity
44
+ #
45
+ # @param identity # Unique identifier, beginning with "PO"
46
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
47
+ # Else, they will be the body of the request.
48
+ def get(identity, options = {}, custom_headers = {})
49
+ path = sub_url('/payouts/:identity', 'identity' => identity)
50
+
51
+ response = make_request(:get, path, options, custom_headers)
52
+
53
+ Resources::Payout.new(unenvelope_body(response.body))
54
+ end
55
+
56
+ # Unenvelope the response of the body using the service's `envelope_key`
57
+ #
58
+ # @param body [Hash]
59
+ def unenvelope_body(body)
60
+ body[envelope_key] || body['data']
61
+ end
62
+
63
+ private
64
+
65
+ # return the key which API responses will envelope data under
66
+ def envelope_key
67
+ 'payouts'
68
+ end
69
+
70
+ # take a URL with placeholder params and substitute them out for the acutal value
71
+ # @param url [String] the URL with placeholders in
72
+ # @param param_map [Hash] a hash of placeholders and their actual values
73
+ def sub_url(url, param_map)
74
+ param_map.reduce(url) do |new_url, (param, value)|
75
+ new_url.gsub(":#{param}", value)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,130 @@
1
+ require_relative './base_service'
2
+
3
+ # encoding: utf-8
4
+ #
5
+ # WARNING: Do not edit by hand, this file was generated by Crank:
6
+ #
7
+ # https://github.com/gocardless/crank
8
+
9
+ module GoCardless
10
+ module Services
11
+ # Service for making requests to the PublishableApiKey endpoints
12
+ class PublishableApiKeyService < BaseService
13
+ # Creates a publishable API key object.
14
+ # Example URL: /publishable_api_keys
15
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
16
+ # Else, they will be the body of the request.
17
+ def create(options = {}, custom_headers = {})
18
+ path = '/publishable_api_keys'
19
+ new_options = {}
20
+ new_options[envelope_key] = options
21
+ options = new_options
22
+ response = make_request(:post, path, options, custom_headers)
23
+
24
+ Resources::PublishableApiKey.new(unenvelope_body(response.body))
25
+ end
26
+
27
+ # Returns a
28
+ # [cursor-paginated](https://developer.gocardless.com/pro/#overview-cursor-pagination)
29
+ # list of your publishable API keys
30
+ # Example URL: /publishable_api_keys
31
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
32
+ # Else, they will be the body of the request.
33
+ def list(options = {}, custom_headers = {})
34
+ path = '/publishable_api_keys'
35
+
36
+ response = make_request(:get, path, options, custom_headers)
37
+ ListResponse.new(
38
+ raw_response: response,
39
+ unenveloped_body: unenvelope_body(response.body),
40
+ resource_class: Resources::PublishableApiKey
41
+ )
42
+ end
43
+
44
+ # Get a lazily enumerated list of all the items returned. This is simmilar to the `list` method but will paginate for you automatically.
45
+ #
46
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
47
+ # Otherwise they will be the body of the request.
48
+ def all(options = {})
49
+ Paginator.new(
50
+ service: self,
51
+ path: '/publishable_api_keys',
52
+ options: options
53
+ ).enumerator
54
+ end
55
+
56
+ # Returns all details about a single publishable API key
57
+ # Example URL: /publishable_api_keys/:identity
58
+ #
59
+ # @param identity # Unique identifier, beginning with "PK"
60
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
61
+ # Else, they will be the body of the request.
62
+ def get(identity, options = {}, custom_headers = {})
63
+ path = sub_url('/publishable_api_keys/:identity', 'identity' => identity)
64
+
65
+ response = make_request(:get, path, options, custom_headers)
66
+
67
+ Resources::PublishableApiKey.new(unenvelope_body(response.body))
68
+ end
69
+
70
+ # Updates a publishable API key. Only the `name` fields are supported. Any other
71
+ # fields passed will be ignored.
72
+ # Example URL: /publishable_api_keys/:identity
73
+ #
74
+ # @param identity # Unique identifier, beginning with "PK"
75
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
76
+ # Else, they will be the body of the request.
77
+ def update(identity, options = {}, custom_headers = {})
78
+ path = sub_url('/publishable_api_keys/:identity', 'identity' => identity)
79
+
80
+ new_options = {}
81
+ new_options[envelope_key] = options
82
+ options = new_options
83
+ response = make_request(:put, path, options, custom_headers)
84
+
85
+ Resources::PublishableApiKey.new(unenvelope_body(response.body))
86
+ end
87
+
88
+ # Disables a publishable API key. Once disabled, the publishable API key will
89
+ # not be usable to authenticate any requests.
90
+ # Example URL: /publishable_api_keys/:identity/actions/disable
91
+ #
92
+ # @param identity # Unique identifier, beginning with "PK"
93
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
94
+ # Else, they will be the body of the request.
95
+ def disable(identity, options = {}, custom_headers = {})
96
+ path = sub_url('/publishable_api_keys/:identity/actions/disable', 'identity' => identity)
97
+
98
+ new_options = {}
99
+ new_options['data'] = options
100
+ options = new_options
101
+ response = make_request(:post, path, options, custom_headers)
102
+
103
+ Resources::PublishableApiKey.new(unenvelope_body(response.body))
104
+ end
105
+
106
+ # Unenvelope the response of the body using the service's `envelope_key`
107
+ #
108
+ # @param body [Hash]
109
+ def unenvelope_body(body)
110
+ body[envelope_key] || body['data']
111
+ end
112
+
113
+ private
114
+
115
+ # return the key which API responses will envelope data under
116
+ def envelope_key
117
+ 'publishable_api_keys'
118
+ end
119
+
120
+ # take a URL with placeholder params and substitute them out for the acutal value
121
+ # @param url [String] the URL with placeholders in
122
+ # @param param_map [Hash] a hash of placeholders and their actual values
123
+ def sub_url(url, param_map)
124
+ param_map.reduce(url) do |new_url, (param, value)|
125
+ new_url.gsub(":#{param}", value)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,96 @@
1
+ require_relative './base_service'
2
+
3
+ # encoding: utf-8
4
+ #
5
+ # WARNING: Do not edit by hand, this file was generated by Crank:
6
+ #
7
+ # https://github.com/gocardless/crank
8
+
9
+ module GoCardless
10
+ module Services
11
+ # Service for making requests to the RedirectFlow endpoints
12
+ class RedirectFlowService < BaseService
13
+ # Creates a redirect flow object which can then be used to redirect your
14
+ # customer to the GoCardless Pro hosted payment pages.
15
+ # Example URL: /redirect_flows
16
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
17
+ # Else, they will be the body of the request.
18
+ def create(options = {}, custom_headers = {})
19
+ path = '/redirect_flows'
20
+ new_options = {}
21
+ new_options[envelope_key] = options
22
+ options = new_options
23
+ response = make_request(:post, path, options, custom_headers)
24
+
25
+ Resources::RedirectFlow.new(unenvelope_body(response.body))
26
+ end
27
+
28
+ # Returns all details about a single redirect flow
29
+ # Example URL: /redirect_flows/:identity
30
+ #
31
+ # @param identity # Unique identifier, beginning with "RE"
32
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
33
+ # Else, they will be the body of the request.
34
+ def get(identity, options = {}, custom_headers = {})
35
+ path = sub_url('/redirect_flows/:identity', 'identity' => identity)
36
+
37
+ response = make_request(:get, path, options, custom_headers)
38
+
39
+ Resources::RedirectFlow.new(unenvelope_body(response.body))
40
+ end
41
+
42
+ # This creates a
43
+ # [customer](https://developer.gocardless.com/pro/#api-endpoints-customers),
44
+ # [customer bank
45
+ # account](https://developer.gocardless.com/pro/#api-endpoints-customer-bank-account),
46
+ # and [mandate](https://developer.gocardless.com/pro/#api-endpoints-mandates)
47
+ # using the details supplied by your customer and returns the ID of the created
48
+ # mandate.
49
+ #
50
+ # This will return a `redirect_flow_incomplete` error if your
51
+ # customer has not yet been redirected back to your site, and a
52
+ # `redirect_flow_already_completed` error if your integration has already
53
+ # completed this flow. It will return a `bad_request` error if the
54
+ # `session_token` differs to the one supplied when the redirect flow was
55
+ # created.
56
+ # Example URL: /redirect_flows/:identity/actions/complete
57
+ #
58
+ # @param identity # Unique identifier, beginning with "RE"
59
+ # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters.
60
+ # Else, they will be the body of the request.
61
+ def complete(identity, options = {}, custom_headers = {})
62
+ path = sub_url('/redirect_flows/:identity/actions/complete', 'identity' => identity)
63
+
64
+ new_options = {}
65
+ new_options['data'] = options
66
+ options = new_options
67
+ response = make_request(:post, path, options, custom_headers)
68
+
69
+ Resources::RedirectFlow.new(unenvelope_body(response.body))
70
+ end
71
+
72
+ # Unenvelope the response of the body using the service's `envelope_key`
73
+ #
74
+ # @param body [Hash]
75
+ def unenvelope_body(body)
76
+ body[envelope_key] || body['data']
77
+ end
78
+
79
+ private
80
+
81
+ # return the key which API responses will envelope data under
82
+ def envelope_key
83
+ 'redirect_flows'
84
+ end
85
+
86
+ # take a URL with placeholder params and substitute them out for the acutal value
87
+ # @param url [String] the URL with placeholders in
88
+ # @param param_map [Hash] a hash of placeholders and their actual values
89
+ def sub_url(url, param_map)
90
+ param_map.reduce(url) do |new_url, (param, value)|
91
+ new_url.gsub(":#{param}", value)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end