Changelog
[Unreleased]
[0.5.0] - 2026-10-10
Upgrading from 0.4
Two behaviours changed in small ways. Check them if you use these calls:
transactions.initiatesendsmetadatastringified, as Paystack documents it, so numbers in it keep their type when you read them back ({count: 42}stays42; it used to come back as"42").- Filters that take a numeric ID (
customer_id:,plan_id:,transaction_id:andauthorization_id:) raiseInvalidValueErrorfor anything but a numeric ID. Paystack used to answer a code such asCUS_abc123there with an empty list and no error, so if you passed one you were silently getting nothing.
If you are on 0.4.0, take 0.4.1 first if you only need the security fix.
Added
Response#dig(*keys)reads a nested value and returnsnilas soon as a key is missing, for fields Paystack only sometimes sends (response.dig(:authorization, :authorization_code)). Dot access still raisesNoMethodErrorfor a key that is not there, which catches typos.- The
paystack-sdk-overviewskill now has a "Topic skills" table that routes an agent to the right skill for the task, and the skills link to each other with[[name]]. A spec fails if a shipped skill is not reachable from the overview. client.webhook_events(Paystack's Webhook Events API:list,lookup,fetch,resend,resend_matching), the log of the webhooks Paystack sent you with their delivery status, the payload and what your endpoint answered. It is documented by Paystack but not in its OpenAPI spec, so it is hand-written and recorded inspec/fixtures/docs_only_operations.yml;bin/paystack-spec auditand the contract checker read that file.list,lookupandfetchwere checked against the test API;resendandresend_matchingwere not (they deliver webhooks to your endpoint), andresend_matchingrequirespreview:.Webhook::EVENTSalso listsrefund.needs-attention(named in Paystack's Refunds guide) andpaymentrequest.draft,product.create,product.updateandproduct.delete, which Paystack was seen sending but its Webhooks page does not list.- Skills
paystack-sdk-charge-statuses(what each chargestatusmeans, what to show the payer, whichcharges.*call comes next, and whysuccess?is not "the charge worked"; the flows were reproduced on the test API with Paystack's test cards) andpaystack-sdk-mobile-money(Ghana providers and codes, the phone number form, the 180-second approval window, how the result arrives, and what test mode does not prove). Specs run what they say. - Skill
paystack-sdk-webhooks: receiving Paystack webhooks in Rails or Rack withPaystackSdk::Webhook(signature over the raw body,verify!vsvalid_signature?vsconstruct_eventand their errors, CSRF, the optional IP allow-list, answering 200 fast, the documented retry policy, duplicate and out-of-order deliveries, verifying by reference before granting value, the documented events, and testing with a signed request). Its Ruby is run byspec/skills_webhooks_spec.rb; payload fields beyond Paystack's one documented sample are marked unverified. - Skill
paystack-sdk-refunds: full and partial refunds, the pending-until-processed lifecycle, what to record, refund webhooks, and what to do after a timeout. Includes what the test API showed (a GHS 1.00 minimum per refund, an omittedamountasks for the whole original amount and fails after a partial refund, a refunded transaction verifies asreversal-pending) and marks the rest unverified. - Skill
paystack-sdk-money-safety: the checklist for any code that moves or records money (integer minor-unit amounts, verifying by reference withpaid?(amount:, currency:)and the customer email before granting value, never resending a write after a timeout, reference discipline, storing only the encrypted authorization code, keeping keys out of logs,sandbox_onlylimits, webhook and reconciliation rules) with a pre-merge table. Its claims are run byspec/skills_money_safety_spec.rbor were observed on the Paystack test API. - Skill
paystack-sdk-testing: keys for test, staging and CI (sandbox_only,live?and what they do not check), a copyable WebMock helper with success, 400, 401, 429, 500 and timeout stubs (each run through the SDK in the gem's specs), an opt-in test-API suite pattern, Paystack's published test cards and the MTN test number, testing a webhook endpoint withWebhook.sign, and what test mode cannot prove. - Skill
paystack-sdk-saved-card-renewals: charging a returning customer's saved card on your own schedule withtransactions.charge_authorization. Covers saving onlyreusableauthorizations keyed to their email, the responses observed on the Paystack test API (invalid, inactive, other-email and mobile money codes, duplicate references), lookup-first renewal references and the never-retry-after-timeout rule, partial debit (refused for GHS on the test API), the reported confirmation race, mobile money having no reusable token, and dunning advice. Its helper code is run byspec/skills_saved_card_renewals_spec.rb. - Skill
paystack-sdk-payments: the hosted checkout flow withclient.transactions(initiatekeywords, redirecting toauthorization_url, why the callback proves nothing,verifyandpaid?, what each verify status means, idempotent confirmation from the callback and the webhook as labelled design advice, which fields to store,fetchandlist). Its claims are run byspec/skills_payments_spec.rbor were observed on Paystack's test API; the checkout page itself could not be driven and is marked unverified. - AI skills for Claude Code, shipped in the gem and installed with
paystack_sdk skills install(any Ruby project) orrails generate paystack_sdk:skills(Rails) into.claude/skills/. The first skill ispaystack-sdk-overview. Installing is idempotent, writes onlypaystack-sdk-<topic>folders marked with.paystack_sdk.json, never overwrites a folder it did not install (--forceto replace), removes only its own stale folders, stamps eachSKILL.mdwith the gem version, and supports--dir,--globaland--dry-run. Alsoskills list,skills pathandskills uninstall. - The
paystack_sdkexecutable (exe/paystack_sdk); it handles the skills andversionand does not load Faraday.
Fixed
transactions.initiatenow sendsmetadatastringified, ascharge_authorizationalready did and as Paystack documents it. Paystack accepts an object too, but stores it differently: a nested number comes back as a string ({count: 42}as{"count" => "42"}), whereas a stringified object keeps it (42). Checked against the test API. If you read numbers back out ofmetadata, they are now numbers.- Filters that take a numeric ID (
customer_id:ontransactions.list,transactions.export,subscriptions.listandpayment_requests.list,plan_id:onsubscriptions.list,transaction_id:onrefunds.list,authorization_id:oncustomers.direct_debit_activation_charge) now refuse anything but a numeric ID (an Integer, or a string of digits) withInvalidValueError. Paystack answers a code such asCUS_abc123there with an empty list and no error, which looks like "no results". If you passed a code to one of these, you were silently getting nothing. - Passing a String date or date-time to a
from:orto:filter (transactions.list(from: "2026-10-01"), and every other date filter) raisedNoMethodError: undefined method 'iso8601' for class Timein a plain Ruby process, because the gem never loaded Ruby'stimelibrary. It only worked where something else had loaded it, such as Rails or RSpec. The gem now requires it. A Date or Time object was never affected.
[0.4.1] - 2026-10-10
Security
- The secret key no longer appears when a
Client, a resource (client.transactions...) or the Faraday connection is inspected. Ruby's defaultinspectandppprinted every instance variable, and the connection holds the key in itsAuthorizationheader, soputs client,Rails.logger.info(client), an error page, or an error tracker that serialises locals wrote the key out.Client#inspectandResources::Base#inspectnow return only the class name, and the connection built by the SDK (and itsheaders) inspect without the key. The key is still sent. Errors raised by the SDK, their causes andResponsenever contained the key (checked). Affects every version before 0.4.1 (0.0.x to 0.4.0): if you may have logged one of these objects, rotate the key. A connection you pass in yourself (Client.new(connection)) is yours, and its owninspectis unchanged.
[0.4.0] - 2026-10-09
Added
- The SDK now covers every operation in Paystack's OpenAPI spec (163 of 163). Resources that could not be exercised against the Paystack test API (Terminals, Dedicated Virtual Accounts, and the state-changing calls on Apple Pay, Integrations, Virtual Terminals and Direct Debits) follow the spec and docs, and their README sections and method docs say so.
Pages(client.pages), generated from Paystack's OpenAPI spec:create,list,fetch(id_or_slug:),update(id_or_slug:),check_slug_availability(slug:)andadd_products(id:, products:). Checked against the Paystack test API,fetchandupdatetake the numeric ID or the slug,updateneeds nonameordescription(the docs mark them required), andadd_productsreadsproducts(the docs sayproduct) and takes the numeric ID only.Products(client.products), generated from Paystack's OpenAPI spec:create,list,fetch(id:),update(id:, ...)anddelete(id:).delete(DELETE /product/{id}) is in the spec but on no docs page; the test API deletes the product. Checked against the test API,createdoes not requiredescription(the spec and docs say it does;name,priceandcurrencyare required) and takesmetadataas a Hash (the spec says a JSON string, which Paystack stores as an object of its characters). Products are addressed by numeric ID.Orders(client.orders):create,list,fetch(id:),fetch_product_orders(id:)(the product's ID) andvalidate(code:)(a GET that takes the order code), generated from Paystack's OpenAPI spec.createfollows the spec (email,first_name,last_name,phone,currency,items,shipping): the test API rejects the shape on Paystack's docs page (customer,line_items) with "Customer email is required".itemsare{item: <product ID>, type: "product", quantity:, amount:}andshippingneedsstreet_line,city,state,countryandshipping_fee.fetch(id:)returned a generic API error for a just-created order in the test API.Storefronts(client.storefronts), generated from Paystack's OpenAPI spec:create,list,fetch(id:),update(id:),delete(id:),verify_slug(slug:),fetch_orders(id:),add_products(id:, products:),list_products(id:),publish(id:)andduplicate(id:). Checked against the Paystack test API,createrequiresslugandcurrency(the docs mark slug optional and omit currency),updatetakesslug,add_productstakes numeric product IDs, andpublishcopies the storefront and its products to the live integration even with a test key.bin/scaffold_names.ymlentries can carry anote:, rendered as a@notein the method's docs.storefronts.publishuses it to warn that publishing creates a live copy even with a test key.BulkCharges(client.bulk_charges), generated from Paystack's OpenAPI spec:initiate(charges:),list_batches,fetch_batch(id_or_code:),fetch_charges(id_or_code:),pause_batch(batch_code:)andresume_batch(batch_code:).initiatesendscharges(an Array of Hashes withauthorization,amountandreference) as the JSON array body. Pause and resume are GET requests, as the docs and the test API have them.fromandto, which the docs list, are left out: the test API ignores them.bin/paystack-scaffoldtakes abody:keyword inbin/scaffold_names.ymlfor an operation whose body is a JSON array (defaultitems), and names that keyword, not an empty string, in the missing-parameter error.ApplePay(client.apple_pay), generated from Paystack's OpenAPI spec:list_domains,register_domain(domain_name:)andunregister_domain(domain_name:). Onlylist_domainswas checked against the Paystack test API (it answers withoutuse_cursor, which the docs mark required). Registering and unregistering change account-wide Apple Pay configuration that may reach live mode with a test key, so they were not called and are unverified; their method docs carry a@notesaying so.bin/paystack-scaffoldcan name a singular resource (SINGULAR_TAGS), used so the tag "Apple Pay" givesApplePay, notApplePays.Integrations(client.integrations), generated from Paystack's OpenAPI spec:fetch_payment_session_timeoutandupdate_payment_session_timeout(timeout:)(seconds). Only the fetch was checked against the Paystack test API. The update changes an account-wide setting that may be shared with live mode, so it was not called and is unverified; its docs carry a warning.VirtualTerminals(client.virtual_terminals), generated from Paystack's OpenAPI spec:create,list,fetch(code:),update(code:, name:),deactivate(code:),assign_destination(code:, destinations:),unassign_destination(code:, targets:),add_split_code(code:, split_code:)andremove_split_code(code:, split_code:)(a DELETE with a JSON body).listalso takesstatus(activeorinactive), which the docs list and the test API validates but the spec omits. Checked against the Paystack test API:list,fetch, validation errors and unknown-code errors only. Nothing was created, and assign and unassign destination were not called (a destination is a WhatsApp number), so those,createwith a valid body and the split code operations on a real terminal are unverified.DirectDebits(client.direct_debits), generated from Paystack's OpenAPI spec:list_mandate_authorizations(cursor:, status:, per_page:)andtrigger_activation_charge(customer_ids:). These are the integration-wide/directdebit/...endpoints; the per-customer ones stay onclient.customers.list_mandate_authorizationswas checked against the test API (all parameters optional,statuslimited to pending, active and revoked).trigger_activation_chargecharges customers' bank accounts and follows the docs and spec without a live check.Terminals(client.terminals), generated from Paystack's OpenAPI spec:send_event(terminal_id:),fetch_event_status(terminal_id:, event_id:),fetch_status(terminal_id:),list,fetch(terminal_id:),update(terminal_id:),commission(serial_number:)anddecommission(serial_number:). Unverified against the Paystack API: the test account has no Terminal access (every GET returns 403 "not yet available in your country"), so the methods follow the spec, and the device operations were never called.
[0.3.0] - 2026-10-09
Added
client.splitsfor Paystack's Transaction Splits API:create,list,fetch,update,add_subaccount(also updates an existing subaccount's share) andremove_subaccount, generated from the OpenAPI spec.listsendsperPage(the API ignores the spec'sper_page);fetchtakes a split code or numeric ID;add_subaccountrequiressubaccountandshare, andremove_subaccountrequiressubaccount, as the docs and test API do.disputes(new, generated from Paystack's OpenAPI spec):list,fetch,list_transaction,update,add_evidence,fetch_upload_url,resolveandexport.fetch_upload_urltakesupload_filename, which Paystack documents and the test API honours although the OpenAPI spec omits it.Subaccounts(client.subaccounts):create,list,fetch(id_or_code:)andupdate(id_or_code:, ...), generated from Paystack's OpenAPI spec. The bank is sent asbank_code, as Paystack's docs name it (the spec sayssettlement_bank; the test API reads both and prefersbank_code).list(active:)takes1or0: Paystack readstrueas inactive.Plans(client.plans):create,list,fetch(id_or_code:)andupdate(id_or_code:, ...), generated from Paystack's OpenAPI spec.intervalacceptshourlyandquarterlyas well as the spec's five values (the test API accepts all seven; the spec omits two).updatetakesupdate_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.settlements(new):list(per_page:, page:, from:, to:)forGET /settlementandtransactions(id:)forGET /settlement/{id}/transactions.fromandtoare in Paystack's docs but not the spec; the test API validates them.Balances(client.balances):fetchandledger(per_page:, page:, from:, to:), generated from Paystack's OpenAPI spec and read-only (GET).fetchreturns 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.Subscriptions(client.subscriptions):create,list,fetch(id_or_code:),enable(code:, token:),disable(code:, token:),generate_update_link(code:)andsend_update_link(code:), generated from Paystack's OpenAPI spec.listfilters byplan_id:andcustomer_id:(sent asplanandcustomer): the test API matches numeric IDs only.start_dateis checked as ISO 8601 before sending, since the test API refuses an invalid one but still creates the subscription.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_splitandfetch_bank_providers.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)andarchive(id_or_code:). Checked against the Paystack test API,createdoes not requireamountwhenline_itemsortaxare given (the spec says it is required), takesmetadataas a Hash andredirect_url(both docs-only), and accepts a date asdue_date;listfilters by the numericcustomer_id:and takesinclude_archive:.bin/paystack-scaffoldappliesrequired: truefrom body-field entries inspec/support/paystack_contract_exceptions.yml, as it already did for query parameters.bin/paystack-scaffoldappliesenum:from body-field entries inspec/support/paystack_contract_exceptions.yml, as it already did for query parameters.
Fixed
bin/paystack-scaffoldnow adds a resource'sClientaccessor aboveClient'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@seelink ofcustomers.direct_debit_activation_chargeto#directdebit-activation-charge.
[0.2.0] - 2026-10-09
Breaking
Transactionsis now generated from Paystack's OpenAPI spec and takes keyword arguments instead of payload hashes. Method names are unchanged.initiate(email:, amount:, ...),charge_authorization(email:, amount:, authorization_code:, ...)andpartial_debit(email:, amount:, authorization_code:, currency:, ...)no longer accept a hash; useinitiate(**params)to migrate.fetch(id:)replacesfetch(transaction_id);timeline(id:)replacestimeline(id_or_reference).list,totalsandexporttake named filters (from,to,status,customer_id,settlement, ...) instead of**params, so a misspelt filter now raisesArgumentError.listno longer defaultsper_page: 50, page: 1; Paystack's own defaults apply.- The numeric customer filter on
listandexportiscustomer_id:(it sends Paystack'scustomer).
Chargesis now generated from Paystack's OpenAPI spec and takes keyword arguments instead of payload hashes.mobile_money(email:, amount:, mobile_money:, currency: nil, reference: nil, metadata: nil)no longer accepts a hash; usemobile_money(**params)to migrate. It no longer sendscallback_url, which neither Paystack's docs nor its spec list forPOST /charge(the API does not validate it either).submit_otp(otp:, reference:)no longer accepts a hash.
Banksis regenerated from Paystack's OpenAPI spec,Miscellaneousis new, andVerification(client.verification) is removed. All take keyword arguments.verification.resolve_account(account_number:, bank_code:)is nowbanks.resolve_account_number(account_number:, bank_code:).verification.validate_account(hash)is nowbanks.validate_account(account_name:, account_number:, account_type:, bank_code:, country_code:, document_type:, document_number: nil).verification.resolve_card_bin(bin)is nowmiscellaneous.resolve_card_bin(bin:). New:miscellaneous.list_countriesandmiscellaneous.list_states(country:).banks.listtakes named filters instead of a hash (per_page:is sent asperPage,next_cursor:asnext) and now accepts every filter Paystack documents (country,type,gateway,use_cursor, ...). Itstypeenum usesghipss(the spec'sghippsis a typo), andcurrencystill acceptsUSD.
TransferRecipientsis now generated from Paystack's OpenAPI spec and takes keyword arguments instead of payload hashes. Method names are unchanged.create(type:, name:, account_number:, bank_code:, ...)no longer accepts a hash; usecreate(**params)to migrate.typeis checked against Paystack's list (nuban,ghipss,mobile_money,basa,authorization).fetch(id_or_code:),update(id_or_code:, name:, email:)anddelete(id_or_code:)replacerecipient_code:. The value can be the recipient code or its numeric ID.updatetakesname:andemail:instead of aparams:hash;nameis optional, as the API accepts an update with onlyemail(docs saynameis required; checked against the test API).listtakesper_page:,page:,use_cursor:,next_cursor:andprevious:instead of a query hash.perPageis what Paystack's docs name the page size; the API honours it.fromandtoare in the docs but the API ignores them, so they are not offered.
Customersis now generated from Paystack's OpenAPI spec and takes keyword arguments instead of payload hashes. Method names are unchanged.create(email:, ...),set_risk_action(customer:, risk_action: nil)anddeactivate_authorization(authorization_code:)no longer accept a hash; usecreate(**params)to migrate.fetch(email_or_code:)replacesfetch(email_or_code)(the keyword is the path variable's name in Paystack's docs; it takes an email or a customer code);update(code:, first_name: nil, ...)replacesupdate(code, payload);validate(code:, ...)replacesvalidate(code, payload).validatenow requiresfirst_name,last_name,type,country,bvn,bank_codeandaccount_number, as Paystack does (the test API answers 400 without each). It previously did not requirebvnor the names.listtakes named filters (per_page,page,from,to,use_cursor,next_cursor,previous) instead of**params, and no longer defaultsper_page: 50, page: 1; Paystack's own defaults apply.metadataoncreateandupdatemust be a Hash; it is sent as a JSON object (Paystack rejects a JSON string).
Transfersis now generated from Paystack's OpenAPI spec and takes keyword arguments instead of payload hashes. Existing method names are unchanged.create(source:, amount:, recipient:, reference:, reason: nil, currency: nil)no longer accepts a hash; usecreate(**params)to migrate.referenceis now required, as Paystack's docs and spec both require it, andcurrencymust be one of NGN, ZAR, KES, GHS.fetch(id_or_code:)replacesfetch(id:), using the docs' name for the path variable (it takes a transfer ID or aTRF_code). The request is unchanged.listtakes named filters (per_page,page,from,to,recipient,status, and cursor pagination withuse_cursor,next_cursor,previous) instead of a query hash, so a misspelt filter now raisesArgumentError.per_pageis sent asperPage.
Fixed
- Values placed in URL paths (references, codes, ids, card BINs) are now escaped, and
./..are refused withPaystackSdk::InvalidValueErrorbefore any request is sent. Previously a value such as".."was resolved by the URL builder into a different endpoint (transactions.verify(reference: "..")called/transaction), and/,?or#in a value changed the path or query. Affectstransactions(verify,fetch,timeline),transfers(fetch,verify),transfer_recipients(fetch,update,delete),customers(fetch,update,validate) andmiscellaneous(resolve_card_bin). Response#[]andResponse#key?returnednil/falsefor every key on real Paystack bodies (string keys). Both now accept strings or symbols.Customers#deactivate_authorizationcalled a non-existent endpoint (customer/deactivate_authorization). It now posts to Paystack's documentedPOST /customer/authorization/deactivate.429responses now raiseRateLimitError(previously swallowed as a client error because the400..499branch matched first).retry_afteris read from Paystack'sx-ratelimit-resetheader (nil when absent) instead of the undocumentedRetry-After.
Added
Client#live?andClient.new(..., sandbox_only: true): the client refuses to be built with anything but ansk_test_key (or a pre-built connection that sends one), raisingArgumentErrorbefore any request.Response#paid?(amount: nil, currency: nil)(call succeeded,statusis"success", and the amount and currency match if given) andResponse#status?(value).spec/sandbox/: specs against Paystack's real test API, skipped unlessPAYSTACK_TEST_SECRET_KEYis set to ansk_test_key. They confirm thatcharge_authorizationcharges a saved reusable card.client.refunds, generated from Paystack's OpenAPI spec:create(transaction:, amount: nil, currency: nil, customer_note: nil, merchant_note: nil),list,fetch(id:)andretry_with_customer_details(id:, refund_account_details:).transactiontakes the transaction reference or its numeric ID; leaveamountout for a full refund.listfilters bytransaction_id:(sent astransaction, documented by Paystack, absent from the spec). Both confirmed against the test API.transfer_recipients.bulk_create(batch:)forPOST /transferrecipient/bulk.- The rest of the Charge API:
charges.create,submit_pin,submit_phone,submit_birthday,submit_addressandcheck_pending.createtakes the channel objects (bank,mobile_money,ussd,eft,qr,bank_transfer,capitec_pay) as keyword hashes, pluscurrency,split_codeandsubaccount, which Paystack documents and the test API honours although the OpenAPI spec omits them. charges.mobile_moneyaccepts thempesa_offlineandmptillproviders Paystack documents, and an M-PESA Tillaccountin place ofphone.bin/paystack-scaffoldgenerates body parameters recorded as docs-only (spec: null) inspec/support/paystack_contract_exceptions.yml, as it already did for query parameters.customerscovers the rest of Paystack's Customer API:initialize_authorization,verify_authorization,initialize_direct_debit,direct_debit_activation_chargeandfetch_mandate_authorizations, plus cursor pagination onlist(use_cursor,next_cursor,previous).bin/scaffold_names.ymlentries can be a hash,{name: ..., keywords: {path_variable: ruby_keyword}}, so a path variable takes the Ruby keyword Paystack's docs use where the spec names it differently.bin/paystack-scaffoldapplies body-field entries fromspec/support/paystack_contract_exceptions.yml(wire name, type and description), as it already did for query parameters.transfers.bulk_create,export,resend_otp,disable_otp,finalize_disable_otpandenable_otp(Paystack's Initiate Bulk Transfer, Export Transfers and Transfers Control OTP operations).transactions.exportacceptscurrency,amount,settledandpayment_page(documented by Paystack, absent from the OpenAPI spec; confirmed to filter results against the test API) andsubaccount_code. Paystack's docs also listperPageandpageon Export, but the API ignores them, so the SDK does not offer them.PaystackSdk::Webhookverifies Paystack webhook signatures (HMAC SHA512, constant-time) and parses events:valid_signature?,verify!,construct_event,sign,trusted_ip?, plus the documentedEVENTSandIP_ADDRESSES. New errors:WebhookError,InvalidSignatureError,InvalidPayloadError.Response#metaexposes the pagination metadata (total,page,pageCount,perPage) that list endpoints return.- Default request timeouts (
timeout,open_timeout) and automatic retries with backoff (max_retries,retry_interval,retry_non_idempotent) on SDK-built connections. Writes are only retried on429;GETs are also retried on network failures and 502/503/504. PaystackSdk::TimeoutErrorandPaystackSdk::ConnectionErrorwrap transport failures.- Connection options are validated and raise
ArgumentErrorwhen invalid or when combined with a pre-built connection.
Changed
- Faraday constraint relaxed to
>= 2.13, < 3; addedfaraday-retrydependency.
[0.1.0] - 2025-06-26
Changed
Error Handling Strategy
- BREAKING: Clarified error handling approach - the SDK now consistently follows a two-tier error handling strategy:
- Validation errors (missing/invalid input data) are thrown as exceptions immediately before API calls
- API response errors (business logic, not found, etc.) are returned as unsuccessful Response objects
- Updated documentation to clearly explain when exceptions are thrown vs. when to check
response.success?
Test Infrastructure
- BREAKING: Standardized all test specifications to use connection doubles instead of Faraday-specific mocks
- Updated test pattern across all resource specs to use
instance_double("PaystackSdk::Connection")for better framework independence - All tests now follow consistent mocking pattern with
PaystackSdk::Response.new()expectations
Documentation
- Enhanced README with comprehensive error handling examples
- Added validation error examples showing
MissingParamError,InvalidFormatError, andInvalidValueError - Updated Quick Start guide to demonstrate proper exception handling
- Clarified the difference between input validation (exceptions) and API response handling (Response objects)
Improved
- Better separation of concerns between input validation and API error handling
- More consistent test suite that's not tied to specific HTTP client implementation
- Clearer developer experience with predictable error handling patterns
[0.0.9] - 2025-06-26
Added
- Verification resource with support for:
- Resolving bank accounts
- Resolving card BINs
- Validating accounts (with required and optional fields)
- Registered
verificationresource in the main client for easy access - Regression tests for all Verification resource methods, including required field validation and full parameter support
Changed
- Fix the link for listing banks API
[0.0.8] - 2025-06-26
Added
- Banks resource with support for listing banks and currency validation
- Regression tests for Banks resource, including validation for allowed currencies
- Registered
banksresource in the main client for easy access
Improved
- Validation for allowed currency values in Banks resource for safer API usage
[0.0.7] - 2025-06-25
Added
- Transfer Recipients resource with full CRUD operations (create, list, fetch, update, delete)
- Transfers resource with full API support (create/initiate, list, fetch, finalize, verify)
- All resource methods now use
handle_responsefor consistent response handling - Regression tests for Transfer Recipients and Transfers resources, ensuring response wrapping and validation
Changed
- Registered
transfer_recipientsandtransfersresources in the main client for easy access - Updated specs to verify that all resource methods wrap responses using
PaystackSdk::Response
Improved
- Enhanced test coverage for new resources and response handling
- Improved code consistency by enforcing response wrapping and validation patterns across all new resources
[0.0.6] - 2025-06-10
Added
- Customers resource with full CRUD operations (create, list, fetch, update)
- Customer validation and risk action management features
- Customer authorization deactivation functionality
- Comprehensive documentation for Customers API in README
Changed
- Improved test consistency by standardizing response body format using string keys with hashrocket notation (
=>) across all test specifications - Enhanced error handling specificity in tests by using appropriate error classes (
InvalidValueError,InvalidFormatError,MissingParamError) instead of genericErrorclass
Improved
- Better test coverage and reliability with consistent response format handling
- More precise error validation ensuring proper exception types are raised for different validation failures
- Enhanced documentation with comprehensive examples for all customer operations
[0.0.5] - 2025-05-15
Added
- Connection utilities module for improved API connection handling and management
- Enhanced connection configuration and error handling capabilities
[0.0.4] - 2025-05-15
Added
- Enhanced transaction resource validations with comprehensive parameter checking
- New transaction methods:
charge_authorizationandpartial_debit - Flexible
listmethod accepting additional parameters for advanced API requests
Changed
- Updated SDK authentication to use
secret_keyinstead ofapi_keyfor consistency with Paystack documentation - Improved transaction method names for better clarity and consistency
- Enhanced README documentation with comprehensive usage examples
Improved
- Better validation error messages and handling
- More robust parameter validation across all transaction methods
[0.0.3] - 2025-05-13
Changed
- Renamed
#initialize_transactionmethod to#initiatefor better clarity and consistency with Paystack API - Updated .gitignore to exclude Gemfile.lock from version control
Fixed
- Corrected typos in README documentation regarding original API response handling
[0.0.2] - 2025-05-11
Added
- Comprehensive Response class for Paystack API response handling with dynamic attribute access
- Enhanced response object capabilities with better data access patterns
- Improved debugging support for development
Changed
- Refactored transaction specs to utilize PaystackSdk::Response for better consistency
- Removed redundant success? method in favor of centralized Response handling
- Enhanced documentation clarity on original API response handling
Improved
- Better response handling architecture across all SDK components
- More intuitive API for accessing response data and metadata
[0.0.1] - 2025-05-10
Added
- Initial release of Paystack Ruby SDK
- Basic transaction operations (initiate, verify)
- Foundational SDK architecture with modular design
- Comprehensive error handling framework
- Basic client initialization and configuration
- Initial documentation and usage examples