@conto_finance/sdk 0.6.0-beta.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -228
- package/dist/index.d.mts +178 -166
- package/dist/index.d.ts +178 -166
- package/dist/index.js +265 -226
- package/dist/index.mjs +259 -224
- package/package.json +3 -9
- package/dist/link-testing.d.mts +0 -37
- package/dist/link-testing.d.ts +0 -37
- package/dist/link-testing.js +0 -264
- package/dist/link-testing.mjs +0 -244
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ var require_package = __commonJS({
|
|
|
46
46
|
"package.json"(exports2, module2) {
|
|
47
47
|
module2.exports = {
|
|
48
48
|
name: "@conto_finance/sdk",
|
|
49
|
-
version: "0.6.0
|
|
49
|
+
version: "0.6.0",
|
|
50
50
|
description: "Conto SDK for AI agent payment authorization",
|
|
51
51
|
main: "./dist/index.js",
|
|
52
52
|
module: "./dist/index.mjs",
|
|
@@ -71,16 +71,10 @@ var require_package = __commonJS({
|
|
|
71
71
|
types: "./dist/checkout-verifier.d.ts",
|
|
72
72
|
import: "./dist/checkout-verifier.mjs",
|
|
73
73
|
require: "./dist/checkout-verifier.js"
|
|
74
|
-
},
|
|
75
|
-
"./link-testing": {
|
|
76
|
-
types: "./dist/link-testing.d.ts",
|
|
77
|
-
import: "./dist/link-testing.mjs",
|
|
78
|
-
require: "./dist/link-testing.js"
|
|
79
74
|
}
|
|
80
75
|
},
|
|
81
76
|
publishConfig: {
|
|
82
|
-
access: "public"
|
|
83
|
-
tag: "beta"
|
|
77
|
+
access: "public"
|
|
84
78
|
},
|
|
85
79
|
engines: {
|
|
86
80
|
node: ">=20.19.0"
|
|
@@ -93,7 +87,7 @@ var require_package = __commonJS({
|
|
|
93
87
|
dev: "tsup --watch",
|
|
94
88
|
lint: "tsc --noEmit",
|
|
95
89
|
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
96
|
-
"test:contract": "npm run build && node --test test/contract.test.mjs test/playwright.test.mjs test/browserbase.test.mjs test/checkout-verifier.test.mjs test/developer-experience.test.mjs test/
|
|
90
|
+
"test:contract": "npm run build && node --test test/contract.test.mjs test/playwright.test.mjs test/browserbase.test.mjs test/checkout-verifier.test.mjs test/developer-experience.test.mjs test/checkout.test.mjs",
|
|
97
91
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
98
92
|
"verify:release": "node scripts/verify-release.mjs",
|
|
99
93
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -159,6 +153,12 @@ __export(index_exports, {
|
|
|
159
153
|
BrowserActionFinalizationPendingError: () => BrowserActionFinalizationPendingError,
|
|
160
154
|
BrowserCheckoutTargetVerificationError: () => BrowserCheckoutTargetVerificationError,
|
|
161
155
|
CardsClient: () => CardsClient,
|
|
156
|
+
CheckoutApprovalDeniedError: () => CheckoutApprovalDeniedError,
|
|
157
|
+
CheckoutApprovalExpiredError: () => CheckoutApprovalExpiredError,
|
|
158
|
+
CheckoutApprovalPendingError: () => CheckoutApprovalPendingError,
|
|
159
|
+
CheckoutApprovalTimeoutError: () => CheckoutApprovalTimeoutError,
|
|
160
|
+
CheckoutCheckpointClosedError: () => CheckoutCheckpointClosedError,
|
|
161
|
+
CheckoutClient: () => CheckoutClient,
|
|
162
162
|
ConnectedCardsClient: () => ConnectedCardsClient,
|
|
163
163
|
Conto: () => Conto,
|
|
164
164
|
ContoAdmin: () => ContoAdmin,
|
|
@@ -167,8 +167,6 @@ __export(index_exports, {
|
|
|
167
167
|
ControlledExternalCardPaymentTool: () => ControlledExternalCardPaymentTool,
|
|
168
168
|
ExternalCardConfirmationPendingError: () => ExternalCardConfirmationPendingError,
|
|
169
169
|
Giro: () => Conto,
|
|
170
|
-
LinkClient: () => LinkClient,
|
|
171
|
-
LinkPaymentExecutionError: () => LinkPaymentExecutionError,
|
|
172
170
|
TestCardsClient: () => TestCardsClient,
|
|
173
171
|
canonicalizeBrowserCheckoutTarget: () => canonicalizeBrowserCheckoutTarget,
|
|
174
172
|
createVerifiedBrowserCheckoutDriver: () => createVerifiedBrowserCheckoutDriver,
|
|
@@ -554,7 +552,6 @@ var ContoAdmin = class {
|
|
|
554
552
|
this.cards = new AdminCardsClient(this);
|
|
555
553
|
this.policies = new PoliciesClient(this);
|
|
556
554
|
this.team = new TeamClient(this);
|
|
557
|
-
this.paymentRails = new PaymentRailsClient(this);
|
|
558
555
|
}
|
|
559
556
|
/** Make an authenticated request to the Conto API */
|
|
560
557
|
async request(method, path, body) {
|
|
@@ -610,22 +607,6 @@ var ContoAdmin = class {
|
|
|
610
607
|
}
|
|
611
608
|
};
|
|
612
609
|
_orgApiKey = new WeakMap();
|
|
613
|
-
var PaymentRailsClient = class {
|
|
614
|
-
constructor(client) {
|
|
615
|
-
this.client = client;
|
|
616
|
-
}
|
|
617
|
-
/** Read gate and configuration readiness without calling Stripe. */
|
|
618
|
-
async getStripeLinkCheckoutReadiness() {
|
|
619
|
-
return this.client.request("GET", "/api/stripe-link/checkout-readiness");
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Ask Conto to verify that its configured Stripe credential resolves to its
|
|
623
|
-
* configured merchant account. This call never returns either identifier.
|
|
624
|
-
*/
|
|
625
|
-
async verifyStripeLinkCheckoutAccount() {
|
|
626
|
-
return this.client.request("POST", "/api/stripe-link/checkout-readiness");
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
610
|
var AdminCardsClient = class {
|
|
630
611
|
constructor(client) {
|
|
631
612
|
this.client = client;
|
|
@@ -696,8 +677,11 @@ var AdminCardsClient = class {
|
|
|
696
677
|
return this.client.request("GET", `/api/external-cards/requests${suffix ? `?${suffix}` : ""}`);
|
|
697
678
|
}
|
|
698
679
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
680
|
+
* @deprecated Resolution requires a signed-in user. SDK admin keys and
|
|
681
|
+
* organization API keys receive HTTP 403 INSUFFICIENT_PERMISSIONS from this
|
|
682
|
+
* endpoint. Have a signed-in organization owner or admin review the processor
|
|
683
|
+
* evidence and resolve the request through Conto's authenticated interface.
|
|
684
|
+
* Retained for source compatibility; this method does not bypass that restriction.
|
|
701
685
|
*/
|
|
702
686
|
async resolveRequest(requestId, input) {
|
|
703
687
|
return this.client.request(
|
|
@@ -1077,27 +1061,6 @@ var ContoError = class extends Error {
|
|
|
1077
1061
|
}
|
|
1078
1062
|
}
|
|
1079
1063
|
};
|
|
1080
|
-
var LinkPaymentExecutionError = class extends ContoError {
|
|
1081
|
-
constructor(executionId, error) {
|
|
1082
|
-
const original = error instanceof ContoError ? error : void 0;
|
|
1083
|
-
super(
|
|
1084
|
-
original?.message ?? "Link execution could not be confirmed",
|
|
1085
|
-
{
|
|
1086
|
-
code: original?.code ?? "LINK_EXECUTION_UNCONFIRMED",
|
|
1087
|
-
status: original?.status ?? 0,
|
|
1088
|
-
requestId: original?.requestId,
|
|
1089
|
-
retryAfter: original?.retryAfter,
|
|
1090
|
-
fieldErrors: original?.fieldErrors,
|
|
1091
|
-
reasons: original?.reasons,
|
|
1092
|
-
retryGuidance: "Call link.status(executionId) to reconcile. Do not create another purchase or retry execution blindly."
|
|
1093
|
-
},
|
|
1094
|
-
original?.diagnostics
|
|
1095
|
-
);
|
|
1096
|
-
this.nextAction = "RECONCILE";
|
|
1097
|
-
this.name = "LinkPaymentExecutionError";
|
|
1098
|
-
this.executionId = executionId;
|
|
1099
|
-
}
|
|
1100
|
-
};
|
|
1101
1064
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
1102
1065
|
constructor(requestId, confirmationInput, receipt) {
|
|
1103
1066
|
super("External card execution completed but confirmation is pending", {
|
|
@@ -1125,6 +1088,52 @@ var BrowserActionFinalizationPendingError = class extends ContoError {
|
|
|
1125
1088
|
this.observation = input.observation;
|
|
1126
1089
|
}
|
|
1127
1090
|
};
|
|
1091
|
+
var CheckoutCheckpointClosedError = class extends ContoError {
|
|
1092
|
+
constructor(checkpoint, message = `The checkpoint is ${checkpoint.stage.toLowerCase().replace(/_/g, " ")}. Stop this checkout.`, code = "CHECKOUT_CHECKPOINT_CLOSED") {
|
|
1093
|
+
super(message, { code, status: 0 });
|
|
1094
|
+
this.name = "CheckoutCheckpointClosedError";
|
|
1095
|
+
this.checkpoint = checkpoint;
|
|
1096
|
+
this.stage = checkpoint.stage;
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
var CheckoutApprovalDeniedError = class extends CheckoutCheckpointClosedError {
|
|
1100
|
+
constructor(checkpoint) {
|
|
1101
|
+
super(checkpoint, "The owner rejected this purchase. Do not pay.", "CHECKOUT_APPROVAL_DENIED");
|
|
1102
|
+
this.name = "CheckoutApprovalDeniedError";
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
var CheckoutApprovalExpiredError = class extends CheckoutCheckpointClosedError {
|
|
1106
|
+
constructor(checkpoint) {
|
|
1107
|
+
super(
|
|
1108
|
+
checkpoint,
|
|
1109
|
+
"The owner did not approve this purchase in time. Do not pay.",
|
|
1110
|
+
"CHECKOUT_APPROVAL_EXPIRED"
|
|
1111
|
+
);
|
|
1112
|
+
this.name = "CheckoutApprovalExpiredError";
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
var CheckoutApprovalPendingError = class extends ContoError {
|
|
1116
|
+
constructor(checkpoint) {
|
|
1117
|
+
super("The owner has not approved this purchase yet. Do not pay.", {
|
|
1118
|
+
code: "CHECKOUT_APPROVAL_PENDING",
|
|
1119
|
+
status: 0,
|
|
1120
|
+
retryGuidance: "Call checkout.waitForApproval(error.checkpoint), then confirm the approved checkpoint."
|
|
1121
|
+
});
|
|
1122
|
+
this.name = "CheckoutApprovalPendingError";
|
|
1123
|
+
this.checkpoint = checkpoint;
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
var CheckoutApprovalTimeoutError = class extends ContoError {
|
|
1127
|
+
constructor(checkpoint) {
|
|
1128
|
+
super("The owner has not decided yet. Do not pay.", {
|
|
1129
|
+
code: "CHECKOUT_APPROVAL_TIMEOUT",
|
|
1130
|
+
status: 0,
|
|
1131
|
+
retryGuidance: "Wait again with checkout.waitForApproval, or call checkout.cancel to release the held spend."
|
|
1132
|
+
});
|
|
1133
|
+
this.name = "CheckoutApprovalTimeoutError";
|
|
1134
|
+
this.checkpoint = checkpoint;
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1128
1137
|
function generatedIdempotencyKey(prefix) {
|
|
1129
1138
|
return `${prefix}_${(0, import_node_crypto2.randomUUID)()}`;
|
|
1130
1139
|
}
|
|
@@ -1187,7 +1196,7 @@ var Conto = class {
|
|
|
1187
1196
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
1188
1197
|
assertSecureBaseUrl(this.baseUrl);
|
|
1189
1198
|
this.timeout = config.timeout || 3e4;
|
|
1190
|
-
this.
|
|
1199
|
+
this.checkout = new CheckoutClient(this);
|
|
1191
1200
|
this.payments = new PaymentsClient(this);
|
|
1192
1201
|
this.cards = new CardsClient(this);
|
|
1193
1202
|
this.connectedCards = new ConnectedCardsClient(this);
|
|
@@ -1195,31 +1204,6 @@ var Conto = class {
|
|
|
1195
1204
|
this.x402 = new X402Client(this);
|
|
1196
1205
|
this.mpp = new MppClient(this);
|
|
1197
1206
|
}
|
|
1198
|
-
/**
|
|
1199
|
-
* List masked Link payment methods for a session-created connection that is
|
|
1200
|
-
* explicitly bound to this agent. No Link token or payment artifact is
|
|
1201
|
-
* returned through the agent SDK.
|
|
1202
|
-
*/
|
|
1203
|
-
async listStripeLinkPaymentMethods(connectionId, options) {
|
|
1204
|
-
return this.request(
|
|
1205
|
-
"GET",
|
|
1206
|
-
withQuery("/api/sdk/stripe-link/payment-methods", {
|
|
1207
|
-
connectionId,
|
|
1208
|
-
...options?.mode ? { mode: options.mode } : {}
|
|
1209
|
-
})
|
|
1210
|
-
);
|
|
1211
|
-
}
|
|
1212
|
-
/** Read live Link configuration diagnostics without retrieving credentials or authorizing payment. */
|
|
1213
|
-
async getLinkSetup(input = {}) {
|
|
1214
|
-
return this.request(
|
|
1215
|
-
"GET",
|
|
1216
|
-
withQuery("/api/sdk/stripe-link/setup", {
|
|
1217
|
-
connectionId: input.connectionId,
|
|
1218
|
-
paymentMethodId: input.paymentMethodId,
|
|
1219
|
-
merchantId: input.merchantId
|
|
1220
|
-
})
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
1207
|
/**
|
|
1224
1208
|
* Normalize and durably revise a partial agent purchase proposal without
|
|
1225
1209
|
* creating a payment side effect. Missing facts return as NEEDS_CONTEXT
|
|
@@ -1300,6 +1284,22 @@ var Conto = class {
|
|
|
1300
1284
|
rethrowAmbiguousCommerceMutation(error, executionId, "authorize");
|
|
1301
1285
|
}
|
|
1302
1286
|
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Report whether an authorized guarded browser checkout was paid. PAID keeps
|
|
1289
|
+
* the held spend counted, NOT_PAID releases it, and UNKNOWN keeps it held for
|
|
1290
|
+
* the owner to review. Safe to repeat with the same outcome.
|
|
1291
|
+
*/
|
|
1292
|
+
async reportCommerceExecutionOutcome(executionId, input) {
|
|
1293
|
+
return this.request(
|
|
1294
|
+
"POST",
|
|
1295
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/outcome`,
|
|
1296
|
+
{
|
|
1297
|
+
outcome: input.outcome,
|
|
1298
|
+
...input.reference ? { reference: input.reference } : {}
|
|
1299
|
+
},
|
|
1300
|
+
{ idempotent: true }
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
1303
|
/** Claim a durable browser action for one stable privileged-runtime attempt. */
|
|
1304
1304
|
async claimBrowserAction(authorizationId, dispatchAttemptId, browserSessionId, action) {
|
|
1305
1305
|
return this.request(
|
|
@@ -2147,181 +2147,222 @@ var MppClient = class {
|
|
|
2147
2147
|
}
|
|
2148
2148
|
};
|
|
2149
2149
|
var index_default = Conto;
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2150
|
+
function checkoutError(message, code = "INVALID_CHECKOUT_CHECKPOINT") {
|
|
2151
|
+
return new ContoError(message, { code, status: 0 });
|
|
2152
|
+
}
|
|
2153
|
+
function checkpointCart(amountMinor, lineItems, totals) {
|
|
2154
|
+
if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) {
|
|
2155
|
+
throw checkoutError("amountMinor must be a positive integer number of cents");
|
|
2156
|
+
}
|
|
2157
|
+
if (!Array.isArray(lineItems) || lineItems.length === 0) {
|
|
2158
|
+
throw checkoutError("Supply at least one line item");
|
|
2159
|
+
}
|
|
2160
|
+
return {
|
|
2161
|
+
lineItems,
|
|
2162
|
+
totals: totals ?? [{ type: "total", label: "Total", amountMinor }]
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
var CLOSED_CHECKPOINT_STAGES = [
|
|
2166
|
+
"DENIED",
|
|
2167
|
+
"CANCELLED",
|
|
2168
|
+
"EXPIRED",
|
|
2169
|
+
"NOT_PAID"
|
|
2170
|
+
];
|
|
2171
|
+
function closedCheckpointError(checkpoint) {
|
|
2172
|
+
if (checkpoint.approval?.status === "DENIED") return new CheckoutApprovalDeniedError(checkpoint);
|
|
2173
|
+
if (checkpoint.approval?.status === "EXPIRED") {
|
|
2174
|
+
return new CheckoutApprovalExpiredError(checkpoint);
|
|
2175
|
+
}
|
|
2176
|
+
return new CheckoutCheckpointClosedError(checkpoint);
|
|
2177
|
+
}
|
|
2178
|
+
function sleep(ms, signal) {
|
|
2179
|
+
return new Promise((resolve, reject) => {
|
|
2180
|
+
if (signal?.aborted) {
|
|
2181
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const onAbort = () => {
|
|
2185
|
+
clearTimeout(timer);
|
|
2186
|
+
reject(signal?.reason ?? new Error("Aborted"));
|
|
2187
|
+
};
|
|
2188
|
+
const timer = setTimeout(() => {
|
|
2189
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2190
|
+
resolve();
|
|
2191
|
+
}, ms);
|
|
2192
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
var _client;
|
|
2196
|
+
var CheckoutClient = class {
|
|
2197
|
+
constructor(client) {
|
|
2153
2198
|
__privateAdd(this, _client);
|
|
2154
|
-
__privateAdd(this, _defaults);
|
|
2155
2199
|
__privateSet(this, _client, client);
|
|
2156
|
-
__privateSet(this, _defaults, defaults ? { ...defaults } : void 0);
|
|
2157
|
-
}
|
|
2158
|
-
/** Inspect the configured live path. This is not a payment authorization or provider connectivity test. */
|
|
2159
|
-
async setup(input = {}) {
|
|
2160
|
-
return __privateGet(this, _client).getLinkSetup({
|
|
2161
|
-
connectionId: input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2162
|
-
paymentMethodId: input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2163
|
-
merchantId: input.merchantId
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
/** Read masked LIVE funding sources; never selects a source or initiates payment. */
|
|
2167
|
-
async paymentMethods(connectionId) {
|
|
2168
|
-
return __privateGet(this, _client).listStripeLinkPaymentMethods(
|
|
2169
|
-
this.identifier(connectionId ?? __privateGet(this, _defaults)?.connectionId, "connectionId"),
|
|
2170
|
-
{ mode: "LIVE" }
|
|
2171
|
-
);
|
|
2172
2200
|
}
|
|
2173
2201
|
/**
|
|
2174
|
-
*
|
|
2175
|
-
* the owner
|
|
2202
|
+
* First checkpoint. Open the payment method only when the stage is APPROVED.
|
|
2203
|
+
* AWAITING_APPROVAL means the owner must decide first: call waitForApproval().
|
|
2204
|
+
* A ContoError means Conto did not approve: stop the checkout. A
|
|
2205
|
+
* CheckoutCheckpointClosedError means this idempotency key belongs to a
|
|
2206
|
+
* checkpoint that already closed.
|
|
2176
2207
|
*/
|
|
2177
|
-
async
|
|
2178
|
-
const
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2184
|
-
"paymentMethodId"
|
|
2185
|
-
);
|
|
2186
|
-
if (!Number.isSafeInteger(input.amountMinor) || input.amountMinor <= 0 || input.currency !== void 0 && input.currency !== "USD" || !Array.isArray(input.lineItems) || input.lineItems.length === 0 || typeof input.purpose !== "string" || !input.purpose.trim()) {
|
|
2187
|
-
throw new ContoError("Supply a positive USD amount in cents, purpose, and line items", {
|
|
2188
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2189
|
-
status: 0
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
|
-
const itemTotal = input.lineItems.reduce((sum, item) => {
|
|
2193
|
-
if (!item || typeof item.name !== "string" || !item.name.trim() || !Number.isSafeInteger(item.quantity) || item.quantity <= 0 || !Number.isSafeInteger(item.unitAmountMinor) || item.unitAmountMinor < 0) {
|
|
2194
|
-
throw new ContoError(
|
|
2195
|
-
"Each line item needs a name, positive integer quantity, and price in cents",
|
|
2196
|
-
{
|
|
2197
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2198
|
-
status: 0
|
|
2199
|
-
}
|
|
2200
|
-
);
|
|
2201
|
-
}
|
|
2202
|
-
return sum + item.quantity * item.unitAmountMinor;
|
|
2203
|
-
}, 0);
|
|
2204
|
-
if (!input.totals && (!Number.isSafeInteger(itemTotal) || itemTotal !== input.amountMinor)) {
|
|
2205
|
-
throw new ContoError(
|
|
2206
|
-
"Supply the exact totals breakdown when the amount differs from the line items",
|
|
2207
|
-
{
|
|
2208
|
-
code: "LINK_TOTALS_REQUIRED",
|
|
2209
|
-
status: 0
|
|
2210
|
-
}
|
|
2208
|
+
async begin(input) {
|
|
2209
|
+
const idempotencyKey = input.idempotencyKey?.trim();
|
|
2210
|
+
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
|
|
2211
|
+
throw checkoutError(
|
|
2212
|
+
"idempotencyKey must contain 8 to 255 characters",
|
|
2213
|
+
"INVALID_IDEMPOTENCY_KEY"
|
|
2211
2214
|
);
|
|
2212
2215
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2216
|
+
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
2217
|
+
throw checkoutError("Only USD checkouts are supported");
|
|
2218
|
+
}
|
|
2219
|
+
const result = await __privateGet(this, _client).request(
|
|
2220
|
+
"POST",
|
|
2221
|
+
"/api/sdk/checkout-checkpoints",
|
|
2222
|
+
{
|
|
2223
|
+
idempotencyKey,
|
|
2224
|
+
merchant: input.merchant,
|
|
2225
|
+
amountMinor: input.amountMinor,
|
|
2226
|
+
currency: "USD",
|
|
2227
|
+
purpose: input.purpose,
|
|
2228
|
+
...input.category ? { category: input.category } : {},
|
|
2229
|
+
...input.checkoutMethod ? { checkoutMethod: input.checkoutMethod } : {},
|
|
2230
|
+
cart: checkpointCart(input.amountMinor, input.lineItems, input.totals)
|
|
2226
2231
|
},
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
}
|
|
2232
|
-
return
|
|
2232
|
+
{ idempotent: true }
|
|
2233
|
+
);
|
|
2234
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(result.checkpoint.stage)) {
|
|
2235
|
+
throw closedCheckpointError(result.checkpoint);
|
|
2236
|
+
}
|
|
2237
|
+
return result.checkpoint;
|
|
2233
2238
|
}
|
|
2234
2239
|
/**
|
|
2235
|
-
*
|
|
2236
|
-
*
|
|
2240
|
+
* Waits while the owner decides on a checkpoint in AWAITING_APPROVAL and
|
|
2241
|
+
* resolves to the approved checkpoint. Throws CheckoutApprovalDeniedError,
|
|
2242
|
+
* CheckoutApprovalExpiredError, CheckoutCheckpointClosedError, or
|
|
2243
|
+
* CheckoutApprovalTimeoutError (the spend stays held until you cancel).
|
|
2237
2244
|
*/
|
|
2238
|
-
async
|
|
2239
|
-
const
|
|
2240
|
-
const
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
}
|
|
2253
|
-
/** Request cancellation of this existing Link payment; never retried automatically. */
|
|
2254
|
-
async cancel(executionId) {
|
|
2255
|
-
const id = this.identifier(executionId, "executionId");
|
|
2256
|
-
this.assertLinkExecution(await __privateGet(this, _client).getCommerceExecution(id));
|
|
2257
|
-
try {
|
|
2258
|
-
return this.result(await __privateGet(this, _client).cancelCommerceExecution(id));
|
|
2259
|
-
} catch (error) {
|
|
2260
|
-
throw new LinkPaymentExecutionError(id, error);
|
|
2245
|
+
async waitForApproval(checkpoint, options = {}) {
|
|
2246
|
+
const timeoutMs = options.timeoutMs ?? 10 * 6e4;
|
|
2247
|
+
const pollIntervalMs = Math.max(1e3, options.pollIntervalMs ?? 5e3);
|
|
2248
|
+
const deadline = Date.now() + timeoutMs;
|
|
2249
|
+
let current = checkpoint;
|
|
2250
|
+
for (; ; ) {
|
|
2251
|
+
if (current.stage !== "AWAITING_APPROVAL") {
|
|
2252
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(current.stage)) throw closedCheckpointError(current);
|
|
2253
|
+
return current;
|
|
2254
|
+
}
|
|
2255
|
+
const remaining = deadline - Date.now();
|
|
2256
|
+
if (remaining <= 0) throw new CheckoutApprovalTimeoutError(current);
|
|
2257
|
+
await sleep(Math.min(pollIntervalMs, remaining), options.signal);
|
|
2258
|
+
current = await this.get(current.id);
|
|
2261
2259
|
}
|
|
2262
2260
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2261
|
+
/**
|
|
2262
|
+
* Second checkpoint, right before the payment is submitted. If the total
|
|
2263
|
+
* changed since begin() (tax or shipping, for example), this cancels the
|
|
2264
|
+
* approved checkpoint and starts a new one at the final total, so the agent's
|
|
2265
|
+
* rules see the amount actually being paid. Pay only if this resolves. A
|
|
2266
|
+
* CheckoutApprovalPendingError means the owner must approve first: call
|
|
2267
|
+
* waitForApproval(error.checkpoint), then confirm the checkpoint it returns.
|
|
2268
|
+
*/
|
|
2269
|
+
async confirm(checkpoint, input) {
|
|
2270
|
+
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
2271
|
+
throw checkoutError("Only USD checkouts are supported");
|
|
2272
2272
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
if (
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2273
|
+
if (!Number.isSafeInteger(input.amountMinor) || input.amountMinor <= 0) {
|
|
2274
|
+
throw checkoutError("amountMinor must be a positive integer number of cents");
|
|
2275
|
+
}
|
|
2276
|
+
let current = checkpoint;
|
|
2277
|
+
if (input.amountMinor !== checkpoint.amountMinor) {
|
|
2278
|
+
try {
|
|
2279
|
+
await this.cancel(checkpoint.id);
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
const closed = error instanceof ContoError && error.status === 409 && ["CANCELLED", "EXPIRED", "DENIED"].includes((await this.get(checkpoint.id)).stage);
|
|
2282
|
+
if (!closed) throw error;
|
|
2283
|
+
}
|
|
2284
|
+
current = await this.begin({
|
|
2285
|
+
idempotencyKey: `${checkpoint.idempotencyKey.slice(0, 200)}:final-${input.amountMinor}`,
|
|
2286
|
+
merchant: {
|
|
2287
|
+
id: checkpoint.merchant.id,
|
|
2288
|
+
name: checkpoint.merchant.name,
|
|
2289
|
+
checkoutUrl: checkpoint.merchant.checkoutUrl ?? ""
|
|
2290
|
+
},
|
|
2291
|
+
amountMinor: input.amountMinor,
|
|
2292
|
+
purpose: checkpoint.purpose,
|
|
2293
|
+
...checkpoint.category ? { category: checkpoint.category } : {},
|
|
2294
|
+
checkoutMethod: checkpoint.checkoutMethod,
|
|
2295
|
+
lineItems: input.lineItems ?? checkpoint.cart.lineItems,
|
|
2296
|
+
totals: input.totals
|
|
2281
2297
|
});
|
|
2298
|
+
if (current.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(current);
|
|
2282
2299
|
}
|
|
2283
|
-
}
|
|
2284
|
-
async executeIfReady(result) {
|
|
2285
|
-
this.assertLinkExecution(result);
|
|
2286
|
-
if (result.execution.status !== "READY") return this.result(result);
|
|
2287
2300
|
try {
|
|
2288
|
-
|
|
2301
|
+
const result = await __privateGet(this, _client).request(
|
|
2302
|
+
"POST",
|
|
2303
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
|
|
2304
|
+
{
|
|
2305
|
+
amountMinor: input.amountMinor,
|
|
2306
|
+
currency: "USD",
|
|
2307
|
+
...input.pageUrl ? { pageUrl: input.pageUrl } : {}
|
|
2308
|
+
},
|
|
2309
|
+
{ idempotent: true }
|
|
2310
|
+
);
|
|
2311
|
+
return result.checkpoint;
|
|
2289
2312
|
} catch (error) {
|
|
2290
|
-
|
|
2313
|
+
if (error instanceof ContoError && error.status === 409 && error.code === "APPROVAL_REQUIRED") {
|
|
2314
|
+
const latest = await this.get(current.id);
|
|
2315
|
+
if (latest.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(latest);
|
|
2316
|
+
}
|
|
2317
|
+
throw error;
|
|
2291
2318
|
}
|
|
2292
2319
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
const
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2320
|
+
/** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
|
|
2321
|
+
async reportOutcome(checkpointId, input) {
|
|
2322
|
+
const result = await __privateGet(this, _client).request(
|
|
2323
|
+
"POST",
|
|
2324
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/outcome`,
|
|
2325
|
+
{
|
|
2326
|
+
outcome: input.outcome,
|
|
2327
|
+
...input.reference ? { reference: input.reference } : {}
|
|
2328
|
+
},
|
|
2329
|
+
{ idempotent: true }
|
|
2330
|
+
);
|
|
2331
|
+
return result.checkpoint;
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Cancel a checkpoint that was not confirmed and release its spend. A
|
|
2335
|
+
* checkpoint waiting for the owner can be cancelled too.
|
|
2336
|
+
*/
|
|
2337
|
+
async cancel(checkpointId) {
|
|
2338
|
+
const result = await __privateGet(this, _client).request(
|
|
2339
|
+
"POST",
|
|
2340
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/cancel`,
|
|
2341
|
+
{},
|
|
2342
|
+
{ idempotent: true }
|
|
2343
|
+
);
|
|
2344
|
+
return result.checkpoint;
|
|
2345
|
+
}
|
|
2346
|
+
async get(checkpointId) {
|
|
2347
|
+
const result = await __privateGet(this, _client).request(
|
|
2348
|
+
"GET",
|
|
2349
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}`
|
|
2350
|
+
);
|
|
2351
|
+
return result.checkpoint;
|
|
2316
2352
|
}
|
|
2317
2353
|
};
|
|
2318
2354
|
_client = new WeakMap();
|
|
2319
|
-
_defaults = new WeakMap();
|
|
2320
2355
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2321
2356
|
0 && (module.exports = {
|
|
2322
2357
|
BrowserActionFinalizationPendingError,
|
|
2323
2358
|
BrowserCheckoutTargetVerificationError,
|
|
2324
2359
|
CardsClient,
|
|
2360
|
+
CheckoutApprovalDeniedError,
|
|
2361
|
+
CheckoutApprovalExpiredError,
|
|
2362
|
+
CheckoutApprovalPendingError,
|
|
2363
|
+
CheckoutApprovalTimeoutError,
|
|
2364
|
+
CheckoutCheckpointClosedError,
|
|
2365
|
+
CheckoutClient,
|
|
2325
2366
|
ConnectedCardsClient,
|
|
2326
2367
|
Conto,
|
|
2327
2368
|
ContoAdmin,
|
|
@@ -2330,8 +2371,6 @@ _defaults = new WeakMap();
|
|
|
2330
2371
|
ControlledExternalCardPaymentTool,
|
|
2331
2372
|
ExternalCardConfirmationPendingError,
|
|
2332
2373
|
Giro,
|
|
2333
|
-
LinkClient,
|
|
2334
|
-
LinkPaymentExecutionError,
|
|
2335
2374
|
TestCardsClient,
|
|
2336
2375
|
canonicalizeBrowserCheckoutTarget,
|
|
2337
2376
|
createVerifiedBrowserCheckoutDriver,
|