Module: Myrr::Bridge

Defined in:
lib/myrr/bridge.rb

Constant Summary collapse

Error =
Class.new(::StandardError)
SellerNotApproved =
Class.new(Error)
BudgetExceeded =
Class.new(Error)
InsufficientFunds =
Class.new(Error)
NoPaymentMethod =
Class.new(Error)

Class Method Summary collapse

Class Method Details

.resolve(agent_did:, seller_url:, seller_did:, payment_required:, retry_method:, retry_path:, retry_headers: nil) ⇒ Hash

Resolve a 402 Payment Required by auto-paying and retrying.

Calls the bridge endpoint on the agent's protocol server.

Parameters:

  • agent_did (String)

    The agent's did:myrr: identifier

  • seller_url (String)

    Base URL of the seller's protocol server

  • seller_did (String)

    The seller's did:myrr: identifier

  • payment_required (Hash)

    The 402 response body

  • retry_method (String)

    HTTP method for the retry

  • retry_path (String)

    Path to retry

  • retry_headers (Hash, nil) (defaults to: nil)

    Additional headers for the retry

Returns:

  • (Hash)

    The seller's response ({ "status_code" => 200, "body" => "..." })

Raises:



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
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
# File 'lib/myrr/bridge.rb', line 30

def self.resolve(agent_did:, seller_url:, seller_did:, payment_required:, retry_method:, retry_path:, retry_headers: nil)
  config = ::Myrr.config
  server_url = config.protocol_server_url
  api_key = config.site_api_key

  uri = URI.join(server_url, "/v1/pay/resolve")
  http = Net::HTTP.new(uri.host, uri.port)
  http.open_timeout = config.open_timeout || 5
  http.read_timeout = config.read_timeout || 30
  http.use_ssl = (uri.scheme == "https")

  payload = {
    agent_did: agent_did,
    seller_url: seller_url,
    seller_did: seller_did,
    payment_required: payment_required,
    retry: {
      method: retry_method,
      path: retry_path,
      headers: retry_headers || {}
    }
  }

  request = Net::HTTP::Post.new(uri)
  request["Content-Type"] = "application/json"
  request["Authorization"] = "Bearer #{api_key}" if api_key
  request.body = payload.to_json

  response = http.request(request)
  data = JSON.parse(response.body)

  # Map HTTP status to bridge errors
  case response.code.to_i
  when 200..299
    # Check for error payload in body (some bridge errors come as 200 with error field)
    if data.is_a?(Hash)
      case data["error"]
      when "seller_not_approved"
        raise SellerNotApproved, "Seller #{data["seller_did"]} is not approved"
      when "budget_exceeded"
        raise BudgetExceeded, "Budget exceeded: #{data["reason"]} (limit=#{data["limit"]}, current=#{data["current"]})"
      when "no_payment_method"
        raise NoPaymentMethod, data["message"]
      end
    end
    data
  when 400
    if data["error"] == "no_payment_method"
      raise NoPaymentMethod, data["message"]
    end
    raise Error, data["error"] || "Bad request"
  when 402
    raise InsufficientFunds, data["error"] || "Insufficient funds"
  when 403
    if data["error"] == "seller_not_approved"
      raise SellerNotApproved, "Seller #{data["seller_did"]} is not approved"
    elsif data["error"] == "budget_exceeded"
      raise BudgetExceeded, "Budget exceeded: #{data["reason"]} (limit=#{data["limit"]}, current=#{data["current"]})"
    end
    raise Error, data["error"] || "Forbidden"
  else
    raise Error, data["error"] || "HTTP #{response.code}"
  end
rescue JSON::ParserError
  raise Error, "Invalid JSON response"
end