@garuhq/node 0.15.0 → 1.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,107 @@
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
+ ## [1.0.0] — 2026-07-23
7
+
8
+ First stable release. **Breaking:** `charges` now targets the versioned public
9
+ API `/api/v1/charges`, keyed on `uuid`. If you use `garu.charges.*`, read the
10
+ migration below. Nothing else (products, customers, scheduled-charges,
11
+ webhook-events) changed.
12
+
13
+ ### Breaking
14
+
15
+ - **`charges` moved to `/api/v1/charges`** and a charge is keyed by **`uuid`**,
16
+ not a numeric `id`.
17
+ - `charges.get(id: number)` → **`charges.retrieve(uuid: string)`**.
18
+ - `charge.id` → **`charge.uuid`**.
19
+ - **`create` takes a v1 body.** `paymentMethod` uses `'creditCard'` (was
20
+ `'credit_card'`); card data goes under **`card`** (was `cardInfo`) as
21
+ `{ number, holderName, expirationDate, cvv, installments }` (was `cardNumber`).
22
+ Removed the unused `link`, `affiliateId`, `priceId` params.
23
+ - **New response shape** (`Charge`), mirroring the API:
24
+ - `amount` is now the product **base price**; the amount actually charged is
25
+ the new **`chargedTotal`** (they differ on installment card sales). Reconcile
26
+ on `chargedTotal`.
27
+ - `date`/`deadline` → **`createdAt`/`expiresAt`** (`expiresAt` is null for PIX
28
+ and card, set only for boleto).
29
+ - `paymentMethodId` → **`paymentMethod`** (`'pix' | 'boleto' | 'creditCard'`).
30
+ - New method blocks: `pix.code`, `boleto.{barcodeLine,pdfUrl}`,
31
+ `card.{brand,last4,authorizationCode}`, `refund.{amount,reason,refundedAt}`.
32
+ - **`status` is a friendly, stable set:** `pending`, `authorized`, `paid`,
33
+ `failed`, `expired`, `canceled`, `refund_pending`, `refunded`, `chargeback`.
34
+ The raw processor values (`payedPix`, `captured`, …) are gone. Note the
35
+ spelling `canceled` (one `l`).
36
+ - **`list` returns `{ data, count, totalCount, totalPages }`** (was
37
+ `{ data, meta }`), and gains `productId`, `createdAfter`, `createdBefore`,
38
+ `sort` filters.
39
+ - **`refund` amount is in reais**, not centavos, and no longer takes an
40
+ `idempotencyKey`. New **`charges.cancel(uuid)`** for unpaid charges.
41
+ - Removed the now-unused exports `PaymentMethod`, `WirePaymentMethodId`,
42
+ `CardInfo`, `toWirePaymentMethod`. Use `ChargePaymentMethod` and `CardInput`.
43
+
44
+ ### Fixed
45
+
46
+ - `charges.create()` now returns a usable charge. It previously read the raw
47
+ `/api/transactions` envelope, leaving `charge.id` undefined.
48
+ - Refund amount is reais across code, types and the README (a `1000`-for-R$10,00
49
+ example is gone). Carried over from the 0.16.x fix.
50
+
51
+ ### Migration
52
+
53
+ ```ts
54
+ // before (0.16.x)
55
+ const c = await garu.charges.create({
56
+ productId, paymentMethod: 'credit_card', customer,
57
+ cardInfo: { cardNumber: '4111…', cvv, expirationDate, holderName, installments: 2 }
58
+ });
59
+ c.id; // number
60
+ c.paymentMethodId; // 'creditcard'
61
+ const one = await garu.charges.get(c.id);
62
+ await garu.charges.refund(c.id, { amount: 1000 }); // "R$10,00" (bug: reais)
63
+
64
+ // after (1.0.0)
65
+ const c = await garu.charges.create({
66
+ productId, paymentMethod: 'creditCard', customer,
67
+ card: { number: '4111…', cvv, expirationDate, holderName, installments: 2 }
68
+ });
69
+ c.uuid; // string
70
+ c.paymentMethod; // 'creditCard'
71
+ c.chargedTotal; // what was actually charged
72
+ const one = await garu.charges.retrieve(c.uuid);
73
+ await garu.charges.refund(c.uuid, { amount: 10.0 }); // R$10,00
74
+ ```
75
+
76
+ ## [0.16.0] — 2026-07-18
77
+
78
+ ### Changed
79
+
80
+ - **Products now use the versioned public API `/api/v1/products`.** Every product
81
+ method (`list`, `get`, `create`, `update`, `portalConfig.*`) moved from the
82
+ un-versioned `/api/products/*` (dashboard) paths to `/api/v1/products`. Method
83
+ signatures are unchanged — `get`/`update`/portal-config still accept the product
84
+ UUID (recommended) or the legacy numeric id.
85
+ - **`Product.value` is decimal reais (BRL), not centavos.** The type comments and
86
+ create/update examples wrongly said centavos — following them created a product
87
+ priced 100× (`4990` → R$ 4.990,00 instead of R$ 49,90). No behavior change: the
88
+ SDK already sent `value` as-is. Charges/refunds `amount` remain centavos.
89
+ - **`ProductList` is the real flat shape** `{ data, count, totalCount, totalPages }`
90
+ (previously mistyped `{ data, meta }`, which was never populated at runtime).
91
+ - **`Product.installments` is `Installment[]`** (`{ quantity, value }`) — was
92
+ `number[]`, which didn't match the API.
93
+
94
+ ### Deprecated
95
+
96
+ - **`Product.id`** — the v1 API no longer returns a numeric id; use `uuid`. It is
97
+ now optional and `undefined` on v1 responses, though still accepted as an input
98
+ identifier on `get`/`update`/portal-config.
99
+ - **`ListProductsParams.tab`** — not supported by v1 (ignored). `list()` returns
100
+ the authenticated seller's own products.
101
+
102
+ ### Migration
103
+
104
+ Read a product's `uuid`, not `.id`. Persisted numeric ids still work as **input**
105
+ identifiers, but responses now carry only `uuid`.
106
+
6
107
  ## [0.15.0] — 2026-05-31
7
108
 
8
109
  ### Added
package/README.md CHANGED
@@ -58,7 +58,7 @@ const charge = await garu.charges.create({
58
58
  }
59
59
  });
60
60
 
61
- console.log(charge.id, charge.status);
61
+ console.log(charge.uuid, charge.pix?.code);
62
62
  ```
63
63
 
64
64
  ## Setup
@@ -84,12 +84,13 @@ const garu = new Garu({
84
84
 
85
85
  ## Charges
86
86
 
87
- | Method | Description |
88
- | --------------------- | -------------------------------------------- |
89
- | `create(params)` | Create a PIX, credit-card, or boleto charge. |
90
- | `list(params?)` | List charges with pagination and filters. |
91
- | `get(id)` | Fetch a single charge by ID. |
92
- | `refund(id, params?)` | Refund a charge fully or partially. |
87
+ | Method | Description |
88
+ | ----------------------- | --------------------------------------------- |
89
+ | `create(params)` | Create a PIX, credit-card, or boleto charge. |
90
+ | `retrieve(uuid)` | Fetch a single charge by uuid. |
91
+ | `list(params?)` | List charges with pagination and filters. |
92
+ | `refund(uuid, params?)` | Refund a charge fully or partially (reais). |
93
+ | `cancel(uuid)` | Cancel an unpaid charge. |
93
94
 
94
95
  ### Create a PIX charge
95
96
 
@@ -111,13 +112,13 @@ const charge = await garu.charges.create({
111
112
  ```ts
112
113
  const charge = await garu.charges.create({
113
114
  productId: 'b3f2c1e8-6e4a-4b9f-9d1c-2a1f6c3d4e5f',
114
- paymentMethod: 'credit_card',
115
+ paymentMethod: 'creditCard',
115
116
  card: {
116
117
  number: '4111111111111111',
117
118
  holderName: 'MARIA SILVA',
118
- expirationMonth: '12',
119
- expirationYear: '2028',
120
- cvv: '123'
119
+ expirationDate: '2030-12',
120
+ cvv: '123',
121
+ installments: 2
121
122
  },
122
123
  customer: {
123
124
  name: 'Maria Silva',
@@ -131,13 +132,13 @@ const charge = await garu.charges.create({
131
132
  ### List charges
132
133
 
133
134
  ```ts
134
- const { data, meta } = await garu.charges.list({ limit: 10 });
135
+ const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 10 });
135
136
  ```
136
137
 
137
138
  ### Refund a charge
138
139
 
139
140
  ```ts
140
- await garu.charges.refund(4472, { amount: 1000 }); // partial refund (R$10.00)
141
+ await garu.charges.refund('6f1c9b2e-…', { amount: 10.0 }); // partial refund (R$10,00, reais)
141
142
  ```
142
143
 
143
144
  > [!TIP]
@@ -425,7 +426,7 @@ import {
425
426
  } from '@garuhq/node';
426
427
 
427
428
  try {
428
- await garu.charges.refund(4472, { amount: 1000 });
429
+ await garu.charges.refund('6f1c9b2e-…', { amount: 10.0 });
429
430
  } catch (err) {
430
431
  if (err instanceof GaruNotFoundError) {
431
432
  /* 404 */
package/dist/index.cjs CHANGED
@@ -192,11 +192,6 @@ function generateIdempotencyKey() {
192
192
  return crypto.randomUUID();
193
193
  }
194
194
 
195
- // src/types.ts
196
- function toWirePaymentMethod(pm) {
197
- return pm === "credit_card" ? "creditcard" : pm;
198
- }
199
-
200
195
  // src/resources/charges.ts
201
196
  var Charges = class {
202
197
  constructor(http) {
@@ -204,16 +199,16 @@ var Charges = class {
204
199
  }
205
200
  http;
206
201
  /**
207
- * Create a charge (PIX, credit card, or boleto).
202
+ * Create a charge (PIX, boleto, or credit card).
208
203
  *
209
- * Automatically attaches an `X-Idempotency-Key` header — if you don't pass
210
- * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the backend
211
- * caches the first response for 24h.
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.
212
207
  *
213
208
  * @example
214
- * // PIX charge
209
+ * // PIX — render charge.pix.code as a QR in your own checkout
215
210
  * const charge = await garu.charges.create({
216
- * productId: 'b3f2c1e8-6e4a-4b9f-9d1c-2a1f6c3d4e5f',
211
+ * productId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
217
212
  * paymentMethod: 'pix',
218
213
  * customer: {
219
214
  * name: 'Maria Silva',
@@ -222,111 +217,109 @@ var Charges = class {
222
217
  * phone: '11987654321'
223
218
  * }
224
219
  * });
225
- * // charge.id, charge.status
220
+ * console.log(charge.uuid, charge.pix?.code);
226
221
  *
227
222
  * @example
228
- * // Credit card charge, 3 installments
223
+ * // Credit card, 2 installments. Server-to-server only (PCI scope).
229
224
  * const charge = await garu.charges.create({
230
- * productId: 'b3f2c1e8-6e4a-4b9f-9d1c-2a1f6c3d4e5f',
231
- * paymentMethod: 'credit_card',
225
+ * productId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
226
+ * paymentMethod: 'creditCard',
232
227
  * customer: { name: 'Maria Silva', email: 'maria@exemplo.com.br', document: '12345678909', phone: '11987654321' },
233
- * cardInfo: {
234
- * cardNumber: '4111111111111111',
235
- * cvv: '123',
236
- * expirationDate: '2030-12',
228
+ * card: {
229
+ * number: '4111111111111111',
237
230
  * holderName: 'MARIA SILVA',
238
- * installments: 3
231
+ * expirationDate: '2030-12',
232
+ * cvv: '123',
233
+ * installments: 2
239
234
  * }
240
235
  * });
236
+ * // charge.amount is the base price; charge.chargedTotal is what was charged.
241
237
  */
242
238
  async create(params) {
243
239
  const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
244
- const body = this.buildCreateBody(params);
245
- return this.http.call(
246
- (signal) => this.http.client.POST("/api/transactions", {
247
- body,
248
- headers: { "X-Idempotency-Key": idempotencyKey },
249
- signal
250
- })
251
- );
240
+ const body = {
241
+ productId: params.productId,
242
+ paymentMethod: params.paymentMethod,
243
+ customer: params.customer
244
+ };
245
+ if (params.card) body.card = params.card;
246
+ if (params.checkoutSessionToken) body.checkoutSessionToken = params.checkoutSessionToken;
247
+ if (params.additionalInfo !== void 0) body.additionalInfo = params.additionalInfo;
248
+ return this.post("/api/v1/charges", body, { "X-Idempotency-Key": idempotencyKey });
252
249
  }
253
250
  /**
254
- * List charges for the authenticated seller, with pagination and filters.
251
+ * Retrieve a charge by uuid.
255
252
  *
256
253
  * @example
257
- * const { data, meta } = await garu.charges.list({ status: 'paid', limit: 10 });
258
- * // meta.total paid charges
254
+ * const charge = await garu.charges.retrieve('6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f');
255
+ * if (charge.status === 'paid') fulfil(charge);
256
+ */
257
+ async retrieve(uuid) {
258
+ return this.get(`/api/v1/charges/${encodeURIComponent(uuid)}`);
259
+ }
260
+ /**
261
+ * List charges for the authenticated account, newest first by default.
262
+ *
263
+ * @example
264
+ * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
265
+ * console.log(`${data.length} of ${totalCount} paid charges`);
259
266
  */
260
267
  async list(params = {}) {
261
268
  const query = {};
262
269
  if (params.page !== void 0) query.page = String(params.page);
263
270
  if (params.limit !== void 0) query.limit = String(params.limit);
264
271
  if (params.status) query.status = params.status;
265
- if (params.search) query.search = params.search;
266
272
  if (params.paymentMethod) query.paymentMethod = params.paymentMethod;
273
+ if (params.productId) query.productId = params.productId;
274
+ if (params.createdAfter) query.createdAfter = params.createdAfter;
275
+ if (params.createdBefore) query.createdBefore = params.createdBefore;
276
+ if (params.search) query.search = params.search;
277
+ if (params.sort) query.sort = params.sort;
267
278
  const qs = new URLSearchParams(query).toString();
268
- const url = `/api/transactions${qs ? `?${qs}` : ""}`;
269
- return this.http.call(
270
- (signal) => this.http.client.GET(url, { signal }).then(
271
- (r) => r
272
- )
273
- );
279
+ return this.get(`/api/v1/charges${qs ? `?${qs}` : ""}`);
274
280
  }
275
281
  /**
276
- * Fetch a single charge by numeric ID.
282
+ * Refund a charge, fully or partially. `amount` is in reais.
283
+ *
284
+ * For a Pix Automático charge the refund is a devolução: it returns with the
285
+ * charge in `refund_pending`, reaching `refunded` only once the transfer
286
+ * settles.
277
287
  *
278
288
  * @example
279
- * const charge = await garu.charges.get(4472);
280
- * if (charge.status === 'paid') { ... }
289
+ * await garu.charges.refund('6f1c9b2e-...'); // full
290
+ * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
281
291
  */
282
- async get(id) {
283
- return this.http.call(
284
- (signal) => this.http.client.GET("/api/transactions/{id}", {
285
- params: { path: { id } },
286
- signal
287
- })
288
- );
292
+ async refund(uuid, params = {}) {
293
+ const body = {};
294
+ if (params.amount !== void 0) body.amount = params.amount;
295
+ if (params.reason !== void 0) body.reason = params.reason;
296
+ return this.post(`/api/v1/charges/${encodeURIComponent(uuid)}/refund`, body);
289
297
  }
290
298
  /**
291
- * Refund a charge — fully, or partially by passing `amount` in centavos.
299
+ * Cancel an unpaid charge.
292
300
  *
293
301
  * @example
294
- * // Full refund
295
- * await garu.charges.refund(4472);
296
- *
297
- * @example
298
- * // Partial refund of R$ 10,00
299
- * await garu.charges.refund(4472, { amount: 1000, reason: 'customer_request' });
302
+ * const { canceled } = await garu.charges.cancel('6f1c9b2e-...');
300
303
  */
301
- async refund(id, params = {}) {
302
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
303
- const body = {};
304
- if (params.amount !== void 0) body.amount = params.amount;
305
- if (params.reason !== void 0) body.reason = params.reason;
304
+ async cancel(uuid) {
306
305
  return this.http.call(
307
- (signal) => this.http.client.POST("/api/transactions/{id}/refund", {
308
- params: { path: { id } },
309
- body,
310
- headers: { "X-Idempotency-Key": idempotencyKey },
311
- signal
312
- })
306
+ (signal) => this.http.client.DELETE(
307
+ `/api/v1/charges/${encodeURIComponent(uuid)}`,
308
+ { signal }
309
+ )
313
310
  );
314
311
  }
315
- buildCreateBody(params) {
316
- const body = {
317
- customer: params.customer,
318
- productId: params.productId,
319
- paymentMethodId: toWirePaymentMethod(params.paymentMethod),
320
- link: params.link ?? null,
321
- affiliateId: params.affiliateId ?? null
322
- };
323
- if (params.additionalInfo !== void 0) body.additionalInfo = params.additionalInfo;
324
- if (params.priceId !== void 0) body.priceId = params.priceId;
325
- if (params.checkoutSessionToken !== void 0) {
326
- body.checkoutSessionToken = params.checkoutSessionToken;
327
- }
328
- if (params.cardInfo) body.CardInfo = params.cardInfo;
329
- return body;
312
+ // v1 charge routes are not in the generated OpenAPI schema (it is regenerated
313
+ // from a live deploy), so these use the client's untyped path.
314
+ get(url) {
315
+ return this.http.call(
316
+ (signal) => this.http.client.GET(url, { signal })
317
+ );
318
+ }
319
+ post(url, body, headers) {
320
+ return this.http.call(
321
+ (signal) => this.http.client.POST(url, { body, headers, signal })
322
+ );
330
323
  }
331
324
  };
332
325
 
@@ -481,7 +474,7 @@ var ProductPortalConfigResource = class {
481
474
  async get(productId) {
482
475
  return this.http.call(
483
476
  (signal) => this.http.client.GET(
484
- `/api/products/${encodeURIComponent(String(productId))}/portal-config`,
477
+ `/api/v1/products/${encodeURIComponent(String(productId))}/portal-config`,
485
478
  {
486
479
  signal
487
480
  }
@@ -504,7 +497,7 @@ var ProductPortalConfigResource = class {
504
497
  async set(productId, params) {
505
498
  return this.http.call(
506
499
  (signal) => this.http.client.POST(
507
- `/api/products/${encodeURIComponent(String(productId))}/portal-config`,
500
+ `/api/v1/products/${encodeURIComponent(String(productId))}/portal-config`,
508
501
  {
509
502
  body: params,
510
503
  signal
@@ -516,7 +509,7 @@ var ProductPortalConfigResource = class {
516
509
  async patch(productId, params) {
517
510
  return this.http.call(
518
511
  (signal) => this.http.client.PATCH(
519
- `/api/products/${encodeURIComponent(String(productId))}/portal-config`,
512
+ `/api/v1/products/${encodeURIComponent(String(productId))}/portal-config`,
520
513
  {
521
514
  body: params,
522
515
  signal
@@ -535,7 +528,7 @@ var ProductPortalConfigResource = class {
535
528
  async clear(productId) {
536
529
  return this.http.call(
537
530
  (signal) => this.http.client.DELETE(
538
- `/api/products/${encodeURIComponent(String(productId))}/portal-config`,
531
+ `/api/v1/products/${encodeURIComponent(String(productId))}/portal-config`,
539
532
  {
540
533
  body: {},
541
534
  signal
@@ -556,16 +549,15 @@ var Products = class {
556
549
  * List products for the authenticated seller, with pagination and search.
557
550
  *
558
551
  * @example
559
- * const { data, meta } = await garu.products.list({ search: 'curso', limit: 10 });
552
+ * const { data, totalCount } = await garu.products.list({ search: 'curso', limit: 10 });
560
553
  */
561
554
  async list(params = {}) {
562
555
  const query = {};
563
556
  if (params.page !== void 0) query.page = String(params.page);
564
557
  if (params.limit !== void 0) query.limit = String(params.limit);
565
558
  if (params.search) query.search = params.search;
566
- if (params.tab) query.tab = params.tab;
567
559
  const qs = new URLSearchParams(query).toString();
568
- const url = `/api/products/seller${qs ? `?${qs}` : ""}`;
560
+ const url = `/api/v1/products${qs ? `?${qs}` : ""}`;
569
561
  return this.http.call(
570
562
  (signal) => this.http.client.GET(url, { signal }).then(
571
563
  (r) => r
@@ -581,7 +573,7 @@ var Products = class {
581
573
  */
582
574
  async get(uuid) {
583
575
  return this.http.call(
584
- (signal) => this.http.client.GET(`/api/products/uuid/${uuid}`, { signal }).then(
576
+ (signal) => this.http.client.GET(`/api/v1/products/${uuid}`, { signal }).then(
585
577
  (r) => r
586
578
  )
587
579
  );
@@ -599,7 +591,7 @@ var Products = class {
599
591
  * @example
600
592
  * const product = await garu.products.create({
601
593
  * name: 'Plano Mensal',
602
- * value: 4990, // R$ 49,90 in centavos
594
+ * value: 49.90, // R$ 49,90 in reais (decimal BRL), NOT centavos
603
595
  * description: 'Acesso completo à plataforma',
604
596
  * pix: true,
605
597
  * creditCard: true,
@@ -612,7 +604,7 @@ var Products = class {
612
604
  const { idempotencyKey, ...body } = params;
613
605
  const key = idempotencyKey ?? generateIdempotencyKey();
614
606
  return this.http.call(
615
- (signal) => this.http.client.POST("/api/products", {
607
+ (signal) => this.http.client.POST("/api/v1/products", {
616
608
  body,
617
609
  headers: { "X-Idempotency-Key": key },
618
610
  signal
@@ -623,19 +615,19 @@ var Products = class {
623
615
  * Update a product (partial PATCH — only the fields you pass are changed).
624
616
  * Returns the updated product.
625
617
  *
626
- * `id` accepts the numeric id or the product UUID the same identifiers
627
- * accepted elsewhere on the `/api/products/:id` path (see
618
+ * `id` accepts the product UUID (recommended) or the legacy numeric id
619
+ * both resolve on the `/api/v1/products/:id` path (see
628
620
  * {@link ProductPortalConfigResource}).
629
621
  *
630
622
  * @example
631
623
  * const updated = await garu.products.update('b3f2c1e8-6e4a-4b9f-9d1c-2a1f6c3d4e5f', {
632
- * value: 5990,
624
+ * value: 59.90, // reais (decimal BRL), NOT centavos
633
625
  * pixAutomatic: true // turn on Pix Automático for this product
634
626
  * });
635
627
  */
636
628
  async update(id, params) {
637
629
  return this.http.call(
638
- (signal) => this.http.client.PATCH(`/api/products/${encodeURIComponent(String(id))}`, {
630
+ (signal) => this.http.client.PATCH(`/api/v1/products/${encodeURIComponent(String(id))}`, {
639
631
  body: params,
640
632
  signal
641
633
  }).then((r) => r)