@conto_finance/sdk 0.8.0 → 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,18 +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.
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.
158
163
  - Retrying with the same input resumes the same checkpoint and Link request while Conto's approval
159
164
  is open. That approval lasts 30 minutes. For about 5 to 10 minutes after it lapses, the stage can
160
165
  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`.
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.
162
176
  - `getLinkCard()` and `replaceLinkCard()` tell Conto the Link request ID as soon as Stripe Link
163
177
  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
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
166
182
  review alert and records what happened. If Conto cannot record the request, the SDK cancels it in
167
183
  Link, then cancels the checkpoint, and throws `LinkCardError` with code `LINK_CARD_NOT_RECORDED`.
168
- `holdReleased` says whether the spend was released.
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.
169
194
  - Pass `onLinkApproval` to get Stripe Link's approval page, so a person without the Link app can
170
195
  approve on the web while signed in to Link. It is called once, after Conto approves and Link
171
196
  creates the request, with `{ checkpoint, linkRequestId, approvalUrl, approvalExpiresAt }`.
@@ -173,11 +198,20 @@ const confirmed = await conto.checkout.confirm(result.checkpoint, {
173
198
  stopped while the person had not decided.
174
199
  - The card (`number`, `securityValue` for the CVC, `expMonth`, `expYear`, `validUntil`, `last4`,
175
200
  `brand`) stays in this process. Printing or serializing the result shows only the last four
176
- digits. It never appears in an error.
201
+ digits, the brand, and the expiry. It never appears in an error.
177
202
  - The card covers the approved total, so `confirm()` refuses a different total for a checkpoint from
178
203
  `getLinkCard()` or `onLinkApproval`, even when `paymentReference` is not passed. Call
179
204
  `conto.checkout.replaceLinkCard(result, { wallet, amountMinor })` to
180
- cancel the old card and checkpoint and get a card for the final total.
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.
181
215
  - Any `LinkWallet` works in place of `linkCliWallet()`: it creates a card request, waits for
182
216
  approval, retrieves the card, and cancels.
183
217
  - The protection holds only when the agent cannot also run `link-cli`, use a shell, or call Link
@@ -191,10 +225,7 @@ const confirmed = await conto.checkout.confirm(result.checkpoint, {
191
225
 
192
226
  ## Shared budgets
193
227
 
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`.
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`.
198
229
 
199
230
  ```typescript
200
231
  // Agent key. Nothing on conto.spending can raise a limit, approve a task, or release a hold.
@@ -224,16 +255,14 @@ payments, agent-to-agent payments, payment grants, checkout relay, hosted Stripe
224
255
  checkout (`runGuardedCheckout()`), card overlays and checkout checkpoints, including
225
256
  `checkout.getLinkCard()`, for every agent it covers. A paused, expired or not yet started budget still
226
257
  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.
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.
229
259
  `updateBudget()` can resume a paused budget or extend an
230
260
  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.
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.
233
262
 
234
263
  - A budget covers purchases in its own currency, so set it to what the wallets pay in: `USDC` on
235
264
  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
265
+ another at par when no budget at that level, workspace, agent or task, is in the exact currency. A
237
266
  managed wallet payment or connected card draw is refused, whatever the budget's state, when a level
238
267
  has budgets for that agent and none covers its currency. `preview()` gives `reasonCode`
239
268
  `BUDGET_CURRENCY_UNSUPPORTED`. A budget's currency cannot be changed.
@@ -241,8 +270,7 @@ organization first.
241
270
  It holds nothing and approves nothing, so keep the request, approval and execute flow.
242
271
  - When a budget requires a task, ask for one with `conto.spending.requestTask()`. Once the budget
243
272
  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.
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.
246
274
  - `context.purchaseKey` identifies one purchase. A key cannot be used again, even after its purchase
247
275
  is released, so use a new key for a new attempt.
248
276
  - `payments.request()` can return `APPROVED` for a purchase a budget refuses. `payments.execute()`
@@ -264,10 +292,9 @@ organization first.
264
292
  with `reviewEvidence()`.
265
293
  - `exportPurchases()` returns CSV bytes and fails with `reasonCode` `EXPORT_TOO_LARGE` above 10,000
266
294
  purchases.
267
- - Budget errors have `code` `POLICY_VIOLATION`, so read `reasonCode` on `ContoError` and
295
+ - Most budget errors have `code` `POLICY_VIOLATION`, so read `reasonCode` on `ContoError` and
268
296
  `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`
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`
271
298
  `SHARED_BUDGET_BLOCKED` and no `reasonCode`.
272
299
  - A `VALIDATION_ERROR` on a spending call usually lists the fields in `fieldErrors`. It does not when
273
300
  the call got a field it does not accept, such as `periodStart` or `currency` on `updateBudget()`. A
package/dist/index.d.mts CHANGED
@@ -2961,6 +2961,12 @@ interface CheckoutCheckpoint {
2961
2961
  outcomeReference?: string;
2962
2962
  /** The payment your checkout reported at confirm or outcome, such as a Link spend request. */
2963
2963
  paymentReference?: CheckoutPaymentReference;
2964
+ /**
2965
+ * True when the checkpoint stopped before its confirm while its Link request may still work, so
2966
+ * the stage is OUTCOME_UNKNOWN. Cancel the Link request, then cancel the checkpoint. Absent on a
2967
+ * confirmed checkpoint with no outcome, whose card may be in use: report the outcome instead.
2968
+ */
2969
+ linkRequestOpen?: true;
2964
2970
  /** Outcomes are reported by your checkout. Conto does not verify settlement here. */
2965
2971
  settlementVerified: false;
2966
2972
  }
@@ -3741,9 +3747,8 @@ declare class CheckoutApprovalTimeoutError extends ContoError {
3741
3747
  constructor(checkpoint: CheckoutCheckpoint);
3742
3748
  }
3743
3749
  /**
3744
- * getLinkCard() or replaceLinkCard() stopped after Conto approved the purchase.
3745
- * No card was returned. Conto cancelled the checkpoint to release the held
3746
- * spend unless holdReleased is false. Do not pay.
3750
+ * getLinkCard() or replaceLinkCard() stopped, and no card was returned. Do not
3751
+ * pay. Follow retryGuidance, which depends on the code and the stage.
3747
3752
  */
3748
3753
  declare class LinkCardError extends ContoError {
3749
3754
  readonly checkpoint: CheckoutCheckpoint;
@@ -3751,11 +3756,16 @@ declare class LinkCardError extends ContoError {
3751
3756
  /**
3752
3757
  * The Stripe Link page where the person could approve the request, set when
3753
3758
  * the SDK stopped while the request was waiting for them. It works only while
3754
- * the request is still open in Link. When holdReleased is true, Conto has
3755
- * already cancelled the request.
3759
+ * the request is still open in Link. With LINK_CARD_NOT_CANCELLED it is set
3760
+ * only when a retry can resume the request. With any other code, the request
3761
+ * is already closed.
3756
3762
  */
3757
3763
  readonly approvalUrl?: string;
3758
- /** True when Conto cancelled the checkpoint and released the held spend. */
3764
+ /**
3765
+ * True when the checkpoint no longer holds the spend. It says nothing about
3766
+ * the card. With LINK_CARD_NOT_CANCELLED, LINK_CARD_NOT_REPLACED or
3767
+ * LINK_CARD_ABORTED, a card may still work.
3768
+ */
3759
3769
  readonly holdReleased: boolean;
3760
3770
  constructor(input: {
3761
3771
  message: string;
@@ -4224,7 +4234,9 @@ declare class CheckoutClient {
4224
4234
  reportOutcome(checkpointId: string, input: ReportCheckoutOutcomeInput): Promise<CheckoutCheckpoint>;
4225
4235
  /**
4226
4236
  * Cancel a checkpoint that was not confirmed and release its spend. A
4227
- * checkpoint waiting for the owner can be cancelled too.
4237
+ * checkpoint waiting for the owner can be cancelled too. This does not close
4238
+ * a Link card, which can still work with nothing held behind it, so cancel
4239
+ * the Link request first.
4228
4240
  */
4229
4241
  cancel(checkpointId: string): Promise<CheckoutCheckpoint>;
4230
4242
  get(checkpointId: string): Promise<CheckoutCheckpoint>;
@@ -4252,16 +4264,22 @@ declare class CheckoutClient {
4252
4264
  * held spend and throws a LinkCardError. When Conto cannot cancel an open Link
4253
4265
  * request, it keeps the hold and throws LINK_CARD_NOT_CANCELLED with the
4254
4266
  * request id. Retrying with the same input resumes the same checkpoint and
4255
- * Link request while Conto's approval is open. Once the stage is OUTCOME_UNKNOWN,
4256
- * a retry throws CHECKOUT_CHECKPOINT_NOT_APPROVED: cancel the Link request, then
4257
- * the checkpoint.
4267
+ * Link request while Conto's approval is open. If its approval ended with the
4268
+ * request open, a retry throws CHECKOUT_CHECKPOINT_NOT_APPROVED. Cancel the Link
4269
+ * request, then the checkpoint. Once a confirm has started, a retry throws
4270
+ * LINK_CARD_NOT_ISSUED. Do not cancel the Link request while a payment may be in
4271
+ * flight. Follow error.retryGuidance, which depends on the stage.
4258
4272
  */
4259
4273
  getLinkCard(input: GetLinkCardInput): Promise<LinkCardResult>;
4260
4274
  /**
4261
4275
  * The final total differs from the one the Link card covers. This cancels
4262
4276
  * the old Link request and checkpoint, begins a checkpoint at the final
4263
4277
  * total (waiting for the owner when a rule requires it), and gets a new card
4264
- * for it. Confirm the returned checkpoint with the same total.
4278
+ * for it. Confirm the returned checkpoint with the same total. It reads the
4279
+ * checkpoint first. At a stage where a payment may be in flight, or with a
4280
+ * different Link request on record, this throws LINK_CARD_NOT_REPLACED and
4281
+ * leaves the card and the hold alone. After the old card is closed, trying
4282
+ * again with the same input resumes the replacement.
4265
4283
  */
4266
4284
  replaceLinkCard(previous: LinkCardResult, input: ReplaceLinkCardInput): Promise<LinkCardResult>;
4267
4285
  }
package/dist/index.d.ts CHANGED
@@ -2961,6 +2961,12 @@ interface CheckoutCheckpoint {
2961
2961
  outcomeReference?: string;
2962
2962
  /** The payment your checkout reported at confirm or outcome, such as a Link spend request. */
2963
2963
  paymentReference?: CheckoutPaymentReference;
2964
+ /**
2965
+ * True when the checkpoint stopped before its confirm while its Link request may still work, so
2966
+ * the stage is OUTCOME_UNKNOWN. Cancel the Link request, then cancel the checkpoint. Absent on a
2967
+ * confirmed checkpoint with no outcome, whose card may be in use: report the outcome instead.
2968
+ */
2969
+ linkRequestOpen?: true;
2964
2970
  /** Outcomes are reported by your checkout. Conto does not verify settlement here. */
2965
2971
  settlementVerified: false;
2966
2972
  }
@@ -3741,9 +3747,8 @@ declare class CheckoutApprovalTimeoutError extends ContoError {
3741
3747
  constructor(checkpoint: CheckoutCheckpoint);
3742
3748
  }
3743
3749
  /**
3744
- * getLinkCard() or replaceLinkCard() stopped after Conto approved the purchase.
3745
- * No card was returned. Conto cancelled the checkpoint to release the held
3746
- * spend unless holdReleased is false. Do not pay.
3750
+ * getLinkCard() or replaceLinkCard() stopped, and no card was returned. Do not
3751
+ * pay. Follow retryGuidance, which depends on the code and the stage.
3747
3752
  */
3748
3753
  declare class LinkCardError extends ContoError {
3749
3754
  readonly checkpoint: CheckoutCheckpoint;
@@ -3751,11 +3756,16 @@ declare class LinkCardError extends ContoError {
3751
3756
  /**
3752
3757
  * The Stripe Link page where the person could approve the request, set when
3753
3758
  * the SDK stopped while the request was waiting for them. It works only while
3754
- * the request is still open in Link. When holdReleased is true, Conto has
3755
- * already cancelled the request.
3759
+ * the request is still open in Link. With LINK_CARD_NOT_CANCELLED it is set
3760
+ * only when a retry can resume the request. With any other code, the request
3761
+ * is already closed.
3756
3762
  */
3757
3763
  readonly approvalUrl?: string;
3758
- /** True when Conto cancelled the checkpoint and released the held spend. */
3764
+ /**
3765
+ * True when the checkpoint no longer holds the spend. It says nothing about
3766
+ * the card. With LINK_CARD_NOT_CANCELLED, LINK_CARD_NOT_REPLACED or
3767
+ * LINK_CARD_ABORTED, a card may still work.
3768
+ */
3759
3769
  readonly holdReleased: boolean;
3760
3770
  constructor(input: {
3761
3771
  message: string;
@@ -4224,7 +4234,9 @@ declare class CheckoutClient {
4224
4234
  reportOutcome(checkpointId: string, input: ReportCheckoutOutcomeInput): Promise<CheckoutCheckpoint>;
4225
4235
  /**
4226
4236
  * Cancel a checkpoint that was not confirmed and release its spend. A
4227
- * checkpoint waiting for the owner can be cancelled too.
4237
+ * checkpoint waiting for the owner can be cancelled too. This does not close
4238
+ * a Link card, which can still work with nothing held behind it, so cancel
4239
+ * the Link request first.
4228
4240
  */
4229
4241
  cancel(checkpointId: string): Promise<CheckoutCheckpoint>;
4230
4242
  get(checkpointId: string): Promise<CheckoutCheckpoint>;
@@ -4252,16 +4264,22 @@ declare class CheckoutClient {
4252
4264
  * held spend and throws a LinkCardError. When Conto cannot cancel an open Link
4253
4265
  * request, it keeps the hold and throws LINK_CARD_NOT_CANCELLED with the
4254
4266
  * request id. Retrying with the same input resumes the same checkpoint and
4255
- * Link request while Conto's approval is open. Once the stage is OUTCOME_UNKNOWN,
4256
- * a retry throws CHECKOUT_CHECKPOINT_NOT_APPROVED: cancel the Link request, then
4257
- * the checkpoint.
4267
+ * Link request while Conto's approval is open. If its approval ended with the
4268
+ * request open, a retry throws CHECKOUT_CHECKPOINT_NOT_APPROVED. Cancel the Link
4269
+ * request, then the checkpoint. Once a confirm has started, a retry throws
4270
+ * LINK_CARD_NOT_ISSUED. Do not cancel the Link request while a payment may be in
4271
+ * flight. Follow error.retryGuidance, which depends on the stage.
4258
4272
  */
4259
4273
  getLinkCard(input: GetLinkCardInput): Promise<LinkCardResult>;
4260
4274
  /**
4261
4275
  * The final total differs from the one the Link card covers. This cancels
4262
4276
  * the old Link request and checkpoint, begins a checkpoint at the final
4263
4277
  * total (waiting for the owner when a rule requires it), and gets a new card
4264
- * for it. Confirm the returned checkpoint with the same total.
4278
+ * for it. Confirm the returned checkpoint with the same total. It reads the
4279
+ * checkpoint first. At a stage where a payment may be in flight, or with a
4280
+ * different Link request on record, this throws LINK_CARD_NOT_REPLACED and
4281
+ * leaves the card and the hold alone. After the old card is closed, trying
4282
+ * again with the same input resumes the replacement.
4265
4283
  */
4266
4284
  replaceLinkCard(previous: LinkCardResult, input: ReplaceLinkCardInput): Promise<LinkCardResult>;
4267
4285
  }