veritrans 2.1.2 → 2.4.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 (98) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +10 -1
  3. data/.idea/.gitignore +8 -0
  4. data/.rubocop.yml +35 -0
  5. data/.travis.yml +10 -5
  6. data/CHANGELOG.md +45 -0
  7. data/Gemfile +6 -7
  8. data/Gemfile.lock +134 -146
  9. data/Maintaining.MD +8 -0
  10. data/README.md +266 -226
  11. data/api_reference.md +534 -143
  12. data/example/coreapi/core_api_credit_card_example.rb +66 -0
  13. data/example/coreapi/readme.md +4 -0
  14. data/example/sinatra/Gemfile +7 -0
  15. data/example/sinatra/README.md +6 -0
  16. data/example/sinatra/index.erb +202 -0
  17. data/example/sinatra/response.erb +1 -0
  18. data/example/sinatra/snap.erb +33 -0
  19. data/example/sinatra/snap_redirect.erb +10 -0
  20. data/example/sinatra/webapp.rb +113 -0
  21. data/example/snap/readme.md +4 -0
  22. data/example/snap/snap_example.rb +39 -0
  23. data/lib/test/all.rb +1 -0
  24. data/lib/test/api_test.rb +319 -0
  25. data/lib/test/config_test.rb +26 -0
  26. data/lib/test/gopay_tokenization_test.rb +80 -0
  27. data/lib/test/snap_test.rb +79 -0
  28. data/lib/test/subscription_test.rb +116 -0
  29. data/lib/test/transaction_test.rb +160 -0
  30. data/lib/veritrans/api.rb +146 -22
  31. data/lib/veritrans/client.rb +48 -12
  32. data/lib/veritrans/config.rb +31 -6
  33. data/lib/veritrans/events.rb +46 -35
  34. data/lib/veritrans/midtrans_error.rb +15 -0
  35. data/lib/veritrans/result.rb +66 -5
  36. data/lib/veritrans/version.rb +1 -1
  37. data/lib/veritrans.rb +121 -12
  38. data/veritrans.gemspec +2 -9
  39. metadata +30 -146
  40. data/.rspec +0 -2
  41. data/Procfile +0 -1
  42. data/Rakefile +0 -16
  43. data/bin/midtrans +0 -3
  44. data/bin/veritrans +0 -68
  45. data/example/README.md +0 -8
  46. data/example/config.ru +0 -6
  47. data/example/index.erb +0 -213
  48. data/example/localization.erb +0 -248
  49. data/example/points.erb +0 -187
  50. data/example/recurring.erb +0 -201
  51. data/example/response.erb +0 -37
  52. data/example/sinatra.rb +0 -188
  53. data/example/style.css +0 -126
  54. data/example/veritrans.yml +0 -11
  55. data/example/widget.erb +0 -51
  56. data/lib/generators/templates/assets/credit_card_form.js +0 -51
  57. data/lib/generators/templates/payments_controller.rb +0 -85
  58. data/lib/generators/templates/veritrans.rb +0 -46
  59. data/lib/generators/templates/veritrans.yml +0 -18
  60. data/lib/generators/templates/views/_credit_card_form.erb +0 -42
  61. data/lib/generators/templates/views/_veritrans_include.erb +0 -10
  62. data/lib/generators/templates/views/payments/create.erb +0 -15
  63. data/lib/generators/templates/views/payments/new.erb +0 -6
  64. data/lib/generators/veritrans/install_generator.rb +0 -32
  65. data/lib/generators/veritrans/payment_form_generator.rb +0 -45
  66. data/lib/veritrans/cli.rb +0 -155
  67. data/lib/veritrans/core_extensions.rb +0 -32
  68. data/spec/cli_spec.rb +0 -83
  69. data/spec/configs/real_key.yml +0 -4
  70. data/spec/configs/veritrans.yml +0 -7
  71. data/spec/configs/veritrans_flat.yml +0 -2
  72. data/spec/configs/veritrans_with_erb.yml +0 -2
  73. data/spec/fixtures/approve_failed.yml +0 -48
  74. data/spec/fixtures/cancel_failed.yml +0 -48
  75. data/spec/fixtures/cancel_success.yml +0 -106
  76. data/spec/fixtures/capture_failed.yml +0 -48
  77. data/spec/fixtures/charge.yml +0 -50
  78. data/spec/fixtures/charge_direct.yml +0 -56
  79. data/spec/fixtures/charge_vtweb.yml +0 -50
  80. data/spec/fixtures/cli_test_1111-not-exists.yml +0 -45
  81. data/spec/fixtures/cli_test_not_exists.yml +0 -45
  82. data/spec/fixtures/cli_test_real_txn.yml +0 -55
  83. data/spec/fixtures/cli_test_unauthorized.yml +0 -47
  84. data/spec/fixtures/events_test_real_txn.yml +0 -55
  85. data/spec/fixtures/expire_failed.yml +0 -50
  86. data/spec/fixtures/expire_success.yml +0 -56
  87. data/spec/fixtures/midtrans_status.yml +0 -117
  88. data/spec/fixtures/status_fail.yml +0 -46
  89. data/spec/fixtures/status_success.yml +0 -109
  90. data/spec/midtrans_rename_spec.rb +0 -27
  91. data/spec/rails_plugin_spec.rb +0 -281
  92. data/spec/spec_helper.rb +0 -61
  93. data/spec/veritrans_client_spec.rb +0 -184
  94. data/spec/veritrans_config_spec.rb +0 -70
  95. data/spec/veritrans_events_spec.rb +0 -72
  96. data/spec/veritrans_logger_spec.rb +0 -46
  97. data/spec/veritrans_snap_spec.rb +0 -39
  98. data/testing_webhooks.md +0 -78
@@ -1,41 +1,45 @@
1
- # Rack based event notification callback processor
2
- #
3
- # Usage:
4
- #
5
- # Rails.application.routes.draw do
6
- # ...
7
- # mount Veritrans::Events.new => '/vt_events'
8
- # end
9
- #
10
- # Veritrans.events.subscribe('payment.success') do |payment|
11
- # payment.mark_paid!(payment.masked_card)
12
- # end
13
- #
14
-
15
- # All possible events:
16
- #
17
- # * payment.success == ['authorize', 'capture', 'settlement']
18
- # * payment.failed == ['deny', 'cancel', 'expire']
19
- # * payment.challenge # when payment.fraud_status == 'challenge'
20
- #
21
- # * payment.authorize
22
- # * payment.capture
23
- # * payment.settlement
24
- # * payment.deny
25
- # * payment.cancel
26
- # * payment.expire
27
- #
28
- # * error
29
-
30
- # For sinatra you can use Rack::URLMap
31
- #
32
- # run Rack::URLMap.new("/" => MyApp.new, "/payment_events" => Veritrans::Events.new)
33
- #
34
-
35
1
  class Veritrans
2
+ #
3
+ # Rack based event notification callback processor
4
+ #
5
+ # Usage:
6
+ #
7
+ # Rails.application.routes.draw do
8
+ # # ...
9
+ # mount Veritrans::Events.new => '/vt_events'
10
+ # end
11
+ #
12
+ # Veritrans::Events.subscribe('payment.success') do |payment|
13
+ # payment.mark_paid!(payment.masked_card)
14
+ # end
15
+ #
16
+ #
17
+ # All possible events:
18
+ #
19
+ # * payment.success == ['authorize', 'capture', 'settlement']
20
+ # * payment.failed == ['deny', 'cancel', 'expire']
21
+ # * payment.challenge # when payment.fraud_status == 'challenge'
22
+ #
23
+ # * payment.authorize
24
+ # * payment.capture
25
+ # * payment.settlement
26
+ # * payment.deny
27
+ # * payment.cancel
28
+ # * payment.expire
29
+ #
30
+ # * error
31
+ #
32
+ # For sinatra you can use Rack::URLMap
33
+ #
34
+ # run Rack::URLMap.new("/" => MyApp.new, "/payment_events" => Veritrans::Events.new)
35
+ #
36
36
  class Events
37
37
 
38
38
  # This is rack application
39
+ # Can be used as:
40
+ #
41
+ # use Veritrans::Events.new
42
+ #
39
43
  def call(env)
40
44
  Veritrans.logger.info "Receive notification callback"
41
45
 
@@ -92,7 +96,7 @@ class Veritrans
92
96
  return send_text("Server error:\n#{error.message}", 500)
93
97
  end
94
98
 
95
- def send_text(body, status = 200)
99
+ def send_text(body, status = 200) # :nodoc:
96
100
  [status, {"Content-Type" => "text/html"}, [body]]
97
101
  end
98
102
 
@@ -101,6 +105,12 @@ class Veritrans
101
105
  class << self
102
106
  attr_accessor :listeners
103
107
 
108
+ # Subscribe for events. The event object will be an instance of Midtrans::Result
109
+ #
110
+ # Midtrans::Events.subscribe('payment.success') do |payment_status|
111
+ # Order.find_by(order_id: payment_status.order_id).mark_paid!
112
+ # end
113
+ #
104
114
  def subscribe(*event_types, &handler)
105
115
  @listeners ||= []
106
116
  event_types.each do |event_type|
@@ -108,6 +118,7 @@ class Veritrans
108
118
  end
109
119
  end
110
120
 
121
+ # Used internally to dispatch event
111
122
  def dispatch(new_event, event_data)
112
123
  @listeners.each do |pair|
113
124
  event_type, handler = *pair
@@ -0,0 +1,15 @@
1
+ class MidtransError < StandardError
2
+ attr_reader :status
3
+ alias_method :http_status_code, :status
4
+ attr_reader :data
5
+ alias_method :api_response, :data
6
+ attr_reader :response
7
+ alias_method :raw_http_client_data, :response
8
+
9
+ def initialize(message, http_status_code = nil, api_response = nil, raw_http_client_data = nil)
10
+ super(message)
11
+ @status = http_status_code
12
+ @data = api_response
13
+ @response = raw_http_client_data
14
+ end
15
+ end
@@ -1,10 +1,46 @@
1
1
  class Veritrans
2
+ # Midtrans response object, a wrapper for raw response object plus helper methods
3
+ #
4
+ # Usual response body for Midtrans.charge or Midtrans.status will look like this:
5
+ #
6
+ # {
7
+ # "status_code": "200",
8
+ # "status_message": "Success, Mandiri Clickpay transaction is successful",
9
+ # "transaction_id": "d788e503-3fab-4296-9c10-83b107324cb9",
10
+ # "order_id": "2016-11-14 11:54:03 +0800",
11
+ # "gross_amount": "10000.00",
12
+ # "payment_type": "mandiri_clickpay",
13
+ # "transaction_time": "2016-11-14 10:54:02",
14
+ # "transaction_status": "settlement",
15
+ # "fraud_status": "accept",
16
+ # "approval_code": "1479095646260",
17
+ # "masked_card": "411111-1111"
18
+ # }
19
+ #
20
+ # Result object can be used like this:
21
+ #
22
+ # result.success? # => true
23
+ # result.status_code # => 200
24
+ # result.transaction_status # => "settlement"
25
+ # result.fraud_status # => "accept"
26
+ # result.approval_code # => "1479095646260"
27
+ # result.masked_card # => "411111-1111"
28
+ #
29
+ # result.data # => {:status_code => "200", :status_message => "Success, Mandiri ..."} # add data as hash
30
+ # result.time # => 1.3501
31
+ #
2
32
  class Result
33
+ # Response body parsed as hash
3
34
  attr_reader :data
35
+ # HTTP status code, should always be 200
4
36
  attr_reader :status
37
+ # Excon::Response object
5
38
  attr_reader :response
39
+ # Request options, a hash with :path, :method, :headers, :body
6
40
  attr_reader :request_options
41
+ # HTTP request time, a Float
7
42
  attr_reader :time
43
+ # Request full URL, e.g. "https://api.sandbox.midtrans.com/v2/charge"
8
44
  attr_reader :url
9
45
 
10
46
  def initialize(response, url, request_options, time)
@@ -33,28 +69,33 @@ class Veritrans
33
69
  @request_options = request_options
34
70
  end
35
71
 
72
+ # Return whenever transaction is successful, based on <tt>status_code</tt>
36
73
  def success?
37
74
  @data[:status_code] == '200' || @data[:status_code] == '201' || @data[:status_code] == '407'
38
75
  end
39
76
 
40
- # for VT-Link
77
+ # Return if VT-Link page was created
41
78
  def created?
42
79
  @data[:status_code] == '201'
43
80
  end
44
81
 
45
- # Docs http://docs.veritrans.co.id/en/api/status_code.html
82
+ # Return <tt>"status_code"</tt> field of response
83
+ # Docs https://api-docs.midtrans.com/#status-code
46
84
  def status_code
47
85
  @data[:status_code].to_i
48
86
  end
49
87
 
88
+ # Return <tt>"status_message"</tt> field of response
50
89
  def status_message
51
90
  @data[:status_message]
52
91
  end
53
92
 
93
+ # Return <tt>"redirect_url"</tt> field of response
54
94
  def redirect_url
55
95
  @data[:redirect_url]
56
96
  end
57
97
 
98
+ # Return <tt>"transaction_id"</tt> field of response
58
99
  def transaction_id
59
100
  @data[:transaction_id]
60
101
  end
@@ -67,6 +108,7 @@ class Veritrans
67
108
  end
68
109
  end
69
110
 
111
+ # Raw response body as String
70
112
  def body
71
113
  response.body
72
114
  end
@@ -86,17 +128,36 @@ class Veritrans
86
128
  end
87
129
  end
88
130
 
131
+ # SNAP response object
89
132
  class SnapResult < Result
133
+
134
+ # HTTP response status code
90
135
  def status_code
91
136
  @response.status.to_i
92
137
  end
93
138
 
139
+ # DEPRECATED, please use #token instead
94
140
  def token_id
95
- @data[:token_id]
141
+ if defined?(ActiveSupport::Deprecation)
142
+ ActiveSupport::Deprecation.warn("`token_id` on SnapResult is deprecated. Please use `token` instead.")
143
+ else
144
+ warn "DEPRECATION WARNING: `token_id` on SnapResult is deprecated. Please use `token` instead."
145
+ end
146
+ @data[:token]
147
+ end
148
+
149
+ # Acccessor for <tt>token</tt> value
150
+ def token
151
+ @data[:token]
152
+ end
153
+
154
+ # Acccessor for <tt>redirect_url</tt> value
155
+ def redirect_url
156
+ @data[:redirect_url]
96
157
  end
97
158
 
98
159
  def success?
99
- status_code == 200
160
+ status_code == 201
100
161
  end
101
162
  end
102
- end
163
+ end
@@ -1,3 +1,3 @@
1
1
  class Veritrans
2
- VERSION = "2.1.2"
2
+ VERSION = "2.4.0"
3
3
  end
data/lib/veritrans.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require 'veritrans/version'
2
- require 'veritrans/core_extensions'
3
2
  require 'veritrans/config'
4
3
  require 'veritrans/client'
5
4
  require 'veritrans/api'
6
5
  require 'veritrans/result'
6
+ require 'veritrans/midtrans_error'
7
7
 
8
8
  if defined?(::Rails)
9
9
  require 'veritrans/events'
@@ -13,18 +13,29 @@ class Veritrans
13
13
  include Veritrans::Client
14
14
  include Veritrans::Api
15
15
 
16
+ autoload :Events, 'veritrans/events'
17
+
16
18
  class << self
17
19
  extend Forwardable
18
20
 
19
21
  def_delegators :instance, :logger, :logger=, :config, :setup, :file_logger, :file_logger=
20
- def_delegators :instance, :request_with_logging, :basic_auth_header, :get, :post, :delete, :make_request
21
- def_delegators :instance, :charge, :cancel, :approve, :status, :capture, :expire
22
- def_delegators :instance, :create_vtlink, :delete_vtlink, :inquiry_points, :create_widget_token, :create_snap_token
22
+ def_delegators :instance, :request_with_logging, :basic_auth_header, :get, :post, :delete, :make_request, :patch
23
+ def_delegators :instance, :charge, :cancel, :approve, :status, :capture, :expire, :refund, :deny
24
+ def_delegators :instance, :create_vtlink, :delete_vtlink, :inquiry_points, :create_widget_token, :create_snap_redirect_url, :create_snap_token, :create_snap_token_string, :create_snap_redirect_url_str, :test_token, :create_card_token
25
+ def_delegators :instance, :link_payment_account, :get_payment_account, :unlink_payment_account, :create_subscription, :get_subscription, :disable_subscription, :enable_subscription, :update_subscription
26
+ def_delegators :instance, :checksum, :events
23
27
 
28
+ # Shortcut for Veritrans::Events
24
29
  def events
30
+ if defined?(ActiveSupport::Deprecation)
31
+ ActiveSupport::Deprecation.warn("`Veritrans.events` is deprecated. Please use `Veritrans::Events`.")
32
+ else
33
+ warn "`Veritrans.events` is deprecated. Please use `Veritrans::Events`."
34
+ end
25
35
  Veritrans::Events if defined?(Veritrans::Events)
26
36
  end
27
37
 
38
+ # More safe json parser
28
39
  def decode_notification_json(input)
29
40
  return Veritrans::Client._json_decode(input)
30
41
  end
@@ -35,6 +46,23 @@ class Veritrans
35
46
 
36
47
  end
37
48
 
49
+ def events
50
+ self.class.events
51
+ end
52
+
53
+ # If you want to use multiple instances of Midtrans in your code (e.g. process payments in different accounts),
54
+ # then you can create instance of Midtrans client
55
+ #
56
+ # mt_client = Midtrans.new(
57
+ # server_key: "My-Different-Key",
58
+ # client_key: "...",
59
+ # api_host: "https://api.sandbox.midtrans.com", # default
60
+ # http_options: { }, # optional
61
+ # logger: Logger.new(STDOUT), # optional
62
+ # file_logger: Logger.new(STDOUT), # optional
63
+ # )
64
+ # mt_client.status("my-different-order-id")
65
+ #
38
66
  def initialize(options = nil)
39
67
  if options && options[:logger]
40
68
  self.logger = options.delete(:logger)
@@ -51,6 +79,22 @@ class Veritrans
51
79
  end
52
80
  end
53
81
 
82
+ # Midtrans configuration. Can be used as DSL and as object
83
+ #
84
+ # Use with block:
85
+ #
86
+ # Midtrans.setup do
87
+ # config.load_yml "./midtrans.yml", Rails.env # load values from config
88
+ # # also can set one by one:
89
+ # config.server_key = "..."
90
+ # config.client_key = "..."
91
+ # config.api_host = "https://api.sandbox.midtrans.com" # (default)
92
+ # end
93
+ #
94
+ # Use as object:
95
+ #
96
+ # Midtrans.config.server_key
97
+ #
54
98
  def config(&block)
55
99
  if block
56
100
  instance_eval(&block)
@@ -58,12 +102,59 @@ class Veritrans
58
102
  @config ||= Veritrans::Config.new
59
103
  end
60
104
  end
61
-
62
105
  alias_method :setup, :config
63
106
 
64
- # General logger
65
- # for rails apps it's === Rails.logger
66
- # for non-rails apps it's logging to stdout
107
+ # Calculate signature_key sha512 checksum for validating HTTP notifications
108
+ #
109
+ # Arguments:
110
+ # [params] A hash, should contain <tt>:order_id, :status_code, :gross_amount</tt>.
111
+ # Additional key <tt>:server_key</tt> is required if Midtrans.config.server_key is not set
112
+ #
113
+ # Example
114
+ #
115
+ # Midtrans.checksum(order_id: "aa11", status_code: "200", gross_amount: 1000, server_key: "my-key")
116
+ # # => "5e00499b23a8932e833238b2f65dd4dd3d10451708c7ec4d93da69e8e7a2bac4f7f97f9f35a986a7d100d7fc58034e12..."
117
+ #
118
+ # Raises:
119
+ # - <tt>ArgumentError</tt> when missing or invalid parameters
120
+ #
121
+ def checksum(params)
122
+ require 'digest' unless defined?(Digest)
123
+
124
+ params_sym = {}
125
+ params.each do |key, value|
126
+ params_sym[key.to_sym] = value
127
+ end
128
+
129
+ if (config.server_key.nil? || config.server_key == "") && params_sym[:server_key].nil?
130
+ raise ArgumentError, "Server key is required. Please set Veritrans.config.server_key or :server_key key"
131
+ end
132
+
133
+ required = [:order_id, :status_code, :gross_amount]
134
+ missing = required - params_sym.keys.select {|k| !!params_sym[k] }
135
+ if missing.size > 0
136
+ raise ArgumentError, "Missing required parameters: #{missing.map(&:inspect).join(", ")}"
137
+ end
138
+
139
+ if params_sym[:gross_amount].is_a?(Numeric)
140
+ params_sym[:gross_amount] = "%0.2f" % params_sym[:gross_amount]
141
+ elsif params_sym[:gross_amount].is_a?(String) && params_sym[:gross_amount] !~ /\d+\.\d\d$/
142
+ raise ArgumentError, %{gross_amount has invalid format, should be a number or string with cents e.g "52.00" (given: #{params_sym[:gross_amount].inspect})}
143
+ end
144
+
145
+ seed = "#{params_sym[:order_id]}#{params_sym[:status_code]}" +
146
+ "#{params_sym[:gross_amount]}#{params_sym[:server_key] || config.server_key}"
147
+
148
+ logger.debug("checksum source: #{seed}")
149
+
150
+ Digest::SHA2.new(512).hexdigest(seed)
151
+ end
152
+
153
+ # General Midtrans logger.
154
+ # For rails apps it will try to use <tt>Rails.logger</tt>, for non-rails apps -- it print to stdout
155
+ #
156
+ # Midtrans.logger.info "Processing payment"
157
+ #
67
158
  def logger
68
159
  return @logger if @logger
69
160
  if defined?(Rails)
@@ -78,6 +169,10 @@ class Veritrans
78
169
  end
79
170
  end
80
171
 
172
+ # Set custom logger
173
+ #
174
+ # Midtrans.logger = Logger.new("./log/midtrans.log")
175
+ #
81
176
  def logger=(value)
82
177
  @logger = value
83
178
  end
@@ -86,10 +181,19 @@ class Veritrans
86
181
  # For rails apps it will write log to RAILS_ROOT/log/veritrans.log
87
182
  def file_logger
88
183
  if !@file_logger
89
- if defined?(Rails) && Rails.root
90
- @file_logger = Logger.new(Rails.root.join("log/veritrans.log").to_s)
91
- else
92
- require 'logger'
184
+ require 'logger'
185
+ begin
186
+ if defined?(Rails) && Rails.root
187
+ require 'fileutils'
188
+ FileUtils.mkdir_p(Rails.root.join("log"))
189
+ @file_logger = Logger.new(Rails.root.join("log/veritrans.log").to_s)
190
+ else
191
+ @file_logger = Logger.new("/dev/null")
192
+ end
193
+ rescue => error
194
+ STDERR.puts "Failed to create Midtrans.file_logger, will use /dev/null"
195
+ STDERR.puts "#{error.class}: #{error.message}"
196
+ STDERR.puts error.backtrace
93
197
  @file_logger = Logger.new("/dev/null")
94
198
  end
95
199
  end
@@ -97,10 +201,15 @@ class Veritrans
97
201
  @file_logger
98
202
  end
99
203
 
204
+ # Set custom file_logger
205
+ #
206
+ # Midtrans.file_logger = Logger.new("./log/midtrans.log")
207
+ #
100
208
  def file_logger=(value)
101
209
  @file_logger = value
102
210
  end
103
211
 
104
212
  end
105
213
 
214
+ # Alias constant for new name of company
106
215
  Midtrans = Veritrans
data/veritrans.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
6
6
  s.version = Veritrans::VERSION
7
7
  s.author = ["Veritrans Dev Team"]
8
8
  s.description= "Veritrans ruby client"
9
- s.email = ["dev@veritrans.co.id"]
9
+ s.email = ["pavel.evstigneev@midtrans.com"]
10
10
  s.homepage = "https://github.com/veritrans/veritrans-ruby"
11
11
  s.summary = %q{Veritrans ruby library}
12
12
  s.license = 'MIT'
@@ -14,15 +14,8 @@ Gem::Specification.new do |s|
14
14
  s.files = `git ls-files`.split("\n")
15
15
  s.test_files = []
16
16
 
17
- s.require_paths = ["lib"]
18
- s.executables = ["veritrans", "midtrans"]
17
+ s.require_paths = ["lib"]
19
18
 
20
19
  s.add_runtime_dependency "excon", "~> 0.20"
21
-
22
- s.add_development_dependency "rspec", '~> 3.4'
23
- s.add_development_dependency "rails", ">= 4.0", "< 6"
24
- s.add_development_dependency 'webmock', '>= 1.20'
25
- s.add_development_dependency 'vcr', '~> 3.0'
26
- s.add_development_dependency "poltergeist", '~> 1.8'
27
20
  end
28
21