weft-sdk 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +9 -0
  3. data/docs/APIKeysApi.md +208 -0
  4. data/docs/AccountApi.md +72 -0
  5. data/docs/AccountDetails.md +30 -0
  6. data/docs/Agent.md +38 -0
  7. data/docs/AgentListResponse.md +20 -0
  8. data/docs/AgentResponse.md +18 -0
  9. data/docs/AgentStats.md +30 -0
  10. data/docs/AgentsApi.md +147 -0
  11. data/docs/ApiKey.md +28 -0
  12. data/docs/ApiKeyCreated.md +26 -0
  13. data/docs/ApiKeyCreatedResponse.md +18 -0
  14. data/docs/ApiKeyListResponse.md +18 -0
  15. data/docs/AuthApi.md +385 -0
  16. data/docs/AuthResponse.md +18 -0
  17. data/docs/AuthResponseData.md +22 -0
  18. data/docs/ConfirmRequest.md +18 -0
  19. data/docs/CreateApiKeyRequest.md +18 -0
  20. data/docs/DefaultApi.md +67 -0
  21. data/docs/Error.md +24 -0
  22. data/docs/ErrorResponse.md +18 -0
  23. data/docs/MeResponse.md +18 -0
  24. data/docs/MessageResponse.md +18 -0
  25. data/docs/MessageResponseData.md +18 -0
  26. data/docs/Pagination.md +24 -0
  27. data/docs/PasswordResetRequest.md +18 -0
  28. data/docs/PasswordUpdateRequest.md +22 -0
  29. data/docs/Payment.md +44 -0
  30. data/docs/PaymentListResponse.md +20 -0
  31. data/docs/PaymentResponse.md +18 -0
  32. data/docs/PaymentsApi.md +147 -0
  33. data/docs/ResendConfirmationRequest.md +18 -0
  34. data/docs/SignInRequest.md +20 -0
  35. data/docs/SignUpRequest.md +22 -0
  36. data/docs/User.md +22 -0
  37. data/lib/weft/facilitator/client.rb +89 -0
  38. data/lib/weft/facilitator/fee.rb +47 -0
  39. data/lib/weft/facilitator/middleware.rb +190 -0
  40. data/lib/weft/generated/api/account_api.rb +77 -0
  41. data/lib/weft/generated/api/agents_api.rb +148 -0
  42. data/lib/weft/generated/api/api_keys_api.rb +204 -0
  43. data/lib/weft/generated/api/auth_api.rb +418 -0
  44. data/lib/weft/generated/api/default_api.rb +77 -0
  45. data/lib/weft/generated/api/payments_api.rb +148 -0
  46. data/lib/weft/generated/api_client.rb +397 -0
  47. data/lib/weft/generated/api_error.rb +58 -0
  48. data/lib/weft/generated/api_model_base.rb +88 -0
  49. data/lib/weft/generated/configuration.rb +317 -0
  50. data/lib/weft/generated/models/account_details.rb +310 -0
  51. data/lib/weft/generated/models/agent.rb +417 -0
  52. data/lib/weft/generated/models/agent_list_response.rb +192 -0
  53. data/lib/weft/generated/models/agent_response.rb +164 -0
  54. data/lib/weft/generated/models/agent_stats.rb +201 -0
  55. data/lib/weft/generated/models/api_key.rb +244 -0
  56. data/lib/weft/generated/models/api_key_created.rb +252 -0
  57. data/lib/weft/generated/models/api_key_created_response.rb +164 -0
  58. data/lib/weft/generated/models/api_key_list_response.rb +166 -0
  59. data/lib/weft/generated/models/auth_response.rb +164 -0
  60. data/lib/weft/generated/models/auth_response_data.rb +199 -0
  61. data/lib/weft/generated/models/confirm_request.rb +164 -0
  62. data/lib/weft/generated/models/create_api_key_request.rb +148 -0
  63. data/lib/weft/generated/models/error.rb +208 -0
  64. data/lib/weft/generated/models/error_response.rb +164 -0
  65. data/lib/weft/generated/models/me_response.rb +164 -0
  66. data/lib/weft/generated/models/message_response.rb +164 -0
  67. data/lib/weft/generated/models/message_response_data.rb +164 -0
  68. data/lib/weft/generated/models/pagination.rb +242 -0
  69. data/lib/weft/generated/models/password_reset_request.rb +164 -0
  70. data/lib/weft/generated/models/password_update_request.rb +216 -0
  71. data/lib/weft/generated/models/payment.rb +437 -0
  72. data/lib/weft/generated/models/payment_list_response.rb +192 -0
  73. data/lib/weft/generated/models/payment_response.rb +164 -0
  74. data/lib/weft/generated/models/resend_confirmation_request.rb +164 -0
  75. data/lib/weft/generated/models/sign_in_request.rb +190 -0
  76. data/lib/weft/generated/models/sign_up_request.rb +216 -0
  77. data/lib/weft/generated/models/user.rb +199 -0
  78. data/lib/weft/generated/version.rb +15 -0
  79. data/lib/weft/generated.rb +4 -0
  80. data/lib/weft/sdk.rb +10 -0
  81. metadata +123 -0
data/docs/Payment.md ADDED
@@ -0,0 +1,44 @@
1
+ # Weft::Payment
2
+
3
+ ## Properties
4
+
5
+ | Name | Type | Description | Notes |
6
+ | ---- | ---- | ----------- | ----- |
7
+ | **id** | **Integer** | | |
8
+ | **tx_hash** | **String** | | |
9
+ | **payer_address** | **String** | | |
10
+ | **recipient_address** | **String** | | |
11
+ | **amount** | **Integer** | Amount in atomic units (1 USDC = 1,000,000) | |
12
+ | **amount_formatted** | **String** | Human-readable amount (e.g. \"1.00 USDC\") | |
13
+ | **currency** | **String** | | |
14
+ | **network** | **String** | CAIP-2 chain identifier | |
15
+ | **resource_url** | **String** | | [optional] |
16
+ | **resource_host** | **String** | | [optional] |
17
+ | **fee_amount** | **Integer** | | [optional] |
18
+ | **settlement_latency_ms** | **Integer** | | [optional] |
19
+ | **settled_at** | **Time** | | |
20
+ | **api_key_name** | **String** | | |
21
+
22
+ ## Example
23
+
24
+ ```ruby
25
+ require 'weft-sdk'
26
+
27
+ instance = Weft::Payment.new(
28
+ id: null,
29
+ tx_hash: null,
30
+ payer_address: null,
31
+ recipient_address: null,
32
+ amount: null,
33
+ amount_formatted: null,
34
+ currency: null,
35
+ network: null,
36
+ resource_url: null,
37
+ resource_host: null,
38
+ fee_amount: null,
39
+ settlement_latency_ms: null,
40
+ settled_at: null,
41
+ api_key_name: null
42
+ )
43
+ ```
44
+
@@ -0,0 +1,20 @@
1
+ # Weft::PaymentListResponse
2
+
3
+ ## Properties
4
+
5
+ | Name | Type | Description | Notes |
6
+ | ---- | ---- | ----------- | ----- |
7
+ | **data** | [**Array<Payment>**](Payment.md) | | |
8
+ | **pagination** | [**Pagination**](Pagination.md) | | |
9
+
10
+ ## Example
11
+
12
+ ```ruby
13
+ require 'weft-sdk'
14
+
15
+ instance = Weft::PaymentListResponse.new(
16
+ data: null,
17
+ pagination: null
18
+ )
19
+ ```
20
+
@@ -0,0 +1,18 @@
1
+ # Weft::PaymentResponse
2
+
3
+ ## Properties
4
+
5
+ | Name | Type | Description | Notes |
6
+ | ---- | ---- | ----------- | ----- |
7
+ | **data** | [**Payment**](Payment.md) | | |
8
+
9
+ ## Example
10
+
11
+ ```ruby
12
+ require 'weft-sdk'
13
+
14
+ instance = Weft::PaymentResponse.new(
15
+ data: null
16
+ )
17
+ ```
18
+
@@ -0,0 +1,147 @@
1
+ # Weft::PaymentsApi
2
+
3
+ All URIs are relative to *https://api.weftlabs.com*
4
+
5
+ | Method | HTTP request | Description |
6
+ | ------ | ------------ | ----------- |
7
+ | [**get_payment**](PaymentsApi.md#get_payment) | **GET** /api/v1/payments/{id} | Get payment details |
8
+ | [**list_payments**](PaymentsApi.md#list_payments) | **GET** /api/v1/payments | List payments |
9
+
10
+
11
+ ## get_payment
12
+
13
+ > <PaymentResponse> get_payment(id)
14
+
15
+ Get payment details
16
+
17
+ ### Examples
18
+
19
+ ```ruby
20
+ require 'time'
21
+ require 'weft-sdk'
22
+ # setup authorization
23
+ Weft.configure do |config|
24
+ # Configure Bearer authorization (APIKey): bearerAuth
25
+ config.access_token = 'YOUR_BEARER_TOKEN'
26
+ end
27
+
28
+ api_instance = Weft::PaymentsApi.new
29
+ id = 56 # Integer | Payment ID
30
+
31
+ begin
32
+ # Get payment details
33
+ result = api_instance.get_payment(id)
34
+ p result
35
+ rescue Weft::ApiError => e
36
+ puts "Error when calling PaymentsApi->get_payment: #{e}"
37
+ end
38
+ ```
39
+
40
+ #### Using the get_payment_with_http_info variant
41
+
42
+ This returns an Array which contains the response data, status code and headers.
43
+
44
+ > <Array(<PaymentResponse>, Integer, Hash)> get_payment_with_http_info(id)
45
+
46
+ ```ruby
47
+ begin
48
+ # Get payment details
49
+ data, status_code, headers = api_instance.get_payment_with_http_info(id)
50
+ p status_code # => 2xx
51
+ p headers # => { ... }
52
+ p data # => <PaymentResponse>
53
+ rescue Weft::ApiError => e
54
+ puts "Error when calling PaymentsApi->get_payment_with_http_info: #{e}"
55
+ end
56
+ ```
57
+
58
+ ### Parameters
59
+
60
+ | Name | Type | Description | Notes |
61
+ | ---- | ---- | ----------- | ----- |
62
+ | **id** | **Integer** | Payment ID | |
63
+
64
+ ### Return type
65
+
66
+ [**PaymentResponse**](PaymentResponse.md)
67
+
68
+ ### Authorization
69
+
70
+ [bearerAuth](../README.md#bearerAuth)
71
+
72
+ ### HTTP request headers
73
+
74
+ - **Content-Type**: Not defined
75
+ - **Accept**: application/json
76
+
77
+
78
+ ## list_payments
79
+
80
+ > <PaymentListResponse> list_payments(opts)
81
+
82
+ List payments
83
+
84
+ ### Examples
85
+
86
+ ```ruby
87
+ require 'time'
88
+ require 'weft-sdk'
89
+ # setup authorization
90
+ Weft.configure do |config|
91
+ # Configure Bearer authorization (APIKey): bearerAuth
92
+ config.access_token = 'YOUR_BEARER_TOKEN'
93
+ end
94
+
95
+ api_instance = Weft::PaymentsApi.new
96
+ opts = {
97
+ page: 56, # Integer | Page number
98
+ per_page: 56 # Integer | Items per page
99
+ }
100
+
101
+ begin
102
+ # List payments
103
+ result = api_instance.list_payments(opts)
104
+ p result
105
+ rescue Weft::ApiError => e
106
+ puts "Error when calling PaymentsApi->list_payments: #{e}"
107
+ end
108
+ ```
109
+
110
+ #### Using the list_payments_with_http_info variant
111
+
112
+ This returns an Array which contains the response data, status code and headers.
113
+
114
+ > <Array(<PaymentListResponse>, Integer, Hash)> list_payments_with_http_info(opts)
115
+
116
+ ```ruby
117
+ begin
118
+ # List payments
119
+ data, status_code, headers = api_instance.list_payments_with_http_info(opts)
120
+ p status_code # => 2xx
121
+ p headers # => { ... }
122
+ p data # => <PaymentListResponse>
123
+ rescue Weft::ApiError => e
124
+ puts "Error when calling PaymentsApi->list_payments_with_http_info: #{e}"
125
+ end
126
+ ```
127
+
128
+ ### Parameters
129
+
130
+ | Name | Type | Description | Notes |
131
+ | ---- | ---- | ----------- | ----- |
132
+ | **page** | **Integer** | Page number | [optional][default to 1] |
133
+ | **per_page** | **Integer** | Items per page | [optional][default to 25] |
134
+
135
+ ### Return type
136
+
137
+ [**PaymentListResponse**](PaymentListResponse.md)
138
+
139
+ ### Authorization
140
+
141
+ [bearerAuth](../README.md#bearerAuth)
142
+
143
+ ### HTTP request headers
144
+
145
+ - **Content-Type**: Not defined
146
+ - **Accept**: application/json
147
+
@@ -0,0 +1,18 @@
1
+ # Weft::ResendConfirmationRequest
2
+
3
+ ## Properties
4
+
5
+ | Name | Type | Description | Notes |
6
+ | ---- | ---- | ----------- | ----- |
7
+ | **email** | **String** | | |
8
+
9
+ ## Example
10
+
11
+ ```ruby
12
+ require 'weft-sdk'
13
+
14
+ instance = Weft::ResendConfirmationRequest.new(
15
+ email: null
16
+ )
17
+ ```
18
+
@@ -0,0 +1,20 @@
1
+ # Weft::SignInRequest
2
+
3
+ ## Properties
4
+
5
+ | Name | Type | Description | Notes |
6
+ | ---- | ---- | ----------- | ----- |
7
+ | **email** | **String** | | |
8
+ | **password** | **String** | | |
9
+
10
+ ## Example
11
+
12
+ ```ruby
13
+ require 'weft-sdk'
14
+
15
+ instance = Weft::SignInRequest.new(
16
+ email: null,
17
+ password: null
18
+ )
19
+ ```
20
+
@@ -0,0 +1,22 @@
1
+ # Weft::SignUpRequest
2
+
3
+ ## Properties
4
+
5
+ | Name | Type | Description | Notes |
6
+ | ---- | ---- | ----------- | ----- |
7
+ | **email** | **String** | | |
8
+ | **password** | **String** | | |
9
+ | **password_confirmation** | **String** | | |
10
+
11
+ ## Example
12
+
13
+ ```ruby
14
+ require 'weft-sdk'
15
+
16
+ instance = Weft::SignUpRequest.new(
17
+ email: null,
18
+ password: null,
19
+ password_confirmation: null
20
+ )
21
+ ```
22
+
data/docs/User.md ADDED
@@ -0,0 +1,22 @@
1
+ # Weft::User
2
+
3
+ ## Properties
4
+
5
+ | Name | Type | Description | Notes |
6
+ | ---- | ---- | ----------- | ----- |
7
+ | **id** | **Integer** | | |
8
+ | **email** | **String** | | |
9
+ | **status** | **String** | | [optional] |
10
+
11
+ ## Example
12
+
13
+ ```ruby
14
+ require 'weft-sdk'
15
+
16
+ instance = Weft::User.new(
17
+ id: null,
18
+ email: null,
19
+ status: null
20
+ )
21
+ ```
22
+
@@ -0,0 +1,89 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module Weft
5
+ module Facilitator
6
+ DEFAULT_URL = 'https://x402.weft.network'.freeze
7
+ DEFAULT_ENV = 'X402_FACILITATOR_URL'.freeze
8
+
9
+ class Client
10
+ def initialize(url: nil, create_headers: nil)
11
+ @url = resolve_url(url)
12
+ validate_url(@url)
13
+ @create_headers = create_headers
14
+ end
15
+
16
+ def verify(payment_payload:, payment_requirements:)
17
+ post_json('/verify', {
18
+ x402Version: 2,
19
+ paymentPayload: payment_payload,
20
+ paymentRequirements: payment_requirements
21
+ })
22
+ end
23
+
24
+ def settle(payment_payload:, payment_requirements:)
25
+ post_json('/settle', {
26
+ x402Version: 2,
27
+ paymentPayload: payment_payload,
28
+ paymentRequirements: payment_requirements
29
+ })
30
+ end
31
+
32
+ def supported
33
+ get_json('/supported')
34
+ end
35
+
36
+ private
37
+
38
+ def resolve_url(url)
39
+ return url if url && !url.empty?
40
+
41
+ env = ENV.fetch(DEFAULT_ENV, nil)
42
+ return env if env && !env.empty?
43
+
44
+ DEFAULT_URL
45
+ end
46
+
47
+ def validate_url(url)
48
+ return unless url.empty? || (!url.start_with?('http://') && !url.start_with?('https://'))
49
+
50
+ raise ArgumentError, "Invalid URL: #{url}"
51
+ end
52
+
53
+ def build_headers(scope)
54
+ headers = { 'Content-Type' => 'application/json' }
55
+ return headers unless @create_headers
56
+
57
+ custom = @create_headers.call
58
+ scope_headers = custom && custom[scope]
59
+ headers.merge(scope_headers || {})
60
+ end
61
+
62
+ def get_json(path)
63
+ uri = URI.join(@url, path)
64
+ request = Net::HTTP::Get.new(uri, build_headers('supported'))
65
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
66
+ http.request(request)
67
+ end
68
+ parse_response(response)
69
+ end
70
+
71
+ def post_json(path, body)
72
+ uri = URI.join(@url, path)
73
+ scope = path.include?('verify') ? 'verify' : 'settle'
74
+ request = Net::HTTP::Post.new(uri, build_headers(scope))
75
+ request.body = JSON.generate(body)
76
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
77
+ http.request(request)
78
+ end
79
+ parse_response(response)
80
+ end
81
+
82
+ def parse_response(response)
83
+ raise "Facilitator request failed: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
84
+
85
+ JSON.parse(response.body)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,47 @@
1
+ module Weft
2
+ module Facilitator
3
+ module Fee
4
+ DEFAULT_TTL = 300
5
+
6
+ @cache = nil
7
+
8
+ class << self
9
+ def get_fee_info(client: Client.new, ttl: DEFAULT_TTL)
10
+ return @cache[:fee] if cache_valid?
11
+
12
+ supported = client.supported
13
+ fee = supported['fee']
14
+ raise 'Fee information not found in /supported response' unless fee
15
+
16
+ validate_fee!(fee)
17
+
18
+ @cache = {
19
+ fee: fee,
20
+ fetched_at: Time.now.to_i,
21
+ ttl: ttl
22
+ }
23
+
24
+ fee
25
+ end
26
+
27
+ def invalidate_fee_cache
28
+ @cache = nil
29
+ end
30
+
31
+ private
32
+
33
+ def cache_valid?
34
+ return false unless @cache
35
+
36
+ Time.now.to_i - @cache[:fetched_at] < @cache[:ttl]
37
+ end
38
+
39
+ def validate_fee!(fee)
40
+ raise 'Invalid fee structure: amount must be a string' unless fee['amount'].is_a?(String)
41
+ raise 'Invalid fee structure: asset must be a string' unless fee['asset'].is_a?(String)
42
+ raise 'Invalid fee structure: network must be a string' unless fee['network'].is_a?(String)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,190 @@
1
+ require 'json'
2
+ require_relative 'client'
3
+
4
+ module Weft
5
+ module Facilitator
6
+ # Configuration for a route that requires payment.
7
+ #
8
+ # {
9
+ # path: "/api/resource", # string or Regexp
10
+ # price: "0.01", # price in asset units
11
+ # asset: "USDC",
12
+ # network: "base-sepolia",
13
+ # pay_to: "0x...", # recipient address
14
+ # description: "Access resource",
15
+ # max_deadline_seconds: 60,
16
+ # }
17
+ RouteConfig = Struct.new(
18
+ :path, :price, :asset, :network, :pay_to,
19
+ :description, :max_deadline_seconds,
20
+ keyword_init: true
21
+ ) do
22
+ def initialize(**kwargs)
23
+ super
24
+ self.network ||= 'base-sepolia'
25
+ self.max_deadline_seconds ||= 60
26
+ self.description ||= ''
27
+ end
28
+ end
29
+
30
+ class RackMiddleware
31
+ PAYMENT_HEADERS = %w[HTTP_X_PAYMENT HTTP_PAYMENT_SIGNATURE].freeze
32
+ X402_VERSION = 2
33
+
34
+ # @param app [#call] The downstream Rack app
35
+ # @param routes [Array<Hash>] Route payment configurations
36
+ # @param config [Hash] Options including :facilitator_url
37
+ def initialize(app, routes: [], config: {})
38
+ @app = app
39
+ @config = config
40
+ @routes = routes.map do |r|
41
+ r.is_a?(RouteConfig) ? r : RouteConfig.new(**r)
42
+ end
43
+ @client = Client.new(url: config[:facilitator_url])
44
+ end
45
+
46
+ def call(env)
47
+ return @app.call(env) if @routes.empty?
48
+
49
+ request_path = env['PATH_INFO'] || '/'
50
+ route = match_route(request_path)
51
+
52
+ return @app.call(env) unless route
53
+
54
+ payment_header = extract_payment_header(env)
55
+
56
+ return payment_required_response(route, env) unless payment_header
57
+
58
+ # Verify the payment via facilitator
59
+ payment_payload = parse_payment(payment_header)
60
+ requirements = build_requirements(route, env)
61
+
62
+ begin
63
+ verify_result = @client.verify(
64
+ payment_payload: payment_payload,
65
+ payment_requirements: requirements
66
+ )
67
+ rescue StandardError => e
68
+ return error_response(402, "Payment verification failed: #{e.message}")
69
+ end
70
+
71
+ unless verify_result['valid']
72
+ return error_response(402, verify_result['message'] || 'Payment verification failed')
73
+ end
74
+
75
+ # Call the downstream app
76
+ status, headers, body = @app.call(env)
77
+
78
+ # If the app returned an error, don't settle
79
+ return [status, headers, body] if status >= 400
80
+
81
+ # Settle the payment
82
+ begin
83
+ settle_result = @client.settle(
84
+ payment_payload: payment_payload,
85
+ payment_requirements: requirements
86
+ )
87
+
88
+ return error_response(402, "Settlement failed: #{settle_result['message']}") unless settle_result['success']
89
+
90
+ headers['X-Payment-TxHash'] = settle_result['txHash'] if settle_result['txHash']
91
+ rescue StandardError => e
92
+ return error_response(402, "Settlement failed: #{e.message}")
93
+ end
94
+
95
+ [status, headers, body]
96
+ end
97
+
98
+ private
99
+
100
+ def match_route(path)
101
+ @routes.find do |route|
102
+ case route.path
103
+ when Regexp
104
+ route.path.match?(path)
105
+ when '*'
106
+ true
107
+ else
108
+ route.path == path
109
+ end
110
+ end
111
+ end
112
+
113
+ def extract_payment_header(env)
114
+ PAYMENT_HEADERS.each do |header|
115
+ value = env[header]
116
+ return value if value && !value.empty?
117
+ end
118
+ nil
119
+ end
120
+
121
+ def parse_payment(header_value)
122
+ JSON.parse(header_value)
123
+ rescue JSON::ParserError
124
+ # If not JSON, treat as an opaque token
125
+ { 'token' => header_value }
126
+ end
127
+
128
+ def build_requirements(route, env)
129
+ {
130
+ 'scheme' => 'exact',
131
+ 'network' => route.network,
132
+ 'asset' => route.asset,
133
+ 'amount' => route.price,
134
+ 'payTo' => route.pay_to,
135
+ 'maxTimeoutSeconds' => route.max_deadline_seconds,
136
+ 'resource' => {
137
+ 'url' => request_url(env),
138
+ 'method' => env['REQUEST_METHOD'],
139
+ 'description' => route.description
140
+ }
141
+ }
142
+ end
143
+
144
+ def request_url(env)
145
+ scheme = env['rack.url_scheme'] || 'https'
146
+ host = env['HTTP_HOST'] || env['SERVER_NAME'] || 'localhost'
147
+ path = env['PATH_INFO'] || '/'
148
+ "#{scheme}://#{host}#{path}"
149
+ end
150
+
151
+ def payment_required_response(route, env)
152
+ body = {
153
+ 'x402Version' => X402_VERSION,
154
+ 'error' => 'Payment Required',
155
+ 'accepts' => [{
156
+ 'scheme' => 'exact',
157
+ 'network' => route.network,
158
+ 'asset' => route.asset,
159
+ 'amount' => route.price,
160
+ 'payTo' => route.pay_to,
161
+ 'maxTimeoutSeconds' => route.max_deadline_seconds,
162
+ 'resource' => {
163
+ 'url' => request_url(env),
164
+ 'method' => env['REQUEST_METHOD'],
165
+ 'description' => route.description
166
+ }
167
+ }]
168
+ }
169
+
170
+ [
171
+ 402,
172
+ {
173
+ 'Content-Type' => 'application/json',
174
+ 'X-Payment-Required' => 'true'
175
+ },
176
+ [JSON.generate(body)]
177
+ ]
178
+ end
179
+
180
+ def error_response(status, message)
181
+ body = { 'error' => message }
182
+ [
183
+ status,
184
+ { 'Content-Type' => 'application/json' },
185
+ [JSON.generate(body)]
186
+ ]
187
+ end
188
+ end
189
+ end
190
+ end