@garuhq/node 1.0.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/CHANGELOG.md CHANGED
@@ -3,6 +3,35 @@
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.1.0] — 2026-08-15
7
+
8
+
9
+ ### Added
10
+
11
+ - **`garu.installmentPlans` — boleto parcelado (carnê).** One product sold as N
12
+ monthly bank slips. This is seller-financed consumer credit, not a card
13
+ instalment: nobody guarantees a boleto, so a buyer who stops at parcela 4
14
+ leaves the seller with four parcelas and no recourse through Garu. Only the
15
+ first slip is registered at creation; the rest are emitted month by month and
16
+ the sale activates when parcela 1 compensates.
17
+ - `create` (auto-attaches `X-Idempotency-Key`, which matters more here than
18
+ anywhere else in the API — the call registers a real boleto, so a blind
19
+ retry hands one buyer two payable barcodes), `list`, `get`,
20
+ `reissueInstallment`, `postponeInstallment`, `markInstallmentPaid`,
21
+ `cancel`, `requestRefund`.
22
+ - `create` takes an optional `affiliateId`. It is fixed at sale time and every
23
+ later parcela inherits it, so omitting it pays that affiliate nothing for
24
+ the whole carnê.
25
+ - Read `totalCollected` against `totalScheduled`: they differ once a bank adds
26
+ multa or mora, and `totalCollected` can legitimately exceed what was billed.
27
+
28
+ - **`garu.refundRequests` — refunds Garu cannot make for you.** A boleto cannot
29
+ be reversed and Celcoin exposes no Pix devolução, so the funds already settled
30
+ to you and the return is a bank transfer only you can make. `list`, `get`,
31
+ `confirm`, `reject`. Confirming records that you _assert_ the money went back;
32
+ Garu never observes the transfer. Card and Woovi Pix are unaffected and keep
33
+ their real automated reversals via `garu.charges.refund`.
34
+
6
35
  ## [1.0.0] — 2026-07-23
7
36
 
8
37
  First stable release. **Breaking:** `charges` now targets the versioned public
package/dist/index.cjs CHANGED
@@ -323,6 +323,299 @@ var Charges = class {
323
323
  }
324
324
  };
325
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}` : ""}`;
401
+ return this.http.call(
402
+ (signal) => this.http.client.GET(url, { signal }).then(
403
+ (r) => r
404
+ )
405
+ );
406
+ }
407
+ /**
408
+ * Retrieve one carnê with every parcela: due date, status, barcode line and
409
+ * boleto PDF.
410
+ *
411
+ * @example
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
415
+ */
416
+ async get(uuid) {
417
+ return this.http.call(
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)
442
+ );
443
+ }
444
+ /**
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.
448
+ *
449
+ * @example
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.
470
+ *
471
+ * @example
472
+ * const parcela = await garu.installmentPlans.markInstallmentPaid(uuid, 3);
473
+ * parcela.status; // 'paid'
474
+ */
475
+ async markInstallmentPaid(uuid, number) {
476
+ return this.http.call(
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,
498
+ signal
499
+ }).then((r) => r)
500
+ );
501
+ }
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);
553
+ }
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
+ );
616
+ }
617
+ };
618
+
326
619
  // src/resources/customers.ts
327
620
  var Customers = class {
328
621
  constructor(http) {
@@ -1166,6 +1459,10 @@ var DEFAULT_MAX_RETRIES = 2;
1166
1459
  var SDK_VERSION = "0.11.1";
1167
1460
  var Garu = class {
1168
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;
1169
1466
  customers;
1170
1467
  meta;
1171
1468
  products;
@@ -1187,6 +1484,8 @@ var Garu = class {
1187
1484
  fetch: options.fetch
1188
1485
  });
1189
1486
  this.charges = new Charges(http);
1487
+ this.installmentPlans = new InstallmentPlans(http);
1488
+ this.refundRequests = new RefundRequests(http);
1190
1489
  this.customers = new Customers(http);
1191
1490
  this.meta = new Meta(http);
1192
1491
  this.products = new Products(http);