mppx 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.
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mppx
4
+ module Errors
5
+ class PaymentError < StandardError
6
+ def type
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def title
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def status
15
+ 402
16
+ end
17
+
18
+ def to_problem_details(challenge_id = nil)
19
+ result = {
20
+ type: type,
21
+ title: title,
22
+ status: status,
23
+ detail: message
24
+ }
25
+ result[:challengeId] = challenge_id if challenge_id
26
+ result
27
+ end
28
+ end
29
+
30
+ class MalformedCredentialError < PaymentError
31
+ def initialize(reason: nil)
32
+ msg = reason ? "Credential is malformed: #{reason}." : "Credential is malformed."
33
+ super(msg)
34
+ end
35
+
36
+ def type = "https://paymentauth.org/problems/malformed-credential"
37
+ def title = "Malformed Credential"
38
+ def status = 402
39
+ end
40
+
41
+ class InvalidChallengeError < PaymentError
42
+ def initialize(id: nil, reason: nil)
43
+ id_part = id ? " \"#{id}\"" : ""
44
+ reason_part = reason ? ": #{reason}" : ""
45
+ super("Challenge#{id_part} is invalid#{reason_part}.")
46
+ end
47
+
48
+ def type = "https://paymentauth.org/problems/invalid-challenge"
49
+ def title = "Invalid Challenge"
50
+ def status = 402
51
+ end
52
+
53
+ class VerificationFailedError < PaymentError
54
+ def initialize(reason: nil)
55
+ msg = reason ? "Payment verification failed: #{reason}." : "Payment verification failed."
56
+ super(msg)
57
+ end
58
+
59
+ def type = "https://paymentauth.org/problems/verification-failed"
60
+ def title = "Verification Failed"
61
+ def status = 402
62
+ end
63
+
64
+ class PaymentActionRequiredError < PaymentError
65
+ def initialize(reason: nil)
66
+ msg = reason ? "Payment requires action: #{reason}." : "Payment requires action."
67
+ super(msg)
68
+ end
69
+
70
+ def type = "https://paymentauth.org/problems/payment-action-required"
71
+ def title = "Payment Action Required"
72
+ def status = 402
73
+ end
74
+
75
+ class PaymentExpiredError < PaymentError
76
+ def initialize(expires: nil)
77
+ msg = expires ? "Payment expired at #{expires}." : "Payment has expired."
78
+ super(msg)
79
+ end
80
+
81
+ def type = "https://paymentauth.org/problems/payment-expired"
82
+ def title = "Payment Expired"
83
+ def status = 402
84
+ end
85
+
86
+ class PaymentRequiredError < PaymentError
87
+ def initialize(description: nil)
88
+ parts = ["Payment is required"]
89
+ parts << "(#{description})" if description
90
+ super("#{parts.join(" ")}.")
91
+ end
92
+
93
+ def type = "https://paymentauth.org/problems/payment-required"
94
+ def title = "Payment Required"
95
+ def status = 402
96
+ end
97
+
98
+ class InvalidPayloadError < PaymentError
99
+ def initialize(reason: nil)
100
+ msg = reason ? "Credential payload is invalid: #{reason}." : "Credential payload is invalid."
101
+ super(msg)
102
+ end
103
+
104
+ def type = "https://paymentauth.org/problems/invalid-payload"
105
+ def title = "Invalid Payload"
106
+ def status = 402
107
+ end
108
+
109
+ class BadRequestError < PaymentError
110
+ def initialize(reason: nil)
111
+ msg = reason ? "Bad request: #{reason}." : "Bad request."
112
+ super(msg)
113
+ end
114
+
115
+ def type = "https://paymentauth.org/problems/bad-request"
116
+ def title = "Bad Request"
117
+ def status = 400
118
+ end
119
+
120
+ class PaymentInsufficientError < PaymentError
121
+ def initialize(reason: nil)
122
+ msg = reason ? "Payment insufficient: #{reason}." : "Payment amount is insufficient."
123
+ super(msg)
124
+ end
125
+
126
+ def type = "https://paymentauth.org/problems/payment-insufficient"
127
+ def title = "Payment Insufficient"
128
+ def status = 402
129
+ end
130
+
131
+ class PaymentMethodUnsupportedError < PaymentError
132
+ def initialize(method: nil)
133
+ msg = method ? "Payment method \"#{method}\" is not supported." : "Payment method is not supported."
134
+ super(msg)
135
+ end
136
+
137
+ def type = "https://paymentauth.org/problems/method-unsupported"
138
+ def title = "Method Unsupported"
139
+ def status = 400
140
+ end
141
+
142
+ class InsufficientBalanceError < PaymentError
143
+ def initialize(reason: nil)
144
+ msg = reason ? "Insufficient balance: #{reason}." : "Insufficient balance."
145
+ super(msg)
146
+ end
147
+
148
+ def type = "https://paymentauth.org/problems/session/insufficient-balance"
149
+ def title = "Insufficient Balance"
150
+ def status = 402
151
+ end
152
+
153
+ class InvalidSignatureError < PaymentError
154
+ def initialize(reason: nil)
155
+ msg = reason ? "Invalid signature: #{reason}." : "Invalid signature."
156
+ super(msg)
157
+ end
158
+
159
+ def type = "https://paymentauth.org/problems/session/invalid-signature"
160
+ def title = "Invalid Signature"
161
+ def status = 402
162
+ end
163
+
164
+ class SignerMismatchError < PaymentError
165
+ def initialize(reason: nil)
166
+ msg = reason ? "Signer mismatch: #{reason}." : "Signer is not authorized for this channel."
167
+ super(msg)
168
+ end
169
+
170
+ def type = "https://paymentauth.org/problems/session/signer-mismatch"
171
+ def title = "Signer Mismatch"
172
+ def status = 402
173
+ end
174
+
175
+ class AmountExceedsDepositError < PaymentError
176
+ def initialize(reason: nil)
177
+ msg = reason ? "Amount exceeds deposit: #{reason}." : "Voucher amount exceeds channel deposit."
178
+ super(msg)
179
+ end
180
+
181
+ def type = "https://paymentauth.org/problems/session/amount-exceeds-deposit"
182
+ def title = "Amount Exceeds Deposit"
183
+ def status = 402
184
+ end
185
+
186
+ class DeltaTooSmallError < PaymentError
187
+ def initialize(reason: nil)
188
+ msg = reason ? "Delta too small: #{reason}." : "Amount increase below minimum voucher delta."
189
+ super(msg)
190
+ end
191
+
192
+ def type = "https://paymentauth.org/problems/session/delta-too-small"
193
+ def title = "Delta Too Small"
194
+ def status = 402
195
+ end
196
+
197
+ class ChannelNotFoundError < PaymentError
198
+ def initialize(reason: nil)
199
+ msg = reason ? "Channel not found: #{reason}." : "No channel with this ID exists."
200
+ super(msg)
201
+ end
202
+
203
+ def type = "https://paymentauth.org/problems/session/channel-not-found"
204
+ def title = "Channel Not Found"
205
+ def status = 410
206
+ end
207
+
208
+ class ChannelClosedError < PaymentError
209
+ def initialize(reason: nil)
210
+ msg = reason ? "Channel closed: #{reason}." : "Channel is closed."
211
+ super(msg)
212
+ end
213
+
214
+ def type = "https://paymentauth.org/problems/session/channel-finalized"
215
+ def title = "Channel Closed"
216
+ def status = 410
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mppx
4
+ module Expires
5
+ module_function
6
+
7
+ def seconds(n)
8
+ format_time(Time.now.utc + n)
9
+ end
10
+
11
+ def minutes(n)
12
+ format_time(Time.now.utc + (n * 60))
13
+ end
14
+
15
+ def hours(n)
16
+ format_time(Time.now.utc + (n * 3600))
17
+ end
18
+
19
+ def days(n)
20
+ format_time(Time.now.utc + (n * 86_400))
21
+ end
22
+
23
+ def weeks(n)
24
+ format_time(Time.now.utc + (n * 7 * 86_400))
25
+ end
26
+
27
+ def months(n)
28
+ format_time(Time.now.utc + (n * 30 * 86_400))
29
+ end
30
+
31
+ def years(n)
32
+ format_time(Time.now.utc + (n * 365 * 86_400))
33
+ end
34
+
35
+ def format_time(time)
36
+ time.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
37
+ end
38
+ end
39
+ end
data/lib/mppx/mcp.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mppx
4
+ module Mcp
5
+ PAYMENT_REQUIRED_CODE = -32_042
6
+ PAYMENT_VERIFICATION_FAILED_CODE = -32_043
7
+ CREDENTIAL_META_KEY = "org.paymentauth/credential"
8
+ RECEIPT_META_KEY = "org.paymentauth/receipt"
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mppx
4
+ module Method
5
+ module_function
6
+
7
+ def from(name:, intent:, schema:)
8
+ {
9
+ name: name,
10
+ intent: intent,
11
+ schema: schema
12
+ }.freeze
13
+ end
14
+
15
+ def to_client(method, create_credential:)
16
+ method.merge(create_credential: create_credential).freeze
17
+ end
18
+
19
+ def to_server(method, verify:, defaults: nil, request: nil, respond: nil)
20
+ result = method.merge(verify: verify)
21
+ result[:defaults] = defaults if defaults
22
+ result[:request] = request if request
23
+ result[:respond] = respond if respond
24
+ result.freeze
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Mppx
6
+ module PaymentRequest
7
+ module_function
8
+
9
+ def from(request)
10
+ request
11
+ end
12
+
13
+ def from_method(method, request)
14
+ schema = method[:schema][:request]
15
+ schema.call(request)
16
+ end
17
+
18
+ def serialize(request)
19
+ json = CanonicalJson.canonicalize(request)
20
+ Base64Url.encode(json)
21
+ end
22
+
23
+ def deserialize(encoded)
24
+ json = Base64Url.decode(encoded)
25
+ JSON.parse(json)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Mppx
6
+ module Receipt
7
+ module_function
8
+
9
+ def from(method:, reference:, status: "success", timestamp:, external_id: nil)
10
+ raise ArgumentError, "status must be 'success'" unless status == "success"
11
+
12
+ receipt = {
13
+ method: method,
14
+ reference: reference,
15
+ status: status,
16
+ timestamp: timestamp
17
+ }
18
+ receipt[:externalId] = external_id if external_id
19
+ receipt
20
+ end
21
+
22
+ def serialize(receipt)
23
+ json = JSON.generate(receipt)
24
+ Base64Url.encode(json)
25
+ end
26
+
27
+ def deserialize(encoded)
28
+ json = Base64Url.decode(encoded)
29
+ parsed = JSON.parse(json)
30
+ {
31
+ method: parsed["method"],
32
+ reference: parsed["reference"],
33
+ status: parsed["status"],
34
+ timestamp: parsed["timestamp"]
35
+ }.tap do |r|
36
+ r[:externalId] = parsed["externalId"] if parsed["externalId"]
37
+ end
38
+ end
39
+
40
+ def from_response(headers)
41
+ header = headers["Payment-Receipt"] || headers["payment-receipt"]
42
+ raise "Missing Payment-Receipt header." unless header
43
+
44
+ deserialize(header)
45
+ end
46
+ end
47
+ end