@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.js CHANGED
@@ -186,11 +186,6 @@ function generateIdempotencyKey() {
186
186
  return randomUUID();
187
187
  }
188
188
 
189
- // src/types.ts
190
- function toWirePaymentMethod(pm) {
191
- return pm === "credit_card" ? "creditcard" : pm;
192
- }
193
-
194
189
  // src/resources/charges.ts
195
190
  var Charges = class {
196
191
  constructor(http) {
@@ -198,16 +193,16 @@ var Charges = class {
198
193
  }
199
194
  http;
200
195
  /**
201
- * Create a charge (PIX, credit card, or boleto).
196
+ * Create a charge (PIX, boleto, or credit card).
202
197
  *
203
- * Automatically attaches an `X-Idempotency-Key` header — if you don't pass
204
- * `idempotencyKey`, the SDK generates a UUIDv4. Safe to retry: the backend
205
- * caches the first response for 24h.
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.
206
201
  *
207
202
  * @example
208
- * // PIX charge
203
+ * // PIX — render charge.pix.code as a QR in your own checkout
209
204
  * const charge = await garu.charges.create({
210
- * productId: 'b3f2c1e8-6e4a-4b9f-9d1c-2a1f6c3d4e5f',
205
+ * productId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
211
206
  * paymentMethod: 'pix',
212
207
  * customer: {
213
208
  * name: 'Maria Silva',
@@ -216,50 +211,187 @@ var Charges = class {
216
211
  * phone: '11987654321'
217
212
  * }
218
213
  * });
219
- * // charge.id, charge.status
214
+ * console.log(charge.uuid, charge.pix?.code);
220
215
  *
221
216
  * @example
222
- * // Credit card charge, 3 installments
217
+ * // Credit card, 2 installments. Server-to-server only (PCI scope).
223
218
  * const charge = await garu.charges.create({
224
- * productId: 'b3f2c1e8-6e4a-4b9f-9d1c-2a1f6c3d4e5f',
225
- * paymentMethod: 'credit_card',
219
+ * productId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
220
+ * paymentMethod: 'creditCard',
226
221
  * customer: { name: 'Maria Silva', email: 'maria@exemplo.com.br', document: '12345678909', phone: '11987654321' },
227
- * cardInfo: {
228
- * cardNumber: '4111111111111111',
229
- * cvv: '123',
230
- * expirationDate: '2030-12',
222
+ * card: {
223
+ * number: '4111111111111111',
231
224
  * holderName: 'MARIA SILVA',
232
- * installments: 3
225
+ * expirationDate: '2030-12',
226
+ * cvv: '123',
227
+ * installments: 2
233
228
  * }
234
229
  * });
230
+ * // charge.amount is the base price; charge.chargedTotal is what was charged.
235
231
  */
236
232
  async create(params) {
237
233
  const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
238
- const body = this.buildCreateBody(params);
239
- return this.http.call(
240
- (signal) => this.http.client.POST("/api/transactions", {
241
- body,
242
- headers: { "X-Idempotency-Key": idempotencyKey },
243
- signal
244
- })
245
- );
234
+ const body = {
235
+ productId: params.productId,
236
+ paymentMethod: params.paymentMethod,
237
+ customer: params.customer
238
+ };
239
+ if (params.card) body.card = params.card;
240
+ if (params.checkoutSessionToken) body.checkoutSessionToken = params.checkoutSessionToken;
241
+ if (params.additionalInfo !== void 0) body.additionalInfo = params.additionalInfo;
242
+ return this.post("/api/v1/charges", body, { "X-Idempotency-Key": idempotencyKey });
246
243
  }
247
244
  /**
248
- * List charges for the authenticated seller, with pagination and filters.
245
+ * Retrieve a charge by uuid.
249
246
  *
250
247
  * @example
251
- * const { data, meta } = await garu.charges.list({ status: 'paid', limit: 10 });
252
- * // meta.total paid charges
248
+ * const charge = await garu.charges.retrieve('6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f');
249
+ * if (charge.status === 'paid') fulfil(charge);
250
+ */
251
+ async retrieve(uuid) {
252
+ return this.get(`/api/v1/charges/${encodeURIComponent(uuid)}`);
253
+ }
254
+ /**
255
+ * List charges for the authenticated account, newest first by default.
256
+ *
257
+ * @example
258
+ * const { data, totalCount } = await garu.charges.list({ status: 'paid', limit: 50 });
259
+ * console.log(`${data.length} of ${totalCount} paid charges`);
253
260
  */
254
261
  async list(params = {}) {
255
262
  const query = {};
256
263
  if (params.page !== void 0) query.page = String(params.page);
257
264
  if (params.limit !== void 0) query.limit = String(params.limit);
258
265
  if (params.status) query.status = params.status;
259
- if (params.search) query.search = params.search;
260
266
  if (params.paymentMethod) query.paymentMethod = params.paymentMethod;
267
+ if (params.productId) query.productId = params.productId;
268
+ if (params.createdAfter) query.createdAfter = params.createdAfter;
269
+ if (params.createdBefore) query.createdBefore = params.createdBefore;
270
+ if (params.search) query.search = params.search;
271
+ if (params.sort) query.sort = params.sort;
261
272
  const qs = new URLSearchParams(query).toString();
262
- const url = `/api/transactions${qs ? `?${qs}` : ""}`;
273
+ return this.get(`/api/v1/charges${qs ? `?${qs}` : ""}`);
274
+ }
275
+ /**
276
+ * Refund a charge, fully or partially. `amount` is in reais.
277
+ *
278
+ * For a Pix Automático charge the refund is a devolução: it returns with the
279
+ * charge in `refund_pending`, reaching `refunded` only once the transfer
280
+ * settles.
281
+ *
282
+ * @example
283
+ * await garu.charges.refund('6f1c9b2e-...'); // full
284
+ * await garu.charges.refund('6f1c9b2e-...', { amount: 10.0 }); // R$10,00
285
+ */
286
+ async refund(uuid, params = {}) {
287
+ const body = {};
288
+ if (params.amount !== void 0) body.amount = params.amount;
289
+ if (params.reason !== void 0) body.reason = params.reason;
290
+ return this.post(`/api/v1/charges/${encodeURIComponent(uuid)}/refund`, body);
291
+ }
292
+ /**
293
+ * Cancel an unpaid charge.
294
+ *
295
+ * @example
296
+ * const { canceled } = await garu.charges.cancel('6f1c9b2e-...');
297
+ */
298
+ async cancel(uuid) {
299
+ return this.http.call(
300
+ (signal) => this.http.client.DELETE(
301
+ `/api/v1/charges/${encodeURIComponent(uuid)}`,
302
+ { signal }
303
+ )
304
+ );
305
+ }
306
+ // v1 charge routes are not in the generated OpenAPI schema (it is regenerated
307
+ // from a live deploy), so these use the client's untyped path.
308
+ get(url) {
309
+ return this.http.call(
310
+ (signal) => this.http.client.GET(url, { signal })
311
+ );
312
+ }
313
+ post(url, body, headers) {
314
+ return this.http.call(
315
+ (signal) => this.http.client.POST(url, { body, headers, signal })
316
+ );
317
+ }
318
+ };
319
+
320
+ // src/resources/installment-plans.ts
321
+ var InstallmentPlans = class {
322
+ constructor(http) {
323
+ this.http = http;
324
+ }
325
+ http;
326
+ /**
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.
331
+ *
332
+ * @example
333
+ * const carne = await garu.installmentPlans.create({
334
+ * productId: '40381e8e-6ee7-4b8e-9393-766a6e2109d2',
335
+ * customerId: 4821,
336
+ * installments: 12
337
+ * });
338
+ * // A R$1.200 product at fator 1,30 bills R$130,00 a month:
339
+ * carne.totalScheduled; // 1560
340
+ * carne.installmentAmount; // 130
341
+ * carne.installmentsDetail?.[0]; // parcela 1, with its barcode
342
+ *
343
+ * @example
344
+ * // Attribute the sale to an affiliate. Fixed at sale time: every later
345
+ * // parcela inherits it, so omitting it pays them nothing for the whole
346
+ * // carnê. The affiliate must already be active on this product.
347
+ * await garu.installmentPlans.create({
348
+ * productId: '40381e8e-6ee7-4b8e-9393-766a6e2109d2',
349
+ * customerId: 4821,
350
+ * installments: 6,
351
+ * firstDueDate: '2026-10-05',
352
+ * affiliateId: 5
353
+ * });
354
+ */
355
+ async create(params) {
356
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
357
+ const { idempotencyKey: _omit, ...body } = params;
358
+ return this.http.call(
359
+ (signal) => this.http.client.POST("/api/v1/installment-plans", {
360
+ body,
361
+ headers: { "X-Idempotency-Key": idempotencyKey },
362
+ signal
363
+ }).then((r) => r)
364
+ );
365
+ }
366
+ /**
367
+ * List carnês, newest first. `dueFrom`/`dueTo` filter on the FIRST
368
+ * parcela's due date, which is what identifies the plan; filtering on every
369
+ * parcela would return one carnê twelve times.
370
+ *
371
+ * @example
372
+ * const atRisk = await garu.installmentPlans.list({ status: 'defaulted' });
373
+ *
374
+ * @example
375
+ * const live = await garu.installmentPlans.list({
376
+ * status: ['active', 'pending_activation'],
377
+ * customerId: 4821,
378
+ * limit: 50
379
+ * });
380
+ */
381
+ async list(params = {}) {
382
+ const qs = new URLSearchParams();
383
+ if (params.page !== void 0) qs.set("page", String(params.page));
384
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
385
+ if (params.customerId !== void 0) qs.set("customerId", String(params.customerId));
386
+ if (params.productId) qs.set("productId", params.productId);
387
+ if (params.dueFrom) qs.set("dueFrom", params.dueFrom);
388
+ if (params.dueTo) qs.set("dueTo", params.dueTo);
389
+ if (params.status) {
390
+ const statuses = Array.isArray(params.status) ? params.status : [params.status];
391
+ for (const s of statuses) qs.append("status", s);
392
+ }
393
+ const query = qs.toString();
394
+ const url = `/api/v1/installment-plans${query ? `?${query}` : ""}`;
263
395
  return this.http.call(
264
396
  (signal) => this.http.client.GET(url, { signal }).then(
265
397
  (r) => r
@@ -267,60 +399,214 @@ var Charges = class {
267
399
  );
268
400
  }
269
401
  /**
270
- * Fetch a single charge by numeric ID.
402
+ * Retrieve one carnê with every parcela: due date, status, barcode line and
403
+ * boleto PDF.
271
404
  *
272
405
  * @example
273
- * const charge = await garu.charges.get(4472);
274
- * if (charge.status === 'paid') { ... }
406
+ * const carne = await garu.installmentPlans.get(uuid);
407
+ * const unpaid = carne.installmentsDetail?.filter((i) => i.status !== 'paid');
408
+ * carne.totalCollected; // what has actually cleared, not what was billed
275
409
  */
276
- async get(id) {
410
+ async get(uuid) {
277
411
  return this.http.call(
278
- (signal) => this.http.client.GET("/api/transactions/{id}", {
279
- params: { path: { id } },
280
- signal
281
- })
412
+ (signal) => this.http.client.GET(`/api/v1/installment-plans/${uuid}`, { signal }).then(
413
+ (r) => r
414
+ )
415
+ );
416
+ }
417
+ /**
418
+ * Issue a segunda via for one parcela, once the current slip has expired.
419
+ *
420
+ * A boleto stays payable at any bank until its due date plus five days, so
421
+ * Garu refuses while the old barcode is still live — two live barcodes for
422
+ * one parcela is how a buyer pays it twice. Once per parcela per day.
423
+ *
424
+ * @example
425
+ * const result = await garu.installmentPlans.reissueInstallment(uuid, 4);
426
+ * if (result.status === 'emitted') {
427
+ * send(result.installment!.boleto!.barcodeLine);
428
+ * }
429
+ */
430
+ async reissueInstallment(uuid, number) {
431
+ return this.http.call(
432
+ (signal) => this.http.client.POST(
433
+ `/api/v1/installment-plans/${uuid}/installments/${number}/reissue`,
434
+ { signal }
435
+ ).then((r) => r)
282
436
  );
283
437
  }
284
438
  /**
285
- * Refund a charge fully, or partially by passing `amount` in centavos.
439
+ * Move one parcela to a later date. Its siblings keep theirs — this
440
+ * postpones a payment, it does not restructure the carnê. A slip already
441
+ * emitted stays payable on its original date until it expires.
286
442
  *
287
443
  * @example
288
- * // Full refund
289
- * await garu.charges.refund(4472);
444
+ * await garu.installmentPlans.postponeInstallment(uuid, 4, {
445
+ * newDueDate: '2026-12-20'
446
+ * });
447
+ */
448
+ async postponeInstallment(uuid, number, params) {
449
+ return this.http.call(
450
+ (signal) => this.http.client.POST(
451
+ `/api/v1/installment-plans/${uuid}/installments/${number}/postpone`,
452
+ { body: params, signal }
453
+ ).then((r) => r)
454
+ );
455
+ }
456
+ /**
457
+ * Record a parcela as paid, for when the buyer paid the slip but the
458
+ * webhook never arrived.
459
+ *
460
+ * Garu asks the provider to confirm the charge really compensated before
461
+ * recording it, because this settles the transaction and pays affiliate and
462
+ * co-producer commissions. A provider outage refuses the action rather than
463
+ * trusting the assertion.
290
464
  *
291
465
  * @example
292
- * // Partial refund of R$ 10,00
293
- * await garu.charges.refund(4472, { amount: 1000, reason: 'customer_request' });
466
+ * const parcela = await garu.installmentPlans.markInstallmentPaid(uuid, 3);
467
+ * parcela.status; // 'paid'
294
468
  */
295
- async refund(id, params = {}) {
296
- const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
297
- const body = {};
298
- if (params.amount !== void 0) body.amount = params.amount;
299
- if (params.reason !== void 0) body.reason = params.reason;
469
+ async markInstallmentPaid(uuid, number) {
300
470
  return this.http.call(
301
- (signal) => this.http.client.POST("/api/transactions/{id}/refund", {
302
- params: { path: { id } },
303
- body,
304
- headers: { "X-Idempotency-Key": idempotencyKey },
471
+ (signal) => this.http.client.POST(
472
+ `/api/v1/installment-plans/${uuid}/installments/${number}/mark-paid`,
473
+ { signal }
474
+ ).then((r) => r)
475
+ );
476
+ }
477
+ /**
478
+ * Cancel the carnê. Emission and reminders stop and open slips are
479
+ * cancelled at the provider.
480
+ *
481
+ * Money already collected is NOT returned — open a refund request for that.
482
+ * A cancelled carnê is never revived by a late payment; that money opens a
483
+ * refund request instead.
484
+ *
485
+ * @example
486
+ * await garu.installmentPlans.cancel(uuid, { note: 'Comprador desistiu' });
487
+ */
488
+ async cancel(uuid, params = {}) {
489
+ return this.http.call(
490
+ (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/cancel`, {
491
+ body: params,
305
492
  signal
306
- })
493
+ }).then((r) => r)
307
494
  );
308
495
  }
309
- buildCreateBody(params) {
310
- const body = {
311
- customer: params.customer,
312
- productId: params.productId,
313
- paymentMethodId: toWirePaymentMethod(params.paymentMethod),
314
- link: params.link ?? null,
315
- affiliateId: params.affiliateId ?? null
316
- };
317
- if (params.additionalInfo !== void 0) body.additionalInfo = params.additionalInfo;
318
- if (params.priceId !== void 0) body.priceId = params.priceId;
319
- if (params.checkoutSessionToken !== void 0) {
320
- body.checkoutSessionToken = params.checkoutSessionToken;
496
+ /**
497
+ * Ask for this carnê to be refunded.
498
+ *
499
+ * Garu does NOT move the money. A boleto cannot be reversed and the funds
500
+ * already settled to you, so this records the request and notifies your
501
+ * team. Transfer the money to the buyer yourself, then close it with
502
+ * `garu.refundRequests.confirm`.
503
+ *
504
+ * @example
505
+ * const request = await garu.installmentPlans.requestRefund(uuid, {
506
+ * reason: 'Produto não entregue'
507
+ * });
508
+ * request.status; // 'pending' — nothing has moved yet
509
+ * request.amount; // defaults to everything the carnê collected
510
+ */
511
+ async requestRefund(uuid, params = {}) {
512
+ return this.http.call(
513
+ (signal) => this.http.client.POST(`/api/v1/installment-plans/${uuid}/refund-requests`, {
514
+ body: params,
515
+ signal
516
+ }).then((r) => r)
517
+ );
518
+ }
519
+ };
520
+
521
+ // src/resources/refund-requests.ts
522
+ var RefundRequests = class {
523
+ constructor(http) {
524
+ this.http = http;
525
+ }
526
+ http;
527
+ /**
528
+ * List refund requests, newest first. Covers carnê and Pix/boleto alike.
529
+ *
530
+ * @example
531
+ * // Everything you still owe a buyer.
532
+ * const owed = await garu.refundRequests.list({ status: 'pending' });
533
+ * const total = owed.data.reduce((sum, r) => sum + r.amount, 0);
534
+ *
535
+ * @example
536
+ * const forThisCarne = await garu.refundRequests.list({ planId: carne.uuid });
537
+ */
538
+ async list(params = {}) {
539
+ const qs = new URLSearchParams();
540
+ if (params.page !== void 0) qs.set("page", String(params.page));
541
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
542
+ if (params.planId) qs.set("planId", params.planId);
543
+ if (params.chargeId) qs.set("chargeId", params.chargeId);
544
+ if (params.status) {
545
+ const statuses = Array.isArray(params.status) ? params.status : [params.status];
546
+ for (const s of statuses) qs.append("status", s);
321
547
  }
322
- if (params.cardInfo) body.CardInfo = params.cardInfo;
323
- return body;
548
+ const query = qs.toString();
549
+ const url = `/api/v1/refund-requests${query ? `?${query}` : ""}`;
550
+ return this.http.call(
551
+ (signal) => this.http.client.GET(url, { signal }).then(
552
+ (r) => r
553
+ )
554
+ );
555
+ }
556
+ /**
557
+ * Retrieve one refund request.
558
+ *
559
+ * @example
560
+ * const request = await garu.refundRequests.get(uuid);
561
+ * request.installmentPlanId ?? request.chargeId; // exactly one is set
562
+ */
563
+ async get(uuid) {
564
+ return this.http.call(
565
+ (signal) => this.http.client.GET(`/api/v1/refund-requests/${uuid}`, { signal }).then(
566
+ (r) => r
567
+ )
568
+ );
569
+ }
570
+ /**
571
+ * Record that you returned the money. Call this AFTER transferring it.
572
+ *
573
+ * Confirming closes a carnê as refunded, stops remaining parcelas, cancels
574
+ * open slips at the provider and claws back the affiliate and co-producer
575
+ * commissions on the parcelas that cleared. For a Pix or boleto charge it
576
+ * marks the charge reversed and fires `transaction.refunded`. Idempotent:
577
+ * confirming twice does not claw back twice.
578
+ *
579
+ * @example
580
+ * // 1. You send the money to the buyer, out of band.
581
+ * // 2. Then tell Garu it happened.
582
+ * await garu.refundRequests.confirm(uuid, {
583
+ * note: 'Pix devolvido em 14/08, e2e E12345678'
584
+ * });
585
+ */
586
+ async confirm(uuid, params = {}) {
587
+ return this.http.call(
588
+ (signal) => this.http.client.POST(`/api/v1/refund-requests/${uuid}/confirm`, {
589
+ body: params,
590
+ signal
591
+ }).then((r) => r)
592
+ );
593
+ }
594
+ /**
595
+ * Decline the request. The carnê is untouched and keeps running.
596
+ * Idempotent.
597
+ *
598
+ * @example
599
+ * await garu.refundRequests.reject(uuid, {
600
+ * note: 'Produto entregue e retirado na loja em 02/08'
601
+ * });
602
+ */
603
+ async reject(uuid, params = {}) {
604
+ return this.http.call(
605
+ (signal) => this.http.client.POST(`/api/v1/refund-requests/${uuid}/reject`, {
606
+ body: params,
607
+ signal
608
+ }).then((r) => r)
609
+ );
324
610
  }
325
611
  };
326
612
 
@@ -1167,6 +1453,10 @@ var DEFAULT_MAX_RETRIES = 2;
1167
1453
  var SDK_VERSION = "0.11.1";
1168
1454
  var Garu = class {
1169
1455
  charges;
1456
+ /** Boleto parcelado (carnê): one product sold as N monthly bank slips. */
1457
+ installmentPlans;
1458
+ /** Refunds Garu has been asked to make and cannot make for you. */
1459
+ refundRequests;
1170
1460
  customers;
1171
1461
  meta;
1172
1462
  products;
@@ -1188,6 +1478,8 @@ var Garu = class {
1188
1478
  fetch: options.fetch
1189
1479
  });
1190
1480
  this.charges = new Charges(http);
1481
+ this.installmentPlans = new InstallmentPlans(http);
1482
+ this.refundRequests = new RefundRequests(http);
1191
1483
  this.customers = new Customers(http);
1192
1484
  this.meta = new Meta(http);
1193
1485
  this.products = new Products(http);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/node",
3
- "version": "0.16.0",
3
+ "version": "1.1.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",