paystack_sdk 0.0.5 → 0.0.6
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/.standard.yml +2 -2
- data/CHANGELOG.md +85 -2
- data/README.md +250 -0
- data/lib/paystack_sdk/client.rb +15 -0
- data/lib/paystack_sdk/resources/customers.rb +192 -0
- data/lib/paystack_sdk/response.rb +88 -19
- data/lib/paystack_sdk/utils/connection_utils.rb +1 -1
- data/lib/paystack_sdk/validations.rb +72 -33
- data/lib/paystack_sdk/version.rb +1 -1
- data/lib/paystack_sdk.rb +82 -0
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4ff6401efb6a0aba25394b61aa74e1a2645848c015565f3590ffdc10f504748
|
4
|
+
data.tar.gz: e59175c14b106866b7129e5cb640ec9a64feebb5a2ddce12940e0436e8dc88af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 964956e0a22d4ea337ac49896a865d43a0d6ba6ac2f2cc307a49f120b0b407cb4dedf80c6f3b5ed8a227b89d283e5a9d2729210c3763d4f6236f373de5e9bc6b
|
7
|
+
data.tar.gz: e86ef5b41b26ee645f3f3b208cc945cb382d38d768ace5440af66fc0e82a3fac2e036902723021da0d896b90b4b1877b5f395f3ea2860e577010035624608de7
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,88 @@
|
|
1
|
-
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [0.0.6] - 2025-06-10
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Customers resource with full CRUD operations (create, list, fetch, update)
|
8
|
+
- Customer validation and risk action management features
|
9
|
+
- Customer authorization deactivation functionality
|
10
|
+
- Comprehensive documentation for Customers API in README
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
- Improved test consistency by standardizing response body format using string keys with hashrocket notation (`=>`) across all test specifications
|
15
|
+
- Enhanced error handling specificity in tests by using appropriate error classes (`InvalidValueError`, `InvalidFormatError`, `MissingParamError`) instead of generic `Error` class
|
16
|
+
|
17
|
+
### Improved
|
18
|
+
|
19
|
+
- Better test coverage and reliability with consistent response format handling
|
20
|
+
- More precise error validation ensuring proper exception types are raised for different validation failures
|
21
|
+
- Enhanced documentation with comprehensive examples for all customer operations
|
22
|
+
|
23
|
+
## [0.0.5] - 2025-05-15
|
24
|
+
|
25
|
+
### Added
|
26
|
+
|
27
|
+
- Connection utilities module for improved API connection handling and management
|
28
|
+
- Enhanced connection configuration and error handling capabilities
|
29
|
+
|
30
|
+
## [0.0.4] - 2025-05-15
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
- Enhanced transaction resource validations with comprehensive parameter checking
|
35
|
+
- New transaction methods: `charge_authorization` and `partial_debit`
|
36
|
+
- Flexible `list` method accepting additional parameters for advanced API requests
|
37
|
+
|
38
|
+
### Changed
|
39
|
+
|
40
|
+
- Updated SDK authentication to use `secret_key` instead of `api_key` for consistency with Paystack documentation
|
41
|
+
- Improved transaction method names for better clarity and consistency
|
42
|
+
- Enhanced README documentation with comprehensive usage examples
|
43
|
+
|
44
|
+
### Improved
|
45
|
+
|
46
|
+
- Better validation error messages and handling
|
47
|
+
- More robust parameter validation across all transaction methods
|
48
|
+
|
49
|
+
## [0.0.3] - 2025-05-13
|
50
|
+
|
51
|
+
### Changed
|
52
|
+
|
53
|
+
- Renamed `#initialize_transaction` method to `#initiate` for better clarity and consistency with Paystack API
|
54
|
+
- Updated .gitignore to exclude Gemfile.lock from version control
|
55
|
+
|
56
|
+
### Fixed
|
57
|
+
|
58
|
+
- Corrected typos in README documentation regarding original API response handling
|
59
|
+
|
60
|
+
## [0.0.2] - 2025-05-11
|
61
|
+
|
62
|
+
### Added
|
63
|
+
|
64
|
+
- Comprehensive Response class for Paystack API response handling with dynamic attribute access
|
65
|
+
- Enhanced response object capabilities with better data access patterns
|
66
|
+
- Improved debugging support for development
|
67
|
+
|
68
|
+
### Changed
|
69
|
+
|
70
|
+
- Refactored transaction specs to utilize PaystackSdk::Response for better consistency
|
71
|
+
- Removed redundant success? method in favor of centralized Response handling
|
72
|
+
- Enhanced documentation clarity on original API response handling
|
73
|
+
|
74
|
+
### Improved
|
75
|
+
|
76
|
+
- Better response handling architecture across all SDK components
|
77
|
+
- More intuitive API for accessing response data and metadata
|
2
78
|
|
3
79
|
## [0.0.1] - 2025-05-10
|
4
80
|
|
5
|
-
|
81
|
+
### Added
|
82
|
+
|
83
|
+
- Initial release of Paystack Ruby SDK
|
84
|
+
- Basic transaction operations (initiate, verify)
|
85
|
+
- Foundational SDK architecture with modular design
|
86
|
+
- Comprehensive error handling framework
|
87
|
+
- Basic client initialization and configuration
|
88
|
+
- Initial documentation and usage examples
|
data/README.md
CHANGED
@@ -14,6 +14,14 @@ The `paystack_sdk` gem provides a simple and intuitive interface for interacting
|
|
14
14
|
- [List Transactions](#list-transactions)
|
15
15
|
- [Fetch a Transaction](#fetch-a-transaction)
|
16
16
|
- [Get Transaction Totals](#get-transaction-totals)
|
17
|
+
- [Customers](#customers)
|
18
|
+
- [Create a Customer](#create-a-customer)
|
19
|
+
- [List Customers](#list-customers)
|
20
|
+
- [Fetch a Customer](#fetch-a-customer)
|
21
|
+
- [Update a Customer](#update-a-customer)
|
22
|
+
- [Validate a Customer](#validate-a-customer)
|
23
|
+
- [Set Risk Action](#set-risk-action)
|
24
|
+
- [Deactivate Authorization](#deactivate-authorization)
|
17
25
|
- [Response Handling](#response-handling)
|
18
26
|
- [Working with Response Objects](#working-with-response-objects)
|
19
27
|
- [Accessing the Original Response](#accessing-the-original-response)
|
@@ -65,11 +73,31 @@ response = paystack.transactions.initiate(params)
|
|
65
73
|
|
66
74
|
if response.success?
|
67
75
|
puts "Visit this URL to complete payment: #{response.authorization_url}"
|
76
|
+
puts "Transaction reference: #{response.reference}"
|
68
77
|
else
|
69
78
|
puts "Error: #{response.error_message}"
|
70
79
|
end
|
80
|
+
|
81
|
+
# Create a customer
|
82
|
+
customer_params = {
|
83
|
+
email: "customer@email.com",
|
84
|
+
first_name: "John",
|
85
|
+
last_name: "Doe"
|
86
|
+
}
|
87
|
+
|
88
|
+
customer_response = paystack.customers.create(customer_params)
|
89
|
+
|
90
|
+
if customer_response.success?
|
91
|
+
puts "Customer created: #{customer_response.data.customer_code}"
|
92
|
+
else
|
93
|
+
puts "Error: #{customer_response.error_message}"
|
94
|
+
end
|
71
95
|
```
|
72
96
|
|
97
|
+
### Response Format
|
98
|
+
|
99
|
+
The SDK handles API responses that use string keys (as returned by Paystack) and provides seamless access through both string and symbol notation. All response data maintains the original string key format from the API while offering convenient dot notation access.
|
100
|
+
|
73
101
|
## Usage
|
74
102
|
|
75
103
|
### Client Initialization
|
@@ -219,6 +247,165 @@ else
|
|
219
247
|
end
|
220
248
|
```
|
221
249
|
|
250
|
+
### Customers
|
251
|
+
|
252
|
+
The SDK provides comprehensive support for Paystack's Customer API, allowing you to manage customer records and their associated data.
|
253
|
+
|
254
|
+
#### Create a Customer
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
# Prepare customer parameters
|
258
|
+
params = {
|
259
|
+
email: "customer@example.com",
|
260
|
+
first_name: "John",
|
261
|
+
last_name: "Doe",
|
262
|
+
phone: "+2348123456789"
|
263
|
+
}
|
264
|
+
|
265
|
+
# Create the customer
|
266
|
+
response = paystack.customers.create(params)
|
267
|
+
|
268
|
+
if response.success?
|
269
|
+
puts "Customer created successfully!"
|
270
|
+
puts "Customer Code: #{response.data.customer_code}"
|
271
|
+
puts "Email: #{response.data.email}"
|
272
|
+
puts "Name: #{response.data.first_name} #{response.data.last_name}"
|
273
|
+
else
|
274
|
+
puts "Error: #{response.error_message}"
|
275
|
+
end
|
276
|
+
```
|
277
|
+
|
278
|
+
#### List Customers
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
# Get all customers (default pagination: 50 per page)
|
282
|
+
response = paystack.customers.list
|
283
|
+
|
284
|
+
# With custom pagination
|
285
|
+
response = paystack.customers.list(per_page: 20, page: 2)
|
286
|
+
|
287
|
+
# With date filters
|
288
|
+
response = paystack.customers.list(
|
289
|
+
per_page: 10,
|
290
|
+
page: 1,
|
291
|
+
from: "2025-01-01",
|
292
|
+
to: "2025-06-10"
|
293
|
+
)
|
294
|
+
|
295
|
+
if response.success?
|
296
|
+
puts "Total customers: #{response.data.size}"
|
297
|
+
|
298
|
+
response.data.each do |customer|
|
299
|
+
puts "Code: #{customer.customer_code}"
|
300
|
+
puts "Email: #{customer.email}"
|
301
|
+
puts "Name: #{customer.first_name} #{customer.last_name}"
|
302
|
+
puts "----------------"
|
303
|
+
end
|
304
|
+
else
|
305
|
+
puts "Error: #{response.error_message}"
|
306
|
+
end
|
307
|
+
```
|
308
|
+
|
309
|
+
#### Fetch a Customer
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
# Fetch by customer code
|
313
|
+
customer_code = "CUS_xr58yrr2ujlft9k"
|
314
|
+
response = paystack.customers.fetch(customer_code)
|
315
|
+
|
316
|
+
# Or fetch by email
|
317
|
+
response = paystack.customers.fetch("customer@example.com")
|
318
|
+
|
319
|
+
if response.success?
|
320
|
+
customer = response.data
|
321
|
+
puts "Customer details:"
|
322
|
+
puts "Code: #{customer.customer_code}"
|
323
|
+
puts "Email: #{customer.email}"
|
324
|
+
puts "Name: #{customer.first_name} #{customer.last_name}"
|
325
|
+
puts "Phone: #{customer.phone}"
|
326
|
+
else
|
327
|
+
puts "Error: #{response.error_message}"
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
#### Update a Customer
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
customer_code = "CUS_xr58yrr2ujlft9k"
|
335
|
+
update_params = {
|
336
|
+
first_name: "Jane",
|
337
|
+
last_name: "Smith",
|
338
|
+
phone: "+2348987654321"
|
339
|
+
}
|
340
|
+
|
341
|
+
response = paystack.customers.update(customer_code, update_params)
|
342
|
+
|
343
|
+
if response.success?
|
344
|
+
puts "Customer updated successfully!"
|
345
|
+
puts "Updated Name: #{response.data.first_name} #{response.data.last_name}"
|
346
|
+
else
|
347
|
+
puts "Error: #{response.error_message}"
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
351
|
+
#### Validate a Customer
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
customer_code = "CUS_xr58yrr2ujlft9k"
|
355
|
+
validation_params = {
|
356
|
+
country: "NG",
|
357
|
+
type: "bank_account",
|
358
|
+
account_number: "0123456789",
|
359
|
+
bvn: "20012345677",
|
360
|
+
bank_code: "007",
|
361
|
+
first_name: "John",
|
362
|
+
last_name: "Doe"
|
363
|
+
}
|
364
|
+
|
365
|
+
response = paystack.customers.validate(customer_code, validation_params)
|
366
|
+
|
367
|
+
if response.success?
|
368
|
+
puts "Customer validation initiated: #{response.message}"
|
369
|
+
else
|
370
|
+
puts "Error: #{response.error_message}"
|
371
|
+
end
|
372
|
+
```
|
373
|
+
|
374
|
+
#### Set Risk Action
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
params = {
|
378
|
+
customer: "CUS_xr58yrr2ujlft9k",
|
379
|
+
risk_action: "allow" # Options: "default", "allow", "deny"
|
380
|
+
}
|
381
|
+
|
382
|
+
response = paystack.customers.set_risk_action(params)
|
383
|
+
|
384
|
+
if response.success?
|
385
|
+
puts "Risk action set successfully!"
|
386
|
+
puts "Customer: #{response.data.customer_code}"
|
387
|
+
puts "Risk Action: #{response.data.risk_action}"
|
388
|
+
else
|
389
|
+
puts "Error: #{response.error_message}"
|
390
|
+
end
|
391
|
+
```
|
392
|
+
|
393
|
+
#### Deactivate Authorization
|
394
|
+
|
395
|
+
```ruby
|
396
|
+
params = {
|
397
|
+
authorization_code: "AUTH_72btv547"
|
398
|
+
}
|
399
|
+
|
400
|
+
response = paystack.customers.deactivate_authorization(params)
|
401
|
+
|
402
|
+
if response.success?
|
403
|
+
puts "Authorization deactivated: #{response.message}"
|
404
|
+
else
|
405
|
+
puts "Error: #{response.error_message}"
|
406
|
+
end
|
407
|
+
```
|
408
|
+
|
222
409
|
### Response Handling
|
223
410
|
|
224
411
|
#### Working with Response Objects
|
@@ -272,7 +459,24 @@ current_page = original.dig("meta", "page")
|
|
272
459
|
|
273
460
|
#### Error Handling
|
274
461
|
|
462
|
+
The SDK provides specific error classes for different types of failures, making it easier to handle errors appropriately:
|
463
|
+
|
275
464
|
```ruby
|
465
|
+
begin
|
466
|
+
response = paystack.transactions.verify(reference: "invalid_reference")
|
467
|
+
rescue PaystackSdk::ResourceNotFoundError => e
|
468
|
+
puts "Resource not found: #{e.message}"
|
469
|
+
rescue PaystackSdk::AuthenticationError => e
|
470
|
+
puts "Authentication failed: #{e.message}"
|
471
|
+
rescue PaystackSdk::ValidationError => e
|
472
|
+
puts "Validation error: #{e.message}"
|
473
|
+
rescue PaystackSdk::APIError => e
|
474
|
+
puts "API error: #{e.message}"
|
475
|
+
rescue PaystackSdk::Error => e
|
476
|
+
puts "General error: #{e.message}"
|
477
|
+
end
|
478
|
+
|
479
|
+
# Alternatively, check response success without exceptions
|
276
480
|
response = paystack.transactions.verify(reference: "invalid_reference")
|
277
481
|
|
278
482
|
unless response.success?
|
@@ -287,6 +491,22 @@ unless response.success?
|
|
287
491
|
end
|
288
492
|
```
|
289
493
|
|
494
|
+
##### Error Types
|
495
|
+
|
496
|
+
The SDK includes several specific error classes:
|
497
|
+
|
498
|
+
- **`PaystackSdk::ValidationError`** - Base class for all validation errors
|
499
|
+
|
500
|
+
- **`PaystackSdk::MissingParamError`** - Raised when required parameters are missing
|
501
|
+
- **`PaystackSdk::InvalidFormatError`** - Raised when parameters have invalid format (e.g., invalid email)
|
502
|
+
- **`PaystackSdk::InvalidValueError`** - Raised when parameters have invalid values (e.g., not in allowed list)
|
503
|
+
|
504
|
+
- **`PaystackSdk::APIError`** - Base class for API-related errors
|
505
|
+
- **`PaystackSdk::AuthenticationError`** - Authentication failures
|
506
|
+
- **`PaystackSdk::ResourceNotFoundError`** - Resource not found (404 errors)
|
507
|
+
- **`PaystackSdk::RateLimitError`** - Rate limiting encountered
|
508
|
+
- **`PaystackSdk::ServerError`** - Server errors (5xx responses)
|
509
|
+
|
290
510
|
## Advanced Usage
|
291
511
|
|
292
512
|
### Environment Variables
|
@@ -299,6 +519,7 @@ ENV["PAYSTACK_SECRET_KEY"] = "sk_test_xxx"
|
|
299
519
|
|
300
520
|
# Then initialize resources without specifying the key
|
301
521
|
transactions = PaystackSdk::Resources::Transactions.new
|
522
|
+
customers = PaystackSdk::Resources::Customers.new
|
302
523
|
```
|
303
524
|
|
304
525
|
### Direct Resource Instantiation
|
@@ -308,6 +529,7 @@ For more advanced usage, you can instantiate resource classes directly:
|
|
308
529
|
```ruby
|
309
530
|
# With a secret key
|
310
531
|
transactions = PaystackSdk::Resources::Transactions.new(secret_key: "sk_test_xxx")
|
532
|
+
customers = PaystackSdk::Resources::Customers.new(secret_key: "sk_test_xxx")
|
311
533
|
|
312
534
|
# With an existing Faraday connection
|
313
535
|
connection = Faraday.new(url: "https://api.paystack.co") do |conn|
|
@@ -316,6 +538,7 @@ end
|
|
316
538
|
|
317
539
|
# The secret key can be omitted if set in an environment
|
318
540
|
transactions = PaystackSdk::Resources::Transactions.new(connection, secret_key:)
|
541
|
+
customers = PaystackSdk::Resources::Customers.new(connection, secret_key:)
|
319
542
|
```
|
320
543
|
|
321
544
|
For more detailed documentation on specific resources, please refer to the following guides:
|
@@ -329,6 +552,33 @@ For more detailed documentation on specific resources, please refer to the follo
|
|
329
552
|
|
330
553
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
331
554
|
|
555
|
+
### Testing
|
556
|
+
|
557
|
+
The SDK includes comprehensive test coverage with consistent response format handling. All test specifications use string keys with hashrocket notation (`=>`) to match the actual format returned by the Paystack API:
|
558
|
+
|
559
|
+
```ruby
|
560
|
+
# Example test response format
|
561
|
+
.and_return(Faraday::Response.new(status: 200, body: {
|
562
|
+
"status" => true,
|
563
|
+
"message" => "Transaction initialized",
|
564
|
+
"data" => {
|
565
|
+
"authorization_url" => "https://checkout.paystack.com/abc123",
|
566
|
+
"access_code" => "access_code_123",
|
567
|
+
"reference" => "ref_123"
|
568
|
+
}
|
569
|
+
}))
|
570
|
+
```
|
571
|
+
|
572
|
+
Tests also validate specific error types to ensure proper exception handling:
|
573
|
+
|
574
|
+
```ruby
|
575
|
+
# Testing specific error types
|
576
|
+
expect { customers.set_risk_action(invalid_params) }
|
577
|
+
.to raise_error(PaystackSdk::InvalidValueError, /risk_action/i)
|
578
|
+
```
|
579
|
+
|
580
|
+
### Installation and Release
|
581
|
+
|
332
582
|
To install this gem onto your local machine, run:
|
333
583
|
|
334
584
|
```bash
|
data/lib/paystack_sdk/client.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "resources/transactions"
|
4
|
+
require_relative "resources/customers"
|
4
5
|
require_relative "utils/connection_utils"
|
5
6
|
|
6
7
|
module PaystackSdk
|
@@ -46,5 +47,19 @@ module PaystackSdk
|
|
46
47
|
def transactions
|
47
48
|
@transactions ||= Resources::Transactions.new(@connection)
|
48
49
|
end
|
50
|
+
|
51
|
+
# Provides access to the `Customers` resource.
|
52
|
+
#
|
53
|
+
# @return [PaystackSdk::Resources::Customers] An instance of the
|
54
|
+
# `Customers` resource.
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# ```ruby
|
58
|
+
# customers = client.customers
|
59
|
+
# response = customers.list
|
60
|
+
# ```
|
61
|
+
def customers
|
62
|
+
@customers ||= Resources::Customers.new(@connection)
|
63
|
+
end
|
49
64
|
end
|
50
65
|
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module PaystackSdk
|
6
|
+
module Resources
|
7
|
+
# The `Customers` class provides methods for interacting with the Paystack Customers API.
|
8
|
+
# It allows you to create, list, fetch, update, and manage customers on your integration.
|
9
|
+
#
|
10
|
+
# Example usage:
|
11
|
+
# ```ruby
|
12
|
+
# customers = PaystackSdk::Resources::Customers.new(secret_key:)
|
13
|
+
#
|
14
|
+
# # Create a customer
|
15
|
+
# payload = { email: "customer@email.com", first_name: "Zero", last_name: "Sum" }
|
16
|
+
# response = customers.create(payload)
|
17
|
+
# if response.success?
|
18
|
+
# puts "Customer created successfully."
|
19
|
+
# puts "Customer code: #{response.customer_code}"
|
20
|
+
# else
|
21
|
+
# puts "Error creating customer: #{response.error_message}"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # List customers
|
25
|
+
# response = customers.list(per_page: 50, page: 1)
|
26
|
+
#
|
27
|
+
# # Fetch a customer
|
28
|
+
# response = customers.fetch("CUS_xxxxx")
|
29
|
+
#
|
30
|
+
# # Update a customer
|
31
|
+
# response = customers.update("CUS_xxxxx", { first_name: "John" })
|
32
|
+
# ```
|
33
|
+
class Customers < PaystackSdk::Resources::Base
|
34
|
+
# Creates a new customer.
|
35
|
+
#
|
36
|
+
# @param payload [Hash] The payload containing customer details.
|
37
|
+
# @option payload [String] :email (required) Customer's email address
|
38
|
+
# @option payload [String] :first_name Customer's first name
|
39
|
+
# @option payload [String] :last_name Customer's last name
|
40
|
+
# @option payload [String] :phone Customer's phone number
|
41
|
+
# @option payload [Hash] :metadata Additional customer information
|
42
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
43
|
+
# @raise [PaystackSdk::Error] If the payload is invalid or the API request fails.
|
44
|
+
def create(payload)
|
45
|
+
validate_fields!(
|
46
|
+
payload: payload,
|
47
|
+
validations: {
|
48
|
+
email: {type: :email, required: true},
|
49
|
+
first_name: {type: :string, required: false},
|
50
|
+
last_name: {type: :string, required: false},
|
51
|
+
phone: {type: :string, required: false}
|
52
|
+
}
|
53
|
+
)
|
54
|
+
|
55
|
+
response = @connection.post("customer", payload)
|
56
|
+
handle_response(response)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Lists all customers.
|
60
|
+
#
|
61
|
+
# @param per_page [Integer] Number of records per page (default: 50)
|
62
|
+
# @param page [Integer] Page number to retrieve (default: 1)
|
63
|
+
# @param from [String] Start date for filtering
|
64
|
+
# @param to [String] End date for filtering
|
65
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
66
|
+
# @raise [PaystackSdk::Error] If the API request fails.
|
67
|
+
def list(per_page: 50, page: 1, **params)
|
68
|
+
validate_positive_integer!(value: per_page, name: "per_page", allow_nil: true)
|
69
|
+
validate_positive_integer!(value: page, name: "page", allow_nil: true)
|
70
|
+
|
71
|
+
if params[:from]
|
72
|
+
validate_date_format!(date_str: params[:from], name: "from")
|
73
|
+
end
|
74
|
+
|
75
|
+
if params[:to]
|
76
|
+
validate_date_format!(date_str: params[:to], name: "to")
|
77
|
+
end
|
78
|
+
|
79
|
+
query_params = {perPage: per_page, page: page}.merge(params)
|
80
|
+
response = @connection.get("customer", query_params)
|
81
|
+
handle_response(response)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Fetches details of a single customer by email or code.
|
85
|
+
#
|
86
|
+
# @param email_or_code [String] Email or customer code
|
87
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
88
|
+
# @raise [PaystackSdk::Error] If the parameter is invalid or the API request fails.
|
89
|
+
def fetch(email_or_code)
|
90
|
+
validate_presence!(value: email_or_code, name: "email_or_code")
|
91
|
+
response = @connection.get("customer/#{email_or_code}")
|
92
|
+
handle_response(response)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Updates a customer's details.
|
96
|
+
#
|
97
|
+
# @param code [String] Customer's code
|
98
|
+
# @param payload [Hash] The payload containing customer details to update
|
99
|
+
# @option payload [String] :first_name Customer's first name
|
100
|
+
# @option payload [String] :last_name Customer's last name
|
101
|
+
# @option payload [String] :phone Customer's phone number
|
102
|
+
# @option payload [Hash] :metadata Additional customer information
|
103
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
104
|
+
# @raise [PaystackSdk::Error] If the parameters are invalid or the API request fails.
|
105
|
+
def update(code, payload)
|
106
|
+
validate_presence!(value: code, name: "code")
|
107
|
+
validate_hash!(input: payload, name: "payload")
|
108
|
+
|
109
|
+
response = @connection.put("customer/#{code}", payload)
|
110
|
+
handle_response(response)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Validates a customer's identity.
|
114
|
+
#
|
115
|
+
# @param code [String] Customer's code
|
116
|
+
# @param payload [Hash] The payload containing validation details
|
117
|
+
# @option payload [String] :country (required) 2 letter country code
|
118
|
+
# @option payload [String] :type (required) Type of identification
|
119
|
+
# @option payload [String] :account_number Bank account number (required for bank_account type)
|
120
|
+
# @option payload [String] :bvn Bank Verification Number
|
121
|
+
# @option payload [String] :bank_code Bank code
|
122
|
+
# @option payload [String] :first_name Customer's first name
|
123
|
+
# @option payload [String] :last_name Customer's last name
|
124
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
125
|
+
# @raise [PaystackSdk::Error] If the parameters are invalid or the API request fails.
|
126
|
+
def validate(code, payload)
|
127
|
+
validate_presence!(value: code, name: "code")
|
128
|
+
validate_fields!(
|
129
|
+
payload: payload,
|
130
|
+
validations: {
|
131
|
+
country: {type: :string, required: true},
|
132
|
+
type: {type: :string, required: true},
|
133
|
+
account_number: {type: :string, required: true},
|
134
|
+
bank_code: {type: :string, required: true}
|
135
|
+
}
|
136
|
+
)
|
137
|
+
|
138
|
+
response = @connection.post("customer/#{code}/identification", payload)
|
139
|
+
handle_response(response)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Sets the risk action for a customer.
|
143
|
+
#
|
144
|
+
# @param payload [Hash] The payload containing risk action details
|
145
|
+
# @option payload [String] :customer (required) Customer's code or email address
|
146
|
+
# @option payload [String] :risk_action (required) Risk action to set ('default', 'allow', or 'deny')
|
147
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
148
|
+
# @raise [PaystackSdk::MissingParamError] If required parameters are missing
|
149
|
+
# @raise [PaystackSdk::InvalidValueError] If risk_action is not one of the allowed values
|
150
|
+
# @raise [PaystackSdk::APIError] If the API request fails.
|
151
|
+
#
|
152
|
+
# @example
|
153
|
+
# ```ruby
|
154
|
+
# payload = { customer: "CUS_xxxxx", risk_action: "allow" }
|
155
|
+
# response = customers.set_risk_action(payload)
|
156
|
+
# if response.success?
|
157
|
+
# puts "Risk action updated successfully"
|
158
|
+
# end
|
159
|
+
# ```
|
160
|
+
def set_risk_action(payload)
|
161
|
+
validate_fields!(
|
162
|
+
payload: payload,
|
163
|
+
validations: {
|
164
|
+
customer: {type: :string, required: true},
|
165
|
+
risk_action: {type: :inclusion, required: true, allowed_values: %w[default allow deny]}
|
166
|
+
}
|
167
|
+
)
|
168
|
+
|
169
|
+
response = @connection.post("customer/set_risk_action", payload)
|
170
|
+
handle_response(response)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Deactivates a customer's authorization.
|
174
|
+
#
|
175
|
+
# @param payload [Hash] The payload containing authorization details
|
176
|
+
# @option payload [String] :authorization_code (required) Authorization code to deactivate
|
177
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
178
|
+
# @raise [PaystackSdk::Error] If the parameters are invalid or the API request fails.
|
179
|
+
def deactivate_authorization(payload)
|
180
|
+
validate_fields!(
|
181
|
+
payload: payload,
|
182
|
+
validations: {
|
183
|
+
authorization_code: {type: :string, required: true}
|
184
|
+
}
|
185
|
+
)
|
186
|
+
|
187
|
+
response = @connection.post("customer/deactivate_authorization", payload)
|
188
|
+
handle_response(response)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -3,12 +3,17 @@ module PaystackSdk
|
|
3
3
|
# It offers convenient access to response data through dot notation and
|
4
4
|
# supports both direct attribute access and hash/array-like operations.
|
5
5
|
#
|
6
|
+
# The Response class handles API responses that use string keys (as returned
|
7
|
+
# by the Paystack API) and provides seamless access through both string and
|
8
|
+
# symbol notation.
|
9
|
+
#
|
6
10
|
# Features:
|
7
11
|
# - Dynamic attribute access via dot notation (response.data.attribute)
|
8
|
-
# - Hash-like access (response[:key])
|
12
|
+
# - Hash-like access (response[:key] or response["key"])
|
9
13
|
# - Array-like access for list responses (response[0])
|
10
14
|
# - Iteration support (response.each)
|
11
15
|
# - Automatic handling of nested data structures
|
16
|
+
# - Consistent handling of string-keyed API responses
|
12
17
|
#
|
13
18
|
# @example Basic usage
|
14
19
|
# ```ruby
|
@@ -49,28 +54,48 @@ module PaystackSdk
|
|
49
54
|
# @return [Hash, Array, Object] The underlying data
|
50
55
|
attr_reader :raw_data
|
51
56
|
|
57
|
+
# @return [Integer] The status code of the API response
|
58
|
+
attr_reader :status_code
|
59
|
+
|
52
60
|
# Initializes a new Response object
|
53
61
|
#
|
54
62
|
# @param response [Faraday::Response, Hash, Array] The raw API response or data
|
63
|
+
# @raise [PaystackSdk::APIError] If the API returns an error response
|
64
|
+
# @raise [PaystackSdk::AuthenticationError] If authentication fails (401)
|
65
|
+
# @raise [PaystackSdk::ResourceNotFoundError] If resource is not found (404 or 400 with not found message)
|
66
|
+
# @raise [PaystackSdk::RateLimitError] If rate limit is exceeded (429)
|
67
|
+
# @raise [PaystackSdk::ServerError] If server returns 5xx error
|
55
68
|
def initialize(response)
|
56
69
|
if response.is_a?(Faraday::Response)
|
57
|
-
|
58
|
-
@success = response.success?
|
70
|
+
@status_code = response.status
|
59
71
|
@body = response.body
|
60
72
|
@api_message = extract_api_message(@body)
|
61
73
|
@raw_data = extract_data_from_body(@body)
|
62
74
|
|
63
|
-
|
64
|
-
|
75
|
+
case @status_code
|
76
|
+
when 200..299
|
77
|
+
@success = true
|
78
|
+
when 400
|
79
|
+
handle_400_response
|
80
|
+
when 401
|
81
|
+
raise AuthenticationError.new
|
82
|
+
when 404
|
83
|
+
handle_404_response
|
84
|
+
when 429
|
85
|
+
retry_after = response.headers["Retry-After"]
|
86
|
+
raise RateLimitError.new(retry_after || 30)
|
87
|
+
when 500..599
|
88
|
+
raise ServerError.new(@status_code, @api_message)
|
89
|
+
else
|
90
|
+
@success = false
|
91
|
+
raise APIError.new(@api_message || "Paystack API Error")
|
65
92
|
end
|
66
93
|
elsif response.is_a?(Response)
|
67
|
-
|
68
|
-
@success = response.success
|
94
|
+
@success = response.success?
|
69
95
|
@error_message = response.error_message
|
70
96
|
@api_message = response.api_message
|
71
97
|
@raw_data = response.raw_data
|
72
98
|
else
|
73
|
-
# For direct data (Hash, Array, etc.)
|
74
99
|
@success = true
|
75
100
|
@raw_data = response
|
76
101
|
end
|
@@ -197,24 +222,44 @@ module PaystackSdk
|
|
197
222
|
|
198
223
|
private
|
199
224
|
|
200
|
-
# Extract
|
225
|
+
# Extract the identifier from an error response
|
226
|
+
# This looks for common patterns in error messages to find resource identifiers
|
201
227
|
#
|
202
228
|
# @param body [Hash] The response body
|
203
|
-
# @return [String
|
229
|
+
# @return [String] The extracted identifier or "unknown"
|
230
|
+
def extract_identifier(body)
|
231
|
+
return "unknown" unless body.is_a?(Hash)
|
232
|
+
|
233
|
+
# First try to get identifier from the message
|
234
|
+
message = body["message"].to_s.downcase
|
235
|
+
if message =~ /with (id|code|reference|email): ([^\s]+)/i
|
236
|
+
return $2
|
237
|
+
end
|
238
|
+
|
239
|
+
# If not found in message, try to extract from error code
|
240
|
+
if body["code"]&.match?(/^(transaction|customer)_/)
|
241
|
+
parts = body["code"].to_s.split("_")
|
242
|
+
return parts.last if parts.last != "not_found"
|
243
|
+
end
|
244
|
+
|
245
|
+
"unknown"
|
246
|
+
end
|
247
|
+
|
248
|
+
# Extract the API message from the response body
|
249
|
+
#
|
250
|
+
# @param body [Hash, nil] The response body
|
251
|
+
# @return [String, nil] The API message if present
|
204
252
|
def extract_api_message(body)
|
205
|
-
body["message"]
|
253
|
+
body["message"] if body.is_a?(Hash) && body["message"]
|
206
254
|
end
|
207
255
|
|
208
|
-
# Extract data from response body
|
256
|
+
# Extract the data from the response body
|
209
257
|
#
|
210
|
-
# @param body [Hash,
|
211
|
-
# @return [Hash, Array,
|
258
|
+
# @param body [Hash, nil] The response body
|
259
|
+
# @return [Hash, Array, nil] The data from the response
|
212
260
|
def extract_data_from_body(body)
|
213
|
-
|
214
|
-
|
215
|
-
else
|
216
|
-
body
|
217
|
-
end
|
261
|
+
return body unless body.is_a?(Hash)
|
262
|
+
body["data"] || body
|
218
263
|
end
|
219
264
|
|
220
265
|
# Wrap value in Response if needed
|
@@ -234,5 +279,29 @@ module PaystackSdk
|
|
234
279
|
value
|
235
280
|
end
|
236
281
|
end
|
282
|
+
|
283
|
+
def handle_400_response
|
284
|
+
if @body["code"]&.end_with?("_not_found") ||
|
285
|
+
@api_message&.match?(/not found|cannot find|does not exist/i)
|
286
|
+
resource_type = determine_resource_type
|
287
|
+
meta_info = @body["meta"]&.dig("nextStep")
|
288
|
+
message = @api_message
|
289
|
+
message = "#{message}\nHint: #{meta_info}" if meta_info
|
290
|
+
raise ResourceNotFoundError.new(resource_type, message)
|
291
|
+
else
|
292
|
+
@success = false
|
293
|
+
raise APIError.new(@api_message || "Paystack API Error")
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def handle_404_response
|
298
|
+
resource_type = determine_resource_type
|
299
|
+
raise ResourceNotFoundError.new(resource_type, @api_message)
|
300
|
+
end
|
301
|
+
|
302
|
+
def determine_resource_type
|
303
|
+
resource = @body["code"].split("_").first
|
304
|
+
resource.capitalize
|
305
|
+
end
|
237
306
|
end
|
238
307
|
end
|
@@ -23,7 +23,7 @@ module PaystackSdk
|
|
23
23
|
else
|
24
24
|
# Try to get API key from environment variable
|
25
25
|
env_secret_key = ENV["PAYSTACK_SECRET_KEY"]
|
26
|
-
raise
|
26
|
+
raise AuthenticationError, "No connection or API key provided" unless env_secret_key
|
27
27
|
|
28
28
|
create_connection(secret_key: env_secret_key)
|
29
29
|
end
|
@@ -5,15 +5,41 @@ module PaystackSdk
|
|
5
5
|
# It includes methods for validating common parameters like references, amounts, dates, etc.
|
6
6
|
#
|
7
7
|
# This module is intended to be included in resource classes to provide consistent
|
8
|
-
# parameter validation before making API calls.
|
8
|
+
# parameter validation before making API calls. All validation methods raise specific
|
9
|
+
# error types to enable proper error handling in client applications.
|
10
|
+
#
|
11
|
+
# @example Usage in a resource class
|
12
|
+
# ```ruby
|
13
|
+
# class MyResource < PaystackSdk::Resources::Base
|
14
|
+
# include PaystackSdk::Validations
|
15
|
+
#
|
16
|
+
# def create(payload)
|
17
|
+
# validate_fields!(
|
18
|
+
# payload: payload,
|
19
|
+
# validations: {
|
20
|
+
# email: { type: :email, required: true },
|
21
|
+
# amount: { type: :positive_integer, required: true },
|
22
|
+
# currency: { type: :currency, required: true }
|
23
|
+
# }
|
24
|
+
# )
|
25
|
+
# # Make API call...
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# ```
|
29
|
+
#
|
30
|
+
# @see PaystackSdk::MissingParamError
|
31
|
+
# @see PaystackSdk::InvalidFormatError
|
32
|
+
# @see PaystackSdk::InvalidValueError
|
9
33
|
module Validations
|
10
34
|
# Validates that input is a hash.
|
11
35
|
#
|
12
36
|
# @param input [Object] The input to validate
|
13
37
|
# @param name [String] Name of the parameter for error messages
|
14
|
-
# @raise [PaystackSdk::
|
38
|
+
# @raise [PaystackSdk::InvalidFormatError] If input is not a hash
|
15
39
|
def validate_hash!(input:, name: "Payload")
|
16
|
-
|
40
|
+
unless input.is_a?(Hash)
|
41
|
+
raise PaystackSdk::InvalidFormatError.new(name, "Hash")
|
42
|
+
end
|
17
43
|
end
|
18
44
|
|
19
45
|
# Validates that required parameters are present in a payload.
|
@@ -21,15 +47,15 @@ module PaystackSdk
|
|
21
47
|
# @param payload [Hash] The payload to validate
|
22
48
|
# @param required_params [Array<Symbol>] List of required parameter keys
|
23
49
|
# @param operation_name [String] Name of the operation for error messages
|
24
|
-
# @raise [PaystackSdk::
|
50
|
+
# @raise [PaystackSdk::MissingParamError] If any required parameters are missing
|
25
51
|
def validate_required_params!(payload:, required_params:, operation_name: "Operation")
|
26
52
|
missing_params = required_params.select do |param|
|
27
53
|
!payload.key?(param) && !payload.key?(param.to_s)
|
28
54
|
end
|
29
55
|
|
30
56
|
unless missing_params.empty?
|
31
|
-
|
32
|
-
raise PaystackSdk::
|
57
|
+
param = missing_params.first
|
58
|
+
raise PaystackSdk::MissingParamError.new(param)
|
33
59
|
end
|
34
60
|
end
|
35
61
|
|
@@ -37,10 +63,10 @@ module PaystackSdk
|
|
37
63
|
#
|
38
64
|
# @param value [Object] The value to validate
|
39
65
|
# @param name [String] Name of the parameter for error messages
|
40
|
-
# @raise [PaystackSdk::
|
66
|
+
# @raise [PaystackSdk::MissingParamError] If value is nil or empty
|
41
67
|
def validate_presence!(value:, name: "Parameter")
|
42
68
|
if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
43
|
-
raise PaystackSdk::
|
69
|
+
raise PaystackSdk::MissingParamError.new(name)
|
44
70
|
end
|
45
71
|
end
|
46
72
|
|
@@ -49,12 +75,13 @@ module PaystackSdk
|
|
49
75
|
# @param value [Object] The value to validate
|
50
76
|
# @param name [String] Name of the parameter for error messages
|
51
77
|
# @param allow_nil [Boolean] Whether nil values are allowed
|
52
|
-
# @raise [PaystackSdk::
|
78
|
+
# @raise [PaystackSdk::InvalidValueError] If value is not a positive integer
|
79
|
+
# @raise [PaystackSdk::MissingParamError] If value is nil and not allowed
|
53
80
|
def validate_positive_integer!(value:, name: "Parameter", allow_nil: true)
|
54
81
|
if value.nil?
|
55
|
-
raise PaystackSdk::
|
82
|
+
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
56
83
|
elsif !value.is_a?(Integer) || value < 1
|
57
|
-
raise PaystackSdk::
|
84
|
+
raise PaystackSdk::InvalidValueError.new(name, "must be a positive integer")
|
58
85
|
end
|
59
86
|
end
|
60
87
|
|
@@ -62,10 +89,10 @@ module PaystackSdk
|
|
62
89
|
#
|
63
90
|
# @param reference [String] The reference to validate
|
64
91
|
# @param name [String] Name of the parameter for error messages
|
65
|
-
# @raise [PaystackSdk::
|
92
|
+
# @raise [PaystackSdk::InvalidFormatError] If reference format is invalid
|
66
93
|
def validate_reference_format!(reference:, name: "Reference")
|
67
94
|
unless reference.to_s.match?(/^[a-zA-Z0-9._=-]+$/)
|
68
|
-
raise PaystackSdk::
|
95
|
+
raise PaystackSdk::InvalidFormatError.new(name, "alphanumeric characters and the following: -, ., =")
|
69
96
|
end
|
70
97
|
end
|
71
98
|
|
@@ -74,17 +101,18 @@ module PaystackSdk
|
|
74
101
|
# @param date_str [String] The date string to validate
|
75
102
|
# @param name [String] Name of the parameter for error messages
|
76
103
|
# @param allow_nil [Boolean] Whether nil values are allowed
|
77
|
-
# @raise [PaystackSdk::
|
104
|
+
# @raise [PaystackSdk::InvalidFormatError] If date format is invalid
|
105
|
+
# @raise [PaystackSdk::MissingParamError] If date is nil and not allowed
|
78
106
|
def validate_date_format!(date_str:, name: "Date", allow_nil: true)
|
79
107
|
if date_str.nil?
|
80
|
-
raise PaystackSdk::
|
108
|
+
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
81
109
|
return
|
82
110
|
end
|
83
111
|
|
84
112
|
begin
|
85
113
|
Date.parse(date_str.to_s)
|
86
114
|
rescue Date::Error
|
87
|
-
raise PaystackSdk::
|
115
|
+
raise PaystackSdk::InvalidFormatError.new(name, "YYYY-MM-DD or ISO8601")
|
88
116
|
end
|
89
117
|
end
|
90
118
|
|
@@ -94,20 +122,26 @@ module PaystackSdk
|
|
94
122
|
# @param allowed_values [Array] List of allowed values
|
95
123
|
# @param name [String] Name of the parameter for error messages
|
96
124
|
# @param allow_nil [Boolean] Whether nil values are allowed
|
97
|
-
# @
|
98
|
-
# @raise [PaystackSdk::
|
99
|
-
|
125
|
+
# @raise [PaystackSdk::InvalidValueError] If value is not in allowed_values
|
126
|
+
# @raise [PaystackSdk::MissingParamError] If value is nil and not allowed
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# ```ruby
|
130
|
+
# validate_allowed_values!(
|
131
|
+
# value: "allow",
|
132
|
+
# allowed_values: %w[default allow deny],
|
133
|
+
# name: "risk_action"
|
134
|
+
# )
|
135
|
+
# ```
|
136
|
+
def validate_allowed_values!(value:, allowed_values:, name: "Parameter", allow_nil: true)
|
100
137
|
if value.nil?
|
101
|
-
raise PaystackSdk::
|
138
|
+
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
102
139
|
return
|
103
140
|
end
|
104
141
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
unless check_allowed.include?(check_value)
|
109
|
-
allowed_list = allowed_values.join("', '")
|
110
|
-
raise PaystackSdk::Error, "#{name} must be one of: '#{allowed_list}'"
|
142
|
+
unless allowed_values.include?(value)
|
143
|
+
allowed_list = allowed_values.join(", ")
|
144
|
+
raise PaystackSdk::InvalidValueError.new(name, "must be one of: #{allowed_list}")
|
111
145
|
end
|
112
146
|
end
|
113
147
|
|
@@ -119,12 +153,12 @@ module PaystackSdk
|
|
119
153
|
# @raise [PaystackSdk::Error] If email format is invalid
|
120
154
|
def validate_email!(email:, name: "Email", allow_nil: false)
|
121
155
|
if email.nil?
|
122
|
-
raise PaystackSdk::
|
156
|
+
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
123
157
|
return
|
124
158
|
end
|
125
159
|
|
126
160
|
unless email.to_s.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
|
127
|
-
raise PaystackSdk::
|
161
|
+
raise PaystackSdk::InvalidFormatError.new(name, "valid email address")
|
128
162
|
end
|
129
163
|
end
|
130
164
|
|
@@ -136,12 +170,12 @@ module PaystackSdk
|
|
136
170
|
# @raise [PaystackSdk::Error] If currency format is invalid
|
137
171
|
def validate_currency!(currency:, name: "Currency", allow_nil: true)
|
138
172
|
if currency.nil?
|
139
|
-
raise PaystackSdk::
|
173
|
+
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
140
174
|
return
|
141
175
|
end
|
142
176
|
|
143
177
|
unless currency.to_s.match?(/\A[A-Z]{3}\z/)
|
144
|
-
raise PaystackSdk::
|
178
|
+
raise PaystackSdk::InvalidFormatError.new(name, "3-letter ISO code (e.g., NGN, USD, GHS)")
|
145
179
|
end
|
146
180
|
end
|
147
181
|
|
@@ -152,6 +186,7 @@ module PaystackSdk
|
|
152
186
|
# @raise [PaystackSdk::Error] If any validation fails
|
153
187
|
#
|
154
188
|
# @example
|
189
|
+
# ```ruby
|
155
190
|
# validate_fields!(
|
156
191
|
# payload: params,
|
157
192
|
# validations: {
|
@@ -161,7 +196,10 @@ module PaystackSdk
|
|
161
196
|
# reference: { type: :reference, required: false }
|
162
197
|
# }
|
163
198
|
# )
|
199
|
+
# ```
|
164
200
|
def validate_fields!(payload:, validations:)
|
201
|
+
validate_hash!(input: payload, name: "Payload")
|
202
|
+
|
165
203
|
# First check required fields
|
166
204
|
required_fields = validations.select { |_, opts| opts[:required] }.keys
|
167
205
|
validate_required_params!(payload: payload, required_params: required_fields) unless required_fields.empty?
|
@@ -184,14 +222,15 @@ module PaystackSdk
|
|
184
222
|
validate_currency!(currency: value, name: field.to_s, allow_nil: !options[:required])
|
185
223
|
when :inclusion
|
186
224
|
if value
|
187
|
-
|
225
|
+
validate_allowed_values!(
|
188
226
|
value: value,
|
189
227
|
allowed_values: options[:allowed_values],
|
190
228
|
name: field.to_s,
|
191
|
-
allow_nil: !options[:required]
|
192
|
-
case_sensitive: options[:case_sensitive]
|
229
|
+
allow_nil: !options[:required]
|
193
230
|
)
|
194
231
|
end
|
232
|
+
when :string
|
233
|
+
validate_presence!(value: value, name: field.to_s) if !options[:allow_nil] && options[:required]
|
195
234
|
end
|
196
235
|
end
|
197
236
|
end
|
data/lib/paystack_sdk/version.rb
CHANGED
data/lib/paystack_sdk.rb
CHANGED
@@ -5,5 +5,87 @@ require_relative "paystack_sdk/version"
|
|
5
5
|
require_relative "paystack_sdk/client"
|
6
6
|
|
7
7
|
module PaystackSdk
|
8
|
+
# Base error class for all Paystack SDK errors.
|
9
|
+
# All SDK-specific exceptions inherit from this class.
|
8
10
|
class Error < StandardError; end
|
11
|
+
|
12
|
+
# Base class for all validation errors.
|
13
|
+
# Raised when input parameters fail validation before API calls.
|
14
|
+
class ValidationError < Error; end
|
15
|
+
|
16
|
+
# Raised when a required parameter is missing.
|
17
|
+
# Contains the parameter name for detailed error handling.
|
18
|
+
class MissingParamError < ValidationError
|
19
|
+
attr_reader :param_name
|
20
|
+
|
21
|
+
def initialize(param_name)
|
22
|
+
@param_name = param_name
|
23
|
+
super("Missing required parameter: #{param_name}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when a parameter has an invalid format.
|
28
|
+
# Contains both the parameter name and expected format for detailed error handling.
|
29
|
+
class InvalidFormatError < ValidationError
|
30
|
+
attr_reader :param_name, :expected_format
|
31
|
+
|
32
|
+
def initialize(param_name, expected_format)
|
33
|
+
@param_name = param_name
|
34
|
+
@expected_format = expected_format
|
35
|
+
super("Invalid format for #{param_name}. Expected format: #{expected_format}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Raised when a parameter has an invalid value.
|
40
|
+
# Contains both the parameter name and the reason for the invalid value.
|
41
|
+
class InvalidValueError < ValidationError
|
42
|
+
attr_reader :param_name, :reason
|
43
|
+
|
44
|
+
def initialize(param_name, reason)
|
45
|
+
@param_name = param_name
|
46
|
+
@reason = reason
|
47
|
+
super("Invalid value for #{param_name}: #{reason}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Base class for API errors.
|
52
|
+
# Raised when the Paystack API returns an error response.
|
53
|
+
class APIError < Error; end
|
54
|
+
|
55
|
+
# Raised when authentication fails
|
56
|
+
class AuthenticationError < APIError
|
57
|
+
def initialize(message = "Invalid API key or authentication failed")
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Raised when a resource is not found
|
63
|
+
class ResourceNotFoundError < APIError
|
64
|
+
attr_reader :resource_type
|
65
|
+
|
66
|
+
def initialize(resource_type, message)
|
67
|
+
@resource_type = resource_type
|
68
|
+
super(message)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Raised when rate limiting is encountered
|
73
|
+
class RateLimitError < APIError
|
74
|
+
attr_reader :retry_after
|
75
|
+
|
76
|
+
def initialize(retry_after)
|
77
|
+
@retry_after = retry_after
|
78
|
+
super("Rate limit exceeded. Retry after #{retry_after} seconds")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Raised when the server returns a 5xx error
|
83
|
+
class ServerError < APIError
|
84
|
+
attr_reader :status_code
|
85
|
+
|
86
|
+
def initialize(status_code, message = "An error occurred on the Paystack server")
|
87
|
+
@status_code = status_code
|
88
|
+
super("#{message} (Status: #{status_code})")
|
89
|
+
end
|
90
|
+
end
|
9
91
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paystack_sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxwell Nana Forson (theLazyProgrammer)
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: faraday
|
@@ -117,6 +116,7 @@ files:
|
|
117
116
|
- lib/paystack_sdk.rb
|
118
117
|
- lib/paystack_sdk/client.rb
|
119
118
|
- lib/paystack_sdk/resources/base.rb
|
119
|
+
- lib/paystack_sdk/resources/customers.rb
|
120
120
|
- lib/paystack_sdk/resources/transactions.rb
|
121
121
|
- lib/paystack_sdk/response.rb
|
122
122
|
- lib/paystack_sdk/utils/connection_utils.rb
|
@@ -131,7 +131,6 @@ metadata:
|
|
131
131
|
allowed_push_host: https://rubygems.org
|
132
132
|
homepage_uri: https://github.com/nanafox/paystack_sdk
|
133
133
|
changelog_uri: https://github.com/nanafox/paystack_sdk/blob/main/CHANGELOG.md
|
134
|
-
post_install_message:
|
135
134
|
rdoc_options: []
|
136
135
|
require_paths:
|
137
136
|
- lib
|
@@ -146,8 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
145
|
- !ruby/object:Gem::Version
|
147
146
|
version: '0'
|
148
147
|
requirements: []
|
149
|
-
rubygems_version: 3.
|
150
|
-
signing_key:
|
148
|
+
rubygems_version: 3.6.9
|
151
149
|
specification_version: 4
|
152
150
|
summary: A Ruby SDK for integrating with Paystack's payment gateway API.
|
153
151
|
test_files: []
|