Class: AfconWave::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/afconwave.rb

Overview

─── Main Client ──────────────────────────────────────────────────────────────

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(secret_key:, base_url: 'https://api.afconwave.com/api/v1', timeout: 30) ⇒ Client

Returns a new instance of Client.



32
33
34
35
36
# File 'lib/afconwave.rb', line 32

def initialize(secret_key:, base_url: 'https://api.afconwave.com/api/v1', timeout: 30)
  @secret_key = secret_key
  @base_url = base_url
  @timeout = timeout
end

Instance Attribute Details

#base_urlObject

Returns the value of attribute base_url.



30
31
32
# File 'lib/afconwave.rb', line 30

def base_url
  @base_url
end

#secret_keyObject

Returns the value of attribute secret_key.



30
31
32
# File 'lib/afconwave.rb', line 30

def secret_key
  @secret_key
end

#timeoutObject

Returns the value of attribute timeout.



30
31
32
# File 'lib/afconwave.rb', line 30

def timeout
  @timeout
end

Class Method Details

.verify_webhook_signature(payload:, signature:, secret:, tolerance: 300) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/afconwave.rb', line 38

def self.verify_webhook_signature(payload:, signature:, secret:, tolerance: 300)
  # 1. Verify Signature (timing-safe compare via OpenSSL stdlib)
  expected = OpenSSL::HMAC.hexdigest('sha256', secret, payload)

  # OpenSSL.fixed_length_secure_compare requires equal-length inputs.
  return false unless signature.is_a?(String) && expected.bytesize == signature.bytesize
  return false unless OpenSSL.fixed_length_secure_compare(expected, signature)

  # 2. Verify Timestamp (Replay Protection)
  begin
    data = JSON.parse(payload)
    if data['timestamp']
      current_time = Time.now.to_i # seconds
      webhook_time = data['timestamp'] / 1000 # convert ms to seconds
      age = (current_time - webhook_time).abs

      return false if age > tolerance
    end
  rescue JSON::ParserError
    # Non-JSON payload, signature is valid but can't check timestamp
  end

  true
end

Instance Method Details

#create_payment(**data) ⇒ Object

─── Top-level Convenience Methods (Matches README) ─────────────────────



107
# File 'lib/afconwave.rb', line 107

def create_payment(**data); payments.create(**data); end

#create_payout(**data) ⇒ Object



110
# File 'lib/afconwave.rb', line 110

def create_payout(**data); payouts.create(**data); end

#cryptoObject



65
# File 'lib/afconwave.rb', line 65

def crypto; @crypto ||= Resource::Crypto.new(self); end

#disputesObject



67
# File 'lib/afconwave.rb', line 67

def disputes; @disputes ||= Resource::Disputes.new(self); end

#list_payments(**params) ⇒ Object



109
# File 'lib/afconwave.rb', line 109

def list_payments(**params); payments.list(**params); end

#paymentsObject



63
# File 'lib/afconwave.rb', line 63

def payments; @payments ||= Resource::Payments.new(self); end

#payoutsObject



64
# File 'lib/afconwave.rb', line 64

def payouts; @payouts ||= Resource::Payouts.new(self); end

#refundsObject



66
# File 'lib/afconwave.rb', line 66

def refunds; @refunds ||= Resource::Refunds.new(self); end

#request(method:, path:, data: nil, params: nil) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/afconwave.rb', line 69

def request(method:, path:, data: nil, params: nil)
  uri = URI("#{base_url}#{path}")
  uri.query = URI.encode_www_form(params) if params

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true if uri.scheme == 'https'
  http.read_timeout = @timeout

  req = case method.upcase
        when 'POST'
          request = Net::HTTP::Post.new(uri)
          request.body = data.to_json if data
          request
        when 'GET'
          Net::HTTP::Get.new(uri)
        else
          raise Error.new("Unsupported method #{method}")
        end

  req['Authorization'] = "Bearer #{secret_key}"
  req['Content-Type'] = 'application/json'
  req['Accept'] = 'application/json'

  response = http.request(req)
  res_data = JSON.parse(response.body) rescue { 'error' => 'Invalid JSON response' }

  unless response.is_a?(Net::HTTPSuccess)
    case response.code.to_i
    when 401 then raise AuthError.new(res_data['error'] || 'Invalid API Key', status_code: 401)
    else raise Error.new(res_data['error'] || response.message, status_code: response.code.to_i, code: res_data['code'])
    end
  end

  res_data['data'] || res_data
end

#retrieve_payment(id) ⇒ Object



108
# File 'lib/afconwave.rb', line 108

def retrieve_payment(id); payments.retrieve(id); end