tbk 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
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_accessor :request_ip
19
+ attr_reader :raw
20
+ attr_reader :params
21
+
22
+ def initialize(options)
23
+ options = { :body => options } if options.is_a?(String)
24
+
25
+ self.commerce = options[:commerce] || TBK::Commerce.default_commerce
26
+ self.request_ip = options[:request_ip]
27
+ self.parse(options[:body])
28
+ end
29
+
30
+ def acknowledge
31
+ self.commerce.webpay_encrypt('ACK')
32
+ end
33
+
34
+ def reject
35
+ self.commerce.webpay_encrypt('ERR')
36
+ end
37
+
38
+ def success?
39
+ self.params[:TBK_RESPUESTA] == "0"
40
+ end
41
+
42
+ def order_id
43
+ self.params[:TBK_ORDEN_COMPRA]
44
+ end
45
+
46
+ def session_id
47
+ self.params[:TBK_ID_SESION]
48
+ end
49
+
50
+ def transaction_id
51
+ self.params[:TBK_ID_TRANSACCION]
52
+ end
53
+
54
+ def amount
55
+ self.params[:TBK_MONTO].to_f/100
56
+ end
57
+
58
+ def authorization
59
+ self.params[:TBK_CODIGO_AUTORIZACION]
60
+ end
61
+
62
+ def signature
63
+ self.params[:TBK_MAC]
64
+ end
65
+
66
+ def card_display_number
67
+ "XXXX-XXXX-XXXX-#{ card_last_numbers }"
68
+ end
69
+
70
+ def card_last_numbers
71
+ self.params[:TBK_FINAL_NUMERO_TARJETA]
72
+ end
73
+
74
+ def message
75
+ RESPONSE_CODES[self.params[:TBK_RESPUESTA]]
76
+ end
77
+
78
+ def paid_at
79
+ @paid_at ||= begin
80
+ year = Time.now.year
81
+ month = self.params[:TBK_FECHA_TRANSACCION][0...2].to_i
82
+ day = self.params[:TBK_FECHA_TRANSACCION][2...4].to_i
83
+ hour = self.params[:TBK_HORA_TRANSACCION][0...2].to_i
84
+ minutes = self.params[:TBK_HORA_TRANSACCION][2...4].to_i
85
+ seconds = self.params[:TBK_HORA_TRANSACCION][4...6].to_i
86
+
87
+ offset = if defined? TZInfo::Timezone
88
+ # Use tzinfo gem if available
89
+ TZInfo::Timezone.get('America/Santiago').period_for_utc(DateTime.new(year,month,day,hour,minutes,0)).utc_offset
90
+ else
91
+ -14400
92
+ end
93
+
94
+ Time.new(year, month, day, hour, minutes, seconds, offset)
95
+ end
96
+ end
97
+
98
+
99
+ protected
100
+ def parse(post)
101
+ @raw = post.to_s
102
+ @raw_params = {}
103
+ for line in @raw.split('&')
104
+ key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten
105
+ @raw_params[key] = CGI.unescape(value)
106
+ end
107
+
108
+ @params = {}
109
+ decrypted_params = self.commerce.webpay_decrypt(@raw_params['TBK_PARAM'])
110
+ for line in decrypted_params[:body].split('#')
111
+ key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten
112
+ @params[key.to_sym] = CGI.unescape(value)
113
+ end
114
+ @params[:TBK_MAC] = decrypted_params[:signature]
115
+
116
+ TBK::Webpay.logger.confirmation(self)
117
+
118
+ true
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: us-ascii
2
+ module TBK
3
+ module Webpay
4
+ module Encryption
5
+ # Constants
6
+ KEY_ID = 101
7
+ KEY = TBK.parse_key('webpay.101')
8
+ TEST_KEY = TBK.parse_key('webpay_test.101')
9
+ IV_PADDING = "\x10\xBB\xFF\xBF\x00\x00\x00\x00\x00\x00\x00\x00\xF4\xBF"
10
+
11
+ def webpay_key_id
12
+ KEY_ID
13
+ end
14
+
15
+ def webpay_key
16
+ self.production? ? KEY : TEST_KEY
17
+ end
18
+
19
+ def webpay_key_length
20
+ webpay_key.n.num_bytes
21
+ end
22
+
23
+ def webpay_encrypt(text)
24
+ signature = self.key.sign(OpenSSL::Digest::SHA512.new, text)
25
+
26
+ key = SecureRandom.random_bytes(32)
27
+ encripted_key = webpay_key.public_encrypt(key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
28
+
29
+ iv = SecureRandom.random_bytes(16)
30
+
31
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
32
+ cipher.encrypt
33
+ cipher.key = key
34
+ cipher.iv = iv + IV_PADDING
35
+ encripted_text = cipher.update(signature + text) + cipher.final
36
+
37
+ Base64.encode64( iv + encripted_key + encripted_text).strip
38
+ rescue RuntimeError => error
39
+ raise TBK::Webpay::EncryptionError.new("Encryption failed",error)
40
+ end
41
+
42
+ def webpay_decrypt(encripted_text)
43
+ data = Base64.decode64(encripted_text)
44
+
45
+ iv = data[0...16]
46
+ encripted_key = data[16...(16 + self.key_bytes)]
47
+ key = self.key.private_decrypt(encripted_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
48
+
49
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
50
+ cipher.decrypt
51
+ cipher.key = key
52
+ cipher.iv = iv + IV_PADDING
53
+ decrypted_text = cipher.update(data[(16 + self.key_bytes)..-1]) + cipher.final
54
+ signature = decrypted_text[0...(webpay_key_length)]
55
+ text = decrypted_text[(webpay_key_length)..-1]
56
+
57
+ unless webpay_key.verify(OpenSSL::Digest::SHA512.new, signature, text)
58
+ raise TBK::Webpay::EncryptionError, "Invalid message signature"
59
+ end
60
+
61
+ {
62
+ :body => text,
63
+ :signature => signature.unpack('H*').first
64
+ }
65
+ rescue TBK::Error
66
+ raise
67
+ rescue => error
68
+ raise TBK::Webpay::EncryptionError.new("Decryption failed",error)
69
+ end
70
+
71
+ end
72
+ end
73
+
74
+ Commerce.send :include, Webpay::Encryption
75
+ end
@@ -0,0 +1,41 @@
1
+ require "tbk/webpay/logger/base_logger"
2
+ module TBK
3
+ module Webpay
4
+
5
+ # Get the logger class for a given Symbol
6
+ def self.logger_for_symbol(sym)
7
+ logger_class_name = "#{sym.to_s.gsub(/(^\w)|(_\w)/){ |s| s.upcase[-1] }}Logger"
8
+ logger_require_path = "tbk/webpay/logger/#{sym}_logger"
9
+
10
+ require logger_require_path
11
+
12
+ TBK::Webpay::Logger.const_get(logger_class_name)
13
+ end
14
+
15
+ def self.logger(logger=nil, &block)
16
+ if logger
17
+ klass = case logger
18
+ when Symbol
19
+ self.logger_for_symbol(logger)
20
+ when Class
21
+ logger
22
+ else
23
+ raise ArgumentError, "first argument must be a Symbol or a Class"
24
+ end
25
+
26
+ @logger = klass.new(&block)
27
+ end
28
+
29
+ @logger
30
+ end
31
+ self.logger(:null)
32
+
33
+ module Config
34
+ def webpay_logger(logger=nil, &block)
35
+ TBK::Webpay.logger(logger, &block)
36
+ end
37
+ end
38
+
39
+ TBK::Config.send(:include, Config)
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ module TBK
2
+ module Webpay
3
+ module Logger
4
+ # This is an abstract class that defines the required interface of a Webpay logger
5
+ class BaseLogger
6
+
7
+ # Allow logger customization with a block
8
+ def initialize(&block)
9
+ block.call(self) if block
10
+ validate!
11
+ end
12
+
13
+ # Abstract method to log a payment
14
+ def payment(payment)
15
+ raise NotImplementedError, "TBK::Webpay::Logger::BaseLogger subclass must implement #payment method"
16
+ end
17
+
18
+ # Abstract method to log a payment confirmation
19
+ def confirmation(confirmation)
20
+ raise NotImplementedError, "TBK::Webpay::Logger::BaseLogger subclass must implement #confirmation method"
21
+ end
22
+
23
+ private
24
+ # Method to validate that the logger is propperly setted up
25
+ def validate!
26
+ if self.class == TBK::Webpay::Logger::BaseLogger
27
+ raise ArgumentError, "You can't use TBK::Webpay::Logger::BaseLogger directly"
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ module TBK
2
+ module Webpay
3
+ module Logger
4
+ class NullLogger < BaseLogger
5
+ def payment(payment)
6
+ # NoOp
7
+ end
8
+
9
+ def confirmation(confirmation)
10
+ # NoOp
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,193 @@
1
+ require 'pathname'
2
+
3
+ module TBK
4
+ module Webpay
5
+ module Logger
6
+ class OfficialLogger < BaseLogger
7
+
8
+ def directory(directory=nil)
9
+ if directory
10
+ @directory = Pathname(directory)
11
+
12
+ # Create the directory if needed
13
+ Dir.mkdir(@directory) unless Dir.exists?(@directory)
14
+ end
15
+
16
+ @directory
17
+ end
18
+
19
+ def payment(payment)
20
+ events_log_file do |file|
21
+ file.write PAYMENT_FORMAT % {
22
+ date: now.strftime(LOG_DATE_FORMAT),
23
+ time: now.strftime(LOG_TIME_FORMAT),
24
+ pid: Process.pid,
25
+ commerce_id: payment.commerce.id,
26
+ transaction_id: payment.transaction_id,
27
+ request_ip: payment.request_ip,
28
+ token: payment.token,
29
+ webpay_server: (payment.commerce.test? ? 'https://certificacion.webpay.cl' : 'https://webpay.transbank.cl')
30
+ }
31
+ end
32
+
33
+ configuration_log_file do |file|
34
+ response_uri = URI.parse(payment.confirmation_url)
35
+
36
+ file.write CONFIGURATION_FORMAT % {
37
+ commerce_id: payment.commerce.id,
38
+ server_ip: response_uri.host,
39
+ server_port: response_uri.port,
40
+ response_path: response_uri.path,
41
+ webpay_server: (payment.commerce.test? ? 'https://certificacion.webpay.cl' : 'https://webpay.transbank.cl'),
42
+ webpay_server_port: (payment.commerce.test? ? '6433' : '433')
43
+ }
44
+ end
45
+ end
46
+
47
+ def confirmation(confirmation)
48
+ events_log_file do |file|
49
+ file.write CONFIRMATION_FORMAT % {
50
+ date: now.strftime(LOG_DATE_FORMAT),
51
+ time: now.strftime(LOG_TIME_FORMAT),
52
+ pid: Process.pid,
53
+ commerce_id: confirmation.commerce.id,
54
+ transaction_id: confirmation.transaction_id,
55
+ request_ip: confirmation.request_ip,
56
+ order_id: confirmation.order_id,
57
+ }
58
+ end
59
+
60
+ bitacora_log_file do |file|
61
+ file.write BITACORA_FORMAT % confirmation.params.merge({
62
+ commerce_id: confirmation.commerce.id
63
+ })
64
+ end
65
+ end
66
+
67
+ private
68
+ def validate!
69
+ unless self.directory
70
+ raise ArgumentError, "#{self.class} requires a directory attribute"
71
+ end
72
+ end
73
+
74
+ def now
75
+ offset = if defined? TZInfo::Timezone
76
+ # Use tzinfo gem if available
77
+ TZInfo::Timezone.get('America/Santiago').period_for_utc(Time.now.utc).utc_offset
78
+ else
79
+ -14400
80
+ end
81
+
82
+ Time.now.getlocal(offset)
83
+ end
84
+
85
+ def configuration_log_file(&block)
86
+ log_file(CONFIGURATION_FILE_NAME, 'w+', &block)
87
+ end
88
+
89
+ def events_log_file(&block)
90
+ name = EVENTS_LOG_FILE_NAME_FORMAT % now.strftime(EVENTS_LOG_FILE_DATE_FORMAT)
91
+
92
+ log_file(name, &block)
93
+ end
94
+
95
+ def bitacora_log_file(&block)
96
+ name = BITACORA_LOG_FILE_NAME_FORMAT % now.strftime(BITACORA_LOG_FILE_DATE_FORMAT)
97
+
98
+ log_file(name, &block)
99
+ end
100
+
101
+ def log_file(name, mode='a+', &block)
102
+ path = self.directory.join(name)
103
+
104
+ File.open(path, mode, &block)
105
+ end
106
+
107
+ # Formats
108
+ # Here comes an ugly part.
109
+ # Check https://github.com/sagmor/tbk/pull/21#issuecomment-38714753 for the source of this formats.
110
+
111
+ LOG_DATE_FORMAT = "%d%m%Y".freeze
112
+ LOG_TIME_FORMAT = "%H%M%S".freeze
113
+ CONFIGURATION_FILE_NAME = "tbk_config.dat".freeze
114
+ EVENTS_LOG_FILE_NAME_FORMAT = "TBK_EVN%s.log".freeze
115
+ EVENTS_LOG_FILE_DATE_FORMAT = "%Y%m%d".freeze
116
+ BITACORA_LOG_FILE_NAME_FORMAT = "tbk_bitacora_TR_NORMAL_%s.log".freeze
117
+ BITACORA_LOG_FILE_DATE_FORMAT = "%m%d".freeze
118
+ PAYMENT_FORMAT = <<EOF.freeze
119
+ ;%<pid>-12s; ;Filtro ;Inicio ;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ; ;Inicio de filtrado
120
+ ;%<pid>-12s; ;Filtro ;tbk_param.txt ;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ; ;Archivo parseado
121
+ ;%<pid>-12s; ;Filtro ;Terminado ;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ; ;Datos Filtrados con exito
122
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;inicio ;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Parseo realizado
123
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Datos en datos/tbk_config.dat
124
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Mac generado
125
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Construccion TBK_PARAM
126
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;TBK_PARAM encriptado
127
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Datos listos para ser enviados
128
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Medio 1: Transaccion segura
129
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Datos validados
130
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Token=%<token>s
131
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Redireccion web
132
+ %<transaction_id>-10s;%<pid>-12s; ;pago ;%<webpay_server>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Todo OK
133
+ EOF
134
+
135
+ CONFIRMATION_FORMAT = <<EOF.freeze
136
+ ;%<pid>-12s; ;resultado ;Desencriptando ;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ; ;TBK_PARAM desencriptado
137
+ ;%<pid>-12s; ;resultado ;Validacion ;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ; ;Entidad emisora de los datos validada
138
+ ;%<pid>-12s; ;resultado ;%<order_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ; ;Parseo de los datos
139
+ ;%<pid>-12s; ;resultado ;%<order_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ; ;http://127.0.0.1/webpay/notify
140
+ %<transaction_id>-10s;%<pid>-12s; ;transacc ;%<transaction_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;conectandose al port :(80)
141
+ %<transaction_id>-10s;%<pid>-12s; ;resultado ;logro abrir_conexion ;%<date>-14s;%<date>-6s;%<request_ip>-15s; 0 ;%<commerce_id>-20s;Abrio socket para conex-com
142
+ %<transaction_id>-10s;%<pid>-12s; ;transacc ;%<transaction_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;POST a url http://127.0.0.1/webpay/notify
143
+ %<transaction_id>-10s;%<pid>-12s; ;transacc ;%<transaction_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;mensaje enviado
144
+ ;%<pid>-12s; ;check_mac ; ;%<date>-14s;%<date>-6s;EMPTY ;OK ; ;Todo OK
145
+ %<transaction_id>-10s;%<pid>-12s; ;transacc ;%<transaction_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Llego ACK del Comercio
146
+ %<transaction_id>-10s;%<pid>-12s; ;resultado ;%<order_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;tienda acepto transaccion
147
+ %<transaction_id>-10s;%<pid>-12s; ;resultado ;%<order_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;respuesta enviada a TBK (ACK)
148
+ %<transaction_id>-10s;%<pid>-12s; ;resultado ;%<order_id>-40s;%<date>-14s;%<date>-6s;%<request_ip>-15s;OK ;%<commerce_id>-20s;Todo OK
149
+ EOF
150
+
151
+ BITACORA_FORMAT = %w{
152
+ ACK;
153
+ TBK_ORDEN_COMPRA=%<TBK_ORDEN_COMPRA>s;
154
+ TBK_CODIGO_COMERCIO=%<commerce_id>s;
155
+ TBK_TIPO_TRANSACCION=%<TBK_TIPO_TRANSACCION>s;
156
+ TBK_RESPUESTA=%<TBK_RESPUESTA>s;
157
+ TBK_MONTO=%<TBK_MONTO>s;
158
+ TBK_CODIGO_AUTORIZACION=%<TBK_CODIGO_AUTORIZACION>s;
159
+ TBK_FINAL_NUMERO_TARJETA=%<TBK_FINAL_NUMERO_TARJETA>s;
160
+ TBK_FECHA_CONTABLE=%<TBK_FECHA_CONTABLE>s;
161
+ TBK_FECHA_TRANSACCION=%<TBK_FECHA_TRANSACCION>s;
162
+ TBK_HORA_TRANSACCION=%<TBK_HORA_TRANSACCION>s;
163
+ TBK_ID_SESION=%<TBK_ID_SESION>s;
164
+ TBK_ID_TRANSACCION=%<TBK_ID_TRANSACCION>s;
165
+ TBK_TIPO_PAGO=%<TBK_TIPO_PAGO>s;
166
+ TBK_NUMERO_CUOTAS=%<TBK_NUMERO_CUOTAS>s;
167
+ TBK_VCI=%<TBK_VCI>s;
168
+ TBK_MAC=%<TBK_MAC>s
169
+ }.join(' ').freeze
170
+
171
+ CONFIGURATION_FORMAT = <<EOF.freeze
172
+ IDCOMERCIO = %<commerce_id>s
173
+ MEDCOM = 1
174
+ TBK_KEY_ID = 101
175
+ PARAMVERIFCOM = 1
176
+ URLCGICOM = %<response_path>s
177
+ SERVERCOM = %<server_ip>s
178
+ PORTCOM = %<server_port>s
179
+ WHITELISTCOM = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 0123456789./:=&?_
180
+ HOST = %<server_ip>s
181
+ WPORT = %<server_port>s
182
+ URLCGITRA = /filtroUnificado/bp_revision.cgi
183
+ URLCGIMEDTRA = /filtroUnificado/bp_validacion.cgi
184
+ SERVERTRA = %<webpay_server>s
185
+ PORTTRA = %<webpay_server_port>s
186
+ PREFIJO_CONF_TR = HTML_
187
+ HTML_TR_NORMAL = http://127.0.0.1/webpay/notify
188
+ EOF
189
+
190
+ end
191
+ end
192
+ end
193
+ end