@clocknext/sdk 0.17.0 → 0.19.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/README.md +77 -2
- package/dist/index.cjs +96 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +195 -21
- package/dist/index.d.ts +195 -21
- package/dist/index.js +96 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +4 -0
- package/src/index.ts +9 -1
- package/src/resources/customers.ts +54 -0
- package/src/resources/signals.ts +3 -1
- package/src/resources/taxes.ts +59 -0
- package/src/types.ts +156 -21
package/README.md
CHANGED
|
@@ -561,7 +561,8 @@ await cnk.customers.unitBalance(customerId, { unitName: "Seats" });
|
|
|
561
561
|
let cursor: string | undefined;
|
|
562
562
|
do {
|
|
563
563
|
const page = await cnk.signals.usageLog({ customerId, limit: 50, cursor });
|
|
564
|
-
// page.usageLogs — each row has a `
|
|
564
|
+
// page.usageLogs — each row has a `type`: credit | outcome | unit | wallet | composite,
|
|
565
|
+
// and `custom`: the `custom` object sent on the signal (or null)
|
|
565
566
|
cursor = page.nextCursor ?? undefined;
|
|
566
567
|
} while (cursor);
|
|
567
568
|
```
|
|
@@ -617,6 +618,43 @@ wallet.reservedBalance; // number | undefined
|
|
|
617
618
|
|
|
618
619
|
When the feature is off, `wallet.reservedBalance` is left out of `balances()`.
|
|
619
620
|
|
|
621
|
+
### Wallet ledger
|
|
622
|
+
|
|
623
|
+
`customers.wallet()` lists every movement of wallet money, newest first, with
|
|
624
|
+
both balances. Each row's `source` says what caused it:
|
|
625
|
+
|
|
626
|
+
| `source` | Meaning |
|
|
627
|
+
|---|---|
|
|
628
|
+
| `SUBSCRIPTION` | Wallet money from a new plan purchase |
|
|
629
|
+
| `RENEWAL` | Wallet money from a plan renewal, or unused money removed at renewal |
|
|
630
|
+
| `MANUAL` | Changed in the ClockNext dashboard |
|
|
631
|
+
| `API` | Changed with `adjustWallet()` |
|
|
632
|
+
| `CUSTOMER_PORTAL` | Top-up paid by the customer in the customer portal |
|
|
633
|
+
| `USAGE_LOG` | Charged by a signal; `usageLogId` links the signal |
|
|
634
|
+
| `INVOICE_SETTLEMENT` | Usage cleared by a paid pay-as-you-go invoice |
|
|
635
|
+
| `INVOICE_VOID` | Settlement undone by a voided invoice |
|
|
636
|
+
|
|
637
|
+
On a `USAGE_LOG` row, `usageLogId` is the id of the matching row in
|
|
638
|
+
`signals.usageLog()`, so each charge links to the exact signal that caused it.
|
|
639
|
+
`notes` is only text a person wrote — your own note on an `API` row, the
|
|
640
|
+
admin's note on a `MANUAL` row — and `null` on every row the system writes.
|
|
641
|
+
|
|
642
|
+
```ts
|
|
643
|
+
const page = await cnk.customers.wallet(customerId, { from: "2026-10-01", to: "2026-10-07" });
|
|
644
|
+
page.balanceUsd; // ordinary wallet, USD
|
|
645
|
+
page.reservedBalanceUsd; // Reserved Wallet, or null when the feature is off
|
|
646
|
+
|
|
647
|
+
for (const t of page.transactions) {
|
|
648
|
+
// t.type: "CREDIT" | "DEBIT"; t.amount is always positive
|
|
649
|
+
// t.entitlementType: "CREDIT" | "OUTCOME" | "UNIT" | "COMPOSITE" | null
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Every row, across all pages — pick out the signal charges:
|
|
653
|
+
for await (const t of cnk.customers.iterateWallet(customerId)) {
|
|
654
|
+
if (t.source === "USAGE_LOG") console.log(t.usageLogId, t.amountUsd);
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
620
658
|
To change how much a customer is granted **every billing cycle** (not just
|
|
621
659
|
their balance right now), use `updateRecurringGrant`. Any increase or decrease
|
|
622
660
|
applies to their balance immediately. `quantity` is USD for the wallet and a
|
|
@@ -655,9 +693,10 @@ cnk.customers.get / update / delete / iterate
|
|
|
655
693
|
cnk.customers.getByExternalId / updateByExternalId / updateRecurringGrantByExternalId
|
|
656
694
|
cnk.customers.adjustCreditByExternalId / adjustOutcomeByExternalId / adjustUnitByExternalId
|
|
657
695
|
cnk.customers.adjustCompositeByExternalId / adjustWalletByExternalId
|
|
658
|
-
cnk.customers.deleteByExternalId / balancesByExternalId / planByExternalId
|
|
696
|
+
cnk.customers.deleteByExternalId / balancesByExternalId / planByExternalId / walletByExternalId
|
|
659
697
|
cnk.customers.listMembers / addMember / updateMember / removeMember
|
|
660
698
|
cnk.customers.balances / creditBalance / outcomeBalance / unitBalance / plan
|
|
699
|
+
cnk.customers.wallet / iterateWallet
|
|
661
700
|
cnk.customers.adjustCredit / adjustOutcome / adjustUnit / adjustComposite / adjustWallet
|
|
662
701
|
cnk.customers.updateRecurringGrant
|
|
663
702
|
|
|
@@ -774,6 +813,42 @@ await cnk.units.unarchive(unit.id); // and bring it back
|
|
|
774
813
|
Models are enabled in the ClockNext dashboard. A bundle line names its model by
|
|
775
814
|
`modelId` — the same id you send as `usage.model` on a signal.
|
|
776
815
|
|
|
816
|
+
## Taxes
|
|
817
|
+
|
|
818
|
+
`cnk.taxes` manages the tax rates charged to your customers by billing country
|
|
819
|
+
— the same rates as **Settings → Taxes** in the dashboard. Every active tax for
|
|
820
|
+
a customer's country is applied to their invoices and portal purchases
|
|
821
|
+
automatically; a customer with no country is charged no tax.
|
|
822
|
+
|
|
823
|
+
It has four calls: `list()`, `get(id)`, `create(input)` and `update(id, input)`.
|
|
824
|
+
|
|
825
|
+
- **`behavior` is lower case:** `"inclusive"` (already inside the price) or
|
|
826
|
+
`"exclusive"` (added on top).
|
|
827
|
+
- **`countryCode`** is a 2-letter ISO code (`"IN"`, `"GB"`); lower case is
|
|
828
|
+
accepted, a country name is refused.
|
|
829
|
+
- **No duplicates:** two active taxes can't share a name (ignoring case) in the
|
|
830
|
+
same country — that's a `ConflictError`.
|
|
831
|
+
- **`update()` is partial**, and only affects invoices issued afterwards —
|
|
832
|
+
issued invoices keep the tax they were issued with.
|
|
833
|
+
- **Archiving is dashboard-only.** There is no `archive()` / `unarchive()` /
|
|
834
|
+
`delete()`; an archived tax reads back with `isActive: false` and can still
|
|
835
|
+
be updated.
|
|
836
|
+
|
|
837
|
+
```ts
|
|
838
|
+
const gst = await cnk.taxes.create({
|
|
839
|
+
name: "GST",
|
|
840
|
+
percentage: 18,
|
|
841
|
+
countryCode: "IN",
|
|
842
|
+
behavior: "exclusive",
|
|
843
|
+
description: "India GST",
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
const active = await cnk.taxes.list({ active: true }); // omit for archived ones too
|
|
847
|
+
const one = await cnk.taxes.get(gst.id);
|
|
848
|
+
|
|
849
|
+
await cnk.taxes.update(gst.id, { percentage: 12 }); // only the percentage changes
|
|
850
|
+
```
|
|
851
|
+
|
|
777
852
|
## Customer portal
|
|
778
853
|
|
|
779
854
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -333,7 +333,7 @@ function sleep(ms) {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// src/http.ts
|
|
336
|
-
var SDK_VERSION = "0.
|
|
336
|
+
var SDK_VERSION = "0.19.0" ;
|
|
337
337
|
var Transport = class {
|
|
338
338
|
constructor(cfg) {
|
|
339
339
|
this.cfg = cfg;
|
|
@@ -682,6 +682,47 @@ var Customers = class {
|
|
|
682
682
|
path: `/api/v1/customers/${encodeURIComponent(id)}/balances`
|
|
683
683
|
});
|
|
684
684
|
}
|
|
685
|
+
/**
|
|
686
|
+
* One page of the customer's wallet ledger — every movement of wallet
|
|
687
|
+
* money, newest first — plus both wallet balances. Cursor-paginated: pass
|
|
688
|
+
* back `nextCursor` as `cursor`. `GET /api/v1/customers/:id/wallet`.
|
|
689
|
+
*/
|
|
690
|
+
wallet(id, params = {}) {
|
|
691
|
+
return this.walletPage(`/api/v1/customers/${encodeURIComponent(id)}/wallet`, params);
|
|
692
|
+
}
|
|
693
|
+
/** {@link wallet}, addressing the customer by its `externalId`.
|
|
694
|
+
* `GET /api/v1/customers/ext/:externalId/wallet`. */
|
|
695
|
+
walletByExternalId(externalId, params = {}) {
|
|
696
|
+
return this.walletPage(
|
|
697
|
+
`/api/v1/customers/ext/${encodeURIComponent(externalId)}/wallet`,
|
|
698
|
+
params
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Async iterator over the customer's WHOLE wallet ledger, newest first,
|
|
703
|
+
* following the cursor. `params.cursor` is ignored.
|
|
704
|
+
* `for await (const t of cnk.customers.iterateWallet(id)) { … }`
|
|
705
|
+
*/
|
|
706
|
+
async *iterateWallet(id, params = {}) {
|
|
707
|
+
let cursor;
|
|
708
|
+
do {
|
|
709
|
+
const page = await this.wallet(id, { ...params, cursor });
|
|
710
|
+
for (const t of page.transactions) yield t;
|
|
711
|
+
cursor = page.nextCursor ?? void 0;
|
|
712
|
+
} while (cursor);
|
|
713
|
+
}
|
|
714
|
+
walletPage(path, params) {
|
|
715
|
+
return this.transport.request({
|
|
716
|
+
method: "GET",
|
|
717
|
+
path,
|
|
718
|
+
query: {
|
|
719
|
+
limit: params.limit,
|
|
720
|
+
cursor: params.cursor,
|
|
721
|
+
from: params.from,
|
|
722
|
+
to: params.to
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
685
726
|
/** {@link balances}, addressing the customer by its `externalId`.
|
|
686
727
|
* `GET /api/v1/customers/ext/:externalId/balances`. */
|
|
687
728
|
balancesByExternalId(externalId) {
|
|
@@ -1306,6 +1347,7 @@ var Signals = class {
|
|
|
1306
1347
|
...customerRefToWire(input),
|
|
1307
1348
|
runId: input.runId,
|
|
1308
1349
|
...input.member ? { member: input.member } : {},
|
|
1350
|
+
...input.custom ? { custom: input.custom } : {},
|
|
1309
1351
|
idempotencyKey
|
|
1310
1352
|
},
|
|
1311
1353
|
retry: true
|
|
@@ -1343,6 +1385,7 @@ var Signals = class {
|
|
|
1343
1385
|
...input.quantity != null ? { quantity: input.quantity } : {},
|
|
1344
1386
|
...input.member ? { member: input.member } : {},
|
|
1345
1387
|
...input.composite ? { composite: { [input.composite.ref]: input.composite.value } } : {},
|
|
1388
|
+
...input.custom ? { custom: input.custom } : {},
|
|
1346
1389
|
// Assigned HERE, before the send, so the SAME key rides every
|
|
1347
1390
|
// transport-level retry below. Without it a single 500 followed by a
|
|
1348
1391
|
// success is TWO un-keyed reports, and the server dedups on the key —
|
|
@@ -1397,6 +1440,7 @@ var Signals = class {
|
|
|
1397
1440
|
body: {
|
|
1398
1441
|
...customerRefToWire(input),
|
|
1399
1442
|
composite: { [input.composite.ref]: input.composite.value },
|
|
1443
|
+
...input.custom ? { custom: input.custom } : {},
|
|
1400
1444
|
// Auto-assigned like the others. A retry here could not double-BILL
|
|
1401
1445
|
// even without it — closing a closed occurrence is a server-side
|
|
1402
1446
|
// no-op — but that safety comes from occurrence-level dedup, not from
|
|
@@ -1441,7 +1485,6 @@ var Signals = class {
|
|
|
1441
1485
|
customerId: params.customerId,
|
|
1442
1486
|
limit: params.limit,
|
|
1443
1487
|
cursor: params.cursor,
|
|
1444
|
-
period: params.period,
|
|
1445
1488
|
from: params.from,
|
|
1446
1489
|
to: params.to
|
|
1447
1490
|
}
|
|
@@ -1513,6 +1556,54 @@ var Units = class {
|
|
|
1513
1556
|
}
|
|
1514
1557
|
};
|
|
1515
1558
|
|
|
1559
|
+
// src/resources/taxes.ts
|
|
1560
|
+
var Taxes = class {
|
|
1561
|
+
constructor(transport) {
|
|
1562
|
+
this.transport = transport;
|
|
1563
|
+
}
|
|
1564
|
+
transport;
|
|
1565
|
+
/** List them, oldest first. `{ active: true }` returns only active ones,
|
|
1566
|
+
* `{ active: false }` only archived ones; omit it for both. */
|
|
1567
|
+
async list(params = {}) {
|
|
1568
|
+
const res = await this.transport.request({
|
|
1569
|
+
method: "GET",
|
|
1570
|
+
path: "/api/v1/taxes",
|
|
1571
|
+
query: { active: activeQuery(params.active) }
|
|
1572
|
+
});
|
|
1573
|
+
return filterByActive(res.taxes, params.active);
|
|
1574
|
+
}
|
|
1575
|
+
/** One tax. */
|
|
1576
|
+
async get(id) {
|
|
1577
|
+
const res = await this.transport.request({
|
|
1578
|
+
method: "GET",
|
|
1579
|
+
path: `/api/v1/taxes/${encodeURIComponent(id)}`
|
|
1580
|
+
});
|
|
1581
|
+
return res.tax;
|
|
1582
|
+
}
|
|
1583
|
+
/** Create one. It starts active. Two active taxes can't share a name
|
|
1584
|
+
* (ignoring case) in the same country — that's a `ConflictError`. Not
|
|
1585
|
+
* retried on failure — a retry could create a second row. */
|
|
1586
|
+
async create(input) {
|
|
1587
|
+
const res = await this.transport.request({
|
|
1588
|
+
method: "POST",
|
|
1589
|
+
path: "/api/v1/taxes",
|
|
1590
|
+
body: input
|
|
1591
|
+
});
|
|
1592
|
+
return res.tax;
|
|
1593
|
+
}
|
|
1594
|
+
/** PARTIAL update: only the fields you send change. Archived taxes can be
|
|
1595
|
+
* edited and stay archived. Only invoices issued afterwards use the new
|
|
1596
|
+
* values. */
|
|
1597
|
+
async update(id, input) {
|
|
1598
|
+
const res = await this.transport.request({
|
|
1599
|
+
method: "PATCH",
|
|
1600
|
+
path: `/api/v1/taxes/${encodeURIComponent(id)}`,
|
|
1601
|
+
body: input
|
|
1602
|
+
});
|
|
1603
|
+
return res.tax;
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1516
1607
|
// src/client.ts
|
|
1517
1608
|
var ClockNext = class {
|
|
1518
1609
|
/** Record usage signals (credit / wallet / outcome / unit) and read the usage log. */
|
|
@@ -1529,6 +1620,8 @@ var ClockNext = class {
|
|
|
1529
1620
|
units;
|
|
1530
1621
|
/** Composites: list, get, create, update, archive, unarchive (no delete). */
|
|
1531
1622
|
composites;
|
|
1623
|
+
/** Tax rates by country: list, get, create, update (archive in the dashboard). */
|
|
1624
|
+
taxes;
|
|
1532
1625
|
/** Subscribe customers to plans (purchases). */
|
|
1533
1626
|
purchases;
|
|
1534
1627
|
/** Read invoices and mint hosted pay links. */
|
|
@@ -1555,6 +1648,7 @@ var ClockNext = class {
|
|
|
1555
1648
|
this.outcomes = new Outcomes(transport);
|
|
1556
1649
|
this.units = new Units(transport);
|
|
1557
1650
|
this.composites = new Composites(transport);
|
|
1651
|
+
this.taxes = new Taxes(transport);
|
|
1558
1652
|
this.purchases = new Purchases(transport);
|
|
1559
1653
|
this.invoices = new Invoices(transport);
|
|
1560
1654
|
this.payments = new Payments(transport);
|