paystack_sdk 0.1.1 → 0.2.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 +67 -0
- data/README.md +624 -86
- data/lib/paystack_sdk/client.rb +64 -21
- data/lib/paystack_sdk/middleware/transport_errors.rb +23 -0
- data/lib/paystack_sdk/request_helpers.rb +117 -0
- data/lib/paystack_sdk/resources/banks.rb +163 -13
- data/lib/paystack_sdk/resources/base.rb +4 -2
- data/lib/paystack_sdk/resources/charges.rb +190 -76
- data/lib/paystack_sdk/resources/customers.rb +260 -139
- data/lib/paystack_sdk/resources/extensions/charges.rb +65 -0
- data/lib/paystack_sdk/resources/miscellaneous.rb +58 -0
- data/lib/paystack_sdk/resources/refunds.rb +116 -0
- data/lib/paystack_sdk/resources/transactions.rb +365 -238
- data/lib/paystack_sdk/resources/transfer_recipients.rb +135 -27
- data/lib/paystack_sdk/resources/transfers.rb +260 -25
- data/lib/paystack_sdk/response.rb +88 -9
- data/lib/paystack_sdk/utils/connection_utils.rb +100 -5
- data/lib/paystack_sdk/version.rb +1 -1
- data/lib/paystack_sdk/webhook.rb +152 -0
- data/lib/paystack_sdk.rb +27 -2
- metadata +57 -4
- data/lib/paystack_sdk/resources/verification.rb +0 -36
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../middleware/transport_errors"
|
|
4
|
+
|
|
3
5
|
module PaystackSdk
|
|
4
6
|
module Utils
|
|
5
7
|
# The `ConnectionUtils` module provides shared functionality for creating
|
|
@@ -9,32 +11,92 @@ module PaystackSdk
|
|
|
9
11
|
# The base URL for the Paystack API.
|
|
10
12
|
BASE_URL = "https://api.paystack.co"
|
|
11
13
|
|
|
14
|
+
# Seconds to wait for the connection to open.
|
|
15
|
+
DEFAULT_OPEN_TIMEOUT = 5
|
|
16
|
+
|
|
17
|
+
# Seconds to wait for a response.
|
|
18
|
+
DEFAULT_TIMEOUT = 30
|
|
19
|
+
|
|
20
|
+
# Retries performed after the initial attempt.
|
|
21
|
+
DEFAULT_MAX_RETRIES = 2
|
|
22
|
+
|
|
23
|
+
# Base delay in seconds before the first retry (doubles each attempt).
|
|
24
|
+
DEFAULT_RETRY_INTERVAL = 0.5
|
|
25
|
+
|
|
26
|
+
# HTTP methods that only read data and are always safe to repeat.
|
|
27
|
+
SAFE_METHODS = %i[get head options].freeze
|
|
28
|
+
|
|
29
|
+
# Statuses worth retrying. 500 is excluded because it usually means a
|
|
30
|
+
# bug, not a transient fault.
|
|
31
|
+
RETRY_STATUSES = [429, 502, 503, 504].freeze
|
|
32
|
+
|
|
33
|
+
# Longest pause (seconds) between retries. If Paystack asks for a longer
|
|
34
|
+
# wait via `x-ratelimit-reset`, the request is not retried and the
|
|
35
|
+
# error is raised so the caller can decide.
|
|
36
|
+
MAX_RETRY_WAIT = 10
|
|
37
|
+
|
|
38
|
+
# Response header Paystack uses to say when the rate-limit window ends.
|
|
39
|
+
# @see https://paystack.com/docs/api/rate-limits/
|
|
40
|
+
RATE_LIMIT_RESET_HEADER = "x-ratelimit-reset"
|
|
41
|
+
|
|
12
42
|
# Initializes a connection based on the provided parameters.
|
|
13
43
|
#
|
|
14
44
|
# @param connection [Faraday::Connection, nil] An existing connection object.
|
|
15
45
|
# @param secret_key [String, nil] Optional API key to use for creating a new connection.
|
|
46
|
+
# @param options [Hash] Connection options, see {#create_connection}.
|
|
16
47
|
# @return [Faraday::Connection] A connection object for API requests.
|
|
17
48
|
# @raise [PaystackSdk::Error] If no connection or API key can be found.
|
|
18
|
-
def initialize_connection(connection = nil, secret_key: nil)
|
|
49
|
+
def initialize_connection(connection = nil, secret_key: nil, **options)
|
|
19
50
|
if connection
|
|
51
|
+
unless options.empty?
|
|
52
|
+
raise ArgumentError,
|
|
53
|
+
"#{options.keys.join(", ")} cannot be used with a pre-built connection; configure the connection itself"
|
|
54
|
+
end
|
|
55
|
+
|
|
20
56
|
connection
|
|
21
57
|
elsif secret_key
|
|
22
|
-
create_connection(secret_key
|
|
58
|
+
create_connection(secret_key:, **options)
|
|
23
59
|
else
|
|
24
60
|
# Try to get API key from environment variable
|
|
25
61
|
env_secret_key = ENV["PAYSTACK_SECRET_KEY"]
|
|
26
62
|
raise AuthenticationError, "No connection or API key provided" unless env_secret_key
|
|
27
63
|
|
|
28
|
-
create_connection(secret_key: env_secret_key)
|
|
64
|
+
create_connection(secret_key: env_secret_key, **options)
|
|
29
65
|
end
|
|
30
66
|
end
|
|
31
67
|
|
|
32
68
|
# Creates a new Faraday connection with the Paystack API.
|
|
33
69
|
#
|
|
70
|
+
# Requests time out and are retried with exponential backoff on network
|
|
71
|
+
# failures and on 429/502/503/504 responses. Because this SDK moves
|
|
72
|
+
# money, retries are deliberately conservative:
|
|
73
|
+
#
|
|
74
|
+
# * Read-only requests (GET/HEAD/OPTIONS) are retried on any of the above.
|
|
75
|
+
# * Every other request is retried only on 429, where Paystack has
|
|
76
|
+
# rejected it for exceeding the rate limit. A timeout or 5xx on a
|
|
77
|
+
# write could mean it was processed, so it is never retried unless you
|
|
78
|
+
# pass `retry_non_idempotent: true`.
|
|
79
|
+
#
|
|
80
|
+
# On a 429 the wait honours Paystack's `x-ratelimit-reset` header.
|
|
81
|
+
#
|
|
34
82
|
# @param secret_key [String] The secret API key for authenticating with the Paystack API.
|
|
83
|
+
# @param timeout [Numeric] Seconds to wait for a response.
|
|
84
|
+
# @param open_timeout [Numeric] Seconds to wait for the connection to open.
|
|
85
|
+
# @param max_retries [Integer] Retries after the first attempt (0 disables retrying).
|
|
86
|
+
# @param retry_interval [Numeric] Base delay in seconds before the first retry.
|
|
87
|
+
# @param retry_non_idempotent [Boolean] Also retry writes on network failures and 5xx.
|
|
88
|
+
# Only enable this if you supply your own deduplication (e.g. verify by reference).
|
|
35
89
|
# @return [Faraday::Connection] A configured Faraday connection.
|
|
36
|
-
def create_connection(secret_key:
|
|
37
|
-
|
|
90
|
+
def create_connection(secret_key:, timeout: DEFAULT_TIMEOUT, open_timeout: DEFAULT_OPEN_TIMEOUT,
|
|
91
|
+
max_retries: DEFAULT_MAX_RETRIES, retry_interval: DEFAULT_RETRY_INTERVAL,
|
|
92
|
+
retry_non_idempotent: false)
|
|
93
|
+
validate_connection_options!(timeout:, open_timeout:, max_retries:, retry_interval:)
|
|
94
|
+
|
|
95
|
+
Faraday.new(url: BASE_URL, request: {timeout:, open_timeout:}) do |conn|
|
|
96
|
+
conn.use Middleware::TransportErrors
|
|
97
|
+
if max_retries > 0
|
|
98
|
+
conn.request :retry, retry_options(max_retries, retry_interval, retry_non_idempotent)
|
|
99
|
+
end
|
|
38
100
|
conn.request :json
|
|
39
101
|
conn.response :json, content_type: /\bjson$/
|
|
40
102
|
conn.headers["Authorization"] = "Bearer #{secret_key}"
|
|
@@ -43,6 +105,39 @@ module PaystackSdk
|
|
|
43
105
|
conn.adapter Faraday.default_adapter
|
|
44
106
|
end
|
|
45
107
|
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def validate_connection_options!(timeout:, open_timeout:, max_retries:, retry_interval:)
|
|
112
|
+
{timeout:, open_timeout:}.each do |name, value|
|
|
113
|
+
raise ArgumentError, "#{name} must be a positive number" unless value.is_a?(Numeric) && value > 0
|
|
114
|
+
end
|
|
115
|
+
raise ArgumentError, "max_retries must be a non-negative Integer" unless max_retries.is_a?(Integer) && max_retries >= 0
|
|
116
|
+
raise ArgumentError, "retry_interval must be a non-negative number" unless retry_interval.is_a?(Numeric) && retry_interval >= 0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def retry_options(max_retries, retry_interval, retry_non_idempotent)
|
|
120
|
+
{
|
|
121
|
+
max: max_retries,
|
|
122
|
+
interval: retry_interval,
|
|
123
|
+
interval_randomness: 0.5,
|
|
124
|
+
backoff_factor: 2,
|
|
125
|
+
max_interval: MAX_RETRY_WAIT,
|
|
126
|
+
rate_limit_reset_header: RATE_LIMIT_RESET_HEADER,
|
|
127
|
+
retry_statuses: RETRY_STATUSES,
|
|
128
|
+
methods: [],
|
|
129
|
+
exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed],
|
|
130
|
+
retry_if: ->(env, exception) { retryable_request?(env, exception, retry_non_idempotent) }
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def retryable_request?(env, exception, retry_non_idempotent)
|
|
135
|
+
return true if SAFE_METHODS.include?(env.method)
|
|
136
|
+
return true if retry_non_idempotent
|
|
137
|
+
|
|
138
|
+
# Writes: only when Paystack rejected the request for rate limiting.
|
|
139
|
+
exception.is_a?(Faraday::RetriableResponse) && env.status == 429
|
|
140
|
+
end
|
|
46
141
|
end
|
|
47
142
|
end
|
|
48
143
|
end
|
data/lib/paystack_sdk/version.rb
CHANGED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require_relative "response"
|
|
6
|
+
|
|
7
|
+
module PaystackSdk
|
|
8
|
+
# Helpers for receiving Paystack webhooks, independent of any web framework.
|
|
9
|
+
#
|
|
10
|
+
# Paystack signs every event with the `x-paystack-signature` header: a
|
|
11
|
+
# lowercase hex HMAC SHA512 of the raw request body, using your secret key.
|
|
12
|
+
# Verify it before doing anything with the event, and return a `200 OK`
|
|
13
|
+
# quickly (do long work in a background job): events that are not
|
|
14
|
+
# acknowledged are retried for 72 hours in live mode.
|
|
15
|
+
#
|
|
16
|
+
# @see https://paystack.com/docs/payments/webhooks/
|
|
17
|
+
#
|
|
18
|
+
# @example In a Rails controller
|
|
19
|
+
# ```ruby
|
|
20
|
+
# def create
|
|
21
|
+
# event = PaystackSdk::Webhook.construct_event(
|
|
22
|
+
# payload: request.raw_post,
|
|
23
|
+
# signature: request.headers["X-Paystack-Signature"],
|
|
24
|
+
# secret: ENV.fetch("PAYSTACK_SECRET_KEY")
|
|
25
|
+
# )
|
|
26
|
+
# HandlePaystackEventJob.perform_later(event.payload)
|
|
27
|
+
# head :ok
|
|
28
|
+
# rescue PaystackSdk::WebhookError
|
|
29
|
+
# head :bad_request
|
|
30
|
+
# end
|
|
31
|
+
# ```
|
|
32
|
+
module Webhook
|
|
33
|
+
# Header Paystack puts the signature in. Rack and Rails expose it as
|
|
34
|
+
# `HTTP_X_PAYSTACK_SIGNATURE` or `request.headers["X-Paystack-Signature"]`.
|
|
35
|
+
SIGNATURE_HEADER = "x-paystack-signature"
|
|
36
|
+
|
|
37
|
+
# The only addresses Paystack sends webhooks from, for both test and live.
|
|
38
|
+
IP_ADDRESSES = %w[52.31.139.75 52.49.173.169 52.214.14.220].freeze
|
|
39
|
+
|
|
40
|
+
# Events Paystack documents. Paystack adds events over time, so an event
|
|
41
|
+
# outside this list is still returned by {Webhook.construct_event}.
|
|
42
|
+
EVENTS = %w[
|
|
43
|
+
charge.dispute.create charge.dispute.remind charge.dispute.resolve charge.success
|
|
44
|
+
customeridentification.failed customeridentification.success
|
|
45
|
+
dedicatedaccount.assign.failed dedicatedaccount.assign.success
|
|
46
|
+
invoice.create invoice.payment_failed invoice.update
|
|
47
|
+
paymentrequest.pending paymentrequest.success
|
|
48
|
+
refund.failed refund.pending refund.processed refund.processing
|
|
49
|
+
subscription.create subscription.disable subscription.expiring_cards subscription.not_renew
|
|
50
|
+
transfer.failed transfer.reversed transfer.success
|
|
51
|
+
].freeze
|
|
52
|
+
|
|
53
|
+
# A verified webhook event.
|
|
54
|
+
class Event
|
|
55
|
+
# @return [String] The event name, e.g. "charge.success"
|
|
56
|
+
attr_reader :event
|
|
57
|
+
|
|
58
|
+
# @return [Hash] The parsed JSON body, string-keyed
|
|
59
|
+
attr_reader :payload
|
|
60
|
+
|
|
61
|
+
def initialize(payload)
|
|
62
|
+
@payload = payload
|
|
63
|
+
@event = payload["event"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [PaystackSdk::Response, nil] The event's `data`, wrapped for dot and hash access
|
|
67
|
+
def data
|
|
68
|
+
return @data if defined?(@data)
|
|
69
|
+
|
|
70
|
+
@data = payload.key?("data") ? Response.new(payload["data"]) : nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [Boolean] Whether Paystack documents this event name
|
|
74
|
+
def known?
|
|
75
|
+
EVENTS.include?(event)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class << self
|
|
80
|
+
# Computes the signature Paystack would send for a payload.
|
|
81
|
+
# Mostly useful in your own tests.
|
|
82
|
+
#
|
|
83
|
+
# @param payload [String] The raw request body
|
|
84
|
+
# @param secret [String] Your secret key
|
|
85
|
+
# @return [String] Lowercase hex HMAC SHA512
|
|
86
|
+
def sign(payload, secret)
|
|
87
|
+
OpenSSL::HMAC.hexdigest("SHA512", secret, payload)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Checks a webhook's signature in constant time.
|
|
91
|
+
#
|
|
92
|
+
# @param payload [String] The raw request body, byte for byte. Not a parsed
|
|
93
|
+
# or re-serialised version of it, which would not match.
|
|
94
|
+
# @param signature [String, nil] The `x-paystack-signature` header value
|
|
95
|
+
# @param secret [String] Your secret key
|
|
96
|
+
# @return [Boolean]
|
|
97
|
+
# @raise [ArgumentError] If the payload is not a String or the secret is blank
|
|
98
|
+
def valid_signature?(payload:, signature:, secret:)
|
|
99
|
+
unless payload.is_a?(String)
|
|
100
|
+
raise ArgumentError, "payload must be the raw request body (a String), not a parsed or re-serialised copy"
|
|
101
|
+
end
|
|
102
|
+
raise ArgumentError, "secret must not be blank" if secret.nil? || secret.to_s.empty?
|
|
103
|
+
|
|
104
|
+
expected = sign(payload, secret)
|
|
105
|
+
return false unless signature.is_a?(String) && signature.bytesize == expected.bytesize
|
|
106
|
+
|
|
107
|
+
OpenSSL.fixed_length_secure_compare(expected, signature)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Like {valid_signature?} but raises when the signature is wrong.
|
|
111
|
+
#
|
|
112
|
+
# @return [true]
|
|
113
|
+
# @raise [PaystackSdk::InvalidSignatureError]
|
|
114
|
+
def verify!(payload:, signature:, secret:)
|
|
115
|
+
raise InvalidSignatureError unless valid_signature?(payload: payload, signature: signature, secret: secret)
|
|
116
|
+
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Verifies the signature, then parses the event. Nothing is parsed if the
|
|
121
|
+
# signature is wrong.
|
|
122
|
+
#
|
|
123
|
+
# @return [PaystackSdk::Webhook::Event]
|
|
124
|
+
# @raise [PaystackSdk::InvalidSignatureError] If the signature does not match
|
|
125
|
+
# @raise [PaystackSdk::InvalidPayloadError] If the signed body is not a JSON event
|
|
126
|
+
def construct_event(payload:, signature:, secret:)
|
|
127
|
+
verify!(payload: payload, signature: signature, secret: secret)
|
|
128
|
+
|
|
129
|
+
parsed = begin
|
|
130
|
+
JSON.parse(payload)
|
|
131
|
+
rescue JSON::ParserError
|
|
132
|
+
raise InvalidPayloadError, "Webhook body is not valid JSON"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
unless parsed.is_a?(Hash) && parsed["event"].is_a?(String) && !parsed["event"].empty?
|
|
136
|
+
raise InvalidPayloadError, "Webhook body is not an event (missing \"event\")"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
Event.new(parsed)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Whether an address is one Paystack documents for webhooks. A second
|
|
143
|
+
# check alongside the signature, not a replacement for it.
|
|
144
|
+
#
|
|
145
|
+
# @param ip [String, nil]
|
|
146
|
+
# @return [Boolean]
|
|
147
|
+
def trusted_ip?(ip)
|
|
148
|
+
IP_ADDRESSES.include?(ip)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
data/lib/paystack_sdk.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "faraday"
|
|
4
|
+
require "faraday/retry"
|
|
4
5
|
require_relative "paystack_sdk/version"
|
|
5
6
|
require_relative "paystack_sdk/client"
|
|
7
|
+
require_relative "paystack_sdk/webhook"
|
|
6
8
|
|
|
7
9
|
module PaystackSdk
|
|
8
10
|
# Base error class for all Paystack SDK errors.
|
|
@@ -71,14 +73,23 @@ module PaystackSdk
|
|
|
71
73
|
|
|
72
74
|
# Raised when rate limiting is encountered
|
|
73
75
|
class RateLimitError < APIError
|
|
76
|
+
# @return [Integer, nil] Seconds until the rate-limit window ends, from the
|
|
77
|
+
# `x-ratelimit-reset` header, or nil if Paystack did not send it
|
|
74
78
|
attr_reader :retry_after
|
|
75
79
|
|
|
76
|
-
def initialize(retry_after)
|
|
80
|
+
def initialize(retry_after = nil)
|
|
77
81
|
@retry_after = retry_after
|
|
78
|
-
super("Rate limit exceeded. Retry after #{retry_after} seconds")
|
|
82
|
+
super(retry_after ? "Rate limit exceeded. Retry after #{retry_after} seconds" : "Rate limit exceeded")
|
|
79
83
|
end
|
|
80
84
|
end
|
|
81
85
|
|
|
86
|
+
# Raised when a request could not reach Paystack (DNS, refused, reset, ...)
|
|
87
|
+
# after all retries were exhausted.
|
|
88
|
+
class ConnectionError < Error; end
|
|
89
|
+
|
|
90
|
+
# Raised when a request to Paystack timed out after all retries were exhausted.
|
|
91
|
+
class TimeoutError < ConnectionError; end
|
|
92
|
+
|
|
82
93
|
# Raised when the server returns a 5xx error
|
|
83
94
|
class ServerError < APIError
|
|
84
95
|
attr_reader :status_code
|
|
@@ -88,4 +99,18 @@ module PaystackSdk
|
|
|
88
99
|
super("#{message} (Status: #{status_code})")
|
|
89
100
|
end
|
|
90
101
|
end
|
|
102
|
+
|
|
103
|
+
# Base class for webhook errors.
|
|
104
|
+
class WebhookError < Error; end
|
|
105
|
+
|
|
106
|
+
# Raised when a webhook's signature does not match its payload.
|
|
107
|
+
# Treat the request as not coming from Paystack.
|
|
108
|
+
class InvalidSignatureError < WebhookError
|
|
109
|
+
def initialize(message = "Webhook signature does not match the payload")
|
|
110
|
+
super
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Raised when a correctly signed webhook body is not a JSON event.
|
|
115
|
+
class InvalidPayloadError < WebhookError; end
|
|
91
116
|
end
|
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.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maxwell Nana Forson (theLazyProgrammer)
|
|
@@ -11,18 +11,38 @@ date: 1980-01-02 00:00:00.000000000 Z
|
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.13'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.13'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: faraday-retry
|
|
14
34
|
requirement: !ruby/object:Gem::Requirement
|
|
15
35
|
requirements:
|
|
16
36
|
- - "~>"
|
|
17
37
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 2.
|
|
38
|
+
version: '2.3'
|
|
19
39
|
type: :runtime
|
|
20
40
|
prerelease: false
|
|
21
41
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
42
|
requirements:
|
|
23
43
|
- - "~>"
|
|
24
44
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 2.
|
|
45
|
+
version: '2.3'
|
|
26
46
|
- !ruby/object:Gem::Dependency
|
|
27
47
|
name: debug
|
|
28
48
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -79,6 +99,34 @@ dependencies:
|
|
|
79
99
|
- - "~>"
|
|
80
100
|
- !ruby/object:Gem::Version
|
|
81
101
|
version: '3.13'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: json_schemer
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '2.5'
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '2.5'
|
|
116
|
+
- !ruby/object:Gem::Dependency
|
|
117
|
+
name: webmock
|
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - "~>"
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '3.25'
|
|
123
|
+
type: :development
|
|
124
|
+
prerelease: false
|
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - "~>"
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '3.25'
|
|
82
130
|
- !ruby/object:Gem::Dependency
|
|
83
131
|
name: standard
|
|
84
132
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -115,18 +163,23 @@ files:
|
|
|
115
163
|
- Rakefile
|
|
116
164
|
- lib/paystack_sdk.rb
|
|
117
165
|
- lib/paystack_sdk/client.rb
|
|
166
|
+
- lib/paystack_sdk/middleware/transport_errors.rb
|
|
167
|
+
- lib/paystack_sdk/request_helpers.rb
|
|
118
168
|
- lib/paystack_sdk/resources/banks.rb
|
|
119
169
|
- lib/paystack_sdk/resources/base.rb
|
|
120
170
|
- lib/paystack_sdk/resources/charges.rb
|
|
121
171
|
- lib/paystack_sdk/resources/customers.rb
|
|
172
|
+
- lib/paystack_sdk/resources/extensions/charges.rb
|
|
173
|
+
- lib/paystack_sdk/resources/miscellaneous.rb
|
|
174
|
+
- lib/paystack_sdk/resources/refunds.rb
|
|
122
175
|
- lib/paystack_sdk/resources/transactions.rb
|
|
123
176
|
- lib/paystack_sdk/resources/transfer_recipients.rb
|
|
124
177
|
- lib/paystack_sdk/resources/transfers.rb
|
|
125
|
-
- lib/paystack_sdk/resources/verification.rb
|
|
126
178
|
- lib/paystack_sdk/response.rb
|
|
127
179
|
- lib/paystack_sdk/utils/connection_utils.rb
|
|
128
180
|
- lib/paystack_sdk/validations.rb
|
|
129
181
|
- lib/paystack_sdk/version.rb
|
|
182
|
+
- lib/paystack_sdk/webhook.rb
|
|
130
183
|
- mise.toml
|
|
131
184
|
- sig/paystack_sdk.rbs
|
|
132
185
|
homepage: https://github.com/nanafox/paystack_sdk
|
|
@@ -1,36 +0,0 @@
|
|
|
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
|