@garuhq/node 0.16.0 → 1.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/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,50 +217,187 @@ 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}` : ""}`;
279
+ return this.get(`/api/v1/charges${qs ? `?${qs}` : ""}`);
280
+ }
281
+ /**
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.
287
+ *
288
+ * @example
289
+ * await garu.charges.refund('6f1c9b2e-...'); // full
290
+ * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
291
+ */
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);
297
+ }
298
+ /**
299
+ * Cancel an unpaid charge.
300
+ *
301
+ * @example
302
+ * const { canceled } = await garu.charges.cancel('6f1c9b2e-...');
303
+ */
304
+ async cancel(uuid) {
305
+ return this.http.call(
306
+ (signal) => this.http.client.DELETE(
307
+ `/api/v1/charges/${encodeURIComponent(uuid)}`,
308
+ { signal }
309
+ )
310
+ );
311
+ }
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
+ );
323
+ }
324
+ };
325
+
326
+ // src/resources/installment-plans.ts
327
+ var InstallmentPlans = class {
328
+ constructor(http) {
329
+ this.http = http;
330
+ }
331
+ http;
332
+ /**
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.
337
+ *
338
+ * @example
339
+ * const carne = await garu.installmentPlans.create({
340
+ * productId: '40381e8e-6ee7-4b8e-9393-766a6e2109d2',
341
+ * customerId: 4821,
342
+ * installments: 12
343
+ * });
344
+ * // A R$1.200 product at fator 1,30 bills R$130,00 a month:
345
+ * carne.totalScheduled; // 1560
346
+ * carne.installmentAmount; // 130
347
+ * carne.installmentsDetail?.[0]; // parcela 1, with its barcode
348
+ *
349
+ * @example
350
+ * // Attribute the sale to an affiliate. Fixed at sale time: every later
351
+ * // parcela inherits it, so omitting it pays them nothing for the whole
352
+ * // carnê. The affiliate must already be active on this product.
353
+ * await garu.installmentPlans.create({
354
+ * productId: '40381e8e-6ee7-4b8e-9393-766a6e2109d2',
355
+ * customerId: 4821,
356
+ * installments: 6,
357
+ * firstDueDate: '2026-10-05',
358
+ * affiliateId: 5
359
+ * });
360
+ */
361
+ async create(params) {
362
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
363
+ const { idempotencyKey: _omit, ...body } = params;
364
+ return this.http.call(
365
+ (signal) => this.http.client.POST("/api/v1/installment-plans", {
366
+ body,
367
+ headers: { "X-Idempotency-Key": idempotencyKey },
368
+ signal
369
+ }).then((r) => r)
370
+ );
371
+ }
372
+ /**
373
+ * List carnês, newest first. `dueFrom`/`dueTo` filter on the FIRST
374
+ * parcela's due date, which is what identifies the plan; filtering on every
375
+ * parcela would return one carnê twelve times.
376
+ *
377
+ * @example
378
+ * const atRisk = await garu.installmentPlans.list({ status: 'defaulted' });
379
+ *
380
+ * @example
381
+ * const live = await garu.installmentPlans.list({
382
+ * status: ['active', 'pending_activation'],
383
+ * customerId: 4821,
384
+ * limit: 50
385
+ * });
386
+ */
387
+ async list(params = {}) {
388
+ const qs = new URLSearchParams();
389
+ if (params.page !== void 0) qs.set("page", String(params.page));
390
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
391
+ if (params.customerId !== void 0) qs.set("customerId", String(params.customerId));
392
+ if (params.productId) qs.set("productId", params.productId);
393
+ if (params.dueFrom) qs.set("dueFrom", params.dueFrom);
394
+ if (params.dueTo) qs.set("dueTo", params.dueTo);
395
+ if (params.status) {
396
+ const statuses = Array.isArray(params.status) ? params.status : [params.status];
397
+ for (const s of statuses) qs.append("status", s);
398
+ }
399
+ const query = qs.toString();
400
+ const url = `/api/v1/installment-plans${query ? `?${query}` : ""}`;
269
401
  return this.http.call(
270
402
  (signal) => this.http.client.GET(url, { signal }).then(
271
403
  (r) => r
@@ -273,60 +405,214 @@ var Charges = class {
273
405
  );
274
406
  }
275
407
  /**
276
- * Fetch a single charge by numeric ID.
408
+ * Retrieve one carnê with every parcela: due date, status, barcode line and
409
+ * boleto PDF.
277
410
  *
278
411
  * @example
279
- * const charge = await garu.charges.get(4472);
280
- * if (charge.status === 'paid') { ... }
412
+ * const carne = await garu.installmentPlans.get(uuid);
413
+ * const unpaid = carne.installmentsDetail?.filter((i) => i.status !== 'paid');
414
+ * carne.totalCollected; // what has actually cleared, not what was billed
281
415
  */
282
- async get(id) {
416
+ async get(uuid) {
283
417
  return this.http.call(
284
- (signal) => this.http.client.GET("/api/transactions/{id}", {
285
- params: { path: { id } },
286
- signal
287
- })
418
+ (signal) => this.http.client.GET(`/api/v1/installment-plans/${uuid}`, { signal }).then(
419
+ (r) => r
420
+ )
421
+ );
422
+ }
423
+ /**
424
+ * Issue a segunda via for one parcela, once the current slip has expired.
425
+ *
426
+ * A boleto stays payable at any bank until its due date plus five days, so
427
+ * Garu refuses while the old barcode is still live — two live barcodes for
428
+ * one parcela is how a buyer pays it twice. Once per parcela per day.
429
+ *
430
+ * @example
431
+ * const result = await garu.installmentPlans.reissueInstallment(uuid, 4);
432
+ * if (result.status === 'emitted') {
433
+ * send(result.installment!.boleto!.barcodeLine);
434
+ * }
435
+ */
436
+ async reissueInstallment(uuid, number) {
437
+ return this.http.call(
438
+ (signal) => this.http.client.POST(
439
+ `/api/v1/installment-plans/${uuid}/installments/${number}/reissue`,
440
+ { signal }
441
+ ).then((r) => r)
288
442
  );
289
443
  }
290
444
  /**
291
- * Refund a charge fully, or partially by passing `amount` in centavos.
445
+ * Move one parcela to a later date. Its siblings keep theirs — this
446
+ * postpones a payment, it does not restructure the carnê. A slip already
447
+ * emitted stays payable on its original date until it expires.
292
448
  *
293
449
  * @example
294
- * // Full refund
295
- * await garu.charges.refund(4472);
450
+ * await garu.installmentPlans.postponeInstallment(uuid, 4, {
451
+ * newDueDate: '2026-12-20'
452
+ * });
453
+ */
454
+ async postponeInstallment(uuid, number, params) {
455
+ return this.http.call(
456
+ (signal) => this.http.client.POST(
457
+ `/api/v1/installment-plans/${uuid}/installments/${number}/postpone`,
458
+ { body: params, signal }
459
+ ).then((r) => r)
460
+ );
461
+ }
462
+ /**
463
+ * Record a parcela as paid, for when the buyer paid the slip but the
464
+ * webhook never arrived.
465
+ *
466
+ * Garu asks the provider to confirm the charge really compensated before
467
+ * recording it, because this settles the transaction and pays affiliate and
468
+ * co-producer commissions. A provider outage refuses the action rather than
469
+ * trusting the assertion.
296
470
  *
297
471
  * @example
298
- * // Partial refund of R$ 10,00
299
- * await garu.charges.refund(4472, { amount: 1000, reason: 'customer_request' });
472
+ * const parcela = await garu.installmentPlans.markInstallmentPaid(uuid, 3);
473
+ * parcela.status; // 'paid'
300
474
  */
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;
475
+ async markInstallmentPaid(uuid, number) {
306
476
  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 },
477
+ (signal) => this.http.client.POST(
478
+ `/api/v1/installment-plans/${uuid}/installments/${number}/mark-paid`,
479
+ { signal }
480
+ ).then((r) => r)
481
+ );
482
+ }
483
+ /**
484
+ * Cancel the carnê. Emission and reminders stop and open slips are
485
+ * cancelled at the provider.
486
+ *
487
+ * Money already collected is NOT returned — open a refund request for that.
488
+ * A cancelled carnê is never revived by a late payment; that money opens a
489
+ * refund request instead.
490
+ *
491
+ * @example
492
+ * await garu.installmentPlans.cancel(uuid, { note: 'Comprador desistiu' });
493
+ */
494
+ async cancel(uuid, params = {}) {
495
+ return this.http.call(
496
+ (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/cancel`, {
497
+ body: params,
311
498
  signal
312
- })
499
+ }).then((r) => r)
313
500
  );
314
501
  }
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;
502
+ /**
503
+ * Ask for this carnê to be refunded.
504
+ *
505
+ * Garu does NOT move the money. A boleto cannot be reversed and the funds
506
+ * already settled to you, so this records the request and notifies your
507
+ * team. Transfer the money to the buyer yourself, then close it with
508
+ * `garu.refundRequests.confirm`.
509
+ *
510
+ * @example
511
+ * const request = await garu.installmentPlans.requestRefund(uuid, {
512
+ * reason: 'Produto não entregue'
513
+ * });
514
+ * request.status; // 'pending' — nothing has moved yet
515
+ * request.amount; // defaults to everything the carnê collected
516
+ */
517
+ async requestRefund(uuid, params = {}) {
518
+ return this.http.call(
519
+ (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/refund-requests`, {
520
+ body: params,
521
+ signal
522
+ }).then((r) => r)
523
+ );
524
+ }
525
+ };
526
+
527
+ // src/resources/refund-requests.ts
528
+ var RefundRequests = class {
529
+ constructor(http) {
530
+ this.http = http;
531
+ }
532
+ http;
533
+ /**
534
+ * List refund requests, newest first. Covers carnê and Pix/boleto alike.
535
+ *
536
+ * @example
537
+ * // Everything you still owe a buyer.
538
+ * const owed = await garu.refundRequests.list({ status: 'pending' });
539
+ * const total = owed.data.reduce((sum, r) => sum + r.amount, 0);
540
+ *
541
+ * @example
542
+ * const forThisCarne = await garu.refundRequests.list({ planId: carne.uuid });
543
+ */
544
+ async list(params = {}) {
545
+ const qs = new URLSearchParams();
546
+ if (params.page !== void 0) qs.set("page", String(params.page));
547
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
548
+ if (params.planId) qs.set("planId", params.planId);
549
+ if (params.chargeId) qs.set("chargeId", params.chargeId);
550
+ if (params.status) {
551
+ const statuses = Array.isArray(params.status) ? params.status : [params.status];
552
+ for (const s of statuses) qs.append("status", s);
327
553
  }
328
- if (params.cardInfo) body.CardInfo = params.cardInfo;
329
- return body;
554
+ const query = qs.toString();
555
+ const url = `/api/v1/refund-requests${query ? `?${query}` : ""}`;
556
+ return this.http.call(
557
+ (signal) => this.http.client.GET(url, { signal }).then(
558
+ (r) => r
559
+ )
560
+ );
561
+ }
562
+ /**
563
+ * Retrieve one refund request.
564
+ *
565
+ * @example
566
+ * const request = await garu.refundRequests.get(uuid);
567
+ * request.installmentPlanId ?? request.chargeId; // exactly one is set
568
+ */
569
+ async get(uuid) {
570
+ return this.http.call(
571
+ (signal) => this.http.client.GET(`/api/v1/refund-requests/${uuid}`, { signal }).then(
572
+ (r) => r
573
+ )
574
+ );
575
+ }
576
+ /**
577
+ * Record that you returned the money. Call this AFTER transferring it.
578
+ *
579
+ * Confirming closes a carnê as refunded, stops remaining parcelas, cancels
580
+ * open slips at the provider and claws back the affiliate and co-producer
581
+ * commissions on the parcelas that cleared. For a Pix or boleto charge it
582
+ * marks the charge reversed and fires `transaction.refunded`. Idempotent:
583
+ * confirming twice does not claw back twice.
584
+ *
585
+ * @example
586
+ * // 1. You send the money to the buyer, out of band.
587
+ * // 2. Then tell Garu it happened.
588
+ * await garu.refundRequests.confirm(uuid, {
589
+ * note: 'Pix devolvido em 14/08, e2e E12345678'
590
+ * });
591
+ */
592
+ async confirm(uuid, params = {}) {
593
+ return this.http.call(
594
+ (signal) => this.http.client.POST(`/api/v1/refund-requests/${uuid}/confirm`, {
595
+ body: params,
596
+ signal
597
+ }).then((r) => r)
598
+ );
599
+ }
600
+ /**
601
+ * Decline the request. The carnê is untouched and keeps running.
602
+ * Idempotent.
603
+ *
604
+ * @example
605
+ * await garu.refundRequests.reject(uuid, {
606
+ * note: 'Produto entregue e retirado na loja em 02/08'
607
+ * });
608
+ */
609
+ async reject(uuid, params = {}) {
610
+ return this.http.call(
611
+ (signal) => this.http.client.POST(`/api/v1/refund-requests/${uuid}/reject`, {
612
+ body: params,
613
+ signal
614
+ }).then((r) => r)
615
+ );
330
616
  }
331
617
  };
332
618
 
@@ -1173,6 +1459,10 @@ var DEFAULT_MAX_RETRIES = 2;
1173
1459
  var SDK_VERSION = "0.11.1";
1174
1460
  var Garu = class {
1175
1461
  charges;
1462
+ /** Boleto parcelado (carnê): one product sold as N monthly bank slips. */
1463
+ installmentPlans;
1464
+ /** Refunds Garu has been asked to make and cannot make for you. */
1465
+ refundRequests;
1176
1466
  customers;
1177
1467
  meta;
1178
1468
  products;
@@ -1194,6 +1484,8 @@ var Garu = class {
1194
1484
  fetch: options.fetch
1195
1485
  });
1196
1486
  this.charges = new Charges(http);
1487
+ this.installmentPlans = new InstallmentPlans(http);
1488
+ this.refundRequests = new RefundRequests(http);
1197
1489
  this.customers = new Customers(http);
1198
1490
  this.meta = new Meta(http);
1199
1491
  this.products = new Products(http);