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
data/docs/customers.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Customers
|
|
2
|
+
|
|
3
|
+
The `customers` resource lets you create, retrieve, search, and verify customer records. Customers are linked to payments, invoices, and transaction history in MaliPoPay.
|
|
4
|
+
|
|
5
|
+
## Create a Customer
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
customer = client.customers.create(
|
|
9
|
+
name: 'Juma Bakari',
|
|
10
|
+
phone: '255712345678',
|
|
11
|
+
email: 'juma.bakari@example.com',
|
|
12
|
+
address: 'Plot 45, Samora Avenue, Dar es Salaam',
|
|
13
|
+
customer_type: 'individual',
|
|
14
|
+
notes: 'Preferred payment method: M-Pesa'
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if customer['success']
|
|
18
|
+
puts "Customer created: #{customer['data']['id']}"
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Create a Business Customer
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
business = client.customers.create(
|
|
26
|
+
name: 'Kilimanjaro Trading Co.',
|
|
27
|
+
phone: '255222123456',
|
|
28
|
+
email: 'accounts@kilitrade.co.tz',
|
|
29
|
+
address: 'Industrial Area, Arusha',
|
|
30
|
+
customer_type: 'business',
|
|
31
|
+
tin: '123-456-789',
|
|
32
|
+
notes: 'Net 30 payment terms'
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## List All Customers
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
customers = client.customers.list
|
|
40
|
+
|
|
41
|
+
if customers['success']
|
|
42
|
+
customers['data'].each do |c|
|
|
43
|
+
puts "#{c['name']} (#{c['phone']})"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Get a Customer by ID
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
customer = client.customers.get('cust_abc123')
|
|
52
|
+
|
|
53
|
+
if customer['success']
|
|
54
|
+
puts "Name: #{customer['data']['name']}"
|
|
55
|
+
puts "Phone: #{customer['data']['phone']}"
|
|
56
|
+
puts "Email: #{customer['data']['email']}"
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Get a Customer by Phone Number
|
|
61
|
+
|
|
62
|
+
Look up a customer using their phone number:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
customer = client.customers.get_by_phone('255712345678')
|
|
66
|
+
|
|
67
|
+
if customer['success']
|
|
68
|
+
puts "Found: #{customer['data']['name']}"
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Get a Customer by Customer Number
|
|
73
|
+
|
|
74
|
+
Look up using the MaliPoPay-assigned customer number:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
customer = client.customers.get_by_number('CUST-2024-001')
|
|
78
|
+
|
|
79
|
+
if customer['success']
|
|
80
|
+
puts "Found: #{customer['data']['name']}"
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Search Customers
|
|
85
|
+
|
|
86
|
+
Search by name, phone, email, or other fields:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
results = client.customers.search
|
|
90
|
+
|
|
91
|
+
if results['success']
|
|
92
|
+
puts "Found #{results['data'].length} customers"
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Verify a Customer
|
|
97
|
+
|
|
98
|
+
Customer verification is useful for KYC (Know Your Customer) compliance. This checks the customer's identity against the phone number or ID document registered with their mobile money provider:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
verification = client.customers.verify(
|
|
102
|
+
phone: '255712345678',
|
|
103
|
+
provider: 'M-Pesa'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if verification['success']
|
|
107
|
+
puts "Verified: #{verification['data']}"
|
|
108
|
+
else
|
|
109
|
+
puts "Verification failed: #{verification['message']}"
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Error Handling
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
begin
|
|
117
|
+
customer = client.customers.create(
|
|
118
|
+
name: 'Incomplete Customer'
|
|
119
|
+
# missing phone -- will trigger validation error
|
|
120
|
+
)
|
|
121
|
+
rescue MaliPoPay::ValidationError => e
|
|
122
|
+
puts "Missing required fields: #{e.message}"
|
|
123
|
+
rescue MaliPoPay::NotFoundError
|
|
124
|
+
puts 'Customer not found.'
|
|
125
|
+
rescue MaliPoPay::Error => e
|
|
126
|
+
puts "Error: #{e.message}"
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Next Steps
|
|
131
|
+
|
|
132
|
+
- [Invoices](./invoices.md) -- create invoices for your customers
|
|
133
|
+
- [Payments](./payments.md) -- collect payments from customers
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
The MaliPoPay Ruby SDK uses a structured exception hierarchy so you can rescue specific error types and respond appropriately. All exceptions inherit from `MaliPoPay::Error`.
|
|
4
|
+
|
|
5
|
+
## Exception Hierarchy
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
MaliPoPay::Error (base)
|
|
9
|
+
├── MaliPoPay::AuthenticationError (HTTP 401 -- invalid or missing API key)
|
|
10
|
+
├── MaliPoPay::PermissionError (HTTP 403 -- insufficient permissions)
|
|
11
|
+
├── MaliPoPay::NotFoundError (HTTP 404 -- resource does not exist)
|
|
12
|
+
├── MaliPoPay::ValidationError (HTTP 422 -- invalid request parameters)
|
|
13
|
+
├── MaliPoPay::RateLimitError (HTTP 429 -- too many requests)
|
|
14
|
+
├── MaliPoPay::ApiError (HTTP 5xx -- server-side error)
|
|
15
|
+
└── MaliPoPay::ConnectionError (network timeout, DNS failure, etc.)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Rescuing Specific Exceptions
|
|
19
|
+
|
|
20
|
+
### Ordered by Specificity
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
begin
|
|
24
|
+
result = client.payments.collect(
|
|
25
|
+
amount: 50_000,
|
|
26
|
+
currency: 'TZS',
|
|
27
|
+
phone: '255712345678',
|
|
28
|
+
provider: 'M-Pesa',
|
|
29
|
+
reference: 'ORD-2024-100',
|
|
30
|
+
description: 'Monthly subscription'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
puts "Collection initiated: #{result['reference']}"
|
|
34
|
+
|
|
35
|
+
rescue MaliPoPay::AuthenticationError
|
|
36
|
+
# API key is invalid or expired
|
|
37
|
+
puts 'Authentication failed. Rotate your API key at app.malipopay.co.tz'
|
|
38
|
+
|
|
39
|
+
rescue MaliPoPay::PermissionError
|
|
40
|
+
# API key lacks permission for this operation
|
|
41
|
+
puts 'Insufficient permissions. Check your API key scopes.'
|
|
42
|
+
|
|
43
|
+
rescue MaliPoPay::ValidationError => e
|
|
44
|
+
# The request had invalid fields
|
|
45
|
+
puts "Invalid request: #{e.message}"
|
|
46
|
+
# e.message might say: "phone must be a valid Tanzanian number (255xxxxxxxxx)"
|
|
47
|
+
|
|
48
|
+
rescue MaliPoPay::NotFoundError
|
|
49
|
+
puts 'The requested resource was not found.'
|
|
50
|
+
|
|
51
|
+
rescue MaliPoPay::RateLimitError
|
|
52
|
+
puts 'Too many requests. Back off and retry.'
|
|
53
|
+
|
|
54
|
+
rescue MaliPoPay::ApiError => e
|
|
55
|
+
# MaliPoPay server error -- transient, safe to retry
|
|
56
|
+
puts "Server error (#{e.message}). Retrying..."
|
|
57
|
+
|
|
58
|
+
rescue MaliPoPay::ConnectionError => e
|
|
59
|
+
# Network-level failure
|
|
60
|
+
puts "Connection failed: #{e.message}"
|
|
61
|
+
|
|
62
|
+
rescue MaliPoPay::Error => e
|
|
63
|
+
# Catch-all for any other SDK error
|
|
64
|
+
puts "Unexpected error: #{e.message}"
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Exception Properties
|
|
69
|
+
|
|
70
|
+
Every `MaliPoPay::Error` includes:
|
|
71
|
+
|
|
72
|
+
| Property | Type | Description |
|
|
73
|
+
|----------|------|-------------|
|
|
74
|
+
| `message` | `String` | Human-readable error description |
|
|
75
|
+
| `status_code` | `Integer` or `nil` | HTTP status code (`nil` for `ConnectionError`) |
|
|
76
|
+
|
|
77
|
+
## Retry Strategies
|
|
78
|
+
|
|
79
|
+
The SDK automatically retries transient errors (5xx, connection timeouts) based on the `retries` option. You can also implement your own retry logic for specific cases.
|
|
80
|
+
|
|
81
|
+
### Built-in Retries
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
client = MaliPoPay::Client.new(
|
|
85
|
+
api_key: ENV['MALIPOPAY_API_KEY'],
|
|
86
|
+
retries: 3 # retry up to 3 times on transient failures (default: 2)
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The SDK uses exponential backoff between retries. It will only retry on:
|
|
91
|
+
- `MaliPoPay::ApiError` (5xx responses)
|
|
92
|
+
- `MaliPoPay::ConnectionError` (network timeouts and DNS failures)
|
|
93
|
+
|
|
94
|
+
It will **not** retry on:
|
|
95
|
+
- `AuthenticationError` (fix your API key)
|
|
96
|
+
- `PermissionError` (check your API key scopes)
|
|
97
|
+
- `ValidationError` (fix your request)
|
|
98
|
+
- `NotFoundError` (the resource doesn't exist)
|
|
99
|
+
- `RateLimitError` (handled separately -- see below)
|
|
100
|
+
|
|
101
|
+
### Custom Retry for Rate Limits
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
def with_rate_limit_retry(max_retries: 3)
|
|
105
|
+
attempts = 0
|
|
106
|
+
|
|
107
|
+
begin
|
|
108
|
+
yield
|
|
109
|
+
rescue MaliPoPay::RateLimitError
|
|
110
|
+
attempts += 1
|
|
111
|
+
raise if attempts > max_retries
|
|
112
|
+
|
|
113
|
+
delay = 2**attempts # exponential backoff: 2s, 4s, 8s
|
|
114
|
+
puts "Rate limited. Retrying in #{delay}s..."
|
|
115
|
+
sleep delay
|
|
116
|
+
retry
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Usage
|
|
121
|
+
result = with_rate_limit_retry do
|
|
122
|
+
client.payments.collect(
|
|
123
|
+
amount: 75_000,
|
|
124
|
+
currency: 'TZS',
|
|
125
|
+
phone: '255712345678',
|
|
126
|
+
provider: 'M-Pesa',
|
|
127
|
+
reference: 'ORD-2024-200',
|
|
128
|
+
description: 'Retry example'
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Generic Retry Helper
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
def with_retry(max_retries: 3, on: [MaliPoPay::ApiError, MaliPoPay::ConnectionError])
|
|
137
|
+
attempts = 0
|
|
138
|
+
|
|
139
|
+
begin
|
|
140
|
+
yield
|
|
141
|
+
rescue *on => e
|
|
142
|
+
attempts += 1
|
|
143
|
+
raise if attempts > max_retries
|
|
144
|
+
|
|
145
|
+
delay = 2**attempts
|
|
146
|
+
puts "#{e.class}: #{e.message}. Retry #{attempts}/#{max_retries} in #{delay}s..."
|
|
147
|
+
sleep delay
|
|
148
|
+
retry
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Usage
|
|
153
|
+
result = with_retry(max_retries: 5) do
|
|
154
|
+
client.payments.disburse(
|
|
155
|
+
amount: 250_000,
|
|
156
|
+
currency: 'TZS',
|
|
157
|
+
phone: '255754321098',
|
|
158
|
+
provider: 'Airtel Money',
|
|
159
|
+
reference: 'PAY-2024-055',
|
|
160
|
+
description: 'Supplier payment'
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Common Errors and Solutions
|
|
166
|
+
|
|
167
|
+
### AuthenticationError (401)
|
|
168
|
+
|
|
169
|
+
| Error | Cause | Solution |
|
|
170
|
+
|-------|-------|----------|
|
|
171
|
+
| "Invalid API key" | The `apiToken` header is wrong or missing | Verify your key at [app.malipopay.co.tz](https://app.malipopay.co.tz) under Settings > API Keys |
|
|
172
|
+
| "API key expired" | Key was revoked or rotated | Generate a new key in the dashboard |
|
|
173
|
+
| "Unauthorized environment" | Using a production key on UAT or vice versa | Use the correct key for your environment |
|
|
174
|
+
|
|
175
|
+
### PermissionError (403)
|
|
176
|
+
|
|
177
|
+
| Error | Cause | Solution |
|
|
178
|
+
|-------|-------|----------|
|
|
179
|
+
| "Insufficient permissions" | API key lacks required scope | Check key permissions in the dashboard |
|
|
180
|
+
|
|
181
|
+
### ValidationError (422)
|
|
182
|
+
|
|
183
|
+
| Error | Cause | Solution |
|
|
184
|
+
|-------|-------|----------|
|
|
185
|
+
| "phone must be a valid Tanzanian number" | Phone number not in `255xxxxxxxxx` format | Use the full international format: `255712345678` |
|
|
186
|
+
| "amount must be greater than 0" | Zero or negative amount | Provide a positive integer amount in TZS |
|
|
187
|
+
| "provider is required" | Missing `provider` field | Specify one of: `M-Pesa`, `Airtel Money`, `Mixx`, `Halopesa`, `T-Pesa`, `CRDB`, `NMB` |
|
|
188
|
+
| "reference must be unique" | Duplicate reference string | Generate a unique reference per transaction |
|
|
189
|
+
| "currency must be TZS" | Unsupported currency | MaliPoPay currently supports TZS only |
|
|
190
|
+
|
|
191
|
+
### NotFoundError (404)
|
|
192
|
+
|
|
193
|
+
| Error | Cause | Solution |
|
|
194
|
+
|-------|-------|----------|
|
|
195
|
+
| "Payment not found" | Reference doesn't match any payment | Double-check the reference string |
|
|
196
|
+
| "Customer not found" | Customer ID doesn't exist | Create the customer first or verify the ID |
|
|
197
|
+
| "Invoice not found" | Invoice ID doesn't exist | Check the invoice ID from your records |
|
|
198
|
+
|
|
199
|
+
### RateLimitError (429)
|
|
200
|
+
|
|
201
|
+
| Error | Cause | Solution |
|
|
202
|
+
|-------|-------|----------|
|
|
203
|
+
| "Rate limit exceeded" | Too many API calls in a short period | Implement exponential backoff; batch operations where possible |
|
|
204
|
+
|
|
205
|
+
### ApiError (5xx)
|
|
206
|
+
|
|
207
|
+
| Error | Cause | Solution |
|
|
208
|
+
|-------|-------|----------|
|
|
209
|
+
| "Internal server error" | Temporary server issue | Retry after a short delay; these are transient |
|
|
210
|
+
| "Service unavailable" | Maintenance or provider downtime | Check [status.malipopay.co.tz](https://status.malipopay.co.tz) for updates |
|
|
211
|
+
|
|
212
|
+
### ConnectionError
|
|
213
|
+
|
|
214
|
+
| Error | Cause | Solution |
|
|
215
|
+
|-------|-------|----------|
|
|
216
|
+
| "Request timed out" | Network latency or server unresponsive | Increase `timeout` in client options; check network connectivity |
|
|
217
|
+
| "DNS resolution failed" | Cannot resolve the API hostname | Verify your DNS settings and internet connection |
|
|
218
|
+
|
|
219
|
+
## Logging Errors
|
|
220
|
+
|
|
221
|
+
Use Ruby's Logger for production error tracking:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
require 'logger'
|
|
225
|
+
|
|
226
|
+
logger = Logger.new($stdout)
|
|
227
|
+
|
|
228
|
+
begin
|
|
229
|
+
result = client.payments.collect(
|
|
230
|
+
amount: 30_000,
|
|
231
|
+
currency: 'TZS',
|
|
232
|
+
phone: '255622345678',
|
|
233
|
+
provider: 'Halopesa',
|
|
234
|
+
reference: 'ORD-2024-300',
|
|
235
|
+
description: 'Logging example'
|
|
236
|
+
)
|
|
237
|
+
rescue MaliPoPay::Error => e
|
|
238
|
+
logger.error("MaliPoPay API error: status=#{e.status_code} message=#{e.message}")
|
|
239
|
+
raise
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Rails Integration
|
|
244
|
+
|
|
245
|
+
In Rails, errors are automatically logged. You can add custom handling in an initializer or concern:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# app/controllers/concerns/malipopay_error_handling.rb
|
|
249
|
+
module MalipopayErrorHandling
|
|
250
|
+
extend ActiveSupport::Concern
|
|
251
|
+
|
|
252
|
+
included do
|
|
253
|
+
rescue_from MaliPoPay::AuthenticationError do |e|
|
|
254
|
+
Rails.logger.error("MaliPoPay auth error: #{e.message}")
|
|
255
|
+
render json: { error: 'Payment service authentication failed' }, status: :service_unavailable
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
rescue_from MaliPoPay::ValidationError do |e|
|
|
259
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
rescue_from MaliPoPay::Error do |e|
|
|
263
|
+
Rails.logger.error("MaliPoPay error: #{e.class} - #{e.message}")
|
|
264
|
+
render json: { error: 'Payment service error' }, status: :service_unavailable
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Next Steps
|
|
271
|
+
|
|
272
|
+
- [Configuration](./configuration.md) -- configure retries and timeouts at the client level
|
|
273
|
+
- [Payments](./payments.md) -- payment operations that may raise these exceptions
|
|
274
|
+
- [Webhooks](./webhooks.md) -- webhook signature verification errors
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Getting Started with MaliPoPay Ruby SDK
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- **Ruby 3.0** or later
|
|
6
|
+
- **Bundler** (included with Ruby)
|
|
7
|
+
- A MaliPoPay merchant account with API credentials
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add MaliPoPay to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'malipopay'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then run:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install it directly:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem install malipopay
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Getting Your API Key
|
|
30
|
+
|
|
31
|
+
1. Sign in to your merchant dashboard at [app.malipopay.co.tz](https://app.malipopay.co.tz)
|
|
32
|
+
2. Navigate to **Settings > API Keys**
|
|
33
|
+
3. Click **Generate New Key**
|
|
34
|
+
4. Copy the API key immediately -- it will only be shown once
|
|
35
|
+
5. Store it securely (environment variable, credentials file, etc.)
|
|
36
|
+
|
|
37
|
+
> **Important:** Never commit API keys to source control. Use environment variables or Rails encrypted credentials.
|
|
38
|
+
|
|
39
|
+
## Your First Payment Collection
|
|
40
|
+
|
|
41
|
+
Collect TZS 50,000 from an M-Pesa customer in five lines:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
require 'malipopay'
|
|
45
|
+
|
|
46
|
+
client = MaliPoPay::Client.new(api_key: ENV['MALIPOPAY_API_KEY'])
|
|
47
|
+
|
|
48
|
+
result = client.payments.collect(
|
|
49
|
+
amount: 50_000,
|
|
50
|
+
currency: 'TZS',
|
|
51
|
+
phone: '255712345678',
|
|
52
|
+
provider: 'M-Pesa',
|
|
53
|
+
reference: 'ORDER-2024-001',
|
|
54
|
+
description: 'Payment for office supplies'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
puts "Collection initiated: #{result['reference']}" if result['success']
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
When this runs, the customer at `255712345678` receives a USSD push prompt on their phone asking them to confirm the TZS 50,000 payment with their M-Pesa PIN.
|
|
61
|
+
|
|
62
|
+
## Environment Selection
|
|
63
|
+
|
|
64
|
+
MaliPoPay provides two environments:
|
|
65
|
+
|
|
66
|
+
| Environment | Base URL | Purpose |
|
|
67
|
+
|-------------|----------|---------|
|
|
68
|
+
| **Production** | `https://core-prod.malipopay.co.tz` | Live transactions with real money |
|
|
69
|
+
| **UAT** | `https://core-uat.malipopay.co.tz` | Testing and integration development |
|
|
70
|
+
|
|
71
|
+
### Using UAT for Testing
|
|
72
|
+
|
|
73
|
+
Always develop and test against UAT before going live:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
client = MaliPoPay::Client.new(
|
|
77
|
+
api_key: ENV['MALIPOPAY_UAT_API_KEY'],
|
|
78
|
+
environment: :uat
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Custom Base URL
|
|
83
|
+
|
|
84
|
+
For advanced setups (proxies, custom routing), you can override the base URL:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
client = MaliPoPay::Client.new(
|
|
88
|
+
api_key: ENV['MALIPOPAY_API_KEY'],
|
|
89
|
+
base_url: 'https://custom-proxy.example.com'
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
When `base_url` is set, it takes precedence over the `environment` setting.
|
|
94
|
+
|
|
95
|
+
## Configuring Timeouts and Retries
|
|
96
|
+
|
|
97
|
+
The SDK automatically retries transient failures. You can adjust timeout and retry behavior:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
client = MaliPoPay::Client.new(
|
|
101
|
+
api_key: ENV['MALIPOPAY_API_KEY'],
|
|
102
|
+
environment: :production,
|
|
103
|
+
timeout: 60, # seconds (default: 30)
|
|
104
|
+
retries: 3 # automatic retries (default: 2)
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Complete Minimal Example
|
|
109
|
+
|
|
110
|
+
A full script that collects a payment and verifies it:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
require 'malipopay'
|
|
114
|
+
|
|
115
|
+
api_key = ENV.fetch('MALIPOPAY_API_KEY') { raise 'Set the MALIPOPAY_API_KEY environment variable' }
|
|
116
|
+
|
|
117
|
+
client = MaliPoPay::Client.new(
|
|
118
|
+
api_key: api_key,
|
|
119
|
+
environment: :uat
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
begin
|
|
123
|
+
reference = "ORD-#{Time.now.strftime('%Y%m%d%H%M%S')}"
|
|
124
|
+
|
|
125
|
+
# Step 1: Initiate collection
|
|
126
|
+
collection = client.payments.collect(
|
|
127
|
+
amount: 15_000,
|
|
128
|
+
currency: 'TZS',
|
|
129
|
+
phone: '255754321098',
|
|
130
|
+
provider: 'Airtel Money',
|
|
131
|
+
reference: reference,
|
|
132
|
+
description: 'Monthly subscription'
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
puts "Collection initiated. Reference: #{reference}"
|
|
136
|
+
|
|
137
|
+
# Step 2: Wait for the customer to approve on their phone
|
|
138
|
+
# In production, use webhooks instead of polling
|
|
139
|
+
sleep 30
|
|
140
|
+
|
|
141
|
+
# Step 3: Verify the payment
|
|
142
|
+
verification = client.payments.verify(reference)
|
|
143
|
+
|
|
144
|
+
puts "Payment status: #{verification['status']}"
|
|
145
|
+
|
|
146
|
+
rescue MaliPoPay::AuthenticationError
|
|
147
|
+
puts 'Invalid API key. Check your credentials.'
|
|
148
|
+
rescue MaliPoPay::ValidationError => e
|
|
149
|
+
puts "Invalid request: #{e.message}"
|
|
150
|
+
rescue MaliPoPay::Error => e
|
|
151
|
+
puts "Payment error: #{e.message}"
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Next Steps
|
|
156
|
+
|
|
157
|
+
- [Payments Guide](./payments.md) -- all payment operations in detail
|
|
158
|
+
- [Webhooks](./webhooks.md) -- receive real-time payment notifications
|
|
159
|
+
- [Configuration](./configuration.md) -- advanced client setup
|
|
160
|
+
- [Error Handling](./error-handling.md) -- handling failures gracefully
|