mpesa_stk 1.3 → 3.0.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 +4 -4
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/cop.yml +18 -14
- data/.gitignore +2 -0
- data/.rubocop.yml +63 -0
- data/.sample.env +22 -1
- data/CHANGELOG.md +259 -0
- data/CODE_OF_CONDUCT.md +0 -0
- data/Gemfile +6 -2
- data/Gemfile.lock +91 -43
- data/LICENSE.txt +0 -0
- data/MIGRATION.md +77 -0
- data/README.md +536 -97
- data/Rakefile +8 -6
- data/SECURITY.md +21 -0
- data/bin/console +4 -3
- data/bin/index.jpeg +0 -0
- data/lib/mpesa_stk/access_token.rb +54 -21
- data/lib/mpesa_stk/account_balance.rb +30 -0
- data/lib/mpesa_stk/b2b.rb +44 -0
- data/lib/mpesa_stk/b2c.rb +39 -0
- data/lib/mpesa_stk/c2b.rb +44 -0
- data/lib/mpesa_stk/client.rb +67 -0
- data/lib/mpesa_stk/config.rb +36 -0
- data/lib/mpesa_stk/imsi.rb +28 -0
- data/lib/mpesa_stk/iot.rb +167 -0
- data/lib/mpesa_stk/pull_transactions.rb +44 -0
- data/lib/mpesa_stk/push.rb +43 -105
- data/lib/mpesa_stk/ratiba.rb +37 -0
- data/lib/mpesa_stk/reversal.rb +40 -0
- data/lib/mpesa_stk/stk_push_query.rb +38 -0
- data/lib/mpesa_stk/transaction_status.rb +38 -0
- data/lib/mpesa_stk/version.rb +3 -1
- data/lib/mpesa_stk.rb +27 -3
- data/mpesa_stk.gemspec +34 -21
- metadata +126 -44
- data/lib/mpesa_stk/push_payment.rb +0 -71
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mpesa_stk/client'
|
|
4
|
+
|
|
5
|
+
module MpesaStk
|
|
6
|
+
# Register and query pull transaction callbacks.
|
|
7
|
+
class PullTransactions < Client
|
|
8
|
+
class << self
|
|
9
|
+
def register(**options)
|
|
10
|
+
new(**options).register_url
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(start_date, end_date, **options)
|
|
14
|
+
new(**options).query_transactions(start_date, end_date)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def register_url
|
|
19
|
+
post(
|
|
20
|
+
'pull_transactions_register_url',
|
|
21
|
+
{
|
|
22
|
+
ShortCode: option('business_short_code', :short_code),
|
|
23
|
+
RequestType: @options.fetch(:request_type, ''),
|
|
24
|
+
NominatedNumber: @options.fetch(:nominated_number, ''),
|
|
25
|
+
CallBackURL: option('callback_url')
|
|
26
|
+
},
|
|
27
|
+
error_message: 'Failed to register pull transactions URL'
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def query_transactions(start_date, end_date)
|
|
32
|
+
post(
|
|
33
|
+
'pull_transactions_query_url',
|
|
34
|
+
{
|
|
35
|
+
ShortCode: option('business_short_code', :short_code),
|
|
36
|
+
StartDate: start_date,
|
|
37
|
+
EndDate: end_date,
|
|
38
|
+
OffSetValue: @options.fetch(:offset_value, '0')
|
|
39
|
+
},
|
|
40
|
+
error_message: 'Failed to query pull transactions'
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/mpesa_stk/push.rb
CHANGED
|
@@ -1,131 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mpesa_stk/client'
|
|
2
4
|
|
|
3
5
|
module MpesaStk
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# STK Push (Lipa na M-Pesa): PayBill and Buy Goods.
|
|
7
|
+
#
|
|
8
|
+
# MpesaStk::Push.call(amount, phone) # PayBill, ENV only
|
|
9
|
+
# MpesaStk::Push.call(amount, phone, type: :buy_goods) # Buy Goods
|
|
10
|
+
# MpesaStk::Push.call(amount, phone, key: '...', ...) # per-request overrides
|
|
11
|
+
class Push < Client
|
|
12
|
+
TRANSACTION_TYPES = {
|
|
13
|
+
pay_bill: 'CustomerPayBillOnline',
|
|
14
|
+
buy_goods: 'CustomerBuyGoodsOnline'
|
|
15
|
+
}.freeze
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
class << self
|
|
18
|
+
def call(amount, phone_number, type: :pay_bill, **options)
|
|
19
|
+
new(amount, phone_number, type: type, **options).push_payment
|
|
12
20
|
end
|
|
13
21
|
end
|
|
14
22
|
|
|
15
|
-
attr_reader :
|
|
23
|
+
attr_reader :amount, :phone_number, :transaction_type
|
|
16
24
|
|
|
17
|
-
def initialize(amount, phone_number,
|
|
18
|
-
@
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@business_passkey = business_passkey
|
|
25
|
+
def initialize(amount, phone_number, type: :pay_bill, **options)
|
|
26
|
+
@transaction_type = TRANSACTION_TYPES.fetch(type) do
|
|
27
|
+
raise ArgumentError, "Unknown STK type: #{type}. Use :pay_bill or :buy_goods"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
super(**options)
|
|
24
31
|
@amount = amount
|
|
25
32
|
@phone_number = phone_number
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
def push_payment
|
|
29
|
-
|
|
30
|
-
JSON.parse(response.body)
|
|
36
|
+
post('process_request_url', stk_push_payload, error_message: 'Failed to push payment')
|
|
31
37
|
end
|
|
32
38
|
|
|
33
39
|
private
|
|
34
40
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
end
|
|
41
|
+
def stk_push_payload
|
|
42
|
+
short_code, passkey, timestamp = stk_credentials
|
|
38
43
|
|
|
39
|
-
def headers
|
|
40
44
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def body
|
|
47
|
-
{
|
|
48
|
-
BusinessShortCode: get_business_short_code,
|
|
49
|
-
Password: generate_password,
|
|
50
|
-
Timestamp: "#{timestamp}",
|
|
45
|
+
BusinessShortCode: short_code,
|
|
46
|
+
Password: stk_password(short_code, passkey, timestamp),
|
|
47
|
+
Timestamp: timestamp.to_s,
|
|
51
48
|
TransactionType: transaction_type,
|
|
52
|
-
Amount:
|
|
53
|
-
PartyA:
|
|
54
|
-
PartyB:
|
|
55
|
-
PhoneNumber:
|
|
56
|
-
CallBackURL:
|
|
57
|
-
AccountReference:
|
|
58
|
-
TransactionDesc:
|
|
59
|
-
}
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def generate_bill_reference_number(number)
|
|
63
|
-
charset = Array('A'..'Z') + Array('a'..'z')
|
|
64
|
-
Array.new(number) { charset.sample }.join
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def timestamp
|
|
68
|
-
DateTime.now.strftime("%Y%m%d%H%M%S").to_i
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# shortcode
|
|
72
|
-
# passkey
|
|
73
|
-
# timestamp
|
|
74
|
-
def generate_password
|
|
75
|
-
key = "#{get_business_short_code}#{get_business_passkey}#{timestamp}"
|
|
76
|
-
Base64.encode64(key).split("\n").join
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def get_business_short_code
|
|
80
|
-
if business_short_code.nil? || business_short_code.eql?("")
|
|
81
|
-
if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?("")
|
|
82
|
-
raise Exception.new "Business Short Code is not defined"
|
|
83
|
-
else
|
|
84
|
-
ENV['business_short_code']
|
|
85
|
-
end
|
|
86
|
-
else
|
|
87
|
-
business_short_code
|
|
88
|
-
end
|
|
49
|
+
Amount: amount.to_s,
|
|
50
|
+
PartyA: phone_number.to_s,
|
|
51
|
+
PartyB: party_b(short_code),
|
|
52
|
+
PhoneNumber: phone_number.to_s,
|
|
53
|
+
CallBackURL: option('callback_url'),
|
|
54
|
+
AccountReference: random_reference,
|
|
55
|
+
TransactionDesc: random_reference
|
|
56
|
+
}
|
|
89
57
|
end
|
|
90
58
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
if ENV['business_passkey'].nil? || ENV['business_passkey'].eql?("")
|
|
94
|
-
raise Exception.new "Business Passkey is not defined"
|
|
95
|
-
else
|
|
96
|
-
ENV['business_passkey']
|
|
97
|
-
end
|
|
98
|
-
else
|
|
99
|
-
business_passkey
|
|
100
|
-
end
|
|
59
|
+
def stk_credentials
|
|
60
|
+
[option('business_short_code'), option('business_passkey'), stk_timestamp]
|
|
101
61
|
end
|
|
102
62
|
|
|
103
|
-
def
|
|
104
|
-
if
|
|
105
|
-
if ENV['callback_url'].nil? || ENV['callback_url'].eql?("")
|
|
106
|
-
raise Exception.new "Callback URL is not defined"
|
|
107
|
-
else
|
|
108
|
-
ENV['callback_url']
|
|
109
|
-
end
|
|
110
|
-
else
|
|
111
|
-
callback_url
|
|
112
|
-
end
|
|
113
|
-
end
|
|
63
|
+
def party_b(short_code)
|
|
64
|
+
return short_code if transaction_type == 'CustomerPayBillOnline'
|
|
114
65
|
|
|
115
|
-
|
|
116
|
-
if transaction_type.eql?("CustomerPayBillOnline")
|
|
117
|
-
get_business_short_code
|
|
118
|
-
else
|
|
119
|
-
if till_number.nil?
|
|
120
|
-
if ENV['till_number'].nil? || ENV['till_number'].eql?("")
|
|
121
|
-
raise Exception.new "Till number is not defined"
|
|
122
|
-
else
|
|
123
|
-
ENV['till_number']
|
|
124
|
-
end
|
|
125
|
-
else
|
|
126
|
-
till_number
|
|
127
|
-
end
|
|
128
|
-
end
|
|
66
|
+
option('till_number')
|
|
129
67
|
end
|
|
130
68
|
end
|
|
131
|
-
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mpesa_stk/client'
|
|
4
|
+
|
|
5
|
+
module MpesaStk
|
|
6
|
+
# Create M-Pesa Ratiba standing orders (recurring payments).
|
|
7
|
+
class Ratiba < Client
|
|
8
|
+
class << self
|
|
9
|
+
def call(amount:, party_a:, start_date:, end_date:, **options)
|
|
10
|
+
new(**options, amount: amount, party_a: party_a, start_date: start_date, end_date: end_date).create
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
post('ratiba_url', ratiba_payload, error_message: 'Failed to create standing order')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def ratiba_payload
|
|
21
|
+
{
|
|
22
|
+
StandingOrderName: @options.fetch(:standing_order_name, 'Standing Order'),
|
|
23
|
+
BusinessShortCode: option('business_short_code'),
|
|
24
|
+
TransactionType: @options.fetch(:transaction_type, 'Standing Order Customer Pay Bill'),
|
|
25
|
+
Amount: @options.fetch(:amount).to_s,
|
|
26
|
+
PartyA: @options.fetch(:party_a),
|
|
27
|
+
ReceiverPartyIdentifierType: @options.fetch(:receiver_party_identifier_type, '4'),
|
|
28
|
+
CallBackURL: option('callback_url'),
|
|
29
|
+
AccountReference: @options.fetch(:account_reference, ''),
|
|
30
|
+
TransactionDesc: @options.fetch(:transaction_desc, ''),
|
|
31
|
+
Frequency: @options.fetch(:frequency, '3'),
|
|
32
|
+
StartDate: @options.fetch(:start_date),
|
|
33
|
+
EndDate: @options.fetch(:end_date)
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mpesa_stk/client'
|
|
4
|
+
|
|
5
|
+
module MpesaStk
|
|
6
|
+
# Reverse a completed M-Pesa transaction.
|
|
7
|
+
class Reversal < Client
|
|
8
|
+
class << self
|
|
9
|
+
def call(transaction_id, amount, **options)
|
|
10
|
+
new(transaction_id, amount, **options).reverse_transaction
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :transaction_id, :amount
|
|
15
|
+
|
|
16
|
+
def initialize(transaction_id, amount, **options)
|
|
17
|
+
super(**options)
|
|
18
|
+
@transaction_id = transaction_id
|
|
19
|
+
@amount = amount
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reverse_transaction
|
|
23
|
+
post(
|
|
24
|
+
'reversal_url',
|
|
25
|
+
{
|
|
26
|
+
Initiator: option('initiator'),
|
|
27
|
+
SecurityCredential: option('security_credential'),
|
|
28
|
+
CommandID: 'TransactionReversal',
|
|
29
|
+
TransactionID: transaction_id,
|
|
30
|
+
Amount: amount.to_s,
|
|
31
|
+
ReceiverParty: option('business_short_code', :receiver_party),
|
|
32
|
+
RecieverIdentifierType: @options.fetch(:receiver_identifier_type, '4'),
|
|
33
|
+
ResultURL: option('result_url'),
|
|
34
|
+
QueueTimeOutURL: option('queue_timeout_url')
|
|
35
|
+
},
|
|
36
|
+
error_message: 'Failed to reverse transaction'
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mpesa_stk/client'
|
|
4
|
+
|
|
5
|
+
module MpesaStk
|
|
6
|
+
# Query the status of an STK Push checkout request.
|
|
7
|
+
class StkPushQuery < Client
|
|
8
|
+
class << self
|
|
9
|
+
def call(checkout_request_id, **options)
|
|
10
|
+
new(checkout_request_id, **options).query_status
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :checkout_request_id
|
|
15
|
+
|
|
16
|
+
def initialize(checkout_request_id, **options)
|
|
17
|
+
super(**options)
|
|
18
|
+
@checkout_request_id = checkout_request_id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def query_status
|
|
22
|
+
short_code = option('business_short_code')
|
|
23
|
+
passkey = option('business_passkey')
|
|
24
|
+
timestamp = stk_timestamp
|
|
25
|
+
|
|
26
|
+
post(
|
|
27
|
+
'stk_push_query_url',
|
|
28
|
+
{
|
|
29
|
+
BusinessShortCode: short_code,
|
|
30
|
+
Password: stk_password(short_code, passkey, timestamp),
|
|
31
|
+
Timestamp: timestamp.to_s,
|
|
32
|
+
CheckoutRequestID: checkout_request_id
|
|
33
|
+
},
|
|
34
|
+
error_message: 'Failed to query STK push status'
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mpesa_stk/client'
|
|
4
|
+
|
|
5
|
+
module MpesaStk
|
|
6
|
+
# Query the status of any M-Pesa transaction by ID.
|
|
7
|
+
class TransactionStatus < Client
|
|
8
|
+
class << self
|
|
9
|
+
def call(transaction_id, **options)
|
|
10
|
+
new(transaction_id, **options).query_status
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :transaction_id
|
|
15
|
+
|
|
16
|
+
def initialize(transaction_id, **options)
|
|
17
|
+
super(**options)
|
|
18
|
+
@transaction_id = transaction_id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def query_status
|
|
22
|
+
post(
|
|
23
|
+
'transaction_status_url',
|
|
24
|
+
{
|
|
25
|
+
Initiator: option('initiator'),
|
|
26
|
+
SecurityCredential: option('security_credential'),
|
|
27
|
+
CommandID: 'TransactionStatusQuery',
|
|
28
|
+
TransactionID: transaction_id,
|
|
29
|
+
PartyA: option('business_short_code', :party_a),
|
|
30
|
+
IdentifierType: @options.fetch(:identifier_type, '4'),
|
|
31
|
+
ResultURL: option('result_url'),
|
|
32
|
+
QueueTimeOutURL: option('queue_timeout_url')
|
|
33
|
+
},
|
|
34
|
+
error_message: 'Failed to query transaction status'
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/mpesa_stk/version.rb
CHANGED
data/lib/mpesa_stk.rb
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mpesa_stk/version'
|
|
4
|
+
require 'mpesa_stk/config'
|
|
5
|
+
require 'mpesa_stk/client'
|
|
6
|
+
require 'mpesa_stk/access_token'
|
|
3
7
|
require 'mpesa_stk/push'
|
|
4
|
-
require '
|
|
8
|
+
require 'mpesa_stk/transaction_status'
|
|
9
|
+
require 'mpesa_stk/stk_push_query'
|
|
10
|
+
require 'mpesa_stk/b2c'
|
|
11
|
+
require 'mpesa_stk/b2b'
|
|
12
|
+
require 'mpesa_stk/c2b'
|
|
13
|
+
require 'mpesa_stk/account_balance'
|
|
14
|
+
require 'mpesa_stk/reversal'
|
|
15
|
+
require 'mpesa_stk/ratiba'
|
|
16
|
+
require 'mpesa_stk/iot'
|
|
17
|
+
require 'mpesa_stk/imsi'
|
|
18
|
+
require 'mpesa_stk/pull_transactions'
|
|
19
|
+
|
|
20
|
+
require 'dotenv/load' unless ENV['MPESA_STK_SKIP_DOTENV'] == 'true'
|
|
5
21
|
require 'httparty'
|
|
22
|
+
require 'securerandom'
|
|
23
|
+
|
|
24
|
+
# Ruby client for Safaricom Daraja M-Pesa APIs (STK Push, B2C, B2B, C2B, and related services).
|
|
25
|
+
module MpesaStk
|
|
26
|
+
def self.configure(&block)
|
|
27
|
+
Config.configure(&block)
|
|
28
|
+
end
|
|
29
|
+
end
|
data/mpesa_stk.gemspec
CHANGED
|
@@ -1,36 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
|
-
lib = File.expand_path(
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require
|
|
5
|
+
require 'mpesa_stk/version'
|
|
5
6
|
|
|
6
7
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name =
|
|
8
|
+
spec.name = 'mpesa_stk'
|
|
8
9
|
spec.version = MpesaStk::VERSION
|
|
9
|
-
spec.authors = [
|
|
10
|
-
spec.email = [
|
|
10
|
+
spec.authors = %w[mboya cess]
|
|
11
|
+
spec.email = ['mboyaberry@gmail.com', 'cessmbuguar@gmail.com']
|
|
11
12
|
|
|
12
|
-
spec.summary =
|
|
13
|
-
spec.description =
|
|
14
|
-
spec.homepage =
|
|
15
|
-
spec.license =
|
|
13
|
+
spec.summary = 'Lipa na M-Pesa Online Payment.'
|
|
14
|
+
spec.description = 'initiate a M-Pesa transaction on behalf of a customer using STK Push.'
|
|
15
|
+
spec.homepage = 'https://github.com/mboya/mpesa_stk'
|
|
16
|
+
spec.license = 'MIT'
|
|
16
17
|
|
|
17
|
-
spec.
|
|
18
|
+
spec.required_ruby_version = '>= 2.6.0'
|
|
19
|
+
|
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
18
21
|
f.match(%r{^(test|spec|features)/})
|
|
19
22
|
end
|
|
20
|
-
spec.bindir =
|
|
23
|
+
spec.bindir = 'exe'
|
|
21
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
|
-
spec.require_paths = [
|
|
25
|
+
spec.require_paths = ['lib']
|
|
23
26
|
|
|
24
|
-
spec.
|
|
25
|
-
spec.
|
|
27
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
28
|
+
spec.metadata['source_code_uri'] = "#{spec.homepage}.git"
|
|
29
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
|
30
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
31
|
+
|
|
32
|
+
spec.add_dependency 'base64', '>= 0.1.0'
|
|
33
|
+
spec.add_dependency 'csv', '>= 3.0.0'
|
|
34
|
+
spec.add_dependency 'httparty', '>= 0.15.6', '< 0.25.0'
|
|
35
|
+
spec.add_dependency 'redis', '>= 4.0'
|
|
26
36
|
spec.add_dependency 'redis-namespace', '~> 1.5', '>= 1.5.3'
|
|
37
|
+
spec.add_dependency 'redis-rack', '~> 2.0', '>= 2.0.2'
|
|
27
38
|
|
|
28
|
-
spec.add_development_dependency
|
|
29
|
-
spec.add_development_dependency
|
|
30
|
-
spec.add_development_dependency
|
|
39
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
40
|
+
spec.add_development_dependency 'minitest', '~> 5.20'
|
|
41
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
|
31
42
|
|
|
32
|
-
spec.add_development_dependency '
|
|
33
|
-
spec.add_development_dependency 'pry
|
|
34
|
-
spec.add_development_dependency '
|
|
35
|
-
spec.add_development_dependency
|
|
43
|
+
spec.add_development_dependency 'dotenv', '~> 3.0'
|
|
44
|
+
spec.add_development_dependency 'pry', '~> 0.14'
|
|
45
|
+
spec.add_development_dependency 'pry-nav', '~> 1.0'
|
|
46
|
+
spec.add_development_dependency 'rubocop', '~> 1.0'
|
|
47
|
+
spec.add_development_dependency 'simplecov', '~> 0.22'
|
|
48
|
+
spec.add_development_dependency 'webmock', '~> 3.18'
|
|
36
49
|
end
|