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,46 +1,154 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generated by bin/paystack-scaffold from Paystack's OpenAPI spec (PaystackOSS/openapi@d9d444d).
|
|
4
|
+
# Do not edit by hand: regenerate it with `bin/paystack-scaffold Transfer Recipient`. Hand-written extras go in lib/paystack_sdk/resources/extensions/transfer_recipients.rb.
|
|
5
|
+
# scaffold-digest: 74785aa38f6edfebdeff52d47bb3b355c2ad95b2cacdd6c0d7062725415bbfaa
|
|
6
|
+
|
|
7
|
+
require_relative "base"
|
|
2
8
|
|
|
3
9
|
module PaystackSdk
|
|
4
10
|
module Resources
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
# Transfer Recipient operations.
|
|
12
|
+
class TransferRecipients < PaystackSdk::Resources::Base
|
|
13
|
+
# Ruby keyword => the parameter name Paystack documents.
|
|
14
|
+
WIRE_NAMES = {next_cursor: "next", per_page: "perPage"}.freeze
|
|
15
|
+
|
|
16
|
+
# List Transfer Recipients.
|
|
17
|
+
#
|
|
18
|
+
# List transfer recipients available on your integration
|
|
19
|
+
#
|
|
20
|
+
# @param use_cursor [Boolean] A flag to indicate if cursor based pagination should be used
|
|
21
|
+
# @param next_cursor [String] An alphanumeric value returned for every cursor based retrieval, used to retrieve the next set of data
|
|
22
|
+
# @param previous [String] An alphanumeric value returned for every cursor based retrieval, used to retrieve the previous set of data
|
|
23
|
+
# @param per_page [Integer] The number of records to fetch per request
|
|
24
|
+
# @param page [Integer] The offset to retrieve data from
|
|
25
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
26
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
27
|
+
# @see https://paystack.com/docs/api/transfer-recipient/#list
|
|
28
|
+
def list(use_cursor: nil, next_cursor: nil, previous: nil, per_page: nil, page: nil)
|
|
29
|
+
validate_positive_integer!(value: per_page, name: "perPage")
|
|
30
|
+
validate_positive_integer!(value: page, name: "page")
|
|
31
|
+
|
|
32
|
+
wire_query = to_wire({use_cursor:, next_cursor:, previous:, per_page:, page:}, WIRE_NAMES)
|
|
33
|
+
|
|
34
|
+
handle_response(@connection.get("/transferrecipient", wire_query))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Create Transfer Recipient.
|
|
38
|
+
#
|
|
39
|
+
# Creates a new recipient. A duplicate account number will lead to the retrieval of the existing record.
|
|
40
|
+
#
|
|
41
|
+
# @param type [String] Recipient Type One of: nuban, ghipss, mobile_money, basa, authorization.
|
|
42
|
+
# @param name [String] The recipient's name according to their account registration.
|
|
43
|
+
# @param account_number [String] Recipient's bank account number
|
|
44
|
+
# @param bank_code [String] Recipient's bank code, from the List Banks endpoint.
|
|
45
|
+
# @param description [String] A description for this recipient
|
|
46
|
+
# @param currency [String] Currency for the account receiving the transfer
|
|
47
|
+
# @param authorization_code [String] An authorization code from a previous transaction
|
|
48
|
+
# @param metadata [Hash] JSON object of custom data
|
|
49
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
50
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
7
51
|
# @see https://paystack.com/docs/api/transfer-recipient/#create
|
|
8
|
-
def create(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
52
|
+
def create(
|
|
53
|
+
type:,
|
|
54
|
+
name:,
|
|
55
|
+
account_number:,
|
|
56
|
+
bank_code:,
|
|
57
|
+
description: nil,
|
|
58
|
+
currency: nil,
|
|
59
|
+
authorization_code: nil,
|
|
60
|
+
metadata: nil
|
|
61
|
+
)
|
|
62
|
+
validate_presence!(value: type, name: "type")
|
|
63
|
+
validate_allowed_values!(
|
|
64
|
+
value: type,
|
|
65
|
+
allowed_values: %w[nuban ghipss mobile_money basa authorization],
|
|
66
|
+
name: "type"
|
|
67
|
+
)
|
|
68
|
+
validate_presence!(value: name, name: "name")
|
|
69
|
+
validate_presence!(value: account_number, name: "account_number")
|
|
70
|
+
validate_presence!(value: bank_code, name: "bank_code")
|
|
71
|
+
|
|
72
|
+
wire_body = to_wire(
|
|
73
|
+
{
|
|
74
|
+
type:,
|
|
75
|
+
name:,
|
|
76
|
+
account_number:,
|
|
77
|
+
bank_code:,
|
|
78
|
+
description:,
|
|
79
|
+
currency:,
|
|
80
|
+
authorization_code:,
|
|
81
|
+
metadata:
|
|
82
|
+
},
|
|
83
|
+
WIRE_NAMES
|
|
14
84
|
)
|
|
15
|
-
|
|
85
|
+
|
|
86
|
+
handle_response(@connection.post("/transferrecipient", wire_body))
|
|
16
87
|
end
|
|
17
88
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
89
|
+
# Bulk Create Transfer Recipient.
|
|
90
|
+
#
|
|
91
|
+
# Create multiple transfer recipients in batches. A duplicate account number will lead to the retrieval of the existing record.
|
|
92
|
+
#
|
|
93
|
+
# @param batch [Array<Hash>] A list of transfer recipient objects, each with the fields accepted by {#create}
|
|
94
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
95
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
96
|
+
# @see https://paystack.com/docs/api/transfer-recipient/#bulk
|
|
97
|
+
def bulk_create(batch:)
|
|
98
|
+
validate_presence!(value: batch, name: "batch")
|
|
99
|
+
|
|
100
|
+
wire_body = to_wire({batch:}, WIRE_NAMES)
|
|
101
|
+
|
|
102
|
+
handle_response(@connection.post("/transferrecipient/bulk", wire_body))
|
|
22
103
|
end
|
|
23
104
|
|
|
24
|
-
# Fetch
|
|
105
|
+
# Fetch Transfer recipient.
|
|
106
|
+
#
|
|
107
|
+
# Fetch the details of a transfer recipient
|
|
108
|
+
#
|
|
109
|
+
# @param id_or_code [String] The recipient code (RCP_...) or numeric ID
|
|
110
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
111
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
25
112
|
# @see https://paystack.com/docs/api/transfer-recipient/#fetch
|
|
26
|
-
def fetch(
|
|
27
|
-
validate_presence!(value:
|
|
28
|
-
|
|
113
|
+
def fetch(id_or_code:)
|
|
114
|
+
validate_presence!(value: id_or_code, name: "id_or_code")
|
|
115
|
+
|
|
116
|
+
handle_response(@connection.get("/transferrecipient/#{escape_path(id_or_code, name: "id_or_code")}"))
|
|
29
117
|
end
|
|
30
118
|
|
|
31
|
-
# Update
|
|
119
|
+
# Update Transfer Recipient.
|
|
120
|
+
#
|
|
121
|
+
# Update the details of a transfer recipient
|
|
122
|
+
#
|
|
123
|
+
# @param id_or_code [String] The recipient code (RCP_...) or numeric ID
|
|
124
|
+
# @param name [String] Recipient's name
|
|
125
|
+
# @param email [String] Recipient's email address
|
|
126
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
127
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
32
128
|
# @see https://paystack.com/docs/api/transfer-recipient/#update
|
|
33
|
-
def update(
|
|
34
|
-
validate_presence!(value:
|
|
35
|
-
|
|
36
|
-
|
|
129
|
+
def update(id_or_code:, name: nil, email: nil)
|
|
130
|
+
validate_presence!(value: id_or_code, name: "id_or_code")
|
|
131
|
+
validate_email!(email: email, name: "email", allow_nil: true)
|
|
132
|
+
|
|
133
|
+
wire_body = to_wire({name:, email:}, WIRE_NAMES)
|
|
134
|
+
|
|
135
|
+
handle_response(
|
|
136
|
+
@connection.put("/transferrecipient/#{escape_path(id_or_code, name: "id_or_code")}", wire_body)
|
|
137
|
+
)
|
|
37
138
|
end
|
|
38
139
|
|
|
39
|
-
# Delete
|
|
140
|
+
# Delete Transfer Recipient.
|
|
141
|
+
#
|
|
142
|
+
# Delete a transfer recipient (sets the transfer recipient to inactive)
|
|
143
|
+
#
|
|
144
|
+
# @param id_or_code [String] The recipient code (RCP_...) or numeric ID
|
|
145
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
146
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
40
147
|
# @see https://paystack.com/docs/api/transfer-recipient/#delete
|
|
41
|
-
def delete(
|
|
42
|
-
validate_presence!(value:
|
|
43
|
-
|
|
148
|
+
def delete(id_or_code:)
|
|
149
|
+
validate_presence!(value: id_or_code, name: "id_or_code")
|
|
150
|
+
|
|
151
|
+
handle_response(@connection.delete("/transferrecipient/#{escape_path(id_or_code, name: "id_or_code")}"))
|
|
44
152
|
end
|
|
45
153
|
end
|
|
46
154
|
end
|
|
@@ -1,46 +1,281 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generated by bin/paystack-scaffold from Paystack's OpenAPI spec (PaystackOSS/openapi@d9d444d), then
|
|
4
|
+
# edited by hand against Paystack's Transfers and Transfers Control docs pages (descriptions, docs
|
|
5
|
+
# links and the `fetch(id_or_code:)` keyword), so the scaffold treats it as hand-written and will not regenerate it.
|
|
6
|
+
# Hand-written extras go in lib/paystack_sdk/resources/extensions/transfers.rb.
|
|
7
|
+
|
|
8
|
+
require_relative "base"
|
|
2
9
|
|
|
3
10
|
module PaystackSdk
|
|
4
11
|
module Resources
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
validate_required_params!(
|
|
11
|
-
payload: params,
|
|
12
|
-
required_params: %i[source amount recipient],
|
|
13
|
-
operation_name: "Create Transfer"
|
|
14
|
-
)
|
|
15
|
-
handle_response(@connection.post("/transfer", params))
|
|
16
|
-
end
|
|
12
|
+
# Transfer operations: send money from your balance to transfer recipients, and manage the OTP
|
|
13
|
+
# requirement that guards them.
|
|
14
|
+
class Transfers < PaystackSdk::Resources::Base
|
|
15
|
+
# Ruby keyword => the parameter name Paystack documents.
|
|
16
|
+
WIRE_NAMES = {next_cursor: "next", per_page: "perPage"}.freeze
|
|
17
17
|
|
|
18
|
-
# List
|
|
18
|
+
# List Transfers.
|
|
19
|
+
#
|
|
20
|
+
# List the transfers made on your integration.
|
|
21
|
+
#
|
|
22
|
+
# @param use_cursor [Boolean] Set to true to use cursor-based pagination (the response meta then carries `next` and `previous`)
|
|
23
|
+
# @param next_cursor [String] The `next` cursor from a previous cursor-based response
|
|
24
|
+
# @param previous [String] The `previous` cursor from a previous cursor-based response
|
|
25
|
+
# @param per_page [Integer] How many records to retrieve per page (Paystack defaults to 50)
|
|
26
|
+
# @param page [Integer] The page to retrieve (Paystack defaults to 1)
|
|
27
|
+
# @param from [String, Date, Time] A timestamp from which to start listing transfers
|
|
28
|
+
# @param to [String, Date, Time] A timestamp at which to stop listing transfers
|
|
29
|
+
# @param recipient [Integer] Filter by the recipient ID
|
|
30
|
+
# @param status [String] Filter by status. One of: pending, success, failed, otp, abandoned, reversed, blocked, rejected, received.
|
|
31
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
32
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
19
33
|
# @see https://paystack.com/docs/api/transfer/#list
|
|
20
|
-
def list(
|
|
21
|
-
|
|
34
|
+
def list(
|
|
35
|
+
use_cursor: nil,
|
|
36
|
+
next_cursor: nil,
|
|
37
|
+
previous: nil,
|
|
38
|
+
per_page: nil,
|
|
39
|
+
page: nil,
|
|
40
|
+
from: nil,
|
|
41
|
+
to: nil,
|
|
42
|
+
recipient: nil,
|
|
43
|
+
status: nil
|
|
44
|
+
)
|
|
45
|
+
validate_positive_integer!(value: per_page, name: "perPage")
|
|
46
|
+
validate_positive_integer!(value: page, name: "page")
|
|
47
|
+
validate_allowed_values!(
|
|
48
|
+
value: status,
|
|
49
|
+
allowed_values: %w[pending success failed otp abandoned reversed blocked rejected received],
|
|
50
|
+
name: "status"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
wire_query = to_wire(
|
|
54
|
+
{
|
|
55
|
+
use_cursor:,
|
|
56
|
+
next_cursor:,
|
|
57
|
+
previous:,
|
|
58
|
+
per_page:,
|
|
59
|
+
page:,
|
|
60
|
+
from: format_datetime(from, name: "from"),
|
|
61
|
+
to: format_datetime(to, name: "to"),
|
|
62
|
+
recipient:,
|
|
63
|
+
status:
|
|
64
|
+
},
|
|
65
|
+
WIRE_NAMES
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
handle_response(@connection.get("/transfer", wire_query))
|
|
22
69
|
end
|
|
23
70
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
71
|
+
# Initiate Transfer.
|
|
72
|
+
#
|
|
73
|
+
# Send money to your customers. The transfer's status is `pending` when the OTP requirement is
|
|
74
|
+
# disabled, and `otp` when an OTP is required (complete it with {#finalize}).
|
|
75
|
+
#
|
|
76
|
+
# @param amount [Integer] Amount to transfer in the currency's subunit (kobo for NGN, pesewas for GHS)
|
|
77
|
+
# @param recipient [String] Code for the transfer recipient (RCP_...)
|
|
78
|
+
# @param reference [String] A unique identifier for the transfer, so retrying it cannot create a second transfer. Paystack documents 16 to 50 characters of lowercase letters, digits, `-` and `_`.
|
|
79
|
+
# @param source [String] Where to transfer from. Only "balance" for now.
|
|
80
|
+
# @param reason [String] The reason for the transfer; it also shows up in the narration of the recipient's credit notification
|
|
81
|
+
# @param currency [String] The currency of the transfer; Paystack defaults to NGN. One of: NGN, ZAR, KES, GHS.
|
|
82
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
83
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
84
|
+
# @see https://paystack.com/docs/api/transfer/#initiate
|
|
85
|
+
def create(amount:, recipient:, reference:, source:, reason: nil, currency: nil)
|
|
86
|
+
validate_presence!(value: amount, name: "amount")
|
|
87
|
+
validate_positive_integer!(value: amount, name: "amount")
|
|
88
|
+
validate_presence!(value: recipient, name: "recipient")
|
|
89
|
+
validate_presence!(value: reference, name: "reference")
|
|
90
|
+
validate_reference_format!(reference: reference, name: "reference")
|
|
91
|
+
validate_presence!(value: source, name: "source")
|
|
92
|
+
validate_allowed_values!(
|
|
93
|
+
value: currency,
|
|
94
|
+
allowed_values: %w[NGN ZAR KES GHS],
|
|
95
|
+
name: "currency"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
wire_body = to_wire(
|
|
99
|
+
{
|
|
100
|
+
amount:,
|
|
101
|
+
recipient:,
|
|
102
|
+
reference:,
|
|
103
|
+
source:,
|
|
104
|
+
reason:,
|
|
105
|
+
currency:
|
|
106
|
+
},
|
|
107
|
+
WIRE_NAMES
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
handle_response(@connection.post("/transfer", wire_body))
|
|
29
111
|
end
|
|
30
112
|
|
|
31
|
-
# Finalize
|
|
113
|
+
# Finalize Transfer.
|
|
114
|
+
#
|
|
115
|
+
# Finalize an initiated transfer that is waiting for an OTP.
|
|
116
|
+
#
|
|
117
|
+
# @param transfer_code [String] The transfer code you want to finalize
|
|
118
|
+
# @param otp [String] OTP sent to the business phone to verify the transfer
|
|
119
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
120
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
32
121
|
# @see https://paystack.com/docs/api/transfer/#finalize
|
|
33
122
|
def finalize(transfer_code:, otp:)
|
|
34
123
|
validate_presence!(value: transfer_code, name: "transfer_code")
|
|
35
124
|
validate_presence!(value: otp, name: "otp")
|
|
36
|
-
|
|
125
|
+
|
|
126
|
+
wire_body = to_wire({transfer_code:, otp:}, WIRE_NAMES)
|
|
127
|
+
|
|
128
|
+
handle_response(@connection.post("/transfer/finalize_transfer", wire_body))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Initiate Bulk Transfer.
|
|
132
|
+
#
|
|
133
|
+
# Batch multiple transfers in a single request. You need to disable the Transfers OTP
|
|
134
|
+
# requirement to use this endpoint.
|
|
135
|
+
#
|
|
136
|
+
# @param source [String] Where to transfer from. Only "balance" for now.
|
|
137
|
+
# @param transfers [Array<Hash>] The transfers, each with `amount`, `recipient`, `reference` and optionally `reason`, named as Paystack names them
|
|
138
|
+
# @param currency [String] The currency of the transfers. One of: NGN, ZAR, KES, GHS.
|
|
139
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
140
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
141
|
+
# @see https://paystack.com/docs/api/transfer/#bulk
|
|
142
|
+
def bulk_create(source:, transfers:, currency: nil)
|
|
143
|
+
validate_presence!(value: source, name: "source")
|
|
144
|
+
validate_presence!(value: transfers, name: "transfers")
|
|
145
|
+
validate_allowed_values!(
|
|
146
|
+
value: currency,
|
|
147
|
+
allowed_values: %w[NGN ZAR KES GHS],
|
|
148
|
+
name: "currency"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
wire_body = to_wire({source:, transfers:, currency:}, WIRE_NAMES)
|
|
152
|
+
|
|
153
|
+
handle_response(@connection.post("/transfer/bulk", wire_body))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Fetch Transfer.
|
|
157
|
+
#
|
|
158
|
+
# Get details of a transfer on your integration.
|
|
159
|
+
#
|
|
160
|
+
# @param id_or_code [String, Integer] The transfer ID or code you want to fetch
|
|
161
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
162
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
163
|
+
# @see https://paystack.com/docs/api/transfer/#fetch
|
|
164
|
+
def fetch(id_or_code:)
|
|
165
|
+
validate_presence!(value: id_or_code, name: "id_or_code")
|
|
166
|
+
|
|
167
|
+
handle_response(@connection.get("/transfer/#{escape_path(id_or_code, name: "id_or_code")}"))
|
|
37
168
|
end
|
|
38
169
|
|
|
39
|
-
# Verify
|
|
170
|
+
# Verify Transfer.
|
|
171
|
+
#
|
|
172
|
+
# Verify the status of a transfer on your integration.
|
|
173
|
+
#
|
|
174
|
+
# @param reference [String] Transfer reference
|
|
175
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
176
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
40
177
|
# @see https://paystack.com/docs/api/transfer/#verify
|
|
41
178
|
def verify(reference:)
|
|
42
179
|
validate_presence!(value: reference, name: "reference")
|
|
43
|
-
|
|
180
|
+
|
|
181
|
+
handle_response(
|
|
182
|
+
@connection.get("/transfer/verify/#{escape_path(reference, name: "reference")}")
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Export Transfers.
|
|
187
|
+
#
|
|
188
|
+
# Export a list of transfers carried out on your integration. This operation is in Paystack's
|
|
189
|
+
# OpenAPI spec but not on its docs page; the API answers it (404 "Transfers not found" when
|
|
190
|
+
# nothing matches).
|
|
191
|
+
#
|
|
192
|
+
# @param recipient [String] Export transfers by the recipient code
|
|
193
|
+
# @param status [String] Export transfers by status. One of: pending, success, failed, otp, abandoned, reversed, blocked, rejected, received.
|
|
194
|
+
# @param from [String, Date, Time] The start date
|
|
195
|
+
# @param to [String, Date, Time] The end date
|
|
196
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
197
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
198
|
+
# @see https://paystack.com/docs/api/transfer/
|
|
199
|
+
def export(recipient: nil, status: nil, from: nil, to: nil)
|
|
200
|
+
validate_allowed_values!(
|
|
201
|
+
value: status,
|
|
202
|
+
allowed_values: %w[pending success failed otp abandoned reversed blocked rejected received],
|
|
203
|
+
name: "status"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
wire_query = to_wire(
|
|
207
|
+
{
|
|
208
|
+
recipient:,
|
|
209
|
+
status:,
|
|
210
|
+
from: format_datetime(from, name: "from"),
|
|
211
|
+
to: format_datetime(to, name: "to")
|
|
212
|
+
},
|
|
213
|
+
WIRE_NAMES
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
handle_response(@connection.get("/transfer/export", wire_query))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Resend OTP for Transfer.
|
|
220
|
+
#
|
|
221
|
+
# Generates a new OTP and sends it to the business phone, for when it has trouble receiving one.
|
|
222
|
+
#
|
|
223
|
+
# @param transfer_code [String] The transfer code that requires an OTP validation
|
|
224
|
+
# @param reason [String] The purpose of the OTP. The docs list "resend_otp" and "transfer"; the spec also allows "disable_otp".
|
|
225
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
226
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
227
|
+
# @see https://paystack.com/docs/api/transfer-control/#resend-otp
|
|
228
|
+
def resend_otp(transfer_code:, reason:)
|
|
229
|
+
validate_presence!(value: transfer_code, name: "transfer_code")
|
|
230
|
+
validate_presence!(value: reason, name: "reason")
|
|
231
|
+
validate_allowed_values!(
|
|
232
|
+
value: reason,
|
|
233
|
+
allowed_values: %w[disable_otp resend_otp transfer],
|
|
234
|
+
name: "reason"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
wire_body = to_wire({transfer_code:, reason:}, WIRE_NAMES)
|
|
238
|
+
|
|
239
|
+
handle_response(@connection.post("/transfer/resend_otp", wire_body))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Disable OTP for Transfers.
|
|
243
|
+
#
|
|
244
|
+
# Start turning off the OTP requirement so transfers can be completed programmatically.
|
|
245
|
+
# Paystack sends an OTP to the business phone; confirm with {#finalize_disable_otp}.
|
|
246
|
+
#
|
|
247
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
248
|
+
# @raise [PaystackSdk::Error] If the API request fails.
|
|
249
|
+
# @see https://paystack.com/docs/api/transfer-control/#disable-otp
|
|
250
|
+
def disable_otp
|
|
251
|
+
handle_response(@connection.post("/transfer/disable_otp"))
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Finalize Disabling OTP for Transfers.
|
|
255
|
+
#
|
|
256
|
+
# Finalize the request to disable OTP on your transfers.
|
|
257
|
+
#
|
|
258
|
+
# @param otp [String] OTP sent to the business phone to verify disabling the OTP requirement
|
|
259
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
260
|
+
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
261
|
+
# @see https://paystack.com/docs/api/transfer-control/#finalize-disable-otp
|
|
262
|
+
def finalize_disable_otp(otp:)
|
|
263
|
+
validate_presence!(value: otp, name: "otp")
|
|
264
|
+
|
|
265
|
+
wire_body = to_wire({otp:}, WIRE_NAMES)
|
|
266
|
+
|
|
267
|
+
handle_response(@connection.post("/transfer/disable_otp_finalize", wire_body))
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Enable OTP requirement for Transfers.
|
|
271
|
+
#
|
|
272
|
+
# Turn the OTP requirement for transfers back on.
|
|
273
|
+
#
|
|
274
|
+
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
275
|
+
# @raise [PaystackSdk::Error] If the API request fails.
|
|
276
|
+
# @see https://paystack.com/docs/api/transfer-control/#enable-otp
|
|
277
|
+
def enable_otp
|
|
278
|
+
handle_response(@connection.post("/transfer/enable_otp"))
|
|
44
279
|
end
|
|
45
280
|
end
|
|
46
281
|
end
|
|
@@ -60,6 +60,19 @@ module PaystackSdk
|
|
|
60
60
|
# @return [Integer] The status code of the API response
|
|
61
61
|
attr_reader :status_code
|
|
62
62
|
|
|
63
|
+
# Pagination metadata that Paystack returns with list responses
|
|
64
|
+
# (e.g. `total`, `page`, `pageCount`, `perPage`).
|
|
65
|
+
#
|
|
66
|
+
# @return [Response, nil] The wrapped `meta` object, or nil if the response has none
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# response = transactions.list
|
|
70
|
+
# response.meta.total # => 40
|
|
71
|
+
# response.meta.pageCount # => 2
|
|
72
|
+
def meta
|
|
73
|
+
@meta ||= wrap_value(@raw_meta) if @raw_meta
|
|
74
|
+
end
|
|
75
|
+
|
|
63
76
|
# Initializes a new Response object
|
|
64
77
|
#
|
|
65
78
|
# @param response [Faraday::Response, Hash, Array] The raw API response or data
|
|
@@ -75,10 +88,15 @@ module PaystackSdk
|
|
|
75
88
|
@api_message = extract_api_message(@body)
|
|
76
89
|
@message = @api_message
|
|
77
90
|
@raw_data = extract_data_from_body(@body)
|
|
91
|
+
@raw_meta = @body["meta"] if @body.is_a?(Hash) && @body["meta"].is_a?(Hash)
|
|
78
92
|
|
|
79
93
|
case @status_code
|
|
80
94
|
when 200..299
|
|
81
95
|
@success = true
|
|
96
|
+
when 429
|
|
97
|
+
# Rate limiting - raise so callers can back off (the connection
|
|
98
|
+
# already retries automatically unless max_retries is 0)
|
|
99
|
+
raise RateLimitError.new(rate_limit_reset(response))
|
|
82
100
|
when 400..499
|
|
83
101
|
# Client errors - return unsuccessful response for user to handle
|
|
84
102
|
@success = false
|
|
@@ -86,10 +104,6 @@ module PaystackSdk
|
|
|
86
104
|
|
|
87
105
|
# Still raise for authentication issues as these are usually config problems
|
|
88
106
|
raise AuthenticationError.new(@api_message || "Authentication failed") if @status_code == 401
|
|
89
|
-
when 429
|
|
90
|
-
# Rate limiting - raise as users need to implement retry logic
|
|
91
|
-
retry_after = response.headers["Retry-After"]
|
|
92
|
-
raise RateLimitError.new(retry_after || 30)
|
|
93
107
|
when 500..599
|
|
94
108
|
# Server errors - raise as these indicate Paystack infrastructure issues
|
|
95
109
|
raise ServerError.new(@status_code, @api_message)
|
|
@@ -102,6 +116,7 @@ module PaystackSdk
|
|
|
102
116
|
@error_message = response.error_message
|
|
103
117
|
@api_message = response.api_message
|
|
104
118
|
@raw_data = response.raw_data
|
|
119
|
+
@raw_meta = response.raw_meta
|
|
105
120
|
else
|
|
106
121
|
@success = true
|
|
107
122
|
@raw_data = response
|
|
@@ -123,6 +138,34 @@ module PaystackSdk
|
|
|
123
138
|
@success
|
|
124
139
|
end
|
|
125
140
|
|
|
141
|
+
# Whether this response is a successful payment: the call succeeded and the transaction's
|
|
142
|
+
# `status` is "success". Paystack's docs say to confirm the amount and currency as well, so
|
|
143
|
+
# pass the ones you expect and they are compared too.
|
|
144
|
+
#
|
|
145
|
+
# @param amount [Integer, nil] The amount (in the currency's subunit) you expect
|
|
146
|
+
# @param currency [String, nil] The currency you expect, e.g. "GHS"
|
|
147
|
+
# @return [Boolean]
|
|
148
|
+
#
|
|
149
|
+
# @example
|
|
150
|
+
# response = client.transactions.verify(reference: ref)
|
|
151
|
+
# response.paid?(amount: 5000, currency: "GHS")
|
|
152
|
+
def paid?(amount: nil, currency: nil)
|
|
153
|
+
return false unless success? && status?(:success)
|
|
154
|
+
return false if amount && field(:amount) != amount
|
|
155
|
+
return false if currency && field(:currency).to_s.upcase != currency.to_s.upcase
|
|
156
|
+
|
|
157
|
+
true
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Whether the `status` field in the response data equals the given value, e.g.
|
|
161
|
+
# `response.status?(:send_pin)` on a charge. Paystack names the values; none are assumed here.
|
|
162
|
+
#
|
|
163
|
+
# @param value [String, Symbol]
|
|
164
|
+
# @return [Boolean]
|
|
165
|
+
def status?(value)
|
|
166
|
+
field(:status).to_s == value.to_s
|
|
167
|
+
end
|
|
168
|
+
|
|
126
169
|
# Check if the response failed
|
|
127
170
|
#
|
|
128
171
|
# @return [Boolean] true if the API request failed
|
|
@@ -151,6 +194,14 @@ module PaystackSdk
|
|
|
151
194
|
@body
|
|
152
195
|
end
|
|
153
196
|
|
|
197
|
+
# One field of a Hash response body, whichever way it is keyed; nil otherwise.
|
|
198
|
+
def field(name)
|
|
199
|
+
return nil unless @raw_data.is_a?(Hash)
|
|
200
|
+
|
|
201
|
+
@raw_data.key?(name) ? @raw_data[name] : @raw_data[name.to_s]
|
|
202
|
+
end
|
|
203
|
+
private :field
|
|
204
|
+
|
|
154
205
|
# Access hash values via methods (dot notation)
|
|
155
206
|
# Allows accessing data attributes directly: response.attribute_name
|
|
156
207
|
#
|
|
@@ -181,7 +232,8 @@ module PaystackSdk
|
|
|
181
232
|
super
|
|
182
233
|
end
|
|
183
234
|
|
|
184
|
-
# Access data via hash/array notation
|
|
235
|
+
# Access data via hash/array notation.
|
|
236
|
+
# Hash keys can be given as strings or symbols, whichever way the data is keyed.
|
|
185
237
|
#
|
|
186
238
|
# @param key [Object] The key or index to access
|
|
187
239
|
# @return [Object, Response] The value for the given key or index
|
|
@@ -189,19 +241,19 @@ module PaystackSdk
|
|
|
189
241
|
return nil unless @raw_data
|
|
190
242
|
|
|
191
243
|
if @raw_data.is_a?(Hash)
|
|
192
|
-
|
|
193
|
-
wrap_value(
|
|
244
|
+
actual_key = lookup_key(key)
|
|
245
|
+
wrap_value(@raw_data[actual_key]) unless actual_key.nil?
|
|
194
246
|
elsif @raw_data.is_a?(Array) && key.is_a?(Integer)
|
|
195
247
|
wrap_value(@raw_data[key])
|
|
196
248
|
end
|
|
197
249
|
end
|
|
198
250
|
|
|
199
|
-
# Check if key exists in hash
|
|
251
|
+
# Check if key exists in hash (as a string or a symbol)
|
|
200
252
|
#
|
|
201
253
|
# @param key [Symbol, String] The key to check
|
|
202
254
|
# @return [Boolean] Whether the key exists
|
|
203
255
|
def key?(key)
|
|
204
|
-
@raw_data.is_a?(Hash) &&
|
|
256
|
+
@raw_data.is_a?(Hash) && !lookup_key(key).nil?
|
|
205
257
|
end
|
|
206
258
|
|
|
207
259
|
# Iterate through hash entries or array items
|
|
@@ -248,8 +300,35 @@ module PaystackSdk
|
|
|
248
300
|
end
|
|
249
301
|
end
|
|
250
302
|
|
|
303
|
+
protected
|
|
304
|
+
|
|
305
|
+
# @return [Hash, nil] The unwrapped `meta` from the response body
|
|
306
|
+
attr_reader :raw_meta
|
|
307
|
+
|
|
251
308
|
private
|
|
252
309
|
|
|
310
|
+
# Finds the key actually used in the data for a string or symbol lookup.
|
|
311
|
+
#
|
|
312
|
+
# @return [Object, nil] The key present in the data, or nil if there is none
|
|
313
|
+
def lookup_key(key)
|
|
314
|
+
return key if @raw_data.key?(key)
|
|
315
|
+
|
|
316
|
+
alternate = case key
|
|
317
|
+
when String then key.to_sym
|
|
318
|
+
when Symbol then key.to_s
|
|
319
|
+
end
|
|
320
|
+
alternate if !alternate.nil? && @raw_data.key?(alternate)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Seconds until the rate-limit window ends, from Paystack's
|
|
324
|
+
# `x-ratelimit-reset` header (nil if absent, not numeric, negative or not finite).
|
|
325
|
+
def rate_limit_reset(response)
|
|
326
|
+
seconds = Float(response.headers["x-ratelimit-reset"])
|
|
327
|
+
(seconds.finite? && seconds >= 0) ? seconds.ceil : nil
|
|
328
|
+
rescue ArgumentError, TypeError
|
|
329
|
+
nil
|
|
330
|
+
end
|
|
331
|
+
|
|
253
332
|
# Extract the identifier from an error response
|
|
254
333
|
# This looks for common patterns in error messages to find resource identifiers
|
|
255
334
|
#
|