nitro_pay 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/lib/generators/nitro_pay/install_generator.rb +16 -0
- data/lib/generators/templates/nitro_pay.rb +39 -0
- data/lib/nitro_pay.rb +91 -3
- data/lib/nitro_pay/array.rb +6 -0
- data/lib/nitro_pay/config/enums/brands.yml +45 -0
- data/lib/nitro_pay/config/enums/currencies.yml +35 -0
- data/lib/nitro_pay/config/enums/payment_methods.yml +20 -0
- data/lib/nitro_pay/config/enums/recurrence_periods.yml +18 -0
- data/lib/nitro_pay/config/enums/transaction_codes.yml +31 -0
- data/lib/nitro_pay/config/proxy.example.yml +4 -0
- data/lib/nitro_pay/connection.rb +173 -0
- data/lib/nitro_pay/currency.rb +76 -0
- data/lib/nitro_pay/hash.rb +55 -0
- data/lib/nitro_pay/status.rb +29 -0
- data/lib/nitro_pay/string.rb +17 -0
- data/lib/nitro_pay/transaction.rb +123 -0
- data/lib/nitro_pay/version.rb +5 -2
- data/modeling/API Routes.png +0 -0
- data/modeling/Sequence - APIClient.asta +0 -0
- data/modeling/Structure - ClassDiagram.asta +0 -0
- data/modeling/WorkFlow - LocalGEM.asta +0 -0
- data/modeling/WorkFlow - LocalGEM.png +0 -0
- data/modeling/useful_files/card_operators/cielo/test_cards.txt +11 -0
- data/modeling/useful_files/card_operators/cielo/test_keys.txt +3 -0
- data/spec/file_helper.rb +42 -0
- data/spec/helpers.rb +56 -0
- data/spec/nitro_pay/conversions_spec.rb +36 -0
- data/spec/nitro_pay/currency_spec.rb +154 -0
- data/spec/nitro_pay/status_spec.rb +15 -0
- data/spec/nitro_pay/transaction_spec.rb +584 -0
- data/spec/spec_helper.rb +20 -0
- metadata +39 -3
@@ -0,0 +1,76 @@
|
|
1
|
+
module NitroPay
|
2
|
+
# Currency Obj, useful methods to work with many different countries
|
3
|
+
class Currency
|
4
|
+
# Receive it amount in Decimal and convert to operator str, like 10,00 or 10.00 to 1000
|
5
|
+
def self.to_operator_str(amount)
|
6
|
+
# Check invalid entry
|
7
|
+
return nil if amount.nil?
|
8
|
+
return amount.to_s if amount.is_a?Integer
|
9
|
+
amount = format('%.2f', amount) if amount.to_s.float?
|
10
|
+
return amount if amount.is_a?(String) && amount.index('.').nil? && amount.index(',').nil?
|
11
|
+
|
12
|
+
if amount.is_a?String
|
13
|
+
return amount if amount.index('.').nil? && amount.index(',').nil?
|
14
|
+
|
15
|
+
amount = amount.remove('.')
|
16
|
+
amount = amount.remove(',')
|
17
|
+
|
18
|
+
return amount
|
19
|
+
end
|
20
|
+
|
21
|
+
# Convert from BigDecimal
|
22
|
+
if amount.is_a?BigDecimal
|
23
|
+
aux_amount_str = amount.to_s('F')
|
24
|
+
cents = aux_amount_str[aux_amount_str.index('.'), aux_amount_str.length]
|
25
|
+
|
26
|
+
# Check if there is a bug because the Decimal didn't recognize the second 0
|
27
|
+
aux_amount_str = "#{aux_amount_str}0"if cents.index('.')
|
28
|
+
|
29
|
+
return aux_amount_str.remove('.')
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create the amount as String
|
33
|
+
amount.is_a?(String) ? amount_str = amount : amount_str = amount.to_s
|
34
|
+
amount_str_not_formatted = amount_str.remove('.').remove(',')
|
35
|
+
|
36
|
+
# Create the full value
|
37
|
+
cents_value = amount_str_not_formatted[amount_str_not_formatted.length-2, amount_str_not_formatted.length-1]
|
38
|
+
integer_value = amount_str_not_formatted[0, amount_str_not_formatted.length-2]
|
39
|
+
|
40
|
+
# The return constraint
|
41
|
+
"#{integer_value}#{cents_value}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Receive it amount in Integer String (like 1000 that means 10,00 or 10.00) and convert to Decimal value
|
45
|
+
def self.to_decimal(amount)
|
46
|
+
# If it was passed a BigDecimal it must be passed to operator format
|
47
|
+
amount = Currency.to_operator_str(amount) if amount.is_a?BigDecimal
|
48
|
+
|
49
|
+
# Base vars
|
50
|
+
aux_amount = amount
|
51
|
+
cents_chars_counter = 2
|
52
|
+
|
53
|
+
# Building the currency str like BigDecimal understands
|
54
|
+
cents_str = aux_amount[aux_amount.length-cents_chars_counter..aux_amount.length]
|
55
|
+
integer_str = aux_amount[0, aux_amount.length-cents_chars_counter]
|
56
|
+
new_amount = "#{integer_str}.#{cents_str}"
|
57
|
+
|
58
|
+
BigDecimal.new new_amount
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get it cents from operator amount formatted
|
62
|
+
def self.get_cents(amount)
|
63
|
+
aux = amount.to_s
|
64
|
+
cents_length = 2
|
65
|
+
aux[aux.length-cents_length, aux.length]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Receive a numeric form operator format & retrieve it
|
69
|
+
def self.have_cents?(amount)
|
70
|
+
aux = "#{amount.to_f/100}" if amount.is_a?(String) || amount.is_a?(Integer)
|
71
|
+
aux = amount.to_s if amount.is_a?Float
|
72
|
+
cents = aux[aux.index('.')+1, aux.length]
|
73
|
+
cents.length == 2 ? true : false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Override ruby Hash Obj
|
2
|
+
class Hash
|
3
|
+
# Attr to be external accessible
|
4
|
+
attr_accessor :get_url_params
|
5
|
+
|
6
|
+
# Convert string keys to symbol keys
|
7
|
+
def it_keys_to_sym
|
8
|
+
self.keys.each do |key|
|
9
|
+
self[key].it_keys_to_sym if self[key].is_a?(Hash)
|
10
|
+
self[(key.to_sym rescue key) || key] = self.delete(key)
|
11
|
+
end
|
12
|
+
|
13
|
+
return self
|
14
|
+
end
|
15
|
+
|
16
|
+
# Convert it keys to get params
|
17
|
+
def it_keys_to_get_param
|
18
|
+
self.it_keys_to_sym
|
19
|
+
self.get_url_params = '?' if self.get_url_params.nil?
|
20
|
+
|
21
|
+
self.keys.each do |key|
|
22
|
+
self.get_url_params = self.get_url_params+'&' unless self.get_url_params.length == 1
|
23
|
+
|
24
|
+
# Nested obj_attrs
|
25
|
+
if self[key].is_a?(Hash)
|
26
|
+
hash_name = key
|
27
|
+
hash_obj = self[key]
|
28
|
+
|
29
|
+
# Hash to GET URL
|
30
|
+
param = to_nested_get_param hash_name, hash_obj
|
31
|
+
end
|
32
|
+
|
33
|
+
self.get_url_params = self.get_url_params+param
|
34
|
+
end
|
35
|
+
|
36
|
+
# Remove the last char: &
|
37
|
+
return self.get_url_params[0..self.get_url_params.length-2]
|
38
|
+
end
|
39
|
+
|
40
|
+
# SetUp a hash to hash URL GET
|
41
|
+
def to_nested_get_param(hash_name, hash_obj)
|
42
|
+
# initial value
|
43
|
+
get_nested_params = ''
|
44
|
+
|
45
|
+
# foreach keys to mount the URL_PARAM
|
46
|
+
hash_obj.keys.each do |key|
|
47
|
+
key_param = hash_obj[key].to_nested_get_param key, hash_obj[key] if hash_obj[key].is_a?(Hash)
|
48
|
+
key_param = "#{hash_name}[#{key}]=#{hash_obj[key]}&" unless hash_obj[key].is_a?(Hash)
|
49
|
+
get_nested_params = get_nested_params+key_param
|
50
|
+
end
|
51
|
+
|
52
|
+
# return
|
53
|
+
get_nested_params
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module NitroPay
|
2
|
+
class Status < NitroPay::Connection
|
3
|
+
# Attrs
|
4
|
+
attr_accessor :message # API response message
|
5
|
+
attr_accessor :http_code # HTTP Code for the request
|
6
|
+
attr_accessor :api_code # Internal system response code
|
7
|
+
attr_accessor :response # the JSON retrieved
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
def initialize(params = {})
|
11
|
+
super # call it super initialize
|
12
|
+
self.path = 'status'
|
13
|
+
check_it
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check it status and 'setup' it attrs
|
17
|
+
def check_it
|
18
|
+
self.path = 'status'
|
19
|
+
resp = get_request
|
20
|
+
hash_resp = JSON.parse(resp).it_keys_to_sym
|
21
|
+
self.http_code = resp.code
|
22
|
+
self.message = "EndPoint not response(connection error): #{self.url_requested}" if self.http_code != 200
|
23
|
+
self.message = hash_resp[:message] if self.http_code == 200
|
24
|
+
self.api_code = hash_resp[:api_code]
|
25
|
+
self.response = hash_resp
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module NitroPay
|
2
|
+
# Transaction Obj, but can Abstract nested objs like Costumer
|
3
|
+
class Transaction < NitroPay::Connection
|
4
|
+
attr_accessor :resp
|
5
|
+
attr_accessor :status # can be API Status: self.status = NitroPay::Status.new OR/AND Transaction Status
|
6
|
+
attr_accessor :sold_items
|
7
|
+
|
8
|
+
# Constructor
|
9
|
+
def initialize(params = {})
|
10
|
+
super # super init call
|
11
|
+
# Base redirect_link if test_env is set (so the redirect is just appended)
|
12
|
+
self.redirect_link = "#{self.end_point_versioned}/transactions" if params[:test_env]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return it RID easily
|
16
|
+
def rid
|
17
|
+
self.resp[:rid]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return it Purchase URL, to pay on the OperatorPage
|
21
|
+
def purchase_url
|
22
|
+
self.resp[:purchase_url]
|
23
|
+
end
|
24
|
+
|
25
|
+
# GET /api/transactions/:rid by it attr
|
26
|
+
def verify
|
27
|
+
auth_hash = {}
|
28
|
+
auth_hash[:auth] = self.request_params[:auth]
|
29
|
+
if self.rid.nil? then return {error:'RID not received'} else self.path = "transactions/#{self.rid}/#{auth_hash.it_keys_to_get_param}" end
|
30
|
+
return self.get_json_request
|
31
|
+
end
|
32
|
+
|
33
|
+
# POST /api/transactions/page return operator page URL, like the Cielo Page
|
34
|
+
def charge_page(full_resp=false)
|
35
|
+
custom_http_params
|
36
|
+
# SetUp redirect dynamic if is test
|
37
|
+
self.request_params[:transaction][:redirect_link] = "#{self.redirect_link}" if self.request_params[:transaction][:test_env]
|
38
|
+
|
39
|
+
# dynamic path (it is written when a specific method use it)
|
40
|
+
self.path = 'transactions/page'
|
41
|
+
|
42
|
+
# using json_request because need only the answer (do not use something like it HTTP Code)
|
43
|
+
self.resp = self.post_json_request unless full_resp
|
44
|
+
self.resp = self.post_request if full_resp
|
45
|
+
self.resp
|
46
|
+
end
|
47
|
+
|
48
|
+
# POST /api/transactions
|
49
|
+
def charge_store(full_resp=false)
|
50
|
+
custom_http_params
|
51
|
+
|
52
|
+
# dynamic path (it is written when a specific method use it)
|
53
|
+
self.path = 'transactions/store'
|
54
|
+
|
55
|
+
# using json_request because need only the answer (do not use something like it HTTP Code)
|
56
|
+
full_resp ? self.resp = self.post_request : self.resp = self.post_json_request
|
57
|
+
|
58
|
+
# return it received resp
|
59
|
+
self.resp
|
60
|
+
end
|
61
|
+
|
62
|
+
# Update the recurrence amount
|
63
|
+
def update_subscription(rid=nil, full_resp=false)
|
64
|
+
# SetUp
|
65
|
+
self.recurrent_rid = rid if rid
|
66
|
+
self.path = "transactions/#{self.recurrent_rid}/subscription"
|
67
|
+
|
68
|
+
# Perform the request
|
69
|
+
full_resp ? self.resp = self.put_request : self.resp = self.put_json_request
|
70
|
+
|
71
|
+
# return it received resp
|
72
|
+
self.resp
|
73
|
+
end
|
74
|
+
|
75
|
+
# Stop a recurrence based on it transaction rid
|
76
|
+
def unsubscribe(rid=nil, full_resp=false)
|
77
|
+
# SetUp
|
78
|
+
self.recurrent_rid = rid if rid
|
79
|
+
self.path = "transactions/#{self.recurrent_rid}/subscription/unsubscribe"
|
80
|
+
|
81
|
+
# Perform the request
|
82
|
+
full_resp ? self.resp = self.delete_request : self.resp = self.delete_json_request
|
83
|
+
|
84
|
+
# return it received resp
|
85
|
+
self.resp
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return the payments executed for the purchase passed
|
89
|
+
def payment_history(rid=nil, full_resp=false)
|
90
|
+
# SetUp
|
91
|
+
self.recurrent_rid = rid if rid
|
92
|
+
self.path = "transactions/#{self.recurrent_rid}/subscription/payment_history"
|
93
|
+
self.path = "#{self.path}#{self.request_params.it_keys_to_get_param}"
|
94
|
+
|
95
|
+
# Perform the request
|
96
|
+
full_resp ? self.resp = self.get_request : self.resp = self.get_json_request
|
97
|
+
|
98
|
+
# return it received resp
|
99
|
+
self.resp
|
100
|
+
end
|
101
|
+
|
102
|
+
# Check if a subscription is up-to-date or have any pending
|
103
|
+
def up_to_date(rid=nil, full_resp=false)
|
104
|
+
# SetUp
|
105
|
+
self.recurrent_rid = rid if rid
|
106
|
+
|
107
|
+
# Create/customize the path & add the auth as param
|
108
|
+
self.path = "transactions/#{self.recurrent_rid}/subscription/up-to-date"
|
109
|
+
self.path = "#{self.path}#{self.request_params.it_keys_to_get_param}"
|
110
|
+
|
111
|
+
# Perform the request
|
112
|
+
full_resp ? self.resp = self.get_request : self.resp = self.get_json_request
|
113
|
+
|
114
|
+
# return it received resp
|
115
|
+
self.resp
|
116
|
+
end
|
117
|
+
|
118
|
+
# ================ STATIC methods ================
|
119
|
+
# GET /api/transactions/:rid by the rid passed
|
120
|
+
def self.find(rid)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/nitro_pay/version.rb
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
BANDEIRA | Autenticação | Número Cartão | Validade | Cod. Segurança
|
2
|
+
VISA | OK | 4012001037141112 | 05/2018 | 123
|
3
|
+
MASTER | OK | 5453010000066167 | 05/2018 | 123
|
4
|
+
VISA | NO | 4012001038443335 | 05/2018 | 123
|
5
|
+
MASTER | NO | 5453010000066167 | 05/2018 | 123
|
6
|
+
AMERICAN EXP| NO | 376449047333005 | 05/2018 | 1234
|
7
|
+
ELO | NO | 6362970000457013 | 05/2018 | 123
|
8
|
+
DINNERS | NO | 36490102462661 | 05/2018 | 123
|
9
|
+
DISCOVER | NO | 6011020000245045 | 05/2018 | 123
|
10
|
+
JCB | NO | 3566007770004971 | 05/2018 | 123
|
11
|
+
AURA | NO |5078601912345600019| 05/2018 | 123
|
data/spec/file_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Helpers
|
2
|
+
def comment_proxy_yml
|
3
|
+
process_file :comment
|
4
|
+
end
|
5
|
+
|
6
|
+
def uncomment_proxy_yml
|
7
|
+
process_file :uncomment
|
8
|
+
end
|
9
|
+
|
10
|
+
def process_file action
|
11
|
+
file_path = './lib/nitro_pay/config/proxy.yml'
|
12
|
+
file = File.open(file_path, 'r+')
|
13
|
+
old_lines = file.readlines
|
14
|
+
new_lines = []
|
15
|
+
|
16
|
+
# Create the new lines
|
17
|
+
if action == :comment
|
18
|
+
old_lines.each do |old_line|
|
19
|
+
unless old_line[0] == '#'
|
20
|
+
old_line = '#' + old_line
|
21
|
+
end
|
22
|
+
|
23
|
+
new_lines << old_line
|
24
|
+
end
|
25
|
+
elsif action == :uncomment
|
26
|
+
old_lines.each do |old_line|
|
27
|
+
new_lines << old_line.remove('#')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
write_lines file_path, new_lines
|
32
|
+
end
|
33
|
+
|
34
|
+
# Write the new lines
|
35
|
+
def write_lines file_path, new_lines
|
36
|
+
File.open(file_path, 'w') do |file|
|
37
|
+
new_lines.each do |new_line|
|
38
|
+
file.puts new_line
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'file_helper'
|
2
|
+
module Helpers
|
3
|
+
def http_success
|
4
|
+
200
|
5
|
+
end
|
6
|
+
|
7
|
+
def accessible? url
|
8
|
+
RestClient.proxy = NitroPay.proxy
|
9
|
+
resp = RestClient.get url
|
10
|
+
RestClient.proxy = nil
|
11
|
+
resp.code == 200 ? true : false
|
12
|
+
end
|
13
|
+
|
14
|
+
def fake_sold_items
|
15
|
+
sold_items = []
|
16
|
+
|
17
|
+
count = 0
|
18
|
+
items_amount = 3
|
19
|
+
|
20
|
+
until count == items_amount do
|
21
|
+
count = count+1
|
22
|
+
rand_count_departments = Random.new.rand(1..10)
|
23
|
+
sold_item = {
|
24
|
+
remote_id: Faker::Number.number(count),
|
25
|
+
name: Faker::Commerce.product_name,
|
26
|
+
description: Faker::Commerce.department(rand_count_departments, rand_count_departments==2)
|
27
|
+
}
|
28
|
+
|
29
|
+
sold_items << sold_item
|
30
|
+
end
|
31
|
+
|
32
|
+
sold_items
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_json url
|
36
|
+
resp = RestClient.get url
|
37
|
+
JSON.parse(resp).it_keys_to_sym
|
38
|
+
end
|
39
|
+
|
40
|
+
def page_transaction_mock(card_brand, amount, redir_link)
|
41
|
+
page = {}
|
42
|
+
page[:transaction] = NitroPay::Transaction.new({
|
43
|
+
card:{brand: card_brand},
|
44
|
+
clients:{name:Faker::Name.name, email:Faker::Internet.free_email, legal_id:CPF.generate},
|
45
|
+
amount: amount, # The last 2 numbers are the cents
|
46
|
+
redirect_link: redir_link # (* optional) used only for CieloPage
|
47
|
+
})
|
48
|
+
|
49
|
+
# Fake SoldItems added
|
50
|
+
page[:transaction].sold_items = fake_sold_items
|
51
|
+
|
52
|
+
# Perform BuyPage
|
53
|
+
page[:resp] = page[:transaction].charge_page
|
54
|
+
page
|
55
|
+
end
|
56
|
+
end
|