@garuhq/cli 0.5.0 → 0.6.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.
- package/CHANGELOG.md +41 -9
- package/README.md +137 -49
- package/dist/index.cjs +481 -16
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
All notable changes to `@garuhq/cli` are documented in this file. Format:
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.6.0] — 2026-05-25
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `garu scheduled-charges` — a new command group for future-dated
|
|
11
|
+
charges, wrapping the full `@garuhq/node` `scheduledCharges` surface:
|
|
12
|
+
`create`, `list`, `get`, `postpone`, `pause`, `resume`, `mark-paid`,
|
|
13
|
+
`cancel-recurrence`, `cancel-at-period-end`, `change-payment-method`,
|
|
14
|
+
`clear-payment-method`, and `attempts`.
|
|
15
|
+
- `garu scheduled-charges charge-now <id>` — dispatch a scheduled
|
|
16
|
+
charge immediately (the same charge + notification the daily cron
|
|
17
|
+
sends on the due date), instead of waiting for `dueDate`.
|
|
18
|
+
- **Idempotent**: a cycle whose d-day was already dispatched reports
|
|
19
|
+
`already_sent` and is **not** re-charged.
|
|
20
|
+
- The returned pt-BR `message` is always printed; in `--json` mode
|
|
21
|
+
the full `{ outcome, cycleNumber, reason?, message }` is emitted on
|
|
22
|
+
stdout.
|
|
23
|
+
- **Exits non-zero** on outcome `failed`/`not_sent` and on a 4xx
|
|
24
|
+
rejection (`400` for a non-billable status or a recurring series
|
|
25
|
+
with no open cycle; `404` if the charge isn't yours), so it
|
|
26
|
+
composes in shell pipelines. `dispatched` and `already_sent`
|
|
27
|
+
exit `0`.
|
|
28
|
+
- `garu scheduled-charges create --max-recovery-days <n>` — set how
|
|
29
|
+
many days past `dueDate` the daily recovery sweep keeps auto-billing
|
|
30
|
+
a missed charge (integer 1–365). Omit for the system default (14).
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `@garuhq/node` SDK bumped to 0.13.0 for the new
|
|
35
|
+
`scheduledCharges.chargeNow()` method and the `maxRecoveryDays`
|
|
36
|
+
field on create params / `ScheduledChargeRecord`.
|
|
37
|
+
|
|
6
38
|
## [0.5.0] — 2026-05-19
|
|
7
39
|
|
|
8
40
|
### Added
|
|
@@ -14,14 +46,14 @@ All notable changes to `@garuhq/cli` are documented in this file. Format:
|
|
|
14
46
|
then dispatches that clone. The original failure record (response
|
|
15
47
|
status, response body, attempts, timestamps) stays intact.
|
|
16
48
|
- In pretty mode the CLI prints `✓ Resent event <src> → new event
|
|
17
|
-
|
|
49
|
+
<clone>` to stderr so the new id is impossible to miss; the
|
|
18
50
|
cloned event itself is rendered on stdout with a new `resendOf:`
|
|
19
51
|
line.
|
|
20
52
|
- In JSON mode the cloned event is the full stdout payload —
|
|
21
53
|
`.id` is the new event, `.manualResendOf` is the source.
|
|
22
54
|
- Recipient handlers will see this as a distinct delivery: the
|
|
23
55
|
gateway POSTs the clone with `Idempotency-Key:
|
|
24
|
-
|
|
56
|
+
resend_<originalId>`.
|
|
25
57
|
|
|
26
58
|
### Deprecated
|
|
27
59
|
|
|
@@ -50,7 +82,7 @@ All notable changes to `@garuhq/cli` are documented in this file. Format:
|
|
|
50
82
|
- **`garu --version`** correctly reports `0.4.2`. The `0.4.1` release
|
|
51
83
|
shipped with `src/version.ts:CLI_VERSION` still pinned to `'0.4.0'`
|
|
52
84
|
because that file was missed during the bump. A new `scripts/
|
|
53
|
-
|
|
85
|
+
check-version-sync.mjs` runs as part of `prepublishOnly` and fails
|
|
54
86
|
the build if `package.json:version` and `src/version.ts:CLI_VERSION`
|
|
55
87
|
drift apart.
|
|
56
88
|
|
|
@@ -71,7 +103,7 @@ All notable changes to `@garuhq/cli` are documented in this file. Format:
|
|
|
71
103
|
- Bump `@garuhq/node` to `0.11.1` to pick up the empty-body POST fix.
|
|
72
104
|
`garu webhooks events retry <id>` and `garu scheduled-charges resume <id>`
|
|
73
105
|
were failing against production with `Body cannot be empty when content-type
|
|
74
|
-
|
|
106
|
+
is set to 'application/json'`. The SDK now sends an explicit `{}` body on
|
|
75
107
|
every otherwise-empty mutation.
|
|
76
108
|
|
|
77
109
|
## [0.4.0] — 2026-05-19
|
|
@@ -81,15 +113,15 @@ All notable changes to `@garuhq/cli` are documented in this file. Format:
|
|
|
81
113
|
- `garu webhooks events` command tree — inspect and replay webhook
|
|
82
114
|
deliveries from the CLI. The dashboard "Reenviar" button is now
|
|
83
115
|
available from the API key auth path as well, which means support
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
- on-call workflows can resend events from a terminal instead of
|
|
117
|
+
having to log into the dashboard.
|
|
118
|
+
* `garu webhooks events list [--status <s>] [--event-type <t>] [--endpoint-id <n>] [--page <n>] [--limit <n>]`
|
|
87
119
|
— paginated listing with status badges (green `success`, yellow
|
|
88
120
|
`pending`, red `failed`) in TTY mode.
|
|
89
|
-
|
|
121
|
+
* `garu webhooks events get <id>` — fetch one webhook event with
|
|
90
122
|
the full endpoint snapshot, response status, and (truncated)
|
|
91
123
|
response body.
|
|
92
|
-
|
|
124
|
+
* `garu webhooks events retry <id>` — re-deliver a webhook event
|
|
93
125
|
(resets to `pending` and triggers an immediate attempt). Works on
|
|
94
126
|
any status; use this when a customer reports a missed event.
|
|
95
127
|
|
package/README.md
CHANGED
|
@@ -59,11 +59,11 @@ garu doctor
|
|
|
59
59
|
|
|
60
60
|
The CLI resolves your API key using the following priority chain:
|
|
61
61
|
|
|
62
|
-
| Priority | Source
|
|
63
|
-
| ----------- |
|
|
64
|
-
| 1 (highest) | `--api-key` flag
|
|
65
|
-
| 2 | `GARU_API_KEY` env var
|
|
66
|
-
| 3 (lowest) | Config file
|
|
62
|
+
| Priority | Source | How to set |
|
|
63
|
+
| ----------- | ---------------------- | ----------------------------------------- |
|
|
64
|
+
| 1 (highest) | `--api-key` flag | `garu --api-key sk_live_... charges list` |
|
|
65
|
+
| 2 | `GARU_API_KEY` env var | `export GARU_API_KEY=sk_live_...` |
|
|
66
|
+
| 3 (lowest) | Config file | `garu login` |
|
|
67
67
|
|
|
68
68
|
If no key is found from any source, the CLI errors with code `auth_error`.
|
|
69
69
|
|
|
@@ -112,9 +112,9 @@ garu login
|
|
|
112
112
|
garu login --api-key sk_live_... --profile production
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
| Flag | Description
|
|
116
|
-
| ---------------------- |
|
|
117
|
-
| `--api-key <key>` | Pre-supply the key instead of prompting
|
|
115
|
+
| Flag | Description |
|
|
116
|
+
| ---------------------- | ------------------------------------------------ |
|
|
117
|
+
| `--api-key <key>` | Pre-supply the key instead of prompting |
|
|
118
118
|
| `-p, --profile <name>` | Profile name to store under (default: `default`) |
|
|
119
119
|
|
|
120
120
|
On success, credentials are saved to `~/.config/garu/credentials.json` with `0600` permissions (owner read/write only).
|
|
@@ -130,9 +130,9 @@ garu logout
|
|
|
130
130
|
garu logout --profile test
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
| Flag | Description
|
|
134
|
-
| ---------------------- |
|
|
135
|
-
| `-p, --profile <name>` | Only remove this profile instead of the whole file
|
|
133
|
+
| Flag | Description |
|
|
134
|
+
| ---------------------- | -------------------------------------------------- |
|
|
135
|
+
| `-p, --profile <name>` | Only remove this profile instead of the whole file |
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
@@ -156,13 +156,13 @@ garu charges list --status paid --limit 50
|
|
|
156
156
|
garu charges list --search "Maria" --payment-method pix
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
| Flag | Description
|
|
160
|
-
| --------------------------- |
|
|
161
|
-
| `--page <n>` | Page number (1-based)
|
|
162
|
-
| `--limit <n>` | Items per page (1-100)
|
|
163
|
-
| `--status <status>` | Filter by status (e.g. `paid`, `pending`)
|
|
164
|
-
| `--search <query>` | Search by customer name, email, or document
|
|
165
|
-
| `--payment-method <method>` | Filter: `pix`, `creditcard`, `boleto`
|
|
159
|
+
| Flag | Description |
|
|
160
|
+
| --------------------------- | ------------------------------------------- |
|
|
161
|
+
| `--page <n>` | Page number (1-based) |
|
|
162
|
+
| `--limit <n>` | Items per page (1-100) |
|
|
163
|
+
| `--status <status>` | Filter by status (e.g. `paid`, `pending`) |
|
|
164
|
+
| `--search <query>` | Search by customer name, email, or document |
|
|
165
|
+
| `--payment-method <method>` | Filter: `pix`, `creditcard`, `boleto` |
|
|
166
166
|
|
|
167
167
|
---
|
|
168
168
|
|
|
@@ -191,21 +191,21 @@ garu charges create --type credit_card --product-id prod-uuid \
|
|
|
191
191
|
--installments 3
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
| Flag
|
|
195
|
-
|
|
|
196
|
-
| `--type <type>`
|
|
197
|
-
| `--product-id <uuid>`
|
|
198
|
-
| `--customer-name <name>`
|
|
199
|
-
| `--customer-email <email>`
|
|
200
|
-
| `--customer-document <doc>`
|
|
201
|
-
| `--customer-phone <phone>`
|
|
202
|
-
| `--card-number <number>`
|
|
203
|
-
| `--card-cvv <cvv>`
|
|
204
|
-
| `--card-expiration <yyyy-mm>`
|
|
205
|
-
| `--card-holder <name>`
|
|
206
|
-
| `--installments <n>`
|
|
207
|
-
| `--additional-info <text>`
|
|
208
|
-
| `--idempotency-key <key>`
|
|
194
|
+
| Flag | Required | Description |
|
|
195
|
+
| ----------------------------- | ----------- | ---------------------------------------------- |
|
|
196
|
+
| `--type <type>` | Yes | Payment method: `pix`, `credit_card`, `boleto` |
|
|
197
|
+
| `--product-id <uuid>` | Yes | Product UUID |
|
|
198
|
+
| `--customer-name <name>` | Yes | Customer full name |
|
|
199
|
+
| `--customer-email <email>` | Yes | Customer email |
|
|
200
|
+
| `--customer-document <doc>` | Yes | CPF (11 digits) or CNPJ (14 digits) |
|
|
201
|
+
| `--customer-phone <phone>` | Yes | Phone with area code, digits only |
|
|
202
|
+
| `--card-number <number>` | credit_card | Credit card number |
|
|
203
|
+
| `--card-cvv <cvv>` | credit_card | Credit card CVV |
|
|
204
|
+
| `--card-expiration <yyyy-mm>` | credit_card | Expiration date |
|
|
205
|
+
| `--card-holder <name>` | credit_card | Cardholder name |
|
|
206
|
+
| `--installments <n>` | No | Number of installments, 1-12 (default: 1) |
|
|
207
|
+
| `--additional-info <text>` | No | Free-form metadata |
|
|
208
|
+
| `--idempotency-key <key>` | No | Idempotency key (auto-generated if omitted) |
|
|
209
209
|
|
|
210
210
|
---
|
|
211
211
|
|
|
@@ -232,11 +232,99 @@ garu charges refund 4472
|
|
|
232
232
|
garu charges refund 4472 --amount 1000 --reason "customer_request"
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
| Flag | Description
|
|
236
|
-
| ------------------------- |
|
|
235
|
+
| Flag | Description |
|
|
236
|
+
| ------------------------- | ------------------------------------------------- |
|
|
237
237
|
| `--amount <centavos>` | Partial refund amount in centavos (omit for full) |
|
|
238
|
-
| `--reason <text>` | Optional refund reason
|
|
239
|
-
| `--idempotency-key <key>` | Idempotency key (auto-generated if omitted)
|
|
238
|
+
| `--reason <text>` | Optional refund reason |
|
|
239
|
+
| `--idempotency-key <key>` | Idempotency key (auto-generated if omitted) |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### `garu scheduled-charges create`
|
|
244
|
+
|
|
245
|
+
Schedule a future-dated charge — one-time or recurring (PIX, Boleto, or Card).
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# One-time PIX/Boleto charge
|
|
249
|
+
garu scheduled-charges create \
|
|
250
|
+
--customer-id 42 --amount 297.50 --type one_time \
|
|
251
|
+
--due-date 2026-06-15 --methods pix,boleto \
|
|
252
|
+
--description "Mensalidade Junho"
|
|
253
|
+
|
|
254
|
+
# Recurring card subscription, custom recovery window
|
|
255
|
+
garu scheduled-charges create \
|
|
256
|
+
--customer-id 42 --amount 99.00 --type recurring \
|
|
257
|
+
--due-date 2026-06-15 --methods card --product-id 5 \
|
|
258
|
+
--recurrence-interval monthly --recurrence-ends-after 12 \
|
|
259
|
+
--max-recovery-days 30
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Flag | Description |
|
|
263
|
+
| --------------------------------- | ------------------------------------------------------------------------- |
|
|
264
|
+
| `--customer-id <n>` | Customer id (required) |
|
|
265
|
+
| `--amount <brl>` | Decimal BRL amount, e.g. `297.50` (required) |
|
|
266
|
+
| `--type <type>` | `one_time` or `recurring` (required) |
|
|
267
|
+
| `--due-date <yyyy-mm-dd>` | First due date in São Paulo time (required) |
|
|
268
|
+
| `--methods <list>` | Comma-separated: `pix,boleto,card` (required; `card` is recurring-only) |
|
|
269
|
+
| `--product-id <n>` | Product id (required when `methods` includes `card`) |
|
|
270
|
+
| `--description <text>` | Charge description |
|
|
271
|
+
| `--recurrence-interval <i>` | `weekly`/`biweekly`/`monthly`/`bimonthly`/`quarterly`/`biannual`/`yearly` |
|
|
272
|
+
| `--recurrence-interval-count <n>` | Multiplier for the interval |
|
|
273
|
+
| `--recurrence-ends-after <n>` | Stop after N successful cycles |
|
|
274
|
+
| `--recurrence-ends-on <date>` | Stop after this calendar date |
|
|
275
|
+
| `--trial-days <n>` | Free-trial days (1–365, recurring-only) |
|
|
276
|
+
| `--external-reference <ref>` | Your own reconciliation reference |
|
|
277
|
+
| `--metadata <json>` | JSON object of custom metadata |
|
|
278
|
+
| `--max-recovery-days <n>` | Days past due the recovery sweep keeps auto-billing (1–365; default 14) |
|
|
279
|
+
| `--idempotency-key <key>` | Idempotency key (auto-generated if omitted) |
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
### `garu scheduled-charges charge-now`
|
|
284
|
+
|
|
285
|
+
Dispatch a scheduled charge **now** — the same charge + notification the daily
|
|
286
|
+
cron would send on the due date — instead of waiting for `dueDate`.
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
garu scheduled-charges charge-now sch_abc123
|
|
290
|
+
|
|
291
|
+
# Compose in scripts: non-zero exit on a negative outcome
|
|
292
|
+
if garu scheduled-charges charge-now sch_abc123 --json | jq -e '.outcome=="dispatched"'; then
|
|
293
|
+
echo "sent"
|
|
294
|
+
fi
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Idempotent.** If this cycle's d-day was already dispatched, the command
|
|
298
|
+
reports `already_sent` and does **not** re-charge. The returned `message`
|
|
299
|
+
(pt-BR, ready to show) is always printed; in `--json` mode the full result —
|
|
300
|
+
`{ outcome, cycleNumber, reason?, message }` — is written to stdout.
|
|
301
|
+
|
|
302
|
+
The process **exits non-zero** when:
|
|
303
|
+
|
|
304
|
+
- `outcome` is `failed` (card charge failed — see `reason`, e.g. `card_expired`) or
|
|
305
|
+
`not_sent` (couldn't dispatch — e.g. `no_email`, `no_saved_payment_method`), and
|
|
306
|
+
- the gateway rejects the request with a 4xx — `400` if the charge isn't in a
|
|
307
|
+
billable status (`scheduled`/`due_today`) or a recurring series has no open
|
|
308
|
+
cycle, `404` if the charge isn't yours.
|
|
309
|
+
|
|
310
|
+
`dispatched` and `already_sent` exit `0`.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### `garu scheduled-charges` — other subcommands
|
|
315
|
+
|
|
316
|
+
| Command | Description |
|
|
317
|
+
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
318
|
+
| `list [--page --limit --customer-id --status --type ...]` | List scheduled charges (`--status` repeatable; `--due-from/--due-to`, `--search`) |
|
|
319
|
+
| `get <id>` | Fetch a charge with its event timeline and linked transactions |
|
|
320
|
+
| `postpone <id> --new-due-date <date> [--reason]` | Move to a new due date |
|
|
321
|
+
| `pause <id> [--reason]` / `resume <id>` | Pause (no reminders fire) / resume |
|
|
322
|
+
| `mark-paid <id> --payment-date <date> [--external-reference --cycle-number]` | Mark paid out-of-band (`--cycle-number` required for recurring) |
|
|
323
|
+
| `cancel-recurrence <id> [--reason]` | Stop future cycles of a recurring series |
|
|
324
|
+
| `cancel-at-period-end <id> [--disable]` | Toggle Stripe-style soft cancel (omit `--disable` to enable) |
|
|
325
|
+
| `change-payment-method <id> --payment-method-id <n>` | Swap the saved card on a recurring series |
|
|
326
|
+
| `clear-payment-method <id>` | Clear the saved card (future cycles fall back to email-with-link) |
|
|
327
|
+
| `attempts <id> [--page --limit --cycle-number]` | Per-attempt billing log |
|
|
240
328
|
|
|
241
329
|
---
|
|
242
330
|
|
|
@@ -274,10 +362,10 @@ These flags work on every command:
|
|
|
274
362
|
garu [global options] <command> [command options]
|
|
275
363
|
```
|
|
276
364
|
|
|
277
|
-
| Flag | Description
|
|
278
|
-
| ---------------------- |
|
|
365
|
+
| Flag | Description |
|
|
366
|
+
| ---------------------- | ------------------------------------------------------------- |
|
|
279
367
|
| `--api-key <key>` | Override API key for this invocation (takes highest priority) |
|
|
280
|
-
| `-p, --profile <name>` | Credentials profile to use
|
|
368
|
+
| `-p, --profile <name>` | Credentials profile to use |
|
|
281
369
|
| `--json` | Force JSON output even in interactive terminals |
|
|
282
370
|
| `-q, --quiet` | Suppress status output; only print results and errors |
|
|
283
371
|
| `-v, --version` | Print version and exit |
|
|
@@ -289,10 +377,10 @@ garu [global options] <command> [command options]
|
|
|
289
377
|
|
|
290
378
|
The CLI has two output modes:
|
|
291
379
|
|
|
292
|
-
| Mode | When | Stdout | Stderr
|
|
293
|
-
| --------------- | ---------------------- | -------------- |
|
|
294
|
-
| **Interactive** | Terminal (TTY) | Formatted text | Status lines
|
|
295
|
-
| **Machine** | Piped, CI, or `--json` | JSON | Nothing
|
|
380
|
+
| Mode | When | Stdout | Stderr |
|
|
381
|
+
| --------------- | ---------------------- | -------------- | ------------ |
|
|
382
|
+
| **Interactive** | Terminal (TTY) | Formatted text | Status lines |
|
|
383
|
+
| **Machine** | Piped, CI, or `--json` | JSON | Nothing |
|
|
296
384
|
|
|
297
385
|
Switching is automatic -- pipe to another command and JSON output activates:
|
|
298
386
|
|
|
@@ -338,11 +426,11 @@ Agents calling the CLI as a subprocess automatically get JSON output (non-TTY de
|
|
|
338
426
|
|
|
339
427
|
## Configuration
|
|
340
428
|
|
|
341
|
-
| Item
|
|
342
|
-
|
|
|
343
|
-
| Config directory
|
|
344
|
-
| Credentials
|
|
345
|
-
| Override path
|
|
429
|
+
| Item | Path | Notes |
|
|
430
|
+
| ---------------- | --------------------------------- | ------------------------------------- |
|
|
431
|
+
| Config directory | `~/.config/garu/` | Respects `$XDG_CONFIG_HOME` |
|
|
432
|
+
| Credentials | `~/.config/garu/credentials.json` | `0600` permissions (owner read/write) |
|
|
433
|
+
| Override path | `$GARU_CREDENTIALS_PATH` | Point to a custom credentials file |
|
|
346
434
|
|
|
347
435
|
---
|
|
348
436
|
|
package/dist/index.cjs
CHANGED
|
@@ -15,7 +15,7 @@ var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
|
15
15
|
// src/index.ts
|
|
16
16
|
|
|
17
17
|
// src/version.ts
|
|
18
|
-
var CLI_VERSION = "0.
|
|
18
|
+
var CLI_VERSION = "0.6.0";
|
|
19
19
|
var CliError = class extends Error {
|
|
20
20
|
code;
|
|
21
21
|
exitCode;
|
|
@@ -475,6 +475,8 @@ async function logoutCommand(opts = {}) {
|
|
|
475
475
|
printSuccess(`Profile '${opts.profile}' removed.`, opts);
|
|
476
476
|
return { cleared: opts.profile };
|
|
477
477
|
}
|
|
478
|
+
|
|
479
|
+
// src/commands/scheduled-charges.ts
|
|
478
480
|
async function getClient2(opts) {
|
|
479
481
|
if (opts.garu) return opts.garu;
|
|
480
482
|
const auth = await resolveAuth({
|
|
@@ -486,8 +488,209 @@ async function getClient2(opts) {
|
|
|
486
488
|
...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
|
|
487
489
|
});
|
|
488
490
|
}
|
|
489
|
-
|
|
491
|
+
function buildRecurrence(opts) {
|
|
492
|
+
if (opts.recurrenceInterval === void 0) return void 0;
|
|
493
|
+
const recurrence = { interval: opts.recurrenceInterval };
|
|
494
|
+
if (opts.recurrenceIntervalCount !== void 0)
|
|
495
|
+
recurrence.intervalCount = opts.recurrenceIntervalCount;
|
|
496
|
+
if (opts.recurrenceEndsAfter !== void 0) recurrence.endsAfter = opts.recurrenceEndsAfter;
|
|
497
|
+
if (opts.recurrenceEndsOn !== void 0) recurrence.endsOn = opts.recurrenceEndsOn;
|
|
498
|
+
return recurrence;
|
|
499
|
+
}
|
|
500
|
+
async function scheduledChargesCreateCommand(opts) {
|
|
501
|
+
const garu = await getClient2(opts);
|
|
502
|
+
const params = {
|
|
503
|
+
customerId: opts.customerId,
|
|
504
|
+
amount: opts.amount,
|
|
505
|
+
type: opts.type,
|
|
506
|
+
dueDate: opts.dueDate,
|
|
507
|
+
methods: opts.methods
|
|
508
|
+
};
|
|
509
|
+
if (opts.productId !== void 0) params.productId = opts.productId;
|
|
510
|
+
if (opts.description !== void 0) params.description = opts.description;
|
|
511
|
+
const recurrence = buildRecurrence(opts);
|
|
512
|
+
if (recurrence !== void 0) params.recurrence = recurrence;
|
|
513
|
+
if (opts.trialDays !== void 0) params.trialDays = opts.trialDays;
|
|
514
|
+
if (opts.externalReference !== void 0) params.externalReference = opts.externalReference;
|
|
515
|
+
if (opts.metadata !== void 0) params.metadata = opts.metadata;
|
|
516
|
+
if (opts.maxRecoveryDays !== void 0) params.maxRecoveryDays = opts.maxRecoveryDays;
|
|
517
|
+
if (opts.idempotencyKey !== void 0) params.idempotencyKey = opts.idempotencyKey;
|
|
518
|
+
const charge = await garu.scheduledCharges.create(params);
|
|
519
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
520
|
+
return charge;
|
|
521
|
+
}
|
|
522
|
+
async function scheduledChargesListCommand(opts) {
|
|
523
|
+
const garu = await getClient2(opts);
|
|
524
|
+
const params = {};
|
|
525
|
+
if (opts.page !== void 0) params.page = opts.page;
|
|
526
|
+
if (opts.limit !== void 0) params.limit = opts.limit;
|
|
527
|
+
if (opts.customerId !== void 0) params.customerId = opts.customerId;
|
|
528
|
+
if (opts.status !== void 0 && opts.status.length > 0) {
|
|
529
|
+
params.status = opts.status.length === 1 ? opts.status[0] : opts.status;
|
|
530
|
+
}
|
|
531
|
+
if (opts.type !== void 0) params.type = opts.type;
|
|
532
|
+
if (opts.dueFrom !== void 0) params.dueFrom = opts.dueFrom;
|
|
533
|
+
if (opts.dueTo !== void 0) params.dueTo = opts.dueTo;
|
|
534
|
+
if (opts.search !== void 0) params.search = opts.search;
|
|
535
|
+
const result = await garu.scheduledCharges.list(params);
|
|
536
|
+
printResult(result, { ...opts, prettyPrint: prettyScheduledChargeList });
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
async function scheduledChargesGetCommand(opts) {
|
|
540
|
+
const garu = await getClient2(opts);
|
|
541
|
+
const detail = await garu.scheduledCharges.get(opts.id);
|
|
542
|
+
printResult(detail, { ...opts, prettyPrint: prettyScheduledChargeDetail });
|
|
543
|
+
return detail;
|
|
544
|
+
}
|
|
545
|
+
async function scheduledChargesPostponeCommand(opts) {
|
|
546
|
+
const garu = await getClient2(opts);
|
|
547
|
+
const params = { newDueDate: opts.newDueDate };
|
|
548
|
+
if (opts.reason !== void 0) params.reason = opts.reason;
|
|
549
|
+
const charge = await garu.scheduledCharges.postpone(opts.id, params);
|
|
550
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
551
|
+
return charge;
|
|
552
|
+
}
|
|
553
|
+
async function scheduledChargesPauseCommand(opts) {
|
|
554
|
+
const garu = await getClient2(opts);
|
|
555
|
+
const params = {};
|
|
556
|
+
if (opts.reason !== void 0) params.reason = opts.reason;
|
|
557
|
+
const charge = await garu.scheduledCharges.pause(opts.id, params);
|
|
558
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
559
|
+
return charge;
|
|
560
|
+
}
|
|
561
|
+
async function scheduledChargesResumeCommand(opts) {
|
|
562
|
+
const garu = await getClient2(opts);
|
|
563
|
+
const charge = await garu.scheduledCharges.resume(opts.id);
|
|
564
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
565
|
+
return charge;
|
|
566
|
+
}
|
|
567
|
+
async function scheduledChargesMarkPaidCommand(opts) {
|
|
568
|
+
const garu = await getClient2(opts);
|
|
569
|
+
const params = { paymentDate: opts.paymentDate };
|
|
570
|
+
if (opts.externalReference !== void 0) params.externalReference = opts.externalReference;
|
|
571
|
+
if (opts.cycleNumber !== void 0) params.cycleNumber = opts.cycleNumber;
|
|
572
|
+
const charge = await garu.scheduledCharges.markPaid(opts.id, params);
|
|
573
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
574
|
+
return charge;
|
|
575
|
+
}
|
|
576
|
+
async function scheduledChargesCancelRecurrenceCommand(opts) {
|
|
577
|
+
const garu = await getClient2(opts);
|
|
578
|
+
const params = {};
|
|
579
|
+
if (opts.reason !== void 0) params.reason = opts.reason;
|
|
580
|
+
const charge = await garu.scheduledCharges.cancelRecurrence(opts.id, params);
|
|
581
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
582
|
+
return charge;
|
|
583
|
+
}
|
|
584
|
+
async function scheduledChargesCancelAtPeriodEndCommand(opts) {
|
|
585
|
+
const garu = await getClient2(opts);
|
|
586
|
+
const params = { enabled: opts.enabled };
|
|
587
|
+
const charge = await garu.scheduledCharges.setCancelAtPeriodEnd(opts.id, params);
|
|
588
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
589
|
+
return charge;
|
|
590
|
+
}
|
|
591
|
+
async function scheduledChargesChangePaymentMethodCommand(opts) {
|
|
592
|
+
const garu = await getClient2(opts);
|
|
593
|
+
const params = {
|
|
594
|
+
paymentMethodId: opts.paymentMethodId
|
|
595
|
+
};
|
|
596
|
+
const charge = await garu.scheduledCharges.changePaymentMethod(opts.id, params);
|
|
597
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
598
|
+
return charge;
|
|
599
|
+
}
|
|
600
|
+
async function scheduledChargesClearPaymentMethodCommand(opts) {
|
|
601
|
+
const garu = await getClient2(opts);
|
|
602
|
+
const charge = await garu.scheduledCharges.clearPaymentMethod(opts.id);
|
|
603
|
+
printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
|
|
604
|
+
return charge;
|
|
605
|
+
}
|
|
606
|
+
async function scheduledChargesAttemptsCommand(opts) {
|
|
607
|
+
const garu = await getClient2(opts);
|
|
608
|
+
const params = {};
|
|
609
|
+
if (opts.page !== void 0) params.page = opts.page;
|
|
610
|
+
if (opts.limit !== void 0) params.limit = opts.limit;
|
|
611
|
+
if (opts.cycleNumber !== void 0) params.cycleNumber = opts.cycleNumber;
|
|
612
|
+
const result = await garu.scheduledCharges.listAttempts(opts.id, params);
|
|
613
|
+
printResult(result, { ...opts, prettyPrint: prettyAttemptList });
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
async function scheduledChargesChargeNowCommand(opts) {
|
|
490
617
|
const garu = await getClient2(opts);
|
|
618
|
+
const result = await garu.scheduledCharges.chargeNow(opts.id);
|
|
619
|
+
printResult(result, { ...opts, prettyPrint: prettyChargeNow });
|
|
620
|
+
if (result.outcome === "failed" || result.outcome === "not_sent") {
|
|
621
|
+
process.exitCode = 1;
|
|
622
|
+
}
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
function prettyScheduledCharge(c) {
|
|
626
|
+
const lines = [
|
|
627
|
+
`Scheduled charge ${c.id}`,
|
|
628
|
+
` status: ${c.status}`,
|
|
629
|
+
` type: ${c.type}`,
|
|
630
|
+
` amount: ${c.amount}`,
|
|
631
|
+
` dueDate: ${c.dueDate}`,
|
|
632
|
+
` methods: ${c.methods.join(", ")}`
|
|
633
|
+
];
|
|
634
|
+
if (c.description) lines.push(` description: ${c.description}`);
|
|
635
|
+
if (c.maxRecoveryDays !== null) lines.push(` maxRecoveryDays: ${c.maxRecoveryDays}`);
|
|
636
|
+
if (c.externalReference) lines.push(` externalRef: ${c.externalReference}`);
|
|
637
|
+
return lines.join("\n");
|
|
638
|
+
}
|
|
639
|
+
function prettyScheduledChargeList(list) {
|
|
640
|
+
if (list.data.length === 0) {
|
|
641
|
+
return `No scheduled charges found (page ${list.meta.page}/${list.meta.totalPages || 1})`;
|
|
642
|
+
}
|
|
643
|
+
const header = `Scheduled charges (page ${list.meta.page}/${list.meta.totalPages || "?"}, ${list.meta.total} total)`;
|
|
644
|
+
const rows = list.data.map(
|
|
645
|
+
(c) => ` ${c.id.padEnd(20)} ${String(c.status).padEnd(18)} ${String(c.type).padEnd(9)} ${String(c.amount).padStart(10)} ${c.dueDate}`
|
|
646
|
+
);
|
|
647
|
+
return [header, ...rows].join("\n");
|
|
648
|
+
}
|
|
649
|
+
function prettyScheduledChargeDetail(detail) {
|
|
650
|
+
const lines = [prettyScheduledCharge(detail.charge)];
|
|
651
|
+
if (detail.events.length > 0) {
|
|
652
|
+
lines.push(" events:");
|
|
653
|
+
for (const e of detail.events) {
|
|
654
|
+
lines.push(` [${e.id}] ${e.eventType} (${e.createdAt})`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (detail.transactions.length > 0) {
|
|
658
|
+
lines.push(" transactions:");
|
|
659
|
+
for (const t of detail.transactions) {
|
|
660
|
+
lines.push(` [${t.id}] ${t.paymentMethod} ${t.status} ${t.value} (${t.date})`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return lines.join("\n");
|
|
664
|
+
}
|
|
665
|
+
function prettyAttemptList(list) {
|
|
666
|
+
if (list.data.length === 0) {
|
|
667
|
+
return `No billing attempts found (page ${list.meta.page}/${list.meta.totalPages || 1})`;
|
|
668
|
+
}
|
|
669
|
+
const header = `Billing attempts (page ${list.meta.page}/${list.meta.totalPages || "?"}, ${list.meta.total} total)`;
|
|
670
|
+
const rows = list.data.map(
|
|
671
|
+
(a) => ` cycle ${String(a.cycleNumber).padStart(3)} #${a.attemptNumber} ${a.status.padEnd(10)} ${a.paymentMethod.padEnd(7)} ${a.failureCode ?? ""} ${a.attemptedAt}`
|
|
672
|
+
);
|
|
673
|
+
return [header, ...rows].join("\n");
|
|
674
|
+
}
|
|
675
|
+
function prettyChargeNow(result) {
|
|
676
|
+
const lines = [result.message, ` outcome: ${result.outcome}`];
|
|
677
|
+
if (result.cycleNumber !== null) lines.push(` cycle: ${result.cycleNumber}`);
|
|
678
|
+
if (result.reason) lines.push(` reason: ${result.reason}`);
|
|
679
|
+
return lines.join("\n");
|
|
680
|
+
}
|
|
681
|
+
async function getClient3(opts) {
|
|
682
|
+
if (opts.garu) return opts.garu;
|
|
683
|
+
const auth = await resolveAuth({
|
|
684
|
+
...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
|
|
685
|
+
...opts.profile !== void 0 ? { profile: opts.profile } : {}
|
|
686
|
+
});
|
|
687
|
+
return createGaruClient({
|
|
688
|
+
auth,
|
|
689
|
+
...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
async function webhooksEventsListCommand(opts) {
|
|
693
|
+
const garu = await getClient3(opts);
|
|
491
694
|
const params = {};
|
|
492
695
|
if (opts.page !== void 0) params.page = opts.page;
|
|
493
696
|
if (opts.limit !== void 0) params.limit = opts.limit;
|
|
@@ -499,19 +702,19 @@ async function webhooksEventsListCommand(opts) {
|
|
|
499
702
|
return result;
|
|
500
703
|
}
|
|
501
704
|
async function webhooksEventsGetCommand(opts) {
|
|
502
|
-
const garu = await
|
|
705
|
+
const garu = await getClient3(opts);
|
|
503
706
|
const event = await garu.webhookEvents.get(opts.id);
|
|
504
707
|
printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
|
|
505
708
|
return event;
|
|
506
709
|
}
|
|
507
710
|
async function webhooksEventsRetryCommand(opts) {
|
|
508
|
-
const garu = await
|
|
711
|
+
const garu = await getClient3(opts);
|
|
509
712
|
const event = await garu.webhookEvents.retry(opts.id);
|
|
510
713
|
printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
|
|
511
714
|
return event;
|
|
512
715
|
}
|
|
513
716
|
async function webhooksEventsResendCommand(opts) {
|
|
514
|
-
const garu = await
|
|
717
|
+
const garu = await getClient3(opts);
|
|
515
718
|
const clone = await garu.webhookEvents.resend(opts.id);
|
|
516
719
|
printSuccess(`Resent event ${opts.id} \u2192 new event ${clone.id}`, opts);
|
|
517
720
|
printResult(clone, { ...opts, prettyPrint: prettyWebhookEvent });
|
|
@@ -575,6 +778,96 @@ function parseWebhookEventStatus(raw) {
|
|
|
575
778
|
`--status must be 'pending', 'success', or 'failed' (got '${raw}')`
|
|
576
779
|
);
|
|
577
780
|
}
|
|
781
|
+
function parseScheduledChargeType(raw) {
|
|
782
|
+
if (raw === "one_time" || raw === "recurring") return raw;
|
|
783
|
+
throw new CliError("invalid_input", `--type must be 'one_time' or 'recurring' (got '${raw}')`);
|
|
784
|
+
}
|
|
785
|
+
var SCHEDULED_PAYMENT_METHODS = ["pix", "boleto", "card"];
|
|
786
|
+
function parseScheduledPaymentMethods(raw) {
|
|
787
|
+
const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
788
|
+
if (parts.length === 0) {
|
|
789
|
+
throw new CliError(
|
|
790
|
+
"invalid_input",
|
|
791
|
+
`--methods must list at least one of: ${SCHEDULED_PAYMENT_METHODS.join(", ")}`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
for (const p of parts) {
|
|
795
|
+
if (!SCHEDULED_PAYMENT_METHODS.includes(p)) {
|
|
796
|
+
throw new CliError(
|
|
797
|
+
"invalid_input",
|
|
798
|
+
`--methods entries must be one of ${SCHEDULED_PAYMENT_METHODS.join(", ")} (got '${p}')`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return parts;
|
|
803
|
+
}
|
|
804
|
+
var RECURRENCE_INTERVALS = [
|
|
805
|
+
"weekly",
|
|
806
|
+
"biweekly",
|
|
807
|
+
"monthly",
|
|
808
|
+
"bimonthly",
|
|
809
|
+
"quarterly",
|
|
810
|
+
"biannual",
|
|
811
|
+
"yearly"
|
|
812
|
+
];
|
|
813
|
+
function parseRecurrenceInterval(raw) {
|
|
814
|
+
if (RECURRENCE_INTERVALS.includes(raw)) return raw;
|
|
815
|
+
throw new CliError(
|
|
816
|
+
"invalid_input",
|
|
817
|
+
`--recurrence-interval must be one of ${RECURRENCE_INTERVALS.join(", ")} (got '${raw}')`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
var SCHEDULED_CHARGE_STATUSES = [
|
|
821
|
+
"scheduled",
|
|
822
|
+
"due_today",
|
|
823
|
+
"overdue",
|
|
824
|
+
"paid",
|
|
825
|
+
"paused",
|
|
826
|
+
"canceled",
|
|
827
|
+
"trial",
|
|
828
|
+
"pending_tokenization",
|
|
829
|
+
"recurrence_canceled"
|
|
830
|
+
];
|
|
831
|
+
function parseScheduledChargeStatus(raw) {
|
|
832
|
+
if (SCHEDULED_CHARGE_STATUSES.includes(raw)) return raw;
|
|
833
|
+
throw new CliError(
|
|
834
|
+
"invalid_input",
|
|
835
|
+
`--status must be one of ${SCHEDULED_CHARGE_STATUSES.join(", ")} (got '${raw}')`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
function parseIntInRange(raw, label, min, max) {
|
|
839
|
+
const trimmed = raw.trim();
|
|
840
|
+
const n = Number.parseInt(trimmed, 10);
|
|
841
|
+
if (!Number.isFinite(n) || String(n) !== trimmed || n < min || n > max) {
|
|
842
|
+
throw new CliError(
|
|
843
|
+
"invalid_input",
|
|
844
|
+
`${label} must be an integer between ${min} and ${max} (got '${raw}')`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
return n;
|
|
848
|
+
}
|
|
849
|
+
function parseAmountBrl(raw) {
|
|
850
|
+
const n = Number(raw);
|
|
851
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
852
|
+
throw new CliError(
|
|
853
|
+
"invalid_input",
|
|
854
|
+
`--amount must be a positive decimal in BRL, e.g. 297.50 (got '${raw}')`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
return n;
|
|
858
|
+
}
|
|
859
|
+
function parseMetadata(raw) {
|
|
860
|
+
let parsed;
|
|
861
|
+
try {
|
|
862
|
+
parsed = JSON.parse(raw);
|
|
863
|
+
} catch {
|
|
864
|
+
throw new CliError("invalid_input", `--metadata must be valid JSON (got '${raw}')`);
|
|
865
|
+
}
|
|
866
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
867
|
+
throw new CliError("invalid_input", `--metadata must be a JSON object`);
|
|
868
|
+
}
|
|
869
|
+
return parsed;
|
|
870
|
+
}
|
|
578
871
|
|
|
579
872
|
// src/index.ts
|
|
580
873
|
function buildCli() {
|
|
@@ -605,17 +898,19 @@ function buildCli() {
|
|
|
605
898
|
);
|
|
606
899
|
});
|
|
607
900
|
const charges = program.command("charges").description("Create, fetch, and refund charges");
|
|
608
|
-
charges.command("list").description("List charges with pagination and filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option("--status <status>", "filter by status (e.g. paid, pending)").option("--search <query>", "search by customer name, email, or document").option("--payment-method <method>", "filter: pix, creditcard, boleto").action(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
901
|
+
charges.command("list").description("List charges with pagination and filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option("--status <status>", "filter by status (e.g. paid, pending)").option("--search <query>", "search by customer name, email, or document").option("--payment-method <method>", "filter: pix, creditcard, boleto").action(
|
|
902
|
+
async (cmdOpts) => {
|
|
903
|
+
const base = toCommandOptions(program);
|
|
904
|
+
await chargesListCommand({
|
|
905
|
+
...base,
|
|
906
|
+
page: cmdOpts.page,
|
|
907
|
+
limit: cmdOpts.limit,
|
|
908
|
+
status: cmdOpts.status,
|
|
909
|
+
search: cmdOpts.search,
|
|
910
|
+
paymentMethod: cmdOpts.paymentMethod
|
|
911
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
912
|
+
}
|
|
913
|
+
);
|
|
619
914
|
charges.command("create").description("Create a charge (PIX, credit card, or boleto)").requiredOption("--type <type>", "payment method: pix | credit_card | boleto").requiredOption("--product-id <uuid>", "product UUID").requiredOption("--customer-name <name>", "customer full name").requiredOption("--customer-email <email>", "customer email").requiredOption(
|
|
620
915
|
"--customer-document <document>",
|
|
621
916
|
"CPF (11 digits) or CNPJ (14 digits), digits only"
|
|
@@ -654,6 +949,176 @@ function buildCli() {
|
|
|
654
949
|
idempotencyKey: cmdOpts.idempotencyKey
|
|
655
950
|
}).catch((err) => printErrorAndExit(err, base));
|
|
656
951
|
});
|
|
952
|
+
const scheduled = program.command("scheduled-charges").description("Schedule, inspect, and act on future-dated charges");
|
|
953
|
+
scheduled.command("create").description("Schedule a future-dated charge (one-time or recurring)").requiredOption(
|
|
954
|
+
"--customer-id <n>",
|
|
955
|
+
"customer id",
|
|
956
|
+
(v) => parsePositiveIntId(v, "--customer-id")
|
|
957
|
+
).requiredOption("--amount <brl>", "decimal BRL amount, e.g. 297.50").requiredOption("--type <type>", "one_time | recurring").requiredOption("--due-date <yyyy-mm-dd>", "first due date in S\xE3o Paulo time").requiredOption("--methods <list>", "comma-separated: pix,boleto,card").option(
|
|
958
|
+
"--product-id <n>",
|
|
959
|
+
"product id (required when methods includes card)",
|
|
960
|
+
(v) => parsePositiveIntId(v, "--product-id")
|
|
961
|
+
).option("--description <text>", "charge description").option("--recurrence-interval <interval>", "recurring cadence: weekly|monthly|yearly|\u2026").option(
|
|
962
|
+
"--recurrence-interval-count <n>",
|
|
963
|
+
"multiplier for the interval",
|
|
964
|
+
(v) => parsePositiveIntId(v, "--recurrence-interval-count")
|
|
965
|
+
).option(
|
|
966
|
+
"--recurrence-ends-after <n>",
|
|
967
|
+
"stop after N successful cycles",
|
|
968
|
+
(v) => parsePositiveIntId(v, "--recurrence-ends-after")
|
|
969
|
+
).option("--recurrence-ends-on <yyyy-mm-dd>", "stop after this calendar date").option("--trial-days <n>", "free-trial days (1-365, recurring-only)").option("--external-reference <ref>", "your own reconciliation reference").option("--metadata <json>", "JSON object of custom metadata").option(
|
|
970
|
+
"--max-recovery-days <n>",
|
|
971
|
+
"days past due the recovery sweep keeps auto-billing (1-365; default 14)"
|
|
972
|
+
).option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (cmdOpts) => {
|
|
973
|
+
const base = toCommandOptions(program);
|
|
974
|
+
await scheduledChargesCreateCommand({
|
|
975
|
+
...base,
|
|
976
|
+
customerId: cmdOpts.customerId,
|
|
977
|
+
amount: parseAmountBrl(cmdOpts.amount),
|
|
978
|
+
type: parseScheduledChargeType(cmdOpts.type),
|
|
979
|
+
dueDate: cmdOpts.dueDate,
|
|
980
|
+
methods: parseScheduledPaymentMethods(cmdOpts.methods),
|
|
981
|
+
productId: cmdOpts.productId,
|
|
982
|
+
description: cmdOpts.description,
|
|
983
|
+
recurrenceInterval: cmdOpts.recurrenceInterval ? parseRecurrenceInterval(cmdOpts.recurrenceInterval) : void 0,
|
|
984
|
+
recurrenceIntervalCount: cmdOpts.recurrenceIntervalCount,
|
|
985
|
+
recurrenceEndsAfter: cmdOpts.recurrenceEndsAfter,
|
|
986
|
+
recurrenceEndsOn: cmdOpts.recurrenceEndsOn,
|
|
987
|
+
trialDays: cmdOpts.trialDays !== void 0 ? parseIntInRange(cmdOpts.trialDays, "--trial-days", 1, 365) : void 0,
|
|
988
|
+
externalReference: cmdOpts.externalReference,
|
|
989
|
+
metadata: cmdOpts.metadata ? parseMetadata(cmdOpts.metadata) : void 0,
|
|
990
|
+
maxRecoveryDays: cmdOpts.maxRecoveryDays !== void 0 ? parseIntInRange(cmdOpts.maxRecoveryDays, "--max-recovery-days", 1, 365) : void 0,
|
|
991
|
+
idempotencyKey: cmdOpts.idempotencyKey
|
|
992
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
993
|
+
});
|
|
994
|
+
scheduled.command("list").description("List scheduled charges with pagination and filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option(
|
|
995
|
+
"--customer-id <n>",
|
|
996
|
+
"filter by customer id",
|
|
997
|
+
(v) => parsePositiveIntId(v, "--customer-id")
|
|
998
|
+
).option(
|
|
999
|
+
"--status <status>",
|
|
1000
|
+
"filter by status (repeatable)",
|
|
1001
|
+
(val, acc) => acc.concat(val),
|
|
1002
|
+
[]
|
|
1003
|
+
).option("--type <type>", "filter by type: one_time | recurring").option("--due-from <yyyy-mm-dd>", "lower bound for due date").option("--due-to <yyyy-mm-dd>", "upper bound for due date").option("--search <query>", "search by customer name, email, or document").action(
|
|
1004
|
+
async (cmdOpts) => {
|
|
1005
|
+
const base = toCommandOptions(program);
|
|
1006
|
+
await scheduledChargesListCommand({
|
|
1007
|
+
...base,
|
|
1008
|
+
page: cmdOpts.page,
|
|
1009
|
+
limit: cmdOpts.limit,
|
|
1010
|
+
customerId: cmdOpts.customerId,
|
|
1011
|
+
status: cmdOpts.status.length > 0 ? cmdOpts.status.map(parseScheduledChargeStatus) : void 0,
|
|
1012
|
+
type: cmdOpts.type ? parseScheduledChargeType(cmdOpts.type) : void 0,
|
|
1013
|
+
dueFrom: cmdOpts.dueFrom,
|
|
1014
|
+
dueTo: cmdOpts.dueTo,
|
|
1015
|
+
search: cmdOpts.search
|
|
1016
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
1017
|
+
}
|
|
1018
|
+
);
|
|
1019
|
+
scheduled.command("get <id>").description("Fetch a scheduled charge with its event timeline and linked transactions").action(async (id) => {
|
|
1020
|
+
const base = toCommandOptions(program);
|
|
1021
|
+
await scheduledChargesGetCommand({ ...base, id }).catch(
|
|
1022
|
+
(err) => printErrorAndExit(err, base)
|
|
1023
|
+
);
|
|
1024
|
+
});
|
|
1025
|
+
scheduled.command("charge-now <id>").description(
|
|
1026
|
+
"Dispatch a scheduled charge now (the charge + notification the daily cron would send on the due date), instead of waiting for dueDate. Idempotent: an already-dispatched cycle reports `already_sent` and is NOT re-charged. Prints the result message; exits non-zero on outcome failed/not_sent (and on 4xx)."
|
|
1027
|
+
).action(async (id) => {
|
|
1028
|
+
const base = toCommandOptions(program);
|
|
1029
|
+
await scheduledChargesChargeNowCommand({ ...base, id }).catch(
|
|
1030
|
+
(err) => printErrorAndExit(err, base)
|
|
1031
|
+
);
|
|
1032
|
+
});
|
|
1033
|
+
scheduled.command("postpone <id>").description("Move a scheduled charge to a new due date").requiredOption("--new-due-date <yyyy-mm-dd>", "new due date (today or future)").option("--reason <text>", "optional reason recorded in the timeline").action(async (id, cmdOpts) => {
|
|
1034
|
+
const base = toCommandOptions(program);
|
|
1035
|
+
await scheduledChargesPostponeCommand({
|
|
1036
|
+
...base,
|
|
1037
|
+
id,
|
|
1038
|
+
newDueDate: cmdOpts.newDueDate,
|
|
1039
|
+
reason: cmdOpts.reason
|
|
1040
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
1041
|
+
});
|
|
1042
|
+
scheduled.command("pause <id>").description("Pause a scheduled charge (no reminders fire while paused)").option("--reason <text>", "optional reason recorded in the timeline").action(async (id, cmdOpts) => {
|
|
1043
|
+
const base = toCommandOptions(program);
|
|
1044
|
+
await scheduledChargesPauseCommand({ ...base, id, reason: cmdOpts.reason }).catch(
|
|
1045
|
+
(err) => printErrorAndExit(err, base)
|
|
1046
|
+
);
|
|
1047
|
+
});
|
|
1048
|
+
scheduled.command("resume <id>").description("Resume a paused scheduled charge").action(async (id) => {
|
|
1049
|
+
const base = toCommandOptions(program);
|
|
1050
|
+
await scheduledChargesResumeCommand({ ...base, id }).catch(
|
|
1051
|
+
(err) => printErrorAndExit(err, base)
|
|
1052
|
+
);
|
|
1053
|
+
});
|
|
1054
|
+
scheduled.command("mark-paid <id>").description("Mark a scheduled charge as paid out-of-band (bank transfer, cash, etc.)").requiredOption("--payment-date <yyyy-mm-dd>", "date the payment was received (today or past)").option("--external-reference <ref>", "bank reference or internal id for reconciliation").option(
|
|
1055
|
+
"--cycle-number <n>",
|
|
1056
|
+
"cycle to mark paid (REQUIRED for recurring)",
|
|
1057
|
+
(v) => parsePositiveIntId(v, "--cycle-number")
|
|
1058
|
+
).action(
|
|
1059
|
+
async (id, cmdOpts) => {
|
|
1060
|
+
const base = toCommandOptions(program);
|
|
1061
|
+
await scheduledChargesMarkPaidCommand({
|
|
1062
|
+
...base,
|
|
1063
|
+
id,
|
|
1064
|
+
paymentDate: cmdOpts.paymentDate,
|
|
1065
|
+
externalReference: cmdOpts.externalReference,
|
|
1066
|
+
cycleNumber: cmdOpts.cycleNumber
|
|
1067
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
1068
|
+
}
|
|
1069
|
+
);
|
|
1070
|
+
scheduled.command("cancel-recurrence <id>").description("Stop future cycles of a recurring series (recurring-only)").option("--reason <text>", "optional reason recorded in the timeline").action(async (id, cmdOpts) => {
|
|
1071
|
+
const base = toCommandOptions(program);
|
|
1072
|
+
await scheduledChargesCancelRecurrenceCommand({
|
|
1073
|
+
...base,
|
|
1074
|
+
id,
|
|
1075
|
+
reason: cmdOpts.reason
|
|
1076
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
1077
|
+
});
|
|
1078
|
+
scheduled.command("cancel-at-period-end <id>").description("Toggle Stripe-style soft cancel on a recurring series (recurring-only)").option("--disable", "clear the flag instead of setting it").action(async (id, cmdOpts) => {
|
|
1079
|
+
const base = toCommandOptions(program);
|
|
1080
|
+
await scheduledChargesCancelAtPeriodEndCommand({
|
|
1081
|
+
...base,
|
|
1082
|
+
id,
|
|
1083
|
+
enabled: !cmdOpts.disable
|
|
1084
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
1085
|
+
});
|
|
1086
|
+
scheduled.command("change-payment-method <id>").description("Swap the saved card on a recurring series (recurring-only)").requiredOption(
|
|
1087
|
+
"--payment-method-id <n>",
|
|
1088
|
+
"PaymentMethod id (same customer)",
|
|
1089
|
+
(v) => parsePositiveIntId(v, "--payment-method-id")
|
|
1090
|
+
).action(async (id, cmdOpts) => {
|
|
1091
|
+
const base = toCommandOptions(program);
|
|
1092
|
+
await scheduledChargesChangePaymentMethodCommand({
|
|
1093
|
+
...base,
|
|
1094
|
+
id,
|
|
1095
|
+
paymentMethodId: cmdOpts.paymentMethodId
|
|
1096
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
1097
|
+
});
|
|
1098
|
+
scheduled.command("clear-payment-method <id>").description(
|
|
1099
|
+
"Clear the saved card; future cycles fall back to email-with-link (recurring-only)"
|
|
1100
|
+
).action(async (id) => {
|
|
1101
|
+
const base = toCommandOptions(program);
|
|
1102
|
+
await scheduledChargesClearPaymentMethodCommand({ ...base, id }).catch(
|
|
1103
|
+
(err) => printErrorAndExit(err, base)
|
|
1104
|
+
);
|
|
1105
|
+
});
|
|
1106
|
+
scheduled.command("attempts <id>").description("List the per-attempt billing log for a scheduled charge").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option(
|
|
1107
|
+
"--cycle-number <n>",
|
|
1108
|
+
"filter to a single cycle",
|
|
1109
|
+
(v) => parsePositiveIntId(v, "--cycle-number")
|
|
1110
|
+
).action(
|
|
1111
|
+
async (id, cmdOpts) => {
|
|
1112
|
+
const base = toCommandOptions(program);
|
|
1113
|
+
await scheduledChargesAttemptsCommand({
|
|
1114
|
+
...base,
|
|
1115
|
+
id,
|
|
1116
|
+
page: cmdOpts.page,
|
|
1117
|
+
limit: cmdOpts.limit,
|
|
1118
|
+
cycleNumber: cmdOpts.cycleNumber
|
|
1119
|
+
}).catch((err) => printErrorAndExit(err, base));
|
|
1120
|
+
}
|
|
1121
|
+
);
|
|
657
1122
|
const webhooks = program.command("webhooks").description("Inspect and replay webhook deliveries");
|
|
658
1123
|
const events = webhooks.command("events").description("List, inspect, and resend webhook events");
|
|
659
1124
|
events.command("list").description("List webhook events with filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option("--status <status>", "filter: pending, success, or failed").option("--event-type <type>", "filter by Garu event type, e.g. transaction.payment.paid").option(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@garuhq/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Official command-line interface for the Garu payment gateway.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://garu.com.br",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"prepublishOnly": "npm run check:version-sync && npm run typecheck && npm test && npm run build"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@garuhq/node": "0.
|
|
49
|
+
"@garuhq/node": "0.13.0",
|
|
50
50
|
"@inquirer/prompts": "8.4.1",
|
|
51
51
|
"commander": "12.0.0",
|
|
52
52
|
"picocolors": "1.0.0",
|