paystack_sdk 0.4.1 → 0.5.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 +33 -0
- data/README.md +68 -0
- data/exe/paystack_sdk +6 -0
- data/lib/generators/paystack_sdk/skills_generator.rb +27 -0
- data/lib/paystack_sdk/cli.rb +131 -0
- data/lib/paystack_sdk/client.rb +13 -0
- data/lib/paystack_sdk/request_helpers.rb +1 -0
- data/lib/paystack_sdk/resources/customers.rb +2 -1
- data/lib/paystack_sdk/resources/payment_requests.rb +2 -1
- data/lib/paystack_sdk/resources/refunds.rb +2 -1
- data/lib/paystack_sdk/resources/subscriptions.rb +3 -1
- data/lib/paystack_sdk/resources/transactions.rb +6 -4
- data/lib/paystack_sdk/resources/webhook_events.rb +174 -0
- data/lib/paystack_sdk/response.rb +25 -0
- data/lib/paystack_sdk/skills/paystack-sdk-charge-statuses/SKILL.md +85 -0
- data/lib/paystack_sdk/skills/paystack-sdk-mobile-money/SKILL.md +81 -0
- data/lib/paystack_sdk/skills/paystack-sdk-money-safety/SKILL.md +129 -0
- data/lib/paystack_sdk/skills/paystack-sdk-overview/SKILL.md +98 -0
- data/lib/paystack_sdk/skills/paystack-sdk-payments/SKILL.md +199 -0
- data/lib/paystack_sdk/skills/paystack-sdk-refunds/SKILL.md +117 -0
- data/lib/paystack_sdk/skills/paystack-sdk-saved-card-renewals/SKILL.md +154 -0
- data/lib/paystack_sdk/skills/paystack-sdk-testing/SKILL.md +227 -0
- data/lib/paystack_sdk/skills/paystack-sdk-webhooks/SKILL.md +250 -0
- data/lib/paystack_sdk/skills.rb +165 -0
- data/lib/paystack_sdk/validations.rb +17 -0
- data/lib/paystack_sdk/version.rb +1 -1
- data/lib/paystack_sdk/webhook.rb +11 -5
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02712537eff419059ff52364f7f6077b8b40fd171a2f29326d74e2475aa28482
|
|
4
|
+
data.tar.gz: cf8537555639790e816ade12badb9e0ba177c5c76f9bb2142d70d81c3dfb8540
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d833404efed23211c319b6b629c3158e6b1013538fb32648d76fe1fef445c79291b8c01c4291054b98e39cc71c0364060b2a0fcbd10b2673cc58b22a7bc5fd0
|
|
7
|
+
data.tar.gz: fc869da180ae8333a892b46e72768de8f85daa3b9b53bef1ad9a2893dc2d8fe453ffe227a4acb8e14afcda6ba750a59d24049cef99bb9630b1199d8933f8e916
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2026-10-10
|
|
6
|
+
|
|
7
|
+
### Upgrading from 0.4
|
|
8
|
+
|
|
9
|
+
Two behaviours changed in small ways. Check them if you use these calls:
|
|
10
|
+
|
|
11
|
+
- `transactions.initiate` sends `metadata` stringified, as Paystack documents it, so numbers in it keep their type when you read them back (`{count: 42}` stays `42`; it used to come back as `"42"`).
|
|
12
|
+
- Filters that take a numeric ID (`customer_id:`, `plan_id:`, `transaction_id:` and `authorization_id:`) raise `InvalidValueError` for anything but a numeric ID. Paystack used to answer a code such as `CUS_abc123` there with an empty list and no error, so if you passed one you were silently getting nothing.
|
|
13
|
+
|
|
14
|
+
If you are on 0.4.0, take 0.4.1 first if you only need the security fix.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `Response#dig(*keys)` reads a nested value and returns `nil` as soon as a key is missing, for fields Paystack only sometimes sends (`response.dig(:authorization, :authorization_code)`). Dot access still raises `NoMethodError` for a key that is not there, which catches typos.
|
|
19
|
+
- The `paystack-sdk-overview` skill 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.
|
|
20
|
+
- `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 in `spec/fixtures/docs_only_operations.yml`; `bin/paystack-spec audit` and the contract checker read that file. `list`, `lookup` and `fetch` were checked against the test API; `resend` and `resend_matching` were not (they deliver webhooks to your endpoint), and `resend_matching` requires `preview:`.
|
|
21
|
+
- `Webhook::EVENTS` also lists `refund.needs-attention` (named in Paystack's Refunds guide) and `paymentrequest.draft`, `product.create`, `product.update` and `product.delete`, which Paystack was seen sending but its Webhooks page does not list.
|
|
22
|
+
- Skills `paystack-sdk-charge-statuses` (what each charge `status` means, what to show the payer, which `charges.*` call comes next, and why `success?` is not "the charge worked"; the flows were reproduced on the test API with Paystack's test cards) and `paystack-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.
|
|
23
|
+
- Skill `paystack-sdk-webhooks`: receiving Paystack webhooks in Rails or Rack with `PaystackSdk::Webhook` (signature over the raw body, `verify!` vs `valid_signature?` vs `construct_event` and 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 by `spec/skills_webhooks_spec.rb`; payload fields beyond Paystack's one documented sample are marked unverified.
|
|
24
|
+
- 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 omitted `amount` asks for the whole original amount and fails after a partial refund, a refunded transaction verifies as `reversal-pending`) and marks the rest unverified.
|
|
25
|
+
- Skill `paystack-sdk-money-safety`: the checklist for any code that moves or records money (integer minor-unit amounts, verifying by reference with `paid?(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_only` limits, webhook and reconciliation rules) with a pre-merge table. Its claims are run by `spec/skills_money_safety_spec.rb` or were observed on the Paystack test API.
|
|
26
|
+
- 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 with `Webhook.sign`, and what test mode cannot prove.
|
|
27
|
+
- Skill `paystack-sdk-saved-card-renewals`: charging a returning customer's saved card on your own schedule with `transactions.charge_authorization`. Covers saving only `reusable` authorizations 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 by `spec/skills_saved_card_renewals_spec.rb`.
|
|
28
|
+
- Skill `paystack-sdk-payments`: the hosted checkout flow with `client.transactions` (`initiate` keywords, redirecting to `authorization_url`, why the callback proves nothing, `verify` and `paid?`, what each verify status means, idempotent confirmation from the callback and the webhook as labelled design advice, which fields to store, `fetch` and `list`). Its claims are run by `spec/skills_payments_spec.rb` or were observed on Paystack's test API; the checkout page itself could not be driven and is marked unverified.
|
|
29
|
+
- AI skills for Claude Code, shipped in the gem and installed with `paystack_sdk skills install` (any Ruby project) or `rails generate paystack_sdk:skills` (Rails) into `.claude/skills/`. The first skill is `paystack-sdk-overview`. Installing is idempotent, writes only `paystack-sdk-<topic>` folders marked with `.paystack_sdk.json`, never overwrites a folder it did not install (`--force` to replace), removes only its own stale folders, stamps each `SKILL.md` with the gem version, and supports `--dir`, `--global` and `--dry-run`. Also `skills list`, `skills path` and `skills uninstall`.
|
|
30
|
+
- The `paystack_sdk` executable (`exe/paystack_sdk`); it handles the skills and `version` and does not load Faraday.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- `transactions.initiate` now sends `metadata` stringified, as `charge_authorization` already 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 of `metadata`, they are now numbers.
|
|
35
|
+
- Filters that take a numeric ID (`customer_id:` on `transactions.list`, `transactions.export`, `subscriptions.list` and `payment_requests.list`, `plan_id:` on `subscriptions.list`, `transaction_id:` on `refunds.list`, `authorization_id:` on `customers.direct_debit_activation_charge`) now refuse anything but a numeric ID (an Integer, or a string of digits) with `InvalidValueError`. Paystack answers a code such as `CUS_abc123` there 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.
|
|
36
|
+
- Passing a String date or date-time to a `from:` or `to:` filter (`transactions.list(from: "2026-10-01")`, and every other date filter) raised `NoMethodError: undefined method 'iso8601' for class Time` in a plain Ruby process, because the gem never loaded Ruby's `time` library. 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.
|
|
37
|
+
|
|
5
38
|
## [0.4.1] - 2026-10-10
|
|
6
39
|
|
|
7
40
|
### Security
|
data/README.md
CHANGED
|
@@ -4,6 +4,7 @@ The `paystack_sdk` gem provides a simple and intuitive interface for interacting
|
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
|
+
- [AI Skills](#ai-skills)
|
|
7
8
|
- [Installation](#installation)
|
|
8
9
|
- [Quick Start](#quick-start)
|
|
9
10
|
- [Usage](#usage)
|
|
@@ -100,8 +101,10 @@ The `paystack_sdk` gem provides a simple and intuitive interface for interacting
|
|
|
100
101
|
- [List and Fetch Terminals](#list-and-fetch-terminals)
|
|
101
102
|
- [Send an Event and Check It](#send-an-event-and-check-it)
|
|
102
103
|
- [Update, Commission and Decommission](#update-commission-and-decommission)
|
|
104
|
+
- [Webhook Events](#webhook-events)
|
|
103
105
|
- [Response Handling](#response-handling)
|
|
104
106
|
- [Working with Response Objects](#working-with-response-objects)
|
|
107
|
+
- [Optional Fields: `dig`](#optional-fields-dig)
|
|
105
108
|
- [Accessing the Original Response](#accessing-the-original-response)
|
|
106
109
|
- [Error Handling](#error-handling)
|
|
107
110
|
- [Advanced Usage](#advanced-usage)
|
|
@@ -115,6 +118,25 @@ The `paystack_sdk` gem provides a simple and intuitive interface for interacting
|
|
|
115
118
|
- [License](#license)
|
|
116
119
|
- [Code of Conduct](#code-of-conduct)
|
|
117
120
|
|
|
121
|
+
## AI Skills
|
|
122
|
+
|
|
123
|
+
The gem ships skills for AI coding agents (Claude Code `SKILL.md` folders) that teach an agent how to use `paystack_sdk` correctly: the conventions, what raises and what returns, and the checks that keep money safe. They are installed from the gem you have, so they match its version.
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
# Any Ruby project: installs into ./.claude/skills
|
|
127
|
+
bundle exec paystack_sdk skills install
|
|
128
|
+
|
|
129
|
+
# A Rails app
|
|
130
|
+
bin/rails generate paystack_sdk:skills
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- **Safe to repeat.** A second run reports `identical`. After a gem upgrade, run it again: it updates only the folders it installed.
|
|
134
|
+
- **Never overwrites your skills.** Every folder it writes is named `paystack-sdk-<topic>` and carries a `.paystack_sdk.json` marker. A folder with the same name that it did not install is left alone and reported as a `conflict` (`--force` replaces it). It removes only its own folders that a newer gem no longer ships.
|
|
135
|
+
- **Version-stamped.** Each installed `SKILL.md` records the gem version in its frontmatter (`metadata.gem_version`).
|
|
136
|
+
- Options: `--dir DIR` (another directory), `--global` (`~/.claude/skills`), `--dry-run` (change nothing; `rails generate ... --pretend` in Rails). Other commands: `paystack_sdk skills list`, `skills path` (where the skills live inside the gem, for agents that read files directly), `skills uninstall`, `paystack_sdk version`.
|
|
137
|
+
|
|
138
|
+
Run `paystack_sdk skills list` for the skills in your installed version and when each one applies. Each is a `paystack-sdk-<topic>` folder; an agent picks them from their descriptions.
|
|
139
|
+
|
|
118
140
|
## Installation
|
|
119
141
|
|
|
120
142
|
Add this line to your application's Gemfile:
|
|
@@ -1763,6 +1785,37 @@ paystack.terminals.commission(serial_number: "1111150412230003899")
|
|
|
1763
1785
|
paystack.terminals.decommission(serial_number: "1111150412230003899")
|
|
1764
1786
|
```
|
|
1765
1787
|
|
|
1788
|
+
### Webhook Events
|
|
1789
|
+
|
|
1790
|
+
The Webhook Events API is the log of every webhook Paystack tried to send to your integration's webhook URL: whether it was delivered, what Paystack sent, and what your endpoint answered. It is documented by Paystack but is not in its OpenAPI spec, so `client.webhook_events` is written from the docs (the entry in `spec/fixtures/docs_only_operations.yml` records what was checked). Use it to find out why a webhook never arrived, to see the exact payload, and to replay events your endpoint missed.
|
|
1791
|
+
|
|
1792
|
+
```ruby
|
|
1793
|
+
# Events Paystack could not deliver (status is Delivered, Pending or Failed)
|
|
1794
|
+
response = paystack.webhook_events.list(status: "Failed", limit: 20)
|
|
1795
|
+
|
|
1796
|
+
response.each do |event|
|
|
1797
|
+
puts "#{event.event_name} #{event.status} (#{event.status_detail}) answered #{event.response_code}"
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1800
|
+
# The next page: pass meta.next back as next_cursor (not together with previous)
|
|
1801
|
+
paystack.webhook_events.list(next_cursor: response.original_response.dig("meta", "next"))
|
|
1802
|
+
|
|
1803
|
+
# Filters: category, event_type, status, category_row_id, from, to
|
|
1804
|
+
paystack.webhook_events.list(event_type: "charge.success", from: Date.new(2025, 6, 1))
|
|
1805
|
+
|
|
1806
|
+
# One event in full: the payload Paystack sent, and your endpoint's answer
|
|
1807
|
+
event = paystack.webhook_events.fetch(id: "6ac94b47fdad55440ef2066a")
|
|
1808
|
+
event.event_payload[:data] # brackets: a key named data is not reachable with dot access
|
|
1809
|
+
event.merchant_response_body
|
|
1810
|
+
|
|
1811
|
+
# Find an event by its own id, or by the id of the resource it is about (a transaction id)
|
|
1812
|
+
paystack.webhook_events.lookup(id: 6_641_907_106)
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
Checked against Paystack's test API: `list`, `lookup` and `fetch` (read-only). A page holds 50 events by default (the docs say 20) and `limit` is capped at 50. Every event has a `_id`; the payloads Paystack records are `{"event": ..., "data": {...}}` with no event id inside, and `data.id` was present in every payload seen.
|
|
1816
|
+
|
|
1817
|
+
**Resending is unverified and makes Paystack deliver webhooks to your endpoint.** `resend(ids:)` and `resend_matching(preview:, filters:)` were never called: the docs say resending is not idempotent, so your endpoint must dedupe. `resend_matching` makes you choose `preview:` (the docs make it optional) because with no filters it resends everything. Call it with `preview: true` first.
|
|
1818
|
+
|
|
1766
1819
|
### Response Handling
|
|
1767
1820
|
|
|
1768
1821
|
#### Working with Response Objects
|
|
@@ -1820,6 +1873,21 @@ response.meta.perPage # => 20
|
|
|
1820
1873
|
|
|
1821
1874
|
`response.meta` is `nil` when the response has no `meta`.
|
|
1822
1875
|
|
|
1876
|
+
#### Optional Fields: `dig`
|
|
1877
|
+
|
|
1878
|
+
Dot access raises `NoMethodError` for a key that is not in the response. That is what you want for a typo, but not for a field Paystack only sometimes sends (`paid_at` on an unpaid transaction, `authorization` details on a payment without a card). Read those with `dig`: it returns `nil` as soon as a key along the path is missing, accepts strings or symbols (and an Integer to index an Array), and returns the value as it is, like `Hash#dig`.
|
|
1879
|
+
|
|
1880
|
+
```ruby
|
|
1881
|
+
response = paystack.transactions.verify(reference: "order-1042")
|
|
1882
|
+
|
|
1883
|
+
response.dig(:paid_at) # => "2025-06-01T10:00:00.000Z" or nil
|
|
1884
|
+
response.dig(:authorization, :authorization_code) # => "AUTH_xxxx" or nil
|
|
1885
|
+
response.dig(:customer, :email) # => "ama@example.com"
|
|
1886
|
+
response.dig(:log, :history, 0, :message) # an Integer indexes an Array
|
|
1887
|
+
```
|
|
1888
|
+
|
|
1889
|
+
`response[:key]` also gives `nil` for a missing key, but wraps what it finds in a `Response`; `dig` gives you the plain value.
|
|
1890
|
+
|
|
1823
1891
|
#### Accessing the Original Response
|
|
1824
1892
|
|
|
1825
1893
|
Sometimes you may need access to the original API response:
|
data/exe/paystack_sdk
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "paystack_sdk/skills"
|
|
5
|
+
|
|
6
|
+
module PaystackSdk
|
|
7
|
+
module Generators
|
|
8
|
+
# `rails generate paystack_sdk:skills`: installs the gem's AI skills into the app's `.claude/skills/`.
|
|
9
|
+
# It never overwrites a skill folder it did not install, and after a gem upgrade it updates only its
|
|
10
|
+
# own folders. `--pretend` shows what would happen.
|
|
11
|
+
class SkillsGenerator < Rails::Generators::Base
|
|
12
|
+
desc "Install the paystack_sdk AI skills into .claude/skills/ (Claude Code)"
|
|
13
|
+
|
|
14
|
+
class_option :force, type: :boolean, default: false, desc: "Replace a same-named folder paystack_sdk did not install"
|
|
15
|
+
|
|
16
|
+
COLORS = {"create" => :green, "update" => :yellow, "force" => :yellow, "remove" => :red, "conflict" => :red}.freeze
|
|
17
|
+
|
|
18
|
+
def install_skills
|
|
19
|
+
dir = File.join(destination_root, ".claude", "skills")
|
|
20
|
+
results = PaystackSdk::Skills.install(dir, dry_run: options[:pretend], force: options[:force])
|
|
21
|
+
|
|
22
|
+
results.each { |r| say_status(r.status, [r.name, r.note].compact.join(": "), COLORS.fetch(r.status, :blue)) }
|
|
23
|
+
say_status("note", "Some skill folders were left alone; run with --force to replace them.", :red) if results.any? { |r| r.status == "conflict" }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "skills"
|
|
5
|
+
require_relative "version"
|
|
6
|
+
|
|
7
|
+
module PaystackSdk
|
|
8
|
+
# The `paystack_sdk` command. It only handles the AI skills; it does not load Faraday or call Paystack.
|
|
9
|
+
#
|
|
10
|
+
# paystack_sdk skills install [--dir DIR | --global] [--dry-run] [--force]
|
|
11
|
+
# paystack_sdk skills list
|
|
12
|
+
# paystack_sdk skills path
|
|
13
|
+
# paystack_sdk skills uninstall [--dir DIR | --global] [--dry-run]
|
|
14
|
+
# paystack_sdk version
|
|
15
|
+
class CLI
|
|
16
|
+
USAGE = <<~TEXT
|
|
17
|
+
Usage:
|
|
18
|
+
paystack_sdk skills install [--dir DIR | --global] [--dry-run] [--force]
|
|
19
|
+
paystack_sdk skills uninstall [--dir DIR | --global] [--dry-run]
|
|
20
|
+
paystack_sdk skills list
|
|
21
|
+
paystack_sdk skills path
|
|
22
|
+
paystack_sdk version
|
|
23
|
+
|
|
24
|
+
`skills install` copies the AI skills that ship with this gem into .claude/skills/ (Claude Code),
|
|
25
|
+
ready for an agent to load. It is safe to repeat, never overwrites a skill folder it did not
|
|
26
|
+
install, and after a gem upgrade updates only its own folders.
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--dir DIR install into DIR instead of ./.claude/skills
|
|
30
|
+
--global install into ~/.claude/skills
|
|
31
|
+
--dry-run say what would happen and change nothing
|
|
32
|
+
--force replace a same-named folder that paystack_sdk did not install
|
|
33
|
+
TEXT
|
|
34
|
+
|
|
35
|
+
# Runs the command.
|
|
36
|
+
#
|
|
37
|
+
# @param argv [Array<String>]
|
|
38
|
+
# @param out [IO]
|
|
39
|
+
# @param err [IO]
|
|
40
|
+
# @return [Integer] the exit status
|
|
41
|
+
def self.run(argv, out: $stdout, err: $stderr)
|
|
42
|
+
new(out: out, err: err).run(argv.dup)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize(out:, err:)
|
|
46
|
+
@out = out
|
|
47
|
+
@err = err
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def run(argv)
|
|
51
|
+
command = argv.shift
|
|
52
|
+
case command
|
|
53
|
+
when "skills" then skills(argv)
|
|
54
|
+
when "version", "--version", "-v" then @out.puts("paystack_sdk #{VERSION}").then { 0 }
|
|
55
|
+
when nil, "help", "--help", "-h" then @out.puts(USAGE).then { 0 }
|
|
56
|
+
else fail_with("unknown command #{command.inspect}")
|
|
57
|
+
end
|
|
58
|
+
rescue OptionParser::ParseError => e
|
|
59
|
+
fail_with(e.message)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def skills(argv)
|
|
65
|
+
sub = argv.shift
|
|
66
|
+
case sub
|
|
67
|
+
when "install" then install(argv)
|
|
68
|
+
when "uninstall" then uninstall(argv)
|
|
69
|
+
when "list" then list
|
|
70
|
+
when "path" then @out.puts(Skills::SOURCE_DIR).then { 0 }
|
|
71
|
+
else fail_with("unknown skills command #{sub.inspect}")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def install(argv)
|
|
76
|
+
options = parse(argv, force: true)
|
|
77
|
+
dir = target(options)
|
|
78
|
+
results = Skills.install(dir, dry_run: options[:dry_run], force: options[:force])
|
|
79
|
+
report(results, dir, options)
|
|
80
|
+
(results.any? { |r| r.status == "conflict" }) ? 1 : 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def uninstall(argv)
|
|
84
|
+
options = parse(argv, force: false)
|
|
85
|
+
dir = target(options)
|
|
86
|
+
results = Skills.uninstall(dir, dry_run: options[:dry_run])
|
|
87
|
+
report(results, dir, options)
|
|
88
|
+
0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def list
|
|
92
|
+
Skills.all.each { |skill| @out.puts("#{skill.name}\n #{skill.description}") }
|
|
93
|
+
0
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def parse(argv, force:)
|
|
97
|
+
options = {}
|
|
98
|
+
OptionParser.new do |o|
|
|
99
|
+
o.on("--dir DIR") { |v| options[:dir] = v }
|
|
100
|
+
o.on("--global") { options[:global] = true }
|
|
101
|
+
o.on("--dry-run") { options[:dry_run] = true }
|
|
102
|
+
o.on("--force") { options[:force] = true } if force
|
|
103
|
+
end.parse!(argv)
|
|
104
|
+
raise OptionParser::InvalidOption, argv.first unless argv.empty?
|
|
105
|
+
raise OptionParser::InvalidOption, "--dir and --global together" if options[:dir] && options[:global]
|
|
106
|
+
|
|
107
|
+
options
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def target(options)
|
|
111
|
+
return File.expand_path(options[:dir]) if options[:dir]
|
|
112
|
+
return File.join(Dir.home, ".claude", "skills") if options[:global]
|
|
113
|
+
|
|
114
|
+
File.join(Dir.pwd, ".claude", "skills")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def report(results, dir, options)
|
|
118
|
+
@out.puts("#{"Dry run: " if options[:dry_run]}#{dir}")
|
|
119
|
+
results.each do |r|
|
|
120
|
+
@out.puts("#{r.status.rjust(12)} #{r.name}#{" (#{r.note})" if r.note}")
|
|
121
|
+
end
|
|
122
|
+
@out.puts("\nDry run: nothing was written.") if options[:dry_run]
|
|
123
|
+
@out.puts("\nSome folders were left alone. Re-run with --force to replace them.") if results.any? { |r| r.status == "conflict" }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def fail_with(message)
|
|
127
|
+
@err.puts("paystack_sdk: #{message}\n\n#{USAGE}")
|
|
128
|
+
1
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/paystack_sdk/client.rb
CHANGED
|
@@ -27,6 +27,7 @@ require_relative "resources/integrations"
|
|
|
27
27
|
require_relative "resources/virtual_terminals"
|
|
28
28
|
require_relative "resources/direct_debits"
|
|
29
29
|
require_relative "resources/terminals"
|
|
30
|
+
require_relative "resources/webhook_events"
|
|
30
31
|
require_relative "utils/connection_utils"
|
|
31
32
|
|
|
32
33
|
module PaystackSdk
|
|
@@ -344,6 +345,18 @@ module PaystackSdk
|
|
|
344
345
|
@terminals ||= Resources::Terminals.new(@connection)
|
|
345
346
|
end
|
|
346
347
|
|
|
348
|
+
# Provides access to the `WebhookEvents` resource: the log of webhooks Paystack sent you, with their
|
|
349
|
+
# delivery status, and a way to send them again. (Not in Paystack's OpenAPI spec; from its docs.)
|
|
350
|
+
#
|
|
351
|
+
# @return [PaystackSdk::Resources::WebhookEvents] An instance of the
|
|
352
|
+
# `WebhookEvents` resource.
|
|
353
|
+
#
|
|
354
|
+
# @example
|
|
355
|
+
# client.webhook_events.list(status: "Failed")
|
|
356
|
+
def webhook_events
|
|
357
|
+
@webhook_events ||= Resources::WebhookEvents.new(@connection)
|
|
358
|
+
end
|
|
359
|
+
|
|
347
360
|
# A short description that never includes the secret key. Ruby's default `inspect` prints every
|
|
348
361
|
# instance variable, and the connection holds the key in its `Authorization` header, so logging,
|
|
349
362
|
# `pp`, an error page or an error tracker would otherwise write the key out.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/paystack-scaffold from Paystack's OpenAPI spec (PaystackOSS/openapi@d9d444d).
|
|
4
4
|
# Do not edit by hand: regenerate it with `bin/paystack-scaffold Customer`. Hand-written extras go in lib/paystack_sdk/resources/extensions/customers.rb.
|
|
5
|
-
# scaffold-digest:
|
|
5
|
+
# scaffold-digest: f23af37740665b121f36ec65dd45f1275b9b4358e704cf85b43f6cdd3218adae
|
|
6
6
|
|
|
7
7
|
require_relative "base"
|
|
8
8
|
|
|
@@ -281,6 +281,7 @@ module PaystackSdk
|
|
|
281
281
|
def direct_debit_activation_charge(id:, authorization_id:)
|
|
282
282
|
validate_presence!(value: id, name: "id")
|
|
283
283
|
validate_presence!(value: authorization_id, name: "authorization_id")
|
|
284
|
+
validate_numeric_id!(value: authorization_id, name: "authorization_id", allow_nil: false)
|
|
284
285
|
|
|
285
286
|
wire_body = to_wire({authorization_id:}, WIRE_NAMES)
|
|
286
287
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/paystack-scaffold from Paystack's OpenAPI spec (PaystackOSS/openapi@d9d444d).
|
|
4
4
|
# Do not edit by hand: regenerate it with `bin/paystack-scaffold Payment Request`. Hand-written extras go in lib/paystack_sdk/resources/extensions/payment_requests.rb.
|
|
5
|
-
# scaffold-digest:
|
|
5
|
+
# scaffold-digest: cfe9d7cb1a4ff04446434e6a6d3b3ca134e3b04e3b2a223d3f50e546ef6e9c3e
|
|
6
6
|
|
|
7
7
|
require_relative "base"
|
|
8
8
|
|
|
@@ -40,6 +40,7 @@ module PaystackSdk
|
|
|
40
40
|
)
|
|
41
41
|
validate_positive_integer!(value: per_page, name: "perPage")
|
|
42
42
|
validate_positive_integer!(value: page, name: "page")
|
|
43
|
+
validate_numeric_id!(value: customer_id, name: "customer")
|
|
43
44
|
validate_allowed_values!(
|
|
44
45
|
value: status,
|
|
45
46
|
allowed_values: %w[draft pending success failed],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/paystack-scaffold from Paystack's OpenAPI spec (PaystackOSS/openapi@d9d444d).
|
|
4
4
|
# Do not edit by hand: regenerate it with `bin/paystack-scaffold Refund`. Hand-written extras go in lib/paystack_sdk/resources/extensions/refunds.rb.
|
|
5
|
-
# scaffold-digest:
|
|
5
|
+
# scaffold-digest: a937a81e9f6e991fee25d3817854912d092e7ebc140f6deffc87a3bef23bd5ba
|
|
6
6
|
|
|
7
7
|
require_relative "base"
|
|
8
8
|
|
|
@@ -28,6 +28,7 @@ module PaystackSdk
|
|
|
28
28
|
def list(per_page: nil, page: nil, from: nil, to: nil, transaction_id: nil)
|
|
29
29
|
validate_positive_integer!(value: per_page, name: "perPage")
|
|
30
30
|
validate_positive_integer!(value: page, name: "page")
|
|
31
|
+
validate_numeric_id!(value: transaction_id, name: "transaction")
|
|
31
32
|
|
|
32
33
|
wire_query = to_wire(
|
|
33
34
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/paystack-scaffold from Paystack's OpenAPI spec (PaystackOSS/openapi@d9d444d).
|
|
4
4
|
# Do not edit by hand: regenerate it with `bin/paystack-scaffold Subscription`. Hand-written extras go in lib/paystack_sdk/resources/extensions/subscriptions.rb.
|
|
5
|
-
# scaffold-digest:
|
|
5
|
+
# scaffold-digest: afa2b5d66179938b017341e73f1617480718a7b043c2b051c437fad1b209f893
|
|
6
6
|
|
|
7
7
|
require_relative "base"
|
|
8
8
|
|
|
@@ -29,6 +29,8 @@ module PaystackSdk
|
|
|
29
29
|
def list(per_page: nil, page: nil, plan_id: nil, customer_id: nil, from: nil, to: nil)
|
|
30
30
|
validate_positive_integer!(value: per_page, name: "perPage")
|
|
31
31
|
validate_positive_integer!(value: page, name: "page")
|
|
32
|
+
validate_numeric_id!(value: plan_id, name: "plan")
|
|
33
|
+
validate_numeric_id!(value: customer_id, name: "customer")
|
|
32
34
|
|
|
33
35
|
wire_query = to_wire(
|
|
34
36
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Generated by bin/paystack-scaffold from Paystack's OpenAPI spec (PaystackOSS/openapi@d9d444d).
|
|
4
4
|
# Do not edit by hand: regenerate it with `bin/paystack-scaffold Transaction`. Hand-written extras go in lib/paystack_sdk/resources/extensions/transactions.rb.
|
|
5
|
-
# scaffold-digest:
|
|
5
|
+
# scaffold-digest: e2ec619d787ab4bb8a5c5c09e4c0df028f645a578b41df012aad09174ce3eca2
|
|
6
6
|
|
|
7
7
|
require_relative "base"
|
|
8
8
|
|
|
@@ -31,7 +31,7 @@ module PaystackSdk
|
|
|
31
31
|
# @param transaction_charge [String] A flat fee to charge the subaccount for a transaction.
|
|
32
32
|
# @param bearer [String] The bearer of the transaction charge One of: account, subaccount.
|
|
33
33
|
# @param label [String] Used to replace the email address shown on the Checkout
|
|
34
|
-
# @param metadata [
|
|
34
|
+
# @param metadata [String] Custom data as a JSON object (a Hash, or a JSON string).
|
|
35
35
|
# @return [PaystackSdk::Response] The response from the Paystack API.
|
|
36
36
|
# @raise [PaystackSdk::Error] If a parameter is invalid or the API request fails.
|
|
37
37
|
# @see https://paystack.com/docs/api/transaction/#initialize
|
|
@@ -84,7 +84,7 @@ module PaystackSdk
|
|
|
84
84
|
transaction_charge:,
|
|
85
85
|
bearer:,
|
|
86
86
|
label:,
|
|
87
|
-
metadata:
|
|
87
|
+
metadata: stringify_json(metadata)
|
|
88
88
|
},
|
|
89
89
|
WIRE_NAMES
|
|
90
90
|
)
|
|
@@ -283,6 +283,7 @@ module PaystackSdk
|
|
|
283
283
|
allowed_values: %w[merchantApi checkout pos virtualTerminal],
|
|
284
284
|
name: "source"
|
|
285
285
|
)
|
|
286
|
+
validate_numeric_id!(value: customer_id, name: "customer")
|
|
286
287
|
validate_positive_integer!(value: amount, name: "amount")
|
|
287
288
|
validate_allowed_values!(
|
|
288
289
|
value: channel,
|
|
@@ -372,7 +373,7 @@ module PaystackSdk
|
|
|
372
373
|
# @param from [String] The start date
|
|
373
374
|
# @param to [String] The end date
|
|
374
375
|
# @param status [String] Filter by the status of the transaction One of: success, failed, abandoned, reversed, all.
|
|
375
|
-
# @param customer_id [
|
|
376
|
+
# @param customer_id [Integer] Filter by customer ID
|
|
376
377
|
# @param subaccount_code [String] Filter by subaccount code
|
|
377
378
|
# @param settlement [Integer] Filter by the settlement ID
|
|
378
379
|
# @param currency [String] Specify the transaction currency to export.
|
|
@@ -399,6 +400,7 @@ module PaystackSdk
|
|
|
399
400
|
allowed_values: %w[success failed abandoned reversed all],
|
|
400
401
|
name: "status"
|
|
401
402
|
)
|
|
403
|
+
validate_numeric_id!(value: customer_id, name: "customer")
|
|
402
404
|
validate_positive_integer!(value: amount, name: "amount")
|
|
403
405
|
|
|
404
406
|
wire_query = to_wire(
|