conexa 0.0.6 → 0.0.8

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.
@@ -2,6 +2,8 @@ require 'jwt'
2
2
 
3
3
 
4
4
  module Conexa
5
+ # Valid client types for authentication
6
+ CLIENT_TYPES = [:pdv, :e_comerce].freeze
5
7
  #
6
8
  # Class to hold client authetication data
7
9
  #
data/lib/conexa/model.rb CHANGED
@@ -1,5 +1,53 @@
1
1
  module Conexa
2
+ # Base class for all API resources (Customer, Charge, Contract, etc.)
3
+ #
4
+ # == Attribute Access
5
+ #
6
+ # Attributes are automatically accessible via method_missing:
7
+ # customer.name # => "Empresa ABC"
8
+ # customer.company_id # => 3
9
+ # customer.is_active # => true
10
+ #
11
+ # The API returns camelCase (companyId), ConexaObject converts to snake_case
12
+ # (company_id), and method_missing handles the lookup transparently.
13
+ #
14
+ # == Primary Key
15
+ #
16
+ # Each resource needs primary_key_attribute for :id alias and operations
17
+ # that require the resource ID (destroy, save, fetch, etc.):
18
+ #
19
+ # class Charge < Model
20
+ # primary_key_attribute :charge_id
21
+ # end
22
+ #
23
+ # charge.id # => 123 (alias for charge_id)
24
+ # charge.charge_id # => 123
25
+ # charge.chargeId # => 123 (camelCase alias for backwards compat)
26
+ #
27
+ # == Why explicit primary_key_attribute?
28
+ #
29
+ # The default primary_key_name generates "classname_id" (e.g., "charge_id"),
30
+ # but compound names like RecurringSale would generate "recurringsale_id"
31
+ # instead of "recurring_sale_id". Explicit declaration ensures correctness.
32
+ #
2
33
  class Model < ConexaObject
34
+ class << self
35
+ # DSL for primary key attribute with :id alias
36
+ # @example
37
+ # primary_key_attribute :charge_id
38
+ # # Generates: charge_id method + chargeId alias + id alias
39
+ def primary_key_attribute(snake_name)
40
+ camel_name = Util.camelize_str(snake_name.to_s)
41
+
42
+ define_method(snake_name) do
43
+ @attributes[snake_name.to_s]
44
+ end
45
+
46
+ alias_method camel_name.to_sym, snake_name
47
+ alias_method :id, snake_name
48
+ end
49
+ end
50
+
3
51
  def create
4
52
  set_primary_key Conexa::Request.post(self.class.show_url, params: to_hash).call(class_name).attributes['id']
5
53
  fetch
@@ -29,7 +77,7 @@ module Conexa
29
77
  end
30
78
 
31
79
  def primary_key_name
32
- class_name.downcase + "_id"
80
+ Util.to_snake_case(class_name) + "_id"
33
81
  end
34
82
 
35
83
  def class_name
@@ -39,6 +87,7 @@ module Conexa
39
87
  def destroy
40
88
  raise RequestError.new('Invalid ID') unless id.present?
41
89
  update Conexa::Request.delete( self.class.show_url(primary_key) ).call(class_name)
90
+ self
42
91
  end
43
92
 
44
93
  class << self
@@ -68,7 +117,9 @@ module Conexa
68
117
  alias :where :all
69
118
 
70
119
  def destroy id
71
- self.new(id: id).destroy
120
+ instance = self.new
121
+ instance.set_primary_key(id)
122
+ instance.destroy
72
123
  end
73
124
 
74
125
  def url(*params)
data/lib/conexa/object.rb CHANGED
@@ -1,4 +1,21 @@
1
1
  module Conexa
2
+ # Base class for all Conexa objects with dynamic attribute access
3
+ #
4
+ # == Attribute Access via method_missing
5
+ #
6
+ # Attributes can be accessed using either snake_case or camelCase:
7
+ # customer.company_id # => 3
8
+ # customer.companyId # => 3 (converted to snake_case internally)
9
+ #
10
+ # The API returns camelCase attributes which are stored as snake_case.
11
+ # method_missing automatically converts any camelCase calls to snake_case.
12
+ #
13
+ # == Attribute Assignment
14
+ #
15
+ # Attributes can be set using snake_case:
16
+ # customer.name = "New Name"
17
+ # customer.save
18
+ #
2
19
  class ConexaObject
3
20
  attr_reader :attributes
4
21
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conexa
4
+ # Represents an address object (embedded in Customer, etc.)
5
+ # Not a standalone API resource - used for serialization/deserialization
6
+ class Address < ConexaObject
7
+ # @return [String, nil] CEP/ZIP code
8
+ # @return [String, nil] Street name
9
+ # @return [String, nil] Street number
10
+ # @return [String, nil] Neighborhood
11
+ # @return [String, nil] City
12
+ # @return [String, nil] State abbreviation (UF)
13
+ # @return [String, nil] Country (for foreigners)
14
+ # @return [String, nil] Additional details (complement)
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conexa
4
+ # Authentication resource for username/password authentication
5
+ #
6
+ # @example Authenticate with credentials
7
+ # auth = Conexa::Auth.login(username: 'admin', password: 'secret')
8
+ # auth.access_token # => "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
9
+ # auth.expires_in # => 28800
10
+ #
11
+ # @note For most cases, use Application Token instead (configured in Conexa.configure)
12
+ class Auth < ConexaObject
13
+ class << self
14
+ # Authenticate with username and password
15
+ #
16
+ # @param username [String] Login username (admin or employee email)
17
+ # @param password [String] Password
18
+ # @return [Auth] Authentication result with access_token
19
+ # @raise [Conexa::AuthenticationError] if credentials are invalid
20
+ #
21
+ # @example
22
+ # auth = Conexa::Auth.login(username: 'admin', password: 'mypassword')
23
+ # # Use the token for subsequent requests
24
+ # Conexa.configure { |c| c.api_token = auth.access_token }
25
+ def login(username:, password:)
26
+ Conexa::Request.auth('/auth', params: {
27
+ username: username,
28
+ password: password
29
+ }).call('auth')
30
+ end
31
+
32
+ alias_method :authenticate, :login
33
+ end
34
+
35
+ # All attributes (user, token_type, access_token, expires_in)
36
+ # are accessible via method_missing
37
+ end
38
+ end
@@ -1,31 +1,106 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Conexa
4
+ # Charge resource for billing/invoices
5
+ #
6
+ # @example Retrieve a charge
7
+ # charge = Conexa::Charge.find(789)
8
+ # charge.status # => "pending"
9
+ #
10
+ # @example List charges
11
+ # charges = Conexa::Charge.all(customer_id: [127], status: 'pending')
12
+ #
13
+ # @example Settle (pay) a charge
14
+ # Conexa::Charge.settle(789)
15
+ #
16
+ # @!attribute [r] charge_id
17
+ # @return [Integer] Charge ID (also accessible as #id)
18
+ # @!attribute [r] customer_id
19
+ # @return [Integer] Customer ID
20
+ # @!attribute [r] status
21
+ # @return [String] Status: pending, paid, overdue, cancelled
22
+ # @!attribute [r] amount
23
+ # @return [Float] Charge amount
24
+ # @!attribute [r] due_date
25
+ # @return [String] Due date
26
+ # @!attribute [r] paid_at
27
+ # @return [String, nil] Payment date
28
+ #
2
29
  class Charge < Model
3
- # Settle (pay) a charge
4
- # @param params [Hash] optional parameters (e.g., payment details)
30
+ primary_key_attribute :charge_id
31
+
32
+ # Check if charge is paid
33
+ # @return [Boolean]
34
+ def paid?
35
+ status == 'paid'
36
+ end
37
+
38
+ # Check if charge is pending
39
+ # @return [Boolean]
40
+ def pending?
41
+ status == 'pending'
42
+ end
43
+
44
+ # Check if charge is overdue
45
+ # @return [Boolean]
46
+ def overdue?
47
+ status == 'overdue'
48
+ end
49
+
50
+ # Settle (pay) this charge
51
+ # @param params [Hash] optional payment details
5
52
  # @return [self]
6
53
  def settle(params = {})
7
54
  Conexa::Request.post(self.class.show_url("settle", primary_key), params: params).call(class_name)
8
55
  self
9
56
  end
10
57
 
11
- # Get PIX QR Code for the charge
12
- # @return [ConexaObject] PIX data including QR code
58
+ # Get PIX QR Code for this charge
59
+ # @return [ConexaObject] PIX data including qr_code and qr_code_base64
13
60
  def pix
14
61
  Conexa::Request.get(self.class.show_url("pix", primary_key)).call("pix")
15
62
  end
16
63
 
64
+ # Cancel this charge
65
+ # @return [self]
66
+ def cancel
67
+ Conexa::Request.post(self.class.show_url("cancel", primary_key)).call(class_name)
68
+ self
69
+ end
70
+
71
+ # Send charge notification by email
72
+ # @return [self]
73
+ def send_email
74
+ Conexa::Request.post(self.class.show_url("sendEmail", primary_key)).call(class_name)
75
+ self
76
+ end
77
+
17
78
  class << self
18
79
  # Settle a charge by ID
19
80
  # @param id [Integer, String] charge ID
20
- # @param params [Hash] optional parameters
81
+ # @param params [Hash] optional payment details
21
82
  # @return [Charge]
22
83
  def settle(id, params = {})
23
84
  find(id).settle(params)
24
85
  end
25
86
 
26
- # Get PIX QR Code for a charge by ID
87
+ # Cancel a charge by ID
88
+ # @param id [Integer, String] charge ID
89
+ # @return [Charge]
90
+ def cancel(id)
91
+ find(id).cancel
92
+ end
93
+
94
+ # Send email for a charge by ID
95
+ # @param id [Integer, String] charge ID
96
+ # @return [Charge]
97
+ def send_email(id)
98
+ find(id).send_email
99
+ end
100
+
101
+ # Get PIX for a charge by ID
27
102
  # @param id [Integer, String] charge ID
28
- # @return [ConexaObject] PIX data including QR code
103
+ # @return [ConexaObject]
29
104
  def pix(id)
30
105
  find(id).pix
31
106
  end
@@ -1,7 +1,55 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Conexa
4
+ # Contract resource for recurring billing contracts
5
+ #
6
+ # @example Create a contract
7
+ # contract = Conexa::Contract.create(
8
+ # customer_id: 127,
9
+ # plan_id: 5,
10
+ # start_date: '2024-01-01',
11
+ # payment_day: 10
12
+ # )
13
+ #
14
+ # @example End a contract
15
+ # Conexa::Contract.end_contract(456, end_date: '2024-12-31')
16
+ #
17
+ # @!attribute [r] contract_id
18
+ # @return [Integer] Contract ID (also accessible as #id)
19
+ # @!attribute [r] customer_id
20
+ # @return [Integer] Customer ID
21
+ # @!attribute [r] plan_id
22
+ # @return [Integer, nil] Plan ID
23
+ # @!attribute [r] status
24
+ # @return [String] Status: active, ended, cancelled
25
+ # @!attribute [r] start_date
26
+ # @return [String] Start date
27
+ # @!attribute [r] end_date
28
+ # @return [String, nil] End date
29
+ # @!attribute [r] payment_day
30
+ # @return [Integer] Payment day (1-28)
31
+ # @!attribute [r] value
32
+ # @return [Float] Contract value
33
+ # @!attribute [r] billing_day
34
+ # @return [Integer] Billing day
35
+ #
2
36
  class Contract < Model
3
- # End/terminate a contract
4
- # @param params [Hash] optional parameters (e.g., endDate, reason)
37
+ primary_key_attribute :contract_id
38
+
39
+ # Check if contract is active
40
+ # @return [Boolean]
41
+ def active?
42
+ status == 'active'
43
+ end
44
+
45
+ # Check if contract is cancelled/ended
46
+ # @return [Boolean]
47
+ def ended?
48
+ status == 'ended' || status == 'cancelled'
49
+ end
50
+
51
+ # End/terminate this contract
52
+ # @param params [Hash] options including :end_date, :reason
5
53
  # @return [self]
6
54
  def end_contract(params = {})
7
55
  Conexa::Request.post(self.class.show_url("end", primary_key), params: params).call(class_name)
@@ -11,11 +59,18 @@ module Conexa
11
59
  class << self
12
60
  # End a contract by ID
13
61
  # @param id [Integer, String] contract ID
14
- # @param params [Hash] optional parameters (e.g., endDate, reason)
62
+ # @param params [Hash] options including :end_date, :reason
15
63
  # @return [Contract]
16
64
  def end_contract(id, params = {})
17
65
  find(id).end_contract(params)
18
66
  end
67
+
68
+ # Create contract with custom product items
69
+ # @param params [Hash] contract params including :items array
70
+ # @return [Contract]
71
+ def create_with_products(params = {})
72
+ create(params)
73
+ end
19
74
  end
20
75
  end
21
76
  end
@@ -1,5 +1,7 @@
1
1
  module Conexa
2
2
  class CreditCard < Model
3
+ primary_key_attribute :credit_card_id
4
+
3
5
  class << self
4
6
  def url(*params)
5
7
  ["/creditCard", *params].join '/'
@@ -1,5 +1,97 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Conexa
2
- class Customer < Model
4
+ # Customer resource for managing clients in Conexa
5
+ #
6
+ # @example Create a customer (Legal Person - PJ)
7
+ # customer = Conexa::Customer.create(
8
+ # company_id: 3,
9
+ # name: 'Empresa ABC Ltda',
10
+ # legal_person: { cnpj: '99.557.155/0001-90' }
11
+ # )
12
+ #
13
+ # @example Retrieve a customer
14
+ # customer = Conexa::Customer.find(127)
15
+ # customer.name # => "Empresa ABC Ltda"
16
+ #
17
+ # @example List customers
18
+ # customers = Conexa::Customer.all(company_id: [3], is_active: true)
19
+ #
20
+ # @!attribute [r] customer_id
21
+ # @return [Integer] Customer ID (also accessible as #id)
22
+ # @!attribute [r] company_id
23
+ # @return [Integer] Company/Unit ID
24
+ # @!attribute [r] name
25
+ # @return [String] Customer name
26
+ # @!attribute [r] trade_name
27
+ # @return [String, nil] Trade name
28
+ # @!attribute [r] has_login_access
29
+ # @return [Boolean] Whether customer has login access
30
+ # @!attribute [r] is_active
31
+ # @return [Boolean] Whether customer is active
32
+ # @!attribute [r] is_blocked
33
+ # @return [Boolean] Whether customer is blocked
34
+ # @!attribute [r] is_juridical_person
35
+ # @return [Boolean] Whether customer is a legal person (PJ)
36
+ # @!attribute [r] is_foreign
37
+ # @return [Boolean] Whether customer is a foreigner
38
+ # @!attribute [r] legal_person
39
+ # @return [Hash, nil] Legal person data (cnpj, foundation_date, etc.)
40
+ # @!attribute [r] natural_person
41
+ # @return [Hash, nil] Natural person data (cpf, birth_date, etc.)
42
+ # @!attribute [r] created_at
43
+ # @return [String, nil] Created at timestamp (W3C format)
44
+ #
45
+ class Customer < Model
46
+ primary_key_attribute :customer_id
47
+
48
+ # @return [Array<String>] Phone numbers (empty array if null)
49
+ def phones
50
+ @attributes['phones'] || []
51
+ end
52
+
53
+ # @return [Array<String>] Message emails (empty array if null)
54
+ def emails_message
55
+ @attributes['emails_message'] || []
56
+ end
57
+
58
+ # @return [Array<String>] Financial emails (empty array if null)
59
+ def emails_financial_messages
60
+ @attributes['emails_financial_messages'] || []
61
+ end
62
+
63
+ # @return [Array<Integer>] Tag IDs (empty array if null)
64
+ def tags_id
65
+ @attributes['tags_id'] || []
66
+ end
67
+
68
+ # @return [Address, nil] Customer address
69
+ def address
70
+ return nil unless @attributes['address']
71
+ Address.new(@attributes['address'])
72
+ end
73
+
74
+ class << self
75
+ # List persons (requesters) for a customer
76
+ # @param customer_id [Integer] Customer ID
77
+ # @return [Result] List of persons
78
+ def persons(customer_id)
79
+ Conexa::Person.all(customer_id: customer_id)
80
+ end
81
+
82
+ # List contracts for a customer
83
+ # @param customer_id [Integer] Customer ID
84
+ # @return [Result] List of contracts
85
+ def contracts(customer_id)
86
+ Conexa::Contract.all(customer_id: [customer_id])
87
+ end
3
88
 
89
+ # List charges for a customer
90
+ # @param customer_id [Integer] Customer ID
91
+ # @return [Result] List of charges
92
+ def charges(customer_id)
93
+ Conexa::Charge.all(customer_id: [customer_id])
94
+ end
95
+ end
4
96
  end
5
97
  end
@@ -1,5 +1,7 @@
1
1
  module Conexa
2
2
  class InvoicingMethod < Model
3
+ primary_key_attribute :invoicing_method_id
4
+
3
5
  class << self
4
6
  def url(*params)
5
7
  ["/invoicingMethods", *params].join '/'
@@ -1,18 +1,4 @@
1
1
  module Conexa
2
2
  class Person < Model
3
- class << self
4
- def all(*args, **params)
5
- raise NoMethodError
6
- end
7
-
8
- def find_by_id(id, **options)
9
- raise NoMethodError
10
- end
11
-
12
-
13
- def find_by(params = Hash.new, page = nil, size = nil)
14
- raise NoMethodError
15
- end
16
- end
17
3
  end
18
4
  end
@@ -1,5 +1,7 @@
1
1
  module Conexa
2
2
  class RecurringSale < Model
3
+ primary_key_attribute :recurring_sale_id
4
+
3
5
  # End/terminate a recurring sale
4
6
  # @param params [Hash] optional parameters (e.g., endDate)
5
7
  # @return [self]
@@ -1,4 +1,78 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Conexa
2
- class Sale < Model
4
+ # Sale resource for one-time sales
5
+ #
6
+ # @example Create a sale
7
+ # sale = Conexa::Sale.create(
8
+ # customer_id: 450,
9
+ # product_id: 2521,
10
+ # quantity: 1,
11
+ # amount: 80.99
12
+ # )
13
+ #
14
+ # @example List sales
15
+ # sales = Conexa::Sale.all(customer_id: [450], status: 'notBilled')
16
+ #
17
+ # @!attribute [r] sale_id
18
+ # @return [Integer] Sale ID (also accessible as #id)
19
+ # @!attribute [r] customer_id
20
+ # @return [Integer] Customer ID
21
+ # @!attribute [r] requester_id
22
+ # @return [Integer, nil] Requester ID
23
+ # @!attribute [r] product_id
24
+ # @return [Integer] Product ID
25
+ # @!attribute [r] seller_id
26
+ # @return [Integer, nil] Seller (user) ID
27
+ # @!attribute [r] status
28
+ # @return [String] Status: paid, billed, cancelled, notBilled,
29
+ # deductedFromQuota, billedCancelled, billedNegociated, partiallyPaid
30
+ # @!attribute [r] quantity
31
+ # @return [Integer] Quantity
32
+ # @!attribute [r] amount
33
+ # @return [Float] Final amount
34
+ # @!attribute [r] original_amount
35
+ # @return [Float] Original amount before discount
36
+ # @!attribute [r] discount_value
37
+ # @return [Float] Discount value
38
+ # @!attribute [r] reference_date
39
+ # @return [String] Reference date (W3C format)
40
+ # @!attribute [r] notes
41
+ # @return [String, nil] Notes
42
+ # @!attribute [r] created_at
43
+ # @return [String, nil] Created at timestamp
44
+ # @!attribute [r] updated_at
45
+ # @return [String] Updated at timestamp
46
+ #
47
+ class Sale < Model
48
+ primary_key_attribute :sale_id
49
+
50
+ # Check if sale is billed
51
+ # @return [Boolean]
52
+ def billed?
53
+ status == 'billed'
54
+ end
55
+
56
+ # Check if sale is paid
57
+ # @return [Boolean]
58
+ def paid?
59
+ status == 'paid'
60
+ end
61
+
62
+ # Check if sale can be edited
63
+ # @return [Boolean]
64
+ def editable?
65
+ status == 'notBilled'
66
+ end
67
+
68
+ class << self
69
+ def url(*params)
70
+ ["/sales", *params].join '/'
71
+ end
72
+
73
+ def show_url(*params)
74
+ ["/sale", *params].join '/'
75
+ end
76
+ end
3
77
  end
4
78
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Conexa
4
- VERSION = "0.0.6"
4
+ VERSION = "0.0.8"
5
5
  end
data/lib/conexa.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "conexa/errors"
10
10
  require_relative "conexa/util"
11
11
  require_relative "conexa/configuration"
12
12
  require_relative "conexa/order_commom"
13
+ require_relative "conexa/token_manager"
13
14
 
14
15
 
15
16
  Dir[File.expand_path('../conexa/resources/*.rb', __FILE__)].map do |path|
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+
3
+ collection = JSON.parse(File.read('docs/postman-collection.json'))
4
+
5
+ fixtures_dir = 'spec/fixtures'
6
+ Dir.mkdir(fixtures_dir) unless Dir.exist?(fixtures_dir)
7
+
8
+ count = 0
9
+
10
+ collection['item'].each do |section|
11
+ section_name = section['name'].downcase.gsub(' ', '_').gsub(/[^a-z0-9_]/, '')
12
+ next if section_name.include?('desenvolvimento')
13
+
14
+ (section['item'] || []).each do |endpoint|
15
+ method = endpoint.dig('request', 'method') || 'UNKNOWN'
16
+ responses = endpoint['response'] || []
17
+
18
+ responses.each_with_index do |resp, idx|
19
+ next unless resp['code'] && resp['code'] < 300
20
+ next unless resp['body']
21
+
22
+ begin
23
+ body = JSON.parse(resp['body'])
24
+ key = "#{section_name}_#{method.downcase}_#{idx}"
25
+ File.write("#{fixtures_dir}/#{key}.json", JSON.pretty_generate(body))
26
+ puts "Created: #{key}.json"
27
+ count += 1
28
+ rescue JSON::ParserError
29
+ # skip
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ puts "\nTotal: #{count} fixtures"