mpp-rb 0.1.5 → 0.1.7
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.
- checksums.yaml +4 -4
- data/README.md +150 -0
- data/lib/mpp/challenge.rb +31 -8
- data/lib/mpp/challenge_echo.rb +14 -3
- data/lib/mpp/challenge_id.rb +30 -6
- data/lib/mpp/client/transport.rb +1 -1
- data/lib/mpp/extensions/mcp/verify.rb +1 -1
- data/lib/mpp/methods/evm/charge_intent.rb +38 -17
- data/lib/mpp/methods/evm/evm_method.rb +18 -2
- data/lib/mpp/methods/evm.rb +5 -2
- data/lib/mpp/methods/stripe/analytics_metadata.rb +24 -0
- data/lib/mpp/methods/stripe/charge_intent.rb +34 -11
- data/lib/mpp/methods/stripe/crypto_payment_recorder.rb +92 -0
- data/lib/mpp/methods/stripe/defaults.rb +1 -0
- data/lib/mpp/methods/stripe/machine_payments.rb +128 -0
- data/lib/mpp/methods/stripe/payment_intent_method.rb +103 -0
- data/lib/mpp/methods/stripe/payment_intent_options.rb +81 -0
- data/lib/mpp/methods/stripe/stripe_method.rb +30 -3
- data/lib/mpp/methods/stripe.rb +26 -0
- data/lib/mpp/methods/tempo/client_method.rb +58 -12
- data/lib/mpp/methods/tempo/defaults.rb +51 -0
- data/lib/mpp/methods/tempo/intents.rb +195 -125
- data/lib/mpp/methods/tempo/relay.rb +33 -31
- data/lib/mpp/methods/tempo/schemas.rb +3 -4
- data/lib/mpp/parsing.rb +29 -4
- data/lib/mpp/server/compose.rb +32 -15
- data/lib/mpp/server/decorator.rb +3 -1
- data/lib/mpp/server/intent.rb +6 -1
- data/lib/mpp/server/intent_lifecycle.rb +41 -0
- data/lib/mpp/server/method.rb +41 -0
- data/lib/mpp/server/middleware.rb +16 -2
- data/lib/mpp/server/mpp_handler.rb +87 -21
- data/lib/mpp/server/verify.rb +25 -15
- data/lib/mpp/server.rb +1 -0
- data/lib/mpp/validation.rb +27 -0
- data/lib/mpp/version.rb +1 -1
- data/lib/mpp.rb +14 -3
- metadata +22 -1
|
@@ -10,7 +10,7 @@ module Mpp
|
|
|
10
10
|
class MppHandler
|
|
11
11
|
extend T::Sig
|
|
12
12
|
|
|
13
|
-
REQUEST_OPTION_KEYS = [:body, :url, :payment_signature, :accept_payment, :http_method].freeze
|
|
13
|
+
REQUEST_OPTION_KEYS = [:body, :url, :payment_signature, :accept_payment, :http_method, :payment_authorization].freeze
|
|
14
14
|
|
|
15
15
|
sig { returns(T.untyped) }
|
|
16
16
|
attr_reader :method
|
|
@@ -27,6 +27,12 @@ module Mpp
|
|
|
27
27
|
sig { returns(T::Hash[String, T.untyped]) }
|
|
28
28
|
attr_reader :defaults
|
|
29
29
|
|
|
30
|
+
sig { returns(T::Boolean) }
|
|
31
|
+
attr_reader :requires_auth
|
|
32
|
+
|
|
33
|
+
sig { returns(T.nilable(String)) }
|
|
34
|
+
attr_reader :credential_header
|
|
35
|
+
|
|
30
36
|
sig do
|
|
31
37
|
params(
|
|
32
38
|
realm: String,
|
|
@@ -34,16 +40,21 @@ module Mpp
|
|
|
34
40
|
method: T.untyped,
|
|
35
41
|
methods: T.nilable(T::Array[T.untyped]),
|
|
36
42
|
defaults: T.nilable(T::Hash[String, T.untyped]),
|
|
37
|
-
events: T.nilable(Mpp::Events::Dispatcher)
|
|
43
|
+
events: T.nilable(Mpp::Events::Dispatcher),
|
|
44
|
+
requires_auth: T::Boolean
|
|
38
45
|
).void
|
|
39
46
|
end
|
|
40
|
-
def initialize(realm:, secret_key:, method: nil, methods: nil, defaults: nil, events: nil)
|
|
47
|
+
def initialize(realm:, secret_key:, method: nil, methods: nil, defaults: nil, events: nil, requires_auth: false)
|
|
41
48
|
@methods = T.let(normalize_methods(method, methods), T::Array[T.untyped])
|
|
42
49
|
@method = T.let(@methods.first, T.untyped)
|
|
43
50
|
@realm = T.let(realm, String)
|
|
44
51
|
@secret_key = T.let(secret_key, String)
|
|
45
52
|
@defaults = T.let(defaults || {}, T::Hash[String, T.untyped])
|
|
46
53
|
@events = T.let(events || Mpp::Events.server_dispatcher, Mpp::Events::Dispatcher)
|
|
54
|
+
@method_hook_events = T.let(Mpp::Events.server_dispatcher, Mpp::Events::Dispatcher)
|
|
55
|
+
@requires_auth = T.let(requires_auth, T::Boolean)
|
|
56
|
+
@credential_header = T.let(requires_auth ? Mpp::PAYMENT_AUTHORIZATION_HEADER : nil, T.nilable(String))
|
|
57
|
+
register_method_payment_success
|
|
47
58
|
end
|
|
48
59
|
|
|
49
60
|
# Create with auto-detected realm and secret_key.
|
|
@@ -53,16 +64,18 @@ module Mpp
|
|
|
53
64
|
methods: T.nilable(T::Array[T.untyped]),
|
|
54
65
|
realm: T.untyped,
|
|
55
66
|
secret_key: T.untyped,
|
|
56
|
-
events: T.nilable(Mpp::Events::Dispatcher)
|
|
67
|
+
events: T.nilable(Mpp::Events::Dispatcher),
|
|
68
|
+
requires_auth: T::Boolean
|
|
57
69
|
).returns(T.attached_class)
|
|
58
70
|
end
|
|
59
|
-
def self.create(method: nil, methods: nil, realm: nil, secret_key: nil, events: nil)
|
|
71
|
+
def self.create(method: nil, methods: nil, realm: nil, secret_key: nil, events: nil, requires_auth: false)
|
|
60
72
|
new(
|
|
61
73
|
method: method,
|
|
62
74
|
methods: methods,
|
|
63
75
|
realm: realm || Defaults.detect_realm,
|
|
64
76
|
secret_key: secret_key || Defaults.detect_secret_key,
|
|
65
|
-
events: events
|
|
77
|
+
events: events,
|
|
78
|
+
requires_auth: requires_auth
|
|
66
79
|
)
|
|
67
80
|
end
|
|
68
81
|
|
|
@@ -86,6 +99,14 @@ module Mpp
|
|
|
86
99
|
on(Mpp::Events::PAYMENT_SUCCESS, handler, &block)
|
|
87
100
|
end
|
|
88
101
|
|
|
102
|
+
# Select the HTTP header value that carries the Payment credential.
|
|
103
|
+
# When requires_auth is true, Payment credentials ride in
|
|
104
|
+
# Payment-Authorization so Authorization can hold application auth.
|
|
105
|
+
sig { params(authorization: T.nilable(String), payment_authorization: T.nilable(String)).returns(T.nilable(String)) }
|
|
106
|
+
def payment_credential_value(authorization, payment_authorization = nil)
|
|
107
|
+
@requires_auth ? payment_authorization : authorization
|
|
108
|
+
end
|
|
109
|
+
|
|
89
110
|
# Combine method offers into a single callable that presents every method.
|
|
90
111
|
sig { params(entries: T.untyped).returns(ComposedHandler) }
|
|
91
112
|
def compose(*entries)
|
|
@@ -109,9 +130,10 @@ module Mpp
|
|
|
109
130
|
|
|
110
131
|
# Handle a charge intent.
|
|
111
132
|
#
|
|
112
|
-
# With a single registered charge method
|
|
113
|
-
# [Credential, Receipt]. With multiple charge methods
|
|
114
|
-
#
|
|
133
|
+
# With a single registered charge method offering a single currency this
|
|
134
|
+
# returns Challenge or [Credential, Receipt]. With multiple charge methods,
|
|
135
|
+
# or a method accepting several currencies, it implicitly composes one
|
|
136
|
+
# offer per method and currency and returns a ComposedResult.
|
|
115
137
|
sig { params(authorization: T.nilable(String), amount: String, kwargs: T.untyped).returns(T.untyped) }
|
|
116
138
|
def charge(authorization, amount, **kwargs)
|
|
117
139
|
charge_methods = @methods.select { |candidate| candidate.intents.key?("charge") }
|
|
@@ -119,11 +141,12 @@ module Mpp
|
|
|
119
141
|
|
|
120
142
|
request_opts = kwargs.slice(*REQUEST_OPTION_KEYS)
|
|
121
143
|
offer_opts = kwargs.except(*REQUEST_OPTION_KEYS)
|
|
144
|
+
payment = payment_credential_value(authorization, request_opts[:payment_authorization])
|
|
122
145
|
|
|
123
|
-
if charge_methods.length == 1
|
|
146
|
+
if charge_methods.length == 1 && MethodHelper.offer_currencies(charge_methods.first, offer_opts).nil?
|
|
124
147
|
return charge_one(
|
|
125
148
|
charge_methods.first,
|
|
126
|
-
|
|
149
|
+
payment,
|
|
127
150
|
amount,
|
|
128
151
|
**request_opts,
|
|
129
152
|
**offer_opts
|
|
@@ -133,6 +156,7 @@ module Mpp
|
|
|
133
156
|
entries = charge_methods.map { |candidate| [candidate, offer_opts.merge(amount: amount)] }
|
|
134
157
|
ComposedHandler.from_entries(self, entries).call(
|
|
135
158
|
authorization: authorization,
|
|
159
|
+
payment_authorization: request_opts[:payment_authorization],
|
|
136
160
|
payment_signature: request_opts[:payment_signature],
|
|
137
161
|
body: request_opts[:body],
|
|
138
162
|
url: request_opts[:url],
|
|
@@ -142,8 +166,10 @@ module Mpp
|
|
|
142
166
|
end
|
|
143
167
|
|
|
144
168
|
# Verify or challenge a single method. Used by compose and charge.
|
|
145
|
-
|
|
146
|
-
|
|
169
|
+
# The optional request guard can omit an offer after its canonical request
|
|
170
|
+
# is built, ensuring the guard and challenge use the exact same request.
|
|
171
|
+
sig { params(method: T.untyped, authorization: T.nilable(String), amount: String, kwargs: T.untyped, request_guard: T.nilable(T.proc.params(request: T::Hash[String, T.untyped]).returns(T::Boolean))).returns(T.untyped) }
|
|
172
|
+
def charge_one(method, authorization, amount, **kwargs, &request_guard)
|
|
147
173
|
intent = method.intents["charge"]
|
|
148
174
|
raise ArgumentError, "Method #{method.name} does not support charge intent" unless intent
|
|
149
175
|
|
|
@@ -151,7 +177,9 @@ module Mpp
|
|
|
151
177
|
url = kwargs[:url]
|
|
152
178
|
payment_signature = kwargs[:payment_signature]
|
|
153
179
|
offer_opts = kwargs.except(*REQUEST_OPTION_KEYS)
|
|
180
|
+
intent, offer_opts = Mpp::Server::MethodHelper.prepare_intent(method, intent, offer_opts)
|
|
154
181
|
request = build_charge_request(method, amount, **offer_opts)
|
|
182
|
+
return nil if request_guard && !request_guard.call(request)
|
|
155
183
|
|
|
156
184
|
if payment_signature && method.respond_to?(:bind_x402_credential)
|
|
157
185
|
return verify_x402(
|
|
@@ -177,7 +205,9 @@ module Mpp
|
|
|
177
205
|
description: offer_opts[:description],
|
|
178
206
|
expires: offer_opts[:expires],
|
|
179
207
|
events: @events,
|
|
180
|
-
|
|
208
|
+
method_hook_events: @method_hook_events,
|
|
209
|
+
body: body,
|
|
210
|
+
header: @credential_header
|
|
181
211
|
)
|
|
182
212
|
end
|
|
183
213
|
|
|
@@ -187,7 +217,6 @@ module Mpp
|
|
|
187
217
|
currency = kwargs[:currency]
|
|
188
218
|
recipient = kwargs[:recipient]
|
|
189
219
|
external_id = kwargs[:external_id]
|
|
190
|
-
memo = kwargs[:memo]
|
|
191
220
|
fee_payer = if kwargs.key?(:fee_payer)
|
|
192
221
|
kwargs[:fee_payer]
|
|
193
222
|
else
|
|
@@ -197,7 +226,7 @@ module Mpp
|
|
|
197
226
|
extra = kwargs[:extra]
|
|
198
227
|
mppx_scope = kwargs[:mppx_scope]
|
|
199
228
|
|
|
200
|
-
resolved_currency = currency || (method
|
|
229
|
+
resolved_currency = currency || primary_currency(method)
|
|
201
230
|
resolved_recipient = recipient || (method.respond_to?(:recipient) ? method.recipient : nil)
|
|
202
231
|
raise ArgumentError, "currency must be set on the method or passed to charge()" unless resolved_currency
|
|
203
232
|
raise ArgumentError, "recipient must be set on the method or passed to charge()" unless resolved_recipient
|
|
@@ -228,10 +257,9 @@ module Mpp
|
|
|
228
257
|
resolved_chain_id = chain_id
|
|
229
258
|
resolved_chain_id ||= method.chain_id if method.respond_to?(:chain_id)
|
|
230
259
|
|
|
231
|
-
if
|
|
260
|
+
if fee_payer || !resolved_chain_id.nil?
|
|
232
261
|
method_details = {}
|
|
233
262
|
method_details["chainId"] = resolved_chain_id unless resolved_chain_id.nil?
|
|
234
|
-
method_details["memo"] = memo if memo
|
|
235
263
|
method_details["feePayer"] = true if fee_payer
|
|
236
264
|
request["methodDetails"] = method_details
|
|
237
265
|
end
|
|
@@ -268,6 +296,15 @@ module Mpp
|
|
|
268
296
|
list
|
|
269
297
|
end
|
|
270
298
|
|
|
299
|
+
# A method's first accepted currency, else its singular currency.
|
|
300
|
+
sig { params(method: T.untyped).returns(T.untyped) }
|
|
301
|
+
def primary_currency(method)
|
|
302
|
+
currencies = method.respond_to?(:currencies) ? method.currencies : nil
|
|
303
|
+
return currencies.first if currencies.is_a?(Array) && !currencies.empty?
|
|
304
|
+
|
|
305
|
+
method.respond_to?(:currency) ? method.currency : nil
|
|
306
|
+
end
|
|
307
|
+
|
|
271
308
|
sig { params(key: String).returns(T.untyped) }
|
|
272
309
|
def resolve_method_key(key)
|
|
273
310
|
name, intent = key.split("/", 2)
|
|
@@ -304,7 +341,8 @@ module Mpp
|
|
|
304
341
|
description,
|
|
305
342
|
nil,
|
|
306
343
|
expires,
|
|
307
|
-
body
|
|
344
|
+
body,
|
|
345
|
+
@credential_header
|
|
308
346
|
)
|
|
309
347
|
method_context = {name: method.name, intent: intent.name}
|
|
310
348
|
|
|
@@ -317,7 +355,7 @@ module Mpp
|
|
|
317
355
|
body: body,
|
|
318
356
|
http_method: http_method
|
|
319
357
|
)
|
|
320
|
-
receipt =
|
|
358
|
+
receipt = IntentLifecycle.call(intent, credential, challenge.request)
|
|
321
359
|
rescue => error
|
|
322
360
|
if @events.has_handlers?(Mpp::Events::PAYMENT_FAILED)
|
|
323
361
|
Verify.emit_payment_failed(
|
|
@@ -330,6 +368,9 @@ module Mpp
|
|
|
330
368
|
retry_challenge: challenge
|
|
331
369
|
)
|
|
332
370
|
end
|
|
371
|
+
if error.is_a?(Mpp::PaymentError) && error.status != 402
|
|
372
|
+
raise
|
|
373
|
+
end
|
|
333
374
|
if error.is_a?(Mpp::ParseError) || error.is_a?(Mpp::VerificationError) || error.is_a?(Mpp::PaymentError)
|
|
334
375
|
if @events.has_handlers?(Mpp::Events::CHALLENGE_CREATED)
|
|
335
376
|
Verify.emit_challenge_created(
|
|
@@ -346,9 +387,10 @@ module Mpp
|
|
|
346
387
|
raise
|
|
347
388
|
end
|
|
348
389
|
|
|
349
|
-
if @events.has_handlers?(Mpp::Events::PAYMENT_SUCCESS)
|
|
390
|
+
if @events.has_handlers?(Mpp::Events::PAYMENT_SUCCESS) || @method_hook_events.has_handlers?(Mpp::Events::PAYMENT_SUCCESS)
|
|
350
391
|
Verify.emit_payment_success(
|
|
351
392
|
dispatcher: @events,
|
|
393
|
+
method_hook_dispatcher: @method_hook_events,
|
|
352
394
|
challenge: challenge,
|
|
353
395
|
credential: credential,
|
|
354
396
|
method: method_context,
|
|
@@ -358,6 +400,30 @@ module Mpp
|
|
|
358
400
|
end
|
|
359
401
|
[credential, receipt]
|
|
360
402
|
end
|
|
403
|
+
|
|
404
|
+
private
|
|
405
|
+
|
|
406
|
+
sig { void }
|
|
407
|
+
def register_method_payment_success
|
|
408
|
+
@methods.each do |payment_method|
|
|
409
|
+
next unless payment_method.respond_to?(:on_payment_success)
|
|
410
|
+
|
|
411
|
+
hook = payment_method.on_payment_success
|
|
412
|
+
next if hook.nil?
|
|
413
|
+
raise ArgumentError, "on_payment_success must be callable" unless hook.respond_to?(:call)
|
|
414
|
+
|
|
415
|
+
method_name = payment_method.name
|
|
416
|
+
intent_names = payment_method.intents.each_value.map { |intent| intent.name }
|
|
417
|
+
@method_hook_events.on(Mpp::Events::PAYMENT_SUCCESS) do |payload|
|
|
418
|
+
event_method = payload[:method]
|
|
419
|
+
next unless event_method.is_a?(Hash)
|
|
420
|
+
next unless event_method[:name] == method_name
|
|
421
|
+
next unless intent_names.include?(event_method[:intent])
|
|
422
|
+
|
|
423
|
+
hook.call(payload)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
361
427
|
end
|
|
362
428
|
end
|
|
363
429
|
end
|
data/lib/mpp/server/verify.rb
CHANGED
|
@@ -15,17 +15,17 @@ module Mpp
|
|
|
15
15
|
# Verify a payment credential or generate a new challenge.
|
|
16
16
|
#
|
|
17
17
|
# Returns Challenge (payment required) or [Credential, Receipt] (verified).
|
|
18
|
-
sig { params(authorization: T.nilable(String), intent: T.untyped, request: T::Hash[String, T.untyped], realm: String, secret_key: String, method: T.nilable(String), description: T.nilable(String), meta: T.nilable(T::Hash[String, T.untyped]), expires: T.nilable(String), events: T.nilable(Mpp::Events::Dispatcher), body: T.untyped).returns(T.untyped) }
|
|
18
|
+
sig { params(authorization: T.nilable(String), intent: T.untyped, request: T::Hash[String, T.untyped], realm: String, secret_key: String, method: T.nilable(String), description: T.nilable(String), meta: T.nilable(T::Hash[String, T.untyped]), expires: T.nilable(String), events: T.nilable(Mpp::Events::Dispatcher), method_hook_events: T.nilable(Mpp::Events::Dispatcher), body: T.untyped, header: T.nilable(String)).returns(T.untyped) }
|
|
19
19
|
def verify_or_challenge(authorization:, intent:, request:, realm:, secret_key:,
|
|
20
|
-
method: nil, description: nil, meta: nil, expires: nil, events: nil, body: nil)
|
|
20
|
+
method: nil, description: nil, meta: nil, expires: nil, events: nil, method_hook_events: nil, body: nil, header: nil)
|
|
21
21
|
method_name = method || "tempo"
|
|
22
22
|
request = Mpp::Units.transform_units(request)
|
|
23
23
|
dispatcher = events
|
|
24
|
-
events_enabled = dispatcher&.has_handlers?
|
|
24
|
+
events_enabled = dispatcher&.has_handlers? || method_hook_events&.has_handlers?
|
|
25
25
|
method_context = events_enabled ? {name: method_name, intent: intent.name} : nil
|
|
26
26
|
|
|
27
27
|
new_challenge = Kernel.lambda { |credential = nil, error = nil, submitted_challenge = nil|
|
|
28
|
-
challenge = create_challenge(method_name, intent.name, request, realm, secret_key, description, meta, expires, body)
|
|
28
|
+
challenge = create_challenge(method_name, intent.name, request, realm, secret_key, description, meta, expires, body, header)
|
|
29
29
|
if error && dispatcher&.has_handlers?(Mpp::Events::PAYMENT_FAILED)
|
|
30
30
|
emit_payment_failed(
|
|
31
31
|
dispatcher: dispatcher,
|
|
@@ -79,7 +79,8 @@ module Mpp
|
|
|
79
79
|
request: echo_request,
|
|
80
80
|
expires: echo.expires,
|
|
81
81
|
digest: echo.digest,
|
|
82
|
-
opaque: echo_opaque
|
|
82
|
+
opaque: echo_opaque,
|
|
83
|
+
header: echo.header
|
|
83
84
|
)
|
|
84
85
|
unless Mpp.secure_compare(echo.id, expected_id)
|
|
85
86
|
return new_challenge.call(
|
|
@@ -157,7 +158,7 @@ module Mpp
|
|
|
157
158
|
end
|
|
158
159
|
|
|
159
160
|
begin
|
|
160
|
-
receipt =
|
|
161
|
+
receipt = IntentLifecycle.call(intent, credential, request)
|
|
161
162
|
rescue => e
|
|
162
163
|
if dispatcher&.has_handlers?(Mpp::Events::PAYMENT_FAILED)
|
|
163
164
|
emit_payment_failed(
|
|
@@ -172,9 +173,10 @@ module Mpp
|
|
|
172
173
|
end
|
|
173
174
|
Kernel.raise
|
|
174
175
|
end
|
|
175
|
-
if dispatcher&.has_handlers?(Mpp::Events::PAYMENT_SUCCESS)
|
|
176
|
+
if dispatcher&.has_handlers?(Mpp::Events::PAYMENT_SUCCESS) || method_hook_events&.has_handlers?(Mpp::Events::PAYMENT_SUCCESS)
|
|
176
177
|
emit_payment_success(
|
|
177
178
|
dispatcher: dispatcher,
|
|
179
|
+
method_hook_dispatcher: method_hook_events,
|
|
178
180
|
challenge: echo,
|
|
179
181
|
credential: credential,
|
|
180
182
|
method: T.must(method_context),
|
|
@@ -185,9 +187,9 @@ module Mpp
|
|
|
185
187
|
[credential, receipt]
|
|
186
188
|
end
|
|
187
189
|
|
|
188
|
-
sig { params(method: String, intent_name: String, request: T::Hash[String, T.untyped], realm: String, secret_key: String, description: T.nilable(String), meta: T.nilable(T::Hash[String, T.untyped]), expires: T.nilable(String), body: T.untyped).returns(Mpp::Challenge) }
|
|
190
|
+
sig { params(method: String, intent_name: String, request: T::Hash[String, T.untyped], realm: String, secret_key: String, description: T.nilable(String), meta: T.nilable(T::Hash[String, T.untyped]), expires: T.nilable(String), body: T.untyped, header: T.nilable(String)).returns(Mpp::Challenge) }
|
|
189
191
|
def create_challenge(method, intent_name, request, realm, secret_key,
|
|
190
|
-
description = nil, meta = nil, expires = nil, body = nil)
|
|
192
|
+
description = nil, meta = nil, expires = nil, body = nil, header = nil)
|
|
191
193
|
expires = nil if expires && !expires.is_a?(String)
|
|
192
194
|
|
|
193
195
|
if expires.nil?
|
|
@@ -205,7 +207,8 @@ module Mpp
|
|
|
205
207
|
expires: expires,
|
|
206
208
|
digest: digest,
|
|
207
209
|
description: description,
|
|
208
|
-
meta: meta
|
|
210
|
+
meta: meta,
|
|
211
|
+
header: header
|
|
209
212
|
)
|
|
210
213
|
end
|
|
211
214
|
|
|
@@ -258,17 +261,24 @@ module Mpp
|
|
|
258
261
|
dispatcher.emit(Mpp::Events::PAYMENT_FAILED, payload)
|
|
259
262
|
end
|
|
260
263
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
+
# Method hooks are owned by a handler, while `dispatcher` may be shared
|
|
265
|
+
# by many handlers. Emit the same payload to each without exposing that
|
|
266
|
+
# handler ownership in the public event payload.
|
|
267
|
+
sig { params(dispatcher: T.nilable(Mpp::Events::Dispatcher), challenge: T.untyped, credential: T.untyped, method: T::Hash[Symbol, T.untyped], receipt: T.untyped, request: T::Hash[String, T.untyped], method_hook_dispatcher: T.nilable(Mpp::Events::Dispatcher)).void }
|
|
268
|
+
def emit_payment_success(dispatcher:, challenge:, credential:, method:, receipt:, request:, method_hook_dispatcher: nil)
|
|
269
|
+
public_handlers = dispatcher&.has_handlers?(Mpp::Events::PAYMENT_SUCCESS)
|
|
270
|
+
hook_handlers = method_hook_dispatcher&.has_handlers?(Mpp::Events::PAYMENT_SUCCESS)
|
|
271
|
+
return unless public_handlers || hook_handlers
|
|
264
272
|
|
|
265
|
-
|
|
273
|
+
payload = {
|
|
266
274
|
challenge: challenge,
|
|
267
275
|
credential: credential,
|
|
268
276
|
method: method,
|
|
269
277
|
receipt: receipt,
|
|
270
278
|
request: request
|
|
271
|
-
}
|
|
279
|
+
}
|
|
280
|
+
dispatcher.emit(Mpp::Events::PAYMENT_SUCCESS, payload) if public_handlers
|
|
281
|
+
method_hook_dispatcher.emit(Mpp::Events::PAYMENT_SUCCESS, payload) if hook_handlers && method_hook_dispatcher != dispatcher
|
|
272
282
|
end
|
|
273
283
|
end
|
|
274
284
|
end
|
data/lib/mpp/server.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Mpp
|
|
|
6
6
|
autoload :Defaults, "mpp/server/defaults"
|
|
7
7
|
autoload :Intent, "mpp/server/intent"
|
|
8
8
|
autoload :FunctionalIntent, "mpp/server/intent"
|
|
9
|
+
autoload :IntentLifecycle, "mpp/server/intent_lifecycle"
|
|
9
10
|
autoload :Method, "mpp/server/method"
|
|
10
11
|
autoload :Verify, "mpp/server/verify"
|
|
11
12
|
autoload :MppHandler, "mpp/server/mpp_handler"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Mpp
|
|
5
|
+
# Non-mutating validation result, matching mppx's Method.Validation fields.
|
|
6
|
+
# Details are method-specific, not a receipt or a guarantee of settlement.
|
|
7
|
+
# Challenge issuance and route binding are checked by the server, not by
|
|
8
|
+
# constructing this record or calling a rail's validate method directly.
|
|
9
|
+
Validation = Data.define(:challenge, :credential, :intent, :method, :request, :details, :source) do
|
|
10
|
+
extend T::Sig
|
|
11
|
+
|
|
12
|
+
sig do
|
|
13
|
+
params(
|
|
14
|
+
challenge: Mpp::ChallengeEcho,
|
|
15
|
+
credential: Mpp::Credential,
|
|
16
|
+
intent: String,
|
|
17
|
+
method: String,
|
|
18
|
+
request: T::Hash[String, T.untyped],
|
|
19
|
+
details: T.untyped,
|
|
20
|
+
source: T.nilable(String)
|
|
21
|
+
).void
|
|
22
|
+
end
|
|
23
|
+
def initialize(challenge:, credential:, intent:, method:, request:, details:, source: nil)
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/mpp/version.rb
CHANGED
data/lib/mpp.rb
CHANGED
|
@@ -11,10 +11,14 @@ require_relative "mpp/secure_compare"
|
|
|
11
11
|
module Mpp
|
|
12
12
|
extend T::Sig
|
|
13
13
|
|
|
14
|
+
AUTHORIZATION_HEADER = T.let("Authorization", String)
|
|
15
|
+
PAYMENT_AUTHORIZATION_HEADER = T.let("Payment-Authorization", String)
|
|
16
|
+
|
|
14
17
|
autoload :Challenge, "mpp/challenge"
|
|
15
18
|
autoload :ChallengeEcho, "mpp/challenge_echo"
|
|
16
19
|
autoload :Credential, "mpp/credential"
|
|
17
20
|
autoload :Receipt, "mpp/receipt"
|
|
21
|
+
autoload :Validation, "mpp/validation"
|
|
18
22
|
autoload :Parsing, "mpp/parsing"
|
|
19
23
|
autoload :Json, "mpp/json"
|
|
20
24
|
autoload :BodyDigest, "mpp/body_digest"
|
|
@@ -43,9 +47,16 @@ module Mpp
|
|
|
43
47
|
autoload :MCP, "mpp/extensions/mcp"
|
|
44
48
|
end
|
|
45
49
|
|
|
46
|
-
sig { params(method: T.untyped, methods: T.nilable(T::Array[T.untyped]), realm: T.untyped, secret_key: T.untyped, events: T.nilable(Mpp::Events::Dispatcher)).returns(T.untyped) }
|
|
47
|
-
def self.create(method: nil, methods: nil, realm: nil, secret_key: nil, events: nil)
|
|
48
|
-
Server::MppHandler.create(
|
|
50
|
+
sig { params(method: T.untyped, methods: T.nilable(T::Array[T.untyped]), realm: T.untyped, secret_key: T.untyped, events: T.nilable(Mpp::Events::Dispatcher), requires_auth: T::Boolean).returns(T.untyped) }
|
|
51
|
+
def self.create(method: nil, methods: nil, realm: nil, secret_key: nil, events: nil, requires_auth: false)
|
|
52
|
+
Server::MppHandler.create(
|
|
53
|
+
method: method,
|
|
54
|
+
methods: methods,
|
|
55
|
+
realm: realm,
|
|
56
|
+
secret_key: secret_key,
|
|
57
|
+
events: events,
|
|
58
|
+
requires_auth: requires_auth
|
|
59
|
+
)
|
|
49
60
|
end
|
|
50
61
|
|
|
51
62
|
# Error hierarchy
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mpp-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stripe
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.3'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: sorbet-runtime
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
40
54
|
description: Ruby SDK for the Machine Payments Protocol (MPP) — an HTTP 402 Payment
|
|
41
55
|
Authentication scheme.
|
|
42
56
|
executables: []
|
|
@@ -73,9 +87,14 @@ files:
|
|
|
73
87
|
- lib/mpp/methods/evm/charge_intent.rb
|
|
74
88
|
- lib/mpp/methods/evm/evm_method.rb
|
|
75
89
|
- lib/mpp/methods/stripe.rb
|
|
90
|
+
- lib/mpp/methods/stripe/analytics_metadata.rb
|
|
76
91
|
- lib/mpp/methods/stripe/charge_intent.rb
|
|
77
92
|
- lib/mpp/methods/stripe/client_method.rb
|
|
93
|
+
- lib/mpp/methods/stripe/crypto_payment_recorder.rb
|
|
78
94
|
- lib/mpp/methods/stripe/defaults.rb
|
|
95
|
+
- lib/mpp/methods/stripe/machine_payments.rb
|
|
96
|
+
- lib/mpp/methods/stripe/payment_intent_method.rb
|
|
97
|
+
- lib/mpp/methods/stripe/payment_intent_options.rb
|
|
79
98
|
- lib/mpp/methods/stripe/stripe_method.rb
|
|
80
99
|
- lib/mpp/methods/tempo.rb
|
|
81
100
|
- lib/mpp/methods/tempo/account.rb
|
|
@@ -101,6 +120,7 @@ files:
|
|
|
101
120
|
- lib/mpp/server/decorator.rb
|
|
102
121
|
- lib/mpp/server/defaults.rb
|
|
103
122
|
- lib/mpp/server/intent.rb
|
|
123
|
+
- lib/mpp/server/intent_lifecycle.rb
|
|
104
124
|
- lib/mpp/server/method.rb
|
|
105
125
|
- lib/mpp/server/middleware.rb
|
|
106
126
|
- lib/mpp/server/mpp_handler.rb
|
|
@@ -108,6 +128,7 @@ files:
|
|
|
108
128
|
- lib/mpp/server/verify.rb
|
|
109
129
|
- lib/mpp/store.rb
|
|
110
130
|
- lib/mpp/units.rb
|
|
131
|
+
- lib/mpp/validation.rb
|
|
111
132
|
- lib/mpp/version.rb
|
|
112
133
|
- lib/mpp/x402.rb
|
|
113
134
|
- lib/mpp/x402/facilitator.rb
|