mpp-rb 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +133 -0
- data/lib/mpp/body_digest.rb +37 -0
- data/lib/mpp/challenge.rb +115 -0
- data/lib/mpp/challenge_echo.rb +19 -0
- data/lib/mpp/challenge_id.rb +54 -0
- data/lib/mpp/client/transport.rb +137 -0
- data/lib/mpp/client.rb +9 -0
- data/lib/mpp/credential.rb +20 -0
- data/lib/mpp/errors.rb +190 -0
- data/lib/mpp/expires.rb +60 -0
- data/lib/mpp/extensions/mcp/capabilities.rb +23 -0
- data/lib/mpp/extensions/mcp/constants.rb +17 -0
- data/lib/mpp/extensions/mcp/decorator.rb +44 -0
- data/lib/mpp/extensions/mcp/errors.rb +110 -0
- data/lib/mpp/extensions/mcp/types.rb +205 -0
- data/lib/mpp/extensions/mcp/verify.rb +152 -0
- data/lib/mpp/extensions/mcp.rb +16 -0
- data/lib/mpp/json.rb +32 -0
- data/lib/mpp/methods/stripe/charge_intent.rb +90 -0
- data/lib/mpp/methods/stripe/client_method.rb +42 -0
- data/lib/mpp/methods/stripe/defaults.rb +14 -0
- data/lib/mpp/methods/stripe/stripe_method.rb +63 -0
- data/lib/mpp/methods/stripe.rb +14 -0
- data/lib/mpp/methods/tempo/account.rb +52 -0
- data/lib/mpp/methods/tempo/attribution.rb +112 -0
- data/lib/mpp/methods/tempo/client_method.rb +259 -0
- data/lib/mpp/methods/tempo/defaults.rb +77 -0
- data/lib/mpp/methods/tempo/fee_payer_envelope.rb +74 -0
- data/lib/mpp/methods/tempo/intents.rb +377 -0
- data/lib/mpp/methods/tempo/keychain.rb +31 -0
- data/lib/mpp/methods/tempo/proof.rb +127 -0
- data/lib/mpp/methods/tempo/rpc.rb +60 -0
- data/lib/mpp/methods/tempo/schemas.rb +96 -0
- data/lib/mpp/methods/tempo/transaction.rb +144 -0
- data/lib/mpp/methods/tempo.rb +22 -0
- data/lib/mpp/parsing.rb +252 -0
- data/lib/mpp/receipt.rb +31 -0
- data/lib/mpp/secure_compare.rb +25 -0
- data/lib/mpp/server/decorator.rb +32 -0
- data/lib/mpp/server/defaults.rb +45 -0
- data/lib/mpp/server/intent.rb +40 -0
- data/lib/mpp/server/method.rb +27 -0
- data/lib/mpp/server/middleware.rb +51 -0
- data/lib/mpp/server/mpp_handler.rb +97 -0
- data/lib/mpp/server/verify.rb +129 -0
- data/lib/mpp/server.rb +15 -0
- data/lib/mpp/store.rb +49 -0
- data/lib/mpp/units.rb +57 -0
- data/lib/mpp/version.rb +6 -0
- data/lib/mpp-rb.rb +3 -0
- data/lib/mpp.rb +68 -0
- metadata +111 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "method"
|
|
5
|
+
|
|
6
|
+
module Mpp
|
|
7
|
+
module Server
|
|
8
|
+
DEFAULT_DECIMALS = 6
|
|
9
|
+
|
|
10
|
+
class MppHandler
|
|
11
|
+
extend T::Sig
|
|
12
|
+
|
|
13
|
+
sig { returns(T.untyped) }
|
|
14
|
+
attr_reader :method
|
|
15
|
+
|
|
16
|
+
sig { returns(String) }
|
|
17
|
+
attr_reader :realm
|
|
18
|
+
|
|
19
|
+
sig { returns(String) }
|
|
20
|
+
attr_reader :secret_key
|
|
21
|
+
|
|
22
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
|
23
|
+
attr_reader :defaults
|
|
24
|
+
|
|
25
|
+
sig { params(method: T.untyped, realm: String, secret_key: String, defaults: T.nilable(T::Hash[String, T.untyped])).void }
|
|
26
|
+
def initialize(method:, realm:, secret_key:, defaults: nil)
|
|
27
|
+
@method = T.let(method, T.untyped)
|
|
28
|
+
@realm = T.let(realm, String)
|
|
29
|
+
@secret_key = T.let(secret_key, String)
|
|
30
|
+
@defaults = T.let(defaults || {}, T::Hash[String, T.untyped])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Create with auto-detected realm and secret_key.
|
|
34
|
+
sig { params(method: T.untyped, realm: T.untyped, secret_key: T.untyped).returns(T.attached_class) }
|
|
35
|
+
def self.create(method:, realm: nil, secret_key: nil)
|
|
36
|
+
new(
|
|
37
|
+
method: method,
|
|
38
|
+
realm: realm || Defaults.detect_realm,
|
|
39
|
+
secret_key: secret_key || Defaults.detect_secret_key
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Handle a charge intent.
|
|
44
|
+
sig { params(authorization: T.nilable(String), amount: String, currency: T.nilable(String), recipient: T.nilable(String), expires: T.nilable(String), description: T.nilable(String), memo: T.nilable(String), fee_payer: T::Boolean, chain_id: T.nilable(Integer), extra: T.nilable(T::Hash[String, String])).returns(T.untyped) }
|
|
45
|
+
def charge(authorization, amount, currency: nil, recipient: nil, expires: nil,
|
|
46
|
+
description: nil, memo: nil, fee_payer: false, chain_id: nil, extra: nil)
|
|
47
|
+
intent = @method.intents["charge"]
|
|
48
|
+
raise ArgumentError, "Method #{@method.name} does not support charge intent" unless intent
|
|
49
|
+
|
|
50
|
+
resolved_currency = currency || (@method.respond_to?(:currency) ? @method.currency : nil)
|
|
51
|
+
resolved_recipient = recipient || (@method.respond_to?(:recipient) ? @method.recipient : nil)
|
|
52
|
+
raise ArgumentError, "currency must be set on the method or passed to charge()" unless resolved_currency
|
|
53
|
+
raise ArgumentError, "recipient must be set on the method or passed to charge()" unless resolved_recipient
|
|
54
|
+
|
|
55
|
+
decimals = @method.respond_to?(:decimals) ? @method.decimals : DEFAULT_DECIMALS
|
|
56
|
+
base_amount = Mpp::Units.parse_units(amount, decimals).to_s
|
|
57
|
+
|
|
58
|
+
request = {
|
|
59
|
+
"amount" => base_amount,
|
|
60
|
+
"currency" => resolved_currency,
|
|
61
|
+
"recipient" => resolved_recipient
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if extra
|
|
65
|
+
extra.each do |k, v|
|
|
66
|
+
raise ArgumentError, "extra must be a dict[str, str]" unless k.is_a?(String) && v.is_a?(String)
|
|
67
|
+
end
|
|
68
|
+
request["extra"] = extra
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
resolved_chain_id = chain_id
|
|
72
|
+
resolved_chain_id ||= @method.chain_id if @method.respond_to?(:chain_id)
|
|
73
|
+
|
|
74
|
+
if memo || fee_payer || !resolved_chain_id.nil?
|
|
75
|
+
method_details = {}
|
|
76
|
+
method_details["chainId"] = resolved_chain_id unless resolved_chain_id.nil?
|
|
77
|
+
method_details["memo"] = memo if memo
|
|
78
|
+
method_details["feePayer"] = true if fee_payer
|
|
79
|
+
request["methodDetails"] = method_details
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
request = Mpp::Server::MethodHelper.transform_request(@method, request, nil)
|
|
83
|
+
|
|
84
|
+
Verify.verify_or_challenge(
|
|
85
|
+
authorization: authorization,
|
|
86
|
+
intent: intent,
|
|
87
|
+
request: request,
|
|
88
|
+
realm: @realm,
|
|
89
|
+
secret_key: @secret_key,
|
|
90
|
+
method: @method.name,
|
|
91
|
+
description: description,
|
|
92
|
+
expires: expires
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module Mpp
|
|
7
|
+
module Server
|
|
8
|
+
module Verify
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
DEFAULT_EXPIRES_MINUTES = 5
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Verify a payment credential or generate a new challenge.
|
|
16
|
+
#
|
|
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)).returns(T.untyped) }
|
|
19
|
+
def verify_or_challenge(authorization:, intent:, request:, realm:, secret_key:,
|
|
20
|
+
method: nil, description: nil, meta: nil, expires: nil)
|
|
21
|
+
method_name = method || "tempo"
|
|
22
|
+
request = Mpp::Units.transform_units(request)
|
|
23
|
+
|
|
24
|
+
new_challenge = Kernel.lambda {
|
|
25
|
+
create_challenge(method_name, intent.name, request, realm, secret_key, description, meta, expires)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new_challenge.call if authorization.nil?
|
|
29
|
+
|
|
30
|
+
payment_scheme = extract_payment_scheme(authorization)
|
|
31
|
+
return new_challenge.call if payment_scheme.nil?
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
credential = Mpp::Credential.from_authorization(payment_scheme)
|
|
35
|
+
rescue Mpp::ParseError
|
|
36
|
+
return new_challenge.call
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Stateless challenge verification
|
|
40
|
+
echo = credential.challenge
|
|
41
|
+
begin
|
|
42
|
+
echo_request = echo.request.empty? ? {} : Mpp::Parsing.b64_decode(echo.request)
|
|
43
|
+
echo_opaque = (echo.opaque && !T.must(echo.opaque).empty?) ? Mpp::Parsing.b64_decode(echo.opaque) : nil
|
|
44
|
+
rescue Mpp::ParseError
|
|
45
|
+
return new_challenge.call
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
expected_id = Mpp.generate_challenge_id(
|
|
49
|
+
secret_key: secret_key,
|
|
50
|
+
realm: echo.realm,
|
|
51
|
+
method: echo.method,
|
|
52
|
+
intent: echo.intent,
|
|
53
|
+
request: echo_request,
|
|
54
|
+
expires: echo.expires,
|
|
55
|
+
digest: echo.digest,
|
|
56
|
+
opaque: echo_opaque
|
|
57
|
+
)
|
|
58
|
+
return new_challenge.call unless Mpp.secure_compare(echo.id, expected_id)
|
|
59
|
+
|
|
60
|
+
# Assert echoed fields match server's values
|
|
61
|
+
return new_challenge.call unless echo.realm == realm && echo.method == method_name && echo.intent == intent.name
|
|
62
|
+
|
|
63
|
+
# Assert echoed request matches server's current request
|
|
64
|
+
return new_challenge.call unless echo_request == request
|
|
65
|
+
|
|
66
|
+
return new_challenge.call unless echo_opaque == meta
|
|
67
|
+
|
|
68
|
+
# Reject expired challenges as defense-in-depth
|
|
69
|
+
if echo.expires
|
|
70
|
+
begin
|
|
71
|
+
expires_dt = Time.iso8601(T.must(echo.expires).gsub("Z", "+00:00"))
|
|
72
|
+
return new_challenge.call if expires_dt < Time.now.utc
|
|
73
|
+
rescue ArgumentError
|
|
74
|
+
# If we can't parse, continue to stricter check below
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Verify echoed request parameters match endpoint's expected request
|
|
79
|
+
request.each do |key, value|
|
|
80
|
+
return new_challenge.call unless echo_request[key] == value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Enforce challenge expiry - fail closed
|
|
84
|
+
return new_challenge.call unless echo.expires
|
|
85
|
+
|
|
86
|
+
begin
|
|
87
|
+
expires_dt = Time.iso8601(T.must(echo.expires).gsub("Z", "+00:00"))
|
|
88
|
+
rescue ArgumentError
|
|
89
|
+
return new_challenge.call
|
|
90
|
+
end
|
|
91
|
+
return new_challenge.call if expires_dt < Time.now.utc
|
|
92
|
+
|
|
93
|
+
receipt = intent.verify(credential, request)
|
|
94
|
+
[credential, receipt]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
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)).returns(Mpp::Challenge) }
|
|
98
|
+
def create_challenge(method, intent_name, request, realm, secret_key,
|
|
99
|
+
description = nil, meta = nil, expires = nil)
|
|
100
|
+
expires = nil if expires && !expires.is_a?(String)
|
|
101
|
+
|
|
102
|
+
if expires.nil?
|
|
103
|
+
expires_dt = Time.now.utc + (DEFAULT_EXPIRES_MINUTES * 60)
|
|
104
|
+
expires = expires_dt.strftime("%Y-%m-%dT%H:%M:%S.%LZ")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Mpp::Challenge.create(
|
|
108
|
+
secret_key: secret_key,
|
|
109
|
+
realm: realm,
|
|
110
|
+
method: method,
|
|
111
|
+
intent: intent_name,
|
|
112
|
+
request: request,
|
|
113
|
+
expires: expires,
|
|
114
|
+
description: description,
|
|
115
|
+
meta: meta
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
sig { params(header: String).returns(T.nilable(String)) }
|
|
120
|
+
def extract_payment_scheme(header)
|
|
121
|
+
header.split(",").each do |scheme|
|
|
122
|
+
scheme = scheme.strip
|
|
123
|
+
return scheme if scheme.downcase.start_with?("payment ")
|
|
124
|
+
end
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
data/lib/mpp/server.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Mpp
|
|
5
|
+
module Server
|
|
6
|
+
autoload :Defaults, "mpp/server/defaults"
|
|
7
|
+
autoload :Intent, "mpp/server/intent"
|
|
8
|
+
autoload :FunctionalIntent, "mpp/server/intent"
|
|
9
|
+
autoload :Method, "mpp/server/method"
|
|
10
|
+
autoload :Verify, "mpp/server/verify"
|
|
11
|
+
autoload :MppHandler, "mpp/server/mpp_handler"
|
|
12
|
+
autoload :Decorator, "mpp/server/decorator"
|
|
13
|
+
autoload :Middleware, "mpp/server/middleware"
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/mpp/store.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Mpp
|
|
5
|
+
# In-memory key-value store for development/testing.
|
|
6
|
+
# Production implementations should use Redis, DynamoDB, etc.
|
|
7
|
+
#
|
|
8
|
+
# Duck type interface (Store):
|
|
9
|
+
# get(key) -> value or nil
|
|
10
|
+
# put(key, value) -> void
|
|
11
|
+
# delete(key) -> void
|
|
12
|
+
# put_if_absent(key, value) -> bool
|
|
13
|
+
class MemoryStore
|
|
14
|
+
extend T::Sig
|
|
15
|
+
|
|
16
|
+
sig { void }
|
|
17
|
+
def initialize
|
|
18
|
+
@data = T.let({}, T::Hash[T.untyped, T.untyped])
|
|
19
|
+
@mutex = T.let(Mutex.new, Thread::Mutex)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
sig { params(key: String).returns(T.untyped) }
|
|
23
|
+
def get(key)
|
|
24
|
+
@mutex.synchronize { @data[key] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
sig { params(key: String, value: T.untyped).returns(T.untyped) }
|
|
28
|
+
def put(key, value)
|
|
29
|
+
@mutex.synchronize { @data[key] = value }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sig { params(key: String).returns(T.untyped) }
|
|
33
|
+
def delete(key)
|
|
34
|
+
@mutex.synchronize { @data.delete(key) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Store value under key only if key does not already exist.
|
|
38
|
+
# Returns true if the key was new, false if it already existed.
|
|
39
|
+
sig { params(key: String, value: T.untyped).returns(T::Boolean) }
|
|
40
|
+
def put_if_absent(key, value)
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
return false if @data.key?(key)
|
|
43
|
+
|
|
44
|
+
@data[key] = value
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/mpp/units.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bigdecimal"
|
|
5
|
+
|
|
6
|
+
module Mpp
|
|
7
|
+
module Units
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Convert a human-readable decimal string to base units.
|
|
13
|
+
# e.g. parse_units("1.5", 6) => 1500000
|
|
14
|
+
sig { params(value: T.untyped, decimals: T.any(Integer, Float, Rational, BigDecimal)).returns(Integer) }
|
|
15
|
+
def parse_units(value, decimals)
|
|
16
|
+
Kernel.raise ArgumentError, "amount is required" unless value.is_a?(String) && !value.strip.empty?
|
|
17
|
+
|
|
18
|
+
stripped = value.strip
|
|
19
|
+
d = Kernel.BigDecimal(stripped)
|
|
20
|
+
rescue ArgumentError
|
|
21
|
+
Kernel.raise ArgumentError, "Invalid amount: #{value.inspect}"
|
|
22
|
+
else
|
|
23
|
+
Kernel.raise ArgumentError, "amount must be finite" unless d.finite?
|
|
24
|
+
Kernel.raise ArgumentError, "amount must be non-negative" if d.negative?
|
|
25
|
+
|
|
26
|
+
result = d * (Kernel.BigDecimal(10)**decimals)
|
|
27
|
+
int_result = result.to_i
|
|
28
|
+
|
|
29
|
+
unless T.unsafe(result) == int_result
|
|
30
|
+
Kernel.raise ArgumentError,
|
|
31
|
+
"Amount #{value.inspect} with #{decimals} decimals produces fractional base units"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
int_result
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Transform request amounts from human-readable to base units.
|
|
38
|
+
# If `decimals` is present, converts amount and optional suggestedDeposit.
|
|
39
|
+
sig { params(request: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]) }
|
|
40
|
+
def transform_units(request)
|
|
41
|
+
return request unless request.key?("decimals")
|
|
42
|
+
|
|
43
|
+
result = request.dup
|
|
44
|
+
decimals = result.delete("decimals")
|
|
45
|
+
|
|
46
|
+
Kernel.raise ArgumentError, "decimals must be an integer, got #{decimals.class.name}" unless decimals.is_a?(Integer)
|
|
47
|
+
|
|
48
|
+
result["amount"] = parse_units(result["amount"], decimals).to_s if result.key?("amount")
|
|
49
|
+
|
|
50
|
+
if result.key?("suggestedDeposit") && !result["suggestedDeposit"].nil?
|
|
51
|
+
result["suggestedDeposit"] = parse_units(result["suggestedDeposit"], decimals).to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
result
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/mpp/version.rb
ADDED
data/lib/mpp-rb.rb
ADDED
data/lib/mpp.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
require_relative "mpp/version"
|
|
7
|
+
require_relative "mpp/json"
|
|
8
|
+
require_relative "mpp/challenge_id"
|
|
9
|
+
require_relative "mpp/secure_compare"
|
|
10
|
+
|
|
11
|
+
module Mpp
|
|
12
|
+
extend T::Sig
|
|
13
|
+
|
|
14
|
+
autoload :Challenge, "mpp/challenge"
|
|
15
|
+
autoload :ChallengeEcho, "mpp/challenge_echo"
|
|
16
|
+
autoload :Credential, "mpp/credential"
|
|
17
|
+
autoload :Receipt, "mpp/receipt"
|
|
18
|
+
autoload :Parsing, "mpp/parsing"
|
|
19
|
+
autoload :Json, "mpp/json"
|
|
20
|
+
autoload :BodyDigest, "mpp/body_digest"
|
|
21
|
+
autoload :Expires, "mpp/expires"
|
|
22
|
+
autoload :Units, "mpp/units"
|
|
23
|
+
autoload :MemoryStore, "mpp/store"
|
|
24
|
+
|
|
25
|
+
# Server module (autoloaded)
|
|
26
|
+
autoload :Server, "mpp/server"
|
|
27
|
+
|
|
28
|
+
# Client module (autoloaded)
|
|
29
|
+
autoload :Client, "mpp/client"
|
|
30
|
+
|
|
31
|
+
# Methods namespace
|
|
32
|
+
module Methods
|
|
33
|
+
autoload :Tempo, "mpp/methods/tempo"
|
|
34
|
+
autoload :Stripe, "mpp/methods/stripe"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Extensions namespace
|
|
38
|
+
module Extensions
|
|
39
|
+
autoload :MCP, "mpp/extensions/mcp"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig { params(method: T.untyped, realm: T.untyped, secret_key: T.untyped).returns(T.untyped) }
|
|
43
|
+
def self.create(method:, realm: nil, secret_key: nil)
|
|
44
|
+
Server::MppHandler.create(method: method, realm: realm, secret_key: secret_key)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Error hierarchy
|
|
48
|
+
autoload :PaymentError, "mpp/errors"
|
|
49
|
+
autoload :PaymentRequiredError, "mpp/errors"
|
|
50
|
+
autoload :MalformedCredentialError, "mpp/errors"
|
|
51
|
+
autoload :InvalidChallengeError, "mpp/errors"
|
|
52
|
+
autoload :VerificationFailedError, "mpp/errors"
|
|
53
|
+
autoload :PaymentExpiredError, "mpp/errors"
|
|
54
|
+
autoload :InvalidPayloadError, "mpp/errors"
|
|
55
|
+
autoload :PaymentInsufficientError, "mpp/errors"
|
|
56
|
+
autoload :PaymentMethodUnsupportedError, "mpp/errors"
|
|
57
|
+
autoload :PaymentActionRequiredError, "mpp/errors"
|
|
58
|
+
autoload :BadRequestError, "mpp/errors"
|
|
59
|
+
autoload :VerificationError, "mpp/errors"
|
|
60
|
+
autoload :ParseError, "mpp/errors"
|
|
61
|
+
autoload :InsufficientBalanceError, "mpp/errors"
|
|
62
|
+
autoload :InvalidSignatureError, "mpp/errors"
|
|
63
|
+
autoload :SignerMismatchError, "mpp/errors"
|
|
64
|
+
autoload :AmountExceedsDepositError, "mpp/errors"
|
|
65
|
+
autoload :DeltaTooSmallError, "mpp/errors"
|
|
66
|
+
autoload :ChannelNotFoundError, "mpp/errors"
|
|
67
|
+
autoload :ChannelClosedError, "mpp/errors"
|
|
68
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mpp-rb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Stripe
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.3'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.3'
|
|
27
|
+
description: Ruby SDK for the Machine Payments Protocol (MPP) — an HTTP 402 Payment
|
|
28
|
+
Authentication scheme.
|
|
29
|
+
email:
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- LICENSE
|
|
35
|
+
- README.md
|
|
36
|
+
- lib/mpp-rb.rb
|
|
37
|
+
- lib/mpp.rb
|
|
38
|
+
- lib/mpp/body_digest.rb
|
|
39
|
+
- lib/mpp/challenge.rb
|
|
40
|
+
- lib/mpp/challenge_echo.rb
|
|
41
|
+
- lib/mpp/challenge_id.rb
|
|
42
|
+
- lib/mpp/client.rb
|
|
43
|
+
- lib/mpp/client/transport.rb
|
|
44
|
+
- lib/mpp/credential.rb
|
|
45
|
+
- lib/mpp/errors.rb
|
|
46
|
+
- lib/mpp/expires.rb
|
|
47
|
+
- lib/mpp/extensions/mcp.rb
|
|
48
|
+
- lib/mpp/extensions/mcp/capabilities.rb
|
|
49
|
+
- lib/mpp/extensions/mcp/constants.rb
|
|
50
|
+
- lib/mpp/extensions/mcp/decorator.rb
|
|
51
|
+
- lib/mpp/extensions/mcp/errors.rb
|
|
52
|
+
- lib/mpp/extensions/mcp/types.rb
|
|
53
|
+
- lib/mpp/extensions/mcp/verify.rb
|
|
54
|
+
- lib/mpp/json.rb
|
|
55
|
+
- lib/mpp/methods/stripe.rb
|
|
56
|
+
- lib/mpp/methods/stripe/charge_intent.rb
|
|
57
|
+
- lib/mpp/methods/stripe/client_method.rb
|
|
58
|
+
- lib/mpp/methods/stripe/defaults.rb
|
|
59
|
+
- lib/mpp/methods/stripe/stripe_method.rb
|
|
60
|
+
- lib/mpp/methods/tempo.rb
|
|
61
|
+
- lib/mpp/methods/tempo/account.rb
|
|
62
|
+
- lib/mpp/methods/tempo/attribution.rb
|
|
63
|
+
- lib/mpp/methods/tempo/client_method.rb
|
|
64
|
+
- lib/mpp/methods/tempo/defaults.rb
|
|
65
|
+
- lib/mpp/methods/tempo/fee_payer_envelope.rb
|
|
66
|
+
- lib/mpp/methods/tempo/intents.rb
|
|
67
|
+
- lib/mpp/methods/tempo/keychain.rb
|
|
68
|
+
- lib/mpp/methods/tempo/proof.rb
|
|
69
|
+
- lib/mpp/methods/tempo/rpc.rb
|
|
70
|
+
- lib/mpp/methods/tempo/schemas.rb
|
|
71
|
+
- lib/mpp/methods/tempo/transaction.rb
|
|
72
|
+
- lib/mpp/parsing.rb
|
|
73
|
+
- lib/mpp/receipt.rb
|
|
74
|
+
- lib/mpp/secure_compare.rb
|
|
75
|
+
- lib/mpp/server.rb
|
|
76
|
+
- lib/mpp/server/decorator.rb
|
|
77
|
+
- lib/mpp/server/defaults.rb
|
|
78
|
+
- lib/mpp/server/intent.rb
|
|
79
|
+
- lib/mpp/server/method.rb
|
|
80
|
+
- lib/mpp/server/middleware.rb
|
|
81
|
+
- lib/mpp/server/mpp_handler.rb
|
|
82
|
+
- lib/mpp/server/verify.rb
|
|
83
|
+
- lib/mpp/store.rb
|
|
84
|
+
- lib/mpp/units.rb
|
|
85
|
+
- lib/mpp/version.rb
|
|
86
|
+
homepage: https://github.com/stripe/mpp-rb
|
|
87
|
+
licenses:
|
|
88
|
+
- MIT
|
|
89
|
+
metadata:
|
|
90
|
+
rubygems_mfa_required: 'true'
|
|
91
|
+
source_code_uri: https://github.com/stripe/mpp-rb
|
|
92
|
+
post_install_message:
|
|
93
|
+
rdoc_options: []
|
|
94
|
+
require_paths:
|
|
95
|
+
- lib
|
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: 3.3.0
|
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '0'
|
|
106
|
+
requirements: []
|
|
107
|
+
rubygems_version: 3.5.22
|
|
108
|
+
signing_key:
|
|
109
|
+
specification_version: 4
|
|
110
|
+
summary: HTTP 402 Payment Authentication for Ruby
|
|
111
|
+
test_files: []
|