paystack_sdk 0.0.8 → 0.1.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 +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +110 -16
- data/lib/paystack_sdk/client.rb +15 -0
- data/lib/paystack_sdk/resources/banks.rb +1 -1
- data/lib/paystack_sdk/resources/verification.rb +36 -0
- data/lib/paystack_sdk/response.rb +41 -29
- data/lib/paystack_sdk/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a18abe45774bda30eff6f91a3e93ba55066d9ef4f04ddfc690792edc8b005d94
|
4
|
+
data.tar.gz: 652cc2b936ac3a7211290ad6a6f561d57132fd825588232fab724a0b2378cfa7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fcb8324376b91af1c71e894309789fb69a4eeefff5d4a1fa93c190a66c9c01489d5a8a9b4003f8416fbff3ffe5d3474ec6ec49ab9c1a58ed7fb16ebf095c04a
|
7
|
+
data.tar.gz: b094c91f15744f8c02ca9e493d7aa6ab7b9c24f6f0e25cc8e1a94d32bc9d3101aa713a97ff074000e365e1f1857d26735bd99016e82a480b7da839e6be9b349e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,50 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [0.1.0] - 2025-06-26
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
|
7
|
+
#### Error Handling Strategy
|
8
|
+
|
9
|
+
- **BREAKING**: Clarified error handling approach - the SDK now consistently follows a two-tier error handling strategy:
|
10
|
+
- **Validation errors** (missing/invalid input data) are thrown as exceptions immediately before API calls
|
11
|
+
- **API response errors** (business logic, not found, etc.) are returned as unsuccessful Response objects
|
12
|
+
- Updated documentation to clearly explain when exceptions are thrown vs. when to check `response.success?`
|
13
|
+
|
14
|
+
#### Test Infrastructure
|
15
|
+
|
16
|
+
- **BREAKING**: Standardized all test specifications to use connection doubles instead of Faraday-specific mocks
|
17
|
+
- Updated test pattern across all resource specs to use `instance_double("PaystackSdk::Connection")` for better framework independence
|
18
|
+
- All tests now follow consistent mocking pattern with `PaystackSdk::Response.new()` expectations
|
19
|
+
|
20
|
+
#### Documentation
|
21
|
+
|
22
|
+
- Enhanced README with comprehensive error handling examples
|
23
|
+
- Added validation error examples showing `MissingParamError`, `InvalidFormatError`, and `InvalidValueError`
|
24
|
+
- Updated Quick Start guide to demonstrate proper exception handling
|
25
|
+
- Clarified the difference between input validation (exceptions) and API response handling (Response objects)
|
26
|
+
|
27
|
+
### Improved
|
28
|
+
|
29
|
+
- Better separation of concerns between input validation and API error handling
|
30
|
+
- More consistent test suite that's not tied to specific HTTP client implementation
|
31
|
+
- Clearer developer experience with predictable error handling patterns
|
32
|
+
|
33
|
+
## [0.0.9] - 2025-06-26
|
34
|
+
|
35
|
+
### Added
|
36
|
+
|
37
|
+
- Verification resource with support for:
|
38
|
+
- Resolving bank accounts
|
39
|
+
- Resolving card BINs
|
40
|
+
- Validating accounts (with required and optional fields)
|
41
|
+
- Registered `verification` resource in the main client for easy access
|
42
|
+
- Regression tests for all Verification resource methods, including required field validation and full parameter support
|
43
|
+
|
44
|
+
### Changed
|
45
|
+
|
46
|
+
- Fix the link for listing banks API
|
47
|
+
|
3
48
|
## [0.0.8] - 2025-06-26
|
4
49
|
|
5
50
|
### Added
|
data/README.md
CHANGED
@@ -69,13 +69,19 @@ params = {
|
|
69
69
|
currency: "NGN"
|
70
70
|
}
|
71
71
|
|
72
|
-
|
72
|
+
begin
|
73
|
+
response = paystack.transactions.initiate(params)
|
73
74
|
|
74
|
-
if response.success?
|
75
|
-
|
76
|
-
|
77
|
-
else
|
78
|
-
|
75
|
+
if response.success?
|
76
|
+
puts "Visit this URL to complete payment: #{response.authorization_url}"
|
77
|
+
puts "Transaction reference: #{response.reference}"
|
78
|
+
else
|
79
|
+
puts "Error: #{response.error_message}"
|
80
|
+
end
|
81
|
+
rescue PaystackSdk::MissingParamError => e
|
82
|
+
puts "Missing required data: #{e.message}"
|
83
|
+
rescue PaystackSdk::InvalidFormatError => e
|
84
|
+
puts "Invalid data format: #{e.message}"
|
79
85
|
end
|
80
86
|
|
81
87
|
# Create a customer
|
@@ -85,12 +91,16 @@ customer_params = {
|
|
85
91
|
last_name: "Doe"
|
86
92
|
}
|
87
93
|
|
88
|
-
|
94
|
+
begin
|
95
|
+
customer_response = paystack.customers.create(customer_params)
|
89
96
|
|
90
|
-
if customer_response.success?
|
91
|
-
|
92
|
-
else
|
93
|
-
|
97
|
+
if customer_response.success?
|
98
|
+
puts "Customer created: #{customer_response.data.customer_code}"
|
99
|
+
else
|
100
|
+
puts "Error: #{customer_response.error_message}"
|
101
|
+
end
|
102
|
+
rescue PaystackSdk::ValidationError => e
|
103
|
+
puts "Validation error: #{e.message}"
|
94
104
|
end
|
95
105
|
```
|
96
106
|
|
@@ -98,6 +108,55 @@ end
|
|
98
108
|
|
99
109
|
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
110
|
|
111
|
+
### Error Handling
|
112
|
+
|
113
|
+
The SDK uses a two-tier error handling approach:
|
114
|
+
|
115
|
+
1. **Validation Errors** (thrown as exceptions) - for missing or invalid input data
|
116
|
+
2. **API Response Errors** (returned as unsuccessful Response objects) - for API-level issues
|
117
|
+
|
118
|
+
#### Input Validation
|
119
|
+
|
120
|
+
The SDK validates your parameters **before** making API calls and throws exceptions immediately for data issues:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
begin
|
124
|
+
# This will throw an exception before making any API call
|
125
|
+
response = paystack.transactions.initiate({amount: 1000}) # Missing required email
|
126
|
+
rescue PaystackSdk::MissingParamError => e
|
127
|
+
puts "Fix your data: #{e.message}"
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
#### API Response Handling
|
132
|
+
|
133
|
+
All successful API calls return a `Response` object that you can check for success:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
response = paystack.transactions.initiate(valid_params)
|
137
|
+
|
138
|
+
if response.success?
|
139
|
+
puts "Transaction created: #{response.authorization_url}"
|
140
|
+
else
|
141
|
+
puts "Transaction failed: #{response.error_message}"
|
142
|
+
|
143
|
+
# Get detailed error information
|
144
|
+
error_details = response.error_details
|
145
|
+
puts "Status code: #{error_details[:status_code]}"
|
146
|
+
puts "Error message: #{error_details[:message]}"
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
**Note**: The SDK raises exceptions for:
|
151
|
+
|
152
|
+
- **Validation errors** - when required parameters are missing or have invalid formats
|
153
|
+
- **Authentication errors** (401) - usually configuration issues
|
154
|
+
- **Rate limiting** (429) - requires retry logic
|
155
|
+
- **Server errors** (5xx) - Paystack infrastructure issues
|
156
|
+
- **Network errors** - connection failures
|
157
|
+
|
158
|
+
All other API errors (resource not found, business logic errors, etc.) are returned as unsuccessful Response objects.
|
159
|
+
|
101
160
|
## Usage
|
102
161
|
|
103
162
|
### Client Initialization
|
@@ -457,19 +516,19 @@ total_count = original.dig("meta", "total")
|
|
457
516
|
current_page = original.dig("meta", "page")
|
458
517
|
```
|
459
518
|
|
460
|
-
####
|
519
|
+
#### Exception Handling
|
461
520
|
|
462
521
|
The SDK provides specific error classes for different types of failures, making it easier to handle errors appropriately:
|
463
522
|
|
464
523
|
```ruby
|
465
524
|
begin
|
466
525
|
response = paystack.transactions.verify(reference: "invalid_reference")
|
467
|
-
rescue PaystackSdk::ResourceNotFoundError => e
|
468
|
-
puts "Resource not found: #{e.message}"
|
469
526
|
rescue PaystackSdk::AuthenticationError => e
|
470
527
|
puts "Authentication failed: #{e.message}"
|
471
|
-
rescue PaystackSdk::
|
472
|
-
puts "
|
528
|
+
rescue PaystackSdk::RateLimitError => e
|
529
|
+
puts "Rate limit exceeded. Retry after: #{e.retry_after} seconds"
|
530
|
+
rescue PaystackSdk::ServerError => e
|
531
|
+
puts "Server error: #{e.message}"
|
473
532
|
rescue PaystackSdk::APIError => e
|
474
533
|
puts "API error: #{e.message}"
|
475
534
|
rescue PaystackSdk::Error => e
|
@@ -507,6 +566,41 @@ The SDK includes several specific error classes:
|
|
507
566
|
- **`PaystackSdk::RateLimitError`** - Rate limiting encountered
|
508
567
|
- **`PaystackSdk::ServerError`** - Server errors (5xx responses)
|
509
568
|
|
569
|
+
##### Validation Error Examples
|
570
|
+
|
571
|
+
The SDK validates your input data **before** making API calls and will throw exceptions immediately if required data is missing or incorrectly formatted:
|
572
|
+
|
573
|
+
```ruby
|
574
|
+
# Missing required parameter
|
575
|
+
begin
|
576
|
+
paystack.transactions.initiate({amount: 1000}) # Missing email
|
577
|
+
rescue PaystackSdk::MissingParamError => e
|
578
|
+
puts e.message # => "Missing required parameter: email"
|
579
|
+
end
|
580
|
+
|
581
|
+
# Invalid format
|
582
|
+
begin
|
583
|
+
paystack.transactions.initiate({
|
584
|
+
email: "invalid-email", # Not a valid email format
|
585
|
+
amount: 1000
|
586
|
+
})
|
587
|
+
rescue PaystackSdk::InvalidFormatError => e
|
588
|
+
puts e.message # => "Invalid format for Email. Expected format: valid email address"
|
589
|
+
end
|
590
|
+
|
591
|
+
# Invalid value
|
592
|
+
begin
|
593
|
+
paystack.customers.set_risk_action({
|
594
|
+
customer: "CUS_123",
|
595
|
+
risk_action: "invalid_action" # Not in allowed values
|
596
|
+
})
|
597
|
+
rescue PaystackSdk::InvalidValueError => e
|
598
|
+
puts e.message # => "Invalid value for risk_action: must be one of [default, allow, deny]"
|
599
|
+
end
|
600
|
+
```
|
601
|
+
|
602
|
+
These validation errors are thrown immediately and prevent the API call from being made, helping you catch data issues early in development.
|
603
|
+
|
510
604
|
## Advanced Usage
|
511
605
|
|
512
606
|
### Environment Variables
|
data/lib/paystack_sdk/client.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative "resources/customers"
|
|
5
5
|
require_relative "resources/transfer_recipients"
|
6
6
|
require_relative "resources/transfers"
|
7
7
|
require_relative "resources/banks"
|
8
|
+
require_relative "resources/verification"
|
8
9
|
require_relative "utils/connection_utils"
|
9
10
|
|
10
11
|
module PaystackSdk
|
@@ -106,5 +107,19 @@ module PaystackSdk
|
|
106
107
|
def banks
|
107
108
|
@banks ||= Resources::Banks.new(@connection)
|
108
109
|
end
|
110
|
+
|
111
|
+
# Provides access to the `Verification` resource.
|
112
|
+
#
|
113
|
+
# @return [PaystackSdk::Resources::Verification] An instance of the
|
114
|
+
# `Verification` resource.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# ```ruby
|
118
|
+
# verification = client.verification
|
119
|
+
# response = verification.resolve_account(account_number: ..., bank_code: ...)
|
120
|
+
# ```
|
121
|
+
def verification
|
122
|
+
@verification ||= Resources::Verification.new(@connection)
|
123
|
+
end
|
109
124
|
end
|
110
125
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative "../validations"
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module PaystackSdk
|
5
|
+
module Resources
|
6
|
+
class Verification < Base
|
7
|
+
# Resolve Bank Account
|
8
|
+
# @see https://paystack.com/docs/api/verification/#resolve-bank-account
|
9
|
+
def resolve_account(account_number:, bank_code:)
|
10
|
+
validate_presence!(value: account_number, name: "account_number")
|
11
|
+
validate_presence!(value: bank_code, name: "bank_code")
|
12
|
+
handle_response(@connection.get("/bank/resolve", {account_number: account_number, bank_code: bank_code}))
|
13
|
+
end
|
14
|
+
|
15
|
+
# Resolve Card BIN
|
16
|
+
# @see https://paystack.com/docs/api/verification/#resolve-card-bin
|
17
|
+
def resolve_card_bin(bin)
|
18
|
+
validate_presence!(value: bin, name: "bin")
|
19
|
+
handle_response(@connection.get("/decision/bin/#{bin}"))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Validate Account
|
23
|
+
# @see https://paystack.com/docs/api/verification/#validate-account
|
24
|
+
# Required: account_number, account_name, account_type, bank_code, country_code, document_type
|
25
|
+
# Optional: document_number
|
26
|
+
def validate_account(params)
|
27
|
+
validate_required_params!(
|
28
|
+
payload: params,
|
29
|
+
required_params: %i[account_number account_name account_type bank_code country_code document_type],
|
30
|
+
operation_name: "Validate Account"
|
31
|
+
)
|
32
|
+
handle_response(@connection.post("/bank/validate", params))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -63,11 +63,11 @@ module PaystackSdk
|
|
63
63
|
# Initializes a new Response object
|
64
64
|
#
|
65
65
|
# @param response [Faraday::Response, Hash, Array] The raw API response or data
|
66
|
-
# @raise [PaystackSdk::APIError] If the API returns an error response
|
67
66
|
# @raise [PaystackSdk::AuthenticationError] If authentication fails (401)
|
68
|
-
# @raise [PaystackSdk::ResourceNotFoundError] If resource is not found (404 or 400 with not found message)
|
69
67
|
# @raise [PaystackSdk::RateLimitError] If rate limit is exceeded (429)
|
70
68
|
# @raise [PaystackSdk::ServerError] If server returns 5xx error
|
69
|
+
# @note API-level errors (4xx except 401) are returned as unsuccessful Response objects
|
70
|
+
# rather than raised as exceptions. Use response.success? to check for errors.
|
71
71
|
def initialize(response)
|
72
72
|
if response.is_a?(Faraday::Response)
|
73
73
|
@status_code = response.status
|
@@ -79,20 +79,25 @@ module PaystackSdk
|
|
79
79
|
case @status_code
|
80
80
|
when 200..299
|
81
81
|
@success = true
|
82
|
-
when 400
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
82
|
+
when 400..499
|
83
|
+
# Client errors - return unsuccessful response for user to handle
|
84
|
+
@success = false
|
85
|
+
@error_message = @api_message || "Client error"
|
86
|
+
|
87
|
+
# Still raise for authentication issues as these are usually config problems
|
88
|
+
if @status_code == 401
|
89
|
+
raise AuthenticationError.new(@api_message || "Authentication failed")
|
90
|
+
end
|
88
91
|
when 429
|
92
|
+
# Rate limiting - raise as users need to implement retry logic
|
89
93
|
retry_after = response.headers["Retry-After"]
|
90
94
|
raise RateLimitError.new(retry_after || 30)
|
91
95
|
when 500..599
|
96
|
+
# Server errors - raise as these indicate Paystack infrastructure issues
|
92
97
|
raise ServerError.new(@status_code, @api_message)
|
93
98
|
else
|
94
99
|
@success = false
|
95
|
-
|
100
|
+
@error_message = @api_message || "Unknown error"
|
96
101
|
end
|
97
102
|
elsif response.is_a?(Response)
|
98
103
|
@success = response.success?
|
@@ -120,6 +125,26 @@ module PaystackSdk
|
|
120
125
|
@success
|
121
126
|
end
|
122
127
|
|
128
|
+
# Check if the response failed
|
129
|
+
#
|
130
|
+
# @return [Boolean] true if the API request failed
|
131
|
+
def failed?
|
132
|
+
!@success
|
133
|
+
end
|
134
|
+
|
135
|
+
# Get error information if the request failed
|
136
|
+
#
|
137
|
+
# @return [Hash] Hash containing error details, or empty hash if successful
|
138
|
+
def error_details
|
139
|
+
return {} if success?
|
140
|
+
|
141
|
+
{
|
142
|
+
status_code: @status_code,
|
143
|
+
message: @error_message || @api_message,
|
144
|
+
raw_response: @body
|
145
|
+
}.compact
|
146
|
+
end
|
147
|
+
|
123
148
|
# Returns the original response body
|
124
149
|
# This is useful for debugging or accessing raw data
|
125
150
|
#
|
@@ -284,28 +309,15 @@ module PaystackSdk
|
|
284
309
|
end
|
285
310
|
end
|
286
311
|
|
287
|
-
def
|
288
|
-
|
289
|
-
@api_message&.match?(/not found|cannot find|does not exist/i)
|
290
|
-
resource_type = determine_resource_type
|
291
|
-
meta_info = @body["meta"]&.dig("nextStep")
|
292
|
-
message = @api_message
|
293
|
-
message = "#{message}\nHint: #{meta_info}" if meta_info
|
294
|
-
raise ResourceNotFoundError.new(resource_type, message)
|
295
|
-
else
|
296
|
-
@success = false
|
297
|
-
raise APIError.new(@api_message || "Paystack API Error")
|
298
|
-
end
|
299
|
-
end
|
312
|
+
def determine_resource_type
|
313
|
+
return "Unknown" unless @body.is_a?(Hash) && @body["code"]
|
300
314
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
315
|
+
if @body["code"].include?("_")
|
316
|
+
parts = @body["code"].to_s.split("_")
|
317
|
+
return parts.last if parts.last != "not_found"
|
318
|
+
end
|
305
319
|
|
306
|
-
|
307
|
-
resource = @body["code"].split("_").first
|
308
|
-
resource.capitalize
|
320
|
+
"unknown"
|
309
321
|
end
|
310
322
|
end
|
311
323
|
end
|
data/lib/paystack_sdk/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxwell Nana Forson (theLazyProgrammer)
|
@@ -121,6 +121,7 @@ files:
|
|
121
121
|
- lib/paystack_sdk/resources/transactions.rb
|
122
122
|
- lib/paystack_sdk/resources/transfer_recipients.rb
|
123
123
|
- lib/paystack_sdk/resources/transfers.rb
|
124
|
+
- lib/paystack_sdk/resources/verification.rb
|
124
125
|
- lib/paystack_sdk/response.rb
|
125
126
|
- lib/paystack_sdk/utils/connection_utils.rb
|
126
127
|
- lib/paystack_sdk/validations.rb
|