@conto_finance/sdk 0.7.0 → 0.7.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
@@ -156,6 +156,11 @@ const confirmed = await conto.checkout.confirm(result.checkpoint, {
156
156
  held spend and throws `LinkCardDeniedError`, `LinkCardExpiredError`, `LinkCardTimeoutError`, or
157
157
  `LinkWalletUnavailableError`. All extend `LinkCardError`. Do not pay.
158
158
  - Retrying with the same input resumes the same checkpoint and Link request.
159
+ - Pass `onLinkApproval` to get Stripe Link's approval page, so a person without the Link app can
160
+ approve on the web while signed in to Link. It is called once, after Conto approves and Link
161
+ creates the request, with `{ checkpoint, linkRequestId, approvalUrl, approvalExpiresAt }`.
162
+ `approvalUrl` is always an HTTPS link.com address. A `LinkCardError` has it too when the SDK
163
+ stopped while the person had not decided.
159
164
  - The card (`number`, `securityValue` for the CVC, `expMonth`, `expYear`, `validUntil`, `last4`,
160
165
  `brand`) stays in this process. Printing or serializing the result shows only the last four
161
166
  digits. It never appears in an error.
@@ -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
+ };
package/dist/index.d.mts CHANGED
@@ -2643,6 +2643,33 @@ interface WaitForCheckoutApprovalOptions {
2643
2643
  signal?: AbortSignal;
2644
2644
  }
2645
2645
 
2646
+ /**
2647
+ * The Link card request is waiting for the person. Conto has already approved
2648
+ * the purchase, and Link has created the request.
2649
+ */
2650
+ interface LinkApprovalEvent {
2651
+ /** The checkpoint Conto approved. */
2652
+ checkpoint: CheckoutCheckpoint;
2653
+ /** The Link spend request id, such as lsrq_... */
2654
+ linkRequestId: string;
2655
+ /**
2656
+ * The Stripe Link page where the person can approve on the web while signed
2657
+ * in to Link, so they do not need the Link app. Only an HTTPS link.com address
2658
+ * is passed. Absent when Link gave none or it did not pass that check. The
2659
+ * person can still approve in the Link app.
2660
+ */
2661
+ approvalUrl?: string;
2662
+ /** When Link stops accepting approval, as an ISO 8601 time, when Link reports it. */
2663
+ approvalExpiresAt?: string;
2664
+ }
2665
+ /**
2666
+ * Called once when the Link card request is waiting for the person, after
2667
+ * Conto approved the purchase and before the SDK starts waiting. Show
2668
+ * approvalUrl to the person. The SDK does not wait for it to finish, and an
2669
+ * error it throws or rejects with does not affect the purchase. A retry that
2670
+ * resumes a waiting request calls it again with the same URL.
2671
+ */
2672
+ type LinkApprovalCallback = (event: LinkApprovalEvent) => void | Promise<void>;
2646
2673
  /**
2647
2674
  * A Link card for one purchase: the checkpoint begin input plus the Link
2648
2675
  * wallet that runs in your environment. The checkout method is always LINK.
@@ -2654,6 +2681,8 @@ interface GetLinkCardInput extends Omit<BeginCheckoutCheckpointInput, 'checkoutM
2654
2681
  ownerApprovalTimeoutMs?: number;
2655
2682
  /** How long to wait for the person to approve in Link. Defaults to 10 minutes. */
2656
2683
  linkApprovalTimeoutMs?: number;
2684
+ /** Called once with the Link approval page when the person needs to approve in Link. */
2685
+ onLinkApproval?: LinkApprovalCallback;
2657
2686
  signal?: AbortSignal;
2658
2687
  }
2659
2688
  /** A new Link card for a changed final total. */
@@ -2667,6 +2696,8 @@ interface ReplaceLinkCardInput {
2667
2696
  totals?: CommerceTotalInput[];
2668
2697
  ownerApprovalTimeoutMs?: number;
2669
2698
  linkApprovalTimeoutMs?: number;
2699
+ /** Called once with the Link approval page when the person needs to approve in Link. */
2700
+ onLinkApproval?: LinkApprovalCallback;
2670
2701
  signal?: AbortSignal;
2671
2702
  }
2672
2703
  interface LinkCardResult {
@@ -3380,6 +3411,13 @@ declare class CheckoutApprovalTimeoutError extends ContoError {
3380
3411
  declare class LinkCardError extends ContoError {
3381
3412
  readonly checkpoint: CheckoutCheckpoint;
3382
3413
  readonly linkRequestId?: string;
3414
+ /**
3415
+ * The Stripe Link page where the person could approve the request, set when
3416
+ * the SDK stopped while the request was waiting for them. It works only while
3417
+ * the request is still open in Link. When holdReleased is true, Conto has
3418
+ * already cancelled the request.
3419
+ */
3420
+ readonly approvalUrl?: string;
3383
3421
  /** True when Conto cancelled the checkpoint and released the held spend. */
3384
3422
  readonly holdReleased: boolean;
3385
3423
  constructor(input: {
@@ -3387,6 +3425,7 @@ declare class LinkCardError extends ContoError {
3387
3425
  code: string;
3388
3426
  checkpoint: CheckoutCheckpoint;
3389
3427
  linkRequestId?: string;
3428
+ approvalUrl?: string;
3390
3429
  holdReleased: boolean;
3391
3430
  retryGuidance?: string;
3392
3431
  });
@@ -3854,7 +3893,9 @@ declare class CheckoutClient {
3854
3893
  * 1. Conto runs checkpoint 1 and holds the spend. When a rule requires the
3855
3894
  * owner's approval, this waits for it.
3856
3895
  * 2. Only then does the wallet ask Link for a card for exactly the approved
3857
- * amount at this merchant. The person approves it in Link.
3896
+ * amount at this merchant. The person approves it in Link. Pass
3897
+ * onLinkApproval to get Link's approval page, so they can approve on the
3898
+ * web without the Link app.
3858
3899
  * 3. The card is returned. It never reaches Conto.
3859
3900
  *
3860
3901
  * Then fill in the card, call confirm(result.checkpoint, { amountMinor,
@@ -3880,4 +3921,4 @@ declare class CheckoutClient {
3880
3921
  }
3881
3922
 
3882
3923
  export { AdminPurchasingClient, BrowserActionFinalizationPendingError, BrowserCheckoutTargetVerificationError, CardsClient, CheckoutApprovalDeniedError, CheckoutApprovalExpiredError, CheckoutApprovalPendingError, CheckoutApprovalTimeoutError, CheckoutCheckpointClosedError, CheckoutClient, ConnectedCardsClient, Conto, ContoAdmin, ContoAdminError, ContoError, ControlledExternalCardPaymentTool, ExternalCardConfirmationPendingError, Conto as Giro, ContoError as GiroError, LinkCardCredential, LinkCardDeniedError, LinkCardError, LinkCardExpiredError, LinkCardTimeoutError, LinkWallet, LinkWalletUnavailableError, OrganizationPurchasesClient, OrganizationRecurringPurchasesClient, PurchasesClient, RecurringPurchasesClient, TestCardsClient, canonicalizeBrowserCheckoutTarget, createVerifiedBrowserCheckoutDriver, Conto as default, fingerprintBrowserCheckoutTarget, isFinalPurchaseStatus, parseServerSentEvents, verifyPurchaseWebhook };
3883
- export type { AdminExternalCardOverlayEnforcement, Agent, AgentCheckoutAction, AgentCheckoutActionKind, AgentExternalCard, AgentSetupResult, AgentStatus, AgentType, AgentWalletLink, ApprovedExternalCardExecution, AssignExternalCardInput, AssignedPolicy, AuthorizeCheckoutActionResult, BeginCheckoutCheckpointInput, BeginCommerceExecutionInput, BrowserActionClaimResult, BrowserActionDispatchObservationInput, BrowserActionFinalizeResult, BrowserCheckoutAction, BrowserCheckoutTargetResolver, BrowserCheckoutTargetSnapshot, BrowserCheckoutTargetVerificationCode, CancellationOutcome, CanonicalBrowserCheckoutTargetSnapshot, ChainType, CheckoutActionAuthorization, CheckoutCheckpoint, CheckoutCheckpointApproval, CheckoutCheckpointMethod, CheckoutCheckpointNextAction, CheckoutCheckpointStage, CheckoutOutcome, CheckoutPaymentReference, CommerceCheckoutMethod, CommerceContextRequirement, CommerceExecution, CommerceExecutionAdapterKind, CommerceExecutionArtifactKind, CommerceExecutionChannel, CommerceExecutionConstraint, CommerceExecutionConstraintCoverage, CommerceExecutionConstraintCoverageLevel, CommerceExecutionDeliveryMode, CommerceExecutionEvidence, CommerceExecutionProvider, CommerceExecutionResult, CommerceExecutionSourceKind, CommerceExecutionStatus, CommerceFactConfidence, CommerceFactProvenanceInput, CommerceFactSource, CommerceFulfillmentInput, CommerceLineItemInput, CommerceProposalFactKey, CommerceProposalMissingFact, CommerceProposalResolution, CommerceProposalState, CommerceSettlementRail, CommerceTotalInput, CommerceTotalType, ComparePolicyVersionsResponse, ConfirmCheckoutCheckpointInput, ConnectedCardBrief, ConnectedCardCredential, ConnectedCardDrawInput, ConnectedCardDrawMerchant, ConnectedCardDrawResult, ConnectedCardDrawable, ConnectedCardListResult, ConnectedCardMandate, ConnectedCardMandateMerchant, ConnectedCardOutcomeReport, ConnectedCardOutcomeReportResult, ConnectedCardReference, ContoAdminConfig, ContoConfig, ContoRequestOptions, ControlledExternalCardPaymentResult, CreateAgentInput, CreatePolicyInput, CreatePolicyRuleInput, CreatePurchaseInput, CreateSdkKeyInput, CreateSdkKeyResult, CreateWalletInput, CustodyType, CustomerCustodyMode, CustomerWalletControlModel, CustomerWalletKeyAccess, DelegationType, ExecuteCommerceExecutionResult, ExternalCard, ExternalCardApprovalInput, ExternalCardApprovalResult, ExternalCardConfirmationInput, ExternalCardConfirmationResult, ExternalCardExecutionClaimResult, ExternalCardExecutionResult, ExternalCardExecutor, ExternalCardOverlayEnforcement, ExternalCardRequestStatusResult, ExternalWalletApprovalInput, ExternalWalletApprovalResult, ExternalWalletConfirmationInput, ExternalWalletConfirmationResult, FreezeAgentInput, GetLinkCardInput, ContoConfig as GiroConfig, GuardCheckoutResult, GuardedBrowserDispatchResult, GuardedCheckoutInput, LinkCardResult, LinkWalletInput, ListAgentsParams, ListAgentsResponse, ListPoliciesParams, ListPoliciesResponse, ListPolicyVersionsResponse, ListWalletsParams, ListWalletsResponse, MppBudgetResult, MppPreAuthorizeInput, MppPreAuthorizeResult, MppRecordInput, MppRecordResult, NormalizeCommerceProposalInput, OrganizationMember, PaginatedResponse, PaginationParams, PauseExternalCardAssignmentResult, PaymentExecuteResult, PaymentRequestInput, PaymentRequestResult, PaymentStatusResult, Policy, PolicyRule, PolicyType, PolicyVersionChangeType, PolicyVersionDetail, PolicyVersionDiff, PolicyVersionFieldChange, PolicyVersionSnapshot, PolicyVersionSnapshotRule, PolicyVersionSummary, PrivilegedBrowserCheckoutDriver, ProtocolBudgetResult, ProtocolPreAuthorizeResult, ProtocolRecordGrant, ProtocolRecordGrantResult, ProtocolServiceSummary, ProtocolServicesResult, ProveWalletOwnershipInput, ProvisionWalletResult, Purchase, PurchaseAction, PurchaseActionAuthority, PurchaseActionKind, PurchaseAllowance, PurchaseBuyer, PurchaseBuyerCompanyCard, PurchaseBuyerCompanyCardLimits, PurchaseBuyerKitchenContext, PurchaseCompanyCard, PurchaseDelegation, PurchaseEvent, PurchaseEventPage, PurchaseEvidence, PurchaseGrocery, PurchaseGroceryItem, PurchaseGroceryList, PurchaseHostedLink, PurchaseItem, PurchaseKitchenContext, PurchaseKitchenInventoryItem, PurchaseKitchenListItem, PurchaseKitchenPreferences, PurchaseKitchenRecipe, PurchaseKitchenUsual, PurchaseList, PurchaseMoney, PurchasePaymentMethod, PurchasePhase, PurchaseRequest, PurchaseResponseAck, PurchaseStatus, PurchasingCapabilities, PurchasingSettings, RecurringPurchase, RecurringPurchaseCycleResult, RecurringPurchaseCycleStatus, RefreshBalanceResult, RegisterExternalCardInput, ReplaceLinkCardInput, ReportCheckoutOutcomeInput, RollbackPolicyVersionResponse, RuleAction, RuleOperator, RuleType, RunGuardedCheckoutResult, SdkKey, SdkKeyType, SetupCapabilityState, SetupCardDiagnostics, SetupDiagnosticAction, SetupDiagnosticIssue, SetupDiagnosticIssueCode, SetupDiagnostics, SetupWalletDiagnostics, TestCardCredential, TestCardDrawResult, TestCardDrawable, TestCardListResult, TestCardReceipt, TestCheckoutInput, TestCheckoutResult, TestMerchant, TestMerchantProduct, UnfreezeAgentInput, UpdateAgentInput, UpdateExternalCardAssignmentInput, UpdatePolicyInput, UpdatePolicyRuleInput, UpdateWalletInput, VerifiedBrowserCheckoutTarget, WaitForCheckoutApprovalOptions, Wallet, WalletChainBalanceSnapshot, WalletOwnershipChallenge, WalletOwnershipProof, WalletStatus, WalletType, WatchPurchaseOptions, X402Authorization, X402BudgetResult, X402PayInput, X402PayResult, X402PaymentRequirements, X402PreAuthorizeInput, X402PreAuthorizeResult, X402RecordInput, X402RecordResult };
3924
+ export type { AdminExternalCardOverlayEnforcement, Agent, AgentCheckoutAction, AgentCheckoutActionKind, AgentExternalCard, AgentSetupResult, AgentStatus, AgentType, AgentWalletLink, ApprovedExternalCardExecution, AssignExternalCardInput, AssignedPolicy, AuthorizeCheckoutActionResult, BeginCheckoutCheckpointInput, BeginCommerceExecutionInput, BrowserActionClaimResult, BrowserActionDispatchObservationInput, BrowserActionFinalizeResult, BrowserCheckoutAction, BrowserCheckoutTargetResolver, BrowserCheckoutTargetSnapshot, BrowserCheckoutTargetVerificationCode, CancellationOutcome, CanonicalBrowserCheckoutTargetSnapshot, ChainType, CheckoutActionAuthorization, CheckoutCheckpoint, CheckoutCheckpointApproval, CheckoutCheckpointMethod, CheckoutCheckpointNextAction, CheckoutCheckpointStage, CheckoutOutcome, CheckoutPaymentReference, CommerceCheckoutMethod, CommerceContextRequirement, CommerceExecution, CommerceExecutionAdapterKind, CommerceExecutionArtifactKind, CommerceExecutionChannel, CommerceExecutionConstraint, CommerceExecutionConstraintCoverage, CommerceExecutionConstraintCoverageLevel, CommerceExecutionDeliveryMode, CommerceExecutionEvidence, CommerceExecutionProvider, CommerceExecutionResult, CommerceExecutionSourceKind, CommerceExecutionStatus, CommerceFactConfidence, CommerceFactProvenanceInput, CommerceFactSource, CommerceFulfillmentInput, CommerceLineItemInput, CommerceProposalFactKey, CommerceProposalMissingFact, CommerceProposalResolution, CommerceProposalState, CommerceSettlementRail, CommerceTotalInput, CommerceTotalType, ComparePolicyVersionsResponse, ConfirmCheckoutCheckpointInput, ConnectedCardBrief, ConnectedCardCredential, ConnectedCardDrawInput, ConnectedCardDrawMerchant, ConnectedCardDrawResult, ConnectedCardDrawable, ConnectedCardListResult, ConnectedCardMandate, ConnectedCardMandateMerchant, ConnectedCardOutcomeReport, ConnectedCardOutcomeReportResult, ConnectedCardReference, ContoAdminConfig, ContoConfig, ContoRequestOptions, ControlledExternalCardPaymentResult, CreateAgentInput, CreatePolicyInput, CreatePolicyRuleInput, CreatePurchaseInput, CreateSdkKeyInput, CreateSdkKeyResult, CreateWalletInput, CustodyType, CustomerCustodyMode, CustomerWalletControlModel, CustomerWalletKeyAccess, DelegationType, ExecuteCommerceExecutionResult, ExternalCard, ExternalCardApprovalInput, ExternalCardApprovalResult, ExternalCardConfirmationInput, ExternalCardConfirmationResult, ExternalCardExecutionClaimResult, ExternalCardExecutionResult, ExternalCardExecutor, ExternalCardOverlayEnforcement, ExternalCardRequestStatusResult, ExternalWalletApprovalInput, ExternalWalletApprovalResult, ExternalWalletConfirmationInput, ExternalWalletConfirmationResult, FreezeAgentInput, GetLinkCardInput, ContoConfig as GiroConfig, GuardCheckoutResult, GuardedBrowserDispatchResult, GuardedCheckoutInput, LinkApprovalCallback, LinkApprovalEvent, LinkCardResult, LinkWalletInput, ListAgentsParams, ListAgentsResponse, ListPoliciesParams, ListPoliciesResponse, ListPolicyVersionsResponse, ListWalletsParams, ListWalletsResponse, MppBudgetResult, MppPreAuthorizeInput, MppPreAuthorizeResult, MppRecordInput, MppRecordResult, NormalizeCommerceProposalInput, OrganizationMember, PaginatedResponse, PaginationParams, PauseExternalCardAssignmentResult, PaymentExecuteResult, PaymentRequestInput, PaymentRequestResult, PaymentStatusResult, Policy, PolicyRule, PolicyType, PolicyVersionChangeType, PolicyVersionDetail, PolicyVersionDiff, PolicyVersionFieldChange, PolicyVersionSnapshot, PolicyVersionSnapshotRule, PolicyVersionSummary, PrivilegedBrowserCheckoutDriver, ProtocolBudgetResult, ProtocolPreAuthorizeResult, ProtocolRecordGrant, ProtocolRecordGrantResult, ProtocolServiceSummary, ProtocolServicesResult, ProveWalletOwnershipInput, ProvisionWalletResult, Purchase, PurchaseAction, PurchaseActionAuthority, PurchaseActionKind, PurchaseAllowance, PurchaseBuyer, PurchaseBuyerCompanyCard, PurchaseBuyerCompanyCardLimits, PurchaseBuyerKitchenContext, PurchaseCompanyCard, PurchaseDelegation, PurchaseEvent, PurchaseEventPage, PurchaseEvidence, PurchaseGrocery, PurchaseGroceryItem, PurchaseGroceryList, PurchaseHostedLink, PurchaseItem, PurchaseKitchenContext, PurchaseKitchenInventoryItem, PurchaseKitchenListItem, PurchaseKitchenPreferences, PurchaseKitchenRecipe, PurchaseKitchenUsual, PurchaseList, PurchaseMoney, PurchasePaymentMethod, PurchasePhase, PurchaseRequest, PurchaseResponseAck, PurchaseStatus, PurchasingCapabilities, PurchasingSettings, RecurringPurchase, RecurringPurchaseCycleResult, RecurringPurchaseCycleStatus, RefreshBalanceResult, RegisterExternalCardInput, ReplaceLinkCardInput, ReportCheckoutOutcomeInput, RollbackPolicyVersionResponse, RuleAction, RuleOperator, RuleType, RunGuardedCheckoutResult, SdkKey, SdkKeyType, SetupCapabilityState, SetupCardDiagnostics, SetupDiagnosticAction, SetupDiagnosticIssue, SetupDiagnosticIssueCode, SetupDiagnostics, SetupWalletDiagnostics, TestCardCredential, TestCardDrawResult, TestCardDrawable, TestCardListResult, TestCardReceipt, TestCheckoutInput, TestCheckoutResult, TestMerchant, TestMerchantProduct, UnfreezeAgentInput, UpdateAgentInput, UpdateExternalCardAssignmentInput, UpdatePolicyInput, UpdatePolicyRuleInput, UpdateWalletInput, VerifiedBrowserCheckoutTarget, WaitForCheckoutApprovalOptions, Wallet, WalletChainBalanceSnapshot, WalletOwnershipChallenge, WalletOwnershipProof, WalletStatus, WalletType, WatchPurchaseOptions, X402Authorization, X402BudgetResult, X402PayInput, X402PayResult, X402PaymentRequirements, X402PreAuthorizeInput, X402PreAuthorizeResult, X402RecordInput, X402RecordResult };
package/dist/index.d.ts CHANGED
@@ -2643,6 +2643,33 @@ interface WaitForCheckoutApprovalOptions {
2643
2643
  signal?: AbortSignal;
2644
2644
  }
2645
2645
 
2646
+ /**
2647
+ * The Link card request is waiting for the person. Conto has already approved
2648
+ * the purchase, and Link has created the request.
2649
+ */
2650
+ interface LinkApprovalEvent {
2651
+ /** The checkpoint Conto approved. */
2652
+ checkpoint: CheckoutCheckpoint;
2653
+ /** The Link spend request id, such as lsrq_... */
2654
+ linkRequestId: string;
2655
+ /**
2656
+ * The Stripe Link page where the person can approve on the web while signed
2657
+ * in to Link, so they do not need the Link app. Only an HTTPS link.com address
2658
+ * is passed. Absent when Link gave none or it did not pass that check. The
2659
+ * person can still approve in the Link app.
2660
+ */
2661
+ approvalUrl?: string;
2662
+ /** When Link stops accepting approval, as an ISO 8601 time, when Link reports it. */
2663
+ approvalExpiresAt?: string;
2664
+ }
2665
+ /**
2666
+ * Called once when the Link card request is waiting for the person, after
2667
+ * Conto approved the purchase and before the SDK starts waiting. Show
2668
+ * approvalUrl to the person. The SDK does not wait for it to finish, and an
2669
+ * error it throws or rejects with does not affect the purchase. A retry that
2670
+ * resumes a waiting request calls it again with the same URL.
2671
+ */
2672
+ type LinkApprovalCallback = (event: LinkApprovalEvent) => void | Promise<void>;
2646
2673
  /**
2647
2674
  * A Link card for one purchase: the checkpoint begin input plus the Link
2648
2675
  * wallet that runs in your environment. The checkout method is always LINK.
@@ -2654,6 +2681,8 @@ interface GetLinkCardInput extends Omit<BeginCheckoutCheckpointInput, 'checkoutM
2654
2681
  ownerApprovalTimeoutMs?: number;
2655
2682
  /** How long to wait for the person to approve in Link. Defaults to 10 minutes. */
2656
2683
  linkApprovalTimeoutMs?: number;
2684
+ /** Called once with the Link approval page when the person needs to approve in Link. */
2685
+ onLinkApproval?: LinkApprovalCallback;
2657
2686
  signal?: AbortSignal;
2658
2687
  }
2659
2688
  /** A new Link card for a changed final total. */
@@ -2667,6 +2696,8 @@ interface ReplaceLinkCardInput {
2667
2696
  totals?: CommerceTotalInput[];
2668
2697
  ownerApprovalTimeoutMs?: number;
2669
2698
  linkApprovalTimeoutMs?: number;
2699
+ /** Called once with the Link approval page when the person needs to approve in Link. */
2700
+ onLinkApproval?: LinkApprovalCallback;
2670
2701
  signal?: AbortSignal;
2671
2702
  }
2672
2703
  interface LinkCardResult {
@@ -3380,6 +3411,13 @@ declare class CheckoutApprovalTimeoutError extends ContoError {
3380
3411
  declare class LinkCardError extends ContoError {
3381
3412
  readonly checkpoint: CheckoutCheckpoint;
3382
3413
  readonly linkRequestId?: string;
3414
+ /**
3415
+ * The Stripe Link page where the person could approve the request, set when
3416
+ * the SDK stopped while the request was waiting for them. It works only while
3417
+ * the request is still open in Link. When holdReleased is true, Conto has
3418
+ * already cancelled the request.
3419
+ */
3420
+ readonly approvalUrl?: string;
3383
3421
  /** True when Conto cancelled the checkpoint and released the held spend. */
3384
3422
  readonly holdReleased: boolean;
3385
3423
  constructor(input: {
@@ -3387,6 +3425,7 @@ declare class LinkCardError extends ContoError {
3387
3425
  code: string;
3388
3426
  checkpoint: CheckoutCheckpoint;
3389
3427
  linkRequestId?: string;
3428
+ approvalUrl?: string;
3390
3429
  holdReleased: boolean;
3391
3430
  retryGuidance?: string;
3392
3431
  });
@@ -3854,7 +3893,9 @@ declare class CheckoutClient {
3854
3893
  * 1. Conto runs checkpoint 1 and holds the spend. When a rule requires the
3855
3894
  * owner's approval, this waits for it.
3856
3895
  * 2. Only then does the wallet ask Link for a card for exactly the approved
3857
- * amount at this merchant. The person approves it in Link.
3896
+ * amount at this merchant. The person approves it in Link. Pass
3897
+ * onLinkApproval to get Link's approval page, so they can approve on the
3898
+ * web without the Link app.
3858
3899
  * 3. The card is returned. It never reaches Conto.
3859
3900
  *
3860
3901
  * Then fill in the card, call confirm(result.checkpoint, { amountMinor,
@@ -3880,4 +3921,4 @@ declare class CheckoutClient {
3880
3921
  }
3881
3922
 
3882
3923
  export { AdminPurchasingClient, BrowserActionFinalizationPendingError, BrowserCheckoutTargetVerificationError, CardsClient, CheckoutApprovalDeniedError, CheckoutApprovalExpiredError, CheckoutApprovalPendingError, CheckoutApprovalTimeoutError, CheckoutCheckpointClosedError, CheckoutClient, ConnectedCardsClient, Conto, ContoAdmin, ContoAdminError, ContoError, ControlledExternalCardPaymentTool, ExternalCardConfirmationPendingError, Conto as Giro, ContoError as GiroError, LinkCardCredential, LinkCardDeniedError, LinkCardError, LinkCardExpiredError, LinkCardTimeoutError, LinkWallet, LinkWalletUnavailableError, OrganizationPurchasesClient, OrganizationRecurringPurchasesClient, PurchasesClient, RecurringPurchasesClient, TestCardsClient, canonicalizeBrowserCheckoutTarget, createVerifiedBrowserCheckoutDriver, Conto as default, fingerprintBrowserCheckoutTarget, isFinalPurchaseStatus, parseServerSentEvents, verifyPurchaseWebhook };
3883
- export type { AdminExternalCardOverlayEnforcement, Agent, AgentCheckoutAction, AgentCheckoutActionKind, AgentExternalCard, AgentSetupResult, AgentStatus, AgentType, AgentWalletLink, ApprovedExternalCardExecution, AssignExternalCardInput, AssignedPolicy, AuthorizeCheckoutActionResult, BeginCheckoutCheckpointInput, BeginCommerceExecutionInput, BrowserActionClaimResult, BrowserActionDispatchObservationInput, BrowserActionFinalizeResult, BrowserCheckoutAction, BrowserCheckoutTargetResolver, BrowserCheckoutTargetSnapshot, BrowserCheckoutTargetVerificationCode, CancellationOutcome, CanonicalBrowserCheckoutTargetSnapshot, ChainType, CheckoutActionAuthorization, CheckoutCheckpoint, CheckoutCheckpointApproval, CheckoutCheckpointMethod, CheckoutCheckpointNextAction, CheckoutCheckpointStage, CheckoutOutcome, CheckoutPaymentReference, CommerceCheckoutMethod, CommerceContextRequirement, CommerceExecution, CommerceExecutionAdapterKind, CommerceExecutionArtifactKind, CommerceExecutionChannel, CommerceExecutionConstraint, CommerceExecutionConstraintCoverage, CommerceExecutionConstraintCoverageLevel, CommerceExecutionDeliveryMode, CommerceExecutionEvidence, CommerceExecutionProvider, CommerceExecutionResult, CommerceExecutionSourceKind, CommerceExecutionStatus, CommerceFactConfidence, CommerceFactProvenanceInput, CommerceFactSource, CommerceFulfillmentInput, CommerceLineItemInput, CommerceProposalFactKey, CommerceProposalMissingFact, CommerceProposalResolution, CommerceProposalState, CommerceSettlementRail, CommerceTotalInput, CommerceTotalType, ComparePolicyVersionsResponse, ConfirmCheckoutCheckpointInput, ConnectedCardBrief, ConnectedCardCredential, ConnectedCardDrawInput, ConnectedCardDrawMerchant, ConnectedCardDrawResult, ConnectedCardDrawable, ConnectedCardListResult, ConnectedCardMandate, ConnectedCardMandateMerchant, ConnectedCardOutcomeReport, ConnectedCardOutcomeReportResult, ConnectedCardReference, ContoAdminConfig, ContoConfig, ContoRequestOptions, ControlledExternalCardPaymentResult, CreateAgentInput, CreatePolicyInput, CreatePolicyRuleInput, CreatePurchaseInput, CreateSdkKeyInput, CreateSdkKeyResult, CreateWalletInput, CustodyType, CustomerCustodyMode, CustomerWalletControlModel, CustomerWalletKeyAccess, DelegationType, ExecuteCommerceExecutionResult, ExternalCard, ExternalCardApprovalInput, ExternalCardApprovalResult, ExternalCardConfirmationInput, ExternalCardConfirmationResult, ExternalCardExecutionClaimResult, ExternalCardExecutionResult, ExternalCardExecutor, ExternalCardOverlayEnforcement, ExternalCardRequestStatusResult, ExternalWalletApprovalInput, ExternalWalletApprovalResult, ExternalWalletConfirmationInput, ExternalWalletConfirmationResult, FreezeAgentInput, GetLinkCardInput, ContoConfig as GiroConfig, GuardCheckoutResult, GuardedBrowserDispatchResult, GuardedCheckoutInput, LinkCardResult, LinkWalletInput, ListAgentsParams, ListAgentsResponse, ListPoliciesParams, ListPoliciesResponse, ListPolicyVersionsResponse, ListWalletsParams, ListWalletsResponse, MppBudgetResult, MppPreAuthorizeInput, MppPreAuthorizeResult, MppRecordInput, MppRecordResult, NormalizeCommerceProposalInput, OrganizationMember, PaginatedResponse, PaginationParams, PauseExternalCardAssignmentResult, PaymentExecuteResult, PaymentRequestInput, PaymentRequestResult, PaymentStatusResult, Policy, PolicyRule, PolicyType, PolicyVersionChangeType, PolicyVersionDetail, PolicyVersionDiff, PolicyVersionFieldChange, PolicyVersionSnapshot, PolicyVersionSnapshotRule, PolicyVersionSummary, PrivilegedBrowserCheckoutDriver, ProtocolBudgetResult, ProtocolPreAuthorizeResult, ProtocolRecordGrant, ProtocolRecordGrantResult, ProtocolServiceSummary, ProtocolServicesResult, ProveWalletOwnershipInput, ProvisionWalletResult, Purchase, PurchaseAction, PurchaseActionAuthority, PurchaseActionKind, PurchaseAllowance, PurchaseBuyer, PurchaseBuyerCompanyCard, PurchaseBuyerCompanyCardLimits, PurchaseBuyerKitchenContext, PurchaseCompanyCard, PurchaseDelegation, PurchaseEvent, PurchaseEventPage, PurchaseEvidence, PurchaseGrocery, PurchaseGroceryItem, PurchaseGroceryList, PurchaseHostedLink, PurchaseItem, PurchaseKitchenContext, PurchaseKitchenInventoryItem, PurchaseKitchenListItem, PurchaseKitchenPreferences, PurchaseKitchenRecipe, PurchaseKitchenUsual, PurchaseList, PurchaseMoney, PurchasePaymentMethod, PurchasePhase, PurchaseRequest, PurchaseResponseAck, PurchaseStatus, PurchasingCapabilities, PurchasingSettings, RecurringPurchase, RecurringPurchaseCycleResult, RecurringPurchaseCycleStatus, RefreshBalanceResult, RegisterExternalCardInput, ReplaceLinkCardInput, ReportCheckoutOutcomeInput, RollbackPolicyVersionResponse, RuleAction, RuleOperator, RuleType, RunGuardedCheckoutResult, SdkKey, SdkKeyType, SetupCapabilityState, SetupCardDiagnostics, SetupDiagnosticAction, SetupDiagnosticIssue, SetupDiagnosticIssueCode, SetupDiagnostics, SetupWalletDiagnostics, TestCardCredential, TestCardDrawResult, TestCardDrawable, TestCardListResult, TestCardReceipt, TestCheckoutInput, TestCheckoutResult, TestMerchant, TestMerchantProduct, UnfreezeAgentInput, UpdateAgentInput, UpdateExternalCardAssignmentInput, UpdatePolicyInput, UpdatePolicyRuleInput, UpdateWalletInput, VerifiedBrowserCheckoutTarget, WaitForCheckoutApprovalOptions, Wallet, WalletChainBalanceSnapshot, WalletOwnershipChallenge, WalletOwnershipProof, WalletStatus, WalletType, WatchPurchaseOptions, X402Authorization, X402BudgetResult, X402PayInput, X402PayResult, X402PaymentRequirements, X402PreAuthorizeInput, X402PreAuthorizeResult, X402RecordInput, X402RecordResult };
3924
+ export type { AdminExternalCardOverlayEnforcement, Agent, AgentCheckoutAction, AgentCheckoutActionKind, AgentExternalCard, AgentSetupResult, AgentStatus, AgentType, AgentWalletLink, ApprovedExternalCardExecution, AssignExternalCardInput, AssignedPolicy, AuthorizeCheckoutActionResult, BeginCheckoutCheckpointInput, BeginCommerceExecutionInput, BrowserActionClaimResult, BrowserActionDispatchObservationInput, BrowserActionFinalizeResult, BrowserCheckoutAction, BrowserCheckoutTargetResolver, BrowserCheckoutTargetSnapshot, BrowserCheckoutTargetVerificationCode, CancellationOutcome, CanonicalBrowserCheckoutTargetSnapshot, ChainType, CheckoutActionAuthorization, CheckoutCheckpoint, CheckoutCheckpointApproval, CheckoutCheckpointMethod, CheckoutCheckpointNextAction, CheckoutCheckpointStage, CheckoutOutcome, CheckoutPaymentReference, CommerceCheckoutMethod, CommerceContextRequirement, CommerceExecution, CommerceExecutionAdapterKind, CommerceExecutionArtifactKind, CommerceExecutionChannel, CommerceExecutionConstraint, CommerceExecutionConstraintCoverage, CommerceExecutionConstraintCoverageLevel, CommerceExecutionDeliveryMode, CommerceExecutionEvidence, CommerceExecutionProvider, CommerceExecutionResult, CommerceExecutionSourceKind, CommerceExecutionStatus, CommerceFactConfidence, CommerceFactProvenanceInput, CommerceFactSource, CommerceFulfillmentInput, CommerceLineItemInput, CommerceProposalFactKey, CommerceProposalMissingFact, CommerceProposalResolution, CommerceProposalState, CommerceSettlementRail, CommerceTotalInput, CommerceTotalType, ComparePolicyVersionsResponse, ConfirmCheckoutCheckpointInput, ConnectedCardBrief, ConnectedCardCredential, ConnectedCardDrawInput, ConnectedCardDrawMerchant, ConnectedCardDrawResult, ConnectedCardDrawable, ConnectedCardListResult, ConnectedCardMandate, ConnectedCardMandateMerchant, ConnectedCardOutcomeReport, ConnectedCardOutcomeReportResult, ConnectedCardReference, ContoAdminConfig, ContoConfig, ContoRequestOptions, ControlledExternalCardPaymentResult, CreateAgentInput, CreatePolicyInput, CreatePolicyRuleInput, CreatePurchaseInput, CreateSdkKeyInput, CreateSdkKeyResult, CreateWalletInput, CustodyType, CustomerCustodyMode, CustomerWalletControlModel, CustomerWalletKeyAccess, DelegationType, ExecuteCommerceExecutionResult, ExternalCard, ExternalCardApprovalInput, ExternalCardApprovalResult, ExternalCardConfirmationInput, ExternalCardConfirmationResult, ExternalCardExecutionClaimResult, ExternalCardExecutionResult, ExternalCardExecutor, ExternalCardOverlayEnforcement, ExternalCardRequestStatusResult, ExternalWalletApprovalInput, ExternalWalletApprovalResult, ExternalWalletConfirmationInput, ExternalWalletConfirmationResult, FreezeAgentInput, GetLinkCardInput, ContoConfig as GiroConfig, GuardCheckoutResult, GuardedBrowserDispatchResult, GuardedCheckoutInput, LinkApprovalCallback, LinkApprovalEvent, LinkCardResult, LinkWalletInput, ListAgentsParams, ListAgentsResponse, ListPoliciesParams, ListPoliciesResponse, ListPolicyVersionsResponse, ListWalletsParams, ListWalletsResponse, MppBudgetResult, MppPreAuthorizeInput, MppPreAuthorizeResult, MppRecordInput, MppRecordResult, NormalizeCommerceProposalInput, OrganizationMember, PaginatedResponse, PaginationParams, PauseExternalCardAssignmentResult, PaymentExecuteResult, PaymentRequestInput, PaymentRequestResult, PaymentStatusResult, Policy, PolicyRule, PolicyType, PolicyVersionChangeType, PolicyVersionDetail, PolicyVersionDiff, PolicyVersionFieldChange, PolicyVersionSnapshot, PolicyVersionSnapshotRule, PolicyVersionSummary, PrivilegedBrowserCheckoutDriver, ProtocolBudgetResult, ProtocolPreAuthorizeResult, ProtocolRecordGrant, ProtocolRecordGrantResult, ProtocolServiceSummary, ProtocolServicesResult, ProveWalletOwnershipInput, ProvisionWalletResult, Purchase, PurchaseAction, PurchaseActionAuthority, PurchaseActionKind, PurchaseAllowance, PurchaseBuyer, PurchaseBuyerCompanyCard, PurchaseBuyerCompanyCardLimits, PurchaseBuyerKitchenContext, PurchaseCompanyCard, PurchaseDelegation, PurchaseEvent, PurchaseEventPage, PurchaseEvidence, PurchaseGrocery, PurchaseGroceryItem, PurchaseGroceryList, PurchaseHostedLink, PurchaseItem, PurchaseKitchenContext, PurchaseKitchenInventoryItem, PurchaseKitchenListItem, PurchaseKitchenPreferences, PurchaseKitchenRecipe, PurchaseKitchenUsual, PurchaseList, PurchaseMoney, PurchasePaymentMethod, PurchasePhase, PurchaseRequest, PurchaseResponseAck, PurchaseStatus, PurchasingCapabilities, PurchasingSettings, RecurringPurchase, RecurringPurchaseCycleResult, RecurringPurchaseCycleStatus, RefreshBalanceResult, RegisterExternalCardInput, ReplaceLinkCardInput, ReportCheckoutOutcomeInput, RollbackPolicyVersionResponse, RuleAction, RuleOperator, RuleType, RunGuardedCheckoutResult, SdkKey, SdkKeyType, SetupCapabilityState, SetupCardDiagnostics, SetupDiagnosticAction, SetupDiagnosticIssue, SetupDiagnosticIssueCode, SetupDiagnostics, SetupWalletDiagnostics, TestCardCredential, TestCardDrawResult, TestCardDrawable, TestCardListResult, TestCardReceipt, TestCheckoutInput, TestCheckoutResult, TestMerchant, TestMerchantProduct, UnfreezeAgentInput, UpdateAgentInput, UpdateExternalCardAssignmentInput, UpdatePolicyInput, UpdatePolicyRuleInput, UpdateWalletInput, VerifiedBrowserCheckoutTarget, WaitForCheckoutApprovalOptions, Wallet, WalletChainBalanceSnapshot, WalletOwnershipChallenge, WalletOwnershipProof, WalletStatus, WalletType, WatchPurchaseOptions, X402Authorization, X402BudgetResult, X402PayInput, X402PayResult, X402PaymentRequirements, X402PreAuthorizeInput, X402PreAuthorizeResult, X402RecordInput, X402RecordResult };
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ var require_package = __commonJS({
47
47
  "package.json"(exports2, module2) {
48
48
  module2.exports = {
49
49
  name: "@conto_finance/sdk",
50
- version: "0.7.0",
50
+ version: "0.7.1",
51
51
  description: "Conto SDK for AI agent payment authorization",
52
52
  main: "./dist/index.js",
53
53
  module: "./dist/index.mjs",
@@ -197,6 +197,32 @@ var Sentry2 = __toESM(require("@sentry/node"));
197
197
  var import_node_crypto3 = require("crypto");
198
198
  var import_package_version2 = __toESM(require_package_version());
199
199
 
200
+ // src/link-approval.ts
201
+ function safeLinkApprovalUrl(value) {
202
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048) return void 0;
203
+ let url;
204
+ try {
205
+ url = new URL(value);
206
+ } catch {
207
+ return void 0;
208
+ }
209
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" || !(url.hostname === "link.com" || url.hostname.endsWith(".link.com"))) {
210
+ return void 0;
211
+ }
212
+ url.hash = "";
213
+ return url.toString();
214
+ }
215
+ function linkTime(value) {
216
+ let ms = Number.NaN;
217
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
218
+ ms = value < 1e12 ? value * 1e3 : value;
219
+ } else if (typeof value === "string" && value.length > 0 && value.length <= 64) {
220
+ ms = Date.parse(value);
221
+ }
222
+ const date = new Date(ms);
223
+ return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
224
+ }
225
+
200
226
  // src/purchases.ts
201
227
  var import_node_crypto = require("crypto");
202
228
  var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "blocked", "failed", "cancelled"]);
@@ -1684,6 +1710,8 @@ var LinkCardError = class extends ContoError {
1684
1710
  this.name = "LinkCardError";
1685
1711
  this.checkpoint = input.checkpoint;
1686
1712
  if (input.linkRequestId) this.linkRequestId = input.linkRequestId;
1713
+ const approvalUrl = safeLinkApprovalUrl(input.approvalUrl);
1714
+ if (approvalUrl) this.approvalUrl = approvalUrl;
1687
1715
  this.holdReleased = input.holdReleased;
1688
1716
  }
1689
1717
  };
@@ -2991,6 +3019,11 @@ async function closeLinkRequest(wallet, id) {
2991
3019
  return status !== void 0 && LINK_CLOSED_STATUSES.has(status);
2992
3020
  }
2993
3021
  }
3022
+ function assertLinkApprovalCallback(callback) {
3023
+ if (callback !== void 0 && typeof callback !== "function") {
3024
+ throw checkoutError("onLinkApproval must be a function");
3025
+ }
3026
+ }
2994
3027
  function linkCreatedNothing(error) {
2995
3028
  return errorCode(error) === "LINK_WALLET_UNAVAILABLE" || errorReason(error) === "invalid_input";
2996
3029
  }
@@ -3000,6 +3033,23 @@ var LinkFailureSignal = class extends Error {
3000
3033
  this.failure = failure;
3001
3034
  }
3002
3035
  };
3036
+ function linkApprovalDetails(request) {
3037
+ const approvalUrl = safeLinkApprovalUrl(request.approvalUrl);
3038
+ const approvalExpiresAt = linkTime(request.approvalExpiresAt);
3039
+ return {
3040
+ ...approvalUrl ? { approvalUrl } : {},
3041
+ ...approvalExpiresAt ? { approvalExpiresAt } : {}
3042
+ };
3043
+ }
3044
+ function notifyLinkApproval(callback, event) {
3045
+ try {
3046
+ const result = callback(Object.freeze(event));
3047
+ if (result !== null && typeof result === "object" && "then" in result) {
3048
+ Promise.resolve(result).catch(() => void 0);
3049
+ }
3050
+ } catch {
3051
+ }
3052
+ }
3003
3053
  var _client4, _CheckoutClient_instances, linkCardFor_fn;
3004
3054
  var CheckoutClient = class {
3005
3055
  constructor(client) {
@@ -3185,7 +3235,9 @@ var CheckoutClient = class {
3185
3235
  * 1. Conto runs checkpoint 1 and holds the spend. When a rule requires the
3186
3236
  * owner's approval, this waits for it.
3187
3237
  * 2. Only then does the wallet ask Link for a card for exactly the approved
3188
- * amount at this merchant. The person approves it in Link.
3238
+ * amount at this merchant. The person approves it in Link. Pass
3239
+ * onLinkApproval to get Link's approval page, so they can approve on the
3240
+ * web without the Link app.
3189
3241
  * 3. The card is returned. It never reaches Conto.
3190
3242
  *
3191
3243
  * Then fill in the card, call confirm(result.checkpoint, { amountMinor,
@@ -3201,10 +3253,18 @@ var CheckoutClient = class {
3201
3253
  * Link request.
3202
3254
  */
3203
3255
  async getLinkCard(input) {
3204
- const { wallet, ownerApprovalTimeoutMs, linkApprovalTimeoutMs, signal, ...beginInput } = input;
3256
+ const {
3257
+ wallet,
3258
+ ownerApprovalTimeoutMs,
3259
+ linkApprovalTimeoutMs,
3260
+ onLinkApproval,
3261
+ signal,
3262
+ ...beginInput
3263
+ } = input;
3205
3264
  if (!wallet || typeof wallet.createCardRequest !== "function") {
3206
3265
  throw checkoutError("wallet must be a LinkWallet, such as linkCliWallet()");
3207
3266
  }
3267
+ assertLinkApprovalCallback(onLinkApproval);
3208
3268
  let checkpoint = await this.begin({ ...beginInput, checkoutMethod: "LINK" });
3209
3269
  if (checkpoint.stage === "AWAITING_APPROVAL") {
3210
3270
  checkpoint = await this.waitForApproval(checkpoint, {
@@ -3212,7 +3272,11 @@ var CheckoutClient = class {
3212
3272
  ...signal ? { signal } : {}
3213
3273
  });
3214
3274
  }
3215
- return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, linkApprovalTimeoutMs, signal);
3275
+ return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, {
3276
+ linkApprovalTimeoutMs,
3277
+ signal,
3278
+ onLinkApproval
3279
+ });
3216
3280
  }
3217
3281
  /**
3218
3282
  * The final total differs from the one the Link card covers. This cancels
@@ -3221,10 +3285,18 @@ var CheckoutClient = class {
3221
3285
  * for it. Confirm the returned checkpoint with the same total.
3222
3286
  */
3223
3287
  async replaceLinkCard(previous, input) {
3224
- const { wallet, amountMinor, ownerApprovalTimeoutMs, linkApprovalTimeoutMs, signal } = input;
3288
+ const {
3289
+ wallet,
3290
+ amountMinor,
3291
+ ownerApprovalTimeoutMs,
3292
+ linkApprovalTimeoutMs,
3293
+ onLinkApproval,
3294
+ signal
3295
+ } = input;
3225
3296
  if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) {
3226
3297
  throw checkoutError("amountMinor must be a positive integer number of cents");
3227
3298
  }
3299
+ assertLinkApprovalCallback(onLinkApproval);
3228
3300
  if (amountMinor === previous.checkpoint.amountMinor) return previous;
3229
3301
  const old = previous.checkpoint;
3230
3302
  if (!await closeLinkRequest(wallet, previous.linkRequestId)) {
@@ -3263,12 +3335,17 @@ var CheckoutClient = class {
3263
3335
  ...signal ? { signal } : {}
3264
3336
  });
3265
3337
  }
3266
- return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, linkApprovalTimeoutMs, signal);
3338
+ return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, {
3339
+ linkApprovalTimeoutMs,
3340
+ signal,
3341
+ onLinkApproval
3342
+ });
3267
3343
  }
3268
3344
  };
3269
3345
  _client4 = new WeakMap();
3270
3346
  _CheckoutClient_instances = new WeakSet();
3271
- linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 * 6e4, signal) {
3347
+ linkCardFor_fn = async function(checkpoint, wallet, options) {
3348
+ const { linkApprovalTimeoutMs = 10 * 6e4, signal, onLinkApproval } = options;
3272
3349
  if (checkpoint.stage !== "APPROVED") {
3273
3350
  throw checkoutError(
3274
3351
  `Conto has not approved a card for this checkpoint (stage ${checkpoint.stage}). Do not pay.`,
@@ -3278,6 +3355,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3278
3355
  const deadline = Date.now() + linkApprovalTimeoutMs;
3279
3356
  const call = signal ? { signal } : {};
3280
3357
  let linkRequestId;
3358
+ let approvalUrl;
3281
3359
  let createStarted = false;
3282
3360
  try {
3283
3361
  const cardRequest = linkCardRequest(checkpoint);
@@ -3298,6 +3376,13 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3298
3376
  message: "Link returned a request for a different amount."
3299
3377
  });
3300
3378
  }
3379
+ if (LINK_WAITING_STATUSES.has(request.status)) {
3380
+ const details = linkApprovalDetails(request);
3381
+ approvalUrl = details.approvalUrl;
3382
+ if (onLinkApproval) {
3383
+ notifyLinkApproval(onLinkApproval, { checkpoint, linkRequestId: request.id, ...details });
3384
+ }
3385
+ }
3301
3386
  while (linkRequestWaiting(request)) {
3302
3387
  const remainingSeconds = Math.floor((deadline - Date.now()) / 1e3);
3303
3388
  if (remainingSeconds <= 0) {
@@ -3312,6 +3397,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3312
3397
  ...call,
3313
3398
  timeoutSeconds: remainingSeconds
3314
3399
  });
3400
+ approvalUrl = LINK_WAITING_STATUSES.has(request.status) ? linkApprovalDetails(request).approvalUrl ?? approvalUrl : void 0;
3315
3401
  if (linkRequestWaiting(request) && Date.now() - started < 1e3) {
3316
3402
  await sleep2(1e3, signal);
3317
3403
  }
@@ -3343,6 +3429,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3343
3429
  code: "LINK_CARD_NOT_CANCELLED",
3344
3430
  checkpoint,
3345
3431
  linkRequestId,
3432
+ ...approvalUrl ? { approvalUrl } : {},
3346
3433
  holdReleased: false,
3347
3434
  retryGuidance: "Retry with the same input to resume this Link request, or cancel it with wallet.cancel(error.linkRequestId) and then call checkout.cancel(error.checkpoint.id)."
3348
3435
  });
@@ -3356,6 +3443,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3356
3443
  code: failure.code,
3357
3444
  checkpoint,
3358
3445
  ...linkRequestId ? { linkRequestId } : {},
3446
+ ...linkRequestId && approvalUrl ? { approvalUrl } : {},
3359
3447
  holdReleased
3360
3448
  });
3361
3449
  }
package/dist/index.mjs CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ linkTime,
3
+ safeLinkApprovalUrl
4
+ } from "./chunk-PVMUL3JC.mjs";
1
5
  import {
2
6
  BrowserCheckoutTargetVerificationError,
3
7
  canonicalizeBrowserCheckoutTarget,
@@ -18,7 +22,7 @@ var require_package = __commonJS({
18
22
  "package.json"(exports, module) {
19
23
  module.exports = {
20
24
  name: "@conto_finance/sdk",
21
- version: "0.7.0",
25
+ version: "0.7.1",
22
26
  description: "Conto SDK for AI agent payment authorization",
23
27
  main: "./dist/index.js",
24
28
  module: "./dist/index.mjs",
@@ -1499,6 +1503,8 @@ var LinkCardError = class extends ContoError {
1499
1503
  this.name = "LinkCardError";
1500
1504
  this.checkpoint = input.checkpoint;
1501
1505
  if (input.linkRequestId) this.linkRequestId = input.linkRequestId;
1506
+ const approvalUrl = safeLinkApprovalUrl(input.approvalUrl);
1507
+ if (approvalUrl) this.approvalUrl = approvalUrl;
1502
1508
  this.holdReleased = input.holdReleased;
1503
1509
  }
1504
1510
  };
@@ -2806,6 +2812,11 @@ async function closeLinkRequest(wallet, id) {
2806
2812
  return status !== void 0 && LINK_CLOSED_STATUSES.has(status);
2807
2813
  }
2808
2814
  }
2815
+ function assertLinkApprovalCallback(callback) {
2816
+ if (callback !== void 0 && typeof callback !== "function") {
2817
+ throw checkoutError("onLinkApproval must be a function");
2818
+ }
2819
+ }
2809
2820
  function linkCreatedNothing(error) {
2810
2821
  return errorCode(error) === "LINK_WALLET_UNAVAILABLE" || errorReason(error) === "invalid_input";
2811
2822
  }
@@ -2815,6 +2826,23 @@ var LinkFailureSignal = class extends Error {
2815
2826
  this.failure = failure;
2816
2827
  }
2817
2828
  };
2829
+ function linkApprovalDetails(request) {
2830
+ const approvalUrl = safeLinkApprovalUrl(request.approvalUrl);
2831
+ const approvalExpiresAt = linkTime(request.approvalExpiresAt);
2832
+ return {
2833
+ ...approvalUrl ? { approvalUrl } : {},
2834
+ ...approvalExpiresAt ? { approvalExpiresAt } : {}
2835
+ };
2836
+ }
2837
+ function notifyLinkApproval(callback, event) {
2838
+ try {
2839
+ const result = callback(Object.freeze(event));
2840
+ if (result !== null && typeof result === "object" && "then" in result) {
2841
+ Promise.resolve(result).catch(() => void 0);
2842
+ }
2843
+ } catch {
2844
+ }
2845
+ }
2818
2846
  var _client4, _CheckoutClient_instances, linkCardFor_fn;
2819
2847
  var CheckoutClient = class {
2820
2848
  constructor(client) {
@@ -3000,7 +3028,9 @@ var CheckoutClient = class {
3000
3028
  * 1. Conto runs checkpoint 1 and holds the spend. When a rule requires the
3001
3029
  * owner's approval, this waits for it.
3002
3030
  * 2. Only then does the wallet ask Link for a card for exactly the approved
3003
- * amount at this merchant. The person approves it in Link.
3031
+ * amount at this merchant. The person approves it in Link. Pass
3032
+ * onLinkApproval to get Link's approval page, so they can approve on the
3033
+ * web without the Link app.
3004
3034
  * 3. The card is returned. It never reaches Conto.
3005
3035
  *
3006
3036
  * Then fill in the card, call confirm(result.checkpoint, { amountMinor,
@@ -3016,10 +3046,18 @@ var CheckoutClient = class {
3016
3046
  * Link request.
3017
3047
  */
3018
3048
  async getLinkCard(input) {
3019
- const { wallet, ownerApprovalTimeoutMs, linkApprovalTimeoutMs, signal, ...beginInput } = input;
3049
+ const {
3050
+ wallet,
3051
+ ownerApprovalTimeoutMs,
3052
+ linkApprovalTimeoutMs,
3053
+ onLinkApproval,
3054
+ signal,
3055
+ ...beginInput
3056
+ } = input;
3020
3057
  if (!wallet || typeof wallet.createCardRequest !== "function") {
3021
3058
  throw checkoutError("wallet must be a LinkWallet, such as linkCliWallet()");
3022
3059
  }
3060
+ assertLinkApprovalCallback(onLinkApproval);
3023
3061
  let checkpoint = await this.begin({ ...beginInput, checkoutMethod: "LINK" });
3024
3062
  if (checkpoint.stage === "AWAITING_APPROVAL") {
3025
3063
  checkpoint = await this.waitForApproval(checkpoint, {
@@ -3027,7 +3065,11 @@ var CheckoutClient = class {
3027
3065
  ...signal ? { signal } : {}
3028
3066
  });
3029
3067
  }
3030
- return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, linkApprovalTimeoutMs, signal);
3068
+ return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, {
3069
+ linkApprovalTimeoutMs,
3070
+ signal,
3071
+ onLinkApproval
3072
+ });
3031
3073
  }
3032
3074
  /**
3033
3075
  * The final total differs from the one the Link card covers. This cancels
@@ -3036,10 +3078,18 @@ var CheckoutClient = class {
3036
3078
  * for it. Confirm the returned checkpoint with the same total.
3037
3079
  */
3038
3080
  async replaceLinkCard(previous, input) {
3039
- const { wallet, amountMinor, ownerApprovalTimeoutMs, linkApprovalTimeoutMs, signal } = input;
3081
+ const {
3082
+ wallet,
3083
+ amountMinor,
3084
+ ownerApprovalTimeoutMs,
3085
+ linkApprovalTimeoutMs,
3086
+ onLinkApproval,
3087
+ signal
3088
+ } = input;
3040
3089
  if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) {
3041
3090
  throw checkoutError("amountMinor must be a positive integer number of cents");
3042
3091
  }
3092
+ assertLinkApprovalCallback(onLinkApproval);
3043
3093
  if (amountMinor === previous.checkpoint.amountMinor) return previous;
3044
3094
  const old = previous.checkpoint;
3045
3095
  if (!await closeLinkRequest(wallet, previous.linkRequestId)) {
@@ -3078,12 +3128,17 @@ var CheckoutClient = class {
3078
3128
  ...signal ? { signal } : {}
3079
3129
  });
3080
3130
  }
3081
- return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, linkApprovalTimeoutMs, signal);
3131
+ return __privateMethod(this, _CheckoutClient_instances, linkCardFor_fn).call(this, checkpoint, wallet, {
3132
+ linkApprovalTimeoutMs,
3133
+ signal,
3134
+ onLinkApproval
3135
+ });
3082
3136
  }
3083
3137
  };
3084
3138
  _client4 = new WeakMap();
3085
3139
  _CheckoutClient_instances = new WeakSet();
3086
- linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 * 6e4, signal) {
3140
+ linkCardFor_fn = async function(checkpoint, wallet, options) {
3141
+ const { linkApprovalTimeoutMs = 10 * 6e4, signal, onLinkApproval } = options;
3087
3142
  if (checkpoint.stage !== "APPROVED") {
3088
3143
  throw checkoutError(
3089
3144
  `Conto has not approved a card for this checkpoint (stage ${checkpoint.stage}). Do not pay.`,
@@ -3093,6 +3148,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3093
3148
  const deadline = Date.now() + linkApprovalTimeoutMs;
3094
3149
  const call = signal ? { signal } : {};
3095
3150
  let linkRequestId;
3151
+ let approvalUrl;
3096
3152
  let createStarted = false;
3097
3153
  try {
3098
3154
  const cardRequest = linkCardRequest(checkpoint);
@@ -3113,6 +3169,13 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3113
3169
  message: "Link returned a request for a different amount."
3114
3170
  });
3115
3171
  }
3172
+ if (LINK_WAITING_STATUSES.has(request.status)) {
3173
+ const details = linkApprovalDetails(request);
3174
+ approvalUrl = details.approvalUrl;
3175
+ if (onLinkApproval) {
3176
+ notifyLinkApproval(onLinkApproval, { checkpoint, linkRequestId: request.id, ...details });
3177
+ }
3178
+ }
3116
3179
  while (linkRequestWaiting(request)) {
3117
3180
  const remainingSeconds = Math.floor((deadline - Date.now()) / 1e3);
3118
3181
  if (remainingSeconds <= 0) {
@@ -3127,6 +3190,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3127
3190
  ...call,
3128
3191
  timeoutSeconds: remainingSeconds
3129
3192
  });
3193
+ approvalUrl = LINK_WAITING_STATUSES.has(request.status) ? linkApprovalDetails(request).approvalUrl ?? approvalUrl : void 0;
3130
3194
  if (linkRequestWaiting(request) && Date.now() - started < 1e3) {
3131
3195
  await sleep2(1e3, signal);
3132
3196
  }
@@ -3158,6 +3222,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3158
3222
  code: "LINK_CARD_NOT_CANCELLED",
3159
3223
  checkpoint,
3160
3224
  linkRequestId,
3225
+ ...approvalUrl ? { approvalUrl } : {},
3161
3226
  holdReleased: false,
3162
3227
  retryGuidance: "Retry with the same input to resume this Link request, or cancel it with wallet.cancel(error.linkRequestId) and then call checkout.cancel(error.checkpoint.id)."
3163
3228
  });
@@ -3171,6 +3236,7 @@ linkCardFor_fn = async function(checkpoint, wallet, linkApprovalTimeoutMs = 10 *
3171
3236
  code: failure.code,
3172
3237
  checkpoint,
3173
3238
  ...linkRequestId ? { linkRequestId } : {},
3239
+ ...linkRequestId && approvalUrl ? { approvalUrl } : {},
3174
3240
  holdReleased
3175
3241
  });
3176
3242
  }
@@ -38,8 +38,13 @@ interface LinkSpendRequest {
38
38
  amountMinor?: number;
39
39
  merchantName?: string;
40
40
  merchantUrl?: string;
41
- /** Link page where the person can review the request. */
41
+ /**
42
+ * The Stripe Link page where the person can approve the request on the web
43
+ * while signed in to Link. Only an HTTPS link.com address is kept.
44
+ */
42
45
  approvalUrl?: string;
46
+ /** When Link stops accepting approval for the request, as an ISO 8601 time. */
47
+ approvalExpiresAt?: string;
43
48
  /** Set with requires_action. `auto_resume` means Link continues once the person finishes the step. */
44
49
  actionResolution?: string;
45
50
  /** Set with requires_action when Link gives the person a page to finish the step. */
@@ -38,8 +38,13 @@ interface LinkSpendRequest {
38
38
  amountMinor?: number;
39
39
  merchantName?: string;
40
40
  merchantUrl?: string;
41
- /** Link page where the person can review the request. */
41
+ /**
42
+ * The Stripe Link page where the person can approve the request on the web
43
+ * while signed in to Link. Only an HTTPS link.com address is kept.
44
+ */
42
45
  approvalUrl?: string;
46
+ /** When Link stops accepting approval for the request, as an ISO 8601 time. */
47
+ approvalExpiresAt?: string;
43
48
  /** Set with requires_action. `auto_resume` means Link continues once the person finishes the step. */
44
49
  actionResolution?: string;
45
50
  /** Set with requires_action when Link gives the person a page to finish the step. */
@@ -40,6 +40,34 @@ var import_node_fs = require("fs");
40
40
  var import_promises = require("fs/promises");
41
41
  var import_node_os = require("os");
42
42
  var import_node_path = __toESM(require("path"));
43
+
44
+ // src/link-approval.ts
45
+ function safeLinkApprovalUrl(value) {
46
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048) return void 0;
47
+ let url;
48
+ try {
49
+ url = new URL(value);
50
+ } catch {
51
+ return void 0;
52
+ }
53
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" || !(url.hostname === "link.com" || url.hostname.endsWith(".link.com"))) {
54
+ return void 0;
55
+ }
56
+ url.hash = "";
57
+ return url.toString();
58
+ }
59
+ function linkTime(value) {
60
+ let ms = Number.NaN;
61
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
62
+ ms = value < 1e12 ? value * 1e3 : value;
63
+ } else if (typeof value === "string" && value.length > 0 && value.length <= 64) {
64
+ ms = Date.parse(value);
65
+ }
66
+ const date = new Date(ms);
67
+ return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
68
+ }
69
+
70
+ // src/link-wallet.ts
43
71
  var LinkWalletError = class extends Error {
44
72
  constructor(reason, message, linkCode) {
45
73
  super(message);
@@ -102,13 +130,16 @@ function parseSpendRequest(value) {
102
130
  const status = typeof data.status === "string" && KNOWN_STATUSES.has(data.status) ? data.status : "unknown";
103
131
  const nextAction = record(record(record(data.status_details).requires_action).next_action);
104
132
  const amount = data.amount;
133
+ const approvalUrl = safeLinkApprovalUrl(data.approval_url);
134
+ const approvalExpiresAt = linkTime(data.approval_window_expires_at);
105
135
  return {
106
136
  id,
107
137
  status,
108
138
  ...Number.isSafeInteger(amount) ? { amountMinor: amount } : {},
109
139
  ...text(data.merchant_name) ? { merchantName: text(data.merchant_name) } : {},
110
140
  ...httpsUrl(data.merchant_url) ? { merchantUrl: httpsUrl(data.merchant_url) } : {},
111
- ...httpsUrl(data.approval_url) ? { approvalUrl: httpsUrl(data.approval_url) } : {},
141
+ ...approvalUrl ? { approvalUrl } : {},
142
+ ...approvalExpiresAt ? { approvalExpiresAt } : {},
112
143
  ...text(nextAction.resolution) ? { actionResolution: text(nextAction.resolution) } : {},
113
144
  ...httpsUrl(nextAction.action_url) ? { actionUrl: httpsUrl(nextAction.action_url) } : {}
114
145
  };
@@ -116,15 +147,6 @@ function parseSpendRequest(value) {
116
147
  function invalidCard() {
117
148
  return new LinkWalletError("invalid_response", "Link returned card data this SDK cannot read.");
118
149
  }
119
- function validUntil(value) {
120
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
121
- return new Date(value < 1e12 ? value * 1e3 : value).toISOString();
122
- }
123
- if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
124
- return new Date(value).toISOString();
125
- }
126
- return void 0;
127
- }
128
150
  function parseCard(value) {
129
151
  const card = record(value);
130
152
  const number = typeof card.number === "string" ? card.number.replace(/[\s-]/g, "") : "";
@@ -136,7 +158,7 @@ function parseCard(value) {
136
158
  if (!/^\d{12,19}$/.test(number) || !/^\d{3,4}$/.test(securityValue) || !Number.isInteger(expMonth) || expMonth < 1 || expMonth > 12 || !Number.isInteger(expYear) || expYear < 2e3) {
137
159
  throw invalidCard();
138
160
  }
139
- const until = validUntil(card.valid_until);
161
+ const until = linkTime(card.valid_until);
140
162
  const brand = text(card.brand);
141
163
  return {
142
164
  number,
@@ -1,3 +1,7 @@
1
+ import {
2
+ linkTime,
3
+ safeLinkApprovalUrl
4
+ } from "./chunk-PVMUL3JC.mjs";
1
5
  import "./chunk-TGOYFBKZ.mjs";
2
6
 
3
7
  // src/link-wallet.ts
@@ -68,13 +72,16 @@ function parseSpendRequest(value) {
68
72
  const status = typeof data.status === "string" && KNOWN_STATUSES.has(data.status) ? data.status : "unknown";
69
73
  const nextAction = record(record(record(data.status_details).requires_action).next_action);
70
74
  const amount = data.amount;
75
+ const approvalUrl = safeLinkApprovalUrl(data.approval_url);
76
+ const approvalExpiresAt = linkTime(data.approval_window_expires_at);
71
77
  return {
72
78
  id,
73
79
  status,
74
80
  ...Number.isSafeInteger(amount) ? { amountMinor: amount } : {},
75
81
  ...text(data.merchant_name) ? { merchantName: text(data.merchant_name) } : {},
76
82
  ...httpsUrl(data.merchant_url) ? { merchantUrl: httpsUrl(data.merchant_url) } : {},
77
- ...httpsUrl(data.approval_url) ? { approvalUrl: httpsUrl(data.approval_url) } : {},
83
+ ...approvalUrl ? { approvalUrl } : {},
84
+ ...approvalExpiresAt ? { approvalExpiresAt } : {},
78
85
  ...text(nextAction.resolution) ? { actionResolution: text(nextAction.resolution) } : {},
79
86
  ...httpsUrl(nextAction.action_url) ? { actionUrl: httpsUrl(nextAction.action_url) } : {}
80
87
  };
@@ -82,15 +89,6 @@ function parseSpendRequest(value) {
82
89
  function invalidCard() {
83
90
  return new LinkWalletError("invalid_response", "Link returned card data this SDK cannot read.");
84
91
  }
85
- function validUntil(value) {
86
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
87
- return new Date(value < 1e12 ? value * 1e3 : value).toISOString();
88
- }
89
- if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
90
- return new Date(value).toISOString();
91
- }
92
- return void 0;
93
- }
94
92
  function parseCard(value) {
95
93
  const card = record(value);
96
94
  const number = typeof card.number === "string" ? card.number.replace(/[\s-]/g, "") : "";
@@ -102,7 +100,7 @@ function parseCard(value) {
102
100
  if (!/^\d{12,19}$/.test(number) || !/^\d{3,4}$/.test(securityValue) || !Number.isInteger(expMonth) || expMonth < 1 || expMonth > 12 || !Number.isInteger(expYear) || expYear < 2e3) {
103
101
  throw invalidCard();
104
102
  }
105
- const until = validUntil(card.valid_until);
103
+ const until = linkTime(card.valid_until);
106
104
  const brand = text(card.brand);
107
105
  return {
108
106
  number,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conto_finance/sdk",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Conto SDK for AI agent payment authorization",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",