veritrans 2.0.4 → 2.1.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 (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