@conto_finance/sdk 0.7.0 → 0.8.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 +111 -8
- package/dist/chunk-PVMUL3JC.mjs +30 -0
- package/dist/index.d.mts +395 -8
- package/dist/index.d.ts +395 -8
- package/dist/index.js +560 -76
- package/dist/index.mjs +536 -76
- package/dist/link-wallet.d.mts +6 -1
- package/dist/link-wallet.d.ts +6 -1
- package/dist/link-wallet.js +33 -11
- package/dist/link-wallet.mjs +9 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -155,23 +155,124 @@ const confirmed = await conto.checkout.confirm(result.checkpoint, {
|
|
|
155
155
|
or `link-cli` is missing or logged out, `getLinkCard()` cancels the checkpoint to release the
|
|
156
156
|
held spend and throws `LinkCardDeniedError`, `LinkCardExpiredError`, `LinkCardTimeoutError`, or
|
|
157
157
|
`LinkWalletUnavailableError`. All extend `LinkCardError`. Do not pay.
|
|
158
|
-
- Retrying with the same input resumes the same checkpoint and Link request
|
|
158
|
+
- Retrying with the same input resumes the same checkpoint and Link request while Conto's approval
|
|
159
|
+
is open. That approval lasts 30 minutes. For about 5 to 10 minutes after it lapses, the stage can
|
|
160
|
+
still read `APPROVED`, so a retry can return the card, but `confirm()` refuses it. Once the stage
|
|
161
|
+
is `OUTCOME_UNKNOWN`, a retry throws `CHECKOUT_CHECKPOINT_NOT_APPROVED`.
|
|
162
|
+
- `getLinkCard()` and `replaceLinkCard()` tell Conto the Link request ID as soon as Stripe Link
|
|
163
|
+
creates it. If Conto's approval then lapses, or a rule stops `confirm()`, Conto keeps the spend held,
|
|
164
|
+
because the card may still work. The checkpoint moves to stage `OUTCOME_UNKNOWN`. Cancel the Link
|
|
165
|
+
request, then call `conto.checkout.cancel(id)` to release the spend. Otherwise the owner gets a
|
|
166
|
+
review alert and records what happened. If Conto cannot record the request, the SDK cancels it in
|
|
167
|
+
Link, then cancels the checkpoint, and throws `LinkCardError` with code `LINK_CARD_NOT_RECORDED`.
|
|
168
|
+
`holdReleased` says whether the spend was released.
|
|
169
|
+
- Pass `onLinkApproval` to get Stripe Link's approval page, so a person without the Link app can
|
|
170
|
+
approve on the web while signed in to Link. It is called once, after Conto approves and Link
|
|
171
|
+
creates the request, with `{ checkpoint, linkRequestId, approvalUrl, approvalExpiresAt }`.
|
|
172
|
+
`approvalUrl` is always an HTTPS link.com address. A `LinkCardError` has it too when the SDK
|
|
173
|
+
stopped while the person had not decided.
|
|
159
174
|
- The card (`number`, `securityValue` for the CVC, `expMonth`, `expYear`, `validUntil`, `last4`,
|
|
160
175
|
`brand`) stays in this process. Printing or serializing the result shows only the last four
|
|
161
176
|
digits. It never appears in an error.
|
|
162
|
-
- The card covers the approved total, so `confirm()`
|
|
163
|
-
|
|
177
|
+
- The card covers the approved total, so `confirm()` refuses a different total for a checkpoint from
|
|
178
|
+
`getLinkCard()` or `onLinkApproval`, even when `paymentReference` is not passed. Call
|
|
179
|
+
`conto.checkout.replaceLinkCard(result, { wallet, amountMinor })` to
|
|
164
180
|
cancel the old card and checkpoint and get a card for the final total.
|
|
165
181
|
- Any `LinkWallet` works in place of `linkCliWallet()`: it creates a card request, waits for
|
|
166
182
|
approval, retrieves the card, and cancels.
|
|
167
183
|
- The protection holds only when the agent cannot also run `link-cli`, use a shell, or call Link
|
|
168
184
|
with your credentials itself.
|
|
169
185
|
- Pass observed final items and charges to `confirm()` even when the total is unchanged.
|
|
170
|
-
The server
|
|
186
|
+
The server checks the items and the final total. Other rows, such as tax and shipping, may
|
|
187
|
+
differ from the ones passed to `begin()`. The trusted host must
|
|
171
188
|
validate the actual merchant and checkout, own a durable one-use payment dispatch
|
|
172
189
|
claim, and keep card use outside model tools. Replaying a confirmed checkpoint is
|
|
173
190
|
not permission to submit payment again.
|
|
174
191
|
|
|
192
|
+
## Shared budgets
|
|
193
|
+
|
|
194
|
+
A shared budget is one limit that several agents draw on. The budget holder sets it in Spending
|
|
195
|
+
controls or with `admin.spending`. Agents no budget covers are not affected, except a buyer linked to
|
|
196
|
+
an owner workspace. Until a budget in that workspace covers it, those paths fail with `reasonCode`
|
|
197
|
+
`LINKED_BUDGET_REQUIRED`.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Agent key. Nothing on conto.spending can raise a limit, approve a task, or release a hold.
|
|
201
|
+
const { budgets } = await conto.spending.budgets();
|
|
202
|
+
await conto.spending.preview({
|
|
203
|
+
amount: '40',
|
|
204
|
+
currency: 'USDC',
|
|
205
|
+
sourceId: walletId,
|
|
206
|
+
merchant: recipientAddress,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Organization API key.
|
|
210
|
+
await admin.spending.createBudget({
|
|
211
|
+
name: 'Team tools',
|
|
212
|
+
scope: 'AGENTS',
|
|
213
|
+
participantIds: [agentA, agentB],
|
|
214
|
+
amount: '500',
|
|
215
|
+
currency: 'USDC',
|
|
216
|
+
period: 'MONTHLY',
|
|
217
|
+
periodStart: new Date().toISOString(),
|
|
218
|
+
periodEnd: new Date(Date.now() + 365 * 86_400_000).toISOString(),
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**A budget stops other payment paths.** While it exists, it stops x402, MPP, external wallet
|
|
223
|
+
payments, agent-to-agent payments, payment grants, checkout relay, hosted Stripe Link, guarded browser
|
|
224
|
+
checkout (`runGuardedCheckout()`), card overlays and checkout checkpoints, including
|
|
225
|
+
`checkout.getLinkCard()`, for every agent it covers. A paused, expired or not yet started budget still
|
|
226
|
+
stops them. Until its start date, while paused, and after its end date, a budget also refuses the
|
|
227
|
+
managed wallet payments and connected card draws it covers, for those agents. A task's budget does
|
|
228
|
+
neither. It applies only to a managed wallet payment or connected card draw that names the task.
|
|
229
|
+
`updateBudget()` can resume a paused budget or extend an
|
|
230
|
+
expired one, and a start date cannot be changed. To end a budget, call `admin.spending.deleteBudget(id, { version })`. It fails with
|
|
231
|
+
`reasonCode` `BUDGET_HAS_HOLDS` while a purchase holds spend against the budget. Try budgets on a test
|
|
232
|
+
organization first.
|
|
233
|
+
|
|
234
|
+
- A budget covers purchases in its own currency, so set it to what the wallets pay in: `USDC` on
|
|
235
|
+
Base, `USDC.e` on Tempo or `pathUSD` on Tempo Testnet. `USD`, `USDC` and `USDC.e` budgets cover one
|
|
236
|
+
another at par when no budget at that level, workspace or agent, is in the exact currency. A
|
|
237
|
+
managed wallet payment or connected card draw is refused, whatever the budget's state, when a level
|
|
238
|
+
has budgets for that agent and none covers its currency. `preview()` gives `reasonCode`
|
|
239
|
+
`BUDGET_CURRENCY_UNSUPPORTED`. A budget's currency cannot be changed.
|
|
240
|
+
- `preview()` needs a managed wallet linked to the agent, and that wallet's stablecoin as `currency`.
|
|
241
|
+
It holds nothing and approves nothing, so keep the request, approval and execute flow.
|
|
242
|
+
- When a budget requires a task, ask for one with `conto.spending.requestTask()`. Once the budget
|
|
243
|
+
holder approves it, pass its id as `context.taskRequestId` on `payments.request()` from a managed
|
|
244
|
+
wallet, or as `taskRequestId` on `connectedCards.draw()`. With a self-custody wallet,
|
|
245
|
+
`payments.request()` returns `DENIED`, and `payments.approve()` refuses the task fields.
|
|
246
|
+
- `context.purchaseKey` identifies one purchase. A key cannot be used again, even after its purchase
|
|
247
|
+
is released, so use a new key for a new attempt.
|
|
248
|
+
- `payments.request()` can return `APPROVED` for a purchase a budget refuses. `payments.execute()`
|
|
249
|
+
then throws status 409 and code `SHARED_BUDGET_BLOCKED`. With `autoExecute`, the request stays
|
|
250
|
+
`APPROVED` and nothing is thrown. To see why, call `preview()` with the same `taskRequestId` and
|
|
251
|
+
`purchaseKey`.
|
|
252
|
+
- `purchases()` lists purchases against budgets in the agent's organization, not Commerce
|
|
253
|
+
`conto.purchases`. It is empty when none of those budgets has applied. A buyer linked to an owner
|
|
254
|
+
workspace has no row for a purchase that only an owner budget covered. The owner reads those with
|
|
255
|
+
`admin.spending.purchases()`.
|
|
256
|
+
- `admin.spending` needs an organization API key. `createBudget()`, `updateBudget()`, `deleteBudget()`
|
|
257
|
+
and `decideTask()` need `policies:write`. `accounting()`, `attachEvidence()` and `reviewEvidence()`
|
|
258
|
+
need `transactions:write`. `reconcile()` needs `spending:reconcile`, which Standard keys do not
|
|
259
|
+
have. An admin SDK key cannot change budgets, decide tasks, or reconcile.
|
|
260
|
+
- `attachEvidence()` takes a PDF, PNG or JPEG of at most 2 MiB, with up to 10 files per purchase. Set
|
|
261
|
+
the Blob's `type`, for example `new Blob([bytes], { type: 'application/pdf' })`. A Blob with no type,
|
|
262
|
+
or with a type that does not match the file's first bytes, fails with `reasonCode` `INVALID_FILE`.
|
|
263
|
+
Conto does not take the amount, currency or merchant from the file. The budget holder enters them
|
|
264
|
+
with `reviewEvidence()`.
|
|
265
|
+
- `exportPurchases()` returns CSV bytes and fails with `reasonCode` `EXPORT_TOO_LARGE` above 10,000
|
|
266
|
+
purchases.
|
|
267
|
+
- Budget errors have `code` `POLICY_VIOLATION`, so read `reasonCode` on `ContoError` and
|
|
268
|
+
`ContoAdminError`. Some also have `nextActions`, such as `SHARED_BUDGET_EXCEEDED` with
|
|
269
|
+
`REQUEST_BUDGET_INCREASE`. `checkout.begin()`, `getLinkCard()` and `replaceLinkCard()` have `code`
|
|
270
|
+
`CHECKPOINT_BUDGET_NOT_SUPPORTED` with that `reasonCode` set. `payments.execute()` has `code`
|
|
271
|
+
`SHARED_BUDGET_BLOCKED` and no `reasonCode`.
|
|
272
|
+
- A `VALIDATION_ERROR` on a spending call usually lists the fields in `fieldErrors`. It does not when
|
|
273
|
+
the call got a field it does not accept, such as `periodStart` or `currency` on `updateBudget()`. A
|
|
274
|
+
`payments.request()` validation failure never has `fieldErrors`.
|
|
275
|
+
|
|
175
276
|
## External-card Policy Overlay
|
|
176
277
|
|
|
177
278
|
The admin client registers only a masked card alias and assigns cooperative
|
|
@@ -317,9 +418,10 @@ Shop Pay, or PayPal browser action without requiring private provider integratio
|
|
|
317
418
|
privileged browser executor must be available only through this wrapper. An unrestricted click or
|
|
318
419
|
payment tool would bypass the control. Persist its stable dispatch attempt before calling the
|
|
319
420
|
helper. The helper owns authorization, one-use claim, the privileged callback, and finalization.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
421
|
+
When a rule requires the owner's approval, `runGuardedCheckout` stops with status 409 and code
|
|
422
|
+
`APPROVAL_REQUIRED`. Checkpoints wait for the owner instead, so for Stripe Link prefer the
|
|
423
|
+
[checkpoints in the Stripe Link guide](https://conto.finance/docs/guides/stripe-link#add-the-checkpoints)
|
|
424
|
+
or `getLinkCard()`.
|
|
323
425
|
|
|
324
426
|
Isolated browser processes can implement `PrivilegedBrowserCheckoutDriver` and use
|
|
325
427
|
`runGuardedCheckoutWithDriver`. The agent model must receive only that guarded wrapper, never the
|
|
@@ -522,7 +624,8 @@ and other non-idempotent mutations are never retried automatically. If execution
|
|
|
522
624
|
payment until an operator has resolved it.
|
|
523
625
|
|
|
524
626
|
`ContoError` and `ContoAdminError` expose a stable error contract: `code`, `status`, `requestId`,
|
|
525
|
-
`retryAfter`,
|
|
627
|
+
`retryAfter`, `fieldErrors` on most spending validation failures, `reasonCode` and `nextActions` on a
|
|
628
|
+
budget refusal, and locally generated `retryGuidance` where applicable. Arbitrary server details,
|
|
526
629
|
context, hints, and workflow steps are not forwarded through SDK errors:
|
|
527
630
|
|
|
528
631
|
```typescript
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/link-approval.ts
|
|
2
|
+
function safeLinkApprovalUrl(value) {
|
|
3
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 2048) return void 0;
|
|
4
|
+
let url;
|
|
5
|
+
try {
|
|
6
|
+
url = new URL(value);
|
|
7
|
+
} catch {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" || !(url.hostname === "link.com" || url.hostname.endsWith(".link.com"))) {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
url.hash = "";
|
|
14
|
+
return url.toString();
|
|
15
|
+
}
|
|
16
|
+
function linkTime(value) {
|
|
17
|
+
let ms = Number.NaN;
|
|
18
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
19
|
+
ms = value < 1e12 ? value * 1e3 : value;
|
|
20
|
+
} else if (typeof value === "string" && value.length > 0 && value.length <= 64) {
|
|
21
|
+
ms = Date.parse(value);
|
|
22
|
+
}
|
|
23
|
+
const date = new Date(ms);
|
|
24
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
safeLinkApprovalUrl,
|
|
29
|
+
linkTime
|
|
30
|
+
};
|