myrr-rb 0.3.3
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/CHANGELOG.md +77 -0
- data/LICENSE +21 -0
- data/README.md +70 -0
- data/lib/generators/myrr/install/install_generator.rb +20 -0
- data/lib/generators/myrr/install/templates/initializer.rb +12 -0
- data/lib/myrr/bridge.rb +97 -0
- data/lib/myrr/budget.rb +67 -0
- data/lib/myrr/card.rb +54 -0
- data/lib/myrr/client.rb +158 -0
- data/lib/myrr/configuration.rb +58 -0
- data/lib/myrr/pay/client.rb +154 -0
- data/lib/myrr/pay/middleware.rb +186 -0
- data/lib/myrr/pay.rb +13 -0
- data/lib/myrr/port/metadata.rb +79 -0
- data/lib/myrr/port/middleware.rb +113 -0
- data/lib/myrr/rack/middleware.rb +93 -0
- data/lib/myrr/railtie.rb +25 -0
- data/lib/myrr/tokenizer.rb +28 -0
- data/lib/myrr/transaction.rb +57 -0
- data/lib/myrr/version.rb +3 -0
- data/lib/myrr/wallet.rb +131 -0
- data/lib/myrr-rb.rb +3 -0
- data/lib/myrr.rb +76 -0
- metadata +150 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Myrr
|
|
4
|
+
# Read-only value object representing a wallet transaction.
|
|
5
|
+
class Transaction
|
|
6
|
+
attr_reader :id, :amount, :currency, :status, :source, :merchant_name,
|
|
7
|
+
:merchant_category, :decline_reason, :created_at
|
|
8
|
+
|
|
9
|
+
# @param attrs [Hash] Transaction attributes from the API
|
|
10
|
+
def initialize(attrs)
|
|
11
|
+
@id = attrs["id"]
|
|
12
|
+
@amount = attrs["amount"]
|
|
13
|
+
@currency = attrs["currency"] || "usd"
|
|
14
|
+
@status = attrs["status"]
|
|
15
|
+
@source = attrs["source"]
|
|
16
|
+
@merchant_name = attrs["merchant_name"]
|
|
17
|
+
@merchant_category = attrs["merchant_category"]
|
|
18
|
+
@decline_reason = attrs["decline_reason"]
|
|
19
|
+
@created_at = attrs["created_at"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [String] Human-readable amount
|
|
23
|
+
def amount_dollars
|
|
24
|
+
"$" + format("%.2f", @amount.to_f / 100)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Boolean] Whether the transaction was authorized
|
|
28
|
+
def authorized?
|
|
29
|
+
@status == "authorized"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Boolean] Whether the transaction was declined
|
|
33
|
+
def declined?
|
|
34
|
+
@status == "declined"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Boolean] Whether this is a funding transaction
|
|
38
|
+
def funding?
|
|
39
|
+
@status == "funding"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Boolean] Whether this is a Myrr Pay top-up
|
|
43
|
+
def myrr_pay_topup?
|
|
44
|
+
@source == "myrr_pay_topup"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Boolean] Whether this is a wallet funding
|
|
48
|
+
def wallet_funding?
|
|
49
|
+
@source == "wallet_funding"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Boolean] Whether this is a regular merchant purchase
|
|
53
|
+
def merchant_purchase?
|
|
54
|
+
@source.nil? || @source == "merchant_purchase"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/myrr/version.rb
ADDED
data/lib/myrr/wallet.rb
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "client"
|
|
5
|
+
|
|
6
|
+
module Myrr
|
|
7
|
+
# Wallet client for the Myrr protocol server.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# wallet = Myrr::Wallet.create(agent_did: "did:myrr:abc123")
|
|
11
|
+
# wallet = Myrr::Wallet.find("wallet-uuid")
|
|
12
|
+
# wallet = Myrr::Wallet.find_by_agent("did:myrr:abc123")
|
|
13
|
+
# wallet.fund(amount_cents: 50_00)
|
|
14
|
+
# wallet.cards # => Array of Myrr::Card
|
|
15
|
+
# wallet.transactions(limit: 10)
|
|
16
|
+
# wallet.topup(seller_did: "did:myrr:seller", seller_name: "DataPulse", amount_cents: 10_00, payment_intent_id: "pi_xxx")
|
|
17
|
+
class Wallet
|
|
18
|
+
attr_reader :id, :agent_did, :balance, :currency, :status, :stripe_customer_id,
|
|
19
|
+
:metadata, :created_at, :updated_at
|
|
20
|
+
|
|
21
|
+
# @param attrs [Hash] Wallet attributes from the API
|
|
22
|
+
def initialize(attrs)
|
|
23
|
+
@id = attrs["id"]
|
|
24
|
+
@agent_did = attrs["agent_did"]
|
|
25
|
+
@balance = attrs["balance"]
|
|
26
|
+
@currency = attrs["currency"] || "usd"
|
|
27
|
+
@status = attrs["status"]
|
|
28
|
+
@stripe_customer_id = attrs["stripe_customer_id"]
|
|
29
|
+
@metadata = attrs["metadata"]
|
|
30
|
+
@created_at = attrs["created_at"]
|
|
31
|
+
@updated_at = attrs["updated_at"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Create a new wallet for the given agent.
|
|
35
|
+
#
|
|
36
|
+
# @param agent_did [String] The did:myrr: identifier
|
|
37
|
+
# @param metadata [Hash] Optional metadata
|
|
38
|
+
# @param client [Myrr::Client, nil] Optional custom client
|
|
39
|
+
# @return [Myrr::Wallet]
|
|
40
|
+
def self.create(agent_did:, metadata: {}, client: nil)
|
|
41
|
+
client ||= Myrr.client
|
|
42
|
+
body = { agent_did: agent_did }
|
|
43
|
+
body[:metadata] = metadata if metadata.any?
|
|
44
|
+
resp = client.send(:post, "/v1/wallets", body)
|
|
45
|
+
new(resp)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Find a wallet by its UUID.
|
|
49
|
+
#
|
|
50
|
+
# @param id [String] Wallet UUID
|
|
51
|
+
# @param client [Myrr::Client, nil] Optional custom client
|
|
52
|
+
# @return [Myrr::Wallet, nil]
|
|
53
|
+
def self.find(id, client: nil)
|
|
54
|
+
client ||= Myrr.client
|
|
55
|
+
resp = client.send(:get, "/v1/wallets/#{id}")
|
|
56
|
+
new(resp)
|
|
57
|
+
rescue Myrr::Error => e
|
|
58
|
+
raise unless e.message.include?("not_found")
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Find a wallet by its agent did:myrr: identifier.
|
|
63
|
+
#
|
|
64
|
+
# @param agent_did [String] The did:myrr: identifier
|
|
65
|
+
# @param client [Myrr::Client, nil] Optional custom client
|
|
66
|
+
# @return [Myrr::Wallet, nil]
|
|
67
|
+
def self.find_by_agent(agent_did, client: nil)
|
|
68
|
+
client ||= Myrr.client
|
|
69
|
+
resp = client.send(:get, "/v1/wallets?agent_did=#{agent_did}")
|
|
70
|
+
new(resp)
|
|
71
|
+
rescue Myrr::Error => e
|
|
72
|
+
raise unless e.message.include?("not_found")
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Pre-approve a Myrr Pay top-up before confirming the PaymentIntent.
|
|
77
|
+
#
|
|
78
|
+
# @param seller_did [String] Seller's did:myrr: identifier
|
|
79
|
+
# @param seller_name [String] Seller's display name
|
|
80
|
+
# @param amount_cents [Integer] Top-up amount in cents
|
|
81
|
+
# @param payment_intent_id [String] Stripe PaymentIntent ID
|
|
82
|
+
# @param metadata [Hash] Optional metadata
|
|
83
|
+
# @return [Hash] { "status" => "approved"/"declined", "reason" => String? }
|
|
84
|
+
def topup(seller_did:, seller_name:, amount_cents:, payment_intent_id: nil, metadata: {})
|
|
85
|
+
body = {
|
|
86
|
+
seller_did: seller_did,
|
|
87
|
+
seller_name: seller_name,
|
|
88
|
+
amount_cents: amount_cents,
|
|
89
|
+
payment_intent_id: payment_intent_id
|
|
90
|
+
}
|
|
91
|
+
body[:metadata] = metadata if metadata.any?
|
|
92
|
+
self.class.client.send(:post, "/v1/wallets/#{id}/topups", body)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Create a Stripe Checkout session to fund this wallet.
|
|
96
|
+
#
|
|
97
|
+
# @param amount_cents [Integer] Amount in cents (minimum 50)
|
|
98
|
+
# @return [String] Checkout URL
|
|
99
|
+
def fund(amount_cents:)
|
|
100
|
+
resp = self.class.client.send(:post, "/v1/wallets/#{id}/fund", { amount_cents: amount_cents })
|
|
101
|
+
resp["checkout_url"]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# List virtual cards for this wallet.
|
|
105
|
+
#
|
|
106
|
+
# @return [Array<Myrr::Card>]
|
|
107
|
+
def cards
|
|
108
|
+
resp = self.class.client.send(:get, "/v1/wallets/#{id}/cards")
|
|
109
|
+
(resp["cards"] || []).map { |c| Myrr::Card.new(c) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# List transactions for this wallet.
|
|
113
|
+
#
|
|
114
|
+
# @param limit [Integer] Max results (default 20, max 100)
|
|
115
|
+
# @param before [String, nil] Cursor for pagination
|
|
116
|
+
# @return [Array<Myrr::Transaction>]
|
|
117
|
+
def transactions(limit: 20, before: nil)
|
|
118
|
+
query = "limit=#{limit}"
|
|
119
|
+
query += "&before=#{before}" if before
|
|
120
|
+
resp = self.class.client.send(:get, "/v1/wallets/#{id}/transactions?#{query}")
|
|
121
|
+
txns = (resp["transactions"] || []).map { |t| Myrr::Transaction.new(t) }
|
|
122
|
+
[txns, resp["has_more"], resp["next_cursor"]]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def self.client
|
|
128
|
+
Myrr.client
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/myrr-rb.rb
ADDED
data/lib/myrr.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require_relative "myrr/version"
|
|
2
|
+
require_relative "myrr/configuration"
|
|
3
|
+
require_relative "myrr/tokenizer"
|
|
4
|
+
require_relative "myrr/port/middleware"
|
|
5
|
+
require_relative "myrr/railtie" if defined?(Rails::Railtie)
|
|
6
|
+
|
|
7
|
+
# Myrr — Agent-friendly content for your Rails app.
|
|
8
|
+
#
|
|
9
|
+
# In its default (Port) mode, myrr-rb counts tokens on every text/markdown
|
|
10
|
+
# response and adds an +X-Token-Count+ header. No Go server, no database,
|
|
11
|
+
# no configuration required.
|
|
12
|
+
#
|
|
13
|
+
# @example Zero-config setup
|
|
14
|
+
# # Just add the gem, create .md.erb views, and you're done.
|
|
15
|
+
module Myrr
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# The current configuration object.
|
|
20
|
+
# @return [Myrr::Configuration]
|
|
21
|
+
attr_reader :config
|
|
22
|
+
|
|
23
|
+
# Configure Myrr.
|
|
24
|
+
#
|
|
25
|
+
# @yield [config] Yields the configuration object for modification.
|
|
26
|
+
# @yieldparam config [Myrr::Configuration]
|
|
27
|
+
def configure
|
|
28
|
+
@config ||= Configuration.new
|
|
29
|
+
yield(@config) if block_given?
|
|
30
|
+
@config
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Reset configuration to defaults.
|
|
34
|
+
def reset_config!
|
|
35
|
+
@config = Configuration.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Create a new protocol server client.
|
|
39
|
+
# Requires +protocol_server_url+ to be configured.
|
|
40
|
+
# (Identity-layer feature — opt-in, requires explicit require.)
|
|
41
|
+
#
|
|
42
|
+
# @return [Myrr::Client]
|
|
43
|
+
def client
|
|
44
|
+
Client.new(
|
|
45
|
+
server_url: config.protocol_server_url,
|
|
46
|
+
api_key: config.site_api_key,
|
|
47
|
+
open_timeout: config.open_timeout,
|
|
48
|
+
read_timeout: config.read_timeout
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Verify an agent identity. Returns agent info or nil.
|
|
53
|
+
# (Identity-layer feature — opt-in, requires explicit require.)
|
|
54
|
+
#
|
|
55
|
+
# Performs challenge-response verification via the configured protocol
|
|
56
|
+
# server (or identity_adapter). If +fail_open+ is true, returns nil
|
|
57
|
+
# on network errors instead of raising.
|
|
58
|
+
#
|
|
59
|
+
# @param did_key [String] The did:myrr: identifier
|
|
60
|
+
# @return [Hash, nil] Agent info with +did_key+, +status+, +owner+, or nil
|
|
61
|
+
def verify_agent(did_key)
|
|
62
|
+
if config.identity_adapter
|
|
63
|
+
config.identity_adapter.call(did_key)
|
|
64
|
+
else
|
|
65
|
+
client.verify(did_key)
|
|
66
|
+
end
|
|
67
|
+
rescue => e
|
|
68
|
+
if config.fail_open
|
|
69
|
+
warn "[myrr] Identity verification failed (fail_open=true): #{e.message}"
|
|
70
|
+
nil
|
|
71
|
+
else
|
|
72
|
+
raise
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: myrr-rb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaka Ruto
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: tiktoken_ruby
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.0.16
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.0.16
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.20'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '5.20'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rack-test
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.1'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.1'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: webmock
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.19'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.19'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rake
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '13.0'
|
|
96
|
+
description: Count tokens on text/markdown responses, add an X-Token-Count header,
|
|
97
|
+
and serve .md.erb views alongside .html.erb. Zero config out of the box.
|
|
98
|
+
email:
|
|
99
|
+
- kr@kakaruto.com
|
|
100
|
+
executables: []
|
|
101
|
+
extensions: []
|
|
102
|
+
extra_rdoc_files: []
|
|
103
|
+
files:
|
|
104
|
+
- CHANGELOG.md
|
|
105
|
+
- LICENSE
|
|
106
|
+
- README.md
|
|
107
|
+
- lib/generators/myrr/install/install_generator.rb
|
|
108
|
+
- lib/generators/myrr/install/templates/initializer.rb
|
|
109
|
+
- lib/myrr-rb.rb
|
|
110
|
+
- lib/myrr.rb
|
|
111
|
+
- lib/myrr/bridge.rb
|
|
112
|
+
- lib/myrr/budget.rb
|
|
113
|
+
- lib/myrr/card.rb
|
|
114
|
+
- lib/myrr/client.rb
|
|
115
|
+
- lib/myrr/configuration.rb
|
|
116
|
+
- lib/myrr/pay.rb
|
|
117
|
+
- lib/myrr/pay/client.rb
|
|
118
|
+
- lib/myrr/pay/middleware.rb
|
|
119
|
+
- lib/myrr/port/metadata.rb
|
|
120
|
+
- lib/myrr/port/middleware.rb
|
|
121
|
+
- lib/myrr/rack/middleware.rb
|
|
122
|
+
- lib/myrr/railtie.rb
|
|
123
|
+
- lib/myrr/tokenizer.rb
|
|
124
|
+
- lib/myrr/transaction.rb
|
|
125
|
+
- lib/myrr/version.rb
|
|
126
|
+
- lib/myrr/wallet.rb
|
|
127
|
+
homepage: https://myrrlabs.com
|
|
128
|
+
licenses:
|
|
129
|
+
- MIT
|
|
130
|
+
metadata:
|
|
131
|
+
homepage_uri: https://myrrlabs.com
|
|
132
|
+
source_code_uri: https://github.com/myrrlabs/myrr-rb
|
|
133
|
+
rdoc_options: []
|
|
134
|
+
require_paths:
|
|
135
|
+
- lib
|
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
|
+
requirements:
|
|
138
|
+
- - ">="
|
|
139
|
+
- !ruby/object:Gem::Version
|
|
140
|
+
version: '3.0'
|
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
requirements: []
|
|
147
|
+
rubygems_version: 4.0.3
|
|
148
|
+
specification_version: 4
|
|
149
|
+
summary: Agent-friendly content for your Rails app
|
|
150
|
+
test_files: []
|