@timbro/payments 0.1.0 → 0.2.0-next.1

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/README.md CHANGED
@@ -1,286 +1,92 @@
1
1
  # `@timbro/payments`
2
2
 
3
- Node 24 ESM SDK for merchant Payments, their payment-linked fiscal documents, and the merchant
4
- business that onboards them.
3
+ Create a payment on your server and send the customer to Checkout. Read the Payment state from your server; signed webhooks notify you when it changes. Use `@timbro/invoices` to issue, retrieve, and deliver invoices independently. This SDK coordinates invoices with Payments and Refunds through their canonical `invoiceId`.
5
4
 
6
- `apiKey` takes either merchant key. A secret key (`sk_test_…` or `sk_live_…`) creates and reads
7
- Payments; an administrator key, which the owner creates and names in the Workspace business
8
- settings, also administers the business and its Merchant Accounts. Keys are additive: creating another one,
9
- of either kind, never retires the keys you already use.
5
+ Requires Node.js 24.19 or later and ESM. Install the stable `0.1.0` release:
10
6
 
11
- Each Payment is identified by an immutable `paymentReference` within the merchant and TEST/LIVE scope. The SDK derives a stable HTTP idempotency key from that identity and retries payment creation a bounded number of times. Optional `source` coordinates preserve an upstream order identity; `saleReference` remains a searchable business label and may repeat.
12
-
13
- Payment reads preserve that source, the canonical sale, decimal money values,
14
- Checkout URL, stable capture allocations with sanitized payment method
15
- summaries, and the fiscal recipient projection needed by an ERP.
7
+ ```sh
8
+ npm install @timbro/payments
9
+ ```
16
10
 
17
- Use the amount form when the adapter has one payable amount. Declare the tax
18
- included in that amount; Timbro does not infer a tax rate for an amount-only
19
- request. Omitting `tax` lets an explicitly configured merchant policy apply or
20
- returns an input error when no policy covers the sale. Omitting `invoicing`
21
- means no fiscal issuance is requested:
11
+ Keep the API key on your server. Its granted capabilities determine which Payment, Invoice coordination, and Business setup tasks it can perform. This example assumes the merchant has an explicit amount-only tax default configured. Timbro does not infer a tax rate. For an amount-only override, pass `tax: { includedAmount: "190.68" }`; see [taxes and sale items](https://timbro.do/en/docs/cobros/impuestos-y-articulos/) for setup and itemized sales.
22
12
 
23
13
  ```ts
24
14
  import { createTimbroPayments } from "@timbro/payments";
25
15
 
26
- const timbro = createTimbroPayments({ apiKey: process.env.TIMBRO_PAYMENTS_SECRET_KEY });
27
- const payment = await timbro.payments.create({
28
- paymentReference: "order-123-payment-1",
29
- amount: { currency: "DOP", value: "1250.00" },
30
- description: "Consumo en el comercio",
31
- tax: { includedAmount: "190.68" },
32
- });
33
- return Response.redirect(payment.checkout.url);
34
- ```
35
-
36
- Use the itemized form when the adapter has commercial lines. Each line carries
37
- its description, exact decimal price, and tax treatment. Fiscal item fields
38
- become required when invoicing needs them:
39
-
40
- ```ts
41
- await timbro.payments.create({
42
- paymentReference: "order-126-payment-1",
43
- source: { id: "odoo:pos.order:126", version: "3" },
44
- currency: "DOP",
45
- items: [{ description: "Café", quantity: "2", unitPrice: "118.00",
46
- tax: { kind: "itbis_18", included: true } }],
47
- expectedTotals: { net: "200.00", tax: "36.00", legalTip: "0.00", total: "236.00" },
16
+ const timbro = createTimbroPayments({
17
+ apiKey: process.env.TIMBRO_PAYMENTS_SECRET_KEY,
48
18
  });
49
- ```
50
19
 
51
- Timbro computes line rounding and totals once. `expectedTotals` lets the caller
52
- assert agreement with its accounting snapshot. Product IDs, line IDs, quantity,
53
- unit, and good/service kind are optional for payment-only lines; invoicing
54
- requires the fiscal details it consumes. Money fields on Payment and Refund
55
- resources use `{ currency, value }` decimal strings.
56
-
57
- Refunds are durable full or partial resources with idempotent create, retrieve,
58
- and list operations. Provider outcome and fiscal note coordination are separate.
59
- The Gateway reserves captured allocation balances before provider work begins:
20
+ export async function POST(): Promise<Response> {
21
+ const payment = await timbro.payments.create({
22
+ paymentReference: "order-123-payment-1",
23
+ amount: { currency: "DOP", value: "1250.00" },
24
+ description: "Service",
25
+ returnUrl: "https://shop.example.com/orders/123",
26
+ });
60
27
 
61
- ```ts
62
- const refund = await timbro.refunds.create({
63
- paymentId: payment.id,
64
- refundReference: "order-124-return-1",
65
- amount: { currency: "DOP", value: "125.00" },
66
- // Optional for split Payments; omit to reserve across every captured allocation.
67
- allocationIds: payment.capturedPayment?.allocations.map(({ id }) => id),
68
- });
28
+ return Response.redirect(payment.checkout.url);
29
+ }
69
30
  ```
70
31
 
71
- The Refund response keeps `allocationIds` and exact `allocationAmounts` so an
72
- ERP can reconcile one or more split-payer allocations without exceeding any
73
- captured balance. The Refund row owns the durable provider binding,
74
- submission fence, and recovery schedule. Full, unsplit DOP TEST Azul card refunds
75
- use the original capture credentials and tax, and confirm approval through
76
- `VerifyPayment` on a unique refund correlation. TEST CardNet full refunds retain
77
- the Ztrans submission and same-key inquiry path, pending provider qualification
78
- in [issue #587](https://github.com/indexa-labs/checkout/issues/587). Partial, split,
79
- live-mode, wallet, and sandbox paths remain `operator_required` before remote money movement.
80
-
81
- An authenticated ERP can attach Timbro's accepted E34 fiscal credit note after
82
- the refund request is recorded. This advances only `fiscalCoordination`; it does
83
- not claim that provider money movement succeeded:
84
-
85
- ```ts
86
- await timbro.refunds.attachFiscalCoordination({
87
- refundId: refund.id,
88
- timbroFiscalDocumentId: "timbro-operation-01h123456789abcdefgh",
89
- eNcf: "E3400000001",
90
- acceptedAt: "2026-09-11T00:00:00.000Z",
91
- idempotencyKey: "refund-fiscal-note-124-1",
92
- });
93
- ```
32
+ Replace `order-123-payment-1` with a stable reference for your order and persist it before collecting payment. Reuse the same reference and request after a timeout to recover that Payment; changing the request under that reference conflicts. `returnUrl` returns the customer from Checkout and does not confirm payment. See [retries and idempotency](https://timbro.do/en/docs/cobros/reintentos-e-idempotencia/) and [signed webhooks](https://timbro.do/en/docs/cobros/webhooks/).
94
33
 
95
- Optional `merchantAccountId` pins an authorized account for the Payment.
96
- `merchantAccounts.retrieveCheckoutCapabilities(id)` discovers its supported
97
- Checkout features. Preferences can restrict available methods, but cannot grant
98
- capabilities or bypass merchant policy. Empty `wallets` explicitly disables both
99
- wallets; omitted preferences retain the merchant's applicable defaults.
100
- `testOptions.threeDs` selects authentication for a test Payment only and cannot
101
- disable production authentication. CardNet requires its supported 3DS flow.
34
+ ## Read, cancel, or refund
102
35
 
103
- Cancel an unpaid Payment from the merchant backend with a stable command key.
104
- The operation invalidates prepared Checkout submissions immediately. Split
105
- authorizations enter provider release and the returned Payment remains
106
- `releasing` until every void is confirmed; retrieve the Payment to observe the
107
- authoritative terminal `canceled` state. The gateway emits one ordered
108
- `payment.canceled` webhook only after that terminal state is committed. A same-key `202` cancellation replay is
109
- intentionally stale while release is pending; polling the Payment is the
110
- correctness authority. A submitted or ambiguous unsplit attempt returns a
111
- conflict and stays under correlation reconciliation. Captured
112
- money is outside cancellation and requires a separate refund policy.
36
+ Retrieve a Payment from your server to read its current state:
113
37
 
114
38
  ```ts
115
- const canceled = await timbro.payments.cancel({
116
- paymentId: payment.id,
117
- });
39
+ const payment = await timbro.payments.retrieve("payment_123");
118
40
  ```
119
41
 
120
- `merchantCheckoutPolicy.retrieve()` reads tenant- and mode-scoped defaults and
121
- constraints. Administrators replace them through a revision-checked `PUT`;
122
- ordinary merchant secret keys cannot update policy. The optional `defaults.tax`
123
- selects an explicit inclusive tax treatment for amount-only requests:
42
+ Cancel only a Payment that is still unpaid and eligible for cancellation:
124
43
 
125
44
  ```ts
126
- const admin = createTimbroPayments({ apiKey: process.env.TIMBRO_ADMIN_KEY });
127
- const policy = await admin.merchantCheckoutPolicy.retrieve();
128
- await admin.merchantCheckoutPolicy.update({
129
- expectedRevision: policy.revision,
130
- defaults: {
131
- ...policy.defaults,
132
- tax: { kind: "itbis_18", included: true },
133
- },
134
- constraints: policy.constraints,
135
- });
45
+ await timbro.payments.cancel({ paymentId: "payment_123" });
136
46
  ```
137
47
 
138
- Accepted configuration is retained with the Payment; later policy edits do not
139
- reroute existing Payments. Account readiness can still withdraw permission to
140
- start a provider effect.
141
-
142
- Use `mode: "required"` when Checkout must remain open until issuance. Required and deferred invoicing require the merchant's real invoice and order references. `externally_coordinated` records the payer recipient choice and lets the authenticated ERP supply those identifiers later.
143
-
144
- ```ts
145
- await timbro.payments.create({
146
- paymentReference: "order-124-payment-1",
147
- source: { id: "odoo:pos.order:124", version: "9" },
148
- currency: "DOP",
149
- items: [{
150
- id: "meal", description: "Meal", quantity: "1", unit: "unit", unitPrice: "1250.00", kind: "good",
151
- tax: { kind: "itbis_18", included: true },
152
- }],
153
- expectedTotals: { net: "1059.32", tax: "190.68", legalTip: "0.00", total: "1250.00" },
154
- invoicing: { mode: "required", type: "consumer", merchantInvoiceNumber: "INV-124", merchantOrderNumber: "124" },
155
- });
156
- ```
157
-
158
- Use `mode: "deferred"` when Checkout owns issuance and retries, but the paid receipt may be shown before issuance finishes. The same complete immutable sale and invoicing intent is captured before provider I/O.
159
-
160
- Refunds use one resource for the provider money movement and the separate fiscal-note coordination state. An ERP or POS supplies its immutable source coordinate and exact captured allocation attribution:
48
+ Refund only after capture. Omitting `amount` reserves the remaining refundable balance once:
161
49
 
162
50
  ```ts
163
51
  const refund = await timbro.refunds.create({
164
- paymentId: payment.id,
165
- source: { id: "odoo:pos.order:124:return:1", version: "1" },
166
- refundReference: "RETURN-124-1",
167
- amount: { currency: "DOP", value: "500.00" },
168
- allocationIds: [payment.capturedPayment!.allocations[0]!.id],
52
+ paymentId: "payment_123",
53
+ refundReference: "order-123-refund-1",
169
54
  });
170
-
171
- if (refund.providerOutcome.status === "succeeded") {
172
- await timbro.refunds.attachFiscalCoordination({
173
- refundId: refund.id,
174
- timbroFiscalDocumentId: "fiscal_document_123",
175
- eNcf: "E340000000001",
176
- acceptedAt: new Date().toISOString(),
177
- idempotencyKey: "refund-order-124-return-1-e34",
178
- });
179
- }
180
55
  ```
181
56
 
182
- `timbro.refunds.create()` also accepts an omitted `amount`, which reserves the
183
- remaining refundable balance once. Repeating the same `refundReference` returns that
184
- same reservation. `processing` means provider submission can still begin,
185
- `verifying` means an ambiguous submission is being checked without
186
- resubmission, and `operator_required` means staff must complete or reconcile
187
- the money movement. Provider outcome and fiscal E34 acceptance stay
188
- independent.
189
-
190
- Full, unsplit DOP TEST Azul card refunds submit against the original captured transaction and accepted tax, then confirm the refund amount through `VerifyPayment`. Azul allows one refund per settled transaction within six months; this implementation supports a full refund. CardNet Ztrans full refunds have deterministic recovery tests, while provider refund, inquiry, and settlement qualification remains open in [issue #587](https://github.com/indexa-labs/checkout/issues/587). Partial, split, live-mode, wallet, and sandbox refund paths reserve the requested captured balance and return `operator_required` before remote provider I/O.
191
-
192
- Before collecting the rest of a merchant's onboarding details, verify its RNC
193
- against the current DGII directory snapshot. The operation is read-only and
194
- returns the canonical taxpayer name for the form to use. Either merchant key
195
- calls it:
57
+ Before the customer submits a payment, choose its Invoice type and buyer:
196
58
 
197
59
  ```ts
198
- const verification = await timbro.merchantOnboarding.verifyRnc({ rnc: "101000155" });
199
- // verification.legalName is the Directory's registered name.
200
- ```
201
-
202
- ## Onboard the merchant business
203
-
204
- The business belongs to the key's tenant, so no call names it. Claiming the business and handing
205
- over the certificate file stay with the person who holds them; everything else is one resource.
206
- Read it, act on the first requirement your integration owns, and read it again:
207
-
208
- ```ts
209
- const admin = createTimbroPayments({ apiKey: process.env.TIMBRO_ADMIN_KEY });
210
-
211
- let business = await admin.merchantBusiness.retrieve();
212
- business = await admin.merchantBusiness.update({
213
- capabilities: { electronicInvoicing: { requested: true } },
214
- fiscalDetails: {
215
- commercialName: "Café Duarte",
216
- address: "Calle El Conde 101",
217
- location: { municipalityCode: "320100" },
218
- phone: "+18095550142",
219
- defaultIncomeType: "01",
220
- },
221
- });
222
- business = await admin.merchantBusiness.importSigningCertificate({
223
- certificateP12Base64: p12.toString("base64"),
224
- password: certificatePassword,
60
+ await timbro.payments.updateInvoiceChoice({
61
+ paymentId: payment.id,
62
+ idempotencyKey: "buyer-1",
63
+ type: "E31",
64
+ buyer: { kind: "domestic", taxId: "130123456" },
225
65
  });
226
- // Timbro sends the TesteCF document itself; poll until DGII answers.
227
- business = await admin.merchantBusiness.retrieve();
228
- if (business.capabilities.electronicInvoicing.status === "active") {
229
- const { credential, fiscalOrigin } = await admin.merchantBusiness.issueSandboxCredential({
230
- idempotencyKey: "sandbox-credential-1",
231
- });
232
- }
233
66
  ```
234
67
 
235
- Each entry of `business.requirements` names its `owner`: `merchant` entries are yours to resolve,
236
- and `field` points into the representation when a field answers it. `dgii` and `timbro` entries
237
- wait, `provider` entries wait on the acquirer, and `operator` entries need Timbro support. `update` replaces each member it carries, and
238
- an `expectedRevision` refuses a stale write with `merchant_business_revision_conflict`.
239
- Electronic invoicing onboards against DGII's TesteCF environment only. The certificate import is
240
- the one call that carries the PKCS#12 archive; send it only from a machine that already holds the
241
- file, and do not resend it once `fiscalSetup.signingCertificate.sha256` matches.
242
- `retryTestFiscalDocument()` sends a new TesteCF document only when none is processing or
243
- accepted. `issueSandboxCredential` returns the ERP credential once and retires the one issued
244
- before it; the same `idempotencyKey` replays it.
245
-
246
- ## Every published operation
68
+ Use `buyer: { kind: "anonymous" }` to clear an earlier recipient. The operation preserves locked choices and refuses changes after submission or split creation.
247
69
 
248
- `timbro.client` is the typed low-level client for every operation in the published document,
249
- with the same key and transport. Requests, responses, and Problem bodies are typed from the
250
- generated `paths`, `components`, and `operations`, which the package also exports:
70
+ For a partial refund, pass exact `amount` and, when returning a voluntary tip, `voluntaryTipAmount`, each as `{ currency, value }`. Omitting the tip portion declares zero; refunding the entire remaining balance includes its remaining tip. `Refund.voluntaryTipAmount` records the reserved portion. Issue the credit note through the Invoices API for the commercial portion, then associate it separately:
251
71
 
252
72
  ```ts
253
- import { createTimbroPayments, type components } from "@timbro/payments";
254
-
255
- const { data, error, response } = await timbro.client.POST("/v1/api_keys/rotate", {
256
- params: { header: { "idempotency-key": "rotate-2026-09" } },
73
+ await timbro.refunds.attachInvoice({
74
+ refundId: refund.id,
75
+ invoiceId: creditNote.id,
76
+ idempotencyKey: "order-123-credit-note",
77
+ receipt: { id: receipt.id, accessToken: receipt.accessToken },
257
78
  });
258
- if (error !== undefined) {
259
- console.error(response.status, error.code);
260
- }
261
- type Requirement = components["schemas"]["MerchantBusinessRequirement"];
262
79
  ```
263
80
 
264
- The facade covers every published merchant operation except `rotateApiKey` and
265
- `chooseFiscalDocument`, which the client reaches directly.
81
+ Use the restricted receipt when the ERP owns the Invoice through an independent Source. The Gateway verifies the original Invoice, issuer, environment, currency, acceptance, and amount before setting `Refund.invoiceId`; it never changes the provider outcome.
266
82
 
267
- ## From a terminal
83
+ Reuse a `refundReference` for retries. Automatic refunds currently cover full, unsplit DOP card payments in TEST with Azul. CardNet refund qualification remains open in [issue #587](https://github.com/indexa-labs/checkout/issues/587); partial, split, LIVE, wallet, and sandbox refunds require an operator and return `operator_required`. See [refunds](https://timbro.do/en/docs/cobros/reembolsos/).
268
84
 
269
- The published document is served at `https://api.timbro.do/openapi.json`, so a
270
- generic OpenAPI client needs nothing from Timbro. With [Restish](https://rest.sh), register the
271
- API once and pass a test administrator key on each call, from an environment variable rather than
272
- your shell history:
273
-
274
- ```sh
275
- restish api configure timbro https://api.timbro.do
276
- restish timbro retrieve-merchant-business -H "Authorization: Bearer $TIMBRO_ADMIN_KEY"
277
- restish timbro update-merchant-business -H "Authorization: Bearer $TIMBRO_ADMIN_KEY" \
278
- 'capabilities.electronicInvoicing.requested: true'
279
- jq -n --arg p12 "$(base64 -w0 certificate.p12)" --arg password "$CERTIFICATE_PASSWORD" \
280
- '{certificateP12Base64: $p12, password: $password}' |
281
- restish timbro import-merchant-business-signing-certificate -H "Authorization: Bearer $TIMBRO_ADMIN_KEY"
282
- ```
85
+ ## Guides and API reference
283
86
 
284
- Restish names each command after the operation ID in kebab case. Timbro ships no CLI of its own.
87
+ - [Retrieve payments](https://timbro.do/en/docs/cobros/consultar-pagos/) and [payment states](https://timbro.do/en/docs/cobros/estados-del-pago/)
88
+ - [Taxes and sale items](https://timbro.do/en/docs/cobros/impuestos-y-articulos/) and [request an e-CF](https://timbro.do/en/docs/comprobantes/emitir-ecf/)
89
+ - [Webhooks](https://timbro.do/en/docs/cobros/webhooks/) and [refunds](https://timbro.do/en/docs/cobros/reembolsos/)
90
+ - [Payments API reference](https://timbro.do/en/docs/reference/payments/) and [error handling](https://timbro.do/en/docs/produccion/errores/)
285
91
 
286
- `@timbro/payments` serves payment consumers. `@timbro/sdk` is the consumer-neutral fiscal SDK for ERPs, POS systems, payment systems, and other direct fiscal integrations. Neither package depends on or re-exports the other.
92
+ Use `PaymentsError.code` to classify API failures. The [Payments API reference](https://timbro.do/en/docs/reference/payments/) documents the complete surface; see [error handling](https://timbro.do/en/docs/produccion/errores/) for recovery guidance.