veritrans 2.0.4 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +11 -3
- data/CHANGELOG.md +15 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +136 -97
- data/Procfile +1 -0
- data/README.md +12 -5
- data/Rakefile +7 -5
- data/api_reference.md +21 -1
- data/example/README.md +8 -0
- data/example/config.ru +6 -0
- data/example/index.erb +104 -9
- data/example/localization.erb +248 -0
- data/example/points.erb +187 -0
- data/example/recurring.erb +201 -0
- data/example/response.erb +10 -1
- data/example/sinatra.rb +120 -8
- data/example/style.css +83 -2
- data/example/veritrans.yml +0 -1
- data/example/widget.erb +52 -0
- data/lib/generators/templates/assets/credit_card_form.js +3 -2
- data/lib/generators/templates/payments_controller.rb +5 -0
- data/lib/generators/templates/veritrans.rb +20 -17
- data/lib/generators/veritrans/install_generator.rb +1 -1
- data/lib/generators/veritrans/payment_form_generator.rb +1 -1
- data/lib/veritrans.rb +73 -44
- data/lib/veritrans/api.rb +39 -5
- data/lib/veritrans/cli.rb +1 -1
- data/lib/veritrans/client.rb +15 -10
- data/lib/veritrans/config.rb +11 -4
- data/lib/veritrans/events.rb +4 -4
- data/lib/veritrans/result.rb +28 -7
- data/lib/veritrans/version.rb +2 -2
- data/spec/cli_spec.rb +1 -0
- data/spec/rails_plugin_spec.rb +72 -27
- data/spec/spec_helper.rb +1 -0
- data/spec/veritrans_client_spec.rb +57 -3
- data/spec/veritrans_config_spec.rb +1 -1
- data/spec/veritrans_events_spec.rb +2 -0
- data/spec/veritrans_snap_spec.rb +39 -0
- data/testing_webhooks.md +0 -2
- data/veritrans.gemspec +5 -5
- metadata +29 -15
data/lib/veritrans/config.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'excon'
|
3
3
|
|
4
|
-
|
4
|
+
class Veritrans
|
5
5
|
|
6
|
-
|
7
|
-
extend self
|
6
|
+
class Config
|
8
7
|
|
9
|
-
|
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
|
data/lib/veritrans/events.rb
CHANGED
@@ -15,14 +15,14 @@
|
|
15
15
|
# All possible events:
|
16
16
|
#
|
17
17
|
# * payment.success == ['authorize', 'capture', 'settlement']
|
18
|
-
# * payment.failed == ['deny', '
|
19
|
-
# * payment.challenge # when payment.
|
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.
|
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
|
-
|
35
|
+
class Veritrans
|
36
36
|
class Events
|
37
37
|
|
38
38
|
# This is rack application
|
data/lib/veritrans/result.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
|
-
|
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,
|
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
|
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
|
-
@
|
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/
|
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
|
-
"
|
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
|
data/lib/veritrans/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "2.0
|
1
|
+
class Veritrans
|
2
|
+
VERSION = "2.1.0"
|
3
3
|
end
|
data/spec/cli_spec.rb
CHANGED
data/spec/rails_plugin_spec.rb
CHANGED
@@ -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 = "
|
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.
|
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://
|
119
|
+
Capybara.app_host = "http://127.0.0.1:#{@rails_port}"
|
120
120
|
|
121
121
|
#puts "RAILS_DIR: #{@app_abs_path}"
|
122
122
|
|
123
|
-
|
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
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
182
|
-
page.
|
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
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
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
|
@@ -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
|