@garuhq/node 3.0.0 → 4.0.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 CHANGED
@@ -3,6 +3,54 @@
3
3
  All notable changes to `@garuhq/node` 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
+ ## [4.0.0] — 2026-08-22
7
+
8
+ **Breaking:** `scheduledCharges` now targets the versioned public API
9
+ `/api/v1/scheduled-charges`. `id` was already a stable, non-enumerable
10
+ string (`sch_...`) — no identifier change — but the list envelope shape
11
+ changed. If you use `garu.scheduledCharges.*`, read the migration below.
12
+
13
+ ### Breaking
14
+
15
+ - **`scheduledCharges` moved to `/api/v1/scheduled-charges`.**
16
+ - **`scheduledCharges.list()` and `.listAttempts()` now return
17
+ `{ data, count, totalCount, totalPages }`** (was `{ data, meta }`).
18
+ - No method signatures changed — every method already took/returned the
19
+ same shapes, since `id` was never a numeric internal id here.
20
+
21
+ ### Added
22
+
23
+ - `ScheduledChargeRecord` now explicitly types `recurrence`,
24
+ `cancelAtPeriodEnd`, `trialEndsAt`, and `subscriptionId` (previously only
25
+ reachable via the type's index signature, untyped).
26
+ - Test coverage for `cancelRecurrence`, `setCancelAtPeriodEnd`,
27
+ `changePaymentMethod`, `clearPaymentMethod`, and `listAttempts` — none of
28
+ these five methods had a single test before this release.
29
+
30
+ ### Fixed
31
+
32
+ - `ScheduledChargeLinkedTransaction.value`'s docstring claimed centavos;
33
+ `/api/v1/charges`' own mapper treats the same `transaction.value` column
34
+ as decimal reais with no conversion. Corrected to match reality — this is
35
+ a documentation fix, not a behavior or wire-format change.
36
+ - `scheduledCharges.create()`'s docstring claimed the SDK's
37
+ `X-Idempotency-Key` prevents duplicate creates on retry. The gateway does
38
+ not deduplicate `/scheduled-charges` creates against it (same pre-existing
39
+ gap as `webhookEvents.resend()` had). Corrected the docstring; the header
40
+ is still sent (harmless) in case the gateway adds this later.
41
+
42
+ ### Migration
43
+
44
+ ```ts
45
+ // before (≤ 3.x)
46
+ const { data, meta } = await garu.scheduledCharges.list({ status: 'overdue' });
47
+ meta.total; // number
48
+
49
+ // after (4.0.0)
50
+ const { data, totalCount } = await garu.scheduledCharges.list({ status: 'overdue' });
51
+ totalCount; // number
52
+ ```
53
+
6
54
  ## [3.0.0] — 2026-08-22
7
55
 
8
56
  **Breaking:** `webhookEvents` now targets the versioned public API
@@ -34,16 +82,16 @@ read the migration below.
34
82
  // before (0.x – 2.x)
35
83
  const failed = await garu.webhookEvents.list({ status: 'failed', limit: 5 });
36
84
  const event = failed.data[0];
37
- event.id; // number
85
+ event.id; // number
38
86
  const clone = await garu.webhookEvents.resend(event.id);
39
- clone.manualResendOf === event.id; // true
87
+ clone.manualResendOf === event.id; // true
40
88
 
41
89
  // after (3.0.0)
42
90
  const failed = await garu.webhookEvents.list({ status: 'failed', limit: 5 });
43
91
  const event = failed.data[0];
44
- event.uuid; // string
92
+ event.uuid; // string
45
93
  const clone = await garu.webhookEvents.resend(event.uuid);
46
- clone.manualResendOf === event.uuid; // true
94
+ clone.manualResendOf === event.uuid; // true
47
95
  ```
48
96
 
49
97
  ## [2.0.0] — 2026-08-22
@@ -78,14 +126,14 @@ keyed on `uuid`. If you use `garu.customers.*`, read the migration below.
78
126
  ```ts
79
127
  // before (1.x)
80
128
  const c = await garu.customers.create({ name, email, document, phone, personType });
81
- c.id; // number
129
+ c.id; // number
82
130
  const one = await garu.customers.get(c.id);
83
131
  await garu.customers.update(c.id, { name: 'Maria Santos' });
84
132
  await garu.customers.delete(c.id);
85
133
 
86
134
  // after (2.0.0)
87
135
  const c = await garu.customers.create({ name, email, document, phone, personType });
88
- c.uuid; // string
136
+ c.uuid; // string
89
137
  const one = await garu.customers.get(c.uuid);
90
138
  await garu.customers.update(c.uuid, { name: 'Maria Santos' });
91
139
  const { removed } = await garu.customers.delete(c.uuid);
@@ -93,7 +141,6 @@ const { removed } = await garu.customers.delete(c.uuid);
93
141
 
94
142
  ## [1.1.0] — 2026-08-15
95
143
 
96
-
97
144
  ### Added
98
145
 
99
146
  - **`garu.installmentPlans` — boleto parcelado (carnê).** One product sold as N
@@ -170,22 +217,26 @@ webhook-events) changed.
170
217
  ```ts
171
218
  // before (0.16.x)
172
219
  const c = await garu.charges.create({
173
- productId, paymentMethod: 'credit_card', customer,
220
+ productId,
221
+ paymentMethod: 'credit_card',
222
+ customer,
174
223
  cardInfo: { cardNumber: '4111…', cvv, expirationDate, holderName, installments: 2 }
175
224
  });
176
- c.id; // number
177
- c.paymentMethodId; // 'creditcard'
225
+ c.id; // number
226
+ c.paymentMethodId; // 'creditcard'
178
227
  const one = await garu.charges.get(c.id);
179
228
  await garu.charges.refund(c.id, { amount: 1000 }); // "R$10,00" (bug: reais)
180
229
 
181
230
  // after (1.0.0)
182
231
  const c = await garu.charges.create({
183
- productId, paymentMethod: 'creditCard', customer,
232
+ productId,
233
+ paymentMethod: 'creditCard',
234
+ customer,
184
235
  card: { number: '4111…', cvv, expirationDate, holderName, installments: 2 }
185
236
  });
186
- c.uuid; // string
187
- c.paymentMethod; // 'creditCard'
188
- c.chargedTotal; // what was actually charged
237
+ c.uuid; // string
238
+ c.paymentMethod; // 'creditCard'
239
+ c.chargedTotal; // what was actually charged
189
240
  const one = await garu.charges.retrieve(c.uuid);
190
241
  await garu.charges.refund(c.uuid, { amount: 10.0 }); // R$10,00
191
242
  ```
package/README.md CHANGED
@@ -147,10 +147,13 @@ await garu.charges.refund('6f1c9b2e-…', { amount: 10.0 }); // partial refund (
147
147
  ## Customers
148
148
 
149
149
  Backed by `/api/v1/customers`, keyed on `uuid` — there is no numeric id in
150
- this shape. `installmentPlans.create` and `scheduledCharges.create` still
151
- link customers by the internal numeric id (unmigrated resources); fetch that
152
- id from the dashboard or the internal `/api/customers` endpoint until they
153
- move to `/api/v1` too.
150
+ this shape. `installmentPlans.create` still links customers by the internal
151
+ numeric id (unmigrated resource); fetch that id from the dashboard or the
152
+ internal `/api/customers` endpoint until it moves to `/api/v1` too.
153
+ `scheduledCharges.create` also takes the internal numeric `customerId`
154
+ customers and scheduled charges are on separate `/api/v1` resources, and
155
+ neither exposes a cross-reference between a customer's `uuid` and their
156
+ numeric id yet.
154
157
 
155
158
  | Method | Description |
156
159
  | --------------------------------------- | ----------------------------------------------- |
@@ -206,20 +209,25 @@ await garu.products.portalConfig.patch('b3f2c1e8-6e4a-4b9f-9d1c-2a1f6c3d4e5f', {
206
209
 
207
210
  Bill an existing customer on a future date — one-time or recurring with card tokenization. The Garu drives email reminders, dunning, retries, and the lifecycle state machine.
208
211
 
209
- | Method | Description |
210
- | ------------------------------------ | --------------------------------------------------------------------------- |
211
- | `create(params)` | Create one-time or recurring schedule. Auto-attaches `X-Idempotency-Key`. |
212
- | `list(params?)` | Paginated list with status / type / dueFrom / dueTo / customerId filters. |
213
- | `get(id)` | Detail bundle: charge + event timeline + linked transactions. |
214
- | `chargeNow(id)` | Force-bill the current cycle now instead of waiting for the due date. |
215
- | `markPaid(id, params)` | Mark cycle paid (off-Garu reconciliation). |
216
- | `postpone(id, params)` | Move the next cycle's due date forward. |
217
- | `pause(id, params?)` / `resume(id)` | Suspend / re-enable a series. |
218
- | `cancelRecurrence(id, params?)` | Hard-stop future cycles (recurring only). |
219
- | `cancelAtPeriodEnd(id, { enabled })` | Stripe-style soft-cancel; reversible. |
220
- | `changePaymentMethod(id, params)` | Swap the saved card. |
221
- | `clearPaymentMethod(id)` | Remove the saved card; future cycles email-with-link. |
222
- | `listAttempts(id, params?)` | Per-attempt billing log every silent-charge / retry / mark-paid (v0.8.2). |
212
+ Backed by `/api/v1/scheduled-charges`. Unlike products/customers/webhook-events,
213
+ `id` here was already a stable, non-enumerable string (`sch_...`) before this
214
+ move there is no separate uuid and no id change, only the path and the list
215
+ envelope (`totalCount`/`totalPages`, not `meta.total`/`meta.totalPages`).
216
+
217
+ | Method | Description |
218
+ | --------------------------------------- | ------------------------------------------------------------------------- |
219
+ | `create(params)` | Create one-time or recurring schedule. |
220
+ | `list(params?)` | Paginated list with status / type / dueFrom / dueTo / customerId filters. |
221
+ | `get(id)` | Detail bundle: charge + event timeline + linked transactions. |
222
+ | `chargeNow(id)` | Force-bill the current cycle now instead of waiting for the due date. |
223
+ | `markPaid(id, params)` | Mark cycle paid (off-Garu reconciliation). |
224
+ | `postpone(id, params)` | Move the next cycle's due date forward. |
225
+ | `pause(id, params?)` / `resume(id)` | Suspend / re-enable a series. |
226
+ | `cancelRecurrence(id, params?)` | Hard-stop future cycles (recurring only). |
227
+ | `setCancelAtPeriodEnd(id, { enabled })` | Stripe-style soft-cancel; reversible. |
228
+ | `changePaymentMethod(id, params)` | Swap the saved card. |
229
+ | `clearPaymentMethod(id)` | Remove the saved card; future cycles email-with-link. |
230
+ | `listAttempts(id, params?)` | Per-attempt billing log — every silent-charge / retry / mark-paid. |
223
231
 
224
232
  ```ts
225
233
  // Recurring with 7-day trial. `maxRecoveryDays` caps how long past the due
@@ -240,11 +248,10 @@ const series = await garu.scheduledCharges.create({
240
248
  // Idempotent: a cycle already dispatched today reports `already_sent`.
241
249
  const result = await garu.scheduledCharges.chargeNow(series.id);
242
250
  if (result.outcome === 'failed') {
243
- // result.reason is e.g. 'card_expired' or a gateway decline code
244
- console.error(`${result.message} (${result.reason})`);
251
+ result.reason; // e.g. 'card_expired' or a gateway decline code
245
252
  }
246
253
 
247
- // Audit why cycle 3 failed (v0.8.2)
254
+ // Audit why cycle 3 failed
248
255
  const { data } = await garu.scheduledCharges.listAttempts(series.id, {
249
256
  cycleNumber: 3
250
257
  });
@@ -308,7 +315,7 @@ const series = await garu.scheduledCharges.create({
308
315
  });
309
316
  ```
310
317
 
311
- Cancel and the rest of the lifecycle use the **same methods** as card-backed series — `cancelRecurrence(id)`, `cancelAtPeriodEnd(id, { enabled })`, `pause(id)` / `resume(id)`. The customer can also revoke the authorization directly in their bank app; Garu surfaces that as a `subscription.cancelled` event.
318
+ Cancel and the rest of the lifecycle use the **same methods** as card-backed series — `cancelRecurrence(id)`, `setCancelAtPeriodEnd(id, { enabled })`, `pause(id)` / `resume(id)`. The customer can also revoke the authorization directly in their bank app; Garu surfaces that as a `subscription.cancelled` event.
312
319
 
313
320
  ### 3. Handle the webhooks
314
321
 
package/dist/index.cjs CHANGED
@@ -942,9 +942,13 @@ var ScheduledCharges = class {
942
942
  }
943
943
  http;
944
944
  /**
945
- * Create a new scheduled charge. Auto-attaches `X-Idempotency-Key`
946
- * (UUIDv4 if you don't pass `idempotencyKey`) so retries on transient
947
- * network failures don't silently double-create.
945
+ * Create a new scheduled charge. The SDK attaches an `X-Idempotency-Key`
946
+ * header (UUIDv4 unless you pass `idempotencyKey`), but the gateway does
947
+ * not currently deduplicate `/scheduled-charges` creates against it — a
948
+ * retry after a network failure can create more than one series. Pair
949
+ * this with your own retry-suppression (e.g. check `list` for an existing
950
+ * series with the same `externalReference`) if that matters for your
951
+ * integration.
948
952
  *
949
953
  * @example
950
954
  * const charge = await garu.scheduledCharges.create({
@@ -974,7 +978,7 @@ var ScheduledCharges = class {
974
978
  const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
975
979
  const { idempotencyKey: _omit, ...body } = params;
976
980
  return this.http.call(
977
- (signal) => this.http.client.POST("/api/scheduled-charges", {
981
+ (signal) => this.http.client.POST("/api/v1/scheduled-charges", {
978
982
  body,
979
983
  headers: { "X-Idempotency-Key": idempotencyKey },
980
984
  signal
@@ -1009,7 +1013,7 @@ var ScheduledCharges = class {
1009
1013
  for (const s of statuses) qs.append("status", s);
1010
1014
  }
1011
1015
  const query = qs.toString();
1012
- const url = `/api/scheduled-charges${query ? `?${query}` : ""}`;
1016
+ const url = `/api/v1/scheduled-charges${query ? `?${query}` : ""}`;
1013
1017
  return this.http.call(
1014
1018
  (signal) => this.http.client.GET(url, { signal }).then(
1015
1019
  (r) => r
@@ -1026,7 +1030,7 @@ var ScheduledCharges = class {
1026
1030
  */
1027
1031
  async get(id) {
1028
1032
  return this.http.call(
1029
- (signal) => this.http.client.GET(`/api/scheduled-charges/${encodeURIComponent(id)}`, {
1033
+ (signal) => this.http.client.GET(`/api/v1/scheduled-charges/${encodeURIComponent(id)}`, {
1030
1034
  signal
1031
1035
  }).then((r) => r)
1032
1036
  );
@@ -1045,7 +1049,7 @@ var ScheduledCharges = class {
1045
1049
  async postpone(id, params) {
1046
1050
  return this.http.call(
1047
1051
  (signal) => this.http.client.POST(
1048
- `/api/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1052
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1049
1053
  {
1050
1054
  body: params,
1051
1055
  signal
@@ -1064,7 +1068,7 @@ var ScheduledCharges = class {
1064
1068
  async pause(id, params = {}) {
1065
1069
  return this.http.call(
1066
1070
  (signal) => this.http.client.POST(
1067
- `/api/scheduled-charges/${encodeURIComponent(id)}/pause`,
1071
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/pause`,
1068
1072
  {
1069
1073
  body: params,
1070
1074
  signal
@@ -1081,7 +1085,7 @@ var ScheduledCharges = class {
1081
1085
  async resume(id) {
1082
1086
  return this.http.call(
1083
1087
  (signal) => this.http.client.POST(
1084
- `/api/scheduled-charges/${encodeURIComponent(id)}/resume`,
1088
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/resume`,
1085
1089
  {
1086
1090
  body: {},
1087
1091
  signal
@@ -1115,7 +1119,7 @@ var ScheduledCharges = class {
1115
1119
  async markPaid(id, params) {
1116
1120
  return this.http.call(
1117
1121
  (signal) => this.http.client.POST(
1118
- `/api/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1122
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1119
1123
  {
1120
1124
  body: params,
1121
1125
  signal
@@ -1137,24 +1141,22 @@ var ScheduledCharges = class {
1137
1141
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1138
1142
  * switch (result.outcome) {
1139
1143
  * case 'dispatched':
1140
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1144
+ * result.cycleNumber; // billed this cycle
1141
1145
  * break;
1142
1146
  * case 'already_sent':
1143
- * console.log('Já havia sido enviada — nada a fazer.');
1144
- * break;
1147
+ * break; // nothing to do
1145
1148
  * case 'failed':
1146
- * // result.reason is e.g. 'card_expired' or a gateway decline code
1147
- * console.error(`Falha na cobrança: ${result.reason}. ${result.message}`);
1149
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1148
1150
  * break;
1149
1151
  * case 'not_sent':
1150
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1152
+ * result.reason;
1151
1153
  * break;
1152
1154
  * }
1153
1155
  */
1154
1156
  async chargeNow(id) {
1155
1157
  return this.http.call(
1156
1158
  (signal) => this.http.client.POST(
1157
- `/api/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1159
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1158
1160
  {
1159
1161
  body: {},
1160
1162
  signal
@@ -1176,7 +1178,7 @@ var ScheduledCharges = class {
1176
1178
  async cancelRecurrence(id, params = {}) {
1177
1179
  return this.http.call(
1178
1180
  (signal) => this.http.client.POST(
1179
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1181
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1180
1182
  {
1181
1183
  body: params,
1182
1184
  signal
@@ -1196,7 +1198,7 @@ var ScheduledCharges = class {
1196
1198
  async setCancelAtPeriodEnd(id, params) {
1197
1199
  return this.http.call(
1198
1200
  (signal) => this.http.client.POST(
1199
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1201
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1200
1202
  {
1201
1203
  body: params,
1202
1204
  signal
@@ -1215,7 +1217,7 @@ var ScheduledCharges = class {
1215
1217
  async changePaymentMethod(id, params) {
1216
1218
  return this.http.call(
1217
1219
  (signal) => this.http.client.POST(
1218
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1220
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1219
1221
  {
1220
1222
  body: params,
1221
1223
  signal
@@ -1234,7 +1236,7 @@ var ScheduledCharges = class {
1234
1236
  async clearPaymentMethod(id) {
1235
1237
  return this.http.call(
1236
1238
  (signal) => this.http.client.DELETE(
1237
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1239
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1238
1240
  {
1239
1241
  body: {},
1240
1242
  signal
@@ -1261,7 +1263,7 @@ var ScheduledCharges = class {
1261
1263
  if (params.limit !== void 0) qs.set("limit", String(params.limit));
1262
1264
  if (params.cycleNumber !== void 0) qs.set("cycleNumber", String(params.cycleNumber));
1263
1265
  const query = qs.toString();
1264
- const url = `/api/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1266
+ const url = `/api/v1/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1265
1267
  return this.http.call(
1266
1268
  (signal) => this.http.client.GET(url, { signal }).then(
1267
1269
  (r) => r
package/dist/index.d.cts CHANGED
@@ -390,7 +390,13 @@ interface ScheduledChargeRecord {
390
390
  /** YYYY-MM-DD in São Paulo time. */
391
391
  dueDate: string;
392
392
  methods: ScheduledPaymentMethod[];
393
+ recurrence: RecurrenceConfig | null;
393
394
  status: ScheduledChargeStatus;
395
+ subscriptionId: number | null;
396
+ /** ISO-8601. Set only when the series was created with `trialDays`. */
397
+ trialEndsAt: string | null;
398
+ /** Recurring only. Toggle with `setCancelAtPeriodEnd`. */
399
+ cancelAtPeriodEnd: boolean;
394
400
  externalReference: string | null;
395
401
  /**
396
402
  * Max days past `dueDate` the daily recovery sweep will still auto-bill a
@@ -425,7 +431,7 @@ interface ScheduledChargeEvent {
425
431
  }
426
432
  interface ScheduledChargeLinkedTransaction {
427
433
  id: number;
428
- /** Centavos (BRL × 100), matching `garu.charges.*` value semantics. */
434
+ /** Decimal BRL (e.g. `297.50`), never centavos. */
429
435
  value: number;
430
436
  paymentMethod: string;
431
437
  status: string;
@@ -438,7 +444,14 @@ interface ScheduledChargeDetail {
438
444
  events: ScheduledChargeEvent[];
439
445
  transactions: ScheduledChargeLinkedTransaction[];
440
446
  }
441
- type ScheduledChargeList = PaginatedList<ScheduledChargeRecord>;
447
+ interface ScheduledChargeList {
448
+ data: ScheduledChargeRecord[];
449
+ /** Items on this page. */
450
+ count: number;
451
+ /** Total matches across all pages. */
452
+ totalCount: number;
453
+ totalPages: number;
454
+ }
442
455
  /** Source of a billing attempt — see SPEC §3.1. */
443
456
  type ScheduledChargeAttemptSource = 'cycle1_interactive' | 'silent_charge' | 'card_retry' | 'manual_mark_paid' | 'fallback_pix';
444
457
  type ScheduledChargeAttemptStatus = 'pending' | 'succeeded' | 'declined' | 'canceled' | 'errored';
@@ -460,7 +473,14 @@ interface ScheduledChargeAttempt {
460
473
  gatewayChargeId: number | null;
461
474
  transactionId: number | null;
462
475
  }
463
- type ScheduledChargeAttemptList = PaginatedList<ScheduledChargeAttempt>;
476
+ interface ScheduledChargeAttemptList {
477
+ data: ScheduledChargeAttempt[];
478
+ /** Items on this page. */
479
+ count: number;
480
+ /** Total matches across all pages. */
481
+ totalCount: number;
482
+ totalPages: number;
483
+ }
464
484
  interface ListScheduledChargeAttemptsParams {
465
485
  page?: number;
466
486
  limit?: number;
@@ -1573,9 +1593,13 @@ declare class ScheduledCharges {
1573
1593
  private readonly http;
1574
1594
  constructor(http: HttpClient);
1575
1595
  /**
1576
- * Create a new scheduled charge. Auto-attaches `X-Idempotency-Key`
1577
- * (UUIDv4 if you don't pass `idempotencyKey`) so retries on transient
1578
- * network failures don't silently double-create.
1596
+ * Create a new scheduled charge. The SDK attaches an `X-Idempotency-Key`
1597
+ * header (UUIDv4 unless you pass `idempotencyKey`), but the gateway does
1598
+ * not currently deduplicate `/scheduled-charges` creates against it — a
1599
+ * retry after a network failure can create more than one series. Pair
1600
+ * this with your own retry-suppression (e.g. check `list` for an existing
1601
+ * series with the same `externalReference`) if that matters for your
1602
+ * integration.
1579
1603
  *
1580
1604
  * @example
1581
1605
  * const charge = await garu.scheduledCharges.create({
@@ -1692,17 +1716,15 @@ declare class ScheduledCharges {
1692
1716
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1693
1717
  * switch (result.outcome) {
1694
1718
  * case 'dispatched':
1695
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1719
+ * result.cycleNumber; // billed this cycle
1696
1720
  * break;
1697
1721
  * case 'already_sent':
1698
- * console.log('Já havia sido enviada — nada a fazer.');
1699
- * break;
1722
+ * break; // nothing to do
1700
1723
  * case 'failed':
1701
- * // result.reason is e.g. 'card_expired' or a gateway decline code
1702
- * console.error(`Falha na cobrança: ${result.reason}. ${result.message}`);
1724
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1703
1725
  * break;
1704
1726
  * case 'not_sent':
1705
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1727
+ * result.reason;
1706
1728
  * break;
1707
1729
  * }
1708
1730
  */
package/dist/index.d.ts CHANGED
@@ -390,7 +390,13 @@ interface ScheduledChargeRecord {
390
390
  /** YYYY-MM-DD in São Paulo time. */
391
391
  dueDate: string;
392
392
  methods: ScheduledPaymentMethod[];
393
+ recurrence: RecurrenceConfig | null;
393
394
  status: ScheduledChargeStatus;
395
+ subscriptionId: number | null;
396
+ /** ISO-8601. Set only when the series was created with `trialDays`. */
397
+ trialEndsAt: string | null;
398
+ /** Recurring only. Toggle with `setCancelAtPeriodEnd`. */
399
+ cancelAtPeriodEnd: boolean;
394
400
  externalReference: string | null;
395
401
  /**
396
402
  * Max days past `dueDate` the daily recovery sweep will still auto-bill a
@@ -425,7 +431,7 @@ interface ScheduledChargeEvent {
425
431
  }
426
432
  interface ScheduledChargeLinkedTransaction {
427
433
  id: number;
428
- /** Centavos (BRL × 100), matching `garu.charges.*` value semantics. */
434
+ /** Decimal BRL (e.g. `297.50`), never centavos. */
429
435
  value: number;
430
436
  paymentMethod: string;
431
437
  status: string;
@@ -438,7 +444,14 @@ interface ScheduledChargeDetail {
438
444
  events: ScheduledChargeEvent[];
439
445
  transactions: ScheduledChargeLinkedTransaction[];
440
446
  }
441
- type ScheduledChargeList = PaginatedList<ScheduledChargeRecord>;
447
+ interface ScheduledChargeList {
448
+ data: ScheduledChargeRecord[];
449
+ /** Items on this page. */
450
+ count: number;
451
+ /** Total matches across all pages. */
452
+ totalCount: number;
453
+ totalPages: number;
454
+ }
442
455
  /** Source of a billing attempt — see SPEC §3.1. */
443
456
  type ScheduledChargeAttemptSource = 'cycle1_interactive' | 'silent_charge' | 'card_retry' | 'manual_mark_paid' | 'fallback_pix';
444
457
  type ScheduledChargeAttemptStatus = 'pending' | 'succeeded' | 'declined' | 'canceled' | 'errored';
@@ -460,7 +473,14 @@ interface ScheduledChargeAttempt {
460
473
  gatewayChargeId: number | null;
461
474
  transactionId: number | null;
462
475
  }
463
- type ScheduledChargeAttemptList = PaginatedList<ScheduledChargeAttempt>;
476
+ interface ScheduledChargeAttemptList {
477
+ data: ScheduledChargeAttempt[];
478
+ /** Items on this page. */
479
+ count: number;
480
+ /** Total matches across all pages. */
481
+ totalCount: number;
482
+ totalPages: number;
483
+ }
464
484
  interface ListScheduledChargeAttemptsParams {
465
485
  page?: number;
466
486
  limit?: number;
@@ -1573,9 +1593,13 @@ declare class ScheduledCharges {
1573
1593
  private readonly http;
1574
1594
  constructor(http: HttpClient);
1575
1595
  /**
1576
- * Create a new scheduled charge. Auto-attaches `X-Idempotency-Key`
1577
- * (UUIDv4 if you don't pass `idempotencyKey`) so retries on transient
1578
- * network failures don't silently double-create.
1596
+ * Create a new scheduled charge. The SDK attaches an `X-Idempotency-Key`
1597
+ * header (UUIDv4 unless you pass `idempotencyKey`), but the gateway does
1598
+ * not currently deduplicate `/scheduled-charges` creates against it — a
1599
+ * retry after a network failure can create more than one series. Pair
1600
+ * this with your own retry-suppression (e.g. check `list` for an existing
1601
+ * series with the same `externalReference`) if that matters for your
1602
+ * integration.
1579
1603
  *
1580
1604
  * @example
1581
1605
  * const charge = await garu.scheduledCharges.create({
@@ -1692,17 +1716,15 @@ declare class ScheduledCharges {
1692
1716
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1693
1717
  * switch (result.outcome) {
1694
1718
  * case 'dispatched':
1695
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1719
+ * result.cycleNumber; // billed this cycle
1696
1720
  * break;
1697
1721
  * case 'already_sent':
1698
- * console.log('Já havia sido enviada — nada a fazer.');
1699
- * break;
1722
+ * break; // nothing to do
1700
1723
  * case 'failed':
1701
- * // result.reason is e.g. 'card_expired' or a gateway decline code
1702
- * console.error(`Falha na cobrança: ${result.reason}. ${result.message}`);
1724
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1703
1725
  * break;
1704
1726
  * case 'not_sent':
1705
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1727
+ * result.reason;
1706
1728
  * break;
1707
1729
  * }
1708
1730
  */
package/dist/index.js CHANGED
@@ -936,9 +936,13 @@ var ScheduledCharges = class {
936
936
  }
937
937
  http;
938
938
  /**
939
- * Create a new scheduled charge. Auto-attaches `X-Idempotency-Key`
940
- * (UUIDv4 if you don't pass `idempotencyKey`) so retries on transient
941
- * network failures don't silently double-create.
939
+ * Create a new scheduled charge. The SDK attaches an `X-Idempotency-Key`
940
+ * header (UUIDv4 unless you pass `idempotencyKey`), but the gateway does
941
+ * not currently deduplicate `/scheduled-charges` creates against it — a
942
+ * retry after a network failure can create more than one series. Pair
943
+ * this with your own retry-suppression (e.g. check `list` for an existing
944
+ * series with the same `externalReference`) if that matters for your
945
+ * integration.
942
946
  *
943
947
  * @example
944
948
  * const charge = await garu.scheduledCharges.create({
@@ -968,7 +972,7 @@ var ScheduledCharges = class {
968
972
  const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
969
973
  const { idempotencyKey: _omit, ...body } = params;
970
974
  return this.http.call(
971
- (signal) => this.http.client.POST("/api/scheduled-charges", {
975
+ (signal) => this.http.client.POST("/api/v1/scheduled-charges", {
972
976
  body,
973
977
  headers: { "X-Idempotency-Key": idempotencyKey },
974
978
  signal
@@ -1003,7 +1007,7 @@ var ScheduledCharges = class {
1003
1007
  for (const s of statuses) qs.append("status", s);
1004
1008
  }
1005
1009
  const query = qs.toString();
1006
- const url = `/api/scheduled-charges${query ? `?${query}` : ""}`;
1010
+ const url = `/api/v1/scheduled-charges${query ? `?${query}` : ""}`;
1007
1011
  return this.http.call(
1008
1012
  (signal) => this.http.client.GET(url, { signal }).then(
1009
1013
  (r) => r
@@ -1020,7 +1024,7 @@ var ScheduledCharges = class {
1020
1024
  */
1021
1025
  async get(id) {
1022
1026
  return this.http.call(
1023
- (signal) => this.http.client.GET(`/api/scheduled-charges/${encodeURIComponent(id)}`, {
1027
+ (signal) => this.http.client.GET(`/api/v1/scheduled-charges/${encodeURIComponent(id)}`, {
1024
1028
  signal
1025
1029
  }).then((r) => r)
1026
1030
  );
@@ -1039,7 +1043,7 @@ var ScheduledCharges = class {
1039
1043
  async postpone(id, params) {
1040
1044
  return this.http.call(
1041
1045
  (signal) => this.http.client.POST(
1042
- `/api/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1046
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1043
1047
  {
1044
1048
  body: params,
1045
1049
  signal
@@ -1058,7 +1062,7 @@ var ScheduledCharges = class {
1058
1062
  async pause(id, params = {}) {
1059
1063
  return this.http.call(
1060
1064
  (signal) => this.http.client.POST(
1061
- `/api/scheduled-charges/${encodeURIComponent(id)}/pause`,
1065
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/pause`,
1062
1066
  {
1063
1067
  body: params,
1064
1068
  signal
@@ -1075,7 +1079,7 @@ var ScheduledCharges = class {
1075
1079
  async resume(id) {
1076
1080
  return this.http.call(
1077
1081
  (signal) => this.http.client.POST(
1078
- `/api/scheduled-charges/${encodeURIComponent(id)}/resume`,
1082
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/resume`,
1079
1083
  {
1080
1084
  body: {},
1081
1085
  signal
@@ -1109,7 +1113,7 @@ var ScheduledCharges = class {
1109
1113
  async markPaid(id, params) {
1110
1114
  return this.http.call(
1111
1115
  (signal) => this.http.client.POST(
1112
- `/api/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1116
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1113
1117
  {
1114
1118
  body: params,
1115
1119
  signal
@@ -1131,24 +1135,22 @@ var ScheduledCharges = class {
1131
1135
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1132
1136
  * switch (result.outcome) {
1133
1137
  * case 'dispatched':
1134
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1138
+ * result.cycleNumber; // billed this cycle
1135
1139
  * break;
1136
1140
  * case 'already_sent':
1137
- * console.log('Já havia sido enviada — nada a fazer.');
1138
- * break;
1141
+ * break; // nothing to do
1139
1142
  * case 'failed':
1140
- * // result.reason is e.g. 'card_expired' or a gateway decline code
1141
- * console.error(`Falha na cobrança: ${result.reason}. ${result.message}`);
1143
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1142
1144
  * break;
1143
1145
  * case 'not_sent':
1144
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1146
+ * result.reason;
1145
1147
  * break;
1146
1148
  * }
1147
1149
  */
1148
1150
  async chargeNow(id) {
1149
1151
  return this.http.call(
1150
1152
  (signal) => this.http.client.POST(
1151
- `/api/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1153
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1152
1154
  {
1153
1155
  body: {},
1154
1156
  signal
@@ -1170,7 +1172,7 @@ var ScheduledCharges = class {
1170
1172
  async cancelRecurrence(id, params = {}) {
1171
1173
  return this.http.call(
1172
1174
  (signal) => this.http.client.POST(
1173
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1175
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1174
1176
  {
1175
1177
  body: params,
1176
1178
  signal
@@ -1190,7 +1192,7 @@ var ScheduledCharges = class {
1190
1192
  async setCancelAtPeriodEnd(id, params) {
1191
1193
  return this.http.call(
1192
1194
  (signal) => this.http.client.POST(
1193
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1195
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1194
1196
  {
1195
1197
  body: params,
1196
1198
  signal
@@ -1209,7 +1211,7 @@ var ScheduledCharges = class {
1209
1211
  async changePaymentMethod(id, params) {
1210
1212
  return this.http.call(
1211
1213
  (signal) => this.http.client.POST(
1212
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1214
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1213
1215
  {
1214
1216
  body: params,
1215
1217
  signal
@@ -1228,7 +1230,7 @@ var ScheduledCharges = class {
1228
1230
  async clearPaymentMethod(id) {
1229
1231
  return this.http.call(
1230
1232
  (signal) => this.http.client.DELETE(
1231
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1233
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1232
1234
  {
1233
1235
  body: {},
1234
1236
  signal
@@ -1255,7 +1257,7 @@ var ScheduledCharges = class {
1255
1257
  if (params.limit !== void 0) qs.set("limit", String(params.limit));
1256
1258
  if (params.cycleNumber !== void 0) qs.set("cycleNumber", String(params.cycleNumber));
1257
1259
  const query = qs.toString();
1258
- const url = `/api/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1260
+ const url = `/api/v1/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1259
1261
  return this.http.call(
1260
1262
  (signal) => this.http.client.GET(url, { signal }).then(
1261
1263
  (r) => r
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/node",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "Official Node.js / TypeScript SDK for the Garu payment gateway.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://garu.com.br",