malipopay 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6492c53849e03b8431bfc390b4418b1077c18d7aa8098fc4aedb8507aeb0489b
4
+ data.tar.gz: dd706e99038d9fede2fd15a7777192087d6ab3b1b8b429b60635911e7e05ee7a
5
+ SHA512:
6
+ metadata.gz: 00a447b13ed9b346df7ec49320179839fef574d44ef8c6e4faa16dc1ce2f26c948e6db20b014321ab83e0f5b4545f72e0ee0da0da53dc983f3868491974fe6ae
7
+ data.tar.gz: 158b3238b9f5a93ece0b32a078390733864ba04277be4bdab54ca789ce2e405f1931b9e693e44507bcfebb08a32ea878ce928deaeed982a623ea0228aa09b948
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lockwood Technology Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,312 @@
1
+ # MaliPoPay Ruby SDK
2
+
3
+ Official Ruby SDK for the [MaliPoPay](https://malipopay.co.tz) payment platform (Tanzania).
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "malipopay"
11
+ ```
12
+
13
+ Then run:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ Or install directly:
20
+
21
+ ```bash
22
+ gem install malipopay
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```ruby
28
+ require "malipopay"
29
+
30
+ client = MaliPoPay::Client.new(
31
+ api_key: "your_api_token",
32
+ environment: :production # or :uat for testing
33
+ )
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ | Option | Default | Description |
39
+ |------------------|---------------|--------------------------------------|
40
+ | `api_key` | *required* | Your MaliPoPay API token |
41
+ | `environment` | `:production` | `:production` or `:uat` |
42
+ | `base_url` | `nil` | Override the base URL |
43
+ | `timeout` | `30` | Request timeout in seconds |
44
+ | `retries` | `2` | Number of retries on 429/5xx |
45
+ | `webhook_secret` | `nil` | Secret for verifying webhook events |
46
+
47
+ ## Resources
48
+
49
+ ### Payments
50
+
51
+ ```ruby
52
+ # Collect mobile money
53
+ result = client.payments.collect(
54
+ amount: 10_000,
55
+ phone: "255712345678",
56
+ provider: "Vodacom",
57
+ reference: "ORDER-001",
58
+ currency: "TZS"
59
+ )
60
+
61
+ # Disburse (send money)
62
+ client.payments.disburse(
63
+ amount: 5_000,
64
+ phone: "255712345678",
65
+ provider: "M-Pesa",
66
+ reference: "PAY-001"
67
+ )
68
+
69
+ # Verify payment
70
+ status = client.payments.verify("PAY-REF-123")
71
+
72
+ # List payments
73
+ payments = client.payments.list(page: 1, limit: 20)
74
+
75
+ # Search payments
76
+ results = client.payments.search(status: "completed", dateFrom: "2026-01-01")
77
+
78
+ # Approve a pending payment
79
+ client.payments.approve(reference: "PAY-REF-123")
80
+
81
+ # Retry a failed collection
82
+ client.payments.retry_collection("PAY-REF-123")
83
+
84
+ # Create a payment link
85
+ link = client.payments.create_link(amount: 50_000, description: "Product purchase")
86
+
87
+ # Instant payment
88
+ client.payments.pay_now(amount: 10_000, phone: "255712345678")
89
+ ```
90
+
91
+ ### Customers
92
+
93
+ ```ruby
94
+ # Create a customer
95
+ customer = client.customers.create(
96
+ name: "John Doe",
97
+ phone: "255712345678",
98
+ email: "john@example.com"
99
+ )
100
+
101
+ # List customers
102
+ customers = client.customers.list(page: 1, limit: 20)
103
+
104
+ # Get by ID
105
+ customer = client.customers.get("customer_id")
106
+
107
+ # Search
108
+ results = client.customers.search(query: "John")
109
+
110
+ # Get by phone
111
+ customer = client.customers.get_by_phone("255712345678")
112
+
113
+ # Verify customer
114
+ client.customers.verify_customer(phone: "255712345678")
115
+ ```
116
+
117
+ ### Invoices
118
+
119
+ ```ruby
120
+ # Create an invoice
121
+ invoice = client.invoices.create(
122
+ customerId: "cust_123",
123
+ items: [
124
+ { description: "Service", quantity: 1, unitPrice: 100_000 }
125
+ ],
126
+ currency: "TZS",
127
+ dueDate: "2026-12-31"
128
+ )
129
+
130
+ # List invoices
131
+ invoices = client.invoices.list(page: 1, limit: 20)
132
+
133
+ # Get by ID
134
+ invoice = client.invoices.get("invoice_id")
135
+
136
+ # Approve a draft invoice
137
+ client.invoices.approve_draft(invoiceId: "invoice_id")
138
+
139
+ # Record a payment
140
+ client.invoices.record_payment(
141
+ invoiceId: "invoice_id",
142
+ amount: 50_000,
143
+ reference: "RCPT-001"
144
+ )
145
+ ```
146
+
147
+ ### Products
148
+
149
+ ```ruby
150
+ # Create a product
151
+ product = client.products.create(
152
+ name: "Premium Plan",
153
+ price: 99_000,
154
+ description: "Monthly subscription"
155
+ )
156
+
157
+ # List / Get / Update
158
+ products = client.products.list
159
+ product = client.products.get("product_id")
160
+ client.products.update("product_id", price: 89_000)
161
+ ```
162
+
163
+ ### Transactions
164
+
165
+ ```ruby
166
+ # List transactions
167
+ transactions = client.transactions.list(page: 1, limit: 50)
168
+
169
+ # Get by ID
170
+ txn = client.transactions.get("txn_id")
171
+
172
+ # Search
173
+ results = client.transactions.search(dateFrom: "2026-01-01", dateTo: "2026-03-31")
174
+
175
+ # Paginate through all transactions
176
+ client.transactions.paginate(limit: 100).each do |page|
177
+ page["data"].each { |txn| process(txn) }
178
+ end
179
+ ```
180
+
181
+ ### Account
182
+
183
+ ```ruby
184
+ # Get account transactions
185
+ txns = client.account.transactions(dateFrom: "2026-01-01")
186
+
187
+ # Reconciliation
188
+ recon = client.account.reconciliation(dateFrom: "2026-01-01", dateTo: "2026-03-31")
189
+
190
+ # Financial reports
191
+ client.account.financial_position
192
+ client.account.income_statement
193
+ client.account.general_ledger
194
+ client.account.trial_balance
195
+ ```
196
+
197
+ ### SMS
198
+
199
+ ```ruby
200
+ # Send a single SMS
201
+ client.sms.send_sms(
202
+ to: "255712345678",
203
+ message: "Your payment of TZS 10,000 has been received.",
204
+ senderId: "MaliPoPay"
205
+ )
206
+
207
+ # Send bulk SMS
208
+ client.sms.send_bulk(
209
+ messages: [
210
+ { to: "255712345678", message: "Hello John!" },
211
+ { to: "255798765432", message: "Hello Jane!" }
212
+ ],
213
+ senderId: "MaliPoPay"
214
+ )
215
+
216
+ # Schedule SMS
217
+ client.sms.schedule(
218
+ to: "255712345678",
219
+ message: "Reminder: Your invoice is due tomorrow.",
220
+ senderId: "MaliPoPay",
221
+ scheduledAt: "2026-04-15T09:00:00Z"
222
+ )
223
+ ```
224
+
225
+ ### References
226
+
227
+ ```ruby
228
+ banks = client.references.banks
229
+ currencies = client.references.currencies
230
+ countries = client.references.countries
231
+ institutions = client.references.institutions
232
+ business_types = client.references.business_types
233
+ ```
234
+
235
+ ## Webhooks
236
+
237
+ ```ruby
238
+ client = MaliPoPay::Client.new(
239
+ api_key: "your_api_token",
240
+ webhook_secret: "whsec_your_secret"
241
+ )
242
+
243
+ # In your webhook endpoint (e.g., Sinatra, Rails controller)
244
+ payload = request.body.read
245
+ signature = request.headers["X-MaliPoPay-Signature"]
246
+ timestamp = request.headers["X-MaliPoPay-Timestamp"]
247
+
248
+ event = client.webhooks.construct_event(payload, signature, timestamp: timestamp)
249
+
250
+ case event["event"]
251
+ when "payment.completed"
252
+ # Handle successful payment
253
+ when "payment.failed"
254
+ # Handle failed payment
255
+ end
256
+ ```
257
+
258
+ ## Error Handling
259
+
260
+ ```ruby
261
+ begin
262
+ client.payments.collect(amount: 10_000, phone: "255712345678")
263
+ rescue MaliPoPay::AuthenticationError => e
264
+ # Invalid API key (401)
265
+ rescue MaliPoPay::PermissionError => e
266
+ # Insufficient permissions (403)
267
+ rescue MaliPoPay::NotFoundError => e
268
+ # Resource not found (404)
269
+ rescue MaliPoPay::ValidationError => e
270
+ # Invalid parameters (400/422)
271
+ puts e.errors
272
+ rescue MaliPoPay::RateLimitError => e
273
+ # Rate limited (429) - retry after e.retry_after seconds
274
+ rescue MaliPoPay::ApiError => e
275
+ # Server error (5xx)
276
+ rescue MaliPoPay::ConnectionError => e
277
+ # Network error
278
+ end
279
+ ```
280
+
281
+ ## Environments
282
+
283
+ | Environment | Base URL |
284
+ |-------------|---------------------------------------|
285
+ | Production | `https://core-prod.malipopay.co.tz` |
286
+ | UAT | `https://core-uat.malipopay.co.tz` |
287
+
288
+ ## Requirements
289
+
290
+ - Ruby >= 3.0
291
+ - Faraday ~> 2.0
292
+
293
+ ## License
294
+
295
+ MIT License - Copyright (c) 2026 [Lockwood Technology Ltd](https://lockwood.co.tz)
296
+
297
+
298
+ ---
299
+
300
+ ## See Also
301
+
302
+ | SDK | Install |
303
+ |-----|---------|
304
+ | [Node.js](https://github.com/Malipopay/malipopay-node) | `npm install malipopay` |
305
+ | [Python](https://github.com/Malipopay/malipopay-python) | `pip install malipopay` |
306
+ | [PHP](https://github.com/Malipopay/malipopay-php) | `composer require malipopay/malipopay-php` |
307
+ | [Java](https://github.com/Malipopay/malipopay-java) | Maven / Gradle |
308
+ | [.NET](https://github.com/Malipopay/malipopay-dotnet) | `dotnet add package MaliPoPay` |
309
+ | [Ruby](https://github.com/Malipopay/malipopay-ruby) | `gem install malipopay` |
310
+
311
+ [API Reference](https://developers.malipopay.co.tz) | [OpenAPI Spec](https://github.com/Malipopay/malipopay-openapi) | [Test Scenarios](https://github.com/Malipopay/malipopay-sdk-tests)
312
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -0,0 +1,300 @@
1
+ # Configuration
2
+
3
+ This guide covers all the ways to configure the MaliPoPay Ruby SDK, from basic client setup to Rails integration.
4
+
5
+ ## MaliPoPay::Client Options
6
+
7
+ The `MaliPoPay::Client.new` constructor accepts the following options:
8
+
9
+ ```ruby
10
+ client = MaliPoPay::Client.new(
11
+ api_key: 'your_api_key',
12
+ environment: :production, # or :uat
13
+ base_url: nil, # overrides environment if set
14
+ timeout: 30, # request timeout in seconds
15
+ retries: 2, # automatic retries on transient errors
16
+ webhook_secret: nil # for webhook signature verification
17
+ )
18
+ ```
19
+
20
+ ### Options Reference
21
+
22
+ | Option | Type | Default | Description |
23
+ |--------|------|---------|-------------|
24
+ | `api_key` | `String` | *required* | Your MaliPoPay API key |
25
+ | `environment` | `Symbol` | `:production` | `:production` or `:uat` |
26
+ | `base_url` | `String` | `nil` | Custom base URL; overrides `environment` when set |
27
+ | `timeout` | `Integer` | `30` | HTTP request timeout in seconds |
28
+ | `retries` | `Integer` | `2` | Number of automatic retries for transient errors |
29
+ | `webhook_secret` | `String` | `nil` | HMAC-SHA256 secret for webhook signature verification |
30
+
31
+ ### Environment URLs
32
+
33
+ | Environment | Base URL |
34
+ |-------------|----------|
35
+ | `:production` | `https://core-prod.malipopay.co.tz` |
36
+ | `:uat` | `https://core-uat.malipopay.co.tz` |
37
+
38
+ ## Symbols vs Strings
39
+
40
+ The SDK accepts both symbols and strings for the `environment` option. Symbols are the Ruby convention:
41
+
42
+ ```ruby
43
+ # Preferred (symbol)
44
+ client = MaliPoPay::Client.new(api_key: key, environment: :uat)
45
+
46
+ # Also works (string)
47
+ client = MaliPoPay::Client.new(api_key: key, environment: 'uat')
48
+ ```
49
+
50
+ The same applies to provider names in payment methods -- you can use either:
51
+
52
+ ```ruby
53
+ # Both work
54
+ client.payments.collect(provider: 'M-Pesa', ...)
55
+ client.payments.collect(provider: :'M-Pesa', ...)
56
+ ```
57
+
58
+ ## Basic Configuration
59
+
60
+ ### Minimal Setup
61
+
62
+ ```ruby
63
+ # Production with defaults (30s timeout, 2 retries)
64
+ client = MaliPoPay::Client.new(api_key: ENV['MALIPOPAY_API_KEY'])
65
+ ```
66
+
67
+ ### UAT for Testing
68
+
69
+ ```ruby
70
+ client = MaliPoPay::Client.new(
71
+ api_key: ENV['MALIPOPAY_UAT_API_KEY'],
72
+ environment: :uat
73
+ )
74
+ ```
75
+
76
+ ### Custom Timeout and Retries
77
+
78
+ ```ruby
79
+ client = MaliPoPay::Client.new(
80
+ api_key: ENV['MALIPOPAY_API_KEY'],
81
+ environment: :production,
82
+ timeout: 60, # longer timeout for slow networks
83
+ retries: 5 # more retries for critical operations
84
+ )
85
+ ```
86
+
87
+ ## Environment Selection
88
+
89
+ ### Automatic Based on RACK_ENV / RAILS_ENV
90
+
91
+ Tie the MaliPoPay environment to your application environment:
92
+
93
+ ```ruby
94
+ malipopay_env = if ENV['RACK_ENV'] == 'production' || ENV['RAILS_ENV'] == 'production'
95
+ :production
96
+ else
97
+ :uat
98
+ end
99
+
100
+ api_key = if malipopay_env == :production
101
+ ENV.fetch('MALIPOPAY_API_KEY')
102
+ else
103
+ ENV.fetch('MALIPOPAY_UAT_API_KEY')
104
+ end
105
+
106
+ client = MaliPoPay::Client.new(
107
+ api_key: api_key,
108
+ environment: malipopay_env
109
+ )
110
+ ```
111
+
112
+ ### Custom Base URL
113
+
114
+ For proxies or custom routing:
115
+
116
+ ```ruby
117
+ client = MaliPoPay::Client.new(
118
+ api_key: ENV['MALIPOPAY_API_KEY'],
119
+ base_url: 'https://custom-proxy.example.com'
120
+ )
121
+ ```
122
+
123
+ When `base_url` is set, it takes precedence over the `environment` setting.
124
+
125
+ ## Rails Integration
126
+
127
+ ### Initializer
128
+
129
+ Create a Rails initializer to configure the client globally:
130
+
131
+ ```ruby
132
+ # config/initializers/malipopay.rb
133
+
134
+ MALIPOPAY_CLIENT = MaliPoPay::Client.new(
135
+ api_key: Rails.application.credentials.dig(:malipopay, :api_key) ||
136
+ ENV.fetch('MALIPOPAY_API_KEY'),
137
+ environment: Rails.env.production? ? :production : :uat,
138
+ timeout: 30,
139
+ retries: 2,
140
+ webhook_secret: Rails.application.credentials.dig(:malipopay, :webhook_secret) ||
141
+ ENV['MALIPOPAY_WEBHOOK_SECRET']
142
+ )
143
+ ```
144
+
145
+ Then use it anywhere in your Rails app:
146
+
147
+ ```ruby
148
+ class PaymentsController < ApplicationController
149
+ def create
150
+ result = MALIPOPAY_CLIENT.payments.collect(
151
+ amount: params[:amount].to_i,
152
+ currency: 'TZS',
153
+ phone: params[:phone],
154
+ provider: params[:provider],
155
+ reference: "ORD-#{SecureRandom.hex(6).upcase}",
156
+ description: params[:description]
157
+ )
158
+
159
+ if result['success']
160
+ render json: { status: 'initiated', reference: result['reference'] }
161
+ else
162
+ render json: { error: result['message'] }, status: :unprocessable_entity
163
+ end
164
+ rescue MaliPoPay::Error => e
165
+ render json: { error: e.message }, status: :service_unavailable
166
+ end
167
+ end
168
+ ```
169
+
170
+ ### Rails Encrypted Credentials
171
+
172
+ Store your API keys securely with Rails credentials:
173
+
174
+ ```bash
175
+ EDITOR=vim rails credentials:edit
176
+ ```
177
+
178
+ Add your MaliPoPay keys:
179
+
180
+ ```yaml
181
+ malipopay:
182
+ api_key: your_production_api_key
183
+ webhook_secret: your_webhook_secret
184
+ ```
185
+
186
+ Access them in your initializer:
187
+
188
+ ```ruby
189
+ Rails.application.credentials.dig(:malipopay, :api_key)
190
+ ```
191
+
192
+ ### Per-Environment Credentials
193
+
194
+ Use Rails environment-specific credentials:
195
+
196
+ ```bash
197
+ EDITOR=vim rails credentials:edit --environment development
198
+ ```
199
+
200
+ ```yaml
201
+ # config/credentials/development.yml.enc
202
+ malipopay:
203
+ api_key: your_uat_api_key
204
+ webhook_secret: your_uat_webhook_secret
205
+ ```
206
+
207
+ ```bash
208
+ EDITOR=vim rails credentials:edit --environment production
209
+ ```
210
+
211
+ ```yaml
212
+ # config/credentials/production.yml.enc
213
+ malipopay:
214
+ api_key: your_production_api_key
215
+ webhook_secret: your_production_webhook_secret
216
+ ```
217
+
218
+ ## Sinatra Integration
219
+
220
+ For Sinatra apps, configure the client at the top of your app file:
221
+
222
+ ```ruby
223
+ require 'sinatra'
224
+ require 'malipopay'
225
+
226
+ configure do
227
+ set :malipopay, MaliPoPay::Client.new(
228
+ api_key: ENV.fetch('MALIPOPAY_API_KEY'),
229
+ environment: settings.production? ? :production : :uat
230
+ )
231
+ end
232
+
233
+ post '/payments' do
234
+ result = settings.malipopay.payments.collect(
235
+ amount: params[:amount].to_i,
236
+ currency: 'TZS',
237
+ phone: params[:phone],
238
+ provider: 'M-Pesa',
239
+ reference: "ORD-#{SecureRandom.hex(6).upcase}",
240
+ description: 'Payment'
241
+ )
242
+
243
+ json result
244
+ end
245
+ ```
246
+
247
+ ## Timeout and Retry Settings
248
+
249
+ ### Timeout
250
+
251
+ The `timeout` option controls how long the SDK waits for an API response before raising `MaliPoPay::ConnectionError`:
252
+
253
+ ```ruby
254
+ # Short timeout for fast-fail scenarios
255
+ client = MaliPoPay::Client.new(api_key: key, timeout: 10)
256
+
257
+ # Longer timeout for slow networks or large batch operations
258
+ client = MaliPoPay::Client.new(api_key: key, timeout: 120)
259
+ ```
260
+
261
+ ### Retries
262
+
263
+ The `retries` option controls how many times the SDK retries on transient errors (5xx and connection failures):
264
+
265
+ ```ruby
266
+ # No automatic retries (handle retries yourself)
267
+ client = MaliPoPay::Client.new(api_key: key, retries: 0)
268
+
269
+ # Aggressive retries for critical operations
270
+ client = MaliPoPay::Client.new(api_key: key, retries: 5)
271
+ ```
272
+
273
+ The SDK uses exponential backoff between retries (1s, 2s, 4s, ...).
274
+
275
+ ## Security Best Practices
276
+
277
+ 1. **Never hardcode API keys.** Use environment variables or Rails encrypted credentials.
278
+
279
+ 2. **Use `.env` files for local development** (with [dotenv](https://github.com/bkeepers/dotenv)):
280
+ ```ruby
281
+ # Gemfile
282
+ gem 'dotenv-rails', groups: [:development, :test]
283
+ ```
284
+
285
+ ```bash
286
+ # .env (add to .gitignore!)
287
+ MALIPOPAY_API_KEY=your_uat_key
288
+ MALIPOPAY_WEBHOOK_SECRET=your_webhook_secret
289
+ ```
290
+
291
+ 3. **Rotate keys regularly.** Generate new keys at [app.malipopay.co.tz](https://app.malipopay.co.tz) and update your credentials store.
292
+
293
+ 4. **Use separate keys per environment.** Never share keys between UAT and production.
294
+
295
+ ## Next Steps
296
+
297
+ - [Getting Started](./getting-started.md) -- quick start guide
298
+ - [Error Handling](./error-handling.md) -- handle errors and configure retries
299
+ - [Webhooks](./webhooks.md) -- webhook configuration
300
+ - [Payments](./payments.md) -- payment operations