@clocknext/sdk 0.11.0 → 0.12.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 +549 -264
- package/dist/index.cjs +257 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -43
- package/dist/index.d.ts +228 -43
- package/dist/index.js +257 -117
- package/dist/index.js.map +1 -1
- package/examples/node-basic.ts +93 -0
- package/examples/serverless-flush.ts +47 -0
- package/package.json +3 -1
- package/src/client.ts +107 -0
- package/src/config.ts +116 -0
- package/src/errors.ts +215 -0
- package/src/filters.ts +33 -0
- package/src/flusher.ts +144 -0
- package/src/http.ts +178 -0
- package/src/idempotency.ts +33 -0
- package/src/index.ts +165 -0
- package/src/queue.ts +48 -0
- package/src/resources/composites.ts +47 -0
- package/src/resources/credits.ts +76 -0
- package/src/resources/customers.ts +298 -0
- package/src/resources/invoices.ts +51 -0
- package/src/resources/outcomes.ts +82 -0
- package/src/resources/payments.ts +30 -0
- package/src/resources/plans.ts +76 -0
- package/src/resources/portal.ts +31 -0
- package/src/resources/purchases.ts +75 -0
- package/src/resources/signals.ts +393 -0
- package/src/resources/units.ts +76 -0
- package/src/resources/webhooks.ts +37 -0
- package/src/resources/workspace.ts +33 -0
- package/src/retry.ts +56 -0
- package/src/types.ts +1601 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import type { Transport } from "../http.js";
|
|
2
|
+
import type {
|
|
3
|
+
AddMemberInput,
|
|
4
|
+
ApiCustomer,
|
|
5
|
+
CreateCustomerInput,
|
|
6
|
+
CreditAdjustInput,
|
|
7
|
+
CreditBalance,
|
|
8
|
+
CustomerBalances,
|
|
9
|
+
CustomerList,
|
|
10
|
+
CustomerPlan,
|
|
11
|
+
CustomerProfileInput,
|
|
12
|
+
Member,
|
|
13
|
+
MemberProfile,
|
|
14
|
+
OutcomeAdjustInput,
|
|
15
|
+
OutcomeBalance,
|
|
16
|
+
RemovedMember,
|
|
17
|
+
UnitAdjustInput,
|
|
18
|
+
UnitBalance,
|
|
19
|
+
UpdateMemberInput,
|
|
20
|
+
UsageList,
|
|
21
|
+
WalletEntryInput,
|
|
22
|
+
WalletList,
|
|
23
|
+
WalletTransaction,
|
|
24
|
+
} from "../types.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The `customers` resource — full CRUD over the org's customers plus the
|
|
28
|
+
* read-only insight endpoints (usage / balances / plan). Every call is scoped
|
|
29
|
+
* to the API key's organisation server-side; a customer in another org
|
|
30
|
+
* resolves as a NotFoundError.
|
|
31
|
+
*/
|
|
32
|
+
export class Customers {
|
|
33
|
+
constructor(private readonly transport: Transport) {}
|
|
34
|
+
|
|
35
|
+
/** Create a customer. `POST /api/v1/customers`. */
|
|
36
|
+
async create(input: CreateCustomerInput): Promise<ApiCustomer> {
|
|
37
|
+
const res = await this.transport.request<{ customer: ApiCustomer }>({
|
|
38
|
+
method: "POST",
|
|
39
|
+
path: "/api/v1/customers",
|
|
40
|
+
body: input,
|
|
41
|
+
});
|
|
42
|
+
return res.customer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** List customers, most-recent first (cursor-paginated). `GET /api/v1/customers`. */
|
|
46
|
+
async list(params: {
|
|
47
|
+
limit?: number;
|
|
48
|
+
q?: string;
|
|
49
|
+
cursor?: string;
|
|
50
|
+
} = {}): Promise<CustomerList> {
|
|
51
|
+
const res = await this.transport.request<CustomerList>({
|
|
52
|
+
method: "GET",
|
|
53
|
+
path: "/api/v1/customers",
|
|
54
|
+
query: { limit: params.limit, q: params.q, cursor: params.cursor },
|
|
55
|
+
});
|
|
56
|
+
return { customers: res.customers, nextCursor: res.nextCursor };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Async iterator over ALL customers, transparently following the cursor.
|
|
61
|
+
* `for await (const c of cnk.customers.iterate()) { … }`
|
|
62
|
+
*/
|
|
63
|
+
async *iterate(params: { limit?: number; q?: string } = {}): AsyncGenerator<
|
|
64
|
+
ApiCustomer
|
|
65
|
+
> {
|
|
66
|
+
let cursor: string | undefined;
|
|
67
|
+
do {
|
|
68
|
+
const page = await this.list({ ...params, cursor });
|
|
69
|
+
for (const c of page.customers) yield c;
|
|
70
|
+
cursor = page.nextCursor ?? undefined;
|
|
71
|
+
} while (cursor);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Fetch one customer. `GET /api/v1/customers/:id`. */
|
|
75
|
+
async get(id: string): Promise<ApiCustomer> {
|
|
76
|
+
const res = await this.transport.request<{ customer: ApiCustomer }>({
|
|
77
|
+
method: "GET",
|
|
78
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}`,
|
|
79
|
+
});
|
|
80
|
+
return res.customer;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Update a customer (partial). `PATCH /api/v1/customers/:id`. */
|
|
84
|
+
async update(
|
|
85
|
+
id: string,
|
|
86
|
+
input: CustomerProfileInput,
|
|
87
|
+
): Promise<ApiCustomer> {
|
|
88
|
+
const res = await this.transport.request<{ customer: ApiCustomer }>({
|
|
89
|
+
method: "PATCH",
|
|
90
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}`,
|
|
91
|
+
body: input,
|
|
92
|
+
});
|
|
93
|
+
return res.customer;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Delete a customer (members, invitations, usage logs cascade). */
|
|
97
|
+
async delete(id: string): Promise<void> {
|
|
98
|
+
await this.transport.request({
|
|
99
|
+
method: "DELETE",
|
|
100
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A customer's usage history. `GET /api/v1/usage?customerId=`. */
|
|
105
|
+
async usage(id: string, params: { limit?: number } = {}): Promise<UsageList> {
|
|
106
|
+
const res = await this.transport.request<UsageList>({
|
|
107
|
+
method: "GET",
|
|
108
|
+
path: `/api/v1/usage`,
|
|
109
|
+
query: { customerId: id, limit: params.limit },
|
|
110
|
+
});
|
|
111
|
+
return { customer: res.customer, totals: res.totals, logs: res.logs };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Wallet / credit / outcome / unit balances. `GET …/:id/balances`. */
|
|
115
|
+
balances(id: string): Promise<CustomerBalances> {
|
|
116
|
+
return this.transport.request<CustomerBalances>({
|
|
117
|
+
method: "GET",
|
|
118
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/balances`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* One credit balance for a customer, matched by `creditId` or `creditName`;
|
|
124
|
+
* `null` when the customer has no plan or no matching credit. Convenience
|
|
125
|
+
* over `balances()` + `Array.find` — it fetches the FULL balances under the
|
|
126
|
+
* hood (there is no server-side scoped read), so prefer `balances()` when you
|
|
127
|
+
* need several rows from the same customer.
|
|
128
|
+
*/
|
|
129
|
+
async creditBalance(
|
|
130
|
+
customerId: string,
|
|
131
|
+
match: { creditId?: string; creditName?: string },
|
|
132
|
+
): Promise<CreditBalance | null> {
|
|
133
|
+
const { credits } = await this.balances(customerId);
|
|
134
|
+
return (
|
|
135
|
+
credits.find(
|
|
136
|
+
(c) =>
|
|
137
|
+
(match.creditId != null && c.creditId === match.creditId) ||
|
|
138
|
+
(match.creditName != null && c.creditName === match.creditName),
|
|
139
|
+
) ?? null
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** One outcome balance for a customer, matched by `outcomeId` or
|
|
144
|
+
* `outcomeName`; `null` when absent. See {@link creditBalance} for the
|
|
145
|
+
* fetch-then-filter caveat. */
|
|
146
|
+
async outcomeBalance(
|
|
147
|
+
customerId: string,
|
|
148
|
+
match: { outcomeId?: string; outcomeName?: string },
|
|
149
|
+
): Promise<OutcomeBalance | null> {
|
|
150
|
+
const { outcomes } = await this.balances(customerId);
|
|
151
|
+
return (
|
|
152
|
+
outcomes.find(
|
|
153
|
+
(o) =>
|
|
154
|
+
(match.outcomeId != null && o.outcomeId === match.outcomeId) ||
|
|
155
|
+
(match.outcomeName != null && o.outcomeName === match.outcomeName),
|
|
156
|
+
) ?? null
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** One unit balance for a customer, matched by `unitId` or `unitName`;
|
|
161
|
+
* `null` when absent. See {@link creditBalance} for the fetch-then-filter
|
|
162
|
+
* caveat. */
|
|
163
|
+
async unitBalance(
|
|
164
|
+
customerId: string,
|
|
165
|
+
match: { unitId?: string; unitName?: string },
|
|
166
|
+
): Promise<UnitBalance | null> {
|
|
167
|
+
const { units } = await this.balances(customerId);
|
|
168
|
+
return (
|
|
169
|
+
units.find(
|
|
170
|
+
(u) =>
|
|
171
|
+
(match.unitId != null && u.unitId === match.unitId) ||
|
|
172
|
+
(match.unitName != null && u.unitName === match.unitName),
|
|
173
|
+
) ?? null
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The customer's current plan. `GET …/:id/plan`. */
|
|
178
|
+
plan(id: string): Promise<CustomerPlan> {
|
|
179
|
+
return this.transport.request<CustomerPlan>({
|
|
180
|
+
method: "GET",
|
|
181
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/plan`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Members -------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
/** List a customer's members. `GET …/:id/members`. */
|
|
188
|
+
async listMembers(id: string): Promise<Member[]> {
|
|
189
|
+
const res = await this.transport.request<{ members: Member[] }>({
|
|
190
|
+
method: "GET",
|
|
191
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/members`,
|
|
192
|
+
});
|
|
193
|
+
return res.members;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Add a member to a customer. Duplicate `email` → ConflictError. */
|
|
197
|
+
async addMember(id: string, input: AddMemberInput): Promise<MemberProfile> {
|
|
198
|
+
const res = await this.transport.request<{ member: MemberProfile }>({
|
|
199
|
+
method: "POST",
|
|
200
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/members`,
|
|
201
|
+
body: input,
|
|
202
|
+
});
|
|
203
|
+
return res.member;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Update a member's profile. Duplicate `email` → ConflictError. `PATCH …/:id/members/:memberId`. */
|
|
207
|
+
async updateMember(
|
|
208
|
+
id: string,
|
|
209
|
+
memberId: string,
|
|
210
|
+
input: UpdateMemberInput,
|
|
211
|
+
): Promise<MemberProfile> {
|
|
212
|
+
const res = await this.transport.request<{ member: MemberProfile }>({
|
|
213
|
+
method: "PATCH",
|
|
214
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/members/${encodeURIComponent(memberId)}`,
|
|
215
|
+
body: input,
|
|
216
|
+
});
|
|
217
|
+
return res.member;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Remove a member from a customer, returning who was removed. `DELETE …/:id/members/:memberId`. */
|
|
221
|
+
async removeMember(id: string, memberId: string): Promise<RemovedMember> {
|
|
222
|
+
const res = await this.transport.request<{ removed: true; member: RemovedMember }>({
|
|
223
|
+
method: "DELETE",
|
|
224
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/members/${encodeURIComponent(memberId)}`,
|
|
225
|
+
});
|
|
226
|
+
return res.member;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --- Wallet & billing config --------------------------------------------
|
|
230
|
+
|
|
231
|
+
/** The customer's wallet balance + transactions. `GET …/:id/wallet`. */
|
|
232
|
+
wallet(id: string, params: { limit?: number } = {}): Promise<WalletList> {
|
|
233
|
+
return this.transport.request<WalletList>({
|
|
234
|
+
method: "GET",
|
|
235
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/wallet`,
|
|
236
|
+
query: { limit: params.limit },
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Record a manual wallet entry (top-up or deduction). `amount` is always
|
|
242
|
+
* positive; the sign comes from `type`. `POST …/:id/wallet`.
|
|
243
|
+
*/
|
|
244
|
+
async addWalletEntry(
|
|
245
|
+
id: string,
|
|
246
|
+
input: WalletEntryInput,
|
|
247
|
+
): Promise<{ transaction: WalletTransaction; balanceUsd: number }> {
|
|
248
|
+
return this.transport.request<{
|
|
249
|
+
transaction: WalletTransaction;
|
|
250
|
+
balanceUsd: number;
|
|
251
|
+
}>({
|
|
252
|
+
method: "POST",
|
|
253
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/wallet`,
|
|
254
|
+
body: input,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Note: a customer's currency is set through `update(id, { currencyCode })`
|
|
259
|
+
// (PATCH …/:id) — there is no separate currency endpoint. Credit-type
|
|
260
|
+
// assignment has no public API.
|
|
261
|
+
|
|
262
|
+
// --- Balance adjustments -------------------------------------------------
|
|
263
|
+
|
|
264
|
+
/** Grant or claw back a credit balance. `POST …/:id/credits/adjust`. */
|
|
265
|
+
async adjustCredit(id: string, input: CreditAdjustInput): Promise<void> {
|
|
266
|
+
await this.transport.request({
|
|
267
|
+
method: "POST",
|
|
268
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/credits/adjust`,
|
|
269
|
+
body: input,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Grant or claw back an outcome balance. Returns the remaining balance. */
|
|
274
|
+
async adjustOutcome(
|
|
275
|
+
id: string,
|
|
276
|
+
input: OutcomeAdjustInput,
|
|
277
|
+
): Promise<{ remaining: number }> {
|
|
278
|
+
const res = await this.transport.request<{ remaining: number }>({
|
|
279
|
+
method: "POST",
|
|
280
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/outcomes/adjust`,
|
|
281
|
+
body: input,
|
|
282
|
+
});
|
|
283
|
+
return { remaining: res.remaining };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Grant or claw back a unit balance. Returns the remaining balance. */
|
|
287
|
+
async adjustUnit(
|
|
288
|
+
id: string,
|
|
289
|
+
input: UnitAdjustInput,
|
|
290
|
+
): Promise<{ remaining: number }> {
|
|
291
|
+
const res = await this.transport.request<{ remaining: number }>({
|
|
292
|
+
method: "POST",
|
|
293
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/units/adjust`,
|
|
294
|
+
body: input,
|
|
295
|
+
});
|
|
296
|
+
return { remaining: res.remaining };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Transport } from "../http.js";
|
|
2
|
+
import type {
|
|
3
|
+
InvoiceDetail,
|
|
4
|
+
InvoiceListRow,
|
|
5
|
+
InvoiceStatus,
|
|
6
|
+
PayLink,
|
|
7
|
+
} from "../types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The `invoices` resource — read-only. Invoices are generated by the billing
|
|
11
|
+
* engine from purchases; you list them, read one, or mint a hosted pay link for
|
|
12
|
+
* an OPEN invoice. `/api/v1/invoices`.
|
|
13
|
+
*/
|
|
14
|
+
export class Invoices {
|
|
15
|
+
constructor(private readonly transport: Transport) {}
|
|
16
|
+
|
|
17
|
+
/** List invoices, most-recent first. Filter by customer and/or status. */
|
|
18
|
+
async list(
|
|
19
|
+
params: { customerId?: string; status?: InvoiceStatus; limit?: number } = {},
|
|
20
|
+
): Promise<InvoiceListRow[]> {
|
|
21
|
+
const res = await this.transport.request<{ invoices: InvoiceListRow[] }>({
|
|
22
|
+
method: "GET",
|
|
23
|
+
path: "/api/v1/invoices",
|
|
24
|
+
query: {
|
|
25
|
+
customerId: params.customerId,
|
|
26
|
+
status: params.status,
|
|
27
|
+
limit: params.limit,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return res.invoices;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Fetch one full invoice + its plan snapshot. `GET /api/v1/invoices/:id`. */
|
|
34
|
+
get(id: string): Promise<InvoiceDetail> {
|
|
35
|
+
return this.transport.request<InvoiceDetail>({
|
|
36
|
+
method: "GET",
|
|
37
|
+
path: `/api/v1/invoices/${encodeURIComponent(id)}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Mint a hosted pay link for an OPEN invoice. PAID/VOID invoices are rejected
|
|
43
|
+
* with a `ConflictError`. `GET /api/v1/invoices/:id/pay-link`.
|
|
44
|
+
*/
|
|
45
|
+
payLink(id: string): Promise<PayLink> {
|
|
46
|
+
return this.transport.request<PayLink>({
|
|
47
|
+
method: "GET",
|
|
48
|
+
path: `/api/v1/invoices/${encodeURIComponent(id)}/pay-link`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { activeQuery, filterByActive } from "../filters.js";
|
|
2
|
+
import type { Transport } from "../http.js";
|
|
3
|
+
import type {
|
|
4
|
+
CreateOutcomeInput,
|
|
5
|
+
Outcome,
|
|
6
|
+
OutcomeDetail,
|
|
7
|
+
UpdateOutcomeInput,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The `outcomes` resource — manage the org's outcome *types* (the catalog). An
|
|
12
|
+
* outcome is a multi-step workflow. `/api/v1/outcomes`.
|
|
13
|
+
*
|
|
14
|
+
* Two distinct kinds of agent key, in separate namespaces, each unique per
|
|
15
|
+
* organisation — do not conflate them:
|
|
16
|
+
* - the OUTCOME's own `agentKey`, REQUIRED on `create()` and `update()`, which
|
|
17
|
+
* names the outcome itself in the catalogue;
|
|
18
|
+
* - each STEP's `agentKey`, which is what you pass to `signals.outcome()` to
|
|
19
|
+
* report usage and to the outcome-adjust API.
|
|
20
|
+
*/
|
|
21
|
+
export class Outcomes {
|
|
22
|
+
constructor(private readonly transport: Transport) {}
|
|
23
|
+
|
|
24
|
+
/** List outcome types. Pass `{ active: true }` to only return active ones.
|
|
25
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
26
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
27
|
+
async list(params: { active?: boolean } = {}): Promise<Outcome[]> {
|
|
28
|
+
const res = await this.transport.request<{ outcomes: Outcome[] }>({
|
|
29
|
+
method: "GET",
|
|
30
|
+
path: "/api/v1/outcomes",
|
|
31
|
+
query: { active: activeQuery(params.active) },
|
|
32
|
+
});
|
|
33
|
+
return filterByActive(res.outcomes, params.active);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Create an outcome type with its steps. `POST /api/v1/outcomes`. */
|
|
37
|
+
async create(input: CreateOutcomeInput): Promise<Outcome> {
|
|
38
|
+
const res = await this.transport.request<{ outcome: Outcome }>({
|
|
39
|
+
method: "POST",
|
|
40
|
+
path: "/api/v1/outcomes",
|
|
41
|
+
body: input,
|
|
42
|
+
});
|
|
43
|
+
return res.outcome;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Fetch one outcome type with steps + stats. `GET /api/v1/outcomes/:id`. */
|
|
47
|
+
async get(id: string): Promise<OutcomeDetail> {
|
|
48
|
+
const res = await this.transport.request<{ outcome: OutcomeDetail }>({
|
|
49
|
+
method: "GET",
|
|
50
|
+
path: `/api/v1/outcomes/${encodeURIComponent(id)}`,
|
|
51
|
+
});
|
|
52
|
+
return res.outcome;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Update an outcome type's definition. `PATCH /api/v1/outcomes/:id`. */
|
|
56
|
+
async update(id: string, input: UpdateOutcomeInput): Promise<Outcome> {
|
|
57
|
+
const res = await this.transport.request<{ outcome: Outcome }>({
|
|
58
|
+
method: "PATCH",
|
|
59
|
+
path: `/api/v1/outcomes/${encodeURIComponent(id)}`,
|
|
60
|
+
body: input,
|
|
61
|
+
});
|
|
62
|
+
return res.outcome;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Activate or deactivate an outcome type; returns the updated outcome. */
|
|
66
|
+
async setActive(id: string, isActive: boolean): Promise<Outcome> {
|
|
67
|
+
const res = await this.transport.request<{ outcome: Outcome }>({
|
|
68
|
+
method: "PATCH",
|
|
69
|
+
path: `/api/v1/outcomes/${encodeURIComponent(id)}`,
|
|
70
|
+
body: { isActive },
|
|
71
|
+
});
|
|
72
|
+
return res.outcome;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Delete an outcome type. */
|
|
76
|
+
async delete(id: string): Promise<void> {
|
|
77
|
+
await this.transport.request({
|
|
78
|
+
method: "DELETE",
|
|
79
|
+
path: `/api/v1/outcomes/${encodeURIComponent(id)}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Transport } from "../http.js";
|
|
2
|
+
import type { PaymentDetail, PaymentRow } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `payments` resource — read-only. A payment is a PAID invoice; the `id`
|
|
6
|
+
* used here is that underlying invoice id. `/api/v1/payments`.
|
|
7
|
+
*/
|
|
8
|
+
export class Payments {
|
|
9
|
+
constructor(private readonly transport: Transport) {}
|
|
10
|
+
|
|
11
|
+
/** List payments (paid invoices), most-recent first. Filter by customer. */
|
|
12
|
+
async list(
|
|
13
|
+
params: { customerId?: string; limit?: number } = {},
|
|
14
|
+
): Promise<PaymentRow[]> {
|
|
15
|
+
const res = await this.transport.request<{ payments: PaymentRow[] }>({
|
|
16
|
+
method: "GET",
|
|
17
|
+
path: "/api/v1/payments",
|
|
18
|
+
query: { customerId: params.customerId, limit: params.limit },
|
|
19
|
+
});
|
|
20
|
+
return res.payments;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Fetch one payment + its plan snapshot. `GET /api/v1/payments/:id`. */
|
|
24
|
+
get(id: string): Promise<PaymentDetail> {
|
|
25
|
+
return this.transport.request<PaymentDetail>({
|
|
26
|
+
method: "GET",
|
|
27
|
+
path: `/api/v1/payments/${encodeURIComponent(id)}`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { activeQuery, filterByActive } from "../filters.js";
|
|
2
|
+
import type { Transport } from "../http.js";
|
|
3
|
+
import type {
|
|
4
|
+
CreatePlanInput,
|
|
5
|
+
Plan,
|
|
6
|
+
PlanDetail,
|
|
7
|
+
UpdatePlanInput,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The `plans` resource — define and manage the billing plans customers
|
|
12
|
+
* subscribe to. Each plan is a set of entitlement components (wallet / credit /
|
|
13
|
+
* outcome / unit / flat) billed on a cycle. `/api/v1/plans`.
|
|
14
|
+
*/
|
|
15
|
+
export class Plans {
|
|
16
|
+
constructor(private readonly transport: Transport) {}
|
|
17
|
+
|
|
18
|
+
/** List plans, oldest-first. Pass `{ active: true }` to only return active plans.
|
|
19
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
20
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
21
|
+
async list(params: { active?: boolean } = {}): Promise<Plan[]> {
|
|
22
|
+
const res = await this.transport.request<{ plans: Plan[] }>({
|
|
23
|
+
method: "GET",
|
|
24
|
+
path: "/api/v1/plans",
|
|
25
|
+
query: { active: activeQuery(params.active) },
|
|
26
|
+
});
|
|
27
|
+
return filterByActive(res.plans, params.active);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Create a plan. `POST /api/v1/plans`. */
|
|
31
|
+
async create(input: CreatePlanInput): Promise<Plan> {
|
|
32
|
+
const res = await this.transport.request<{ plan: Plan }>({
|
|
33
|
+
method: "POST",
|
|
34
|
+
path: "/api/v1/plans",
|
|
35
|
+
body: input,
|
|
36
|
+
});
|
|
37
|
+
return res.plan;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Fetch one plan with aggregate subscriber/invoice stats. `GET /api/v1/plans/:id`. */
|
|
41
|
+
async get(id: string): Promise<PlanDetail> {
|
|
42
|
+
const res = await this.transport.request<{ plan: PlanDetail }>({
|
|
43
|
+
method: "GET",
|
|
44
|
+
path: `/api/v1/plans/${encodeURIComponent(id)}`,
|
|
45
|
+
});
|
|
46
|
+
return res.plan;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Replace a plan's definition (components + settings). `PATCH /api/v1/plans/:id`. */
|
|
50
|
+
async update(id: string, input: UpdatePlanInput): Promise<Plan> {
|
|
51
|
+
const res = await this.transport.request<{ plan: Plan }>({
|
|
52
|
+
method: "PATCH",
|
|
53
|
+
path: `/api/v1/plans/${encodeURIComponent(id)}`,
|
|
54
|
+
body: input,
|
|
55
|
+
});
|
|
56
|
+
return res.plan;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Activate or deactivate a plan without rewriting it. */
|
|
60
|
+
async setActive(id: string, isActive: boolean): Promise<Plan> {
|
|
61
|
+
const res = await this.transport.request<{ plan: Plan }>({
|
|
62
|
+
method: "PATCH",
|
|
63
|
+
path: `/api/v1/plans/${encodeURIComponent(id)}`,
|
|
64
|
+
body: { isActive },
|
|
65
|
+
});
|
|
66
|
+
return res.plan;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Delete a plan. Refused once any customer has purchased it. */
|
|
70
|
+
async delete(id: string): Promise<void> {
|
|
71
|
+
await this.transport.request({
|
|
72
|
+
method: "DELETE",
|
|
73
|
+
path: `/api/v1/plans/${encodeURIComponent(id)}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Transport } from "../http.js";
|
|
2
|
+
import type { PortalToken, PortalTokenInput } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `portal` resource — mint a short-lived embed token for one of your
|
|
6
|
+
* customers, then render `<iframe src=".../portal/embed?token=<token>">`. The
|
|
7
|
+
* HS256 signing secret never leaves ClockNext; your backend only needs its API
|
|
8
|
+
* key. `POST /api/v1/portal/token`.
|
|
9
|
+
*/
|
|
10
|
+
export class Portal {
|
|
11
|
+
constructor(private readonly transport: Transport) {}
|
|
12
|
+
|
|
13
|
+
async createToken(input: PortalTokenInput): Promise<PortalToken> {
|
|
14
|
+
const res = await this.transport.request<
|
|
15
|
+
{ token: string; expiresAt: number; expiresIn: number }
|
|
16
|
+
>({
|
|
17
|
+
method: "POST",
|
|
18
|
+
path: "/api/v1/portal/token",
|
|
19
|
+
body: {
|
|
20
|
+
customerId: input.customerId,
|
|
21
|
+
ttlSeconds: input.ttlSeconds,
|
|
22
|
+
memberEmail: input.memberEmail,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
token: res.token,
|
|
27
|
+
expiresAt: res.expiresAt,
|
|
28
|
+
expiresIn: res.expiresIn,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Transport } from "../http.js";
|
|
2
|
+
import type {
|
|
3
|
+
CreatePurchaseInput,
|
|
4
|
+
Purchase,
|
|
5
|
+
PurchaseDetail,
|
|
6
|
+
PurchaseStatus,
|
|
7
|
+
} from "../types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The `purchases` resource — a purchase is a customer's subscription to a plan.
|
|
11
|
+
* Creating one auto-cancels the customer's existing scheduled/active purchase
|
|
12
|
+
* (this is also how you "change plan"). `/api/v1/purchases`.
|
|
13
|
+
*/
|
|
14
|
+
export class Purchases {
|
|
15
|
+
constructor(private readonly transport: Transport) {}
|
|
16
|
+
|
|
17
|
+
/** List purchases, most-recent first. Filter by customer and/or status. */
|
|
18
|
+
async list(
|
|
19
|
+
params: { customerId?: string; status?: PurchaseStatus; limit?: number } = {},
|
|
20
|
+
): Promise<Purchase[]> {
|
|
21
|
+
const res = await this.transport.request<{ purchases: Purchase[] }>({
|
|
22
|
+
method: "GET",
|
|
23
|
+
path: "/api/v1/purchases",
|
|
24
|
+
query: {
|
|
25
|
+
customerId: params.customerId,
|
|
26
|
+
status: params.status,
|
|
27
|
+
limit: params.limit,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return res.purchases;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Subscribe a customer to a plan. `POST /api/v1/purchases`. */
|
|
34
|
+
async create(input: CreatePurchaseInput): Promise<Purchase> {
|
|
35
|
+
const res = await this.transport.request<{ purchase: Purchase }>({
|
|
36
|
+
method: "POST",
|
|
37
|
+
path: "/api/v1/purchases",
|
|
38
|
+
body: input,
|
|
39
|
+
});
|
|
40
|
+
return res.purchase;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Fetch one purchase. `GET /api/v1/purchases/:id`. */
|
|
44
|
+
async get(id: string): Promise<PurchaseDetail> {
|
|
45
|
+
const res = await this.transport.request<{ purchase: PurchaseDetail }>({
|
|
46
|
+
method: "GET",
|
|
47
|
+
path: `/api/v1/purchases/${encodeURIComponent(id)}`,
|
|
48
|
+
});
|
|
49
|
+
return res.purchase;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Turn automatic billing on or off for a purchase. When pausing, an optional
|
|
54
|
+
* `pauseReason` is recorded. `PATCH /api/v1/purchases/:id`.
|
|
55
|
+
*/
|
|
56
|
+
async setAutoPayment(
|
|
57
|
+
id: string,
|
|
58
|
+
autoPayment: boolean,
|
|
59
|
+
opts: { pauseReason?: string } = {},
|
|
60
|
+
): Promise<{ autoPayment: boolean }> {
|
|
61
|
+
return this.transport.request<{ autoPayment: boolean }>({
|
|
62
|
+
method: "PATCH",
|
|
63
|
+
path: `/api/v1/purchases/${encodeURIComponent(id)}`,
|
|
64
|
+
body: { autoPayment, pauseReason: opts.pauseReason },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Cancel a purchase (voids any open invoice). `POST /api/v1/purchases/:id/cancel`. */
|
|
69
|
+
async cancel(id: string): Promise<void> {
|
|
70
|
+
await this.transport.request({
|
|
71
|
+
method: "POST",
|
|
72
|
+
path: `/api/v1/purchases/${encodeURIComponent(id)}/cancel`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|