Module: Mpp::Server::AcceptPayment

Extended by:
T::Sig
Defined in:
lib/mpp/server/accept_payment.rb

Overview

Parse, format, and rank the Accept-Payment client-preference header.

Syntax mirrors HTTP content negotiation: method/intent[;q=value], comma-separated. Wildcards (* / tempo/* / */charge) are allowed. Entries with q=0 are excluded. If the header is empty, invalid, or filters out every offer, the original offer list is returned.

Constant Summary collapse

TOKEN_RE =
/\A(?:\*|[a-z0-9-]+)\z/
ENTRY_RE =
%r{\A(?<method>[^/;\s]+|\*)\s*/\s*(?<intent>[^/;\s]+|\*)(?<params>(?:\s*;\s*.+)?)\z}
PARAM_RE =
/\A(?<name>[A-Za-z0-9_-]+)\s*=\s*(?<value>\S+)\z/
QVALUE_RE =
/\A(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)\z/
Entry =
T.type_alias { T::Hash[Symbol, T.untyped] }

Class Method Summary collapse

Class Method Details

.apply(offers, header) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/mpp/server/accept_payment.rb', line 36

def apply(offers, header)
  return offers if header.nil? || header.strip.empty?

  begin
    preferences = parse(header)
  rescue ArgumentError
    return offers
  end

  ranked = rank(offers, preferences)
  ranked.empty? ? offers : ranked
end

.key_of(value) ⇒ Object



66
67
68
69
70
71
# File 'lib/mpp/server/accept_payment.rb', line 66

def key_of(value)
  method, intent = method_intent(value)
  Kernel.raise ArgumentError, "Missing payment method name." if method.empty?

  "#{method}/#{intent}"
end

.parse(header) ⇒ Object



26
27
28
29
30
31
# File 'lib/mpp/server/accept_payment.rb', line 26

def parse(header)
  parts = header.split(",").map(&:strip).reject(&:empty?)
  Kernel.raise ArgumentError, "Accept-Payment header is empty." if parts.empty?

  parts.each_with_index.map { |part, index| parse_entry(part, index) }
end

.rank(offers, preferences) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/mpp/server/accept_payment.rb', line 52

def rank(offers, preferences)
  scored = []
  offers.each_with_index do |offer, index|
    match = best_match(offer, preferences)
    next unless match && T.unsafe(match[:q]) > 0

    scored << {match: match, offer: offer, index: index}
  end

  scored.sort_by! { |row| [-T.unsafe(row[:match][:q]), T.unsafe(row[:index])] }
  scored.map { |row| row[:offer] }
end