paytree 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10a40048e820db73f15da4bdc6511d33883870c4dd31fa6ed97fdffab5078171
4
- data.tar.gz: 6325d61fc365dc3a7264f3564af03aead8da63aaec305f3604b58dba20d0094a
3
+ metadata.gz: db5f3b07684338b0a9b63dabf3cc2a00d01adbfcb0c5e8511dd734fc3071d0e7
4
+ data.tar.gz: aa49b4d56b4eba825fa81ef7a5ff9c75708b92b6036ee5e6439fc1b0821a5e15
5
5
  SHA512:
6
- metadata.gz: 2d953e2487d35dbcd709d174fbf2f03c1199a9cefbdfe913d0209f093840a16ac3ae8ba9d3a4ef401f2480657c9cb1858a400ae08db9494807e1420a88939cdc
7
- data.tar.gz: a50c13a82b78bfd06726ad4b8acd1759e0e196a0eb7e28b4fbc618683f3dde4dae0142ec848237992d23ba8532ff1ffaa3f65d9662e39ef95fb5c01a97afa140
6
+ metadata.gz: d56d06d79d80c8a26569cc9c51b3b55eee3b3ec376e69f40ea21a6e37e325e57a2877cf344be727a98b79614b594ca33feffbadc2591e37f3a5f3d3d5d1d788e
7
+ data.tar.gz: d8dade7c6661f6ef505c5b25dbbacbbc744610f2333f9ba8d433bcbc01beea82b694abc7727c9c7a8ecf2d1eccd81e5c69caad380930fc162d3c49ad8f6730b0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- paytree (0.2.0)
4
+ paytree (0.3.0)
5
5
  faraday (~> 2.0)
6
6
 
7
7
  GEM
@@ -148,6 +148,7 @@ GEM
148
148
 
149
149
  PLATFORMS
150
150
  x86_64-darwin-24
151
+ x86_64-darwin-25
151
152
  x86_64-linux
152
153
 
153
154
  DEPENDENCIES
data/README.md CHANGED
@@ -1,6 +1,16 @@
1
+ <p align="center">
2
+ <img src="https://badge.fury.io/rb/paytree.svg" />
3
+ <img src="https://img.shields.io/badge/license-MIT-green.svg" />
4
+ <img src="https://img.shields.io/badge/ruby-3.2+-red" />
5
+ <a href="https://github.com/mundanecodes/paytree/actions">
6
+ <img src="https://github.com/mundanecodes/paytree/actions/workflows/ci.yml/badge.svg" />
7
+ </a>
8
+ </p>
9
+
1
10
  # Paytree
2
11
 
3
- A simple, highly opinionated Rails-optional Ruby gem for mobile money integrations. Currently supports Kenya's M-Pesa via the Daraja API with plans for additional providers.
12
+ A simple, highly opinionated Rails-optional Ruby gem for mobile money integrations.
13
+ Currently supports Kenya's M-Pesa via the Daraja API with plans for additional providers.
4
14
 
5
15
  ## Features
6
16
 
@@ -8,6 +18,8 @@ A simple, highly opinionated Rails-optional Ruby gem for mobile money integratio
8
18
  - **Convention over Configuration**: One clear setup pattern, opinionated defaults
9
19
  - **Safe Defaults**: Sandbox mode, proper timeouts, comprehensive error handling
10
20
  - **Batteries Included**: STK Push, B2C, B2B, C2B operations out of the box
21
+ - **API Versioning**: Support for both Daraja API v1 and v3 with backward compatibility
22
+ - **Enhanced Reliability**: Automatic token retry with exponential backoff
11
23
  - **Security First**: Credential management, no hardcoded secrets
12
24
 
13
25
  ## Quick Start
@@ -79,12 +91,59 @@ Paytree.configure_mpesa(
79
91
  # shortcode: "YOUR_PRODUCTION_SHORTCODE",
80
92
  # passkey: Rails.application.credentials.mpesa[:passkey],
81
93
  # sandbox: false,
82
- # retryable_errors: ["429.001.01", "500.001.02", "503.001.01"] # Optional: errors to retry
94
+ # api_version: "v1", # Optional: "v1" (default) or "v3"
95
+ # retryable_errors: ["429.001.01", "500.001.02", "503.001.01"] # Optional: errors to retry
83
96
  # )
84
97
  ```
85
98
 
86
99
  ---
87
100
 
101
+ ## API Version Support
102
+
103
+ Paytree supports both M-Pesa Daraja API v1 and v3 endpoints. The API version can be configured globally or via environment variables.
104
+
105
+ ### Configuration Options
106
+
107
+ ```ruby
108
+ # Use v1 API (default - backward compatible)
109
+ Paytree.configure_mpesa(
110
+ key: "YOUR_KEY",
111
+ secret: "YOUR_SECRET",
112
+ api_version: "v1" # Default
113
+ )
114
+
115
+ # Use v3 API (latest features)
116
+ Paytree.configure_mpesa(
117
+ key: "YOUR_KEY",
118
+ secret: "YOUR_SECRET",
119
+ api_version: "v3"
120
+ )
121
+
122
+ # Or via environment variable
123
+ ENV['MPESA_API_VERSION'] = 'v3'
124
+ Paytree.configure_mpesa(
125
+ key: "YOUR_KEY",
126
+ secret: "YOUR_SECRET"
127
+ # api_version automatically picked up from ENV
128
+ )
129
+ ```
130
+
131
+ ### Differences Between v1 and v3
132
+
133
+ | Feature | v1 | v3 |
134
+ |---------|----|----|
135
+ | **Endpoints** | `/mpesa/b2c/v1/paymentrequest` | `/mpesa/b2c/v3/paymentrequest` |
136
+ | **OriginatorConversationID** | Not required | Auto-generated UUID |
137
+ | **Reliability** | Standard | Enhanced with better tracking |
138
+
139
+
140
+ **Backward Compatibility:**
141
+ - Existing code continues to work unchanged (defaults to v1)
142
+ - No breaking changes when upgrading Paytree versions
143
+ - Can switch between v1/v3 by changing configuration only
144
+
145
+ ---
146
+
88
147
  ## Usage Examples
89
148
 
90
149
  ### STK Push (Customer Payment)
@@ -158,7 +217,7 @@ end
158
217
 
159
218
  Send funds directly to a customer’s M-Pesa wallet via the B2C API.
160
219
 
161
- ### Example
220
+ ### Basic Example
162
221
  ```ruby
163
222
  response = Paytree::Mpesa::B2C.call(
164
223
  phone_number: "254712345678",
@@ -176,6 +235,43 @@ else
176
235
  end
177
236
  ```
178
237
 
238
+ ### v3 API Features
239
+
240
+ When using `api_version: "v3"`, B2C calls automatically include an `OriginatorConversationID` for enhanced tracking:
241
+
242
+ ```ruby
243
+ # Configure for v3 API
244
+ Paytree.configure_mpesa(
245
+ key: "YOUR_KEY",
246
+ secret: "YOUR_SECRET",
247
+ api_version: "v3"
248
+ )
249
+
250
+ # Same call, but now uses v3 endpoint with auto-generated OriginatorConversationID
251
+ response = Paytree::Mpesa::B2C.call(
252
+ phone_number: "254712345678",
253
+ amount: 100
254
+ )
255
+
256
+ # v3 response includes additional tracking data
257
+ if response.success?
258
+ puts "Conversation ID: #{response.data["ConversationID"]}"
259
+ puts "Originator ID: #{response.data["OriginatorConversationID"]}" # Auto-generated UUID
260
+ end
261
+ ```
262
+
263
+ ### Custom OriginatorConversationID (v3 only)
264
+
265
+ You can provide your own tracking ID for v3 API calls:
266
+
267
+ ```ruby
268
+ response = Paytree::Mpesa::B2C.call(
269
+ phone_number: "254712345678",
270
+ amount: 100,
271
+ originator_conversation_id: "CUSTOM-TRACK-#{Time.now.to_i}"
272
+ )
273
+ ```
274
+
179
275
  ---
180
276
 
181
277
  ## C2B (Customer to Business)
@@ -268,10 +364,10 @@ unless response.success?
268
364
  puts response.code # "404.001.03" (if available)
269
365
  puts response.data # {
270
366
  # "requestId" => "",
271
- # "errorCode" => "404.001.03",
367
+ # "errorCode" => "404.001.03",
272
368
  # "errorMessage" => "Invalid Access Token"
273
369
  # }
274
-
370
+
275
371
  # Check if error is retryable (based on configuration)
276
372
  if response.retryable?
277
373
  puts "This error can be retried"
@@ -311,6 +407,9 @@ Paytree allows you to configure which error codes should be considered retryable
311
407
  - `"429.001.01"` - Rate limit exceeded
312
408
  - `"500.001.02"` - Temporary server error
313
409
  - `"503.001.01"` - Service temporarily unavailable
410
+ - `"timeout.connection"` - Network connection timeout (Net::OpenTimeout)
411
+ - `"timeout.read"` - Network read timeout (Net::ReadTimeout)
412
+ - `"timeout.request"` - HTTP request timeout (Faraday::TimeoutError)
314
413
 
315
414
  Configure retryable errors during setup:
316
415
 
@@ -318,7 +417,14 @@ Configure retryable errors during setup:
318
417
  Paytree.configure_mpesa(
319
418
  key: "YOUR_KEY",
320
419
  secret: "YOUR_SECRET",
321
- retryable_errors: ["429.001.01", "500.001.02", "503.001.01"]
420
+ retryable_errors: [
421
+ "429.001.01", # Rate limit
422
+ "500.001.02", # Server error
423
+ "503.001.01", # Service unavailable
424
+ "timeout.connection", # Connection timeout
425
+ "timeout.read", # Read timeout
426
+ "timeout.request" # Request timeout
427
+ ]
322
428
  )
323
429
  ```
324
430
 
@@ -5,7 +5,7 @@ module Paytree
5
5
  class Mpesa
6
6
  attr_accessor :key, :secret, :shortcode, :passkey, :adapter,
7
7
  :initiator_name, :initiator_password, :sandbox,
8
- :extras, :timeout, :retryable_errors
8
+ :extras, :timeout, :retryable_errors, :api_version
9
9
 
10
10
  def initialize
11
11
  @extras = {}
@@ -13,6 +13,7 @@ module Paytree
13
13
  @mutex = Mutex.new
14
14
  @timeout = 30 # Default 30 second timeout
15
15
  @retryable_errors = [] # Default empty array
16
+ @api_version = "v1" # Default to v1 for backward compatibility
16
17
  end
17
18
 
18
19
  def base_url
@@ -5,7 +5,9 @@ module Paytree
5
5
  module Adapters
6
6
  module Daraja
7
7
  class B2B < Base
8
- ENDPOINT = "/mpesa/b2b/v1/paymentrequest"
8
+ def self.endpoint
9
+ "/mpesa/b2b/#{config.api_version}/paymentrequest"
10
+ end
9
11
 
10
12
  class << self
11
13
  def call(short_code:, receiver_shortcode:, amount:, account_reference:, **opts)
@@ -28,7 +30,7 @@ module Paytree
28
30
  ResultURL: config.extras[:result_url]
29
31
  }.compact
30
32
 
31
- post_to_mpesa(:b2b, ENDPOINT, payload)
33
+ post_to_mpesa(:b2b, endpoint, payload)
32
34
  end
33
35
  end
34
36
  end
@@ -5,7 +5,9 @@ module Paytree
5
5
  module Adapters
6
6
  module Daraja
7
7
  class B2C < Base
8
- ENDPOINT = "/mpesa/b2c/v1/paymentrequest"
8
+ def self.endpoint
9
+ "/mpesa/b2c/#{config.api_version}/paymentrequest"
10
+ end
9
11
 
10
12
  class << self
11
13
  def call(phone_number:, amount:, **opts)
@@ -23,9 +25,14 @@ module Paytree
23
25
  CommandID: opts[:command_id] || "BusinessPayment",
24
26
  Remarks: opts[:remarks] || "OK",
25
27
  Occasion: opts[:occasion] || "Payment"
26
- }.compact
28
+ }
29
+
30
+ # Add OriginatorConversationID for v3
31
+ if config.api_version == "v3"
32
+ payload[:OriginatorConversationID] = opts[:originator_conversation_id] || generate_conversation_id
33
+ end
27
34
 
28
- post_to_mpesa(:b2c, ENDPOINT, payload)
35
+ post_to_mpesa(:b2c, endpoint, payload.compact)
29
36
  end
30
37
  end
31
38
  end
@@ -1,4 +1,5 @@
1
1
  require "base64"
2
+ require "securerandom"
2
3
  require_relative "response_helpers"
3
4
  require_relative "../../../utils/error_handling"
4
5
 
@@ -106,6 +107,10 @@ module Paytree
106
107
  end
107
108
  end
108
109
 
110
+ def generate_conversation_id
111
+ SecureRandom.uuid
112
+ end
113
+
109
114
  private
110
115
 
111
116
  def fetch_token
@@ -7,7 +7,7 @@ module Paytree
7
7
  Paytree::Errors::Base => e
8
8
  emit_error(e, context)
9
9
  raise
10
- rescue Faraday::TimeoutError => e
10
+ rescue Faraday::TimeoutError, Net::OpenTimeout, Net::ReadTimeout => e
11
11
  handle_faraday_error(
12
12
  e,
13
13
  context,
@@ -54,7 +54,11 @@ module Paytree
54
54
  code = info[:code]
55
55
  else
56
56
  message = error.message
57
- code = nil
57
+ code = case error
58
+ when Net::OpenTimeout then "timeout.connection"
59
+ when Net::ReadTimeout then "timeout.read"
60
+ when Faraday::TimeoutError then "timeout.request"
61
+ end
58
62
  end
59
63
 
60
64
  wrap_and_raise(
@@ -1,3 +1,3 @@
1
1
  module Paytree
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/paytree.rb CHANGED
@@ -51,7 +51,8 @@ module Paytree
51
51
  passkey: "MPESA_PASSKEY",
52
52
  initiator_name: "MPESA_INITIATOR_NAME",
53
53
  initiator_password: "MPESA_INITIATOR_PASSWORD",
54
- sandbox: "MPESA_SANDBOX"
54
+ sandbox: "MPESA_SANDBOX",
55
+ api_version: "MPESA_API_VERSION"
55
56
  }
56
57
 
57
58
  config = {}
data/paytree.gemspec CHANGED
@@ -6,8 +6,10 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ["Charles Chuck"]
7
7
  spec.email = ["chalcchuck@gmail.com"]
8
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)."
9
+ spec.summary = "A Ruby wrapper for the Mpesa API in Kenya."
10
+ spec.description = <<~DESC
11
+ Paytree is a lightweight Ruby wrapper for the full Mpesa API suite in Kenya - including B2C, C2B, STK Push and more. It supports certificate encryption, clean facades, and a pluggable adapter system (e.g. Daraja, Airtel..). Built for Rails and pure Ruby apps.
12
+ DESC
11
13
  spec.homepage = "https://github.com/mundanecodes/paytree"
12
14
  spec.license = "MIT"
13
15
  spec.required_ruby_version = ">= 3.2.0"
@@ -20,10 +22,12 @@ Gem::Specification.new do |spec|
20
22
  spec.metadata["wiki_uri"] = "https://github.com/mundanecodes/paytree/wiki"
21
23
  spec.metadata["mailing_list_uri"] = "https://github.com/mundanecodes/paytree/discussions"
22
24
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
25
+ spec.metadata["keywords"] = "mpesa,mpesa-api,b2c,stk-push,mobile-money,payments,daraja"
26
+ spec.metadata["rubygems_mfa_required"] = "true"
23
27
 
24
28
  spec.files = Dir.chdir(__dir__) do
25
29
  `git ls-files -z`.split("\x0").reject do |f|
26
- f.match(%r{^(test|spec|features|bin|exe)/}) || f.include?(".git")
30
+ f.match(%r{^(test|spec|features|bin|exe)/}) || f.include?(".git") || f.end_with?(".gem")
27
31
  end
28
32
  end
29
33
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paytree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Chuck
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-07-20 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -80,8 +79,12 @@ dependencies:
80
79
  - - ">="
81
80
  - !ruby/object:Gem::Version
82
81
  version: '0'
83
- description: Clean, adapter-based Ruby DSL for mobile money integrations like M-Pesa
84
- via Daraja, with future provider support (Tingg, Airtel, Cellulant).
82
+ description: 'Paytree is a lightweight Ruby wrapper for the full Mpesa API suite in
83
+ Kenya - including B2C, C2B, STK Push and more. It supports certificate encryption,
84
+ clean facades, and a pluggable adapter system (e.g. Daraja, Airtel..). Built for
85
+ Rails and pure Ruby apps.
86
+
87
+ '
85
88
  email:
86
89
  - chalcchuck@gmail.com
87
90
  executables: []
@@ -134,7 +137,8 @@ metadata:
134
137
  wiki_uri: https://github.com/mundanecodes/paytree/wiki
135
138
  mailing_list_uri: https://github.com/mundanecodes/paytree/discussions
136
139
  allowed_push_host: https://rubygems.org
137
- post_install_message:
140
+ keywords: mpesa,mpesa-api,b2c,stk-push,mobile-money,payments,daraja
141
+ rubygems_mfa_required: 'true'
138
142
  rdoc_options: []
139
143
  require_paths:
140
144
  - lib
@@ -149,8 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
153
  - !ruby/object:Gem::Version
150
154
  version: '0'
151
155
  requirements: []
152
- rubygems_version: 3.5.3
153
- signing_key:
156
+ rubygems_version: 3.6.7
154
157
  specification_version: 4
155
- summary: Rails-optional payments abstraction for M-Pesa (Daraja) and more.
158
+ summary: A Ruby wrapper for the Mpesa API in Kenya.
156
159
  test_files: []