veritrans 2.0.4 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +11 -3
  4. data/CHANGELOG.md +15 -0
  5. data/Gemfile +10 -0
  6. data/Gemfile.lock +136 -97
  7. data/Procfile +1 -0
  8. data/README.md +12 -5
  9. data/Rakefile +7 -5
  10. data/api_reference.md +21 -1
  11. data/example/README.md +8 -0
  12. data/example/config.ru +6 -0
  13. data/example/index.erb +104 -9
  14. data/example/localization.erb +248 -0
  15. data/example/points.erb +187 -0
  16. data/example/recurring.erb +201 -0
  17. data/example/response.erb +10 -1
  18. data/example/sinatra.rb +120 -8
  19. data/example/style.css +83 -2
  20. data/example/veritrans.yml +0 -1
  21. data/example/widget.erb +52 -0
  22. data/lib/generators/templates/assets/credit_card_form.js +3 -2
  23. data/lib/generators/templates/payments_controller.rb +5 -0
  24. data/lib/generators/templates/veritrans.rb +20 -17
  25. data/lib/generators/veritrans/install_generator.rb +1 -1
  26. data/lib/generators/veritrans/payment_form_generator.rb +1 -1
  27. data/lib/veritrans.rb +73 -44
  28. data/lib/veritrans/api.rb +39 -5
  29. data/lib/veritrans/cli.rb +1 -1
  30. data/lib/veritrans/client.rb +15 -10
  31. data/lib/veritrans/config.rb +11 -4
  32. data/lib/veritrans/events.rb +4 -4
  33. data/lib/veritrans/result.rb +28 -7
  34. data/lib/veritrans/version.rb +2 -2
  35. data/spec/cli_spec.rb +1 -0
  36. data/spec/rails_plugin_spec.rb +72 -27
  37. data/spec/spec_helper.rb +1 -0
  38. data/spec/veritrans_client_spec.rb +57 -3
  39. data/spec/veritrans_config_spec.rb +1 -1
  40. data/spec/veritrans_events_spec.rb +2 -0
  41. data/spec/veritrans_snap_spec.rb +39 -0
  42. data/testing_webhooks.md +0 -2
  43. data/veritrans.gemspec +5 -5
  44. metadata +29 -15
@@ -1,12 +1,14 @@
1
1
  require 'yaml'
2
2
  require 'excon'
3
3
 
4
- module Veritrans
4
+ class Veritrans
5
5
 
6
- module Config
7
- extend self
6
+ class Config
8
7
 
9
- @api_host = "https://api.sandbox.veritrans.co.id"
8
+ def initialize(options = nil)
9
+ @api_host = "https://api.sandbox.veritrans.co.id"
10
+ apply(options) if options
11
+ end
10
12
 
11
13
  ##
12
14
  # Merhcant's Client key, used to make getToken request. (only for VT-Direct)
@@ -135,8 +137,13 @@ module Veritrans
135
137
 
136
138
  private
137
139
 
140
+ AVAILABLE_KEYS = [:server_key, :client_key, :api_host, :http_options]
141
+
138
142
  def apply(hash)
139
143
  hash.each do |key, value|
144
+ unless AVAILABLE_KEYS.include?(key.to_s.to_sym)
145
+ raise ArgumentError, "Unknown option #{key.inspect}, available keys: #{AVAILABLE_KEYS.map(&:inspect).join(", ")}"
146
+ end
140
147
  send(:"#{key}=", value)
141
148
  end
142
149
  end
@@ -15,14 +15,14 @@
15
15
  # All possible events:
16
16
  #
17
17
  # * payment.success == ['authorize', 'capture', 'settlement']
18
- # * payment.failed == ['deny', 'canel', 'expire']
19
- # * payment.challenge # when payment.froud_status == 'challenge'
18
+ # * payment.failed == ['deny', 'cancel', 'expire']
19
+ # * payment.challenge # when payment.fraud_status == 'challenge'
20
20
  #
21
21
  # * payment.authorize
22
22
  # * payment.capture
23
23
  # * payment.settlement
24
24
  # * payment.deny
25
- # * payment.canel
25
+ # * payment.cancel
26
26
  # * payment.expire
27
27
  #
28
28
  # * error
@@ -32,7 +32,7 @@
32
32
  # run Rack::URLMap.new("/" => MyApp.new, "/payment_events" => Veritrans::Events.new)
33
33
  #
34
34
 
35
- module Veritrans
35
+ class Veritrans
36
36
  class Events
37
37
 
38
38
  # This is rack application
@@ -1,15 +1,18 @@
1
- module Veritrans
1
+ class Veritrans
2
2
  class Result
3
3
  attr_reader :data
4
4
  attr_reader :status
5
5
  attr_reader :response
6
+ attr_reader :request_options
7
+ attr_reader :time
8
+ attr_reader :url
6
9
 
7
- def initialize(response, url, options, time)
10
+ def initialize(response, url, request_options, time)
8
11
  begin
9
12
  if url =~ %r{/v2/.+/transcript$}
10
13
  @data = {}
11
14
  else
12
- @data = Veritrans._json_decode(response.body)
15
+ @data = Veritrans::Client._json_decode(response.body)
13
16
 
14
17
  # Failback for Hash#symbolize_keys
15
18
  @data.keys.each do |key|
@@ -18,7 +21,7 @@ module Veritrans
18
21
  end
19
22
 
20
23
  rescue => e
21
- Veritrans.logger.info "Error parsing papi response #{e.message}"
24
+ Veritrans.logger.info "Error parsing Veritrans response #{e.message}"
22
25
  Veritrans.logger.info e.backtrace.join("\n")
23
26
  @data = {}
24
27
  end
@@ -27,7 +30,7 @@ module Veritrans
27
30
  @status = response.status
28
31
  @response = response
29
32
  @url = url
30
- @options = options
33
+ @request_options = request_options
31
34
  end
32
35
 
33
36
  def success?
@@ -39,7 +42,7 @@ module Veritrans
39
42
  @data[:status_code] == '201'
40
43
  end
41
44
 
42
- # Docs http://docs.veritrans.co.id/sandbox/status_code.html
45
+ # Docs http://docs.veritrans.co.id/en/api/status_code.html
43
46
  def status_code
44
47
  @data[:status_code].to_i
45
48
  end
@@ -52,6 +55,10 @@ module Veritrans
52
55
  @data[:redirect_url]
53
56
  end
54
57
 
58
+ def transaction_id
59
+ @data[:transaction_id]
60
+ end
61
+
55
62
  def messages
56
63
  if @data[:message].present?
57
64
  @data[:message].chomp(']').sub(/^\[/, '').split(',').map(&:strip)
@@ -75,7 +82,21 @@ module Veritrans
75
82
  def inspect
76
83
  time_ms = (@time * 1000).round
77
84
  data = @data.inspect.gsub(/:([^\s]+)=>/, "\\1: ")
78
- "#<Veritrans::Result:#{object_id} ^^ http_status: #{@status} time: #{time_ms}ms ^^ data: #{data}>"
85
+ "#<#{self.class.to_s}:#{object_id} ^^ status: #{@status} time: #{time_ms}ms ^^ data: #{data}>"
86
+ end
87
+ end
88
+
89
+ class SnapResult < Result
90
+ def status_code
91
+ @response.status.to_i
92
+ end
93
+
94
+ def token_id
95
+ @data[:token_id]
96
+ end
97
+
98
+ def success?
99
+ status_code == 200
79
100
  end
80
101
  end
81
102
  end
@@ -1,3 +1,3 @@
1
- module Veritrans
2
- VERSION = "2.0.4"
1
+ class Veritrans
2
+ VERSION = "2.1.0"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  describe Veritrans do
2
+ include ActiveSupport::Testing::Stream
2
3
 
3
4
  before do
4
5
  stub_const("CONFIG", {})
@@ -6,11 +6,11 @@ require 'socket'
6
6
  describe "Rails plugin", vcr: false do
7
7
  include Capybara::DSL
8
8
 
9
- MAIN_RAILS_VER = "4.1.9"
9
+ MAIN_RAILS_VER = "5.0.0.1"
10
10
  APP_DIR = "plugin_test"
11
11
  PLUGIN_DIR = File.expand_path("..", File.dirname(__FILE__))
12
12
 
13
- RAILS_VERSIONS = ["4.0.13", "4.1.14", "4.2.5"]
13
+ RAILS_VERSIONS = ["4.0.13", "4.1.16", "4.2.7.1", "5.0.0.1"]
14
14
 
15
15
  before :all do
16
16
  FileUtils.mkdir_p("#{PLUGIN_DIR}/tmp")
@@ -70,7 +70,7 @@ describe "Rails plugin", vcr: false do
70
70
  end
71
71
 
72
72
  def generate_rails_app(rails_version)
73
- gen = "rails _#{rails_version}_ new #{APP_DIR} -B -G --skip-spring -d sqlite3 --skip-turbolinks --skip-test-unit --no-rc"
73
+ gen = "rails _#{rails_version}_ new #{APP_DIR} -B -G --skip-spring -d sqlite3 --skip-turbolinks --skip-test-unit --skip-action-cable --no-rc --skip-puma --skip-listen"
74
74
  run_cmd(gen)
75
75
 
76
76
  gemfile_content = "
@@ -116,19 +116,27 @@ development:
116
116
  end
117
117
  end
118
118
 
119
- Capybara.app_host = "http://localhost:#{@rails_port}"
119
+ Capybara.app_host = "http://127.0.0.1:#{@rails_port}"
120
120
 
121
121
  #puts "RAILS_DIR: #{@app_abs_path}"
122
122
 
123
- while true
123
+ failed = 0
124
+ while failed < 10
124
125
  begin
125
126
  run_cmd("curl #{Capybara.app_host}/payments/new > /dev/null")
126
127
  break
127
128
  rescue Object => error
129
+ failed += 1
128
130
  p error
129
131
  puts "Retry"
132
+ sleep 0.3
130
133
  end
131
134
  end
135
+
136
+ if failed == 10
137
+ puts `tail -100 #{@app_abs_path}/log/development.log`
138
+ raise Exception, "can not start rails server"
139
+ end
132
140
  end
133
141
 
134
142
  def stop_rails_app
@@ -152,39 +160,72 @@ development:
152
160
  FileUtils.remove_entry_secure(@rails_dir) if @rails_dir
153
161
  end
154
162
 
155
- RAILS_VERSIONS.each do |rails_version|
163
+ def submit_payment_form(card_number)
164
+ # CREATE PAYMENT 1
165
+ visit "/payments/new"
166
+
167
+ fill_in 'credit_card_number', with: card_number
168
+
169
+ click_button "Pay via VT-Direct"
170
+ puts "Clicked Pay"
171
+
172
+ # Waiting for get token request in javascript...
173
+ Timeout.timeout(60.seconds) do
174
+ loop do
175
+ #puts "Path: #{current_path}"
176
+ break if current_path != "/payments/new"
177
+ sleep 1
178
+ end
179
+ end
180
+
181
+ Timeout.timeout(10.seconds) do
182
+ loop do
183
+ break if page.body =~ /<body>/
184
+ sleep 0.1
185
+ end
186
+ end
187
+ end
188
+
189
+ RAILS_VERSIONS.each_with_index do |rails_version, spec_index|
190
+ next if rails_version.start_with?("5") && RUBY_VERSION < "2.2.2"
191
+
156
192
  it "should tests plugin (Rails #{rails_version})" do
157
193
  # PREPARE APP
158
194
  install_rails_in_tmp(rails_version)
159
195
  run_rails_app
160
196
 
161
- # CREATE PAYMENT 1
162
- visit "/payments/new"
163
-
164
- click_button "Pay via VT-Direct"
197
+ card_numbers = [
198
+ "5481 1611 1111 1081",
199
+ "5410 1111 1111 1116",
200
+ "4011 1111 1111 1112",
201
+ "4411 1111 1111 1118",
202
+ "4811 1111 1111 1114",
203
+ "3528 6647 7942 9687",
204
+ "3528 2033 2456 4357"
205
+ ]
206
+ spec_index = (spec_index + RUBY_VERSION.gsub(/[^\d]/, '').to_i) % card_numbers.size
207
+ card_number = card_numbers[spec_index]
165
208
 
166
- # Waiting for get token request in javascript...
167
- Timeout.timeout(30.seconds) do
168
- loop do
169
- break if current_path != "/payments/new"
170
- sleep 0.1
171
- end
172
- end
209
+ submit_payment_form(card_number)
173
210
 
174
- Timeout.timeout(10.seconds) do
175
- loop do
176
- break if page.body =~ /<body>/
177
- sleep 0.1
178
- end
211
+ if page.body =~ /too many transactions/
212
+ puts "!!!!"
213
+ puts "Merchant has sent too many transactions to the same card number"
214
+ puts "!!!!"
215
+ #page.should have_content("Merchant has sent too many transactions to the same card number")
216
+ puts "Wait 10 seconds and retry"
217
+ sleep 10
218
+ submit_payment_form(card_number)
179
219
  end
180
220
 
181
- if page.body =~ /too many transactions/
182
- page.should have_content("Merchant has sent too many transactions to the same card number")
183
- else
184
- page.should have_content("Success, Credit Card transaction is successful")
221
+ if page.body !~ /transaction is successful/
222
+ puts page.body
185
223
  end
224
+ page.should have_content("Success, Credit Card transaction is successful")
186
225
 
187
- created_order_id = ActiveSupport::JSON.decode(find("pre").text)["order_id"]
226
+ order_info = ActiveSupport::JSON.decode(find("pre").text)
227
+ puts "Order Info: #{order_info}"
228
+ created_order_id = order_info["order_id"]
188
229
  #Capybara::Screenshot.screenshot_and_open_image
189
230
 
190
231
  # CREATE PAYMENT 2
@@ -208,6 +249,10 @@ development:
208
249
  Veritrans::CLI.test_webhook(["#{Capybara.app_host}/payments/receive_webhook"])
209
250
  end
210
251
 
252
+ if result2 !~ /status: 200/
253
+ puts `tail -40 #{@app_abs_path}/log/development.log`
254
+ end
255
+
211
256
  result2.should =~ /status: 200/
212
257
  result2.should =~ /body: ok/
213
258
  end
@@ -12,6 +12,7 @@ require 'vcr'
12
12
 
13
13
  require 'capybara/rspec'
14
14
  require 'capybara/poltergeist'
15
+ require 'active_support/testing/stream'
15
16
 
16
17
  Capybara.register_driver :poltergeist do |app|
17
18
  Capybara::Poltergeist::Driver.new(app,
@@ -1,4 +1,4 @@
1
- describe Veritrans do
1
+ describe Veritrans::Client do
2
2
 
3
3
  before do
4
4
  hide_const("Rails")
@@ -21,11 +21,11 @@ describe Veritrans do
21
21
 
22
22
  result = Veritrans.request_with_logging(:get, Veritrans.config.api_host + "/ping", {})
23
23
 
24
- api_request.headers["Host"].should == "api.sandbox.veritrans.co.id"
24
+ api_request.headers["Host"].should == "api.sandbox.veritrans.co.id:443"
25
25
  end
26
26
 
27
27
  it "should use Veritrans.http_options to attach hedaers", vcr: false do
28
- Veritrans::Config.stub(:http_options) do
28
+ Veritrans.config.stub(:http_options) do
29
29
  { headers: { "X-Rspec" => "ok" } }
30
30
  end
31
31
 
@@ -40,6 +40,47 @@ describe Veritrans do
40
40
  api_request.headers["X-Rspec"].should == "ok"
41
41
  end
42
42
 
43
+ it "should be able to create other instance of client" do
44
+ #Veritrans.logger = Logger.new(STDOUT)
45
+
46
+ VCR.configure do |c|
47
+ c.allow_http_connections_when_no_cassette = true
48
+ end
49
+
50
+ other_client = Veritrans.new(
51
+ server_key: "69b61a1b-b0b1-450b-a697-37109dbbecb0",
52
+ logger: Logger.new("/dev/null")
53
+ ) # M000937
54
+
55
+ result = Veritrans.charge(
56
+ payment_type: "mandiri_clickpay",
57
+ transaction_details: {
58
+ gross_amount: 10_000,
59
+ order_id: Time.now.to_s
60
+ },
61
+ mandiri_clickpay: {
62
+ card_number: "4111 1111 1111 1111".gsub(/\s/, ''),
63
+ input3: "%05d" % (rand * 100000).to_i,
64
+ input2: 10000,
65
+ input1: "1" * 10,
66
+ token: "000000"
67
+ },
68
+ )
69
+
70
+ #p result.request_options
71
+
72
+ other_result = other_client.status(result.transaction_id)
73
+
74
+ other_result.status_code.should == 404
75
+ other_result.status_message.should == "The requested resource is not found"
76
+
77
+ #p other_result.request_options
78
+
79
+ VCR.configure do |c|
80
+ c.allow_http_connections_when_no_cassette = true
81
+ end
82
+ end
83
+
43
84
  it "should send charge vt-web request" do
44
85
  VCR.use_cassette('charge_vtweb') do
45
86
  result = Veritrans.charge('vtweb', transaction: { order_id: Time.now.to_s, gross_amount: 100_000 } )
@@ -127,4 +168,17 @@ describe Veritrans do
127
168
  result.status_message.should == "The requested resource is not found"
128
169
  end
129
170
  end
171
+
172
+ it "should handle network exceptions", vcr: false do
173
+ Excon::Connection.any_instance.stub(:send) do
174
+ raise Excon::Errors::SocketError, Excon::Errors::Error.new("testing exception")
175
+ end
176
+
177
+ result = Veritrans.expire("not-exists")
178
+ result.status.should == "500"
179
+ result.data.should == {
180
+ status_code: "500",
181
+ status_message: "Internal server error, no response from backend. Try again later"
182
+ }
183
+ end
130
184
  end
@@ -6,7 +6,7 @@ describe Veritrans::Config do
6
6
 
7
7
  it "should set Veritras as self inside config block" do
8
8
  Veritrans.config do
9
- self.should == Veritrans
9
+ self.should == Veritrans.instance
10
10
  end
11
11
  end
12
12
 
@@ -1,4 +1,6 @@
1
1
  describe Veritrans do
2
+ include ActiveSupport::Testing::Stream
3
+
2
4
  before do
3
5
  stub_const("CONFIG", {})
4
6
  Veritrans.logger = Logger.new(STDOUT)
@@ -0,0 +1,39 @@
1
+ describe Veritrans::Config do
2
+
3
+ before do
4
+ hide_const("Rails")
5
+ Veritrans.logger = Logger.new("/dev/null")
6
+ Veritrans.setup do
7
+ config.load_config "./spec/configs/real_key.yml"
8
+ end
9
+
10
+ VCR.configure do |c|
11
+ c.allow_http_connections_when_no_cassette = true
12
+ end
13
+ end
14
+
15
+ after do
16
+ VCR.configure do |c|
17
+ c.allow_http_connections_when_no_cassette = false
18
+ end
19
+ end
20
+
21
+ def generate_order_id
22
+ "testing-#{rand.round(4)}-#{Time.now.to_i}"
23
+ end
24
+
25
+ it "should create widget token" do
26
+ response = Veritrans.create_snap_token(
27
+ transaction_details: {
28
+ order_id: generate_order_id,
29
+ gross_amount: 30_000
30
+ }
31
+ )
32
+
33
+ response.should be_a(Veritrans::SnapResult)
34
+ response.success?.should be_truthy
35
+ response.token_id.should be_present
36
+ response.token_id.should == response.data[:token_id]
37
+ response.inspect.should =~ /#<Veritrans::SnapResult:\d+ \^\^ status: 200 time: \d+ms \^\^ data: \{token_id: "[\da-f\-]+"\}>/
38
+ end
39
+ end