mpp-rb 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfa6aaca1aa33a23ec690b0c9cae8e04fe54ed71891fb82fd59703af067ec41e
4
- data.tar.gz: 05fc8e20c0213027578d160f2b80fd953a4ea0e2d2ddd89bb6e1a78c971bfb11
3
+ metadata.gz: c71efa5e58021d08e4ed10c0156ed9b140923b0f3ebeb5ce6b42839594c27dd6
4
+ data.tar.gz: d40e3c4994086eea6f2c11023f47fda635637ae6815dc263d63c12ca10235ae8
5
5
  SHA512:
6
- metadata.gz: d1649fad2a0269ba907e5dd59bbcbc3072b26d40f2951cbda262eede6156b97fd75547b8675cbb7d0c89a426a41983adb50276805b9acc048d542b7615f941d9
7
- data.tar.gz: ddbe4e28550e8d428843907be443c0f6e67864e9d598f6cb217eb47b4af8501559284b3f2806a583cbd7363e7f858da79995aafa469c0067b5f183427ede7b1f
6
+ metadata.gz: af6ba306f236fb01dd140f63ce51c764fdee655a7f1282d7adad87d8586e4838d43f096ca79a4053dbab703afdc6fa1806d3e20ea6b6afa75c7c70bf6d3885f3
7
+ data.tar.gz: db9bc204d61dcdb4c3e9d0b8f9034f935dd38d4291b71b37e61a29889240b27667400e5c48333d2cf39e2d6513b5af70368a354a8a20fd5a543558b07b68f700
data/lib/mpp/challenge.rb CHANGED
@@ -58,19 +58,29 @@ module Mpp
58
58
  Mpp::Parsing.parse_www_authenticate(header)
59
59
  end
60
60
 
61
- # Parse multiple Payment challenges from a merged WWW-Authenticate header.
62
- # Handles RFC 9110 §11.6.1 comma-separated authentication schemes.
63
- def self.from_www_authenticate_list(header)
61
+ # Split a merged WWW-Authenticate header value into the individual
62
+ # `Payment ...` challenge chunks it contains, handling RFC 9110 §11.6.1
63
+ # comma-separated authentication schemes. Returns the raw chunk strings so
64
+ # callers can parse each challenge independently; a malformed chunk then
65
+ # does not prevent the remaining chunks from being considered.
66
+ def self.www_authenticate_chunks(header)
64
67
  payment_scheme_indices(header).map do |start_idx|
65
68
  # End each chunk at the next scheme boundary of any kind, so an
66
69
  # interleaved non-Payment scheme (e.g. "Payment ..., Bearer ...,
67
70
  # Payment ...") is not folded into the preceding Payment challenge.
68
71
  end_idx = next_auth_scheme_index(header, start_idx + "Payment".length) || header.length
69
- chunk = T.must(header[start_idx...end_idx]).sub(/,\s*$/, "")
70
- from_www_authenticate(chunk)
72
+ T.must(header[start_idx...end_idx]).sub(/,\s*$/, "")
71
73
  end
72
74
  end
73
75
 
76
+ # Parse every Payment challenge from a merged WWW-Authenticate header.
77
+ # Raises Mpp::ParseError on the first malformed chunk; callers that need to
78
+ # tolerate a malformed challenge among valid ones should iterate
79
+ # `www_authenticate_chunks` and parse each chunk independently instead.
80
+ def self.from_www_authenticate_list(header)
81
+ www_authenticate_chunks(header).map { |chunk| from_www_authenticate(chunk) }
82
+ end
83
+
74
84
  def self.payment_scheme_indices(header)
75
85
  indices = []
76
86
  each_auth_scheme_index(header) do |index, scheme|
@@ -205,12 +205,13 @@ module Mpp
205
205
  sig { params(www_auth_headers: T.untyped, input: T.untyped, response: T.untyped).returns(T::Array[T.untyped]) }
206
206
  def find_matching_challenge(www_auth_headers, input: nil, response: nil)
207
207
  www_auth_headers.each do |header|
208
- next unless header.downcase.start_with?("payment ")
209
-
210
- begin
211
- parsed = Mpp::Challenge.from_www_authenticate(header)
208
+ Mpp::Challenge.www_authenticate_chunks(header).each do |chunk|
209
+ parsed = Mpp::Challenge.from_www_authenticate(chunk)
212
210
  return [parsed, @methods[parsed.method]] if @methods.key?(parsed.method)
213
211
  rescue Mpp::ParseError => e
212
+ # Skip a malformed challenge but keep scanning the rest: a bad chunk
213
+ # earlier in a merged value must not hide a supported challenge that
214
+ # follows it.
214
215
  if @events.has_handlers?(Mpp::Events::PAYMENT_FAILED)
215
216
  @events.emit(Mpp::Events::PAYMENT_FAILED, {
216
217
  error: e,
@@ -5,34 +5,37 @@ require "stringio"
5
5
 
6
6
  module Mpp
7
7
  module Server
8
- # Rack middleware that intercepts requests requiring payment.
8
+ # Rack middleware that gates endpoints behind payment verification.
9
9
  #
10
- # The downstream app signals payment is needed by setting env["mpp.charge"]
11
- # to a hash with at least :amount, and optionally :currency, :recipient,
12
- # :description, :expires, etc.
10
+ # The pricing proc determines which requests require payment and at what
11
+ # price. It receives the Rack env and must return a charge options hash
12
+ # (with at least :amount) or nil for free endpoints. The pricing proc
13
+ # MUST NOT produce side effects.
13
14
  #
14
- # Example:
15
- # use Mpp::Server::Middleware, handler: my_handler
15
+ # Payment is verified BEFORE the downstream app runs — if verification
16
+ # fails, the app never executes and a 402 challenge is returned.
16
17
  #
17
- # # In your app:
18
- # env["mpp.charge"] = { amount: "1.00" }
18
+ # Example:
19
+ # use Mpp::Server::Middleware,
20
+ # handler: my_handler,
21
+ # pricing: ->(env) { {amount: "1.00"} if env["PATH_INFO"] == "/paid" }
19
22
  class Middleware
20
23
  extend T::Sig
21
24
 
22
- sig { params(app: T.untyped, handler: Mpp::Server::MppHandler).void }
23
- def initialize(app, handler:)
25
+ sig { params(app: T.untyped, handler: Mpp::Server::MppHandler, pricing: T.untyped).void }
26
+ def initialize(app, handler:, pricing:)
24
27
  @app = T.let(app, T.untyped)
25
28
  @handler = T.let(handler, Mpp::Server::MppHandler)
29
+ @pricing = T.let(pricing, T.untyped)
26
30
  end
27
31
 
28
32
  sig { params(env: T.untyped).returns(T::Array[T.untyped]) }
29
33
  def call(env)
34
+ charge_opts = @pricing.call(env)
35
+ return @app.call(env) unless charge_opts
36
+
30
37
  authorization = env["HTTP_AUTHORIZATION"]
31
38
  body_capture = capture_request_body(env)
32
- status, headers, body = @app.call(env)
33
-
34
- charge_opts = env["mpp.charge"]
35
- return [status, headers, body] unless charge_opts
36
39
 
37
40
  request_body = body_capture&.materialize
38
41
  env["rack.input"] = StringIO.new(request_body || "") if body_capture
@@ -49,6 +52,7 @@ module Mpp
49
52
  end
50
53
 
51
54
  _credential, receipt = result
55
+ status, headers, body = @app.call(env)
52
56
  headers["Payment-Receipt"] = receipt.to_payment_receipt
53
57
  self.class.mark_authorization_bound_response(headers)
54
58
 
data/lib/mpp/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Mpp
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.4"
6
6
  end
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.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe