tbk 1.0.1 → 1.0.2

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.
@@ -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