Class: Mpp::Client::Transport
- Inherits:
-
Object
- Object
- Mpp::Client::Transport
- 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:
-
Detects 402 responses with WWW-Authenticate: Payment headers
-
Parses the challenge and finds a matching payment method
-
Creates credentials and retries the request
-
Returns the final response
Instance Method Summary collapse
- #delete(url, **kwargs) ⇒ Object
- #get(url, **kwargs) ⇒ Object
-
#initialize(methods:, events: nil) ⇒ Transport
constructor
A new instance of Transport.
- #on(name, handler = nil, &block) ⇒ Object
- #on_challenge_received(handler = nil, &block) ⇒ Object
- #on_credential_created(handler = nil, &block) ⇒ Object
- #on_payment_failed(handler = nil, &block) ⇒ Object
- #on_payment_response(handler = nil, &block) ⇒ Object
- #post(url, **kwargs) ⇒ Object
- #put(url, **kwargs) ⇒ Object
- #request(method, url, headers: {}, body: nil) ⇒ Object
Constructor Details
#initialize(methods:, events: nil) ⇒ Transport
Returns a new instance of Transport.
22 23 24 25 |
# File 'lib/mpp/client/transport.rb', line 22 def initialize(methods:, events: nil) @methods = T.let(methods.to_h { |m| [m.name, m] }, T::Hash[String, T.untyped]) @events = T.let(events || Mpp::Events.client_dispatcher, Mpp::Events::Dispatcher) end |
Instance Method Details
#delete(url, **kwargs) ⇒ Object
178 179 180 |
# File 'lib/mpp/client/transport.rb', line 178 def delete(url, **kwargs) request("DELETE", url, **kwargs) end |
#get(url, **kwargs) ⇒ Object
163 164 165 |
# File 'lib/mpp/client/transport.rb', line 163 def get(url, **kwargs) request("GET", url, **kwargs) end |
#on(name, handler = nil, &block) ⇒ Object
28 29 30 |
# File 'lib/mpp/client/transport.rb', line 28 def on(name, handler = nil, &block) @events.on(name, handler, &block) end |
#on_challenge_received(handler = nil, &block) ⇒ Object
33 34 35 |
# File 'lib/mpp/client/transport.rb', line 33 def on_challenge_received(handler = nil, &block) on(Mpp::Events::CHALLENGE_RECEIVED, handler, &block) end |
#on_credential_created(handler = nil, &block) ⇒ Object
38 39 40 |
# File 'lib/mpp/client/transport.rb', line 38 def on_credential_created(handler = nil, &block) on(Mpp::Events::CREDENTIAL_CREATED, handler, &block) end |
#on_payment_failed(handler = nil, &block) ⇒ Object
43 44 45 |
# File 'lib/mpp/client/transport.rb', line 43 def on_payment_failed(handler = nil, &block) on(Mpp::Events::PAYMENT_FAILED, handler, &block) end |
#on_payment_response(handler = nil, &block) ⇒ Object
48 49 50 |
# File 'lib/mpp/client/transport.rb', line 48 def on_payment_response(handler = nil, &block) on(Mpp::Events::PAYMENT_RESPONSE, handler, &block) end |
#post(url, **kwargs) ⇒ Object
168 169 170 |
# File 'lib/mpp/client/transport.rb', line 168 def post(url, **kwargs) request("POST", url, **kwargs) end |
#put(url, **kwargs) ⇒ Object
173 174 175 |
# File 'lib/mpp/client/transport.rb', line 173 def put(url, **kwargs) request("PUT", url, **kwargs) end |
#request(method, url, headers: {}, body: nil) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/mpp/client/transport.rb', line 55 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, input: url, response: response) 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 auth_header = T.let(nil, T.nilable(String)) create_credential = Kernel.lambda do auth_header ||= (matched_method.create_credential(challenge)) end begin event_credential = nil if @events.has_handlers?(Mpp::Events::CHALLENGE_RECEIVED) # challenge.received can override credential creation; first non-empty credential wins. event_credential = @events.emit_first(Mpp::Events::CHALLENGE_RECEIVED, { challenge: challenge, challenges: [challenge], create_credential: create_credential, input: url, method: matched_method, response: response }) end auth_header = (event_credential) unless event_credential.nil? auth_header ||= create_credential.call if @events.has_handlers?(Mpp::Events::CREDENTIAL_CREATED) @events.emit(Mpp::Events::CREDENTIAL_CREATED, { challenge: challenge, credential: auth_header, input: url, method: matched_method, response: response }) end rescue => e if @events.has_handlers?(Mpp::Events::PAYMENT_FAILED) @events.emit(Mpp::Events::PAYMENT_FAILED, { challenge: challenge, challenges: [challenge], error: e, input: url, method: matched_method, response: response }) end raise end retry_headers = headers.merge("Authorization" => auth_header) payment_response = nil begin payment_response = send_request(uri, method, retry_headers, body) rescue => e if @events.has_handlers?(Mpp::Events::PAYMENT_FAILED) @events.emit(Mpp::Events::PAYMENT_FAILED, { challenge: challenge, challenges: [challenge], credential: auth_header, error: e, input: url, method: matched_method, response: response }) end raise end if payment_response.code.to_i.between?(200, 299) && @events.has_handlers?(Mpp::Events::PAYMENT_RESPONSE) @events.emit(Mpp::Events::PAYMENT_RESPONSE, { challenge: challenge, credential: auth_header, input: url, method: matched_method, response: payment_response }) elsif @events.has_handlers?(Mpp::Events::PAYMENT_FAILED) @events.emit(Mpp::Events::PAYMENT_FAILED, { challenge: challenge, challenges: [challenge], credential: auth_header, error: Mpp::VerificationFailedError.new(reason: "retry returned HTTP #{payment_response.code}"), input: url, method: matched_method, response: payment_response }) end payment_response end |