@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 +29 -0
- package/dist/index.cjs +299 -0
- package/dist/index.d.cts +370 -8
- package/dist/index.d.ts +370 -8
- package/dist/index.js +299 -0
- package/package.json +1 -1
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);
|