paystack_sdk 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a47ad018e13e2e59c645d1834e38ff503c74573670e90fd3e286ff090cc0768d
4
- data.tar.gz: 3ff34906416d4d3d33315af10537113fbc64189b002a95c5d27b6df32b9c5592
3
+ metadata.gz: 8893378c4721f9f126d73b888dd539060c93058488b49c92833183b819d06023
4
+ data.tar.gz: a787c8b3b413e4171c3574e38dc519c0b2eed3df09776b383a0b20fc259bd75a
5
5
  SHA512:
6
- metadata.gz: d041e1cb40107e93fb39e27220c5f33c52baa8e6167a7812c907a1272311bfa7a782f5b1e9e358afa8e5900c56408f62892e35b5b4b3e71fb37b44f1228aa488
7
- data.tar.gz: e1f0f1ffb505ad4a0b6c3c8944ada4e4fd61d9ab29cf7f87551c8697ba606f94bd975068396d3ca5b2226d24ccba38e7f431fab2204b177ccd5da5a1f97176fb
6
+ metadata.gz: a205e604c23fc3ca07a1ad7096ef920c9587fc04d322da9c40e6e00bd92870c2d833f554b275c6651a3f1aa07c23d56e2cb77fc41d2a02005e5848f8686942a4
7
+ data.tar.gz: d5cbb5b6bfefe710024e1cba4dcaa2097577b4df3f17e001199b281945cb8d417000c89a0fd5292d8ca8444272d55313f010b2acfadc130246372433f0465166
data/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.0] - 2026-10-09
6
+
7
+ ### Added
8
+
9
+ - `client.splits` for Paystack's Transaction Splits API: `create`, `list`, `fetch`, `update`, `add_subaccount` (also updates an existing subaccount's share) and `remove_subaccount`, generated from the OpenAPI spec. `list` sends `perPage` (the API ignores the spec's `per_page`); `fetch` takes a split code or numeric ID; `add_subaccount` requires `subaccount` and `share`, and `remove_subaccount` requires `subaccount`, as the docs and test API do.
10
+ - `disputes` (new, generated from Paystack's OpenAPI spec): `list`, `fetch`, `list_transaction`, `update`, `add_evidence`, `fetch_upload_url`, `resolve` and `export`. `fetch_upload_url` takes `upload_filename`, which Paystack documents and the test API honours although the OpenAPI spec omits it.
11
+ - `Subaccounts` (`client.subaccounts`): `create`, `list`, `fetch(id_or_code:)` and `update(id_or_code:, ...)`, generated from Paystack's OpenAPI spec. The bank is sent as `bank_code`, as Paystack's docs name it (the spec says `settlement_bank`; the test API reads both and prefers `bank_code`). `list(active:)` takes `1` or `0`: Paystack reads `true` as inactive.
12
+ - `Plans` (`client.plans`): `create`, `list`, `fetch(id_or_code:)` and `update(id_or_code:, ...)`, generated from Paystack's OpenAPI spec. `interval` accepts `hourly` and `quarterly` as well as the spec's five values (the test API accepts all seven; the spec omits two). `update` takes `update_existing_subscriptions`, which Paystack documents and the test API validates although the spec omits it; leave it out and Paystack applies the change to existing subscriptions. `list(status:)` is documented by Paystack but absent from the spec.
13
+ - `settlements` (new): `list(per_page:, page:, from:, to:)` for `GET /settlement` and `transactions(id:)` for `GET /settlement/{id}/transactions`. `from` and `to` are in Paystack's docs but not the spec; the test API validates them.
14
+ - `Balances` (`client.balances`): `fetch` and `ledger(per_page:, page:, from:, to:)`, generated from Paystack's OpenAPI spec and read-only (GET). `fetch` returns one entry per currency with the balance in the currency's subunit (pesewas for GHS). Paystack's docs have no Balance page; the two operations sit on the Transfers Control page and the behaviour was confirmed against the test API.
15
+ - `Subscriptions` (`client.subscriptions`): `create`, `list`, `fetch(id_or_code:)`, `enable(code:, token:)`, `disable(code:, token:)`, `generate_update_link(code:)` and `send_update_link(code:)`, generated from Paystack's OpenAPI spec. `list` filters by `plan_id:` and `customer_id:` (sent as `plan` and `customer`): the test API matches numeric IDs only. `start_date` is checked as ISO 8601 before sending, since the test API refuses an invalid one but still creates the subscription.
16
+ - `DedicatedVirtualAccounts` (`client.dedicated_virtual_accounts`), generated from Paystack's OpenAPI spec: `create`, `assign`, `list`, `fetch(dedicated_account_id:)`, `requery`, `deactivate(dedicated_account_id:)`, `add_split`, `remove_split` and `fetch_bank_providers`.
17
+ - `PaymentRequests` (`client.payment_requests`), generated from Paystack's OpenAPI spec: `create`, `list`, `fetch(id_or_code:)`, `update(id_or_code:)`, `verify(code:)`, `notify(code:)`, `totals`, `finalize(id_or_code:, send_notification: nil)` and `archive(id_or_code:)`. Checked against the Paystack test API, `create` does not require `amount` when `line_items` or `tax` are given (the spec says it is required), takes `metadata` as a Hash and `redirect_url` (both docs-only), and accepts a date as `due_date`; `list` filters by the numeric `customer_id:` and takes `include_archive:`.
18
+ - `bin/paystack-scaffold` applies `required: true` from body-field entries in `spec/support/paystack_contract_exceptions.yml`, as it already did for query parameters.
19
+ - `bin/paystack-scaffold` applies `enum:` from body-field entries in `spec/support/paystack_contract_exceptions.yml`, as it already did for query parameters.
20
+
21
+ ### Fixed
22
+
23
+ - `bin/paystack-scaffold` now adds a resource's `Client` accessor above `Client`'s private helpers, where it is public, instead of at the end of the class. It also finds the docs anchor of an operation whose cURL sample on the docs page calls another path (Split Dedicated Account Transaction), and corrects the `@see` link of `customers.direct_debit_activation_charge` to `#directdebit-activation-charge`.
24
+
5
25
  ## [0.2.0] - 2026-10-09
6
26
 
7
27
  ### Breaking
data/README.md CHANGED
@@ -41,6 +41,36 @@ The `paystack_sdk` gem provides a simple and intuitive interface for interacting
41
41
  - [Refunds](#refunds)
42
42
  - [Create a Refund](#create-a-refund)
43
43
  - [Fetch, List and Retry Refunds](#fetch-list-and-retry-refunds)
44
+ - [Settlements](#settlements)
45
+ - [Splits](#splits)
46
+ - [Create a Split](#create-a-split)
47
+ - [List, Fetch and Update Splits](#list-fetch-and-update-splits)
48
+ - [Add, Update or Remove a Subaccount](#add-update-or-remove-a-subaccount)
49
+ - [Disputes](#disputes)
50
+ - [List and Fetch Disputes](#list-and-fetch-disputes)
51
+ - [Answer a Dispute](#answer-a-dispute)
52
+ - [Subaccounts](#subaccounts)
53
+ - [Create a Subaccount](#create-a-subaccount)
54
+ - [List and Fetch Subaccounts](#list-and-fetch-subaccounts)
55
+ - [Update a Subaccount](#update-a-subaccount)
56
+ - [Balances](#balances)
57
+ - [Plans](#plans)
58
+ - [Create a Plan](#create-a-plan)
59
+ - [List and Fetch Plans](#list-and-fetch-plans)
60
+ - [Update a Plan](#update-a-plan)
61
+ - [Subscriptions](#subscriptions)
62
+ - [Create a Subscription](#create-a-subscription)
63
+ - [List and Fetch Subscriptions](#list-and-fetch-subscriptions)
64
+ - [Enable or Disable a Subscription](#enable-or-disable-a-subscription)
65
+ - [Let the Customer Update Their Card](#let-the-customer-update-their-card)
66
+ - [Dedicated Virtual Accounts](#dedicated-virtual-accounts)
67
+ - [Create or Assign an Account](#create-or-assign-an-account)
68
+ - [List, Fetch, Requery and Deactivate](#list-fetch-requery-and-deactivate)
69
+ - [Split Payments on an Account](#split-payments-on-an-account)
70
+ - [Payment Requests](#payment-requests)
71
+ - [Create a Payment Request](#create-a-payment-request)
72
+ - [List, Fetch and Verify Payment Requests](#list-fetch-and-verify-payment-requests)
73
+ - [Update, Finalize, Notify and Archive](#update-finalize-notify-and-archive)
44
74
  - [Response Handling](#response-handling)
45
75
  - [Working with Response Objects](#working-with-response-objects)
46
76
  - [Accessing the Original Response](#accessing-the-original-response)
@@ -894,6 +924,445 @@ paystack.refunds.retry_with_customer_details(
894
924
  ```
895
925
 
896
926
  Paystack's docs also list a `currency` filter on the list endpoint, but the test API returned the same refunds for every currency, so the SDK does not offer it.
927
+ ### Settlements
928
+
929
+ Settlements are the payouts Paystack makes to your bank account. Use them to reconcile what was paid out against the transactions behind each payout.
930
+
931
+ ```ruby
932
+ # List settlements (Paystack's defaults apply: 50 per page, page 1)
933
+ paystack.settlements.list
934
+ paystack.settlements.list(per_page: 20, page: 2)
935
+
936
+ # Only settlements within a date range (a date or an ISO 8601 timestamp)
937
+ paystack.settlements.list(from: "2025-01-01", to: "2025-01-31T23:59:59Z")
938
+
939
+ # The transactions that make up one settlement
940
+ paystack.settlements.transactions(id: 3090024)
941
+ ```
942
+
943
+ Paystack's docs also list `status` and `subaccount` filters on the list endpoint, but they are not in its OpenAPI spec and the SDK does not offer them yet: the test account has no settlements, so whether they filter could not be checked. `transactions` takes no filters or pagination for the same reason.
944
+
945
+ ### Splits
946
+
947
+ A transaction split shares the settlement for a payment between your payout account and one or more subaccounts (`ACCT_...`). Create the split once, then pass its `split_code` when you initialize a transaction or create a charge.
948
+
949
+ #### Create a Split
950
+
951
+ ```ruby
952
+ # type is "percentage" (each share is a percentage) or "flat" (each share is an amount in the
953
+ # currency's subunit: kobo, pesewas, cents). currency must be enabled on your integration.
954
+ response = paystack.splits.create(
955
+ name: "Halfsies",
956
+ type: "percentage",
957
+ currency: "GHS",
958
+ subaccounts: [{subaccount: "ACCT_6uujpqtzmnufzkw", share: 50}],
959
+ # Optional: who pays Paystack's fee. One of "account", "subaccount", "all-proportional" or "all"
960
+ # (Paystack's default). "subaccount" also needs bearer_subaccount, a subaccount in the split.
961
+ bearer_type: "all-proportional"
962
+ )
963
+
964
+ split_code = response.data.split_code if response.success? # e.g. "SPL_RcScyW5jp2"
965
+ ```
966
+
967
+ #### List, Fetch and Update Splits
968
+
969
+ ```ruby
970
+ # List, with Paystack's page pagination (default 50 per page) and filters
971
+ paystack.splits.list(per_page: 20, page: 1, active: true)
972
+ paystack.splits.list(name: "Halfsies", subaccount_code: "ACCT_6uujpqtzmnufzkw", from: "2025-01-01", to: "2025-04-30")
973
+
974
+ # Fetch by numeric ID or by split code
975
+ paystack.splits.fetch(id: 2703655)
976
+ paystack.splits.fetch(id: "SPL_RcScyW5jp2")
977
+
978
+ # Update (by numeric ID only: Paystack refuses a split code here)
979
+ paystack.splits.update(id: 2703655, active: false)
980
+ paystack.splits.update(id: 2703655, bearer_type: "subaccount", bearer_subaccount: "ACCT_6uujpqtzmnufzkw")
981
+ ```
982
+
983
+ #### Add, Update or Remove a Subaccount
984
+
985
+ ```ruby
986
+ # Adds the subaccount, or changes its share if it is already in the split (numeric split ID)
987
+ paystack.splits.add_subaccount(id: 2703655, subaccount: "ACCT_eg4sob4590pq9vb", share: 20)
988
+
989
+ paystack.splits.remove_subaccount(id: 2703655, subaccount: "ACCT_eg4sob4590pq9vb")
990
+ ```
991
+
992
+ ### Disputes
993
+
994
+ Disputes (chargebacks) are filed against your transactions by customers or their banks. Listing and fetching are read-only. `update`, `add_evidence` and `resolve` change a real dispute, so call them only when you mean to answer it.
995
+
996
+ #### List and Fetch Disputes
997
+
998
+ ```ruby
999
+ # List, with Paystack's page pagination; filter by status, transaction ID or date range.
1000
+ # status is one of awaiting-merchant-feedback, awaiting-bank-feedback, pending, resolved
1001
+ response = paystack.disputes.list(status: "awaiting-merchant-feedback", per_page: 20, from: "2025-01-01", to: "2025-04-30")
1002
+ response.data.each { |dispute| puts "#{dispute.id}: #{dispute.status}" } if response.success?
1003
+
1004
+ # Fetch one dispute, or every dispute filed for a transaction
1005
+ paystack.disputes.fetch(id: 2867)
1006
+ paystack.disputes.list_transaction(id: 5991760)
1007
+
1008
+ # Export disputes. Paystack answers 404 "No disputes found" when there is nothing to export.
1009
+ paystack.disputes.export(from: "2025-01-01", to: "2025-04-30")
1010
+ ```
1011
+
1012
+ #### Answer a Dispute
1013
+
1014
+ ```ruby
1015
+ # 1. Get a signed URL, upload the evidence file to it, and keep the returned file name
1016
+ upload = paystack.disputes.fetch_upload_url(id: 2867, upload_filename: "receipt.pdf")
1017
+ upload.data.signedUrl # upload your file to this URL
1018
+ upload.data.fileName # pass this as uploaded_filename
1019
+
1020
+ # 2. Add evidence
1021
+ paystack.disputes.add_evidence(
1022
+ id: 2867,
1023
+ customer_email: "ama@example.com",
1024
+ customer_name: "Ama Mensah",
1025
+ customer_phone: "0802345167",
1026
+ service_details: "Tithe payment for March",
1027
+ delivery_date: "2025-03-31"
1028
+ )
1029
+
1030
+ # 3. Accept (with a refund) or decline. refund_amount is in the smallest currency unit.
1031
+ # It is `resolve` because `resolve` would shadow a method Ruby already has.
1032
+ paystack.disputes.resolve(
1033
+ id: 2867,
1034
+ resolution: "merchant-accepted", # or "declined"
1035
+ message: "Refunded at the customer's request",
1036
+ refund_amount: 50_000,
1037
+ uploaded_filename: upload.data.fileName
1038
+ )
1039
+
1040
+ # Or change the refund amount and attachment without resolving
1041
+ paystack.disputes.update(id: 2867, refund_amount: 50_000, uploaded_filename: upload.data.fileName)
1042
+ ```
1043
+
1044
+ ### Subaccounts
1045
+
1046
+ A subaccount is a settlement account that receives its share of the payments made to it, such as one per branch or partner. Pass its code (`ACCT_...`) as `subaccount` when you charge or initialize a transaction.
1047
+
1048
+ #### Create a Subaccount
1049
+
1050
+ ```ruby
1051
+ # bank_code comes from paystack.banks.list (country: "ghana" lists GHS banks and the
1052
+ # MTN, VOD and ATL mobile money codes). account_number is a string: leading zeros matter.
1053
+ # percentage_charge is the percentage the main account keeps from each payment (0 to 100).
1054
+ response = paystack.subaccounts.create(
1055
+ business_name: "Grace Chapel Accra",
1056
+ bank_code: "MTN",
1057
+ account_number: "0551234987",
1058
+ percentage_charge: 2.5,
1059
+ description: "Giving for the Accra branch",
1060
+ primary_contact_name: "Ama Mensah",
1061
+ primary_contact_email: "finance@gracechapel.example",
1062
+ primary_contact_phone: "0551234987"
1063
+ )
1064
+
1065
+ if response.success?
1066
+ puts "Created #{response.data.subaccount_code} for #{response.data.account_name}"
1067
+ else
1068
+ puts "Error: #{response.error_message}" # e.g. "Account details are invalid"
1069
+ end
1070
+ ```
1071
+
1072
+ Paystack's docs name the bank field `bank_code`; its OpenAPI spec calls it `settlement_bank`. The API reads both and prefers `bank_code`, so the SDK sends `bank_code`.
1073
+
1074
+ #### List and Fetch Subaccounts
1075
+
1076
+ ```ruby
1077
+ # Paystack's page pagination (default 50 per page)
1078
+ paystack.subaccounts.list(per_page: 20, page: 2)
1079
+
1080
+ # Without active, the test API lists only active subaccounts. active: 0 lists the inactive
1081
+ # ones. Paystack reads any value other than 1, true included, as inactive, so pass 1 or 0.
1082
+ paystack.subaccounts.list(active: 0)
1083
+
1084
+ # Fetch by subaccount code or numeric ID
1085
+ paystack.subaccounts.fetch(id_or_code: "ACCT_6uujpqtzmnufzkw")
1086
+ ```
1087
+
1088
+ #### Update a Subaccount
1089
+
1090
+ ```ruby
1091
+ # Send only what changes
1092
+ paystack.subaccounts.update(id_or_code: "ACCT_6uujpqtzmnufzkw", percentage_charge: 3)
1093
+
1094
+ # Deactivate (or reactivate with active: true)
1095
+ paystack.subaccounts.update(id_or_code: "ACCT_6uujpqtzmnufzkw", active: false)
1096
+
1097
+ # A new settlement account: Paystack needs bank_code and account_number together
1098
+ paystack.subaccounts.update(id_or_code: "ACCT_6uujpqtzmnufzkw", bank_code: "040100", account_number: "1234567890123")
1099
+ ```
1100
+
1101
+ ### Balances
1102
+
1103
+ Read-only. Amounts are integers in the currency's subunit (pesewas for GHS, kobo for NGN), so `48400` is GHS 484.00. Paystack documents both operations on its Transfers Control page.
1104
+
1105
+ ```ruby
1106
+ # One entry per currency your integration holds
1107
+ response = paystack.balances.fetch
1108
+ response.data.first.currency # => "GHS"
1109
+ response.data.first.balance # => 48400
1110
+ # Iterating (each, map) yields plain hashes with string keys
1111
+ response.data.each { |balance| puts "#{balance["currency"]}: #{balance["balance"]}" }
1112
+
1113
+ # Every pay-in and pay-out, newest first. `difference` is the signed change in the subunit
1114
+ # (-2500 for a refund), `balance` the running balance after it, and `model_responsible`
1115
+ # says what caused it ("Transaction", "Refund", "Transfer", ...).
1116
+ ledger = paystack.balances.ledger(per_page: 20, page: 1)
1117
+ ledger.data.each { |entry| puts "#{entry["createdAt"]} #{entry["model_responsible"]} #{entry["difference"]}" }
1118
+ ledger.meta # => total, skipped, perPage, page, pageCount (50 per page by default)
1119
+
1120
+ # Date filters work by calendar day (the time part is ignored). `to` includes that day;
1121
+ # `from` does not, so from: Date.new(2026, 10, 8) returns entries after 8 October.
1122
+ paystack.balances.ledger(from: Date.new(2026, 10, 1), to: Date.new(2026, 10, 31))
1123
+ ```
1124
+
1125
+ ### Plans
1126
+
1127
+ A plan is a recurring charge definition: an amount, an interval and a currency. Customers are subscribed to a plan to be charged on that schedule. Paystack does not let you delete a plan.
1128
+
1129
+ #### Create a Plan
1130
+
1131
+ ```ruby
1132
+ # amount is in the subunit of the currency (pesewas for GHS). Paystack's minimum is 2 GHS.
1133
+ # interval is one of hourly, daily, weekly, monthly, quarterly, biannually (every 6 months)
1134
+ # or annually, in lowercase. currency defaults to the integration's currency.
1135
+ response = paystack.plans.create(
1136
+ name: "Monthly tithe",
1137
+ amount: 20_000,
1138
+ interval: "monthly",
1139
+ currency: "GHS",
1140
+ description: "Standing tithe",
1141
+ send_invoices: false,
1142
+ send_sms: false,
1143
+ invoice_limit: 12
1144
+ )
1145
+
1146
+ if response.success?
1147
+ puts "Created #{response.data.plan_code}"
1148
+ else
1149
+ puts "Error: #{response.error_message}" # e.g. "Invalid interval selected"
1150
+ end
1151
+ ```
1152
+
1153
+ `send_invoices` and `send_sms` are booleans (Paystack's docs call `send_sms` a string; the API refuses anything that is not a boolean).
1154
+
1155
+ #### List and Fetch Plans
1156
+
1157
+ ```ruby
1158
+ paystack.plans.list(per_page: 20, page: 1)
1159
+
1160
+ # Filter by interval, amount (in the subunit) or a date range
1161
+ paystack.plans.list(interval: "monthly", amount: 20_000)
1162
+ paystack.plans.list(from: "2026-01-01", to: "2026-12-31")
1163
+
1164
+ # Fetch by plan code or numeric ID
1165
+ paystack.plans.fetch(id_or_code: "PLN_gx2wn530m0i3w3m")
1166
+ ```
1167
+
1168
+ #### Update a Plan
1169
+
1170
+ ```ruby
1171
+ # Send only what changes
1172
+ paystack.plans.update(id_or_code: "PLN_gx2wn530m0i3w3m", name: "Monthly tithe (renamed)")
1173
+
1174
+ # By default Paystack applies the change to the plan's existing subscriptions too. Pass
1175
+ # update_existing_subscriptions: false so that only new subscriptions use the new values.
1176
+ paystack.plans.update(id_or_code: "PLN_gx2wn530m0i3w3m", amount: 25_000, update_existing_subscriptions: false)
1177
+ ```
1178
+
1179
+ The response carries a message only, for example `"Plan updated. 1 subscription(s) affected"`.
1180
+ ### Subscriptions
1181
+
1182
+ A subscription charges a customer's saved card on a plan's schedule. The customer needs a reusable card authorization (from an earlier successful card payment), and the plan is created in your Paystack dashboard or through the Plans API.
1183
+
1184
+ #### Create a Subscription
1185
+
1186
+ ```ruby
1187
+ # customer: the customer's email or customer code. plan: the plan code (PLN_...).
1188
+ # authorization: which of the customer's saved cards to charge (AUTH_...); without it Paystack
1189
+ # uses the customer's most recent authorization. It must belong to this customer.
1190
+ # start_date: the date of the first debit, a Time, Date or ISO 8601 string. On the test API it
1191
+ # becomes next_payment_date (2027-01-15T10:00:00Z here), and the schedule follows the plan's interval.
1192
+ response = paystack.subscriptions.create(
1193
+ customer: "CUS_xnxdt6s1zg1f4nx",
1194
+ plan: "PLN_gx2wn530m0i3w3m",
1195
+ authorization: "AUTH_6tmt288t0o",
1196
+ start_date: Time.utc(2027, 1, 15, 10)
1197
+ )
1198
+
1199
+ if response.success?
1200
+ # Keep both: enable and disable need the code and the email token
1201
+ puts "#{response.data.subscription_code} #{response.data.email_token}"
1202
+ else
1203
+ puts "Error: #{response.error_message}" # e.g. "This subscription is already in place."
1204
+ end
1205
+ ```
1206
+
1207
+ A customer can hold one active subscription per plan. The SDK checks that `start_date` is ISO 8601 before sending it: on the test API, an invalid `start_date` is refused with a 400 but still leaves an active subscription with no payment date behind.
1208
+
1209
+ #### List and Fetch Subscriptions
1210
+
1211
+ ```ruby
1212
+ # Filters take numeric IDs: the plan and customer codes match nothing
1213
+ paystack.subscriptions.list(plan_id: 4298026, customer_id: 406976740, per_page: 20, page: 1)
1214
+
1215
+ # Created within a window (Time, Date or ISO 8601 string)
1216
+ paystack.subscriptions.list(from: Date.new(2026, 10, 1), to: Time.now)
1217
+
1218
+ # Fetch by subscription code or numeric ID
1219
+ paystack.subscriptions.fetch(id_or_code: "SUB_vsyqdmlzble3uii")
1220
+ ```
1221
+
1222
+ #### Enable or Disable a Subscription
1223
+
1224
+ ```ruby
1225
+ # token is the subscription's email_token, returned by create, list and fetch
1226
+ paystack.subscriptions.disable(code: "SUB_vsyqdmlzble3uii", token: "d7gofp6yppn3qz7")
1227
+ # => data.status "non-renewing"
1228
+
1229
+ paystack.subscriptions.enable(code: "SUB_vsyqdmlzble3uii", token: "d7gofp6yppn3qz7")
1230
+ ```
1231
+
1232
+ Disabling answers with `data.status` `non-renewing`, and fetch shows the same status. On the test API a disabled subscription that had never been charged could not be enabled again ("Subscription has been cancelled, and cannot be reactivated").
1233
+
1234
+ #### Let the Customer Update Their Card
1235
+
1236
+ ```ruby
1237
+ # A link to a Paystack page where the customer changes the card on the subscription
1238
+ paystack.subscriptions.generate_update_link(code: "SUB_vsyqdmlzble3uii").data.link
1239
+
1240
+ # Or have Paystack email the customer that link
1241
+ paystack.subscriptions.send_update_link(code: "SUB_vsyqdmlzble3uii")
1242
+ ```
1243
+
1244
+ Both take the subscription code; the numeric ID is not found.
1245
+ ### Dedicated Virtual Accounts
1246
+
1247
+ A dedicated virtual account is a bank account number Paystack issues to one of your customers, so they can pay you by bank transfer. Paystack's docs offer it to Nigerian and Ghanaian businesses (`NGN` and `GHS`), and it has to be enabled for your business first: until it is, every call returns an unsuccessful response with status 403 and the message "Dedicated NUBAN is not available for your business".
1248
+
1249
+ #### Create or Assign an Account
1250
+
1251
+ ```ruby
1252
+ # Which banks can issue an account for your integration (e.g. "wema-bank", "titan-paystack")
1253
+ paystack.dedicated_virtual_accounts.fetch_bank_providers
1254
+
1255
+ # For a customer you already created (their code or ID)
1256
+ response = paystack.dedicated_virtual_accounts.create(customer: "CUS_xnxdt6s1zg1f4nx", preferred_bank: "wema-bank")
1257
+
1258
+ if response.success?
1259
+ puts "#{response.data.account_number} at #{response.data.bank.name}"
1260
+ else
1261
+ puts "Error: #{response.error_message}"
1262
+ end
1263
+
1264
+ # Or create the customer, validate them and assign an account in one call. Paystack's docs
1265
+ # show the reply "Assign dedicated account in progress", with no account in it.
1266
+ paystack.dedicated_virtual_accounts.assign(
1267
+ email: "ama@example.com",
1268
+ first_name: "Ama",
1269
+ last_name: "Mensah",
1270
+ phone: "+2348100000000",
1271
+ preferred_bank: "wema-bank",
1272
+ country: "NG"
1273
+ )
1274
+ ```
1275
+
1276
+ #### List, Fetch, Requery and Deactivate
1277
+
1278
+ ```ruby
1279
+ paystack.dedicated_virtual_accounts.list(active: true, currency: "NGN", provider_slug: "wema-bank")
1280
+
1281
+ # By the account's numeric ID
1282
+ paystack.dedicated_virtual_accounts.fetch(dedicated_account_id: 1234553)
1283
+
1284
+ # Ask Paystack to check the account for transfers it has not recorded yet
1285
+ paystack.dedicated_virtual_accounts.requery(account_number: "1234567890", provider_slug: "wema-bank", date: Date.new(2026, 10, 9))
1286
+
1287
+ paystack.dedicated_virtual_accounts.deactivate(dedicated_account_id: 1234553)
1288
+ ```
1289
+
1290
+ #### Split Payments on an Account
1291
+
1292
+ ```ruby
1293
+ # Send payments into the account through a split (or a single subaccount:)
1294
+ paystack.dedicated_virtual_accounts.add_split(account_number: "0033322211", split_code: "SPL_e7jnRLtzla")
1295
+
1296
+ # And stop splitting them
1297
+ paystack.dedicated_virtual_accounts.remove_split(account_number: "0033322211")
1298
+ ```
1299
+
1300
+ `create` and `assign` also take `split_code:` or `subaccount:` to set a split when the account is made.
1301
+
1302
+ ### Payment Requests
1303
+
1304
+ A payment request is an invoice Paystack sends to a customer, who pays it online. Amounts are in the subunit (pesewas, kobo, cents).
1305
+
1306
+ #### Create a Payment Request
1307
+
1308
+ ```ruby
1309
+ # customer is a customer code (CUS_...) or ID. Give line_items and tax, each an Array of Hashes,
1310
+ # and Paystack adds them up (2 x 2000 + 300 = 4300 here); give amount instead when you have none.
1311
+ response = paystack.payment_requests.create(
1312
+ customer: "CUS_p6i0reogc8ulu1n",
1313
+ currency: "GHS",
1314
+ description: "Harvest thanksgiving pledge",
1315
+ line_items: [{name: "Pledge", amount: 2000, quantity: 2}],
1316
+ tax: [{name: "Levy", amount: 300}],
1317
+ due_date: Date.new(2026, 12, 31), # a Date, a Time or an ISO 8601 string
1318
+ metadata: {branch: "Osu"}, # a Hash; Paystack refuses a JSON string here
1319
+ draft: true # a draft is not sent; finalize it later
1320
+ )
1321
+
1322
+ if response.success?
1323
+ puts "Created #{response.data.request_code} for #{response.data.amount}"
1324
+ else
1325
+ puts "Error: #{response.error_message}"
1326
+ end
1327
+ ```
1328
+
1329
+ Paystack's docs say it emails the customer when a request is created, unless you pass `draft: true` or `send_notification: false`. Without `amount`, `line_items` or `tax`, Paystack refuses anything but a draft ("Amount was not passed or could not be extrapolated from line items.").
1330
+
1331
+ #### List, Fetch and Verify Payment Requests
1332
+
1333
+ ```ruby
1334
+ # customer_id is the numeric customer ID (Paystack ignores a CUS_ code here)
1335
+ paystack.payment_requests.list(customer_id: 407172149, status: "pending", per_page: 20)
1336
+
1337
+ # Archived requests are left out unless you pass include_archive: true. Paystack includes them
1338
+ # whenever the parameter is sent, even as false, so leave it out rather than passing false.
1339
+ paystack.payment_requests.list(include_archive: true)
1340
+
1341
+ # Fetch by numeric ID or PRQ_ code
1342
+ paystack.payment_requests.fetch(id_or_code: "PRQ_7w7dpecncnebg7e")
1343
+
1344
+ # Verify takes the PRQ_ code only, and only once the request is no longer a draft
1345
+ paystack.payment_requests.verify(code: "PRQ_7w7dpecncnebg7e")
1346
+
1347
+ # Pending, successful and total amounts per currency
1348
+ paystack.payment_requests.totals
1349
+ ```
1350
+
1351
+ #### Update, Finalize, Notify and Archive
1352
+
1353
+ ```ruby
1354
+ # Send only what changes; new line_items and tax recompute the amount
1355
+ paystack.payment_requests.update(id_or_code: "PRQ_7w7dpecncnebg7e", due_date: "2027-01-31")
1356
+
1357
+ # Finalize a draft. Paystack emails the customer unless send_notification is false.
1358
+ paystack.payment_requests.finalize(id_or_code: "PRQ_7w7dpecncnebg7e", send_notification: false)
1359
+
1360
+ # Email the customer a reminder
1361
+ paystack.payment_requests.notify(code: "PRQ_7w7dpecncnebg7e")
1362
+
1363
+ # Archive: it no longer shows up in list or verify
1364
+ paystack.payment_requests.archive(id_or_code: "PRQ_7w7dpecncnebg7e")
1365
+ ```
897
1366
 
898
1367
  ### Response Handling
899
1368
 
@@ -8,6 +8,15 @@ require_relative "resources/banks"
8
8
  require_relative "resources/charges"
9
9
  require_relative "resources/miscellaneous"
10
10
  require_relative "resources/refunds"
11
+ require_relative "resources/settlements"
12
+ require_relative "resources/splits"
13
+ require_relative "resources/disputes"
14
+ require_relative "resources/subaccounts"
15
+ require_relative "resources/balances"
16
+ require_relative "resources/plans"
17
+ require_relative "resources/subscriptions"
18
+ require_relative "resources/dedicated_virtual_accounts"
19
+ require_relative "resources/payment_requests"
11
20
  require_relative "utils/connection_utils"
12
21
 
13
22
  module PaystackSdk
@@ -173,6 +182,78 @@ module PaystackSdk
173
182
  @refunds ||= Resources::Refunds.new(@connection)
174
183
  end
175
184
 
185
+ # Provides access to the `Settlements` resource.
186
+ #
187
+ # @return [PaystackSdk::Resources::Settlements] An instance of the
188
+ # `Settlements` resource.
189
+ def settlements
190
+ @settlements ||= Resources::Settlements.new(@connection)
191
+ end
192
+
193
+ # Provides access to the `Splits` resource.
194
+ #
195
+ # @return [PaystackSdk::Resources::Splits] An instance of the
196
+ # `Splits` resource.
197
+ def splits
198
+ @splits ||= Resources::Splits.new(@connection)
199
+ end
200
+
201
+ # Provides access to the `Disputes` resource.
202
+ #
203
+ # @return [PaystackSdk::Resources::Disputes] An instance of the
204
+ # `Disputes` resource.
205
+ def disputes
206
+ @disputes ||= Resources::Disputes.new(@connection)
207
+ end
208
+
209
+ # Provides access to the `Subaccounts` resource.
210
+ #
211
+ # @return [PaystackSdk::Resources::Subaccounts] An instance of the
212
+ # `Subaccounts` resource.
213
+ def subaccounts
214
+ @subaccounts ||= Resources::Subaccounts.new(@connection)
215
+ end
216
+
217
+ # Provides access to the `Balances` resource.
218
+ #
219
+ # @return [PaystackSdk::Resources::Balances] An instance of the
220
+ # `Balances` resource.
221
+ def balances
222
+ @balances ||= Resources::Balances.new(@connection)
223
+ end
224
+
225
+ # Provides access to the `Plans` resource.
226
+ #
227
+ # @return [PaystackSdk::Resources::Plans] An instance of the
228
+ # `Plans` resource.
229
+ def plans
230
+ @plans ||= Resources::Plans.new(@connection)
231
+ end
232
+
233
+ # Provides access to the `Subscriptions` resource.
234
+ #
235
+ # @return [PaystackSdk::Resources::Subscriptions] An instance of the
236
+ # `Subscriptions` resource.
237
+ def subscriptions
238
+ @subscriptions ||= Resources::Subscriptions.new(@connection)
239
+ end
240
+
241
+ # Provides access to the `DedicatedVirtualAccounts` resource.
242
+ #
243
+ # @return [PaystackSdk::Resources::DedicatedVirtualAccounts] An instance of the
244
+ # `DedicatedVirtualAccounts` resource.
245
+ def dedicated_virtual_accounts
246
+ @dedicated_virtual_accounts ||= Resources::DedicatedVirtualAccounts.new(@connection)
247
+ end
248
+
249
+ # Provides access to the `PaymentRequests` resource.
250
+ #
251
+ # @return [PaystackSdk::Resources::PaymentRequests] An instance of the
252
+ # `PaymentRequests` resource.
253
+ def payment_requests
254
+ @payment_requests ||= Resources::PaymentRequests.new(@connection)
255
+ end
256
+
176
257
  private
177
258
 
178
259
  # The secret key a connection sends, read from its Authorization header.
@@ -0,0 +1,56 @@
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 Balance`. Hand-written extras go in lib/paystack_sdk/resources/extensions/balances.rb.
5
+ # scaffold-digest: 65e146fbcd39c274107a8234988e1b3b51e3c58a5e74d7c4b0bf9c4f084a9b83
6
+
7
+ require_relative "base"
8
+
9
+ module PaystackSdk
10
+ module Resources
11
+ # Balance operations.
12
+ class Balances < PaystackSdk::Resources::Base
13
+ # Ruby keyword => the parameter name Paystack documents.
14
+ WIRE_NAMES = {per_page: "perPage"}.freeze
15
+
16
+ # Fetch Balance.
17
+ #
18
+ # Fetch the available balance on your integration
19
+ #
20
+ # @return [PaystackSdk::Response] The response from the Paystack API.
21
+ # @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
22
+ # @see https://paystack.com/docs/api/transfer-control/#balance
23
+ def fetch
24
+ handle_response(@connection.get("/balance"))
25
+ end
26
+
27
+ # Balance Ledger.
28
+ #
29
+ # Fetch all pay-ins and pay-outs that occured on your integration
30
+ #
31
+ # @param per_page [Integer] Number of records to fetch per page
32
+ # @param page [Integer] The section to retrieve
33
+ # @param from [String] The start date
34
+ # @param to [String] The end date
35
+ # @return [PaystackSdk::Response] The response from the Paystack API.
36
+ # @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
37
+ # @see https://paystack.com/docs/api/transfer-control/#balance-ledger
38
+ def ledger(per_page: nil, page: nil, from: nil, to: nil)
39
+ validate_positive_integer!(value: per_page, name: "perPage")
40
+ validate_positive_integer!(value: page, name: "page")
41
+
42
+ wire_query = to_wire(
43
+ {
44
+ per_page:,
45
+ page:,
46
+ from: format_datetime(from, name: "from"),
47
+ to: format_datetime(to, name: "to")
48
+ },
49
+ WIRE_NAMES
50
+ )
51
+
52
+ handle_response(@connection.get("/balance/ledger", wire_query))
53
+ end
54
+ end
55
+ end
56
+ end