@garuhq/node 4.0.0 → 5.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,73 @@
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
+ ## [5.0.0] — 2026-09-09
7
+
8
+ **Breaking:** requests that omit `idempotencyKey` no longer carry an
9
+ `X-Idempotency-Key` header. No API contract changed and no gateway endpoint
10
+ requires the header, so most integrations need no code change — but if you were
11
+ relying on a key always being present, you must now pass one, which is the only
12
+ way it ever gave you real protection. See below.
13
+
14
+ ### Fixed
15
+
16
+ - **The SDK no longer invents an idempotency key.** Every write that takes
17
+ `idempotencyKey` (`charges.create`, `charges.refund`, `customers.create`,
18
+ `products.create`, `scheduledCharges.create`, `installmentPlans.create`,
19
+ `installmentPlans.requestRefund`, `webhookEvents.resend`) used to generate a
20
+ fresh UUIDv4 when you omitted one. That protected nothing: an idempotency key
21
+ only works if the SAME key comes back on a retry, and a key invented per call
22
+ is different every time. The header is now sent only when you pass a key.
23
+
24
+ This was not theoretical. On 2026-09-08 an integrator's HTTP client timed out
25
+ at 30s on a card charge that was still being created, retried, drew a fresh
26
+ UUIDv4, and charged a real buyer twice.
27
+
28
+ Docstrings that promised "Safe to retry: the same key returns the original
29
+ charge for 24h" were describing a guarantee the code did not provide. They now
30
+ say what actually happens.
31
+
32
+ ### Changed
33
+
34
+ - Behaviour change, no API change: requests where you omit `idempotencyKey` now
35
+ carry no `X-Idempotency-Key` header. Nothing rejects a missing key — the header
36
+ is optional on every gateway endpoint — and the gateway also gained a duplicate
37
+ guard that replays the original charge when the same buyer, product, rail and
38
+ instalment count arrive again within 60 seconds.
39
+
40
+ **To get real protection, pass a key derived from your own domain** so a retry
41
+ reproduces it:
42
+
43
+ ```ts
44
+ await garu.charges.create({
45
+ productId,
46
+ paymentMethod: 'creditCard',
47
+ customer,
48
+ idempotencyKey: `booking:${booking.id}:charge`
49
+ });
50
+ ```
51
+
52
+ - `generateIdempotencyKey()` is still exported and unchanged. It is only useful
53
+ if you store the result and reuse it across retries of the same operation.
54
+
55
+ ## [4.1.0] — 2026-08-22
56
+
57
+ ### Added
58
+
59
+ - `customers.create()`, `charges.refund()`, and
60
+ `installmentPlans.requestRefund()` now attach an `X-Idempotency-Key`
61
+ header automatically (UUIDv4 unless you pass `idempotencyKey`). The
62
+ gateway now caches and replays the first response for 24h, so a network
63
+ retry can no longer register a duplicate customer, open a second refund
64
+ request, or (via `scheduledCharges.create()` — see Fixed below)
65
+ double-book recurring billing.
66
+
67
+ ### Fixed
68
+
69
+ - `scheduledCharges.create()`'s docstring dropped the "the gateway does not
70
+ currently deduplicate" caveat — the backend now enforces it, so a retried
71
+ create is safe by default.
72
+
6
73
  ## [4.0.0] — 2026-08-22
7
74
 
8
75
  **Breaking:** `scheduledCharges` now targets the versioned public API
package/dist/index.cjs CHANGED
@@ -188,8 +188,8 @@ function backoffDelay(attempt, retryAfterSec) {
188
188
  function sleep(ms) {
189
189
  return new Promise((resolve) => setTimeout(resolve, ms));
190
190
  }
191
- function generateIdempotencyKey() {
192
- return crypto.randomUUID();
191
+ function idempotencyHeaders(key) {
192
+ return key ? { "X-Idempotency-Key": key } : {};
193
193
  }
194
194
 
195
195
  // src/resources/charges.ts
@@ -201,9 +201,11 @@ var Charges = class {
201
201
  /**
202
202
  * Create a charge (PIX, boleto, or credit card).
203
203
  *
204
- * Attaches an `X-Idempotency-Key` header automatically if you don't pass
205
- * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
206
- * returns the original charge for 24h.
204
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
205
+ * original charge for 24h. Derive it from something stable in your own
206
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
207
+ * no key is sent — the SDK does NOT invent one, because a key generated per
208
+ * call is different every time and protects nothing.
207
209
  *
208
210
  * @example
209
211
  * // PIX — render charge.pix.code as a QR in your own checkout
@@ -217,7 +219,7 @@ var Charges = class {
217
219
  * phone: '11987654321'
218
220
  * }
219
221
  * });
220
- * console.log(charge.uuid, charge.pix?.code);
222
+ * // charge.uuid, charge.pix?.code
221
223
  *
222
224
  * @example
223
225
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -236,7 +238,6 @@ var Charges = class {
236
238
  * // charge.amount is the base price; charge.chargedTotal is what was charged.
237
239
  */
238
240
  async create(params) {
239
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
240
241
  const body = {
241
242
  productId: params.productId,
242
243
  paymentMethod: params.paymentMethod,
@@ -245,7 +246,7 @@ var Charges = class {
245
246
  if (params.card) body.card = params.card;
246
247
  if (params.checkoutSessionToken) body.checkoutSessionToken = params.checkoutSessionToken;
247
248
  if (params.additionalInfo !== void 0) body.additionalInfo = params.additionalInfo;
248
- return this.post("/api/v1/charges", body, { "X-Idempotency-Key": idempotencyKey });
249
+ return this.post("/api/v1/charges", body, idempotencyHeaders(params.idempotencyKey));
249
250
  }
250
251
  /**
251
252
  * Retrieve a charge by uuid.
@@ -262,7 +263,7 @@ var Charges = class {
262
263
  *
263
264
  * @example
264
265
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
265
- * console.log(`${data.length} of ${totalCount} paid charges`);
266
+ * // data.length of totalCount paid charges
266
267
  */
267
268
  async list(params = {}) {
268
269
  const query = {};
@@ -285,6 +286,12 @@ var Charges = class {
285
286
  * charge in `refund_pending`, reaching `refunded` only once the transfer
286
287
  * settles.
287
288
  *
289
+ * For Pix/boleto (which open a refund request instead of an automated
290
+ * reversal), pass `idempotencyKey` to make a retry return the original
291
+ * request rather than opening a second one; omit it and no key is sent.
292
+ * Ignored for card, which reverses automatically and has no manual request
293
+ * to duplicate.
294
+ *
288
295
  * @example
289
296
  * await garu.charges.refund('6f1c9b2e-...'); // full
290
297
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
@@ -293,7 +300,7 @@ var Charges = class {
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, idempotencyHeaders(params.idempotencyKey));
297
304
  }
298
305
  /**
299
306
  * Cancel an unpaid charge.
@@ -318,7 +325,11 @@ var Charges = class {
318
325
  }
319
326
  post(url, body, headers) {
320
327
  return this.http.call(
321
- (signal) => this.http.client.POST(url, { body, headers, signal })
328
+ (signal) => this.http.client.POST(url, {
329
+ body,
330
+ headers,
331
+ signal
332
+ })
322
333
  );
323
334
  }
324
335
  };
@@ -330,10 +341,16 @@ var InstallmentPlans = class {
330
341
  }
331
342
  http;
332
343
  /**
333
- * Sell a product as a carnê. Auto-attaches `X-Idempotency-Key` (UUIDv4 if
334
- * you don't pass `idempotencyKey`), which matters more here than anywhere
335
- * else in the API: this call registers a REAL boleto at the bank, so a
336
- * blind retry can put two payable barcodes in one buyer's hands.
344
+ * Sell a product as a carnê.
345
+ *
346
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
347
+ * original plan for 24h. Derive it from something stable in your own
348
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
349
+ * no key is sent — the SDK does NOT invent one, because a key generated per
350
+ * call is different every time and protects nothing.
351
+ * This matters more here than anywhere else in the API: the call registers a
352
+ * REAL boleto at the bank, so a retry without a key can put two payable
353
+ * barcodes in one buyer's hands.
337
354
  *
338
355
  * @example
339
356
  * const carne = await garu.installmentPlans.create({
@@ -359,12 +376,11 @@ var InstallmentPlans = class {
359
376
  * });
360
377
  */
361
378
  async create(params) {
362
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
363
379
  const { idempotencyKey: _omit, ...body } = params;
364
380
  return this.http.call(
365
381
  (signal) => this.http.client.POST("/api/v1/installment-plans", {
366
382
  body,
367
- headers: { "X-Idempotency-Key": idempotencyKey },
383
+ headers: idempotencyHeaders(params.idempotencyKey),
368
384
  signal
369
385
  }).then((r) => r)
370
386
  );
@@ -507,6 +523,10 @@ var InstallmentPlans = class {
507
523
  * team. Transfer the money to the buyer yourself, then close it with
508
524
  * `garu.refundRequests.confirm`.
509
525
  *
526
+ * Pass `idempotencyKey` to cover the request-in-flight window; omit it and no
527
+ * key is sent. The backend already dedupes a second pending request for the
528
+ * same carnê, so this is defense-in-depth rather than the main guard.
529
+ *
510
530
  * @example
511
531
  * const request = await garu.installmentPlans.requestRefund(uuid, {
512
532
  * reason: 'Produto não entregue'
@@ -515,9 +535,11 @@ var InstallmentPlans = class {
515
535
  * request.amount; // defaults to everything the carnê collected
516
536
  */
517
537
  async requestRefund(uuid, params = {}) {
538
+ const { idempotencyKey: _omit, ...body } = params;
518
539
  return this.http.call(
519
540
  (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/refund-requests`, {
520
- body: params,
541
+ body,
542
+ headers: idempotencyHeaders(params.idempotencyKey),
521
543
  signal
522
544
  }).then((r) => r)
523
545
  );
@@ -625,6 +647,12 @@ var Customers = class {
625
647
  /**
626
648
  * Register a customer for the current seller.
627
649
  *
650
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
651
+ * original customer for 24h. Derive it from something stable in your own
652
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
653
+ * no key is sent — the SDK does NOT invent one, because a key generated per
654
+ * call is different every time and protects nothing.
655
+ *
628
656
  * @example
629
657
  * const customer = await garu.customers.create({
630
658
  * name: 'Maria Silva',
@@ -636,9 +664,11 @@ var Customers = class {
636
664
  * customer.uuid;
637
665
  */
638
666
  async create(params) {
667
+ const { idempotencyKey: _omit, ...body } = params;
639
668
  return this.http.call(
640
669
  (signal) => this.http.client.POST("/api/v1/customers", {
641
- body: params,
670
+ body,
671
+ headers: idempotencyHeaders(params.idempotencyKey),
642
672
  signal
643
673
  }).then((r) => r)
644
674
  );
@@ -883,10 +913,11 @@ var Products = class {
883
913
  * product (HTTP 201). Only `name` is required; everything else falls back
884
914
  * to seller/server defaults.
885
915
  *
886
- * Automatically attaches an `X-Idempotency-Key` header if you don't pass
887
- * `idempotencyKey`, the SDK generates a UUIDv4. This makes the built-in
888
- * retry on transient failures safe: a retried POST returns the original
889
- * product instead of creating a duplicate.
916
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
917
+ * original product for 24h. Derive it from something stable in your own
918
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
919
+ * no key is sent — the SDK does NOT invent one, because a key generated per
920
+ * call is different every time and protects nothing.
890
921
  *
891
922
  * @example
892
923
  * const product = await garu.products.create({
@@ -902,11 +933,10 @@ var Products = class {
902
933
  */
903
934
  async create(params) {
904
935
  const { idempotencyKey, ...body } = params;
905
- const key = idempotencyKey ?? generateIdempotencyKey();
906
936
  return this.http.call(
907
937
  (signal) => this.http.client.POST("/api/v1/products", {
908
938
  body,
909
- headers: { "X-Idempotency-Key": key },
939
+ headers: idempotencyHeaders(idempotencyKey),
910
940
  signal
911
941
  }).then((r) => r)
912
942
  );
@@ -942,13 +972,15 @@ var ScheduledCharges = class {
942
972
  }
943
973
  http;
944
974
  /**
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.
975
+ * Create a new scheduled charge.
976
+ *
977
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
978
+ * original series for 24h. Derive it from something stable in your own
979
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
980
+ * no key is sent — the SDK does NOT invent one, because a key generated per
981
+ * call is different every time and protects nothing.
982
+ * Worth the effort here: without a key, a retry double-books recurring
983
+ * billing for the same customer.
952
984
  *
953
985
  * @example
954
986
  * const charge = await garu.scheduledCharges.create({
@@ -975,12 +1007,11 @@ var ScheduledCharges = class {
975
1007
  * });
976
1008
  */
977
1009
  async create(params) {
978
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
979
1010
  const { idempotencyKey: _omit, ...body } = params;
980
1011
  return this.http.call(
981
1012
  (signal) => this.http.client.POST("/api/v1/scheduled-charges", {
982
1013
  body,
983
- headers: { "X-Idempotency-Key": idempotencyKey },
1014
+ headers: idempotencyHeaders(params.idempotencyKey),
984
1015
  signal
985
1016
  }).then((r) => r)
986
1017
  );
@@ -1369,11 +1400,10 @@ var WebhookEvents = class {
1369
1400
  * original — distinguishable both by the `resend_` prefix and by reading
1370
1401
  * the response payload's `manualResendOf` field.
1371
1402
  *
1372
- * The SDK also attaches an `X-Idempotency-Key` header (UUIDv4 unless you
1373
- * pass `idempotencyKey`); the gateway does not currently deduplicate
1374
- * `/resend` calls against it, so retrying this call from your own code
1375
- * after a network failure can create more than one clone — pair it with
1376
- * your own retry-suppression if that matters for your integration.
1403
+ * `idempotencyKey` is forwarded when you pass one and omitted when you don't.
1404
+ * Either way the gateway does not currently deduplicate `/resend` calls
1405
+ * against it, so retrying after a network failure can create more than one
1406
+ * clone pair it with your own retry-suppression if that matters.
1377
1407
  *
1378
1408
  * Returns the *clone* event (new uuid), not the original. The original is
1379
1409
  * unchanged on the server.
@@ -1385,11 +1415,10 @@ var WebhookEvents = class {
1385
1415
  * clone.manualResendOf === event.uuid; // true — points back at the source
1386
1416
  */
1387
1417
  async resend(uuid, params = {}) {
1388
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
1389
1418
  return this.http.call(
1390
1419
  (signal) => this.http.client.POST(`/api/v1/webhook-events/${uuid}/resend`, {
1391
1420
  body: {},
1392
- headers: { "X-Idempotency-Key": idempotencyKey },
1421
+ headers: idempotencyHeaders(params.idempotencyKey),
1393
1422
  signal
1394
1423
  }).then((r) => r)
1395
1424
  );
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;
@@ -1051,6 +1065,13 @@ interface RequestPlanRefundParams {
1051
1065
  /** Defaults to everything the carnê has collected. */
1052
1066
  amount?: number;
1053
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;
1054
1075
  }
1055
1076
  interface ListRefundRequestsParams {
1056
1077
  page?: number;
@@ -1079,9 +1100,11 @@ declare class Charges {
1079
1100
  /**
1080
1101
  * Create a charge (PIX, boleto, or credit card).
1081
1102
  *
1082
- * Attaches an `X-Idempotency-Key` header automatically if you don't pass
1083
- * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
1084
- * returns the original charge for 24h.
1103
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1104
+ * original charge for 24h. Derive it from something stable in your own
1105
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1106
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1107
+ * call is different every time and protects nothing.
1085
1108
  *
1086
1109
  * @example
1087
1110
  * // PIX — render charge.pix.code as a QR in your own checkout
@@ -1095,7 +1118,7 @@ declare class Charges {
1095
1118
  * phone: '11987654321'
1096
1119
  * }
1097
1120
  * });
1098
- * console.log(charge.uuid, charge.pix?.code);
1121
+ * // charge.uuid, charge.pix?.code
1099
1122
  *
1100
1123
  * @example
1101
1124
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -1127,7 +1150,7 @@ declare class Charges {
1127
1150
  *
1128
1151
  * @example
1129
1152
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
1130
- * console.log(`${data.length} of ${totalCount} paid charges`);
1153
+ * // data.length of totalCount paid charges
1131
1154
  */
1132
1155
  list(params?: ListChargesParams): Promise<ChargeList>;
1133
1156
  /**
@@ -1137,6 +1160,12 @@ declare class Charges {
1137
1160
  * charge in `refund_pending`, reaching `refunded` only once the transfer
1138
1161
  * settles.
1139
1162
  *
1163
+ * For Pix/boleto (which open a refund request instead of an automated
1164
+ * reversal), pass `idempotencyKey` to make a retry return the original
1165
+ * request rather than opening a second one; omit it and no key is sent.
1166
+ * Ignored for card, which reverses automatically and has no manual request
1167
+ * to duplicate.
1168
+ *
1140
1169
  * @example
1141
1170
  * await garu.charges.refund('6f1c9b2e-...'); // full
1142
1171
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
@@ -1169,10 +1198,16 @@ declare class InstallmentPlans {
1169
1198
  private readonly http;
1170
1199
  constructor(http: HttpClient);
1171
1200
  /**
1172
- * Sell a product as a carnê. Auto-attaches `X-Idempotency-Key` (UUIDv4 if
1173
- * you don't pass `idempotencyKey`), which matters more here than anywhere
1174
- * else in the API: this call registers a REAL boleto at the bank, so a
1175
- * blind retry can put two payable barcodes in one buyer's hands.
1201
+ * Sell a product as a carnê.
1202
+ *
1203
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1204
+ * original plan for 24h. Derive it from something stable in your own
1205
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1206
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1207
+ * call is different every time and protects nothing.
1208
+ * This matters more here than anywhere else in the API: the call registers a
1209
+ * REAL boleto at the bank, so a retry without a key can put two payable
1210
+ * barcodes in one buyer's hands.
1176
1211
  *
1177
1212
  * @example
1178
1213
  * const carne = await garu.installmentPlans.create({
@@ -1283,6 +1318,10 @@ declare class InstallmentPlans {
1283
1318
  * team. Transfer the money to the buyer yourself, then close it with
1284
1319
  * `garu.refundRequests.confirm`.
1285
1320
  *
1321
+ * Pass `idempotencyKey` to cover the request-in-flight window; omit it and no
1322
+ * key is sent. The backend already dedupes a second pending request for the
1323
+ * same carnê, so this is defense-in-depth rather than the main guard.
1324
+ *
1286
1325
  * @example
1287
1326
  * const request = await garu.installmentPlans.requestRefund(uuid, {
1288
1327
  * reason: 'Produto não entregue'
@@ -1372,6 +1411,12 @@ declare class Customers {
1372
1411
  /**
1373
1412
  * Register a customer for the current seller.
1374
1413
  *
1414
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1415
+ * original customer for 24h. Derive it from something stable in your own
1416
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1417
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1418
+ * call is different every time and protects nothing.
1419
+ *
1375
1420
  * @example
1376
1421
  * const customer = await garu.customers.create({
1377
1422
  * name: 'Maria Silva',
@@ -1540,10 +1585,11 @@ declare class Products {
1540
1585
  * product (HTTP 201). Only `name` is required; everything else falls back
1541
1586
  * to seller/server defaults.
1542
1587
  *
1543
- * Automatically attaches an `X-Idempotency-Key` header if you don't pass
1544
- * `idempotencyKey`, the SDK generates a UUIDv4. This makes the built-in
1545
- * retry on transient failures safe: a retried POST returns the original
1546
- * product instead of creating a duplicate.
1588
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1589
+ * original product for 24h. Derive it from something stable in your own
1590
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1591
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1592
+ * call is different every time and protects nothing.
1547
1593
  *
1548
1594
  * @example
1549
1595
  * const product = await garu.products.create({
@@ -1593,13 +1639,15 @@ declare class ScheduledCharges {
1593
1639
  private readonly http;
1594
1640
  constructor(http: HttpClient);
1595
1641
  /**
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.
1642
+ * Create a new scheduled charge.
1643
+ *
1644
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1645
+ * original series for 24h. Derive it from something stable in your own
1646
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1647
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1648
+ * call is different every time and protects nothing.
1649
+ * Worth the effort here: without a key, a retry double-books recurring
1650
+ * billing for the same customer.
1603
1651
  *
1604
1652
  * @example
1605
1653
  * const charge = await garu.scheduledCharges.create({
@@ -1866,11 +1914,10 @@ declare class WebhookEvents {
1866
1914
  * original — distinguishable both by the `resend_` prefix and by reading
1867
1915
  * the response payload's `manualResendOf` field.
1868
1916
  *
1869
- * The SDK also attaches an `X-Idempotency-Key` header (UUIDv4 unless you
1870
- * pass `idempotencyKey`); the gateway does not currently deduplicate
1871
- * `/resend` calls against it, so retrying this call from your own code
1872
- * after a network failure can create more than one clone — pair it with
1873
- * your own retry-suppression if that matters for your integration.
1917
+ * `idempotencyKey` is forwarded when you pass one and omitted when you don't.
1918
+ * Either way the gateway does not currently deduplicate `/resend` calls
1919
+ * against it, so retrying after a network failure can create more than one
1920
+ * clone pair it with your own retry-suppression if that matters.
1874
1921
  *
1875
1922
  * Returns the *clone* event (new uuid), not the original. The original is
1876
1923
  * unchanged on the server.
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;
@@ -1051,6 +1065,13 @@ interface RequestPlanRefundParams {
1051
1065
  /** Defaults to everything the carnê has collected. */
1052
1066
  amount?: number;
1053
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;
1054
1075
  }
1055
1076
  interface ListRefundRequestsParams {
1056
1077
  page?: number;
@@ -1079,9 +1100,11 @@ declare class Charges {
1079
1100
  /**
1080
1101
  * Create a charge (PIX, boleto, or credit card).
1081
1102
  *
1082
- * Attaches an `X-Idempotency-Key` header automatically if you don't pass
1083
- * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
1084
- * returns the original charge for 24h.
1103
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1104
+ * original charge for 24h. Derive it from something stable in your own
1105
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1106
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1107
+ * call is different every time and protects nothing.
1085
1108
  *
1086
1109
  * @example
1087
1110
  * // PIX — render charge.pix.code as a QR in your own checkout
@@ -1095,7 +1118,7 @@ declare class Charges {
1095
1118
  * phone: '11987654321'
1096
1119
  * }
1097
1120
  * });
1098
- * console.log(charge.uuid, charge.pix?.code);
1121
+ * // charge.uuid, charge.pix?.code
1099
1122
  *
1100
1123
  * @example
1101
1124
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -1127,7 +1150,7 @@ declare class Charges {
1127
1150
  *
1128
1151
  * @example
1129
1152
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
1130
- * console.log(`${data.length} of ${totalCount} paid charges`);
1153
+ * // data.length of totalCount paid charges
1131
1154
  */
1132
1155
  list(params?: ListChargesParams): Promise<ChargeList>;
1133
1156
  /**
@@ -1137,6 +1160,12 @@ declare class Charges {
1137
1160
  * charge in `refund_pending`, reaching `refunded` only once the transfer
1138
1161
  * settles.
1139
1162
  *
1163
+ * For Pix/boleto (which open a refund request instead of an automated
1164
+ * reversal), pass `idempotencyKey` to make a retry return the original
1165
+ * request rather than opening a second one; omit it and no key is sent.
1166
+ * Ignored for card, which reverses automatically and has no manual request
1167
+ * to duplicate.
1168
+ *
1140
1169
  * @example
1141
1170
  * await garu.charges.refund('6f1c9b2e-...'); // full
1142
1171
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
@@ -1169,10 +1198,16 @@ declare class InstallmentPlans {
1169
1198
  private readonly http;
1170
1199
  constructor(http: HttpClient);
1171
1200
  /**
1172
- * Sell a product as a carnê. Auto-attaches `X-Idempotency-Key` (UUIDv4 if
1173
- * you don't pass `idempotencyKey`), which matters more here than anywhere
1174
- * else in the API: this call registers a REAL boleto at the bank, so a
1175
- * blind retry can put two payable barcodes in one buyer's hands.
1201
+ * Sell a product as a carnê.
1202
+ *
1203
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1204
+ * original plan for 24h. Derive it from something stable in your own
1205
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1206
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1207
+ * call is different every time and protects nothing.
1208
+ * This matters more here than anywhere else in the API: the call registers a
1209
+ * REAL boleto at the bank, so a retry without a key can put two payable
1210
+ * barcodes in one buyer's hands.
1176
1211
  *
1177
1212
  * @example
1178
1213
  * const carne = await garu.installmentPlans.create({
@@ -1283,6 +1318,10 @@ declare class InstallmentPlans {
1283
1318
  * team. Transfer the money to the buyer yourself, then close it with
1284
1319
  * `garu.refundRequests.confirm`.
1285
1320
  *
1321
+ * Pass `idempotencyKey` to cover the request-in-flight window; omit it and no
1322
+ * key is sent. The backend already dedupes a second pending request for the
1323
+ * same carnê, so this is defense-in-depth rather than the main guard.
1324
+ *
1286
1325
  * @example
1287
1326
  * const request = await garu.installmentPlans.requestRefund(uuid, {
1288
1327
  * reason: 'Produto não entregue'
@@ -1372,6 +1411,12 @@ declare class Customers {
1372
1411
  /**
1373
1412
  * Register a customer for the current seller.
1374
1413
  *
1414
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1415
+ * original customer for 24h. Derive it from something stable in your own
1416
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1417
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1418
+ * call is different every time and protects nothing.
1419
+ *
1375
1420
  * @example
1376
1421
  * const customer = await garu.customers.create({
1377
1422
  * name: 'Maria Silva',
@@ -1540,10 +1585,11 @@ declare class Products {
1540
1585
  * product (HTTP 201). Only `name` is required; everything else falls back
1541
1586
  * to seller/server defaults.
1542
1587
  *
1543
- * Automatically attaches an `X-Idempotency-Key` header if you don't pass
1544
- * `idempotencyKey`, the SDK generates a UUIDv4. This makes the built-in
1545
- * retry on transient failures safe: a retried POST returns the original
1546
- * product instead of creating a duplicate.
1588
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1589
+ * original product for 24h. Derive it from something stable in your own
1590
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1591
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1592
+ * call is different every time and protects nothing.
1547
1593
  *
1548
1594
  * @example
1549
1595
  * const product = await garu.products.create({
@@ -1593,13 +1639,15 @@ declare class ScheduledCharges {
1593
1639
  private readonly http;
1594
1640
  constructor(http: HttpClient);
1595
1641
  /**
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.
1642
+ * Create a new scheduled charge.
1643
+ *
1644
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
1645
+ * original series for 24h. Derive it from something stable in your own
1646
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
1647
+ * no key is sent — the SDK does NOT invent one, because a key generated per
1648
+ * call is different every time and protects nothing.
1649
+ * Worth the effort here: without a key, a retry double-books recurring
1650
+ * billing for the same customer.
1603
1651
  *
1604
1652
  * @example
1605
1653
  * const charge = await garu.scheduledCharges.create({
@@ -1866,11 +1914,10 @@ declare class WebhookEvents {
1866
1914
  * original — distinguishable both by the `resend_` prefix and by reading
1867
1915
  * the response payload's `manualResendOf` field.
1868
1916
  *
1869
- * The SDK also attaches an `X-Idempotency-Key` header (UUIDv4 unless you
1870
- * pass `idempotencyKey`); the gateway does not currently deduplicate
1871
- * `/resend` calls against it, so retrying this call from your own code
1872
- * after a network failure can create more than one clone — pair it with
1873
- * your own retry-suppression if that matters for your integration.
1917
+ * `idempotencyKey` is forwarded when you pass one and omitted when you don't.
1918
+ * Either way the gateway does not currently deduplicate `/resend` calls
1919
+ * against it, so retrying after a network failure can create more than one
1920
+ * clone pair it with your own retry-suppression if that matters.
1874
1921
  *
1875
1922
  * Returns the *clone* event (new uuid), not the original. The original is
1876
1923
  * unchanged on the server.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import createClient from 'openapi-fetch';
2
- import { createHmac, timingSafeEqual, randomUUID } from 'crypto';
2
+ import { createHmac, timingSafeEqual } from 'crypto';
3
3
 
4
4
  // src/http.ts
5
5
 
@@ -182,8 +182,8 @@ function backoffDelay(attempt, retryAfterSec) {
182
182
  function sleep(ms) {
183
183
  return new Promise((resolve) => setTimeout(resolve, ms));
184
184
  }
185
- function generateIdempotencyKey() {
186
- return randomUUID();
185
+ function idempotencyHeaders(key) {
186
+ return key ? { "X-Idempotency-Key": key } : {};
187
187
  }
188
188
 
189
189
  // src/resources/charges.ts
@@ -195,9 +195,11 @@ var Charges = class {
195
195
  /**
196
196
  * Create a charge (PIX, boleto, or credit card).
197
197
  *
198
- * Attaches an `X-Idempotency-Key` header automatically if you don't pass
199
- * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the same key
200
- * returns the original charge for 24h.
198
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
199
+ * original charge for 24h. Derive it from something stable in your own
200
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
201
+ * no key is sent — the SDK does NOT invent one, because a key generated per
202
+ * call is different every time and protects nothing.
201
203
  *
202
204
  * @example
203
205
  * // PIX — render charge.pix.code as a QR in your own checkout
@@ -211,7 +213,7 @@ var Charges = class {
211
213
  * phone: '11987654321'
212
214
  * }
213
215
  * });
214
- * console.log(charge.uuid, charge.pix?.code);
216
+ * // charge.uuid, charge.pix?.code
215
217
  *
216
218
  * @example
217
219
  * // Credit card, 2 installments. Server-to-server only (PCI scope).
@@ -230,7 +232,6 @@ var Charges = class {
230
232
  * // charge.amount is the base price; charge.chargedTotal is what was charged.
231
233
  */
232
234
  async create(params) {
233
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
234
235
  const body = {
235
236
  productId: params.productId,
236
237
  paymentMethod: params.paymentMethod,
@@ -239,7 +240,7 @@ var Charges = class {
239
240
  if (params.card) body.card = params.card;
240
241
  if (params.checkoutSessionToken) body.checkoutSessionToken = params.checkoutSessionToken;
241
242
  if (params.additionalInfo !== void 0) body.additionalInfo = params.additionalInfo;
242
- return this.post("/api/v1/charges", body, { "X-Idempotency-Key": idempotencyKey });
243
+ return this.post("/api/v1/charges", body, idempotencyHeaders(params.idempotencyKey));
243
244
  }
244
245
  /**
245
246
  * Retrieve a charge by uuid.
@@ -256,7 +257,7 @@ var Charges = class {
256
257
  *
257
258
  * @example
258
259
  * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
259
- * console.log(`${data.length} of ${totalCount} paid charges`);
260
+ * // data.length of totalCount paid charges
260
261
  */
261
262
  async list(params = {}) {
262
263
  const query = {};
@@ -279,6 +280,12 @@ var Charges = class {
279
280
  * charge in `refund_pending`, reaching `refunded` only once the transfer
280
281
  * settles.
281
282
  *
283
+ * For Pix/boleto (which open a refund request instead of an automated
284
+ * reversal), pass `idempotencyKey` to make a retry return the original
285
+ * request rather than opening a second one; omit it and no key is sent.
286
+ * Ignored for card, which reverses automatically and has no manual request
287
+ * to duplicate.
288
+ *
282
289
  * @example
283
290
  * await garu.charges.refund('6f1c9b2e-...'); // full
284
291
  * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
@@ -287,7 +294,7 @@ var Charges = class {
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, idempotencyHeaders(params.idempotencyKey));
291
298
  }
292
299
  /**
293
300
  * Cancel an unpaid charge.
@@ -312,7 +319,11 @@ var Charges = class {
312
319
  }
313
320
  post(url, body, headers) {
314
321
  return this.http.call(
315
- (signal) => this.http.client.POST(url, { body, headers, signal })
322
+ (signal) => this.http.client.POST(url, {
323
+ body,
324
+ headers,
325
+ signal
326
+ })
316
327
  );
317
328
  }
318
329
  };
@@ -324,10 +335,16 @@ var InstallmentPlans = class {
324
335
  }
325
336
  http;
326
337
  /**
327
- * Sell a product as a carnê. Auto-attaches `X-Idempotency-Key` (UUIDv4 if
328
- * you don't pass `idempotencyKey`), which matters more here than anywhere
329
- * else in the API: this call registers a REAL boleto at the bank, so a
330
- * blind retry can put two payable barcodes in one buyer's hands.
338
+ * Sell a product as a carnê.
339
+ *
340
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
341
+ * original plan for 24h. Derive it from something stable in your own
342
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
343
+ * no key is sent — the SDK does NOT invent one, because a key generated per
344
+ * call is different every time and protects nothing.
345
+ * This matters more here than anywhere else in the API: the call registers a
346
+ * REAL boleto at the bank, so a retry without a key can put two payable
347
+ * barcodes in one buyer's hands.
331
348
  *
332
349
  * @example
333
350
  * const carne = await garu.installmentPlans.create({
@@ -353,12 +370,11 @@ var InstallmentPlans = class {
353
370
  * });
354
371
  */
355
372
  async create(params) {
356
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
357
373
  const { idempotencyKey: _omit, ...body } = params;
358
374
  return this.http.call(
359
375
  (signal) => this.http.client.POST("/api/v1/installment-plans", {
360
376
  body,
361
- headers: { "X-Idempotency-Key": idempotencyKey },
377
+ headers: idempotencyHeaders(params.idempotencyKey),
362
378
  signal
363
379
  }).then((r) => r)
364
380
  );
@@ -501,6 +517,10 @@ var InstallmentPlans = class {
501
517
  * team. Transfer the money to the buyer yourself, then close it with
502
518
  * `garu.refundRequests.confirm`.
503
519
  *
520
+ * Pass `idempotencyKey` to cover the request-in-flight window; omit it and no
521
+ * key is sent. The backend already dedupes a second pending request for the
522
+ * same carnê, so this is defense-in-depth rather than the main guard.
523
+ *
504
524
  * @example
505
525
  * const request = await garu.installmentPlans.requestRefund(uuid, {
506
526
  * reason: 'Produto não entregue'
@@ -509,9 +529,11 @@ var InstallmentPlans = class {
509
529
  * request.amount; // defaults to everything the carnê collected
510
530
  */
511
531
  async requestRefund(uuid, params = {}) {
532
+ const { idempotencyKey: _omit, ...body } = params;
512
533
  return this.http.call(
513
534
  (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/refund-requests`, {
514
- body: params,
535
+ body,
536
+ headers: idempotencyHeaders(params.idempotencyKey),
515
537
  signal
516
538
  }).then((r) => r)
517
539
  );
@@ -619,6 +641,12 @@ var Customers = class {
619
641
  /**
620
642
  * Register a customer for the current seller.
621
643
  *
644
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
645
+ * original customer for 24h. Derive it from something stable in your own
646
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
647
+ * no key is sent — the SDK does NOT invent one, because a key generated per
648
+ * call is different every time and protects nothing.
649
+ *
622
650
  * @example
623
651
  * const customer = await garu.customers.create({
624
652
  * name: 'Maria Silva',
@@ -630,9 +658,11 @@ var Customers = class {
630
658
  * customer.uuid;
631
659
  */
632
660
  async create(params) {
661
+ const { idempotencyKey: _omit, ...body } = params;
633
662
  return this.http.call(
634
663
  (signal) => this.http.client.POST("/api/v1/customers", {
635
- body: params,
664
+ body,
665
+ headers: idempotencyHeaders(params.idempotencyKey),
636
666
  signal
637
667
  }).then((r) => r)
638
668
  );
@@ -877,10 +907,11 @@ var Products = class {
877
907
  * product (HTTP 201). Only `name` is required; everything else falls back
878
908
  * to seller/server defaults.
879
909
  *
880
- * Automatically attaches an `X-Idempotency-Key` header if you don't pass
881
- * `idempotencyKey`, the SDK generates a UUIDv4. This makes the built-in
882
- * retry on transient failures safe: a retried POST returns the original
883
- * product instead of creating a duplicate.
910
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
911
+ * original product for 24h. Derive it from something stable in your own
912
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
913
+ * no key is sent — the SDK does NOT invent one, because a key generated per
914
+ * call is different every time and protects nothing.
884
915
  *
885
916
  * @example
886
917
  * const product = await garu.products.create({
@@ -896,11 +927,10 @@ var Products = class {
896
927
  */
897
928
  async create(params) {
898
929
  const { idempotencyKey, ...body } = params;
899
- const key = idempotencyKey ?? generateIdempotencyKey();
900
930
  return this.http.call(
901
931
  (signal) => this.http.client.POST("/api/v1/products", {
902
932
  body,
903
- headers: { "X-Idempotency-Key": key },
933
+ headers: idempotencyHeaders(idempotencyKey),
904
934
  signal
905
935
  }).then((r) => r)
906
936
  );
@@ -936,13 +966,15 @@ var ScheduledCharges = class {
936
966
  }
937
967
  http;
938
968
  /**
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.
969
+ * Create a new scheduled charge.
970
+ *
971
+ * Pass `idempotencyKey` to make this safe to retry: the same key returns the
972
+ * original series for 24h. Derive it from something stable in your own
973
+ * domain (an order id, a booking id) so a retry reproduces it. Omit it and
974
+ * no key is sent — the SDK does NOT invent one, because a key generated per
975
+ * call is different every time and protects nothing.
976
+ * Worth the effort here: without a key, a retry double-books recurring
977
+ * billing for the same customer.
946
978
  *
947
979
  * @example
948
980
  * const charge = await garu.scheduledCharges.create({
@@ -969,12 +1001,11 @@ var ScheduledCharges = class {
969
1001
  * });
970
1002
  */
971
1003
  async create(params) {
972
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
973
1004
  const { idempotencyKey: _omit, ...body } = params;
974
1005
  return this.http.call(
975
1006
  (signal) => this.http.client.POST("/api/v1/scheduled-charges", {
976
1007
  body,
977
- headers: { "X-Idempotency-Key": idempotencyKey },
1008
+ headers: idempotencyHeaders(params.idempotencyKey),
978
1009
  signal
979
1010
  }).then((r) => r)
980
1011
  );
@@ -1363,11 +1394,10 @@ var WebhookEvents = class {
1363
1394
  * original — distinguishable both by the `resend_` prefix and by reading
1364
1395
  * the response payload's `manualResendOf` field.
1365
1396
  *
1366
- * The SDK also attaches an `X-Idempotency-Key` header (UUIDv4 unless you
1367
- * pass `idempotencyKey`); the gateway does not currently deduplicate
1368
- * `/resend` calls against it, so retrying this call from your own code
1369
- * after a network failure can create more than one clone — pair it with
1370
- * your own retry-suppression if that matters for your integration.
1397
+ * `idempotencyKey` is forwarded when you pass one and omitted when you don't.
1398
+ * Either way the gateway does not currently deduplicate `/resend` calls
1399
+ * against it, so retrying after a network failure can create more than one
1400
+ * clone pair it with your own retry-suppression if that matters.
1371
1401
  *
1372
1402
  * Returns the *clone* event (new uuid), not the original. The original is
1373
1403
  * unchanged on the server.
@@ -1379,11 +1409,10 @@ var WebhookEvents = class {
1379
1409
  * clone.manualResendOf === event.uuid; // true — points back at the source
1380
1410
  */
1381
1411
  async resend(uuid, params = {}) {
1382
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
1383
1412
  return this.http.call(
1384
1413
  (signal) => this.http.client.POST(`/api/v1/webhook-events/${uuid}/resend`, {
1385
1414
  body: {},
1386
- headers: { "X-Idempotency-Key": idempotencyKey },
1415
+ headers: idempotencyHeaders(params.idempotencyKey),
1387
1416
  signal
1388
1417
  }).then((r) => r)
1389
1418
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/node",
3
- "version": "4.0.0",
3
+ "version": "5.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",