Class: Remitmd::X402Paywall
- Inherits:
-
Object
- Object
- Remitmd::X402Paywall
- Defined in:
- lib/remitmd/x402_paywall.rb
Overview
x402 paywall for service providers - gate HTTP endpoints behind payments.
Providers use this class to:
- Return HTTP 402 responses with properly formatted PAYMENT-REQUIRED headers
- Verify incoming PAYMENT-SIGNATURE headers against the remit.md facilitator
Instance Method Summary collapse
-
#check(payment_sig) ⇒ Hash
Check whether a PAYMENT-SIGNATURE header represents a valid payment.
-
#initialize(wallet_address:, amount_usdc:, network:, asset:, facilitator_url: "https://remit.md", facilitator_token: "", max_timeout_seconds: 60, resource: nil, description: nil, mime_type: nil) ⇒ X402Paywall
constructor
A new instance of X402Paywall.
-
#payment_required_header ⇒ String
Return the base64-encoded JSON PAYMENT-REQUIRED header value.
-
#rack_middleware ⇒ Class
Rack middleware adapter.
Constructor Details
#initialize(wallet_address:, amount_usdc:, network:, asset:, facilitator_url: "https://remit.md", facilitator_token: "", max_timeout_seconds: 60, resource: nil, description: nil, mime_type: nil) ⇒ X402Paywall
Returns a new instance of X402Paywall.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/remitmd/x402_paywall.rb', line 35 def initialize( # rubocop:disable Metrics/ParameterLists wallet_address:, amount_usdc:, network:, asset:, facilitator_url: "https://remit.md", facilitator_token: "", max_timeout_seconds: 60, resource: nil, description: nil, mime_type: nil ) @wallet_address = wallet_address @amount_base_units = (amount_usdc * 1_000_000).round.to_s @network = network @asset = asset @facilitator_url = facilitator_url.chomp("/") @facilitator_token = facilitator_token @max_timeout_seconds = max_timeout_seconds @resource = resource @description = description @mime_type = mime_type end |
Instance Method Details
#check(payment_sig) ⇒ Hash
Check whether a PAYMENT-SIGNATURE header represents a valid payment. Calls the remit.md facilitator's /api/v1/x402/verify endpoint.
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 |
# File 'lib/remitmd/x402_paywall.rb', line 81 def check(payment_sig) return { is_valid: false } unless payment_sig payment_payload = begin JSON.parse(Base64.decode64(payment_sig)) rescue JSON::ParserError return { is_valid: false, invalid_reason: "INVALID_PAYLOAD" } end body = { paymentPayload: payment_payload, paymentRequired: payment_required_object, } uri = URI("#{@facilitator_url}/api/v1/x402/verify") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == "https" http.read_timeout = 10 req = Net::HTTP::Post.new(uri.path) req["Content-Type"] = "application/json" req["Authorization"] = "Bearer #{@facilitator_token}" unless @facilitator_token.empty? req.body = JSON.generate(body) begin resp = http.request(req) unless resp.is_a?(Net::HTTPSuccess) return { is_valid: false, invalid_reason: "FACILITATOR_ERROR" } end data = JSON.parse(resp.body) rescue StandardError return { is_valid: false, invalid_reason: "FACILITATOR_ERROR" } end { is_valid: data["isValid"] == true, invalid_reason: data["invalidReason"], } end |
#payment_required_header ⇒ String
Return the base64-encoded JSON PAYMENT-REQUIRED header value.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/remitmd/x402_paywall.rb', line 61 def payment_required_header payload = { scheme: "exact", network: @network, amount: @amount_base_units, asset: @asset, payTo: @wallet_address, maxTimeoutSeconds: @max_timeout_seconds, } payload[:resource] = @resource if @resource payload[:description] = @description if @description payload[:mimeType] = @mime_type if @mime_type Base64.strict_encode64(JSON.generate(payload)) end |
#rack_middleware ⇒ Class
Rack middleware adapter.
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 |
# File 'lib/remitmd/x402_paywall.rb', line 128 def rack_middleware paywall = self Class.new do define_method(:initialize) do |app| @app = app @paywall = paywall end define_method(:call) do |env| payment_sig = env["HTTP_PAYMENT_SIGNATURE"] result = @paywall.check(payment_sig) unless result[:is_valid] headers = { "Content-Type" => "application/json", "PAYMENT-REQUIRED" => @paywall.payment_required_header, } body = JSON.generate({ error: "Payment required", invalidReason: result[:invalid_reason], }) return [402, headers, [body]] end @app.call(env) end end end |