veritrans 2.1.2 → 2.4.0

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