Class: Mpp::Client::Transport

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/mpp/client/transport.rb

Overview

Payment-aware HTTP client that handles 402 Payment Required responses.

Wraps Net::HTTP and automatically:

  1. Detects 402 responses with WWW-Authenticate: Payment headers

  2. Parses the challenge and finds a matching payment method

  3. Creates credentials and retries the request

  4. Returns the final response

Instance Method Summary collapse

Constructor Details

#initialize(methods:) ⇒ Transport

Returns a new instance of Transport.



22
23
24
# File 'lib/mpp/client/transport.rb', line 22

def initialize(methods:)
  @methods = T.let(methods.to_h { |m| [m.name, m] }, T::Hash[String, T.untyped])
end

Instance Method Details

#delete(url, **kwargs) ⇒ Object



73
74
75
# File 'lib/mpp/client/transport.rb', line 73

def delete(url, **kwargs)
  request("DELETE", url, **kwargs)
end

#get(url, **kwargs) ⇒ Object



58
59
60
# File 'lib/mpp/client/transport.rb', line 58

def get(url, **kwargs)
  request("GET", url, **kwargs)
end

#post(url, **kwargs) ⇒ Object



63
64
65
# File 'lib/mpp/client/transport.rb', line 63

def post(url, **kwargs)
  request("POST", url, **kwargs)
end

#put(url, **kwargs) ⇒ Object



68
69
70
# File 'lib/mpp/client/transport.rb', line 68

def put(url, **kwargs)
  request("PUT", url, **kwargs)
end

#request(method, url, headers: {}, body: nil) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/mpp/client/transport.rb', line 29

def request(method, url, headers: {}, body: nil)
  uri = URI(url)
  response = send_request(uri, method, headers, body)

  return response unless response.code.to_i == 402

  # Parse WWW-Authenticate headers
  www_auth_headers = response.get_fields("www-authenticate") || []
  challenge, matched_method = find_matching_challenge(www_auth_headers)
  return response unless challenge && matched_method

  # Check expiry before paying (client-side guardrail)
  if challenge.expires
    begin
      expires_dt = Time.iso8601(challenge.expires.gsub("Z", "+00:00"))
      return response if expires_dt < Time.now.utc
    rescue ArgumentError
      # If we can't parse, let server validate
    end
  end

  credential = matched_method.create_credential(challenge)
  auth_header = credential.to_authorization

  retry_headers = headers.merge("Authorization" => auth_header)
  send_request(uri, method, retry_headers, body)
end