justa-ruby 0.1.9

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,71 @@
1
+ module Justa
2
+ class Model < JustaObject
3
+ def create
4
+ update Justa::Request.post(self.class.url, params: to_hash).call(class_name)
5
+ self
6
+ end
7
+
8
+ # def save
9
+ # update Justa::Request.put(url, params: unsaved_attributes).call(class_name)
10
+ # self
11
+ # end
12
+
13
+ def url(*params)
14
+ raise RequestError, "Invalid ID" unless primary_key.present?
15
+
16
+ self.class.url CGI.escape(primary_key.to_s), *params
17
+ end
18
+
19
+ def fetch
20
+ update self.class.find(primary_key, client_key: client_key)
21
+ self
22
+ end
23
+
24
+ def primary_key
25
+ tx_id
26
+ end
27
+
28
+ def class_name
29
+ self.class.to_s.split("::").last
30
+ end
31
+
32
+ class << self
33
+ def create(*args)
34
+ new(*args).create
35
+ end
36
+
37
+ def find_by_id(id, **options)
38
+ raise RequestError, "Invalid ID" unless id.present?
39
+
40
+ Justa::Request.get(url(id), options.merge({ append_document: false })).call underscored_class_name
41
+ end
42
+ alias find find_by_id
43
+
44
+ # def find_by(params = Hash.new, page = nil, count = nil)
45
+ # params = extract_page_count_or_params(page, count, **params)
46
+ # raise RequestError.new('Invalid page count') if params[:page] < 1 or params[:count] < 1
47
+
48
+ # Justa::Request.get(url, params: params).call
49
+ # end
50
+ # alias :find_by_hash :find_by
51
+
52
+ # def all(*args, **params)
53
+ # params = extract_page_count_or_params(*args, **params)
54
+ # find_by params
55
+ # end
56
+ # alias :where :all
57
+
58
+ def url(*params)
59
+ ["/#{CGI.escape class_name}", *params].join "/"
60
+ end
61
+
62
+ def class_name
63
+ name.split("::").last.downcase
64
+ end
65
+
66
+ def underscored_class_name
67
+ name.split("::").last.gsub(/[a-z0-9][A-Z]/) { |s| "#{s[0]}_#{s[1]}" }.downcase
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,143 @@
1
+ module Justa
2
+ class JustaObject
3
+ attr_reader :attributes
4
+
5
+ RESOURCES = Dir[File.expand_path("resources/*.rb", __dir__)].map do |path|
6
+ File.basename(path, ".rb").to_sym
7
+ end.freeze
8
+
9
+ def initialize(response = {})
10
+ # raise MissingCredentialsError.new("Missing :client_key for extra options #{options}") if options && !options[:client_key]
11
+
12
+ @attributes = {}
13
+ @unsaved_attributes = Set.new
14
+
15
+ @client_key = response.dig(:client_key) || Justa.default_client_key # || :default
16
+ update response
17
+ end
18
+
19
+ def []=(key, value)
20
+ @attributes[key] = value
21
+ @unsaved_attributes.add key
22
+ end
23
+
24
+ def empty?
25
+ @attributes.empty?
26
+ end
27
+
28
+ def ==(other)
29
+ self.class == other.class && id == other.id
30
+ end
31
+
32
+ def unsaved_attributes
33
+ Hash[@unsaved_attributes.map do |key|
34
+ [key, to_hash_value(self[key], :unsaved_attributes)]
35
+ end]
36
+ end
37
+
38
+ def to_hash
39
+ Hash[@attributes.map do |key, value|
40
+ [key, to_hash_value(value, :to_hash)]
41
+ end]
42
+ end
43
+
44
+ def to_request_params
45
+ Hash[@attributes.map do |key, value|
46
+ [key.to_s.to_camel(:lower), to_hash_value(value, :to_hash)]
47
+ end]
48
+ end
49
+
50
+ def respond_to?(name, include_all = false)
51
+ return true if name.to_s.end_with? "="
52
+
53
+ @attributes.has_key?(name.to_s) || super
54
+ end
55
+
56
+ # def to_s
57
+ # attributes_str = ''
58
+ # (attributes.keys - ['id', 'object']).sort.each do |key|
59
+ # attributes_str += " \033[1;33m#{key}:\033[0m#{self[key].inspect}" unless self[key].nil?
60
+ # end
61
+ # "\033[1;31m#<#{self.class.name}:\033[0;32m#{id}#{attributes_str}\033[0m\033[0m\033[1;31m>\033[0;32m"
62
+ # end
63
+ # # alias :inspect :to_s
64
+
65
+ protected
66
+
67
+ def update(attributes)
68
+ attributes = attributes.convert_from_request if attributes.respond_to? :convert_from_request
69
+ removed_attributes = @attributes.keys - attributes.to_hash.keys
70
+
71
+ removed_attributes.each do |key|
72
+ @attributes.delete key
73
+ end
74
+
75
+ attributes.each do |key, value|
76
+ key = key.to_s.to_snake
77
+
78
+ @attributes[key] = JustaObject.convert(value, Util.singularize(key), @client_key)
79
+ @unsaved_attributes.delete key
80
+ end
81
+ end
82
+
83
+ def to_hash_value(value, type)
84
+ case value
85
+ when JustaObject
86
+ value.send type
87
+ when Array
88
+ value.map do |v|
89
+ to_hash_value v, type
90
+ end
91
+ else
92
+ value
93
+ end
94
+ end
95
+
96
+ def method_missing(name, *args, &block)
97
+ name = name.to_s
98
+
99
+ unless block_given?
100
+ if name.end_with?("=") && args.size == 1
101
+ attribute_name = name[0...-1]
102
+ return self[attribute_name] = args[0]
103
+ end
104
+
105
+ return self[name] || self[name.to_sym] if args.size == 0
106
+ end
107
+
108
+ return attributes.public_send name, *args, &block if attributes.respond_to? name
109
+
110
+ super name, *args, &block
111
+ end
112
+
113
+ class << self
114
+ def convert(response, resource_name = nil, client_key = nil)
115
+ case response
116
+ when Array
117
+ response.map { |i| convert i, resource_name, client_key }
118
+ when Hash
119
+ resource_class_for(resource_name).new(response.merge({ client_key: client_key }))
120
+ else
121
+ response
122
+ end
123
+ end
124
+
125
+ protected
126
+
127
+ def resource_class_for(resource_name)
128
+ return Justa::JustaObject if resource_name.nil?
129
+
130
+ if RESOURCES.include? resource_name.to_sym
131
+ Object.const_get "Justa::#{capitalize_name resource_name}"
132
+ else
133
+ Justa::JustaObject
134
+ end
135
+ end
136
+
137
+ def capitalize_name(name)
138
+ name.split("_").collect(&:capitalize).join
139
+ # name.gsub(/(\A\w|\_\w)/){ |str| str.gsub('_', '').upcase }
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,15 @@
1
+ module Justa
2
+ class OrderCommom < Model
3
+ # def self.url(*params)
4
+ # ["/#{ CGI.escape underscored_class_name }", *params].join '/'
5
+ # end
6
+
7
+ #
8
+ # Defines primary key for model
9
+ #
10
+ # @return [String] Return the primary_key field value
11
+ def primary_key
12
+ tx_id
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,114 @@
1
+ require "uri"
2
+ require "rest_client"
3
+ require "multi_json"
4
+
5
+ module Justa
6
+ class Request
7
+ DEFAULT_HEADERS = {
8
+ "Content-Type" => "application/json",
9
+ "Accept" => "application/json",
10
+ "User-Agent" => "justa-ruby/#{Justa::VERSION}"
11
+ }.freeze
12
+
13
+ attr_accessor :path, :method, :parameters, :headers, :query
14
+
15
+ def initialize(path, method, options = {})
16
+ @path = path
17
+ @method = method
18
+ @parameters = options[:params] || nil
19
+ @query = options[:query] || {}
20
+ @headers = options[:headers] || {}
21
+ @auth = options[:auth] || false
22
+ @append_document = options.fetch(:append_document, true)
23
+ @client_key = options[:client_key] || @parameters && (@parameters[:client_key] || @parameters["client_key"]) || Justa.default_client_key
24
+ end
25
+
26
+ def run
27
+ response = RestClient::Request.execute request_params
28
+ MultiJson.decode response.body
29
+ rescue RestClient::Exception => e
30
+ begin
31
+ parsed_error = MultiJson.decode e.http_body
32
+
33
+ if e.is_a? RestClient::ResourceNotFound
34
+ if parsed_error["message"]
35
+ raise Justa::NotFound.new(parsed_error, request_params, e)
36
+ else
37
+ raise Justa::NotFound.new(nil, request_params, e)
38
+ end
39
+ elsif parsed_error["message"]
40
+ raise Justa::ResponseError.new(request_params, e, parsed_error["message"])
41
+ else
42
+ raise Justa::ValidationError, parsed_error
43
+ end
44
+ rescue MultiJson::ParseError
45
+ raise Justa::ResponseError.new(request_params, e)
46
+ end
47
+ rescue MultiJson::ParseError
48
+ return {} unless response.code < 200 && response.code > 299
49
+
50
+ raise Justa::ResponseError.new(request_params, response)
51
+ rescue SocketError
52
+ raise Justa::ConnectionError, $!
53
+ rescue RestClient::ServerBrokeConnection
54
+ raise Justa::ConnectionError, $!
55
+ end
56
+
57
+ def call(ressource_name)
58
+ JustaObject.convert run, ressource_name, @client_key
59
+ end
60
+
61
+ def self.get(url, options = {})
62
+ new url, "GET", options
63
+ end
64
+
65
+ def self.auth(url, options = {})
66
+ options[:auth] = true
67
+ new url, "POST", options
68
+ end
69
+
70
+ def self.post(url, options = {})
71
+ new url, "POST", options
72
+ end
73
+
74
+ def self.put(url, options = {})
75
+ new url, "PUT", options
76
+ end
77
+
78
+ def self.patch(url, options = {})
79
+ new url, "PATCH", options
80
+ end
81
+
82
+ def self.delete(url, options = {})
83
+ new url, "DELETE", options
84
+ end
85
+
86
+ def request_params
87
+ aux = {
88
+ method: method,
89
+ url: full_api_url
90
+ }
91
+
92
+ @parameters
93
+
94
+ if !@auth && @parameters && method == "POST"
95
+ aux.merge!({ payload: MultiJson.encode(@parameters.to_request_params) })
96
+ elsif @parameters
97
+ aux.merge!({ payload: @parameters })
98
+ end
99
+
100
+ extra_headers = DEFAULT_HEADERS.merge(@headers)
101
+ extra_headers[:authorization] = "Bearer #{Justa::TokenManager.token_for @client_key}" unless @auth
102
+ extra_headers["integratorId"] = Justa.integrator_id unless @auth
103
+
104
+ aux.merge!({ headers: extra_headers })
105
+ aux
106
+ end
107
+
108
+ def must_append_document?; end
109
+
110
+ def full_api_url
111
+ Justa.api_endpoint + "/payment-provider/api" + @path + (!@auth && @append_document ? ("/" + TokenManager.client_for(@client_key).document.to_s) : "")
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,30 @@
1
+ module Justa
2
+ class Pix < OrderCommom
3
+ # def self.url(*params)
4
+ # ["/#{ CGI.escape underscored_class_name }", *params].join '/'
5
+ # end
6
+
7
+ def primary_key
8
+ tx_id
9
+ end
10
+
11
+ #
12
+ # Request approve to Justa api for this pix ( Only in DEVELOPMENT)
13
+ #
14
+ # @param [Hash] params Parameters for function
15
+ # @option params [Numeric] :value (Required) The amount that will be approved in cents format
16
+ # @option params [String] :end_to_end_id (Optional) The reference for the payment transaction
17
+ # @return [Pix] Return model pix instance
18
+ # @example Pay 1.0 of pix
19
+ # pix_instance.approve(value: 1.0)
20
+ def approve(**params)
21
+ raise JustaError, "Can't approve value in Production environment" if Justa.production?
22
+ raise ParamError.new("Missing value param", :value, :integer, url("approve")) unless params.has_key? :value
23
+
24
+ Justa::Request.post(url("approve"),
25
+ { append_document: false,
26
+ params: params.merge({ client_key: @client_key }) }).call underscored_class_name
27
+ fetch
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,135 @@
1
+ module Justa
2
+ #
3
+ # Class to manage Tokens with singleton structure
4
+ #
5
+ class TokenManager
6
+ attr_reader :authenticators, :mutex
7
+
8
+ # private_class_method :new
9
+
10
+ #
11
+ # Initializes TokenManager
12
+ #
13
+ # This class builds authentication array with a mutex to share tokens
14
+ def initialize
15
+ tokens = nil
16
+ if Justa.credentials
17
+ case Justa.credentials
18
+ when Array
19
+ tokens = Justa.credentials
20
+ when Hash
21
+ tokens = [Justa.credentials]
22
+ end
23
+ else
24
+ tokens = [{
25
+ username: Justa.username,
26
+ password: Justa.password,
27
+ client_id: Justa.client_id,
28
+ client_secret: Justa.client_secret,
29
+ integrator_id: Justa.integrator_id,
30
+ document: Justa.document,
31
+ key: :default,
32
+ default: true
33
+ }]
34
+ end
35
+
36
+ @mutex = Mutex.new
37
+ @authenticators = nil
38
+ setup_autenticators tokens
39
+ end
40
+
41
+ #
42
+ # Sets authenticators based on tokens passed by constructor
43
+ #
44
+ # @param [Array] tokens Array of tokens to be registered as clients
45
+ #
46
+ # @return [Array] Authenticators array
47
+ #
48
+ def setup_autenticators(tokens)
49
+ return @authenticators if @authenticators
50
+
51
+ tokens = tokens.map { |t| Justa::Client.new(**t) }
52
+ @mutex.synchronize do
53
+ @authenticators = []
54
+ tokens.each do |client|
55
+ @authenticators << Authenticator.new(client)
56
+ end
57
+ end
58
+ end
59
+
60
+ #
61
+ # Find a token for a specific Client Key
62
+ #
63
+ # @param [Symbol] key Client Key to be found in Authenticators Array
64
+ #
65
+ # @return [String] Auth token
66
+ #
67
+ def self.token_for(key = Justa.default_client_key)
68
+ instance unless @instance
69
+ k = Justa::Util.to_sym(key)
70
+ raise MissingCredentialsError, "Missing credentials for key: '#{key}'" unless @instance.authenticators
71
+
72
+ @instance.mutex.synchronize do
73
+ auth = @instance.authenticators.find { |obj| obj.key == k }
74
+
75
+ raise MissingCredentialsError, "Missing credentials for key: '#{key}'" if auth.blank?
76
+
77
+ auth.token
78
+ end
79
+ end
80
+
81
+ #
82
+ # Registers a new client to be used
83
+ #
84
+ # @param [Justa::Client] client Client instance to be registered in TokenManager
85
+ #
86
+ # @return [Array] Authenticators array
87
+ # @example Ads a new client to be used in calls to Justa Api
88
+ # Justa::TokenManager.add_client Client.new(client_id: <CLIENT_KEY>, key: :<CLIENT_ALIAS>)
89
+ def self.add_client(client)
90
+ instance unless @instance
91
+ client = (client.is_a? Justa::Client) ? client : Justa::Client.new(**client)
92
+
93
+ raise ParamError.new("Client key '#{client.key}' already exists", "Key", "") if client_for client.key
94
+
95
+ @instance.mutex.synchronize do
96
+ @instance.authenticators << Authenticator.new(client)
97
+ end
98
+ end
99
+
100
+ #
101
+ # Find a Client for a specific Key
102
+ #
103
+ # @param [Symbol] key Client Key to be found in Authenticators Array ( Defaults to Justa.default_client_key)
104
+ #
105
+ # @return [Justa::Client] Client instance registed with the key passed
106
+ #
107
+ def self.client_for(key = Justa.default_client_key)
108
+ k = Justa::Util.to_sym(key)
109
+ instance unless @instance
110
+ return nil unless @instance.authenticators.present?
111
+
112
+ @instance.mutex.synchronize do
113
+ auth = @instance.authenticators.find { |obj| obj.key == k }
114
+ auth&.client
115
+ end
116
+ end
117
+
118
+ #
119
+ # Find a Client Type for a specific Key
120
+ #
121
+ # @param [Symbol] key Client Key to be found in Authenticators Array ( Defaults to Justa.default_client_key)
122
+ #
123
+ # @return [Symbol] Return the cleint type ( :pdv or :e_commerce) ( Defaults to :pdv if not found)
124
+ #
125
+ def self.client_type_for(key = Justa.default_client_key)
126
+ client_for(key)&.type || :pdv
127
+ end
128
+
129
+ def self.instance
130
+ return @instance if @instance
131
+
132
+ @instance = TokenManager.new
133
+ end
134
+ end
135
+ end
data/lib/justa/util.rb ADDED
@@ -0,0 +1,79 @@
1
+ module Justa
2
+ class Util
3
+ class << self
4
+ SINGULARS = {
5
+ "/s$/i" => "",
6
+ "/(ss)$/i" => '\1',
7
+ "/(n)ews$/i" => '\1ews',
8
+ "/([ti])a$/i" => '\1um',
9
+ "/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i" => '\1sis',
10
+ "/(^analy)(sis|ses)$/i" => '\1sis',
11
+ "/([^f])ves$/i" => '\1fe',
12
+ "/(hive)s$/i" => '\1',
13
+ "/(tive)s$/i" => '\1',
14
+ "/([lr])ves$/i" => '\1f',
15
+ "/([^aeiouy]|qu)ies$/i" => '\1y',
16
+ "/(s)eries$/i" => '\1eries',
17
+ "/(m)ovies$/i" => '\1ovie',
18
+ "/(x|ch|ss|sh)es$/i" => '\1',
19
+ "/^(m|l)ice$/i" => '\1ouse', -
20
+ "/(bus)(es)?$/i" => '\1',
21
+ "/(o)es$/i" => '\1',
22
+ "/(shoe)s$/i" => '\1',
23
+ "/(cris|test)(is|es)$/i" => '\1is',
24
+ "/^(a)x[ie]s$/i" => '\1xis',
25
+ "/(octop|vir)(us|i)$/i" => '\1us',
26
+ "/(alias|status)(es)?$/i" => '\1',
27
+ "/^(ox)en/i" => '\1',
28
+ "/(vert|ind)ices$/i" => '\1ex',
29
+ "/(matr)ices$/i" => '\1ix',
30
+ "/(quiz)zes$/i" => '\1',
31
+ "/(database)s$/i" => '\1'
32
+ }
33
+
34
+ def singularize(resource)
35
+ out = ""
36
+ SINGULARS.keys.each do |key|
37
+ out = resource.to_s.gsub(/s$/, SINGULARS[key])
38
+ break out if out != resource
39
+ end
40
+ case resource.class
41
+ when Symbol
42
+ return out.to_sym
43
+ end
44
+ out
45
+ end
46
+
47
+ def to_sym(string)
48
+ string.to_s.strip.gsub(" -", "_").to_sym
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ class Hash
55
+ def except_nested(key)
56
+ r = Marshal.load(Marshal.dump(self))
57
+ r.except_nested!(key)
58
+ end
59
+
60
+ def except_nested!(key)
61
+ reject! { |k, _| k == key || k.to_s == key }
62
+ each do |_, v|
63
+ v.except_nested!(key) if v.is_a?(Hash)
64
+ v.map! { |obj| obj.except_nested!(key) if obj.is_a?(Hash) } if v.is_a?(Array)
65
+ end
66
+ end
67
+
68
+ def to_request_params
69
+ Hash[map do |k, v|
70
+ [k.to_s.to_camel(:lower), v]
71
+ end]
72
+ end
73
+
74
+ def convert_from_request
75
+ Hash[map do |k, v|
76
+ [k.to_s.to_snake, v]
77
+ end]
78
+ end
79
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justa
4
+ VERSION = "0.1.9"
5
+ end
data/lib/justa.rb ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "justa/version"
4
+ require_relative "justa/authenticator"
5
+ require_relative "justa/request"
6
+ require_relative "justa/object"
7
+ require_relative "justa/model"
8
+ require_relative "justa/core_ext"
9
+ require_relative "justa/errors"
10
+ require_relative "justa/util"
11
+ require_relative "justa/token_manager"
12
+ require_relative "justa/order_commom"
13
+
14
+ Dir[File.expand_path("justa/resources/*.rb", __dir__)].map do |path|
15
+ require path
16
+ end
17
+
18
+ module Justa
19
+ class Error < StandardError; end
20
+
21
+ class << self
22
+ attr_accessor :username, :password, :client_id, :client_secret, :integrator_id, :callback_url, :credentials,
23
+ :default_client_key, :document
24
+ attr_reader :api_endpoint
25
+
26
+ def production?
27
+ env = nil
28
+ begin
29
+ env = ENV["RACK_ENV"] == "production" ||
30
+ ENV["RAILS_ENV"] == "production" ||
31
+ ENV["PRODUCTION"] ||
32
+ ENV["production"] || (Rails.env.production? if Object.const_defined?("::Rails"))
33
+ rescue NameError => e
34
+ return false
35
+ end
36
+
37
+ env || false
38
+ end
39
+ end
40
+
41
+ @default_client_key = :default
42
+
43
+ @api_endpoint = Justa.production? ? "https://gateway.justa.com.vc" : "https://integrador.staging.justa.com.vc"
44
+
45
+ puts "Running on production" if production?
46
+ end
data/main.rb ADDED
@@ -0,0 +1,45 @@
1
+ require_relative 'lib/Justa'
2
+
3
+ Justa.secret_key = 'Q__g6ie850Pv4jVBQUeyAMA_mlJH3xG1Acg2FiPU_lYpVol9z69RHmOE4BZDMaHVGsMEg1s9BI5pvgKkCAWDdw'
4
+ Justa.access_key='CyUqG2hSfQJbE8OTWfy1fQ'
5
+ Justa.client_id='ubceuMCPeVygHW8xynrFYzbF3JKE1zlnpjNBReM6gDz0jzQ-5clVgRdtRRDJ5yWENkGMnC3VI2wQSyu_507g5c9ujS6D5eOSBVheKbMVbEW8qk8dYb41yqgl7o9xv-puarpHV7Dk3jv6n6_HZlAl4S8gHjNsayuj2RSqT8AbtY4'
6
+
7
+
8
+ # Justa::Authenticator.instance()
9
+
10
+ # Justa::Authenticator.headers
11
+ sh = Justa::Order.new({
12
+ order_ref: "Justapag-001",
13
+ wallet: "Justa-pagador",
14
+ total: 0.51,
15
+ items: [
16
+ {
17
+ item_title: "Item 1",
18
+ unit_price: 0.30,
19
+ quantity: 1
20
+ },
21
+ {
22
+ item_title: "Item 2",
23
+ unit_price: 0.20,
24
+ quantity: 1
25
+ },
26
+ {
27
+ item_title: "Item 3",
28
+ unit_price: 0.01,
29
+ quantity: 1
30
+ }
31
+ ],
32
+ buyer: {
33
+ name: "Justa PDV",
34
+ cpf_cnpj: "121.191.870-02",
35
+ email: "Justa-pagador@Justa.com.br",
36
+ phone: "+55 11 99999-9999"
37
+ }
38
+ }).create
39
+
40
+ puts sh
41
+ # Justa.api_endpoint='https://postman-echo.com/get'
42
+
43
+ ord = Justa::Order.find_by_id(sh.order_id)
44
+
45
+ puts ord