paytree 0.2.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/.rspec +3 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +32 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +163 -0
- data/LICENSE.txt +21 -0
- data/README.md +341 -0
- data/Rakefile +12 -0
- data/lib/paytree/concerns/feature_set.rb +15 -0
- data/lib/paytree/configs/mpesa.rb +35 -0
- data/lib/paytree/configuration_registry.rb +39 -0
- data/lib/paytree/errors.rb +17 -0
- data/lib/paytree/mpesa/adapters/daraja/b2b.rb +39 -0
- data/lib/paytree/mpesa/adapters/daraja/b2c.rb +36 -0
- data/lib/paytree/mpesa/adapters/daraja/base.rb +134 -0
- data/lib/paytree/mpesa/adapters/daraja/c2b.rb +47 -0
- data/lib/paytree/mpesa/adapters/daraja/response_helpers.rb +37 -0
- data/lib/paytree/mpesa/adapters/daraja/stk_push.rb +40 -0
- data/lib/paytree/mpesa/adapters/daraja/stk_query.rb +33 -0
- data/lib/paytree/mpesa/adapters/daraja.rb +17 -0
- data/lib/paytree/mpesa/adapters.rb +7 -0
- data/lib/paytree/mpesa/b2b.rb +15 -0
- data/lib/paytree/mpesa/b2c.rb +15 -0
- data/lib/paytree/mpesa/c2b.rb +19 -0
- data/lib/paytree/mpesa/stk_push.rb +15 -0
- data/lib/paytree/mpesa/stk_query.rb +15 -0
- data/lib/paytree/mpesa.rb +9 -0
- data/lib/paytree/response.rb +44 -0
- data/lib/paytree/utils/error_handling.rb +115 -0
- data/lib/paytree/version.rb +3 -0
- data/lib/paytree.rb +82 -0
- data/paytree.gemspec +40 -0
- data/sig/paytree.rbs +4 -0
- metadata +156 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
require "paytree/mpesa/adapters/daraja/base"
|
2
|
+
|
3
|
+
module Paytree
|
4
|
+
module Mpesa
|
5
|
+
module Adapters
|
6
|
+
module Daraja
|
7
|
+
class C2B < Base
|
8
|
+
REGISTER_ENDPOINT = "/mpesa/c2b/v1/registerurl"
|
9
|
+
SIMULATE_ENDPOINT = "/mpesa/c2b/v1/simulate"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def register_urls(short_code:, confirmation_url:, validation_url:)
|
13
|
+
with_error_handling(context: :c2b_register) do
|
14
|
+
validate_for(:c2b_register, short_code:, confirmation_url:, validation_url:)
|
15
|
+
|
16
|
+
payload = {
|
17
|
+
ShortCode: short_code,
|
18
|
+
ResponseType: "Completed",
|
19
|
+
ConfirmationURL: confirmation_url,
|
20
|
+
ValidationURL: validation_url
|
21
|
+
}
|
22
|
+
|
23
|
+
post_to_mpesa(:c2b_register, REGISTER_ENDPOINT, payload)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def simulate(phone_number:, amount:, reference:)
|
28
|
+
with_error_handling(context: :c2b_simulate) do
|
29
|
+
validate_for(:c2b_simulate, phone_number:, amount:, reference:)
|
30
|
+
|
31
|
+
payload = {
|
32
|
+
ShortCode: config.shortcode,
|
33
|
+
CommandID: "CustomerPayBillOnline",
|
34
|
+
Amount: amount,
|
35
|
+
Msisdn: phone_number,
|
36
|
+
BillRefNumber: reference
|
37
|
+
}
|
38
|
+
|
39
|
+
post_to_mpesa(:c2b_simulate, SIMULATE_ENDPOINT, payload)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Mpesa
|
3
|
+
module Adapters
|
4
|
+
module Daraja
|
5
|
+
module ResponseHelpers
|
6
|
+
def build_response(response, operation)
|
7
|
+
parsed = response.body
|
8
|
+
|
9
|
+
Paytree::Response.new(
|
10
|
+
provider: :mpesa,
|
11
|
+
operation:,
|
12
|
+
status: response.success? ? :success : :error,
|
13
|
+
message: response_message(parsed),
|
14
|
+
code: response_code(parsed),
|
15
|
+
data: parsed,
|
16
|
+
raw_response: response
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def response_message(parsed)
|
23
|
+
parsed["ResponseDescription"] ||
|
24
|
+
parsed["ResultDesc"] ||
|
25
|
+
parsed["errorMessage"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def response_code(parsed)
|
29
|
+
parsed["ResponseCode"] ||
|
30
|
+
parsed["ResultCode"] ||
|
31
|
+
parsed["errorCode"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "paytree/mpesa/adapters/daraja/base"
|
2
|
+
|
3
|
+
module Paytree
|
4
|
+
module Mpesa
|
5
|
+
module Adapters
|
6
|
+
module Daraja
|
7
|
+
class StkPush < Base
|
8
|
+
ENDPOINT = "/mpesa/stkpush/v1/processrequest"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def call(phone_number:, amount:, reference:)
|
12
|
+
with_error_handling(context: :stk_push) do
|
13
|
+
validate_for(:stk_push, phone_number:, amount:, reference:)
|
14
|
+
|
15
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
16
|
+
password = Base64.strict_encode64("#{config.shortcode}#{config.passkey}#{timestamp}")
|
17
|
+
|
18
|
+
payload = {
|
19
|
+
BusinessShortCode: config.shortcode,
|
20
|
+
Password: password,
|
21
|
+
Timestamp: timestamp,
|
22
|
+
TransactionType: "CustomerPayBillOnline",
|
23
|
+
Amount: amount,
|
24
|
+
PartyA: phone_number,
|
25
|
+
PartyB: config.shortcode,
|
26
|
+
PhoneNumber: phone_number,
|
27
|
+
CallBackURL: config.extras[:callback_url],
|
28
|
+
AccountReference: reference,
|
29
|
+
TransactionDesc: reference
|
30
|
+
}
|
31
|
+
|
32
|
+
post_to_mpesa(:stk_push, ENDPOINT, payload)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "paytree/mpesa/adapters/daraja/base"
|
2
|
+
|
3
|
+
module Paytree
|
4
|
+
module Mpesa
|
5
|
+
module Adapters
|
6
|
+
module Daraja
|
7
|
+
class StkQuery < Base
|
8
|
+
ENDPOINT = "/mpesa/stkpushquery/v1/query"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def call(checkout_request_id:)
|
12
|
+
with_error_handling(context: :stk_query) do
|
13
|
+
config = self.config
|
14
|
+
|
15
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
16
|
+
password = Base64.strict_encode64("#{config.shortcode}#{config.passkey}#{timestamp}")
|
17
|
+
|
18
|
+
payload = {
|
19
|
+
BusinessShortCode: config.shortcode,
|
20
|
+
Password: password,
|
21
|
+
Timestamp: timestamp,
|
22
|
+
CheckoutRequestID: checkout_request_id
|
23
|
+
}
|
24
|
+
|
25
|
+
post_to_mpesa(:stk_query, ENDPOINT, payload)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Mpesa
|
3
|
+
module Adapters
|
4
|
+
module Daraja
|
5
|
+
extend Paytree::FeatureSet
|
6
|
+
|
7
|
+
supports :stk_push, :stk_query, :b2c, :c2b, :b2b
|
8
|
+
|
9
|
+
autoload :StkPush, "paytree/mpesa/adapters/daraja/stk_push"
|
10
|
+
autoload :StkQuery, "paytree/mpesa/adapters/daraja/stk_query"
|
11
|
+
autoload :B2C, "paytree/mpesa/adapters/daraja/b2c"
|
12
|
+
autoload :C2B, "paytree/mpesa/adapters/daraja/c2b"
|
13
|
+
autoload :B2B, "paytree/mpesa/adapters/daraja/b2b"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Mpesa
|
3
|
+
class B2B
|
4
|
+
def self.call(**args)
|
5
|
+
adapter = Paytree::Mpesa.config.adapter
|
6
|
+
|
7
|
+
unless adapter.respond_to?(:supports?) && adapter.supports?(:b2b)
|
8
|
+
raise NotImplementedError, "B2B not supported by #{adapter}"
|
9
|
+
end
|
10
|
+
|
11
|
+
adapter::B2B.call(**args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Mpesa
|
3
|
+
class B2C
|
4
|
+
def self.call(**args)
|
5
|
+
adapter = Paytree::Mpesa.config.adapter
|
6
|
+
|
7
|
+
unless adapter.respond_to?(:supports?) && adapter.supports?(:b2c)
|
8
|
+
raise NotImplementedError, "B2C not supported by #{adapter}"
|
9
|
+
end
|
10
|
+
|
11
|
+
adapter::B2C.call(**args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Mpesa
|
3
|
+
class C2B
|
4
|
+
def self.register_urls(**args)
|
5
|
+
adapter = Paytree::Mpesa.config.adapter
|
6
|
+
raise NotImplementedError unless adapter.supports?(:c2b)
|
7
|
+
|
8
|
+
adapter::C2B.register_urls(**args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.simulate(**args)
|
12
|
+
adapter = Paytree::Mpesa.config.adapter
|
13
|
+
raise NotImplementedError unless adapter.supports?(:c2b)
|
14
|
+
|
15
|
+
adapter::C2B.simulate(**args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Mpesa
|
3
|
+
class StkPush
|
4
|
+
def self.call(**args)
|
5
|
+
adapter = Paytree::Mpesa.config.adapter || Adapters::Daraja
|
6
|
+
|
7
|
+
unless adapter.respond_to?(:supports?) && adapter.supports?(:stk_push)
|
8
|
+
raise NotImplementedError, "STK Push not supported by #{adapter}"
|
9
|
+
end
|
10
|
+
|
11
|
+
adapter::StkPush.call(**args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Mpesa
|
3
|
+
class StkQuery
|
4
|
+
def self.call(**args)
|
5
|
+
adapter = Paytree::Mpesa.config.adapter
|
6
|
+
|
7
|
+
unless adapter.respond_to?(:supports?) && adapter.supports?(:stk_query)
|
8
|
+
raise NotImplementedError, "STK Query not supported by #{adapter}"
|
9
|
+
end
|
10
|
+
|
11
|
+
adapter::StkQuery.call(**args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Paytree
|
2
|
+
class Response
|
3
|
+
attr_reader :provider, :operation, :status, :message, :code, :data, :raw_response
|
4
|
+
|
5
|
+
def initialize(provider:, operation:, status:, message:, code:, data:, raw_response:)
|
6
|
+
@provider = provider
|
7
|
+
@operation = operation
|
8
|
+
@status = status
|
9
|
+
@message = message
|
10
|
+
@code = code
|
11
|
+
@data = data
|
12
|
+
@raw_response = raw_response
|
13
|
+
end
|
14
|
+
|
15
|
+
def success?
|
16
|
+
status == :success
|
17
|
+
end
|
18
|
+
|
19
|
+
def error?
|
20
|
+
status == :error
|
21
|
+
end
|
22
|
+
|
23
|
+
def retryable?
|
24
|
+
return false unless error?
|
25
|
+
return false unless code
|
26
|
+
|
27
|
+
config = Paytree[provider]
|
28
|
+
return false unless config&.respond_to?(:retryable_errors)
|
29
|
+
|
30
|
+
config.retryable_errors.include?(code)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_h
|
34
|
+
{
|
35
|
+
provider:,
|
36
|
+
operation:,
|
37
|
+
status:,
|
38
|
+
message:,
|
39
|
+
code:,
|
40
|
+
data:
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Paytree
|
2
|
+
module Utils
|
3
|
+
module ErrorHandling
|
4
|
+
def with_error_handling(context: nil)
|
5
|
+
yield
|
6
|
+
rescue Paytree::Errors::ValidationError,
|
7
|
+
Paytree::Errors::Base => e
|
8
|
+
emit_error(e, context)
|
9
|
+
raise
|
10
|
+
rescue Faraday::TimeoutError => e
|
11
|
+
handle_faraday_error(
|
12
|
+
e,
|
13
|
+
context,
|
14
|
+
error_class: Paytree::Errors::MpesaResponseError,
|
15
|
+
error_type: "Timeout"
|
16
|
+
)
|
17
|
+
rescue Faraday::ParsingError, JSON::ParserError => e
|
18
|
+
handle_faraday_error(
|
19
|
+
e,
|
20
|
+
context,
|
21
|
+
error_class: Paytree::Errors::MpesaMalformedResponse,
|
22
|
+
error_type: "Malformed response"
|
23
|
+
)
|
24
|
+
rescue Faraday::ClientError => e
|
25
|
+
handle_faraday_error(
|
26
|
+
e,
|
27
|
+
context,
|
28
|
+
error_class: Paytree::Errors::MpesaClientError,
|
29
|
+
error_type: "Client error",
|
30
|
+
extract_info: true
|
31
|
+
)
|
32
|
+
rescue Faraday::ServerError => e
|
33
|
+
handle_faraday_error(
|
34
|
+
e,
|
35
|
+
context,
|
36
|
+
error_class: Paytree::Errors::MpesaServerError,
|
37
|
+
error_type: "Server error",
|
38
|
+
extract_info: true
|
39
|
+
)
|
40
|
+
rescue => e
|
41
|
+
wrap_and_raise(
|
42
|
+
Paytree::Errors::Base,
|
43
|
+
"Unexpected error in #{context}: #{e.message}",
|
44
|
+
e, context
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def handle_faraday_error(error, context, error_class:, error_type:, extract_info: false)
|
51
|
+
if extract_info
|
52
|
+
info = parse_faraday_error(error)
|
53
|
+
message = info[:message] || error.message
|
54
|
+
code = info[:code]
|
55
|
+
else
|
56
|
+
message = error.message
|
57
|
+
code = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
wrap_and_raise(
|
61
|
+
error_class, "#{error_type} in #{context}: #{message}", error, context, code
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def wrap_and_raise(klass, message, original, context, code = nil)
|
66
|
+
error = klass.new(message)
|
67
|
+
error.define_singleton_method(:code) { code } if code
|
68
|
+
emit_error(error, context)
|
69
|
+
|
70
|
+
raise error
|
71
|
+
end
|
72
|
+
|
73
|
+
def emit_error(error, context, **metadata)
|
74
|
+
config = get_config_for_context(context)
|
75
|
+
logger = config.respond_to?(:logger) ? config.logger : Logger.new($stdout)
|
76
|
+
|
77
|
+
logger.error format_error_message(error, context)
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_config_for_context(context)
|
81
|
+
provider = extract_provider_from_context(context) || :mpesa
|
82
|
+
Paytree[provider]
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_provider_from_context(context)
|
86
|
+
return :mpesa if context.to_s.downcase.include?("mpesa")
|
87
|
+
# Can be extended to support other providers based on context
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_faraday_error(faraday_error)
|
92
|
+
body = faraday_error.response&.dig(:body)
|
93
|
+
return {} unless body.is_a?(Hash)
|
94
|
+
|
95
|
+
{
|
96
|
+
message: body["errorMessage"] ||
|
97
|
+
body["ResponseDescription"] ||
|
98
|
+
body["ResultDesc"],
|
99
|
+
code: body["errorCode"] ||
|
100
|
+
body["ResponseCode"] ||
|
101
|
+
body["ResultCode"]
|
102
|
+
}
|
103
|
+
rescue NoMethodError, KeyError, TypeError
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
|
107
|
+
def format_error_message(error, context)
|
108
|
+
provider = extract_provider_from_context(context) || :mpesa
|
109
|
+
code = (error.respond_to?(:code) && error.code) ? " (code: #{error.code})" : ""
|
110
|
+
|
111
|
+
"[#{provider.to_s.upcase}/#{context}] #{error.class}: #{error.message}#{code}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/paytree.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require "json"
|
2
|
+
require "faraday"
|
3
|
+
|
4
|
+
Dir[File.join(__dir__, "paytree/**/*.rb")].sort.each { |file| require file }
|
5
|
+
|
6
|
+
module Paytree
|
7
|
+
class << self
|
8
|
+
def registry
|
9
|
+
@registry ||= ConfigurationRegistry.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure(provider, config_class = nil)
|
13
|
+
raise ArgumentError, "Missing block" unless block_given?
|
14
|
+
raise ArgumentError, "Missing config_class" unless config_class
|
15
|
+
|
16
|
+
registry.configure(provider, config_class) { |hash| yield hash }
|
17
|
+
end
|
18
|
+
|
19
|
+
def configure_mpesa(**options)
|
20
|
+
config = Configs::Mpesa.new
|
21
|
+
|
22
|
+
# Auto-load from environment variables if not provided
|
23
|
+
options = auto_load_env_vars.merge(options)
|
24
|
+
|
25
|
+
# Set configuration values
|
26
|
+
options.each do |key, value|
|
27
|
+
if config.respond_to?("#{key}=")
|
28
|
+
config.send("#{key}=", value)
|
29
|
+
elsif key == :extras
|
30
|
+
config.extras.merge!(value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set smart defaults
|
35
|
+
config.sandbox = true if config.sandbox.nil?
|
36
|
+
|
37
|
+
registry.store_config(:mpesa, config)
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](provider)
|
41
|
+
registry[provider]
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def auto_load_env_vars
|
47
|
+
env_mapping = {
|
48
|
+
key: "MPESA_CONSUMER_KEY",
|
49
|
+
secret: "MPESA_CONSUMER_SECRET",
|
50
|
+
shortcode: "MPESA_SHORTCODE",
|
51
|
+
passkey: "MPESA_PASSKEY",
|
52
|
+
initiator_name: "MPESA_INITIATOR_NAME",
|
53
|
+
initiator_password: "MPESA_INITIATOR_PASSWORD",
|
54
|
+
sandbox: "MPESA_SANDBOX"
|
55
|
+
}
|
56
|
+
|
57
|
+
config = {}
|
58
|
+
env_mapping.each do |config_key, env_var|
|
59
|
+
value = ENV[env_var]
|
60
|
+
next unless value
|
61
|
+
|
62
|
+
# Convert sandbox to boolean
|
63
|
+
if config_key == :sandbox
|
64
|
+
value = %w[true 1 yes].include?(value.downcase)
|
65
|
+
end
|
66
|
+
|
67
|
+
config[config_key] = value
|
68
|
+
end
|
69
|
+
|
70
|
+
# Load extras from environment
|
71
|
+
extras = {}
|
72
|
+
%w[callback_url result_url timeout_url].each do |extra|
|
73
|
+
env_var = "MPESA_#{extra.upcase}"
|
74
|
+
extras[extra.to_sym] = ENV[env_var] if ENV[env_var]
|
75
|
+
end
|
76
|
+
|
77
|
+
config[:extras] = extras unless extras.empty?
|
78
|
+
|
79
|
+
config
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/paytree.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative "lib/paytree/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "paytree"
|
5
|
+
spec.version = Paytree::VERSION
|
6
|
+
spec.authors = ["Charles Chuck"]
|
7
|
+
spec.email = ["chalcchuck@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "Rails-optional payments abstraction for M-Pesa (Daraja) and more."
|
10
|
+
spec.description = "Clean, adapter-based Ruby DSL for mobile money integrations like M-Pesa via Daraja, with future provider support (Tingg, Airtel, Cellulant)."
|
11
|
+
spec.homepage = "https://github.com/mundanecodes/paytree"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = ">= 3.2.0"
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/mundanecodes/paytree"
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/mundanecodes/paytree/blob/main/CHANGELOG.md"
|
18
|
+
spec.metadata["documentation_uri"] = "https://github.com/mundanecodes/paytree/blob/main/README.md"
|
19
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/mundanecodes/paytree/issues"
|
20
|
+
spec.metadata["wiki_uri"] = "https://github.com/mundanecodes/paytree/wiki"
|
21
|
+
spec.metadata["mailing_list_uri"] = "https://github.com/mundanecodes/paytree/discussions"
|
22
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
23
|
+
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(test|spec|features|bin|exe)/}) || f.include?(".git")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
# Runtime deps
|
33
|
+
spec.add_dependency "faraday", "~> 2.0"
|
34
|
+
|
35
|
+
# Dev/test deps
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
37
|
+
spec.add_development_dependency "webmock", "~> 3.18"
|
38
|
+
spec.add_development_dependency "standard"
|
39
|
+
spec.add_development_dependency "rubocop-rails-omakase"
|
40
|
+
end
|
data/sig/paytree.rbs
ADDED