tbk 0.9.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.8.7
5
+ - jruby-18mode # JRuby in 1.8 mode
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+ - rbx-18mode
8
+ - rbx-19mode
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --no-private
2
+ --hide-void-return
3
+
4
+ --markup=markdown
5
+ --markup-provider=redcarpet
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tbk.gemspec
4
+ gemspec
5
+
6
+ platforms :jruby do
7
+ gem "jruby-openssl"
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Seba Gamboa
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,101 @@
1
+ Pure Ruby implementation of Transbank's Webpay KCC 6.0 protocol
2
+
3
+ ### Disclaimer
4
+
5
+ This library is not developed, supported nor endorsed in any way by Transbank S.A.
6
+ and is the result of reverse engineering Transbank's Integration Kit (aka. KCC)
7
+ for interoperability purposes as allowed by
8
+ [Chilean Law 20.435 Article 71 Ñ Section b](http://www.leychile.cl/Navegar?idNorma=1012827)
9
+
10
+ ### Usage
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'tbk'
16
+ ```
17
+
18
+ To start a payment from your application
19
+
20
+ ```ruby
21
+ class WebpayController < ApplicationController
22
+
23
+ # ...
24
+
25
+ # Start a payment
26
+ def pay
27
+ # Initialyze you commerce
28
+ @commerce = TBK::Commerce.new({
29
+ id: YOUR_COMMERCE_ID,
30
+ key: YOUR_RSA_KEY
31
+ })
32
+
33
+ # Setup the payment
34
+ @payment = TBK::Webpay::Payment.new({
35
+ commerce: @commerce,
36
+ amount: ORDER_AMOUNT,
37
+ order_id: ORDER_ID,
38
+ success_url: webpay_success_url,
39
+ # Webpay can only access the HTTP protocol to a direct IP address (keep that in mind)
40
+ confirmation_url: webpay_confirmation_url(host: SERVER_IP_ADDRESS, protocol: 'http'),
41
+
42
+ # Optionaly supply:
43
+ session_id: SOME_SESSION_VALUE,
44
+ failure_url: webpay_failure_url # success_url is used by default
45
+ })
46
+
47
+ # Redirect the user to Webpay
48
+ redirect_to @payment.redirect_url
49
+ end
50
+
51
+ # ...
52
+ end
53
+ ```
54
+
55
+ And to process a payment
56
+
57
+ ```ruby
58
+ class WebpayController < ApplicationController
59
+
60
+ # ...
61
+
62
+ # Confirmation callback executed from Webpay servers
63
+ def confirmation
64
+ # Initialyze you commerce
65
+ @commerce = TBK::Commerce.new({
66
+ id: YOUR_COMMERCE_ID,
67
+ key: YOUR_RSA_KEY
68
+ })
69
+
70
+ # Read the confirmation data from the request
71
+ @confirmation = TBK::Webpay::Confirmation.new({
72
+ commerce: @commerce,
73
+ post: request.raw_post
74
+ })
75
+
76
+ if # confirmation is invalid for some reason (wrong order_id or amount, double payment, etc...)
77
+ render text: @confirmation.reject
78
+ return # reject and stop execution
79
+ end
80
+
81
+ if @confirmation.success?
82
+ # EXITO!
83
+ # perform everything you have to do here.
84
+ end
85
+
86
+ # Acknowledge payment
87
+ render text: @confirmation.acknowledge
88
+ end
89
+
90
+ # ...
91
+
92
+ end
93
+ ```
94
+
95
+ ### Contributing
96
+
97
+ 1. Fork it
98
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
99
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
100
+ 4. Push to the branch (`git push origin my-new-feature`)
101
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,53 @@
1
+
2
+ module TBK
3
+ # Represents a commerce registered with Transbank
4
+ class Commerce
5
+
6
+ # The registered commerce id
7
+ attr_accessor :id
8
+
9
+ # The commerce secret RSA key
10
+ attr_accessor :key
11
+
12
+ # Initialyzes a new commerce
13
+ # @param [Hash] attributes The commerce attributes
14
+ # @option attributes [Integer] :id The commerce ID
15
+ # @option attributes [String|OpenSSL::PKey::RSA] :key The commerce RSA private key
16
+ # @option attributes [Boolean] :test flag to set commerce in test mode
17
+ def initialize(attributes)
18
+ @test = attributes[:test]
19
+
20
+ self.id = attributes[:id]
21
+ raise TBK::CommerceError, "Missing commerce id" if self.id.nil?
22
+
23
+ self.key = case attributes[:key]
24
+ when String
25
+ OpenSSL::PKey::RSA.new(attributes[:key])
26
+ when OpenSSL::PKey::RSA.new
27
+ attributes[:key]
28
+ when nil
29
+ TEST_COMMERCE_KEY if self.test?
30
+ end
31
+
32
+ raise TBK::CommerceError, "Missing commerce key" if self.key.nil?
33
+ raise TBK::CommerceError, "Commerce key must be a RSA private key" unless self.key.private?
34
+ end
35
+
36
+ # @return [Boolean] wether or not the commerce is in test mode
37
+ def test?
38
+ @test || false
39
+ end
40
+
41
+ # @return [Boolean] wether or not the commerce is in production mode
42
+ def production?
43
+ !self.test?
44
+ end
45
+
46
+ # @return [Integer] RSA key bytes
47
+ def key_bytes
48
+ self.key.n.num_bytes
49
+ end
50
+
51
+ TEST_COMMERCE_KEY = TBK.parse_key('test_commerce')
52
+ end
53
+ end
data/lib/tbk/errors.rb ADDED
@@ -0,0 +1,16 @@
1
+ module TBK
2
+ class Error < StandardError
3
+ attr_reader :origin
4
+ def initialize(msg, origin=nil)
5
+ super(msg)
6
+ @origin = origin
7
+ end
8
+ end
9
+
10
+ class CommerceError < Error; end
11
+
12
+ module Webpay
13
+ class PaymentError < Error; end
14
+ class EncryptionError < Error; end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEowIBAAKCAQEAn3HzPC1ZBzCO3edUCf/XJiwj3bzJpjjTi/zBO9O+DDzZCaMp
3
+ 14aspxQryvJhv8644E19Q+NHfxtz1cxd2wnSYKvay1gJx30ZlTOAkzUj4QMimR16
4
+ vomLlQ3T2MAz1znt/PVPVU7T/JOG9R+EbiHNVKa/hUjwJEFVXLQNME97nHoLjb3v
5
+ V5yV2aVhmox7b54n6F3UVPHvCsHKbJpXpE+vnLpVmdETbNpFVrDygXyG+mnEvyiO
6
+ BLIwEY3XTMrgXvS069groLi5Gg8C5LDaYOWjE9084T4fiWGrHhn2781R1rykunTu
7
+ 77wiWPuQHMS0+YC7mhnsk8Z/ilD+aWz/vhsgHwIDAQABAoIBAQCM+Nrt4cpNKQmn
8
+ +Ne8348CGRS9ACXp6WRg6OCQXO4zM7lRZAminVgZgSQXE6aJR+T9rIWMeG7GWydX
9
+ aJGzEEQJZOjV0MkUr+7mk9qiTOGkGHmGlyHnRQU8jDU59vXe3UEl3l5+NmwHbQht
10
+ waf9F7XLmoLK/WoVJA6tICRpCl1oQrpziqN+gjdmMpz9i8I1sMFE7+Y7xf+7S2u7
11
+ c1MRPUWqgdS9yViQVh3vZi25m5CyKRVnOB0hpNuZ7nrJymtADYSWt9wV2W1fX+MX
12
+ UUoYfxyQQvWryHhGdedU7GGAnoEdblUcDkBuAaFmsm1P8K4HQZLWP4v6pYlW2JLa
13
+ Zoaerb3BAoGBANCRevl0CLB0HBU7sCs0eN9fTkIEsh3OVIxPSBqDnKsynJrIWovK
14
+ cs37Vb6phzdQO3ADoFJvR9ck8+v6Cv0KR8IOFl9wfC4ZoxkKBBeq94ZLN+YhE2PW
15
+ KiRFybqcgCtzxKS3MyWgpIcT9xFtHVjlorZ8Jk51fgLZbGzamtLhderVAoGBAMO0
16
+ mIiiV4l2vXzu4tFfkpu/GOx/D9/vAic3X9FOky09BNCyuMXMQgI8e3wWsGEZghls
17
+ Vg9KDV5EPxAmpumcdPFK2IMACaH41ac7vys3ZD8kMK0INQkuDAcG4YsxMaTwEPo0
18
+ p1i3zwwEWwknw1yJkOyozz0EcIzS9NrZZEjnBHEjAoGAQ81XdeqzvHEyg/CQd6sq
19
+ NCtubGXMZYYi1C4d2Yi5kKn2YRcK4HDi23V+TWodK+0oNWToZIQKjbVUmn0Bv3rt
20
+ EvezbDlMFUx+SfCIng0VRJIFTQmpnQYNUxdg2gpwXC/ZWFa6CNxtQABMjFy1cqXM
21
+ PJild1IYseJurgBu3mkvBTUCgYBqA/T1X2woLUis2wPIBAv5juXDh3lkB6eU8uxX
22
+ CEe2I+3t2EM781B2wajrKadWkmjluMhN9AGV5UZ8S1P0DStUYwUywdx1/8RNmZIP
23
+ qSwHAGXV9jI0zNr7G4Em0/leriWkRM26w6fHjLx8EyxDfsohSbkqBrOptcWqoEUx
24
+ MOQ5HQKBgAS4sbddOas2MapuhKU2surEb3Kz3RCIpta4bXgTQMt9wawcZSSpvnfT
25
+ zs5sehYvBFszL3MV98Uc50HXMf7gykRCmPRmB9S+f+kiVRvQDHfc9nRNg2XgcotU
26
+ KAE16PQM8GihQ0C+EcXHouyud5CRJGfyurokRlH/jY3BiRAG5c+6
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,14 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxKKjroxE7X44TQovh9A9
3
+ ZpntP7LrdoyFsnJbDKjOOCoiid92FydN5qemyQCeXhsc7QHUXwGdth22fB8xJr3a
4
+ MZBEUJ+BKFrL+W6yE5V+F5Bj0Uq3lL0QMAIftGhLpgqw0ZMtU89kyd9Q4Rclq4r8
5
+ p2m/ZD7Pn5EmTOFSeyoWTMZQDl7OEoCKh/cZH5NJdWL08lCI+sGLOghRmFzkve4h
6
+ F9JCwKA7NYG7j3BWh39Oj2NIXEY/TO1Y3Y2WfNv9nvTpr46SpFlyp0KOhSiqgvXX
7
+ DgeXlebyqS82ch2DzOV9fjDAw7t71WXJBAev8Gd6HXwIXE/JP6AnLCa2Y+b6Wv8K
8
+ GWBCMIBXWL0m7WHeCaJ9Hx2yXZmHJh8FgeKffFKCwn3X90JiMocOSGsOE+Sfo85S
9
+ h/39Vc7vZS3i7kJDDoz9ab9/vFy30RuJf4p8Erh7kWtERVoG6/EhR+j4N3mgIOBZ
10
+ SHfzDAoOnqP5l7t2RXYcEbRLVN6o+XgUtalX33EJxJRsXoz9a6PxYlesIwPbKteD
11
+ BZ/xyJDwTc2gU2YzSH8G9anKrcvITBDULSAuxQUkYOiLbkb7vSKWDYKe0do6ibO3
12
+ RY/KXI63Q7bGKYaI2aa/8GnqVJ2G1U2s59NpqX0aaWjn59gsA8trA0YKOZP4xJIh
13
+ CvLM94G4V7lxe2IHKPqLscMCAwEAAQ==
14
+ -----END PUBLIC KEY-----
@@ -0,0 +1,14 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtKe3HHWwRcizAfkbS92V
3
+ fQr8cUb94TRjQPzNTqBduvvj65AD5J98Cn1htE3NzOz+PjPRcnfVe53V4f3+YlIb
4
+ 6nnxyeuYLByiwoPkCmpOFBxNp04/Yh3dxN4xgOANXA37rNbDeO4WIEMG6zbdQMNJ
5
+ 7RqQUlJSmui8gt3YxtqWBhBVW79qDCYVzxFrv3SH7pRuYEr+cxDvzRylxnJgr6ee
6
+ N7gmjoSMqF16f9aGdQ12obzV0A35BqpN6pRFoS/NvICbEeedS9g5gyUHf54a+juB
7
+ OV2HH5VJsCCgcb7I7Sio/xXTyP+QjIGJfpukkE8F+ohwRiChZ9jMXofPtuZYZiFQ
8
+ /gX08s5Qdpaph65UINP7crYbzpVJdrT2J0etyMcZbEanEkoX8YakLEBpPhyyR7mC
9
+ 73fWd9sTuBEkG6kzCuG2JAyo6V8eyISnlKDEVd+/6G/Zpb5cUdBCERTYz5gvNoZN
10
+ zkuq4isiXh5MOLGs91H8ermuhdQe/lqvXf8Op/EYrAuxcdrZK0orI4LbPdUrC0Jc
11
+ Fl02qgXRrSpXo72anOlFc9P0blD4CMevW2+1wvIPA0DaJPsTnwBWOUqcfa7GAFH5
12
+ KGs3zCiZ5YTLDlnaps8koSssTVRi7LVT8HhiC5mjBklxmZjBv6ckgQeFWgp18kuU
13
+ ve5Elj5HSV7x2PCz8RKB4XcCAwEAAQ==
14
+ -----END PUBLIC KEY-----
data/lib/tbk/keys.rb ADDED
@@ -0,0 +1,5 @@
1
+ module TBK
2
+ def self.parse_key(name)
3
+ OpenSSL::PKey::RSA.new( File.read(File.expand_path("../keys/#{name}.pem",__FILE__)) )
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ module TBK
2
+ module VERSION
3
+ GEM = "0.9.0"
4
+ KCC = "6.0"
5
+ WEBSITE = "http://sagmor.com/tbk/"
6
+ end
7
+ end
@@ -0,0 +1,111 @@
1
+ # encoding: UTF-8
2
+ module TBK
3
+ module Webpay
4
+ class Confirmation
5
+ RESPONSE_CODES = {
6
+ '0' => 'Transacción aprobada.',
7
+ '-1' => 'Rechazo de tx. en B24, No autorizada',
8
+ '-2' => 'Transacción debe reintentarse.',
9
+ '-3' => 'Error en tx.',
10
+ '-4' => 'Rechazo de tx. En B24, No autorizada',
11
+ '-5' => 'Rechazo por error de tasa.',
12
+ '-6' => 'Excede cupo máximo mensual.',
13
+ '-7' => 'Excede límite diario por transacción.',
14
+ '-8' => 'Rubro no autorizado.'
15
+ }
16
+
17
+ attr_accessor :commerce
18
+ attr_reader :raw
19
+ attr_reader :params
20
+
21
+ def initialize(options)
22
+ self.commerce = options[:commerce]
23
+ self.parse(options[:post])
24
+ end
25
+
26
+ def acknowledge
27
+ self.commerce.webpay_encrypt('ACK')
28
+ end
29
+
30
+ def reject
31
+ self.commerce.webpay_encrypt('ERR')
32
+ end
33
+
34
+ def success?
35
+ self.params["TBK_RESPUESTA"] == "0"
36
+ end
37
+
38
+ def order_id
39
+ self.params["TBK_ORDEN_COMPRA"]
40
+ end
41
+
42
+ def session_id
43
+ self.params["TBK_ID_SESION"]
44
+ end
45
+
46
+ def transaction_id
47
+ self.params["TBK_ID_TRANSACCION"]
48
+ end
49
+
50
+ def amount
51
+ self.params["TBK_MONTO"].to_f/100
52
+ end
53
+
54
+ def authorization
55
+ self.params["TBK_CODIGO_AUTORIZACION"]
56
+ end
57
+
58
+ def card_display_number
59
+ "XXXX-XXXX-XXXX-#{ card_last_numbers }"
60
+ end
61
+
62
+ def card_last_numbers
63
+ self.params["TBK_FINAL_NUMERO_TARJETA"]
64
+ end
65
+
66
+ def message
67
+ RESPONSE_CODES[self.params["TBK_RESPUESTA"]]
68
+ end
69
+
70
+ def paid_at
71
+ @paid_at ||= begin
72
+ year = Time.now.year
73
+ month = self.params["TBK_FECHA_TRANSACCION"][0...2].to_i
74
+ day = self.params["TBK_FECHA_TRANSACCION"][2...4].to_i
75
+ hour = self.params["TBK_HORA_TRANSACCION"][0...2].to_i
76
+ minutes = self.params["TBK_HORA_TRANSACCION"][2...4].to_i
77
+ seconds = self.params["TBK_HORA_TRANSACCION"][4...6].to_i
78
+
79
+ offset = if defined? TZInfo::Timezone
80
+ # Use tzinfo gem if available
81
+ TZInfo::Timezone.get('America/Santiago').period_for_utc(DateTime.new(year,month,day,hour,minutes,0)).utc_offset
82
+ else
83
+ -14400
84
+ end
85
+
86
+ Time.new(year, month, day, hour, minutes, seconds, offset)
87
+ end
88
+ end
89
+
90
+
91
+ protected
92
+ def parse(post)
93
+ @raw = post.to_s
94
+ @raw_params = {}
95
+ for line in @raw.split('&')
96
+ key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten
97
+ @raw_params[key] = CGI.unescape(value)
98
+ end
99
+
100
+ puts @raw_params.inspect
101
+
102
+ @params = {}
103
+ decrypted_params = self.commerce.webpay_decrypt(@raw_params['TBK_PARAM'])
104
+ for line in decrypted_params.split('#')
105
+ key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten
106
+ @params[key] = CGI.unescape(value)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,71 @@
1
+ module TBK
2
+ module Webpay
3
+ module Encryption
4
+ # Constants
5
+ KEY_ID = 101
6
+ KEY = TBK.parse_key('webpay.101')
7
+ TEST_KEY = TBK.parse_key('webpay_test.101')
8
+ IV_PADDING = "\x10\xBB\xFF\xBF\x00\x00\x00\x00\x00\x00\x00\x00\xF4\xBF"
9
+
10
+ def webpay_key_id
11
+ KEY_ID
12
+ end
13
+
14
+ def webpay_key
15
+ self.production? ? KEY : TEST_KEY
16
+ end
17
+
18
+ def webpay_key_length
19
+ webpay_key.n.num_bytes
20
+ end
21
+
22
+ def webpay_encrypt(text)
23
+ signature = self.key.sign(OpenSSL::Digest::SHA512.new, text)
24
+
25
+ key = SecureRandom.random_bytes(32)
26
+ encripted_key = webpay_key.public_encrypt(key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
27
+
28
+ iv = SecureRandom.random_bytes(16)
29
+
30
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
31
+ cipher.encrypt
32
+ cipher.key = key
33
+ cipher.iv = iv + IV_PADDING
34
+ encripted_text = cipher.update(signature + text) + cipher.final
35
+
36
+ Base64.encode64( iv + encripted_key + encripted_text).strip
37
+ rescue RuntimeError => error
38
+ raise TBK::Webpay::EncryptionError.new("Encryption failed",error)
39
+ end
40
+
41
+ def webpay_decrypt(encripted_text)
42
+ data = Base64.decode64(encripted_text)
43
+
44
+ iv = data[0...16]
45
+ encripted_key = data[16...(16 + self.key_bytes)]
46
+ key = self.key.private_decrypt(encripted_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
47
+
48
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
49
+ cipher.decrypt
50
+ cipher.key = key
51
+ cipher.iv = iv + IV_PADDING
52
+ decrypted_text = cipher.update(data[(16 + self.key_bytes)..-1]) + cipher.final
53
+ signature = decrypted_text[0...(webpay_key_length)]
54
+ text = decrypted_text[(webpay_key_length)..-1]
55
+
56
+ unless webpay_key.verify(OpenSSL::Digest::SHA512.new, signature, text)
57
+ raise TBK::Webpay::EncryptionError, "Invalid message signature"
58
+ end
59
+
60
+ text
61
+ rescue TBK::Error
62
+ raise
63
+ rescue => error
64
+ raise TBK::Webpay::EncryptionError.new("Decryption failed",error)
65
+ end
66
+
67
+ end
68
+ end
69
+
70
+ Commerce.send :include, Webpay::Encryption
71
+ end
@@ -0,0 +1,159 @@
1
+ module TBK
2
+ module Webpay
3
+ class Payment
4
+ attr_accessor :commerce
5
+ attr_accessor :amount
6
+ attr_accessor :order_id
7
+ attr_accessor :session_id
8
+ attr_accessor :confirmation_url
9
+ attr_accessor :success_url
10
+ attr_accessor :failure_url
11
+
12
+ def initialize(options = {})
13
+ self.commerce = options[:commerce]
14
+ self.amount = options[:amount]
15
+ self.order_id = options[:order_id]
16
+ self.session_id = options[:session_id]
17
+ self.confirmation_url = options[:confirmation_url]
18
+ self.success_url = options[:success_url]
19
+ self.failure_url = options[:failure_url]
20
+ end
21
+
22
+ # Transaction ids are generated randombly on the client side
23
+ def transaction_id
24
+ @transaction_id ||= (rand * 10000000000).to_i
25
+ end
26
+
27
+ def redirect_url
28
+ "#{ self.process_url }?TBK_VERSION_KCC=#{ TBK::VERSION::KCC }&TBK_TOKEN=#{ self.token }"
29
+ end
30
+
31
+ def to_html_form(options = {}, &block)
32
+ form = <<-EOF
33
+ <form method="POST"
34
+ class="#{ options[:class] || 'tbk_payment_form'}"
35
+ id="#{ options[:id] }"
36
+ action="#{ self.process_url }">
37
+ <input type="hidden" name="TBK_PARAM" value="#{ self.param }">
38
+ <input type="hidden" name="TBK_VERSION_KCC" value="#{ TBK::VERSION::KCC }">
39
+ <input type="hidden" name="TBK_CODIGO_COMERCIO" value="#{ self.commerce.id }">
40
+ <input type="hidden" name="TBK_KEY_ID" value="#{ self.commerce.webpay_key_id }">
41
+ #{ yield if block_given? }
42
+ </form>
43
+ EOF
44
+
45
+ form = form.html_safe if form.respond_to? :html_safe
46
+
47
+ form
48
+ end
49
+
50
+ protected
51
+ def process_url
52
+ if self.commerce.test?
53
+ "https://certificacion.webpay.cl:6443/filtroUnificado/bp_revision.cgi"
54
+ else
55
+ "https://webpay.transbank.cl:443/cgi-bin/bp_revision.cgi"
56
+ end
57
+ end
58
+
59
+ def validation_url
60
+ if self.commerce.test?
61
+ "https://certificacion.webpay.cl:6443/filtroUnificado/bp_validacion.cgi"
62
+ else
63
+ "https://webpay.transbank.cl:443/cgi-bin/bp_validacion.cgi"
64
+ end
65
+ end
66
+
67
+ def token
68
+ @token ||= begin
69
+ uri = URI.parse( self.validation_url )
70
+
71
+ response = nil
72
+ until response && response['location'].nil?
73
+ uri = URI.parse( response.nil? ? self.validation_url : response['location'] )
74
+
75
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
76
+ post = Net::HTTP::Post.new uri.path
77
+ post["user-agent"] = "TBK/#{ TBK::VERSION::GEM } (Ruby/#{ RUBY_VERSION }; +#{ TBK::VERSION::WEBSITE })"
78
+ post.set_form_data({
79
+ 'TBK_VERSION_KCC' => TBK::VERSION::KCC,
80
+ 'TBK_CODIGO_COMERCIO' => self.commerce.id,
81
+ 'TBK_KEY_ID' => self.commerce.webpay_key_id,
82
+ 'TBK_PARAM' => self.param
83
+ })
84
+
85
+ # http.read_timeout = Webpay::Config.timeout
86
+ http.request post
87
+ end
88
+ end
89
+
90
+ unless response.code == "200"
91
+ raise TBK::Webpay::PaymentError, "Payment token generation failed"
92
+ end
93
+
94
+ response = self.commerce.webpay_decrypt(response.body)
95
+
96
+ unless /ERROR=([a-zA-Z0-9]+)/.match(response)[1] == "0"
97
+ raise TBK::Webpay::PaymentError, "Payment token generation failed"
98
+ end
99
+
100
+ /TOKEN=([a-zA-Z0-9]+)/.match(response)[1]
101
+ end
102
+ end
103
+
104
+ def param
105
+ @param ||= begin
106
+ self.verify!
107
+ self.commerce.webpay_encrypt(raw_params)
108
+ end
109
+ end
110
+
111
+ def verify!
112
+ raise TBK::Webpay::PaymentError, "Commerce required" if self.commerce.nil?
113
+ raise TBK::Webpay::PaymentError, "Invalid amount (#{self.amount.inspect})" unless self.amount && self.amount > 0
114
+ raise TBK::Webpay::PaymentError, "Order ID required" if self.order_id.nil?
115
+ raise TBK::Webpay::PaymentError, "Success URL required" if self.success_url.nil?
116
+ raise TBK::Webpay::PaymentError, "Confirmation URL required" if self.confirmation_url.nil?
117
+ confirmation_uri = URI.parse(self.confirmation_url)
118
+ raise TBK::Webpay::PaymentError, "Confirmation URL host MUST be an IP address" unless /(\d{1,3}\.){3}\d{1,3}/.match(confirmation_uri.host)
119
+ raise TBK::Webpay::PaymentError, "Confirmation URL port MUST be 80 or 8080" unless [80, 8080].include?(confirmation_uri.port)
120
+ end
121
+
122
+ def raw_params(splitter="#", include_pseudomac=true)
123
+ params = []
124
+
125
+ params << "TBK_ORDEN_COMPRA=#{ self.order_id }"
126
+ params << "TBK_CODIGO_COMERCIO=#{ self.commerce.id }"
127
+ params << "TBK_ID_TRANSACCION=#{ self.transaction_id }"
128
+
129
+ uri = URI.parse(self.confirmation_url)
130
+
131
+ params << "TBK_URL_CGI_COMERCIO=#{ uri.path }"
132
+ params << "TBK_SERVIDOR_COMERCIO=#{ uri.host }"
133
+ params << "TBK_PUERTO_COMERCIO=#{ uri.port }"
134
+
135
+ params << "TBK_VERSION_KCC=#{ TBK::VERSION::KCC }"
136
+ params << "TBK_KEY_ID=#{ self.commerce.webpay_key_id }"
137
+ params << "PARAMVERIFCOM=1"
138
+
139
+ if include_pseudomac
140
+ # An old pseudo mac generation carried from an old implementation
141
+ digest = OpenSSL::Digest::MD5.new
142
+ digest << self.raw_params('&',false) << self.commerce.id.to_s << "webpay"
143
+ mac = digest.to_s
144
+
145
+ params << "TBK_MAC=#{ mac }"
146
+ end
147
+
148
+
149
+ params << "TBK_MONTO=#{ (self.amount * 100).to_i }"
150
+ params << "TBK_ID_SESION=#{ self.session_id }" if self.session_id
151
+ params << "TBK_URL_EXITO=#{ self.success_url }"
152
+ params << "TBK_URL_FRACASO=#{ self.failure_url || self.success_url }"
153
+ params << "TBK_TIPO_TRANSACCION=TR_NORMAL"
154
+
155
+ params.join(splitter)
156
+ end
157
+ end
158
+ end
159
+ end
data/lib/tbk/webpay.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'tbk/webpay/encryption'
2
+ require 'tbk/webpay/payment'
3
+ require 'tbk/webpay/confirmation'
data/lib/tbk.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "base64"
2
+ require "openssl"
3
+ require "securerandom"
4
+ require "net/https"
5
+ require "cgi"
6
+
7
+ require "tbk/version"
8
+ require "tbk/errors"
9
+ require "tbk/keys"
10
+ require "tbk/commerce"
11
+ require "tbk/webpay"
@@ -0,0 +1,3 @@
1
+ require 'bundler/setup'
2
+ require 'tbk'
3
+ Dir["spec/support/**/*.rb"].each { |f| require File.expand_path(f) }
@@ -0,0 +1,5 @@
1
+ RSpec.configure do |config|
2
+ config.expect_with :rspec do |expect|
3
+ expect.syntax = :expect
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module TestCommerce
2
+ def test_commerce
3
+ @test_commerce ||= TBK::Commerce.new({
4
+ :id => 597026007976,
5
+ :test => true
6
+ })
7
+ end
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.include TestCommerce
12
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe TBK::Webpay::Confirmation do
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe TBK::Webpay::Encryption do
4
+ end
@@ -0,0 +1 @@
1
+ require 'spec_helper'
data/tbk.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tbk/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "tbk"
8
+ gem.version = TBK::VERSION::GEM
9
+ gem.authors = ["Seba Gamboa"]
10
+ gem.email = ["me@sagmor.com"]
11
+ gem.description = %q{Ruby implementation of Transbank's Webpay protocol}
12
+ gem.summary = "Pure Ruby implementation of Transbank's Webpay KCC #{TBK::VERSION::KCC}"
13
+ gem.homepage = TBK::VERSION::WEBSITE
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "tzinfo"
21
+ gem.add_development_dependency "rake"
22
+ gem.add_development_dependency "rspec"
23
+ gem.add_development_dependency "yard"
24
+ gem.add_development_dependency "redcarpet"
25
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tbk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Seba Gamboa
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: tzinfo
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: yard
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: redcarpet
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Ruby implementation of Transbank's Webpay protocol
95
+ email:
96
+ - me@sagmor.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - .travis.yml
103
+ - .yardopts
104
+ - Gemfile
105
+ - LICENSE
106
+ - README.md
107
+ - Rakefile
108
+ - lib/tbk.rb
109
+ - lib/tbk/commerce.rb
110
+ - lib/tbk/errors.rb
111
+ - lib/tbk/keys.rb
112
+ - lib/tbk/keys/test_commerce.pem
113
+ - lib/tbk/keys/webpay.101.pem
114
+ - lib/tbk/keys/webpay_test.101.pem
115
+ - lib/tbk/version.rb
116
+ - lib/tbk/webpay.rb
117
+ - lib/tbk/webpay/confirmation.rb
118
+ - lib/tbk/webpay/encryption.rb
119
+ - lib/tbk/webpay/payment.rb
120
+ - spec/spec_helper.rb
121
+ - spec/support/syntax.rb
122
+ - spec/support/test_commerce.rb
123
+ - spec/webpay/confirmation_spec.rb
124
+ - spec/webpay/encryption_spec.rb
125
+ - spec/webpay/payment_spec.rb
126
+ - tbk.gemspec
127
+ homepage: http://sagmor.com/tbk/
128
+ licenses: []
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ segments:
140
+ - 0
141
+ hash: -1481680372280631458
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ segments:
149
+ - 0
150
+ hash: -1481680372280631458
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 1.8.23
154
+ signing_key:
155
+ specification_version: 3
156
+ summary: Pure Ruby implementation of Transbank's Webpay KCC 6.0
157
+ test_files:
158
+ - spec/spec_helper.rb
159
+ - spec/support/syntax.rb
160
+ - spec/support/test_commerce.rb
161
+ - spec/webpay/confirmation_spec.rb
162
+ - spec/webpay/encryption_spec.rb
163
+ - spec/webpay/payment_spec.rb
164
+ has_rdoc: