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 +7 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +312 -0
- data/Rakefile +7 -0
- data/docs/configuration.md +300 -0
- data/docs/customers.md +133 -0
- data/docs/error-handling.md +274 -0
- data/docs/getting-started.md +160 -0
- data/docs/invoices.md +200 -0
- data/docs/payments.md +284 -0
- data/docs/sms.md +182 -0
- data/docs/webhooks.md +219 -0
- data/lib/malipopay/client.rb +76 -0
- data/lib/malipopay/errors.rb +49 -0
- data/lib/malipopay/http_client.rb +151 -0
- data/lib/malipopay/resources/account.rb +60 -0
- data/lib/malipopay/resources/customers.rb +60 -0
- data/lib/malipopay/resources/invoices.rb +61 -0
- data/lib/malipopay/resources/payments.rb +88 -0
- data/lib/malipopay/resources/products.rb +47 -0
- data/lib/malipopay/resources/references.rb +46 -0
- data/lib/malipopay/resources/sms.rb +32 -0
- data/lib/malipopay/resources/transactions.rb +58 -0
- data/lib/malipopay/version.rb +5 -0
- data/lib/malipopay/webhooks/verifier.rb +69 -0
- data/lib/malipopay.rb +29 -0
- data/malipopay.gemspec +42 -0
- metadata +174 -0
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
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,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
|