@conto_finance/sdk 0.7.1 → 0.8.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
@@ -106,7 +106,9 @@ await conto.checkout.reportOutcome(confirmed.id, { outcome: 'PAID', reference: o
106
106
  - `begin()` throws `CheckoutCheckpointClosedError` when its idempotency key belongs to a
107
107
  checkpoint that already closed. Start a new purchase with a new key.
108
108
  - `reportOutcome()` accepts `PAID`, `NOT_PAID`, or `UNKNOWN`. Until an outcome arrives, the spend
109
- stays counted. Cancel an unconfirmed checkpoint with `conto.checkout.cancel(id)`.
109
+ stays counted. Cancel an unconfirmed checkpoint with `conto.checkout.cancel(id)`. With a Link
110
+ card, cancel the Link request first, because the checkpoint cancel releases the spend even if the
111
+ card still works.
110
112
  - Conto records what your checkout reports and does not see the charge. For control at the
111
113
  moment of payment, pay with a Conto-managed wallet or a card Conto authorizes.
112
114
 
@@ -154,8 +156,41 @@ const confirmed = await conto.checkout.confirm(result.checkpoint, {
154
156
  - When the person declines in Link, the request expires, the wait passes `linkApprovalTimeoutMs`,
155
157
  or `link-cli` is missing or logged out, `getLinkCard()` cancels the checkpoint to release the
156
158
  held spend and throws `LinkCardDeniedError`, `LinkCardExpiredError`, `LinkCardTimeoutError`, or
157
- `LinkWalletUnavailableError`. All extend `LinkCardError`. Do not pay.
158
- - Retrying with the same input resumes the same checkpoint and Link request.
159
+ `LinkWalletUnavailableError`. All extend `LinkCardError`. Do not pay. If a Link request is already
160
+ on record, it closes that first, and if it cannot, it keeps the hold and throws
161
+ `LINK_CARD_NOT_CANCELLED`. `holdReleased` says whether the checkpoint still holds the spend, not
162
+ whether the card is closed, so follow `error.retryGuidance` whatever its value.
163
+ - Retrying with the same input resumes the same checkpoint and Link request while Conto's approval
164
+ is open. That approval lasts 30 minutes. For about 5 to 10 minutes after it lapses, the stage can
165
+ still read `APPROVED`, so a retry can return the card, but `confirm()` refuses it. Once the stage
166
+ is `OUTCOME_UNKNOWN` with `linkRequestOpen: true`, a retry throws
167
+ `CHECKOUT_CHECKPOINT_NOT_APPROVED`. Once a `confirm()` has started, a retry throws
168
+ `LINK_CARD_NOT_ISSUED` and returns no card. Do not cancel the Link request while a payment may be in
169
+ flight. Follow `error.retryGuidance`.
170
+ - A request in `requires_action` that Link will not resume cannot be retried or cancelled. Link closes
171
+ it on its own, and `error.retryGuidance` gives the steps. `LINK_CARD_DUPLICATE_REQUEST` means Link has
172
+ an open request for another purchase with the same details. `error.retryGuidance` names that request
173
+ and its checkpoint and gives the steps for each stage. Once it closes, start this one with a new
174
+ idempotency key. A `signal` that fires during the owner wait throws
175
+ `LINK_CARD_ABORTED` with the checkpoint, and the spend stays held.
176
+ - `getLinkCard()` and `replaceLinkCard()` tell Conto the Link request ID as soon as Stripe Link
177
+ creates it. If Conto's approval then lapses, or a rule stops `confirm()`, Conto keeps the spend held,
178
+ because the card may still work. The checkpoint moves to stage `OUTCOME_UNKNOWN` with
179
+ `linkRequestOpen: true`. Cancel the Link request, then call `conto.checkout.cancel(id)` to release
180
+ the spend. If the card was used, report `PAID`. If you cannot tell, report `UNKNOWN`, which keeps
181
+ the spend held until the owner records what happened. Otherwise the owner gets a
182
+ review alert and records what happened. If Conto cannot record the request, the SDK cancels it in
183
+ Link, then cancels the checkpoint, and throws `LinkCardError` with code `LINK_CARD_NOT_RECORDED`.
184
+ `holdReleased` says whether the spend was released. If the read fails, a confirm has started, or Link
185
+ cannot cancel a request that is still open, the SDK throws `LINK_CARD_NOT_CANCELLED` instead. Conto
186
+ may have no record of that request, so the hold can end when the approval lapses while the card may
187
+ still work. Retry or cancel before then.
188
+ - `linkCliWallet().cancel()` also succeeds when Link already closed the request. If it throws, the
189
+ card may still work or may have been used, so keep the hold.
190
+ - An owner's not paid decision does not close a Link card, which can work for up to 48 hours. After
191
+ `CheckoutCheckpointClosedError`, if `error.checkpoint.paymentReference` is set, cancel the request
192
+ with `wallet.cancel(error.checkpoint.paymentReference.id)` before you start a new purchase. If that
193
+ call throws, the card may have been used, so do not begin a new purchase.
159
194
  - Pass `onLinkApproval` to get Stripe Link's approval page, so a person without the Link app can
160
195
  approve on the web while signed in to Link. It is called once, after Conto approves and Link
161
196
  creates the request, with `{ checkpoint, linkRequestId, approvalUrl, approvalExpiresAt }`.
@@ -163,20 +198,108 @@ const confirmed = await conto.checkout.confirm(result.checkpoint, {
163
198
  stopped while the person had not decided.
164
199
  - The card (`number`, `securityValue` for the CVC, `expMonth`, `expYear`, `validUntil`, `last4`,
165
200
  `brand`) stays in this process. Printing or serializing the result shows only the last four
166
- digits. It never appears in an error.
167
- - The card covers the approved total, so `confirm()` with a `paymentReference` refuses a
168
- different total. Call `conto.checkout.replaceLinkCard(result, { wallet, amountMinor })` to
169
- cancel the old card and checkpoint and get a card for the final total.
201
+ digits, the brand, and the expiry. It never appears in an error.
202
+ - The card covers the approved total, so `confirm()` refuses a different total for a checkpoint from
203
+ `getLinkCard()` or `onLinkApproval`, even when `paymentReference` is not passed. Call
204
+ `conto.checkout.replaceLinkCard(result, { wallet, amountMinor })` to
205
+ cancel the old card and checkpoint and get a card for the final total. `replaceLinkCard()` always
206
+ reads the checkpoint first. `confirm()` reads it when the total differs and it has no reference,
207
+ including for checkouts without a Link card, so a saved copy is safe to pass. A `getLinkCard()`
208
+ retry reads it after a failure, before it closes a request on record. Those reads need
209
+ `transactions:read`. If the checkpoint was already confirmed, or a different Link request is on
210
+ record, `replaceLinkCard()` throws `LINK_CARD_NOT_REPLACED` and leaves the card and the hold alone.
211
+ Report the outcome instead of replacing a card whose payment may be in flight. If the old card
212
+ closes but Conto does not release the spend, it throws `CHECKPOINT_NOT_CANCELLED`, and trying
213
+ again is safe. If the total changes again, pass the latest result. Do not run it while another
214
+ flow may confirm the same checkpoint.
170
215
  - Any `LinkWallet` works in place of `linkCliWallet()`: it creates a card request, waits for
171
216
  approval, retrieves the card, and cancels.
172
217
  - The protection holds only when the agent cannot also run `link-cli`, use a shell, or call Link
173
218
  with your credentials itself.
174
219
  - Pass observed final items and charges to `confirm()` even when the total is unchanged.
175
- The server rejects a changed cart against the approved intent. The trusted host must
220
+ The server checks the items and the final total. Other rows, such as tax and shipping, may
221
+ differ from the ones passed to `begin()`. The trusted host must
176
222
  validate the actual merchant and checkout, own a durable one-use payment dispatch
177
223
  claim, and keep card use outside model tools. Replaying a confirmed checkpoint is
178
224
  not permission to submit payment again.
179
225
 
226
+ ## Shared budgets
227
+
228
+ A shared budget is one limit that several agents draw on. The budget holder sets it in Budgets or with `admin.spending`. A budget stops other payment paths for the agents it covers, listed below. Agents no budget covers are not affected, except a buyer linked to an owner workspace. Until a budget in that workspace covers it, those paths fail with `reasonCode` `LINKED_BUDGET_REQUIRED`.
229
+
230
+ ```typescript
231
+ // Agent key. Nothing on conto.spending can raise a limit, approve a task, or release a hold.
232
+ const { budgets } = await conto.spending.budgets();
233
+ await conto.spending.preview({
234
+ amount: '40',
235
+ currency: 'USDC',
236
+ sourceId: walletId,
237
+ merchant: recipientAddress,
238
+ });
239
+
240
+ // Organization API key.
241
+ await admin.spending.createBudget({
242
+ name: 'Team tools',
243
+ scope: 'AGENTS',
244
+ participantIds: [agentA, agentB],
245
+ amount: '500',
246
+ currency: 'USDC',
247
+ period: 'MONTHLY',
248
+ periodStart: new Date().toISOString(),
249
+ periodEnd: new Date(Date.now() + 365 * 86_400_000).toISOString(),
250
+ });
251
+ ```
252
+
253
+ **A budget stops other payment paths.** While it exists, it stops x402, MPP, external wallet
254
+ payments, agent-to-agent payments, payment grants, checkout relay, hosted Stripe Link, guarded browser
255
+ checkout (`runGuardedCheckout()`), card overlays and checkout checkpoints, including
256
+ `checkout.getLinkCard()`, for every agent it covers. A paused, expired or not yet started budget still
257
+ stops them. Until its start date, while paused, and after its end date, a budget also refuses the
258
+ managed wallet payments and connected card draws it covers, for those agents. A task's budget stops nothing on other paths. It applies only to a managed wallet payment or connected card draw that names the task, and it refuses that payment or draw while it is paused.
259
+ `updateBudget()` can resume a paused budget or extend an
260
+ expired one, and a start date cannot be changed. To end a budget, call `admin.spending.deleteBudget(id, { version })`. It fails with
261
+ `reasonCode` `BUDGET_HAS_HOLDS` while a purchase holds spend against the budget. Try a budget first on one agent with a Tempo Testnet wallet.
262
+
263
+ - A budget covers purchases in its own currency, so set it to what the wallets pay in: `USDC` on
264
+ Base, `USDC.e` on Tempo or `pathUSD` on Tempo Testnet. `USD`, `USDC` and `USDC.e` budgets cover one
265
+ another at par when no budget at that level, workspace, agent or task, is in the exact currency. A
266
+ managed wallet payment or connected card draw is refused, whatever the budget's state, when a level
267
+ has budgets for that agent and none covers its currency. `preview()` gives `reasonCode`
268
+ `BUDGET_CURRENCY_UNSUPPORTED`. A budget's currency cannot be changed.
269
+ - `preview()` needs a managed wallet linked to the agent, and that wallet's stablecoin as `currency`.
270
+ It holds nothing and approves nothing, so keep the request, approval and execute flow.
271
+ - When a budget requires a task, ask for one with `conto.spending.requestTask()`. Once the budget
272
+ holder approves it, pass its id as `context.taskRequestId` on `payments.request()` from a managed
273
+ wallet, or as `taskRequestId` on `connectedCards.draw()`. A self-custody wallet cannot use a task. When a budget covers the agent, `payments.request()` throws status 409 with `reasonCode` `EXECUTION_PATH_NOT_CONNECTED`. When none does, a request that names a task ends in `DENIED`. `payments.approve()` refuses the task fields.
274
+ - `context.purchaseKey` identifies one purchase. A key cannot be used again, even after its purchase
275
+ is released, so use a new key for a new attempt.
276
+ - `payments.request()` can return `APPROVED` for a purchase a budget refuses. `payments.execute()`
277
+ then throws status 409 and code `SHARED_BUDGET_BLOCKED`. With `autoExecute`, the request stays
278
+ `APPROVED` and nothing is thrown. To see why, call `preview()` with the same `taskRequestId` and
279
+ `purchaseKey`.
280
+ - `purchases()` lists purchases against budgets in the agent's organization, not Commerce
281
+ `conto.purchases`. It is empty when none of those budgets has applied. A buyer linked to an owner
282
+ workspace has no row for a purchase that only an owner budget covered. The owner reads those with
283
+ `admin.spending.purchases()`.
284
+ - `admin.spending` needs an organization API key. `createBudget()`, `updateBudget()`, `deleteBudget()`
285
+ and `decideTask()` need `policies:write`. `accounting()`, `attachEvidence()` and `reviewEvidence()`
286
+ need `transactions:write`. `reconcile()` needs `spending:reconcile`, which Standard keys do not
287
+ have. An admin SDK key cannot change budgets, decide tasks, or reconcile.
288
+ - `attachEvidence()` takes a PDF, PNG or JPEG of at most 2 MiB, with up to 10 files per purchase. Set
289
+ the Blob's `type`, for example `new Blob([bytes], { type: 'application/pdf' })`. A Blob with no type,
290
+ or with a type that does not match the file's first bytes, fails with `reasonCode` `INVALID_FILE`.
291
+ Conto does not take the amount, currency or merchant from the file. The budget holder enters them
292
+ with `reviewEvidence()`.
293
+ - `exportPurchases()` returns CSV bytes and fails with `reasonCode` `EXPORT_TOO_LARGE` above 10,000
294
+ purchases.
295
+ - Most budget errors have `code` `POLICY_VIOLATION`, so read `reasonCode` on `ContoError` and
296
+ `ContoAdminError`. Some also have `nextActions`, such as `SHARED_BUDGET_EXCEEDED` with
297
+ `REQUEST_BUDGET_INCREASE`. `checkout.begin()`, `getLinkCard()` and `replaceLinkCard()` have `code` `CHECKPOINT_BUDGET_NOT_SUPPORTED` and `reasonCode` `EXECUTION_PATH_NOT_CONNECTED`. `payments.execute()` has `code`
298
+ `SHARED_BUDGET_BLOCKED` and no `reasonCode`.
299
+ - A `VALIDATION_ERROR` on a spending call usually lists the fields in `fieldErrors`. It does not when
300
+ the call got a field it does not accept, such as `periodStart` or `currency` on `updateBudget()`. A
301
+ `payments.request()` validation failure never has `fieldErrors`.
302
+
180
303
  ## External-card Policy Overlay
181
304
 
182
305
  The admin client registers only a masked card alias and assigns cooperative
@@ -322,9 +445,10 @@ Shop Pay, or PayPal browser action without requiring private provider integratio
322
445
  privileged browser executor must be available only through this wrapper. An unrestricted click or
323
446
  payment tool would bypass the control. Persist its stable dispatch attempt before calling the
324
447
  helper. The helper owns authorization, one-use claim, the privileged callback, and finalization.
325
- The
326
- [Stripe Link guide](https://conto.finance/docs/guides/stripe-link#make-the-check-enforceable)
327
- describes the runtime boundary and includes an example.
448
+ When a rule requires the owner's approval, `runGuardedCheckout` stops with status 409 and code
449
+ `APPROVAL_REQUIRED`. Checkpoints wait for the owner instead, so for Stripe Link prefer the
450
+ [checkpoints in the Stripe Link guide](https://conto.finance/docs/guides/stripe-link#add-the-checkpoints)
451
+ or `getLinkCard()`.
328
452
 
329
453
  Isolated browser processes can implement `PrivilegedBrowserCheckoutDriver` and use
330
454
  `runGuardedCheckoutWithDriver`. The agent model must receive only that guarded wrapper, never the
@@ -527,7 +651,8 @@ and other non-idempotent mutations are never retried automatically. If execution
527
651
  payment until an operator has resolved it.
528
652
 
529
653
  `ContoError` and `ContoAdminError` expose a stable error contract: `code`, `status`, `requestId`,
530
- `retryAfter`, and locally generated `retryGuidance` where applicable. Arbitrary server details,
654
+ `retryAfter`, `fieldErrors` on most spending validation failures, `reasonCode` and `nextActions` on a
655
+ budget refusal, and locally generated `retryGuidance` where applicable. Arbitrary server details,
531
656
  context, hints, and workflow steps are not forwarded through SDK errors:
532
657
 
533
658
  ```typescript