@garuhq/node 3.0.0 → 4.1.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,72 @@
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.1.0] — 2026-08-22
7
+
8
+ ### Added
9
+
10
+ - `customers.create()`, `charges.refund()`, and
11
+ `installmentPlans.requestRefund()` now attach an `X-Idempotency-Key`
12
+ header automatically (UUIDv4 unless you pass `idempotencyKey`). The
13
+ gateway now caches and replays the first response for 24h, so a network
14
+ retry can no longer register a duplicate customer, open a second refund
15
+ request, or (via `scheduledCharges.create()` — see Fixed below)
16
+ double-book recurring billing.
17
+
18
+ ### Fixed
19
+
20
+ - `scheduledCharges.create()`'s docstring dropped the "the gateway does not
21
+ currently deduplicate" caveat — the backend now enforces it, so a retried
22
+ create is safe by default.
23
+
24
+ ## [4.0.0] — 2026-08-22
25
+
26
+ **Breaking:** `scheduledCharges` now targets the versioned public API
27
+ `/api/v1/scheduled-charges`. `id` was already a stable, non-enumerable
28
+ string (`sch_...`) — no identifier change — but the list envelope shape
29
+ changed. If you use `garu.scheduledCharges.*`, read the migration below.
30
+
31
+ ### Breaking
32
+
33
+ - **`scheduledCharges` moved to `/api/v1/scheduled-charges`.**
34
+ - **`scheduledCharges.list()` and `.listAttempts()` now return
35
+ `{ data, count, totalCount, totalPages }`** (was `{ data, meta }`).
36
+ - No method signatures changed — every method already took/returned the
37
+ same shapes, since `id` was never a numeric internal id here.
38
+
39
+ ### Added
40
+
41
+ - `ScheduledChargeRecord` now explicitly types `recurrence`,
42
+ `cancelAtPeriodEnd`, `trialEndsAt`, and `subscriptionId` (previously only
43
+ reachable via the type's index signature, untyped).
44
+ - Test coverage for `cancelRecurrence`, `setCancelAtPeriodEnd`,
45
+ `changePaymentMethod`, `clearPaymentMethod`, and `listAttempts` — none of
46
+ these five methods had a single test before this release.
47
+
48
+ ### Fixed
49
+
50
+ - `ScheduledChargeLinkedTransaction.value`'s docstring claimed centavos;
51
+ `/api/v1/charges`' own mapper treats the same `transaction.value` column
52
+ as decimal reais with no conversion. Corrected to match reality — this is
53
+ a documentation fix, not a behavior or wire-format change.
54
+ - `scheduledCharges.create()`'s docstring claimed the SDK's
55
+ `X-Idempotency-Key` prevents duplicate creates on retry. The gateway does
56
+ not deduplicate `/scheduled-charges` creates against it (same pre-existing
57
+ gap as `webhookEvents.resend()` had). Corrected the docstring; the header
58
+ is still sent (harmless) in case the gateway adds this later.
59
+
60
+ ### Migration
61
+
62
+ ```ts
63
+ // before (≤ 3.x)
64
+ const { data, meta } = await garu.scheduledCharges.list({ status: 'overdue' });
65
+ meta.total; // number
66
+
67
+ // after (4.0.0)
68
+ const { data, totalCount } = await garu.scheduledCharges.list({ status: 'overdue' });
69
+ totalCount; // number
70
+ ```
71
+
6
72
  ## [3.0.0] — 2026-08-22
7
73
 
8
74
  **Breaking:** `webhookEvents` now targets the versioned public API
@@ -34,16 +100,16 @@ read the migration below.
34
100
  // before (0.x – 2.x)
35
101
  const failed = await garu.webhookEvents.list({ status: 'failed', limit: 5 });
36
102
  const event = failed.data[0];
37
- event.id; // number
103
+ event.id; // number
38
104
  const clone = await garu.webhookEvents.resend(event.id);
39
- clone.manualResendOf === event.id; // true
105
+ clone.manualResendOf === event.id; // true
40
106
 
41
107
  // after (3.0.0)
42
108
  const failed = await garu.webhookEvents.list({ status: 'failed', limit: 5 });
43
109
  const event = failed.data[0];
44
- event.uuid; // string
110
+ event.uuid; // string
45
111
  const clone = await garu.webhookEvents.resend(event.uuid);
46
- clone.manualResendOf === event.uuid; // true
112
+ clone.manualResendOf === event.uuid; // true
47
113
  ```
48
114
 
49
115
  ## [2.0.0] — 2026-08-22
@@ -78,14 +144,14 @@ keyed on `uuid`. If you use `garu.customers.*`, read the migration below.
78
144
  ```ts
79
145
  // before (1.x)
80
146
  const c = await garu.customers.create({ name, email, document, phone, personType });
81
- c.id; // number
147
+ c.id; // number
82
148
  const one = await garu.customers.get(c.id);
83
149
  await garu.customers.update(c.id, { name: 'Maria Santos' });
84
150
  await garu.customers.delete(c.id);
85
151
 
86
152
  // after (2.0.0)
87
153
  const c = await garu.customers.create({ name, email, document, phone, personType });
88
- c.uuid; // string
154
+ c.uuid; // string
89
155
  const one = await garu.customers.get(c.uuid);
90
156
  await garu.customers.update(c.uuid, { name: 'Maria Santos' });
91
157
  const { removed } = await garu.customers.delete(c.uuid);
@@ -93,7 +159,6 @@ const { removed } = await garu.customers.delete(c.uuid);
93
159
 
94
160
  ## [1.1.0] — 2026-08-15
95
161
 
96
-
97
162
  ### Added
98
163
 
99
164
  - **`garu.installmentPlans` — boleto parcelado (carnê).** One product sold as N
@@ -170,22 +235,26 @@ webhook-events) changed.
170
235
  ```ts
171
236
  // before (0.16.x)
172
237
  const c = await garu.charges.create({
173
- productId, paymentMethod: 'credit_card', customer,
238
+ productId,
239
+ paymentMethod: 'credit_card',
240
+ customer,
174
241
  cardInfo: { cardNumber: '4111…', cvv, expirationDate, holderName, installments: 2 }
175
242
  });
176
- c.id; // number
177
- c.paymentMethodId; // 'creditcard'
243
+ c.id; // number
244
+ c.paymentMethodId; // 'creditcard'
178
245
  const one = await garu.charges.get(c.id);
179
246
  await garu.charges.refund(c.id, { amount: 1000 }); // "R$10,00" (bug: reais)
180
247
 
181
248
  // after (1.0.0)
182
249
  const c = await garu.charges.create({
183
- productId, paymentMethod: 'creditCard', customer,
250
+ productId,
251
+ paymentMethod: 'creditCard',
252
+ customer,
184
253
  card: { number: '4111…', cvv, expirationDate, holderName, installments: 2 }
185
254
  });
186
- c.uuid; // string
187
- c.paymentMethod; // 'creditCard'
188
- c.chargedTotal; // what was actually charged
255
+ c.uuid; // string
256
+ c.paymentMethod; // 'creditCard'
257
+ c.chargedTotal; // what was actually charged
189
258
  const one = await garu.charges.retrieve(c.uuid);
190
259
  await garu.charges.refund(c.uuid, { amount: 10.0 }); // R$10,00
191
260
  ```
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
@@ -217,7 +217,7 @@ var Charges = class {
217
217
  * phone: '11987654321'
218
218
  * }
219
219
  * });
220
- * console.log(charge.uuid, charge.pix?.code);
220
+ * // charge.uuid, charge.pix?.code
221
221
  *
222
222
  * @example
223
223
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -262,7 +262,7 @@ var Charges = class {
262
262
  *
263
263
  * @example
264
264
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
265
- * console.log(`${data.length} of ${totalCount} paid charges`);
265
+ * // data.length of totalCount paid charges
266
266
  */
267
267
  async list(params = {}) {
268
268
  const query = {};
@@ -285,15 +285,24 @@ var Charges = class {
285
285
  * charge in `refund_pending`, reaching `refunded` only once the transfer
286
286
  * settles.
287
287
  *
288
+ * For Pix/boleto (which open a refund request instead of an automated
289
+ * reversal), attaches an `X-Idempotency-Key` header automatically — if you
290
+ * don't pass `idempotencyKey`, the SDK generates a UUIDv4. Ignored for
291
+ * card, which reverses automatically and has no manual request to
292
+ * duplicate.
293
+ *
288
294
  * @example
289
295
  * await garu.charges.refund('6f1c9b2e-...'); // full
290
296
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
291
297
  */
292
298
  async refund(uuid, params = {}) {
299
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
293
300
  const body = {};
294
301
  if (params.amount !== void 0) body.amount = params.amount;
295
302
  if (params.reason !== void 0) body.reason = params.reason;
296
- return this.post(`/api/v1/charges/${encodeURIComponent(uuid)}/refund`, body);
303
+ return this.post(`/api/v1/charges/${encodeURIComponent(uuid)}/refund`, body, {
304
+ "X-Idempotency-Key": idempotencyKey
305
+ });
297
306
  }
298
307
  /**
299
308
  * Cancel an unpaid charge.
@@ -318,7 +327,11 @@ var Charges = class {
318
327
  }
319
328
  post(url, body, headers) {
320
329
  return this.http.call(
321
- (signal) => this.http.client.POST(url, { body, headers, signal })
330
+ (signal) => this.http.client.POST(url, {
331
+ body,
332
+ headers,
333
+ signal
334
+ })
322
335
  );
323
336
  }
324
337
  };
@@ -507,6 +520,11 @@ var InstallmentPlans = class {
507
520
  * team. Transfer the money to the buyer yourself, then close it with
508
521
  * `garu.refundRequests.confirm`.
509
522
  *
523
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
524
+ * `idempotencyKey`, the SDK generates a UUIDv4. The backend already dedupes
525
+ * a second pending request for the same carnê, so this is mainly
526
+ * defense-in-depth for the request-in-flight window.
527
+ *
510
528
  * @example
511
529
  * const request = await garu.installmentPlans.requestRefund(uuid, {
512
530
  * reason: 'Produto não entregue'
@@ -515,9 +533,12 @@ var InstallmentPlans = class {
515
533
  * request.amount; // defaults to everything the carnê collected
516
534
  */
517
535
  async requestRefund(uuid, params = {}) {
536
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
537
+ const { idempotencyKey: _omit, ...body } = params;
518
538
  return this.http.call(
519
539
  (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/refund-requests`, {
520
- body: params,
540
+ body,
541
+ headers: { "X-Idempotency-Key": idempotencyKey },
521
542
  signal
522
543
  }).then((r) => r)
523
544
  );
@@ -625,6 +646,10 @@ var Customers = class {
625
646
  /**
626
647
  * Register a customer for the current seller.
627
648
  *
649
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
650
+ * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
651
+ * returns the originally-created/matched customer for 24h.
652
+ *
628
653
  * @example
629
654
  * const customer = await garu.customers.create({
630
655
  * name: 'Maria Silva',
@@ -636,9 +661,12 @@ var Customers = class {
636
661
  * customer.uuid;
637
662
  */
638
663
  async create(params) {
664
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
665
+ const { idempotencyKey: _omit, ...body } = params;
639
666
  return this.http.call(
640
667
  (signal) => this.http.client.POST("/api/v1/customers", {
641
- body: params,
668
+ body,
669
+ headers: { "X-Idempotency-Key": idempotencyKey },
642
670
  signal
643
671
  }).then((r) => r)
644
672
  );
@@ -942,9 +970,10 @@ var ScheduledCharges = class {
942
970
  }
943
971
  http;
944
972
  /**
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.
973
+ * Create a new scheduled charge. Attaches an `X-Idempotency-Key` header
974
+ * automatically if you don't pass `idempotencyKey`, the SDK generates a
975
+ * UUIDv4. Safe to retry: the same key returns the originally-created
976
+ * series for 24h instead of double-booking recurring billing.
948
977
  *
949
978
  * @example
950
979
  * const charge = await garu.scheduledCharges.create({
@@ -974,7 +1003,7 @@ var ScheduledCharges = class {
974
1003
  const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
975
1004
  const { idempotencyKey: _omit, ...body } = params;
976
1005
  return this.http.call(
977
- (signal) => this.http.client.POST("/api/scheduled-charges", {
1006
+ (signal) => this.http.client.POST("/api/v1/scheduled-charges", {
978
1007
  body,
979
1008
  headers: { "X-Idempotency-Key": idempotencyKey },
980
1009
  signal
@@ -1009,7 +1038,7 @@ var ScheduledCharges = class {
1009
1038
  for (const s of statuses) qs.append("status", s);
1010
1039
  }
1011
1040
  const query = qs.toString();
1012
- const url = `/api/scheduled-charges${query ? `?${query}` : ""}`;
1041
+ const url = `/api/v1/scheduled-charges${query ? `?${query}` : ""}`;
1013
1042
  return this.http.call(
1014
1043
  (signal) => this.http.client.GET(url, { signal }).then(
1015
1044
  (r) => r
@@ -1026,7 +1055,7 @@ var ScheduledCharges = class {
1026
1055
  */
1027
1056
  async get(id) {
1028
1057
  return this.http.call(
1029
- (signal) => this.http.client.GET(`/api/scheduled-charges/${encodeURIComponent(id)}`, {
1058
+ (signal) => this.http.client.GET(`/api/v1/scheduled-charges/${encodeURIComponent(id)}`, {
1030
1059
  signal
1031
1060
  }).then((r) => r)
1032
1061
  );
@@ -1045,7 +1074,7 @@ var ScheduledCharges = class {
1045
1074
  async postpone(id, params) {
1046
1075
  return this.http.call(
1047
1076
  (signal) => this.http.client.POST(
1048
- `/api/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1077
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1049
1078
  {
1050
1079
  body: params,
1051
1080
  signal
@@ -1064,7 +1093,7 @@ var ScheduledCharges = class {
1064
1093
  async pause(id, params = {}) {
1065
1094
  return this.http.call(
1066
1095
  (signal) => this.http.client.POST(
1067
- `/api/scheduled-charges/${encodeURIComponent(id)}/pause`,
1096
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/pause`,
1068
1097
  {
1069
1098
  body: params,
1070
1099
  signal
@@ -1081,7 +1110,7 @@ var ScheduledCharges = class {
1081
1110
  async resume(id) {
1082
1111
  return this.http.call(
1083
1112
  (signal) => this.http.client.POST(
1084
- `/api/scheduled-charges/${encodeURIComponent(id)}/resume`,
1113
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/resume`,
1085
1114
  {
1086
1115
  body: {},
1087
1116
  signal
@@ -1115,7 +1144,7 @@ var ScheduledCharges = class {
1115
1144
  async markPaid(id, params) {
1116
1145
  return this.http.call(
1117
1146
  (signal) => this.http.client.POST(
1118
- `/api/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1147
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1119
1148
  {
1120
1149
  body: params,
1121
1150
  signal
@@ -1137,24 +1166,22 @@ var ScheduledCharges = class {
1137
1166
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1138
1167
  * switch (result.outcome) {
1139
1168
  * case 'dispatched':
1140
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1169
+ * result.cycleNumber; // billed this cycle
1141
1170
  * break;
1142
1171
  * case 'already_sent':
1143
- * console.log('Já havia sido enviada — nada a fazer.');
1144
- * break;
1172
+ * break; // nothing to do
1145
1173
  * 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}`);
1174
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1148
1175
  * break;
1149
1176
  * case 'not_sent':
1150
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1177
+ * result.reason;
1151
1178
  * break;
1152
1179
  * }
1153
1180
  */
1154
1181
  async chargeNow(id) {
1155
1182
  return this.http.call(
1156
1183
  (signal) => this.http.client.POST(
1157
- `/api/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1184
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1158
1185
  {
1159
1186
  body: {},
1160
1187
  signal
@@ -1176,7 +1203,7 @@ var ScheduledCharges = class {
1176
1203
  async cancelRecurrence(id, params = {}) {
1177
1204
  return this.http.call(
1178
1205
  (signal) => this.http.client.POST(
1179
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1206
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1180
1207
  {
1181
1208
  body: params,
1182
1209
  signal
@@ -1196,7 +1223,7 @@ var ScheduledCharges = class {
1196
1223
  async setCancelAtPeriodEnd(id, params) {
1197
1224
  return this.http.call(
1198
1225
  (signal) => this.http.client.POST(
1199
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1226
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1200
1227
  {
1201
1228
  body: params,
1202
1229
  signal
@@ -1215,7 +1242,7 @@ var ScheduledCharges = class {
1215
1242
  async changePaymentMethod(id, params) {
1216
1243
  return this.http.call(
1217
1244
  (signal) => this.http.client.POST(
1218
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1245
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1219
1246
  {
1220
1247
  body: params,
1221
1248
  signal
@@ -1234,7 +1261,7 @@ var ScheduledCharges = class {
1234
1261
  async clearPaymentMethod(id) {
1235
1262
  return this.http.call(
1236
1263
  (signal) => this.http.client.DELETE(
1237
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1264
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1238
1265
  {
1239
1266
  body: {},
1240
1267
  signal
@@ -1261,7 +1288,7 @@ var ScheduledCharges = class {
1261
1288
  if (params.limit !== void 0) qs.set("limit", String(params.limit));
1262
1289
  if (params.cycleNumber !== void 0) qs.set("cycleNumber", String(params.cycleNumber));
1263
1290
  const query = qs.toString();
1264
- const url = `/api/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1291
+ const url = `/api/v1/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1265
1292
  return this.http.call(
1266
1293
  (signal) => this.http.client.GET(url, { signal }).then(
1267
1294
  (r) => r
package/dist/index.d.cts CHANGED
@@ -219,6 +219,13 @@ interface RefundChargeParams {
219
219
  amount?: number;
220
220
  /** Free-form reason stored on the refund. */
221
221
  reason?: string;
222
+ /**
223
+ * Optional idempotency key for safe retries. The SDK auto-generates a
224
+ * UUIDv4 when omitted and forwards it as `X-Idempotency-Key`. Only used
225
+ * for Pix/boleto, which open a refund request instead of an automated
226
+ * reversal — ignored for card, which has no manual request to duplicate.
227
+ */
228
+ idempotencyKey?: string;
222
229
  }
223
230
  interface ListChargesParams {
224
231
  /** Page number (1-based). Default: 1. */
@@ -324,6 +331,13 @@ interface CreateCustomerParams {
324
331
  city?: string;
325
332
  /** 2-letter uppercase state code, e.g. `SP`. */
326
333
  state?: string;
334
+ /**
335
+ * Optional idempotency key for safe retries. The SDK auto-generates a
336
+ * UUIDv4 when omitted and forwards it as `X-Idempotency-Key`. Document
337
+ * uniqueness already merges a retried create into the same customer, so
338
+ * this mainly protects registrations with no document set.
339
+ */
340
+ idempotencyKey?: string;
327
341
  }
328
342
  interface UpdateCustomerParams {
329
343
  name?: string;
@@ -390,7 +404,13 @@ interface ScheduledChargeRecord {
390
404
  /** YYYY-MM-DD in São Paulo time. */
391
405
  dueDate: string;
392
406
  methods: ScheduledPaymentMethod[];
407
+ recurrence: RecurrenceConfig | null;
393
408
  status: ScheduledChargeStatus;
409
+ subscriptionId: number | null;
410
+ /** ISO-8601. Set only when the series was created with `trialDays`. */
411
+ trialEndsAt: string | null;
412
+ /** Recurring only. Toggle with `setCancelAtPeriodEnd`. */
413
+ cancelAtPeriodEnd: boolean;
394
414
  externalReference: string | null;
395
415
  /**
396
416
  * Max days past `dueDate` the daily recovery sweep will still auto-bill a
@@ -425,7 +445,7 @@ interface ScheduledChargeEvent {
425
445
  }
426
446
  interface ScheduledChargeLinkedTransaction {
427
447
  id: number;
428
- /** Centavos (BRL × 100), matching `garu.charges.*` value semantics. */
448
+ /** Decimal BRL (e.g. `297.50`), never centavos. */
429
449
  value: number;
430
450
  paymentMethod: string;
431
451
  status: string;
@@ -438,7 +458,14 @@ interface ScheduledChargeDetail {
438
458
  events: ScheduledChargeEvent[];
439
459
  transactions: ScheduledChargeLinkedTransaction[];
440
460
  }
441
- type ScheduledChargeList = PaginatedList<ScheduledChargeRecord>;
461
+ interface ScheduledChargeList {
462
+ data: ScheduledChargeRecord[];
463
+ /** Items on this page. */
464
+ count: number;
465
+ /** Total matches across all pages. */
466
+ totalCount: number;
467
+ totalPages: number;
468
+ }
442
469
  /** Source of a billing attempt — see SPEC §3.1. */
443
470
  type ScheduledChargeAttemptSource = 'cycle1_interactive' | 'silent_charge' | 'card_retry' | 'manual_mark_paid' | 'fallback_pix';
444
471
  type ScheduledChargeAttemptStatus = 'pending' | 'succeeded' | 'declined' | 'canceled' | 'errored';
@@ -460,7 +487,14 @@ interface ScheduledChargeAttempt {
460
487
  gatewayChargeId: number | null;
461
488
  transactionId: number | null;
462
489
  }
463
- type ScheduledChargeAttemptList = PaginatedList<ScheduledChargeAttempt>;
490
+ interface ScheduledChargeAttemptList {
491
+ data: ScheduledChargeAttempt[];
492
+ /** Items on this page. */
493
+ count: number;
494
+ /** Total matches across all pages. */
495
+ totalCount: number;
496
+ totalPages: number;
497
+ }
464
498
  interface ListScheduledChargeAttemptsParams {
465
499
  page?: number;
466
500
  limit?: number;
@@ -1031,6 +1065,13 @@ interface RequestPlanRefundParams {
1031
1065
  /** Defaults to everything the carnê has collected. */
1032
1066
  amount?: number;
1033
1067
  reason?: string;
1068
+ /**
1069
+ * Optional idempotency key for safe retries. The SDK auto-generates a
1070
+ * UUIDv4 when omitted and forwards it as `X-Idempotency-Key`. The backend
1071
+ * already dedupes a second pending request for the same carnê, so this is
1072
+ * mainly defense-in-depth for the request-in-flight window.
1073
+ */
1074
+ idempotencyKey?: string;
1034
1075
  }
1035
1076
  interface ListRefundRequestsParams {
1036
1077
  page?: number;
@@ -1075,7 +1116,7 @@ declare class Charges {
1075
1116
  * phone: '11987654321'
1076
1117
  * }
1077
1118
  * });
1078
- * console.log(charge.uuid, charge.pix?.code);
1119
+ * // charge.uuid, charge.pix?.code
1079
1120
  *
1080
1121
  * @example
1081
1122
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -1107,7 +1148,7 @@ declare class Charges {
1107
1148
  *
1108
1149
  * @example
1109
1150
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
1110
- * console.log(`${data.length} of ${totalCount} paid charges`);
1151
+ * // data.length of totalCount paid charges
1111
1152
  */
1112
1153
  list(params?: ListChargesParams): Promise<ChargeList>;
1113
1154
  /**
@@ -1117,6 +1158,12 @@ declare class Charges {
1117
1158
  * charge in `refund_pending`, reaching `refunded` only once the transfer
1118
1159
  * settles.
1119
1160
  *
1161
+ * For Pix/boleto (which open a refund request instead of an automated
1162
+ * reversal), attaches an `X-Idempotency-Key` header automatically — if you
1163
+ * don't pass `idempotencyKey`, the SDK generates a UUIDv4. Ignored for
1164
+ * card, which reverses automatically and has no manual request to
1165
+ * duplicate.
1166
+ *
1120
1167
  * @example
1121
1168
  * await garu.charges.refund('6f1c9b2e-...'); // full
1122
1169
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
@@ -1263,6 +1310,11 @@ declare class InstallmentPlans {
1263
1310
  * team. Transfer the money to the buyer yourself, then close it with
1264
1311
  * `garu.refundRequests.confirm`.
1265
1312
  *
1313
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
1314
+ * `idempotencyKey`, the SDK generates a UUIDv4. The backend already dedupes
1315
+ * a second pending request for the same carnê, so this is mainly
1316
+ * defense-in-depth for the request-in-flight window.
1317
+ *
1266
1318
  * @example
1267
1319
  * const request = await garu.installmentPlans.requestRefund(uuid, {
1268
1320
  * reason: 'Produto não entregue'
@@ -1352,6 +1404,10 @@ declare class Customers {
1352
1404
  /**
1353
1405
  * Register a customer for the current seller.
1354
1406
  *
1407
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
1408
+ * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
1409
+ * returns the originally-created/matched customer for 24h.
1410
+ *
1355
1411
  * @example
1356
1412
  * const customer = await garu.customers.create({
1357
1413
  * name: 'Maria Silva',
@@ -1573,9 +1629,10 @@ declare class ScheduledCharges {
1573
1629
  private readonly http;
1574
1630
  constructor(http: HttpClient);
1575
1631
  /**
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.
1632
+ * Create a new scheduled charge. Attaches an `X-Idempotency-Key` header
1633
+ * automatically if you don't pass `idempotencyKey`, the SDK generates a
1634
+ * UUIDv4. Safe to retry: the same key returns the originally-created
1635
+ * series for 24h instead of double-booking recurring billing.
1579
1636
  *
1580
1637
  * @example
1581
1638
  * const charge = await garu.scheduledCharges.create({
@@ -1692,17 +1749,15 @@ declare class ScheduledCharges {
1692
1749
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1693
1750
  * switch (result.outcome) {
1694
1751
  * case 'dispatched':
1695
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1752
+ * result.cycleNumber; // billed this cycle
1696
1753
  * break;
1697
1754
  * case 'already_sent':
1698
- * console.log('Já havia sido enviada — nada a fazer.');
1699
- * break;
1755
+ * break; // nothing to do
1700
1756
  * 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}`);
1757
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1703
1758
  * break;
1704
1759
  * case 'not_sent':
1705
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1760
+ * result.reason;
1706
1761
  * break;
1707
1762
  * }
1708
1763
  */
package/dist/index.d.ts CHANGED
@@ -219,6 +219,13 @@ interface RefundChargeParams {
219
219
  amount?: number;
220
220
  /** Free-form reason stored on the refund. */
221
221
  reason?: string;
222
+ /**
223
+ * Optional idempotency key for safe retries. The SDK auto-generates a
224
+ * UUIDv4 when omitted and forwards it as `X-Idempotency-Key`. Only used
225
+ * for Pix/boleto, which open a refund request instead of an automated
226
+ * reversal — ignored for card, which has no manual request to duplicate.
227
+ */
228
+ idempotencyKey?: string;
222
229
  }
223
230
  interface ListChargesParams {
224
231
  /** Page number (1-based). Default: 1. */
@@ -324,6 +331,13 @@ interface CreateCustomerParams {
324
331
  city?: string;
325
332
  /** 2-letter uppercase state code, e.g. `SP`. */
326
333
  state?: string;
334
+ /**
335
+ * Optional idempotency key for safe retries. The SDK auto-generates a
336
+ * UUIDv4 when omitted and forwards it as `X-Idempotency-Key`. Document
337
+ * uniqueness already merges a retried create into the same customer, so
338
+ * this mainly protects registrations with no document set.
339
+ */
340
+ idempotencyKey?: string;
327
341
  }
328
342
  interface UpdateCustomerParams {
329
343
  name?: string;
@@ -390,7 +404,13 @@ interface ScheduledChargeRecord {
390
404
  /** YYYY-MM-DD in São Paulo time. */
391
405
  dueDate: string;
392
406
  methods: ScheduledPaymentMethod[];
407
+ recurrence: RecurrenceConfig | null;
393
408
  status: ScheduledChargeStatus;
409
+ subscriptionId: number | null;
410
+ /** ISO-8601. Set only when the series was created with `trialDays`. */
411
+ trialEndsAt: string | null;
412
+ /** Recurring only. Toggle with `setCancelAtPeriodEnd`. */
413
+ cancelAtPeriodEnd: boolean;
394
414
  externalReference: string | null;
395
415
  /**
396
416
  * Max days past `dueDate` the daily recovery sweep will still auto-bill a
@@ -425,7 +445,7 @@ interface ScheduledChargeEvent {
425
445
  }
426
446
  interface ScheduledChargeLinkedTransaction {
427
447
  id: number;
428
- /** Centavos (BRL × 100), matching `garu.charges.*` value semantics. */
448
+ /** Decimal BRL (e.g. `297.50`), never centavos. */
429
449
  value: number;
430
450
  paymentMethod: string;
431
451
  status: string;
@@ -438,7 +458,14 @@ interface ScheduledChargeDetail {
438
458
  events: ScheduledChargeEvent[];
439
459
  transactions: ScheduledChargeLinkedTransaction[];
440
460
  }
441
- type ScheduledChargeList = PaginatedList<ScheduledChargeRecord>;
461
+ interface ScheduledChargeList {
462
+ data: ScheduledChargeRecord[];
463
+ /** Items on this page. */
464
+ count: number;
465
+ /** Total matches across all pages. */
466
+ totalCount: number;
467
+ totalPages: number;
468
+ }
442
469
  /** Source of a billing attempt — see SPEC §3.1. */
443
470
  type ScheduledChargeAttemptSource = 'cycle1_interactive' | 'silent_charge' | 'card_retry' | 'manual_mark_paid' | 'fallback_pix';
444
471
  type ScheduledChargeAttemptStatus = 'pending' | 'succeeded' | 'declined' | 'canceled' | 'errored';
@@ -460,7 +487,14 @@ interface ScheduledChargeAttempt {
460
487
  gatewayChargeId: number | null;
461
488
  transactionId: number | null;
462
489
  }
463
- type ScheduledChargeAttemptList = PaginatedList<ScheduledChargeAttempt>;
490
+ interface ScheduledChargeAttemptList {
491
+ data: ScheduledChargeAttempt[];
492
+ /** Items on this page. */
493
+ count: number;
494
+ /** Total matches across all pages. */
495
+ totalCount: number;
496
+ totalPages: number;
497
+ }
464
498
  interface ListScheduledChargeAttemptsParams {
465
499
  page?: number;
466
500
  limit?: number;
@@ -1031,6 +1065,13 @@ interface RequestPlanRefundParams {
1031
1065
  /** Defaults to everything the carnê has collected. */
1032
1066
  amount?: number;
1033
1067
  reason?: string;
1068
+ /**
1069
+ * Optional idempotency key for safe retries. The SDK auto-generates a
1070
+ * UUIDv4 when omitted and forwards it as `X-Idempotency-Key`. The backend
1071
+ * already dedupes a second pending request for the same carnê, so this is
1072
+ * mainly defense-in-depth for the request-in-flight window.
1073
+ */
1074
+ idempotencyKey?: string;
1034
1075
  }
1035
1076
  interface ListRefundRequestsParams {
1036
1077
  page?: number;
@@ -1075,7 +1116,7 @@ declare class Charges {
1075
1116
  * phone: '11987654321'
1076
1117
  * }
1077
1118
  * });
1078
- * console.log(charge.uuid, charge.pix?.code);
1119
+ * // charge.uuid, charge.pix?.code
1079
1120
  *
1080
1121
  * @example
1081
1122
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -1107,7 +1148,7 @@ declare class Charges {
1107
1148
  *
1108
1149
  * @example
1109
1150
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
1110
- * console.log(`${data.length} of ${totalCount} paid charges`);
1151
+ * // data.length of totalCount paid charges
1111
1152
  */
1112
1153
  list(params?: ListChargesParams): Promise<ChargeList>;
1113
1154
  /**
@@ -1117,6 +1158,12 @@ declare class Charges {
1117
1158
  * charge in `refund_pending`, reaching `refunded` only once the transfer
1118
1159
  * settles.
1119
1160
  *
1161
+ * For Pix/boleto (which open a refund request instead of an automated
1162
+ * reversal), attaches an `X-Idempotency-Key` header automatically — if you
1163
+ * don't pass `idempotencyKey`, the SDK generates a UUIDv4. Ignored for
1164
+ * card, which reverses automatically and has no manual request to
1165
+ * duplicate.
1166
+ *
1120
1167
  * @example
1121
1168
  * await garu.charges.refund('6f1c9b2e-...'); // full
1122
1169
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
@@ -1263,6 +1310,11 @@ declare class InstallmentPlans {
1263
1310
  * team. Transfer the money to the buyer yourself, then close it with
1264
1311
  * `garu.refundRequests.confirm`.
1265
1312
  *
1313
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
1314
+ * `idempotencyKey`, the SDK generates a UUIDv4. The backend already dedupes
1315
+ * a second pending request for the same carnê, so this is mainly
1316
+ * defense-in-depth for the request-in-flight window.
1317
+ *
1266
1318
  * @example
1267
1319
  * const request = await garu.installmentPlans.requestRefund(uuid, {
1268
1320
  * reason: 'Produto não entregue'
@@ -1352,6 +1404,10 @@ declare class Customers {
1352
1404
  /**
1353
1405
  * Register a customer for the current seller.
1354
1406
  *
1407
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
1408
+ * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
1409
+ * returns the originally-created/matched customer for 24h.
1410
+ *
1355
1411
  * @example
1356
1412
  * const customer = await garu.customers.create({
1357
1413
  * name: 'Maria Silva',
@@ -1573,9 +1629,10 @@ declare class ScheduledCharges {
1573
1629
  private readonly http;
1574
1630
  constructor(http: HttpClient);
1575
1631
  /**
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.
1632
+ * Create a new scheduled charge. Attaches an `X-Idempotency-Key` header
1633
+ * automatically if you don't pass `idempotencyKey`, the SDK generates a
1634
+ * UUIDv4. Safe to retry: the same key returns the originally-created
1635
+ * series for 24h instead of double-booking recurring billing.
1579
1636
  *
1580
1637
  * @example
1581
1638
  * const charge = await garu.scheduledCharges.create({
@@ -1692,17 +1749,15 @@ declare class ScheduledCharges {
1692
1749
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1693
1750
  * switch (result.outcome) {
1694
1751
  * case 'dispatched':
1695
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1752
+ * result.cycleNumber; // billed this cycle
1696
1753
  * break;
1697
1754
  * case 'already_sent':
1698
- * console.log('Já havia sido enviada — nada a fazer.');
1699
- * break;
1755
+ * break; // nothing to do
1700
1756
  * 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}`);
1757
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1703
1758
  * break;
1704
1759
  * case 'not_sent':
1705
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1760
+ * result.reason;
1706
1761
  * break;
1707
1762
  * }
1708
1763
  */
package/dist/index.js CHANGED
@@ -211,7 +211,7 @@ var Charges = class {
211
211
  * phone: '11987654321'
212
212
  * }
213
213
  * });
214
- * console.log(charge.uuid, charge.pix?.code);
214
+ * // charge.uuid, charge.pix?.code
215
215
  *
216
216
  * @example
217
217
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -256,7 +256,7 @@ var Charges = class {
256
256
  *
257
257
  * @example
258
258
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
259
- * console.log(`${data.length} of ${totalCount} paid charges`);
259
+ * // data.length of totalCount paid charges
260
260
  */
261
261
  async list(params = {}) {
262
262
  const query = {};
@@ -279,15 +279,24 @@ var Charges = class {
279
279
  * charge in `refund_pending`, reaching `refunded` only once the transfer
280
280
  * settles.
281
281
  *
282
+ * For Pix/boleto (which open a refund request instead of an automated
283
+ * reversal), attaches an `X-Idempotency-Key` header automatically — if you
284
+ * don't pass `idempotencyKey`, the SDK generates a UUIDv4. Ignored for
285
+ * card, which reverses automatically and has no manual request to
286
+ * duplicate.
287
+ *
282
288
  * @example
283
289
  * await garu.charges.refund('6f1c9b2e-...'); // full
284
290
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
285
291
  */
286
292
  async refund(uuid, params = {}) {
293
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
287
294
  const body = {};
288
295
  if (params.amount !== void 0) body.amount = params.amount;
289
296
  if (params.reason !== void 0) body.reason = params.reason;
290
- return this.post(`/api/v1/charges/${encodeURIComponent(uuid)}/refund`, body);
297
+ return this.post(`/api/v1/charges/${encodeURIComponent(uuid)}/refund`, body, {
298
+ "X-Idempotency-Key": idempotencyKey
299
+ });
291
300
  }
292
301
  /**
293
302
  * Cancel an unpaid charge.
@@ -312,7 +321,11 @@ var Charges = class {
312
321
  }
313
322
  post(url, body, headers) {
314
323
  return this.http.call(
315
- (signal) => this.http.client.POST(url, { body, headers, signal })
324
+ (signal) => this.http.client.POST(url, {
325
+ body,
326
+ headers,
327
+ signal
328
+ })
316
329
  );
317
330
  }
318
331
  };
@@ -501,6 +514,11 @@ var InstallmentPlans = class {
501
514
  * team. Transfer the money to the buyer yourself, then close it with
502
515
  * `garu.refundRequests.confirm`.
503
516
  *
517
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
518
+ * `idempotencyKey`, the SDK generates a UUIDv4. The backend already dedupes
519
+ * a second pending request for the same carnê, so this is mainly
520
+ * defense-in-depth for the request-in-flight window.
521
+ *
504
522
  * @example
505
523
  * const request = await garu.installmentPlans.requestRefund(uuid, {
506
524
  * reason: 'Produto não entregue'
@@ -509,9 +527,12 @@ var InstallmentPlans = class {
509
527
  * request.amount; // defaults to everything the carnê collected
510
528
  */
511
529
  async requestRefund(uuid, params = {}) {
530
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
531
+ const { idempotencyKey: _omit, ...body } = params;
512
532
  return this.http.call(
513
533
  (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/refund-requests`, {
514
- body: params,
534
+ body,
535
+ headers: { "X-Idempotency-Key": idempotencyKey },
515
536
  signal
516
537
  }).then((r) => r)
517
538
  );
@@ -619,6 +640,10 @@ var Customers = class {
619
640
  /**
620
641
  * Register a customer for the current seller.
621
642
  *
643
+ * Attaches an `X-Idempotency-Key` header automatically — if you don't pass
644
+ * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
645
+ * returns the originally-created/matched customer for 24h.
646
+ *
622
647
  * @example
623
648
  * const customer = await garu.customers.create({
624
649
  * name: 'Maria Silva',
@@ -630,9 +655,12 @@ var Customers = class {
630
655
  * customer.uuid;
631
656
  */
632
657
  async create(params) {
658
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
659
+ const { idempotencyKey: _omit, ...body } = params;
633
660
  return this.http.call(
634
661
  (signal) => this.http.client.POST("/api/v1/customers", {
635
- body: params,
662
+ body,
663
+ headers: { "X-Idempotency-Key": idempotencyKey },
636
664
  signal
637
665
  }).then((r) => r)
638
666
  );
@@ -936,9 +964,10 @@ var ScheduledCharges = class {
936
964
  }
937
965
  http;
938
966
  /**
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.
967
+ * Create a new scheduled charge. Attaches an `X-Idempotency-Key` header
968
+ * automatically if you don't pass `idempotencyKey`, the SDK generates a
969
+ * UUIDv4. Safe to retry: the same key returns the originally-created
970
+ * series for 24h instead of double-booking recurring billing.
942
971
  *
943
972
  * @example
944
973
  * const charge = await garu.scheduledCharges.create({
@@ -968,7 +997,7 @@ var ScheduledCharges = class {
968
997
  const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
969
998
  const { idempotencyKey: _omit, ...body } = params;
970
999
  return this.http.call(
971
- (signal) => this.http.client.POST("/api/scheduled-charges", {
1000
+ (signal) => this.http.client.POST("/api/v1/scheduled-charges", {
972
1001
  body,
973
1002
  headers: { "X-Idempotency-Key": idempotencyKey },
974
1003
  signal
@@ -1003,7 +1032,7 @@ var ScheduledCharges = class {
1003
1032
  for (const s of statuses) qs.append("status", s);
1004
1033
  }
1005
1034
  const query = qs.toString();
1006
- const url = `/api/scheduled-charges${query ? `?${query}` : ""}`;
1035
+ const url = `/api/v1/scheduled-charges${query ? `?${query}` : ""}`;
1007
1036
  return this.http.call(
1008
1037
  (signal) => this.http.client.GET(url, { signal }).then(
1009
1038
  (r) => r
@@ -1020,7 +1049,7 @@ var ScheduledCharges = class {
1020
1049
  */
1021
1050
  async get(id) {
1022
1051
  return this.http.call(
1023
- (signal) => this.http.client.GET(`/api/scheduled-charges/${encodeURIComponent(id)}`, {
1052
+ (signal) => this.http.client.GET(`/api/v1/scheduled-charges/${encodeURIComponent(id)}`, {
1024
1053
  signal
1025
1054
  }).then((r) => r)
1026
1055
  );
@@ -1039,7 +1068,7 @@ var ScheduledCharges = class {
1039
1068
  async postpone(id, params) {
1040
1069
  return this.http.call(
1041
1070
  (signal) => this.http.client.POST(
1042
- `/api/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1071
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/postpone`,
1043
1072
  {
1044
1073
  body: params,
1045
1074
  signal
@@ -1058,7 +1087,7 @@ var ScheduledCharges = class {
1058
1087
  async pause(id, params = {}) {
1059
1088
  return this.http.call(
1060
1089
  (signal) => this.http.client.POST(
1061
- `/api/scheduled-charges/${encodeURIComponent(id)}/pause`,
1090
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/pause`,
1062
1091
  {
1063
1092
  body: params,
1064
1093
  signal
@@ -1075,7 +1104,7 @@ var ScheduledCharges = class {
1075
1104
  async resume(id) {
1076
1105
  return this.http.call(
1077
1106
  (signal) => this.http.client.POST(
1078
- `/api/scheduled-charges/${encodeURIComponent(id)}/resume`,
1107
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/resume`,
1079
1108
  {
1080
1109
  body: {},
1081
1110
  signal
@@ -1109,7 +1138,7 @@ var ScheduledCharges = class {
1109
1138
  async markPaid(id, params) {
1110
1139
  return this.http.call(
1111
1140
  (signal) => this.http.client.POST(
1112
- `/api/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1141
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/mark-paid`,
1113
1142
  {
1114
1143
  body: params,
1115
1144
  signal
@@ -1131,24 +1160,22 @@ var ScheduledCharges = class {
1131
1160
  * const result = await garu.scheduledCharges.chargeNow('sch_abc123');
1132
1161
  * switch (result.outcome) {
1133
1162
  * case 'dispatched':
1134
- * console.log(`Cobrança enviada (ciclo ${result.cycleNumber}).`);
1163
+ * result.cycleNumber; // billed this cycle
1135
1164
  * break;
1136
1165
  * case 'already_sent':
1137
- * console.log('Já havia sido enviada — nada a fazer.');
1138
- * break;
1166
+ * break; // nothing to do
1139
1167
  * 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}`);
1168
+ * result.reason; // e.g. 'card_expired' or a gateway decline code
1142
1169
  * break;
1143
1170
  * case 'not_sent':
1144
- * console.warn(`Não enviada (${result.reason}): ${result.message}`);
1171
+ * result.reason;
1145
1172
  * break;
1146
1173
  * }
1147
1174
  */
1148
1175
  async chargeNow(id) {
1149
1176
  return this.http.call(
1150
1177
  (signal) => this.http.client.POST(
1151
- `/api/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1178
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/charge-now`,
1152
1179
  {
1153
1180
  body: {},
1154
1181
  signal
@@ -1170,7 +1197,7 @@ var ScheduledCharges = class {
1170
1197
  async cancelRecurrence(id, params = {}) {
1171
1198
  return this.http.call(
1172
1199
  (signal) => this.http.client.POST(
1173
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1200
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-recurrence`,
1174
1201
  {
1175
1202
  body: params,
1176
1203
  signal
@@ -1190,7 +1217,7 @@ var ScheduledCharges = class {
1190
1217
  async setCancelAtPeriodEnd(id, params) {
1191
1218
  return this.http.call(
1192
1219
  (signal) => this.http.client.POST(
1193
- `/api/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1220
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/cancel-at-period-end`,
1194
1221
  {
1195
1222
  body: params,
1196
1223
  signal
@@ -1209,7 +1236,7 @@ var ScheduledCharges = class {
1209
1236
  async changePaymentMethod(id, params) {
1210
1237
  return this.http.call(
1211
1238
  (signal) => this.http.client.POST(
1212
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1239
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1213
1240
  {
1214
1241
  body: params,
1215
1242
  signal
@@ -1228,7 +1255,7 @@ var ScheduledCharges = class {
1228
1255
  async clearPaymentMethod(id) {
1229
1256
  return this.http.call(
1230
1257
  (signal) => this.http.client.DELETE(
1231
- `/api/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1258
+ `/api/v1/scheduled-charges/${encodeURIComponent(id)}/payment-method`,
1232
1259
  {
1233
1260
  body: {},
1234
1261
  signal
@@ -1255,7 +1282,7 @@ var ScheduledCharges = class {
1255
1282
  if (params.limit !== void 0) qs.set("limit", String(params.limit));
1256
1283
  if (params.cycleNumber !== void 0) qs.set("cycleNumber", String(params.cycleNumber));
1257
1284
  const query = qs.toString();
1258
- const url = `/api/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1285
+ const url = `/api/v1/scheduled-charges/${encodeURIComponent(id)}/attempts${query ? `?${query}` : ""}`;
1259
1286
  return this.http.call(
1260
1287
  (signal) => this.http.client.GET(url, { signal }).then(
1261
1288
  (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.1.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",