conekta-motion 0.1.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 246f61805b3130690f81854eb536a0156f31819e
4
+ data.tar.gz: 8ec2be3b78d4eb18804b848830641e51564c38ff
5
+ SHA512:
6
+ metadata.gz: e0697b4b45f3d2c364056318982cac2003cbeb4800dbbedc7d2de10d66159f1ac5142dab970c1cdc7259de1fa1362f56c29578d79f12afe4834664f9d6a50a25
7
+ data.tar.gz: 5640c419d77616eb2df4d51a7f6960cb6e672a1e69f5ea93e1753f20b34e69742a6cb7081ff26c2d6f5b41c142fe13101e4fc33b7ac7da8909158f502450ef53
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # ConektaMotion
2
+ A Rubymotion gem to use mexican payment gateway Conekta from a Rubymotion iOS
3
+ app.
@@ -0,0 +1,23 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "The MotionKit gem must be required within a RubyMotion project Rakefile."
3
+ end
4
+ require 'dbt'
5
+ require 'afmotion'
6
+
7
+ Motion::Project::App.setup do |app|
8
+ core_lib = File.join(File.dirname(__FILE__), 'conekta-motion')
9
+ constants_lib = File.dirname(__FILE__)
10
+
11
+ constants_file = File.join(constants_lib, 'constants.rb')
12
+ app.files.insert(0, constants_file) if File.exist?(constants_file)
13
+
14
+ # scans app.files until it finds app/ (the default)
15
+ # if found, it inserts just before those files, otherwise it
16
+ # will insert to the end of the list
17
+ insert_point = app.files.find_index { |file| file =~ /^(?:\.\/)?app\// } || 0
18
+ Dir.glob(File.join(core_lib, '**/*.rb')).reverse.each do |file|
19
+ app.files.insert(insert_point, file)
20
+ end
21
+
22
+ DBT.analyze(app)
23
+ end
@@ -0,0 +1,96 @@
1
+ module ConektaMotion
2
+ class Card
3
+ attr_accessor :number, :name, :cvc, :expiration_month,
4
+ :expiration_year
5
+ attr_reader :errors
6
+
7
+ def initialize(number = nil, name = nil, cvc = nil,
8
+ expiration_month = nil, expiration_year = nil)
9
+ self.number = number
10
+ self.name = name
11
+ self.cvc = cvc
12
+ self.expiration_month = expiration_month
13
+ self.expiration_year = expiration_year
14
+
15
+ @errors = {}
16
+ end
17
+
18
+ def to_hash
19
+ {
20
+ card: {
21
+ name: name.to_s,
22
+ number: clean_number.to_s,
23
+ cvc: cvc.to_s,
24
+ exp_month: expiration_month.to_s,
25
+ exp_year: expiration_year.to_s
26
+ }
27
+ }
28
+ end
29
+
30
+ def last_four
31
+ clean_number.reverse[0..3].reverse
32
+ end
33
+
34
+ def valid?
35
+ @errors = {}
36
+
37
+ @errors[:number] = 'Número de tarjeta inválida' if brand.nil? || !valid_card?
38
+
39
+ @errors[:name] = 'Nombre de tarjetahabiente inválido' if name.nil? || name.empty?
40
+
41
+ @errors[:cvc] = 'Código de seguridad inválido' if !valid_cvc?
42
+
43
+ @errors[:expiration_month] = 'Mes de expiración inválido' if !valid_expiration_month?
44
+
45
+ @errors[:expiration_year] = 'Año de expiración inválido' if !valid_expiration_year?
46
+
47
+ @errors.keys.count == 0
48
+ end
49
+
50
+ def brand
51
+ _number = clean_number
52
+
53
+ return :dinners if _number.length == 14 && _number =~ /^3(0[0-5]|[68])/ # 300xxx-305xxx, 36xxxx, 38xxxx
54
+ return :amex if _number.length == 15 && _number =~ /^3[47]/ # 34xxxx, 37xxxx
55
+ return :visa if [13,16].include?(_number.length) && _number =~ /^4/ # 4xxxxx
56
+ return :master if _number.length == 16 && _number =~ /^5[1-5]/ # 51xxxx-55xxxx
57
+ return :discover if _number.length == 16 && _number =~ /^6011/ # 6011xx
58
+ nil
59
+ end
60
+
61
+ private
62
+ def clean_number
63
+ return '' if number.nil?
64
+ number.gsub(/\D/, '')
65
+ end
66
+
67
+ def valid_card?
68
+ reversed_number = clean_number.reverse
69
+
70
+ relative_number = { '0' => 0, '1' => 2, '2' => 4, '3' => 6, '4' => 8, '5' => 1, '6' => 3, '7' => 5, '8' => 7, '9' => 9 }
71
+
72
+ accumulator = 0
73
+
74
+ reversed_number.split('').each_with_index do |digit, index|
75
+ accumulator += (index % 2 == 0) ? digit.to_i : relative_number[digit]
76
+ end
77
+
78
+ accumulator % 10 == 0
79
+ end
80
+
81
+ def valid_cvc?
82
+ return false if cvc.nil? || !(cvc =~ /^\d{3,4}/) || !(3..4).include?(cvc.length)
83
+ true
84
+ end
85
+
86
+ def valid_expiration_month?
87
+ return false if expiration_month.nil? || !(expiration_month =~ /^\d{2}/) || expiration_month.length != 2 || !(1..12).include?(expiration_month.to_i)
88
+ true
89
+ end
90
+
91
+ def valid_expiration_year?
92
+ return false if expiration_year.nil? || !(expiration_year =~ /^\d{2}/) || expiration_year.length != 2 || !(expiration_year.to_i > 0)
93
+ true
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ module ConektaMotion
2
+ ::CM = ConektaMotion unless defined?(::CM)
3
+ end
@@ -0,0 +1,44 @@
1
+ module ConektaMotion
2
+ class Conekta
3
+ def initialize(api_key = API_KEY)
4
+ @api_key = api_key
5
+ end
6
+
7
+ def tokenize_card(card, &block)
8
+ web_client.post('/tokens', card.to_hash) do |response|
9
+ token = nil
10
+
11
+ if response.failure?
12
+ unless response.error.is_a?(NSURLError)
13
+ token = Token.init_from response.object
14
+ end
15
+ else
16
+ token = Token.init_from response.object
17
+ end
18
+
19
+ block.call token
20
+ end
21
+ end
22
+
23
+ private
24
+ def encoded_api_key
25
+ [@api_key].pack("m0")
26
+ end
27
+
28
+ def web_client
29
+ key = encoded_api_key
30
+
31
+ @@web_client ||= begin
32
+ AFMotion::Client.build('https://api.conekta.io') do
33
+ request_serializer :json
34
+ response_serializer :json
35
+
36
+ header 'Content-type', 'application/json'
37
+ header 'Accept', 'application/vnd.conekta-v0.3.0+json'
38
+ header 'Authorization', "Basic #{key}"
39
+ header 'Conekta-Client-User-Agent', '{"agent": "Conekta Conekta iOS SDK"}' #'ConektaMotion iOS', seems that Conekta only accepts this agent.
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ module ConektaMotion
2
+ class Token
3
+ attr_reader :token_id, :error
4
+
5
+ def initialize(token_id, error)
6
+ @token_id = token_id
7
+ @error = error
8
+ end
9
+
10
+ def valid?
11
+ error.nil?
12
+ end
13
+
14
+ class << self
15
+ def init_from(response)
16
+ token_id = nil
17
+ error = nil
18
+
19
+ if response
20
+ token_id = response[:id] if response[:object] == 'token'
21
+ error = response[:message_to_purchaser] if response[:object] == 'error'
22
+ end
23
+
24
+ Token.new token_id, error
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module ConektaMotion
2
+ VERSION = '0.1.1'
3
+ end
data/lib/constants.rb ADDED
@@ -0,0 +1,3 @@
1
+ module ConektaMotion
2
+ API_KEY = 'key_DzVhxySdJAfmboDePSVUCuQ'
3
+ end
data/spec/card_spec.rb ADDED
@@ -0,0 +1,212 @@
1
+ describe 'Card' do
2
+ before do
3
+ @card = ConektaMotion::Card.new
4
+ end
5
+
6
+ describe 'card properties' do
7
+ it 'accepts card number' do
8
+ card = CM::Card.new '4242 4242 4242 4242'
9
+
10
+ card.number.should == '4242 4242 4242 4242'
11
+ end
12
+
13
+ it 'accepts card holder name' do
14
+ card = CM::Card.new '4242 4242 4242 4242',
15
+ 'Juan Perez'
16
+
17
+ card.name.should == 'Juan Perez'
18
+ end
19
+
20
+ it 'accepts CVC code' do
21
+ card = CM::Card.new '4242 4242 4242 4242',
22
+ 'Juan Perez', '001'
23
+
24
+ card.cvc.should == '001'
25
+ end
26
+
27
+ it 'accepts expiration month' do
28
+ card = CM::Card.new '4242 4242 4242 4242',
29
+ 'Juan Perez', '001', '01'
30
+
31
+ card.expiration_month.should == '01'
32
+ end
33
+
34
+ it 'accepts expiration year' do
35
+ card = CM::Card.new '4242 4242 4242 4242',
36
+ 'Juan Perez', '001', '01', '14'
37
+
38
+ card.expiration_year.should == '14'
39
+ end
40
+ end
41
+
42
+ describe 'card validation' do
43
+ describe 'card number' do
44
+ it 'is valid' do
45
+ @card.number = '4242 4242 4242 4242'
46
+ @card.valid?
47
+
48
+ @card.errors.has_key?(:number).should == false
49
+ end
50
+
51
+ it 'is invalid due wrong number' do
52
+ @card.number = '8242 4242'
53
+ @card.valid?
54
+
55
+ @card.errors.has_key?(:number).should == true
56
+ @card.errors[:number].should == 'Número de tarjeta inválida'
57
+ end
58
+
59
+ it 'is invalid due do not pass lunh validation' do
60
+ @card.number = '4242 4242 4242 8282'
61
+ @card.valid?
62
+
63
+ @card.errors.has_key?(:number).should == true
64
+ @card.errors[:number].should == 'Número de tarjeta inválida'
65
+ end
66
+ end
67
+
68
+ describe 'holder name' do
69
+ it 'is valid' do
70
+ @card.name = 'Juan Perez'
71
+ @card.valid?
72
+
73
+ @card.errors.has_key?(:name).should == false
74
+ end
75
+
76
+ it 'is invalid' do
77
+ @card.name = ''
78
+ @card.valid?
79
+
80
+ @card.errors.has_key?(:name).should == true
81
+ @card.errors[:name].should == 'Nombre de tarjetahabiente inválido'
82
+ end
83
+ end
84
+
85
+ describe 'cvc' do
86
+ it 'is valid' do
87
+ @card.cvc = '090'
88
+ @card.valid?
89
+
90
+ @card.errors.has_key?(:cvc).should == false
91
+ end
92
+
93
+ it 'is invalid if empty' do
94
+ @card.cvc = nil
95
+ @card.valid?
96
+
97
+ @card.errors.has_key?(:cvc).should == true
98
+ @card.errors[:cvc].should == 'Código de seguridad inválido'
99
+ end
100
+
101
+ it 'is invalid wrong number ' do
102
+ @card.cvc = '90'
103
+ @card.valid?
104
+
105
+ @card.errors.has_key?(:cvc).should == true
106
+ @card.errors[:cvc].should == 'Código de seguridad inválido'
107
+ end
108
+ end
109
+ end
110
+
111
+ describe 'expiration month' do
112
+ it 'is valid' do
113
+ @card.expiration_month = '01'
114
+ @card.valid?
115
+
116
+ @card.errors.has_key?(:expiration_month).should == false
117
+ end
118
+
119
+ it 'is invalid wrong value' do
120
+ @card.expiration_month = 'a1'
121
+ @card.valid?
122
+
123
+ @card.errors.has_key?(:expiration_month).should == true
124
+ @card.errors[:expiration_month].should == 'Mes de expiración inválido'
125
+ end
126
+
127
+ it 'is invalid when wrong month number' do
128
+ @card.expiration_month = '13'
129
+ @card.valid?
130
+
131
+ @card.errors.has_key?(:expiration_month).should == true
132
+ @card.errors[:expiration_month].should == 'Mes de expiración inválido'
133
+ end
134
+ end
135
+
136
+ describe 'expiration year' do
137
+ it 'is valid' do
138
+ @card.expiration_year = '17'
139
+ @card.valid?
140
+
141
+ @card.errors.has_key?(:expiration_year).should == false
142
+ end
143
+
144
+ it 'is invalid wrong value' do
145
+ @card.expiration_year = '1b'
146
+ @card.valid?
147
+
148
+ @card.errors.has_key?(:expiration_year).should == true
149
+ @card.errors[:expiration_year].should == 'Año de expiración inválido'
150
+ end
151
+ end
152
+
153
+ describe 'card brand' do
154
+ it 'detects visa cards' do
155
+ @card.number = '4035330730728765'
156
+
157
+ @card.brand.should == :visa
158
+ end
159
+
160
+ it 'detects mastercard cards' do
161
+ @card.number = '5352081557347062'
162
+
163
+ @card.brand.should == :master
164
+ end
165
+
166
+ it 'detects amex cards' do
167
+ @card.number = '342411430263950'
168
+
169
+ @card.brand.should == :amex
170
+ end
171
+
172
+ it 'detects dinners cards' do
173
+ @card.number = '38520000023237'
174
+
175
+ @card.brand.should == :dinners
176
+ end
177
+
178
+ it 'detects discover cards' do
179
+ @card.number = '6011111111111117'
180
+
181
+ @card.brand.should == :discover
182
+ end
183
+ end
184
+
185
+ describe '#to_hash' do
186
+ it 'serialize card data' do
187
+ @card.number = '4242 4242 4242 4242'
188
+ @card.name = 'Juan Perez'
189
+ @card.cvc = '090'
190
+ @card.expiration_month = '02'
191
+ @card.expiration_year = '18'
192
+
193
+ @card.to_hash.should == { card:
194
+ {
195
+ name: "Juan Perez",
196
+ number: "4242424242424242",
197
+ cvc: "090",
198
+ exp_month: "02",
199
+ exp_year: "18"
200
+ }
201
+ }
202
+ end
203
+ end
204
+
205
+ describe '#last_four' do
206
+ it 'returns card number last four digits' do
207
+ @card.number = '4242 4242 4242 4242'
208
+
209
+ @card.last_four.should == '4242'
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,28 @@
1
+ describe 'Conekta' do
2
+ extend WebStub::SpecHelpers
3
+
4
+ before do
5
+ disable_network_access!
6
+ @conekta = ConektaMotion::Conekta.new
7
+ end
8
+
9
+ context '#tokenize' do
10
+ it 'should tokenize a valid card' do
11
+ stub_request(:post, 'https://api.conekta.io/tokens').
12
+ to_return(json: { id: 'tok_pWv5DipgPvbabidg4', livemode: false, used: false, object: 'token' })
13
+
14
+ card = CM::Card.new '4242 4242 4242 4242', 'Juan Perez',
15
+ '090', '02', '18'
16
+
17
+ @conekta.tokenize_card card do |token|
18
+ @token = token
19
+ resume
20
+ end
21
+
22
+ wait_max 1.0 do
23
+ @token.nil?.should == false
24
+ @token.valid?.should == true
25
+ end
26
+ end
27
+ end
28
+ end
data/spec/main_spec.rb ADDED
@@ -0,0 +1,9 @@
1
+ describe "Application 'conekta-motion'" do
2
+ # before do
3
+ # @app = UIApplication.sharedApplication
4
+ # end
5
+ #
6
+ # it "has one window" do
7
+ # @app.windows.size.should == 1
8
+ # end
9
+ end
@@ -0,0 +1,21 @@
1
+ describe 'Token' do
2
+ describe 'parse' do
3
+ it 'ok response' do
4
+ response = { id: 'tok_pWv5DipgPvbabidg4', livemode: false, used: false, object: 'token' }
5
+
6
+ token = CM::Token.init_from response
7
+
8
+ token.valid?.should == true
9
+ token.token_id.should == 'tok_pWv5DipgPvbabidg4'
10
+ end
11
+
12
+ it 'error response' do
13
+ response = { type: nil, message_to_purchaser: "The card number is invalid.", object: "error", code: "invalid_number", message: "The card number is invalid.", param: "number" }
14
+
15
+ token = CM::Token.init_from response
16
+
17
+ token.valid?.should == false
18
+ token.error.should == 'The card number is invalid.'
19
+ end
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: conekta-motion
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Mario A. Chavez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dbt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: afmotion
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: cocoapods
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: motion-cocoapods
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: |2
70
+ ConektaMotion allows your Rubymotion app to tokenize a Bank Card with Conekta.io payment gateway.
71
+ email:
72
+ - mario.chavez@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - README.md
78
+ - lib/conekta-motion.rb
79
+ - lib/conekta-motion/card.rb
80
+ - lib/conekta-motion/conekta-motion.rb
81
+ - lib/conekta-motion/conekta.rb
82
+ - lib/conekta-motion/token.rb
83
+ - lib/conekta-motion/version.rb
84
+ - lib/constants.rb
85
+ - spec/card_spec.rb
86
+ - spec/conekta_spec.rb
87
+ - spec/main_spec.rb
88
+ - spec/token_spec.rb
89
+ homepage: https://github.com/mariochavez/conekta-motion
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.4.5
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Rubymotion library to use payment gateway Conekta.io
113
+ test_files:
114
+ - spec/card_spec.rb
115
+ - spec/conekta_spec.rb
116
+ - spec/main_spec.rb
117
+ - spec/token_spec.rb