paystack_sdk 0.0.9 → 0.1.1
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 +30 -0
- data/README.md +110 -16
- data/lib/paystack_sdk/client.rb +15 -0
- data/lib/paystack_sdk/resources/charges.rb +105 -0
- data/lib/paystack_sdk/resources/customers.rb +2 -6
- data/lib/paystack_sdk/response.rb +45 -35
- data/lib/paystack_sdk/validations.rb +24 -20
- data/lib/paystack_sdk/version.rb +1 -1
- metadata +17 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7771d8c6ca4931f237e71c171c7507cfcd9ca5b6159974ee4a4a37d3d667a0e7
|
4
|
+
data.tar.gz: e2c83863b81cd30f7fed5380f145ccfa283a25ea334a30e33cb86170636f0b50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aeeef1dcbfd223247142cfa6ee3ffb85624f2cc392ba5a993f9224ab5a4166273f386b1bf4a6fc4d0571dd30b3a626f08a1d0234cbe3808124e350c88228bebb
|
7
|
+
data.tar.gz: f8af0ecc7ef80cd7cfd0ab4858365bdce1b244115402869e5b1b29e3a6018816f19224ed0df2483d2fd732d6babd12a75764f4c324cdc55ee32bc35212d83654
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,35 @@
|
|
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
|
+
|
3
33
|
## [0.0.9] - 2025-06-26
|
4
34
|
|
5
35
|
### 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
@@ -6,6 +6,7 @@ require_relative "resources/transfer_recipients"
|
|
6
6
|
require_relative "resources/transfers"
|
7
7
|
require_relative "resources/banks"
|
8
8
|
require_relative "resources/verification"
|
9
|
+
require_relative "resources/charges"
|
9
10
|
require_relative "utils/connection_utils"
|
10
11
|
|
11
12
|
module PaystackSdk
|
@@ -121,5 +122,19 @@ module PaystackSdk
|
|
121
122
|
def verification
|
122
123
|
@verification ||= Resources::Verification.new(@connection)
|
123
124
|
end
|
125
|
+
|
126
|
+
# Provides access to the `Charges` resource.
|
127
|
+
#
|
128
|
+
# @return [PaystackSdk::Resources::Charges] An instance of the
|
129
|
+
# `Charges` resource.
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
# ```ruby
|
133
|
+
# charges = client.charges
|
134
|
+
# response = charges.mobile_money(payload)
|
135
|
+
# ```
|
136
|
+
def charges
|
137
|
+
@charges ||= Resources::Charges.new(@connection)
|
138
|
+
end
|
124
139
|
end
|
125
140
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module PaystackSdk
|
6
|
+
module Resources
|
7
|
+
# The `Charges` resource exposes helpers for initiating and managing charges
|
8
|
+
# through alternative payment channels such as Mobile Money.
|
9
|
+
#
|
10
|
+
# At the moment the SDK focuses on supporting the Mobile Money channel which
|
11
|
+
# requires posting to the `/charge` endpoint with the customer's email,
|
12
|
+
# amount, currency, and the provider specific `mobile_money` payload.
|
13
|
+
class Charges < PaystackSdk::Resources::Base
|
14
|
+
MOBILE_MONEY_PROVIDERS = %w[mtn atl vod mpesa orange wave].freeze
|
15
|
+
|
16
|
+
# Initiates a Mobile Money payment.
|
17
|
+
#
|
18
|
+
# @param payload [Hash] The payload containing charge details.
|
19
|
+
# @option payload [String] :email Customer's email address (required)
|
20
|
+
# @option payload [Integer] :amount Amount in the lowest currency unit (required)
|
21
|
+
# @option payload [String] :currency ISO currency code (default: GHS)
|
22
|
+
# @option payload [String] :reference Optional reference supplied by the merchant
|
23
|
+
# @option payload [String] :callback_url Optional callback URL for Paystack to redirect to
|
24
|
+
# @option payload [Hash] :metadata Optional metadata to attach to the transaction
|
25
|
+
# @option payload [Hash] :mobile_money The mobile money details (required)
|
26
|
+
# - :phone [String] Customer's mobile money phone number (required)
|
27
|
+
# - :provider [String] Mobile money provider code (required)
|
28
|
+
#
|
29
|
+
# @return [PaystackSdk::Response] The wrapped API response.
|
30
|
+
# @raise [PaystackSdk::ValidationError] If the payload is invalid.
|
31
|
+
def mobile_money(payload)
|
32
|
+
validate_mobile_money_payload!(payload)
|
33
|
+
|
34
|
+
normalized_payload = normalize_mobile_money_provider(payload)
|
35
|
+
response = @connection.post("/charge", normalized_payload)
|
36
|
+
handle_response(response)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Submits an OTP for authorising a pending Mobile Money charge (e.g. Vodafone).
|
40
|
+
#
|
41
|
+
# @param payload [Hash] Payload containing the OTP and charge reference.
|
42
|
+
# @option payload [String] :otp The OTP supplied by the customer (required)
|
43
|
+
# @option payload [String] :reference The charge reference returned from initiation (required)
|
44
|
+
#
|
45
|
+
# @return [PaystackSdk::Response] The wrapped API response.
|
46
|
+
# @raise [PaystackSdk::ValidationError] If the payload is invalid.
|
47
|
+
def submit_otp(payload)
|
48
|
+
validate_fields!(
|
49
|
+
payload: payload,
|
50
|
+
validations: {
|
51
|
+
otp: {type: :string, required: true},
|
52
|
+
reference: {type: :reference, required: true}
|
53
|
+
}
|
54
|
+
)
|
55
|
+
|
56
|
+
response = @connection.post("/charge/submit_otp", payload)
|
57
|
+
handle_response(response)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def validate_mobile_money_payload!(payload)
|
63
|
+
validate_fields!(
|
64
|
+
payload: payload,
|
65
|
+
validations: {
|
66
|
+
email: {type: :email, required: true},
|
67
|
+
amount: {type: :positive_integer, required: true},
|
68
|
+
currency: {type: :currency, required: false},
|
69
|
+
reference: {type: :reference, required: false},
|
70
|
+
callback_url: {required: false},
|
71
|
+
metadata: {required: false},
|
72
|
+
mobile_money: {required: true}
|
73
|
+
}
|
74
|
+
)
|
75
|
+
|
76
|
+
mobile_money = payload[:mobile_money] || payload["mobile_money"]
|
77
|
+
validate_hash!(input: mobile_money, name: "mobile_money")
|
78
|
+
|
79
|
+
phone = mobile_money[:phone] || mobile_money["phone"]
|
80
|
+
validate_presence!(value: phone, name: "mobile_money phone")
|
81
|
+
|
82
|
+
provider = mobile_money[:provider] || mobile_money["provider"]
|
83
|
+
validate_mobile_money_provider!(provider)
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate_mobile_money_provider!(provider)
|
87
|
+
normalized = provider&.to_s&.downcase
|
88
|
+
validate_allowed_values!(
|
89
|
+
value: normalized,
|
90
|
+
allowed_values: MOBILE_MONEY_PROVIDERS,
|
91
|
+
name: "mobile_money provider",
|
92
|
+
allow_nil: false
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def normalize_mobile_money_provider(payload)
|
97
|
+
mm = payload[:mobile_money] || payload["mobile_money"] || {}
|
98
|
+
provider = mm[:provider] || mm["provider"]
|
99
|
+
normalized_provider = provider&.to_s&.downcase
|
100
|
+
# Avoid mutating the caller's payload
|
101
|
+
payload.merge(mobile_money: mm.merge(provider: normalized_provider))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -68,13 +68,9 @@ module PaystackSdk
|
|
68
68
|
validate_positive_integer!(value: per_page, name: "per_page", allow_nil: true)
|
69
69
|
validate_positive_integer!(value: page, name: "page", allow_nil: true)
|
70
70
|
|
71
|
-
if params[:from]
|
72
|
-
validate_date_format!(date_str: params[:from], name: "from")
|
73
|
-
end
|
71
|
+
validate_date_format!(date_str: params[:from], name: "from") if params[:from]
|
74
72
|
|
75
|
-
if params[:to]
|
76
|
-
validate_date_format!(date_str: params[:to], name: "to")
|
77
|
-
end
|
73
|
+
validate_date_format!(date_str: params[:to], name: "to") if params[:to]
|
78
74
|
|
79
75
|
query_params = {perPage: per_page, page: page}.merge(params)
|
80
76
|
response = @connection.get("customer", query_params)
|
@@ -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,23 @@ 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
|
+
raise AuthenticationError.new(@api_message || "Authentication failed") if @status_code == 401
|
88
89
|
when 429
|
90
|
+
# Rate limiting - raise as users need to implement retry logic
|
89
91
|
retry_after = response.headers["Retry-After"]
|
90
92
|
raise RateLimitError.new(retry_after || 30)
|
91
93
|
when 500..599
|
94
|
+
# Server errors - raise as these indicate Paystack infrastructure issues
|
92
95
|
raise ServerError.new(@status_code, @api_message)
|
93
96
|
else
|
94
97
|
@success = false
|
95
|
-
|
98
|
+
@error_message = @api_message || "Unknown error"
|
96
99
|
end
|
97
100
|
elsif response.is_a?(Response)
|
98
101
|
@success = response.success?
|
@@ -120,6 +123,26 @@ module PaystackSdk
|
|
120
123
|
@success
|
121
124
|
end
|
122
125
|
|
126
|
+
# Check if the response failed
|
127
|
+
#
|
128
|
+
# @return [Boolean] true if the API request failed
|
129
|
+
def failed?
|
130
|
+
!@success
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get error information if the request failed
|
134
|
+
#
|
135
|
+
# @return [Hash] Hash containing error details, or empty hash if successful
|
136
|
+
def error_details
|
137
|
+
return {} if success?
|
138
|
+
|
139
|
+
{
|
140
|
+
status_code: @status_code,
|
141
|
+
message: @error_message || @api_message,
|
142
|
+
raw_response: @body
|
143
|
+
}.compact
|
144
|
+
end
|
145
|
+
|
123
146
|
# Returns the original response body
|
124
147
|
# This is useful for debugging or accessing raw data
|
125
148
|
#
|
@@ -186,7 +209,7 @@ module PaystackSdk
|
|
186
209
|
# @yield [key, value] For hashes, passes each key-value pair
|
187
210
|
# @yield [value] For arrays, passes each item
|
188
211
|
# @return [Response, Enumerator] Self for chaining or Enumerator if no block given
|
189
|
-
def each
|
212
|
+
def each
|
190
213
|
return enum_for(:each) unless block_given?
|
191
214
|
|
192
215
|
if @raw_data.is_a?(Hash)
|
@@ -206,7 +229,7 @@ module PaystackSdk
|
|
206
229
|
# @return [Integer] The number of items
|
207
230
|
# @!method empty?
|
208
231
|
# @return [Boolean] Whether the collection is empty
|
209
|
-
[
|
232
|
+
%i[size length count empty?].each do |method_name|
|
210
233
|
define_method(method_name) do
|
211
234
|
@raw_data.send(method_name) if @raw_data.respond_to?(method_name)
|
212
235
|
end
|
@@ -217,9 +240,10 @@ module PaystackSdk
|
|
217
240
|
# @return [Object, Response] The first item, wrapped if necessary
|
218
241
|
# @!method last
|
219
242
|
# @return [Object, Response] The last item, wrapped if necessary
|
220
|
-
[
|
243
|
+
%i[first last].each do |method_name|
|
221
244
|
define_method(method_name) do
|
222
245
|
return nil unless @raw_data.is_a?(Array)
|
246
|
+
|
223
247
|
wrap_value(@raw_data.send(method_name))
|
224
248
|
end
|
225
249
|
end
|
@@ -236,9 +260,7 @@ module PaystackSdk
|
|
236
260
|
|
237
261
|
# First try to get identifier from the message
|
238
262
|
message = body["message"].to_s.downcase
|
239
|
-
if message =~ /with (id|code|reference|email): ([^\s]+)/i
|
240
|
-
return $2
|
241
|
-
end
|
263
|
+
return ::Regexp.last_match(2) if message =~ /with (id|code|reference|email): ([^\s]+)/i
|
242
264
|
|
243
265
|
# If not found in message, try to extract from error code
|
244
266
|
if body["code"]&.match?(/^(transaction|customer)_/)
|
@@ -263,6 +285,7 @@ module PaystackSdk
|
|
263
285
|
# @return [Hash, Array, nil] The data from the response
|
264
286
|
def extract_data_from_body(body)
|
265
287
|
return body unless body.is_a?(Hash)
|
288
|
+
|
266
289
|
body["data"] || body
|
267
290
|
end
|
268
291
|
|
@@ -284,28 +307,15 @@ module PaystackSdk
|
|
284
307
|
end
|
285
308
|
end
|
286
309
|
|
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
|
310
|
+
def determine_resource_type
|
311
|
+
return "Unknown" unless @body.is_a?(Hash) && @body["code"]
|
300
312
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
313
|
+
if @body["code"].include?("_")
|
314
|
+
parts = @body["code"].to_s.split("_")
|
315
|
+
return parts.last if parts.last != "not_found"
|
316
|
+
end
|
305
317
|
|
306
|
-
|
307
|
-
resource = @body["code"].split("_").first
|
308
|
-
resource.capitalize
|
318
|
+
"unknown"
|
309
319
|
end
|
310
320
|
end
|
311
321
|
end
|
@@ -37,9 +37,9 @@ module PaystackSdk
|
|
37
37
|
# @param name [String] Name of the parameter for error messages
|
38
38
|
# @raise [PaystackSdk::InvalidFormatError] If input is not a hash
|
39
39
|
def validate_hash!(input:, name: "Payload")
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
return if input.is_a?(Hash)
|
41
|
+
|
42
|
+
raise PaystackSdk::InvalidFormatError.new(name, "Hash")
|
43
43
|
end
|
44
44
|
|
45
45
|
# Validates that required parameters are present in a payload.
|
@@ -53,10 +53,10 @@ module PaystackSdk
|
|
53
53
|
!payload.key?(param) && !payload.key?(param.to_s)
|
54
54
|
end
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
return if missing_params.empty?
|
57
|
+
|
58
|
+
param = missing_params.first
|
59
|
+
raise PaystackSdk::MissingParamError.new(param)
|
60
60
|
end
|
61
61
|
|
62
62
|
# Validates that a value is present (not nil or empty).
|
@@ -91,9 +91,9 @@ module PaystackSdk
|
|
91
91
|
# @param name [String] Name of the parameter for error messages
|
92
92
|
# @raise [PaystackSdk::InvalidFormatError] If reference format is invalid
|
93
93
|
def validate_reference_format!(reference:, name: "Reference")
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
return if reference.to_s.match?(/^[a-zA-Z0-9._=-]+$/)
|
95
|
+
|
96
|
+
raise PaystackSdk::InvalidFormatError.new(name, "alphanumeric characters and the following: -, ., =")
|
97
97
|
end
|
98
98
|
|
99
99
|
# Validates a date string format.
|
@@ -106,6 +106,7 @@ module PaystackSdk
|
|
106
106
|
def validate_date_format!(date_str:, name: "Date", allow_nil: true)
|
107
107
|
if date_str.nil?
|
108
108
|
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
109
|
+
|
109
110
|
return
|
110
111
|
end
|
111
112
|
|
@@ -136,13 +137,14 @@ module PaystackSdk
|
|
136
137
|
def validate_allowed_values!(value:, allowed_values:, name: "Parameter", allow_nil: true)
|
137
138
|
if value.nil?
|
138
139
|
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
140
|
+
|
139
141
|
return
|
140
142
|
end
|
141
143
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
144
|
+
return if allowed_values.include?(value)
|
145
|
+
|
146
|
+
allowed_list = allowed_values.join(", ")
|
147
|
+
raise PaystackSdk::InvalidValueError.new(name, "must be one of: #{allowed_list}")
|
146
148
|
end
|
147
149
|
|
148
150
|
# Validates an email format.
|
@@ -154,12 +156,13 @@ module PaystackSdk
|
|
154
156
|
def validate_email!(email:, name: "Email", allow_nil: false)
|
155
157
|
if email.nil?
|
156
158
|
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
159
|
+
|
157
160
|
return
|
158
161
|
end
|
159
162
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
+
return if email.to_s.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
|
164
|
+
|
165
|
+
raise PaystackSdk::InvalidFormatError.new(name, "valid email address")
|
163
166
|
end
|
164
167
|
|
165
168
|
# Validates a currency code format.
|
@@ -171,12 +174,13 @@ module PaystackSdk
|
|
171
174
|
def validate_currency!(currency:, name: "Currency", allow_nil: true)
|
172
175
|
if currency.nil?
|
173
176
|
raise PaystackSdk::MissingParamError.new(name) unless allow_nil
|
177
|
+
|
174
178
|
return
|
175
179
|
end
|
176
180
|
|
177
|
-
|
178
|
-
|
179
|
-
|
181
|
+
return if currency.to_s.match?(/\A[A-Z]{3}\z/)
|
182
|
+
|
183
|
+
raise PaystackSdk::InvalidFormatError.new(name, "3-letter ISO code (e.g., NGN, USD, GHS)")
|
180
184
|
end
|
181
185
|
|
182
186
|
# Validates multiple fields at once.
|
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.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxwell Nana Forson (theLazyProgrammer)
|
@@ -24,75 +24,75 @@ dependencies:
|
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: 2.13.1
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
27
|
+
name: debug
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
30
|
- - "~>"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 1.9.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
39
|
+
version: 1.9.0
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
|
-
name:
|
41
|
+
name: irb
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 1.
|
46
|
+
version: 1.15.1
|
47
47
|
type: :development
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 1.
|
53
|
+
version: 1.15.1
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
|
-
name:
|
55
|
+
name: rake
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
60
|
+
version: 13.2.1
|
61
61
|
type: :development
|
62
62
|
prerelease: false
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
67
|
+
version: 13.2.1
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
|
-
name:
|
69
|
+
name: rspec
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: 13
|
74
|
+
version: '3.13'
|
75
75
|
type: :development
|
76
76
|
prerelease: false
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: 13
|
81
|
+
version: '3.13'
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
|
-
name:
|
83
|
+
name: standard
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: 1.
|
88
|
+
version: 1.49.0
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: 1.
|
95
|
+
version: 1.49.0
|
96
96
|
description: |
|
97
97
|
The `paystack_sdk` gem provides a simple and intuitive interface for
|
98
98
|
interacting with Paystack's payment gateway API. It allows developers to
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- lib/paystack_sdk/client.rb
|
118
118
|
- lib/paystack_sdk/resources/banks.rb
|
119
119
|
- lib/paystack_sdk/resources/base.rb
|
120
|
+
- lib/paystack_sdk/resources/charges.rb
|
120
121
|
- lib/paystack_sdk/resources/customers.rb
|
121
122
|
- lib/paystack_sdk/resources/transactions.rb
|
122
123
|
- lib/paystack_sdk/resources/transfer_recipients.rb
|