paystack_sdk 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfe0137a6cc918043bc34e2be6dcb579713ef3dfec0641763234e66d51e08717
4
- data.tar.gz: a331be86e1c23da3621d4b6a1d6d6c5ecfa31de7685c3776872b3ff6f3adc99d
3
+ metadata.gz: bcb48b6fccd1a9125723781ac1f6d7adcf116459890587132dd12c516325a4aa
4
+ data.tar.gz: 175a50985ee81851b541efbc24c8c8075f8ecdf082e12b14a7736a248fbbca02
5
5
  SHA512:
6
- metadata.gz: de4c777e2d89ce4cf1e30a13a352d4dab8bee5696716af0eb771964ce3aad3a10b0a6381c67b64c42dea342954c5e6d69d05b75fa43e72c288f55eeb45192a7d
7
- data.tar.gz: cc889820541066ea486dfe9b4aa86d1009c692efd7c1fa52929a0791ae1f954ecabd0c66bb75b733bae0af44398e6687004867a78dc64bf7f33b33ec81eaebbc
6
+ metadata.gz: 6a49b817e05714ee17fc0f950385cb9af46209fd3fc7b58f69d012a08bcaf2540c1b0fa45a5d7398bd4d38eb1abd56e65bbec815dbc5e8ea1e53ebecb1799977
7
+ data.tar.gz: d0b9948e467e520ee7e1b636c9ca0865a6ee0f8ff804dac7445ba7e939964b657bc7dab66a7fedba633212b5ab39c52fba69ea28a3a351796bca225f11042389
data/.standard.yml CHANGED
@@ -1,3 +1,5 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/standardrb/standard
3
3
  ruby_version: 3.2.2
4
+ fix: true
5
+ format: progress
data/README.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  The `paystack_sdk` gem provides a simple and intuitive interface for interacting with Paystack's payment gateway API. It allows developers to easily integrate Paystack's payment processing features into their Ruby applications. With support for various endpoints, this SDK simplifies tasks such as initiating transactions, verifying payments, managing customers, and more.
4
4
 
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Usage](#usage)
10
+ - [Client Initialization](#client-initialization)
11
+ - [Transactions](#transactions)
12
+ - [Initialize a Transaction](#initialize-a-transaction)
13
+ - [Verify a Transaction](#verify-a-transaction)
14
+ - [List Transactions](#list-transactions)
15
+ - [Fetch a Transaction](#fetch-a-transaction)
16
+ - [Get Transaction Totals](#get-transaction-totals)
17
+ - [Response Handling](#response-handling)
18
+ - [Working with Response Objects](#working-with-response-objects)
19
+ - [Accessing the Original Response](#accessing-the-original-response)
20
+ - [Error Handling](#error-handling)
21
+ - [Advanced Usage](#advanced-usage)
22
+ - [Environment Variables](#environment-variables)
23
+ - [Direct Resource Instantiation](#direct-resource-instantiation)
24
+ - [Development](#development)
25
+ - [Contributing](#contributing)
26
+ - [License](#license)
27
+ - [Code of Conduct](#code-of-conduct)
28
+
5
29
  ## Installation
6
30
 
7
31
  Add this line to your application's Gemfile:
@@ -22,52 +46,281 @@ Or install it yourself as:
22
46
  gem install paystack_sdk
23
47
  ```
24
48
 
49
+ ## Quick Start
50
+
51
+ ```ruby
52
+ require 'paystack_sdk'
53
+
54
+ # Initialize the client with your secret key
55
+ paystack = PaystackSdk::Client.new(secret_key: "sk_test_xxx")
56
+
57
+ # Initialize a transaction
58
+ params = {
59
+ email: "customer@email.com",
60
+ amount: 2300, # Amount in the smallest currency unit (kobo for NGN)
61
+ currency: "NGN"
62
+ }
63
+
64
+ response = paystack.transactions.initiate(params)
65
+
66
+ if response.success?
67
+ puts "Visit this URL to complete payment: #{response.authorization_url}"
68
+ else
69
+ puts "Error: #{response.error_message}"
70
+ end
71
+ ```
72
+
25
73
  ## Usage
26
74
 
27
- Here’s a basic example of how to use the `paystack_sdk` gem:
75
+ ### Client Initialization
28
76
 
29
77
  ```ruby
30
- require 'paystack_sdk'
78
+ # Initialize with your Paystack secret key
79
+ paystack = PaystackSdk::Client.new(secret_key: "sk_test_xxx")
80
+
81
+ # You can access the connection directly if needed
82
+ connection = paystack.connection
83
+ ```
84
+
85
+ ### Transactions
31
86
 
32
- # Initialize the SDK with your Paystack secret key
33
- paystack = PaystackSdk::Client.new(api_key: "sk_test_xxx")
87
+ The SDK provides comprehensive support for Paystack's Transaction API.
34
88
 
35
- # Example: Initialize a payment
36
- params = {email: "customer@email.com", amount: "2300", currency: "USD"}
37
- response = paystack.transactions.initialize_transaction(params)
89
+ #### Initialize a Transaction
90
+
91
+ ```ruby
92
+ # Prepare transaction parameters
93
+ params = {
94
+ email: "customer@example.com",
95
+ amount: 10000, # Amount in the smallest currency unit (e.g., kobo, pesewas, cents)
96
+ currency: "GHS",
97
+ callback_url: "https://example.com/callback"
98
+ }
99
+
100
+ # Initialize the transaction
101
+ response = paystack.transactions.initiate(params)
38
102
 
39
103
  if response.success?
40
- puts response.authorization_url
104
+ puts "Transaction initialized successfully!"
105
+ puts "Authorization URL: #{response.authorization_url}"
106
+ puts "Access Code: #{response.access_code}"
107
+ puts "Reference: #{response.reference}"
41
108
  else
42
- puts resposne.error_message
109
+ puts "Error: #{response.error_message}"
43
110
  end
111
+ ```
112
+
113
+ #### Verify a Transaction
44
114
 
45
- # Example: Verify a payment
115
+ ```ruby
116
+ # Verify using transaction reference
46
117
  response = paystack.transactions.verify(reference: "transaction_reference")
118
+
119
+ if response.success?
120
+ transaction = response.data
121
+ puts "Transaction verified successfully!"
122
+ puts "Status: #{transaction.status}"
123
+ puts "Amount: #{transaction.amount}"
124
+ puts "Currency: #{transaction.currency}"
125
+ puts "Customer Email: #{transaction.customer.email}"
126
+
127
+ # Check specific transaction status
128
+ case transaction.status
129
+ when "success"
130
+ puts "Payment successful!"
131
+ when "pending"
132
+ puts "Payment is pending."
133
+ else
134
+ puts "Current status: #{transaction.status}"
135
+ end
136
+ else
137
+ puts "Verification failed: #{response.error_message}"
138
+ end
139
+ ```
140
+
141
+ #### List Transactions
142
+
143
+ ```ruby
144
+ # Get all transactions (default pagination: 50 per page)
145
+ response = paystack.transactions.list
146
+
147
+ # With custom pagination
148
+ response = paystack.transactions.list(per_page: 20, page: 2)
149
+
150
+ # With additional filters
151
+ response = paystack.transactions.list(
152
+ per_page: 10,
153
+ page: 1,
154
+ from: "2025-01-01",
155
+ to: "2025-04-30",
156
+ status: "success"
157
+ )
158
+
159
+ if response.success?
160
+ puts "Total transactions: #{response.count}" # response.size is another way
161
+
162
+ response.data.each do |transaction|
163
+ puts "ID: #{transaction.id}"
164
+ puts "Reference: #{transaction.reference}"
165
+ puts "Amount: #{transaction.amount}"
166
+ puts "----------------"
167
+ end
168
+
169
+ # Get the first transaction
170
+ first_transaction = response.data.first
171
+ puts "First transaction reference: #{first_transaction.reference}"
172
+
173
+ # Get the last transaction
174
+ last_transaction = response.data.last
175
+ puts "Last transaction amount: #{last_transaction.amount}"
176
+ else
177
+ puts "Error: #{response.error_message}"
178
+ end
179
+ ```
180
+
181
+ #### Fetch a Transaction
182
+
183
+ ```ruby
184
+ # Fetch a specific transaction by ID
185
+ transaction_id = "12345"
186
+ response = paystack.transactions.fetch(transaction_id)
187
+
188
+ if response.success?
189
+ transaction = response.data
190
+ puts "Transaction details:"
191
+ puts "ID: #{transaction.id}"
192
+ puts "Reference: #{transaction.reference}"
193
+ puts "Amount: #{transaction.amount}"
194
+ puts "Status: #{transaction.status}"
195
+
196
+ # Access customer information
197
+ puts "Customer Email: #{transaction.customer.email}"
198
+ puts "Customer Name: #{transaction.customer.name}"
199
+ else
200
+ puts "Error: #{response.error_message}"
201
+ end
202
+ ```
203
+
204
+ #### Get Transaction Totals
205
+
206
+ ```ruby
207
+ # Get transaction volume and success metrics
208
+ response = paystack.transactions.totals
209
+
47
210
  if response.success?
48
- puts "Payment verified successfully!"
211
+ puts "Total Transactions: #{response.data.total_transactions}"
212
+ puts "Total Volume: #{response.data.total_volume}"
213
+ puts "Pending Transfers: #{response.data.pending_transfers}"
49
214
  else
50
- puts "Payment verification failed: #{response.error_message}"
215
+ puts "Error: #{response.error_message}"
51
216
  end
52
217
  ```
53
218
 
54
- ### The Orginal Response
219
+ ### Response Handling
220
+
221
+ #### Working with Response Objects
222
+
223
+ All API requests return a `PaystackSdk::Response` object that provides easy access to the response data.
224
+
225
+ ```ruby
226
+ response = paystack.transactions.initiate(params)
227
+
228
+ # Check if the request was successful
229
+ response.success? # => true or false
230
+
231
+ # Access response message
232
+ response.api_message # => "Authorization URL created"
233
+
234
+ # Access data using dot notation
235
+ response.data.authorization_url
236
+ response.data.access_code
55
237
 
56
- There will be times you may need access to the original API response. For such cases, you
57
- can use the `#original_response` method on the response object.
238
+ # Access data directly from the response
239
+ response.authorization_url # Same as response.data.authorization_url
58
240
 
59
- The return value is a hash with all the values from the HTTP request. This could be useful
60
- when you need to debug or gain access to the response its raw state.
241
+ # Access nested data
242
+ response.data.customer.email
243
+
244
+ # For arrays, use array methods
245
+ response.data.first # First item in an array
246
+ response.data.last # Last item in an array
247
+ response.data.size # Size of the array
248
+
249
+ # Iterate through array data
250
+ response.data.each do |item|
251
+ puts item.id
252
+ end
253
+ ```
254
+
255
+ #### Accessing the Original Response
256
+
257
+ Sometimes you may need access to the original API response:
61
258
 
62
- For example
63
259
  ```ruby
64
- response = transaction.list
260
+ response = paystack.transactions.list
261
+
262
+ # Access the original response body
263
+ original = response.original_response
264
+
265
+ # Access metadata from the original response
266
+ total_count = original.dig("meta", "total")
267
+ current_page = original.dig("meta", "page")
268
+ ```
269
+
270
+ #### Error Handling
271
+
272
+ ```ruby
273
+ response = paystack.transactions.verify(reference: "invalid_reference")
274
+
275
+ unless response.success?
276
+ puts "Error: #{response.error_message}"
277
+
278
+ # Take action based on the error
279
+ if response.error_message.include?("not found")
280
+ puts "The transaction reference was not found."
281
+ elsif response.error_message.include?("Invalid key")
282
+ puts "API authentication failed. Check your API key."
283
+ end
284
+ end
285
+ ```
286
+
287
+ ## Advanced Usage
288
+
289
+ ### Environment Variables
290
+
291
+ You can use environment variables to configure the SDK:
292
+
293
+ ```ruby
294
+ # Set the PAYSTACK_SECRET_KEY environment variable
295
+ ENV["PAYSTACK_SECRET_KEY"] = "sk_test_xxx"
296
+
297
+ # Then initialize resources without specifying the key
298
+ transactions = PaystackSdk::Resources::Transactions.new
299
+ ```
300
+
301
+ ### Direct Resource Instantiation
302
+
303
+ For more advanced usage, you can instantiate resource classes directly:
304
+
305
+ ```ruby
306
+ # With a secret key
307
+ transactions = PaystackSdk::Resources::Transactions.new(secret_key: "sk_test_xxx")
308
+
309
+ # With an existing Faraday connection
310
+ connection = Faraday.new(url: "https://api.paystack.co") do |conn|
311
+ # Configure the connection
312
+ end
65
313
 
66
- puts response.original_response # => This will return the exact response received from Paystack
314
+ # The secret key can be omitted if set in an environment
315
+ transactions = PaystackSdk::Resources::Transactions.new(connection, secret_key:)
67
316
  ```
68
317
 
318
+ For more detailed documentation on specific resources, please refer to the following guides:
69
319
 
70
- Refer to the [documentation](https://github.com/nanafox/paystack_sdk) for more detailed usage examples and supported endpoints.
320
+ - [Transactions](https://paystack.com/docs/api/transaction/)
321
+ - [Customers](https://paystack.com/docs/api/customer/)
322
+ - [Plans](https://paystack.com/docs/api/plan/)
323
+ - [Subscriptions](https://paystack.com/docs/api/subscription/)
71
324
 
72
325
  ## Development
73
326
 
@@ -9,17 +9,20 @@ module PaystackSdk
9
9
  # The base URL for the Paystack API.
10
10
  BASE_URL = "https://api.paystack.co"
11
11
 
12
+ # @return [Faraday::Connection] The Faraday connection object used for API requests
13
+ attr_reader :connection
14
+
12
15
  # Initializes a new `Client` instance.
13
16
  #
14
- # @param api_key [String] The secret API key for authenticating with the Paystack API.
17
+ # @param secret_key [String] The secret API key for authenticating with the Paystack API.
15
18
  #
16
19
  # @example
17
- # client = PaystackSdk::Client.new(api_key: "sk_test_xxx")
18
- def initialize(api_key:)
20
+ # client = PaystackSdk::Client.new(secret_key: "sk_test_xxx")
21
+ def initialize(secret_key:)
19
22
  @connection = Faraday.new(url: BASE_URL) do |conn|
20
23
  conn.request :json
21
24
  conn.response :json, content_type: /\bjson$/
22
- conn.headers["Authorization"] = "Bearer #{api_key}"
25
+ conn.headers["Authorization"] = "Bearer #{secret_key}"
23
26
  conn.headers["Content-Type"] = "application/json"
24
27
  conn.headers["User-Agent"] = "paystack_sdk/#{PaystackSdk::VERSION}"
25
28
  conn.adapter Faraday.default_adapter
@@ -32,8 +35,10 @@ module PaystackSdk
32
35
  # `Transactions` resource.
33
36
  #
34
37
  # @example
38
+ # ```ruby
35
39
  # transactions = client.transactions
36
- # response = transactions.initialize_transaction(params)
40
+ # response = transactions.initiate(params)
41
+ # ```
37
42
  def transactions
38
43
  @transactions ||= Resources::Transactions.new(@connection)
39
44
  end
@@ -1,25 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../response"
4
+ require_relative "../client"
5
+ require_relative "../validations"
4
6
 
5
7
  module PaystackSdk
6
8
  module Resources
7
9
  # The `Base` class serves as a parent class for all resource classes in the SDK.
8
10
  # It provides shared functionality, such as handling API responses.
9
11
  class Base
12
+ # Include validation methods
13
+ include PaystackSdk::Validations
14
+
10
15
  # Initializes a new `Base` instance.
11
16
  #
12
- # @param connection [Faraday::Connection] The Faraday connection object used for API requests.
13
- def initialize(connection)
14
- @connection = connection
17
+ # @param connection [Faraday::Connection, nil] The Faraday connection object used for API requests.
18
+ # If nil, a new connection will be created using the default API key.
19
+ # @param secret_key [String, nil] Optional API key to use for creating a new connection.
20
+ # Only used if connection is nil.
21
+ #
22
+ # @example With an existing connection
23
+ # connection = Faraday.new(...)
24
+ # resource = PaystackSdk::Resources::SomeResource.new(connection)
25
+ #
26
+ # @example With an API key
27
+ # resource = PaystackSdk::Resources::SomeResource.new(secret_key: "sk_test_xxx")
28
+ #
29
+ # @example With default connection (requires PAYSTACK_SECRET_KEY environment variable)
30
+ # resource = PaystackSdk::Resources::SomeResource.new
31
+ def initialize(connection = nil, secret_key: nil)
32
+ @connection = if connection
33
+ connection
34
+ elsif secret_key
35
+ create_connection(secret_key:)
36
+ else
37
+ # Try to get API key from environment variable
38
+ env_secret_key = ENV["PAYSTACK_SECRET_KEY"]
39
+ raise PaystackSdk::Error, "No connection or API key provided" unless env_secret_key
40
+
41
+ create_connection(secret_key: env_secret_key)
42
+ end
15
43
  end
16
44
 
17
45
  private
18
46
 
19
- # Handles the API response, raising an error if the response is unsuccessful.
47
+ # Creates a new Faraday connection with the Paystack API.
48
+ #
49
+ # @param secret_key [String] The secret API key for authenticating with the Paystack API.
50
+ # @return [Faraday::Connection] A configured Faraday connection.
51
+ def create_connection(secret_key:)
52
+ Faraday.new(url: PaystackSdk::Client::BASE_URL) do |conn|
53
+ conn.request :json
54
+ conn.response :json, content_type: /\bjson$/
55
+ conn.headers["Authorization"] = "Bearer #{secret_key}"
56
+ conn.headers["Content-Type"] = "application/json"
57
+ conn.headers["User-Agent"] = "paystack_sdk/#{PaystackSdk::VERSION}"
58
+ conn.adapter Faraday.default_adapter
59
+ end
60
+ end
61
+
62
+ # Handles the API response, wrapping it in a Response object.
20
63
  #
21
64
  # @param response [Faraday::Response] The response object returned by the Faraday connection.
22
- # @return [PaystackSdk::Response] The parsed response body wrapped in a `PaystackSdk::Response` object if the request was successful.
65
+ # @return [PaystackSdk::Response] The parsed response body wrapped in a `PaystackSdk::Response` object.
23
66
  # @raise [PaystackSdk::Error] If the response indicates an error.
24
67
  def handle_response(response)
25
68
  PaystackSdk::Response.new(response)
@@ -12,20 +12,25 @@ module PaystackSdk
12
12
  #
13
13
  # Example usage:
14
14
  # ```ruby
15
- # transactions = PaystackSdk::Resources::Transactions.new(connection)
15
+ # transactions = PaystackSdk::Resources::Transactions.new(secret_key:)
16
16
  #
17
17
  # # Initialize a transaction
18
18
  # payload = { email: "customer@email.com", amount: 10000, currency: "GHS" }
19
- # response = transactions.initialize_transaction(payload)
19
+ # response = transactions.initiate(payload)
20
20
  # if response.success?
21
21
  # puts "Transaction initialized successfully."
22
- # puts "Authorization URL: #{response.data.authorization_url}"
22
+ # puts "Authorization URL: #{response.authorization_url}"
23
23
  # else
24
24
  # puts "Error initializing transaction: #{response.error_message}"
25
25
  # end
26
26
  #
27
27
  # # Verify a transaction
28
28
  # response = transactions.verify(reference: "transaction_reference")
29
+ # if response.status == "success"
30
+ # puts "The payment with reference '#{response.reference}' is verified"
31
+ # else
32
+ # puts "Current status: #{response.status}"
33
+ # end
29
34
  #
30
35
  # # List transactions
31
36
  # response = transactions.list(per_page: 50, page: 1)
@@ -40,14 +45,26 @@ module PaystackSdk
40
45
  # Initializes a new transaction.
41
46
  #
42
47
  # @param payload [Hash] The payload containing transaction details (e.g., email, amount, currency).
43
- # @return [Hash] The response from the Paystack API.
48
+ # @return [PaystackSdk::Response] The response from the Paystack API.
44
49
  # @raise [PaystackSdk::Error] If the payload is invalid or the API request fails.
45
50
  #
46
51
  # @example
52
+ # ```ruby
47
53
  # payload = { email: "customer@email.com", amount: 10000, currency: "GHS" }
48
- # response = transactions.initialize_transaction(payload)
54
+ # response = transactions.initiate(payload)
55
+ # ```
49
56
  def initiate(payload)
50
- raise PaystackSdk::Error, "Payload must be a hash" unless payload.is_a?(Hash)
57
+ validate_fields!(
58
+ payload: payload,
59
+ validations: {
60
+ email: {type: :email, required: true},
61
+ amount: {type: :positive_integer, required: true},
62
+ currency: {type: :currency, required: false},
63
+ reference: {type: :reference, required: false},
64
+ callback_url: {required: false}
65
+ }
66
+ )
67
+
51
68
  response = @connection.post("/transaction/initialize", payload)
52
69
  handle_response(response)
53
70
  end
@@ -55,12 +72,14 @@ module PaystackSdk
55
72
  # Verifies a transaction using its reference.
56
73
  #
57
74
  # @param reference [String] The unique reference for the transaction.
58
- # @return [Hash] The response from the Paystack API.
75
+ # @return [PaystackSdk::Response] The response from the Paystack API.
59
76
  # @raise [PaystackSdk::Error] If the API request fails.
60
77
  #
61
78
  # @example
62
- # response = transactions.verify("transaction_reference")
79
+ # response = transactions.verify(reference: "transaction_reference")
63
80
  def verify(reference:)
81
+ validate_presence!(value: reference, name: "Reference")
82
+
64
83
  response = @connection.get("/transaction/verify/#{reference}")
65
84
  handle_response(response)
66
85
  end
@@ -69,39 +88,208 @@ module PaystackSdk
69
88
  #
70
89
  # @param per_page [Integer] Number of records per page (default: 50)
71
90
  # @param page [Integer] Page number to retrieve (default: 1)
91
+ # @param from [String] A timestamp from which to start listing transactions e.g. 2016-09-24T00:00:05.000Z, 2016-09-21
92
+ # @param to [String] A timestamp at which to stop listing transactions e.g. 2016-09-24T00:00:05.000Z, 2016-09-21
93
+ # @param status [String] Filter transactions by status ('failed', 'success', 'abandoned')
94
+ # @param customer [Integer] Specify an ID for the customer whose transactions you want to retrieve
95
+ # @param currency [String] Specify the transaction currency to filter
96
+ # @param amount [Integer] Filter by transaction amount
72
97
  # @return [PaystackSdk::Response] The response from the Paystack API containing a
73
98
  # list of transactions.
74
99
  # @raise [PaystackSdk::Error] If the API request fails.
75
100
  #
76
101
  # @example
77
102
  # response = transactions.list(per_page: 20, page: 2)
78
- def list(per_page: 50, page: 1)
79
- response = @connection.get("/transaction", {perPage: per_page, page: page})
103
+ # # With filters
104
+ # response = transactions.list(per_page: 10, from: "2023-01-01", to: "2023-12-31", status: "success")
105
+ def list(per_page: 50, page: 1, **params)
106
+ # Create a combined parameter hash for validation
107
+ all_params = {per_page: per_page, page: page}.merge(params)
108
+
109
+ # Validate parameters
110
+ validate_fields!(
111
+ payload: all_params,
112
+ validations: {
113
+ per_page: {type: :positive_integer, required: false},
114
+ page: {type: :positive_integer, required: false},
115
+ from: {type: :date, required: false},
116
+ to: {type: :date, required: false},
117
+ status: {type: :inclusion, allowed_values: %w[failed success abandoned], required: false},
118
+ customer: {type: :positive_integer, required: false},
119
+ amount: {type: :positive_integer, required: false},
120
+ currency: {type: :currency, required: false}
121
+ }
122
+ )
123
+
124
+ # Prepare request parameters
125
+ request_params = {perPage: per_page, page: page}.merge(params)
126
+ response = @connection.get("/transaction", request_params)
80
127
  handle_response(response)
81
128
  end
82
129
 
83
130
  # Fetches details of a single transaction by its ID.
84
131
  #
85
- # @param transaction_id [Integer] The ID of the transaction to fetch.
86
- # @return [Hash] The response from the Paystack API containing transaction details.
132
+ # @param transaction_id [String, Integer] The ID of the transaction to fetch.
133
+ # @return [PaystackSdk::Response] The response from the Paystack API containing transaction details.
87
134
  # @raise [PaystackSdk::Error] If the API request fails.
88
135
  #
89
136
  # @example
90
- # response = transactions.fetch("transaction_id")
137
+ # response = transactions.fetch("12345")
91
138
  def fetch(transaction_id)
139
+ validate_presence!(value: transaction_id, name: "Transaction ID")
140
+
92
141
  response = @connection.get("/transaction/#{transaction_id}")
93
142
  handle_response(response)
94
143
  end
95
144
 
96
145
  # Fetches the totals of all transactions.
97
146
  #
98
- # @return [Hash] The response from the Paystack API containing transaction totals.
147
+ # @param from [String] A timestamp from which to start listing transaction totals e.g. 2016-09-24T00:00:05.000Z, 2016-09-21
148
+ # @param to [String] A timestamp at which to stop listing transaction totals e.g. 2016-09-24T00:00:05.000Z, 2016-09-21
149
+ # @return [PaystackSdk::Response] The response from the Paystack API containing transaction totals.
150
+ # @raise [PaystackSdk::Error] If the API request fails.
151
+ #
152
+ # @example
153
+ # response = transactions.totals
154
+ # # With date filters
155
+ # response = transactions.totals(from: "2023-01-01", to: "2023-12-31")
156
+ def totals(**params)
157
+ validate_fields!(
158
+ payload: params,
159
+ validations: {
160
+ from: {type: :date, required: false},
161
+ to: {type: :date, required: false}
162
+ }
163
+ )
164
+
165
+ response = @connection.get("/transaction/totals", params)
166
+ handle_response(response)
167
+ end
168
+
169
+ # Exports transactions as a downloadable file.
170
+ #
171
+ # @param from [String] A timestamp from which to start the export e.g. 2016-09-24T00:00:05.000Z, 2016-09-21
172
+ # @param to [String] A timestamp at which to stop the export e.g. 2016-09-24T00:00:05.000Z, 2016-09-21
173
+ # @param status [String] Export only transactions with a specific status ('failed', 'success', 'abandoned')
174
+ # @param currency [String] Specify the transaction currency to export
175
+ # @param amount [Integer] Filter by transaction amount
176
+ # @param settled [Boolean] Set to true to export only settled transactions
177
+ # @param payment_page [Integer] Specify a payment page ID to export only transactions conducted through the page
178
+ # @param customer [Integer] Specify an ID for the customer whose transactions you want to export
179
+ # @param settlement [Integer] Specify a settlement ID to export only transactions in the settlement
180
+ # @return [PaystackSdk::Response] The response from the Paystack API containing the export details.
181
+ # @raise [PaystackSdk::Error] If the API request fails.
182
+ #
183
+ # @example
184
+ # response = transactions.export
185
+ # # With filters
186
+ # response = transactions.export(from: "2023-01-01", to: "2023-12-31", status: "success", currency: "NGN")
187
+ def export(**params)
188
+ validate_fields!(
189
+ payload: params,
190
+ validations: {
191
+ from: {type: :date, required: false},
192
+ to: {type: :date, required: false},
193
+ status: {type: :inclusion, allowed_values: %w[failed success abandoned], required: false},
194
+ currency: {type: :currency, required: false},
195
+ amount: {type: :positive_integer, required: false},
196
+ payment_page: {type: :positive_integer, required: false},
197
+ customer: {type: :positive_integer, required: false},
198
+ settlement: {type: :positive_integer, required: false}
199
+ }
200
+ )
201
+
202
+ response = @connection.get("/transaction/export", params)
203
+ handle_response(response)
204
+ end
205
+
206
+ # Charges an authorization code for subsequent payments.
207
+ #
208
+ # @param payload [Hash] The payload containing charge details.
209
+ # @option payload [String] :authorization_code Authorization code for the transaction (required)
210
+ # @option payload [String] :email Customer's email address (required)
211
+ # @option payload [Integer] :amount Amount in kobo, pesewas, or cents to charge (required)
212
+ # @option payload [String] :reference Unique transaction reference. Only -, ., = and alphanumeric characters allowed
213
+ # @option payload [String] :currency Currency in which amount should be charged (default: NGN)
214
+ # @option payload [Hash] :metadata Additional transaction information
215
+ # @option payload [Array<Hash>] :split_code Split payment among multiple accounts
216
+ # @return [PaystackSdk::Response] The response from the Paystack API.
217
+ # @raise [PaystackSdk::Error] If the payload is invalid or the API request fails.
218
+ #
219
+ # @example
220
+ # payload = {
221
+ # authorization_code: "AUTH_72btv547",
222
+ # email: "customer@email.com",
223
+ # amount: 10000
224
+ # }
225
+ # response = transactions.charge_authorization(payload)
226
+ def charge_authorization(payload)
227
+ validate_fields!(
228
+ payload: payload,
229
+ validations: {
230
+ authorization_code: {required: true},
231
+ email: {type: :email, required: true},
232
+ amount: {type: :positive_integer, required: true},
233
+ reference: {type: :reference, required: false},
234
+ currency: {type: :currency, required: false}
235
+ }
236
+ )
237
+
238
+ response = @connection.post("/transaction/charge_authorization", payload)
239
+ handle_response(response)
240
+ end
241
+
242
+ # Performs a partial debit on a customer's account.
243
+ #
244
+ # @param payload [Hash] The payload containing partial debit details.
245
+ # @option payload [String] :authorization_code Authorization code for the transaction (required)
246
+ # @option payload [String] :currency Currency in which amount should be charged (required)
247
+ # @option payload [Integer] :amount Amount in kobo, pesewas, or cents to charge (required)
248
+ # @option payload [String] :email Customer's email address (required)
249
+ # @option payload [String] :reference Unique transaction reference. Only -, ., = and alphanumeric characters allowed
250
+ # @option payload [Hash] :metadata Additional transaction information
251
+ # @option payload [Array<Hash>] :split_code Split payment among multiple accounts
252
+ # @return [PaystackSdk::Response] The response from the Paystack API.
253
+ # @raise [PaystackSdk::Error] If the payload is invalid or the API request fails.
254
+ #
255
+ # @example
256
+ # payload = {
257
+ # authorization_code: "AUTH_72btv547",
258
+ # currency: "NGN",
259
+ # amount: 10000,
260
+ # email: "customer@email.com"
261
+ # }
262
+ # response = transactions.partial_debit(payload)
263
+ def partial_debit(payload)
264
+ validate_fields!(
265
+ payload: payload,
266
+ validations: {
267
+ authorization_code: {required: true},
268
+ currency: {type: :currency, required: true},
269
+ amount: {type: :positive_integer, required: true},
270
+ email: {type: :email, required: true},
271
+ reference: {type: :reference, required: false}
272
+ }
273
+ )
274
+
275
+ response = @connection.post("/transaction/partial_debit", payload)
276
+ handle_response(response)
277
+ end
278
+
279
+ # View the timeline of a transaction
280
+ #
281
+ # @param id_or_reference [String] The ID or reference of the transaction
282
+ # @return [PaystackSdk::Response] The response from the Paystack API containing timeline details.
99
283
  # @raise [PaystackSdk::Error] If the API request fails.
100
284
  #
101
285
  # @example
102
- # response = transactions.totals
103
- def totals
104
- response = @connection.get("/transaction/totals")
286
+ # response = transactions.timeline("12345")
287
+ # # OR
288
+ # response = transactions.timeline("ref_123456789")
289
+ def timeline(id_or_reference)
290
+ validate_presence!(value: id_or_reference, name: "Transaction ID or Reference")
291
+
292
+ response = @connection.get("/transaction/timeline/#{id_or_reference}")
105
293
  handle_response(response)
106
294
  end
107
295
  end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaystackSdk
4
+ # The Validations module provides shared validation methods for Paystack SDK resources.
5
+ # It includes methods for validating common parameters like references, amounts, dates, etc.
6
+ #
7
+ # This module is intended to be included in resource classes to provide consistent
8
+ # parameter validation before making API calls.
9
+ module Validations
10
+ # Validates that input is a hash.
11
+ #
12
+ # @param input [Object] The input to validate
13
+ # @param name [String] Name of the parameter for error messages
14
+ # @raise [PaystackSdk::Error] If input is not a hash
15
+ def validate_hash!(input:, name: "Payload")
16
+ raise PaystackSdk::Error, "#{name} must be a hash" unless input.is_a?(Hash)
17
+ end
18
+
19
+ # Validates that required parameters are present in a payload.
20
+ #
21
+ # @param payload [Hash] The payload to validate
22
+ # @param required_params [Array<Symbol>] List of required parameter keys
23
+ # @param operation_name [String] Name of the operation for error messages
24
+ # @raise [PaystackSdk::Error] If any required parameters are missing
25
+ def validate_required_params!(payload:, required_params:, operation_name: "Operation")
26
+ missing_params = required_params.select do |param|
27
+ !payload.key?(param) && !payload.key?(param.to_s)
28
+ end
29
+
30
+ unless missing_params.empty?
31
+ missing_list = missing_params.map(&:to_s).join(", ")
32
+ raise PaystackSdk::Error, "#{operation_name} requires these missing parameter(s): #{missing_list}"
33
+ end
34
+ end
35
+
36
+ # Validates that a value is present (not nil or empty).
37
+ #
38
+ # @param value [Object] The value to validate
39
+ # @param name [String] Name of the parameter for error messages
40
+ # @raise [PaystackSdk::Error] If value is nil or empty
41
+ def validate_presence!(value:, name: "Parameter")
42
+ if value.nil? || (value.respond_to?(:empty?) && value.empty?)
43
+ raise PaystackSdk::Error, "#{name} cannot be empty"
44
+ end
45
+ end
46
+
47
+ # Validates that a number is a positive integer.
48
+ #
49
+ # @param value [Object] The value to validate
50
+ # @param name [String] Name of the parameter for error messages
51
+ # @param allow_nil [Boolean] Whether nil values are allowed
52
+ # @raise [PaystackSdk::Error] If value is not a positive integer (and not nil if allow_nil is true)
53
+ def validate_positive_integer!(value:, name: "Parameter", allow_nil: true)
54
+ if value.nil?
55
+ raise PaystackSdk::Error, "#{name} cannot be nil" unless allow_nil
56
+ elsif !value.is_a?(Integer) || value < 1
57
+ raise PaystackSdk::Error, "#{name} must be a positive integer"
58
+ end
59
+ end
60
+
61
+ # Validates a transaction reference format.
62
+ #
63
+ # @param reference [String] The reference to validate
64
+ # @param name [String] Name of the parameter for error messages
65
+ # @raise [PaystackSdk::Error] If reference format is invalid
66
+ def validate_reference_format!(reference:, name: "Reference")
67
+ unless reference.to_s.match?(/^[a-zA-Z0-9._=-]+$/)
68
+ raise PaystackSdk::Error, "#{name} can only contain alphanumeric characters and the following: -, ., ="
69
+ end
70
+ end
71
+
72
+ # Validates a date string format.
73
+ #
74
+ # @param date_str [String] The date string to validate
75
+ # @param name [String] Name of the parameter for error messages
76
+ # @param allow_nil [Boolean] Whether nil values are allowed
77
+ # @raise [PaystackSdk::Error] If date format is invalid
78
+ def validate_date_format!(date_str:, name: "Date", allow_nil: true)
79
+ if date_str.nil?
80
+ raise PaystackSdk::Error, "#{name} cannot be nil" unless allow_nil
81
+ return
82
+ end
83
+
84
+ begin
85
+ Date.parse(date_str.to_s)
86
+ rescue Date::Error
87
+ raise PaystackSdk::Error, "Invalid #{name.downcase} format. Use format: YYYY-MM-DD or ISO8601"
88
+ end
89
+ end
90
+
91
+ # Validates that a value is within an allowed set of values.
92
+ #
93
+ # @param value [Object] The value to validate
94
+ # @param allowed_values [Array] List of allowed values
95
+ # @param name [String] Name of the parameter for error messages
96
+ # @param allow_nil [Boolean] Whether nil values are allowed
97
+ # @param case_sensitive [Boolean] Whether the comparison is case-sensitive
98
+ # @raise [PaystackSdk::Error] If value is not in the allowed set
99
+ def validate_inclusion!(value:, allowed_values:, name: "Parameter", allow_nil: true, case_sensitive: false)
100
+ if value.nil?
101
+ raise PaystackSdk::Error, "#{name} cannot be nil" unless allow_nil
102
+ return
103
+ end
104
+
105
+ check_value = case_sensitive ? value.to_s : value.to_s.downcase
106
+ check_allowed = case_sensitive ? allowed_values : allowed_values.map(&:downcase)
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}'"
111
+ end
112
+ end
113
+
114
+ # Validates an email format.
115
+ #
116
+ # @param email [String] The email to validate
117
+ # @param name [String] Name of the parameter for error messages
118
+ # @param allow_nil [Boolean] Whether nil values are allowed
119
+ # @raise [PaystackSdk::Error] If email format is invalid
120
+ def validate_email!(email:, name: "Email", allow_nil: false)
121
+ if email.nil?
122
+ raise PaystackSdk::Error, "#{name} cannot be nil" unless allow_nil
123
+ return
124
+ end
125
+
126
+ unless email.to_s.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
127
+ raise PaystackSdk::Error, "Invalid #{name.downcase} format"
128
+ end
129
+ end
130
+
131
+ # Validates a currency code format.
132
+ #
133
+ # @param currency [String] The currency code to validate
134
+ # @param name [String] Name of the parameter for error messages
135
+ # @param allow_nil [Boolean] Whether nil values are allowed
136
+ # @raise [PaystackSdk::Error] If currency format is invalid
137
+ def validate_currency!(currency:, name: "Currency", allow_nil: true)
138
+ if currency.nil?
139
+ raise PaystackSdk::Error, "#{name} cannot be nil" unless allow_nil
140
+ return
141
+ end
142
+
143
+ unless currency.to_s.match?(/\A[A-Z]{3}\z/)
144
+ raise PaystackSdk::Error, "#{name} must be a 3-letter ISO code (e.g., NGN, USD, GHS)"
145
+ end
146
+ end
147
+
148
+ # Validates multiple fields at once.
149
+ #
150
+ # @param payload [Hash] The payload containing fields to validate
151
+ # @param validations [Hash] Mapping of field names to validation options
152
+ # @raise [PaystackSdk::Error] If any validation fails
153
+ #
154
+ # @example
155
+ # validate_fields!(
156
+ # payload: params,
157
+ # validations: {
158
+ # email: { type: :email, required: true },
159
+ # amount: { type: :positive_integer, required: true },
160
+ # currency: { type: :currency, required: false },
161
+ # reference: { type: :reference, required: false }
162
+ # }
163
+ # )
164
+ def validate_fields!(payload:, validations:)
165
+ # First check required fields
166
+ required_fields = validations.select { |_, opts| opts[:required] }.keys
167
+ validate_required_params!(payload: payload, required_params: required_fields) unless required_fields.empty?
168
+
169
+ # Then validate each field
170
+ validations.each do |field, options|
171
+ value = payload[field] || payload[field.to_s]
172
+ next if value.nil? && !options[:required]
173
+
174
+ case options[:type]
175
+ when :email
176
+ validate_email!(email: value, name: field.to_s.capitalize, allow_nil: !options[:required])
177
+ when :positive_integer
178
+ validate_positive_integer!(value: value, name: field.to_s, allow_nil: !options[:required])
179
+ when :reference
180
+ validate_reference_format!(reference: value, name: field.to_s) if value
181
+ when :date
182
+ validate_date_format!(date_str: value, name: field.to_s, allow_nil: !options[:required])
183
+ when :currency
184
+ validate_currency!(currency: value, name: field.to_s, allow_nil: !options[:required])
185
+ when :inclusion
186
+ if value
187
+ validate_inclusion!(
188
+ value: value,
189
+ allowed_values: options[:allowed_values],
190
+ name: field.to_s,
191
+ allow_nil: !options[:required],
192
+ case_sensitive: options[:case_sensitive]
193
+ )
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PaystackSdk
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paystack_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxwell Nana Forson (theLazyProgrammer)
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-05-13 00:00:00.000000000 Z
11
+ date: 2025-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -119,6 +119,7 @@ files:
119
119
  - lib/paystack_sdk/resources/base.rb
120
120
  - lib/paystack_sdk/resources/transactions.rb
121
121
  - lib/paystack_sdk/response.rb
122
+ - lib/paystack_sdk/validations.rb
122
123
  - lib/paystack_sdk/version.rb
123
124
  - mise.toml
124
125
  - sig/paystack_sdk.rbs