@conto_finance/sdk 0.5.4 → 0.6.0-beta.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.
@@ -0,0 +1,244 @@
1
+ import "./chunk-WSIKPUZJ.mjs";
2
+
3
+ // src/link-testing.ts
4
+ import { randomUUID } from "crypto";
5
+ import {
6
+ ContoError,
7
+ LinkClient
8
+ } from "./index.mjs";
9
+ function canonical(value) {
10
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
11
+ if (value && typeof value === "object") {
12
+ return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, v]) => `${JSON.stringify(key)}:${canonical(v)}`).join(",")}}`;
13
+ }
14
+ return JSON.stringify(value);
15
+ }
16
+ function fail(code, message, status = 409) {
17
+ throw new ContoError(message, { code, status });
18
+ }
19
+ function createLinkTestClient(options = {}) {
20
+ const scenario = options.scenario ?? "approved";
21
+ const maximumAmountMinor = options.maximumAmountMinor ?? 5e3;
22
+ if (!["approved", "denied", "expired", "interrupted"].includes(scenario) || !Number.isSafeInteger(maximumAmountMinor) || maximumAmountMinor <= 0) {
23
+ throw new ContoError("Invalid Link simulation options", {
24
+ code: "INVALID_LINK_TEST_OPTIONS",
25
+ status: 0
26
+ });
27
+ }
28
+ const records = /* @__PURE__ */ new Map();
29
+ const intents = /* @__PURE__ */ new Map();
30
+ let sequence = 0;
31
+ const sessionId = randomUUID();
32
+ const read = (id) => {
33
+ const record = records.get(id);
34
+ if (!record)
35
+ return fail(
36
+ "SIMULATED_EXECUTION_NOT_FOUND",
37
+ "No such execution in this local simulation",
38
+ 404
39
+ );
40
+ return record;
41
+ };
42
+ const response = (id) => ({
43
+ execution: structuredClone(read(id))
44
+ });
45
+ const setStatus = (id, status) => {
46
+ const record = read(id);
47
+ record.status = status;
48
+ record.evidence.status = status;
49
+ record.providerStatus = `SIMULATED_${status}`;
50
+ record.evidence.providerStatus = record.providerStatus;
51
+ delete record.approvalUrl;
52
+ return response(id);
53
+ };
54
+ const setup = async (_input = {}) => ({
55
+ version: "conto.link-setup.v1",
56
+ mode: "SIMULATED",
57
+ configurationStatus: "CONFIGURED",
58
+ checks: [
59
+ {
60
+ code: "LOCAL_SIMULATION",
61
+ status: "WARNING",
62
+ actor: "developer",
63
+ message: "Local workflow fixture only. No real Link account, Conto policy, checkout, budget, or settlement is tested."
64
+ }
65
+ ],
66
+ missingScopes: [],
67
+ paymentAuthorization: "NOT_EVALUATED",
68
+ providerVerified: false,
69
+ settlementVerified: false
70
+ });
71
+ const transport = {
72
+ getLinkSetup: setup,
73
+ async listStripeLinkPaymentMethods() {
74
+ return {
75
+ organizationId: "simulated_org",
76
+ connectionId: "simulated_connection",
77
+ paymentMethods: [
78
+ {
79
+ id: "simulated_method",
80
+ type: "SIMULATED",
81
+ isDefault: true,
82
+ displayLabel: "Simulated funding source \u2014 no funds",
83
+ eligibleCredentialTypes: ["LINK_PAY_TOKEN"]
84
+ }
85
+ ]
86
+ };
87
+ },
88
+ async beginCommerceExecution(input) {
89
+ if (input.executionMethod !== "LINK_PAY_TOKEN")
90
+ return fail(
91
+ "SIMULATED_METHOD_UNSUPPORTED",
92
+ "Only the Link Pay Token workflow is simulated"
93
+ );
94
+ const key = input.idempotencyKey?.trim();
95
+ if (!key || key.length < 8 || key.length > 255) {
96
+ return fail(
97
+ "INVALID_IDEMPOTENCY_KEY",
98
+ "idempotencyKey must contain 8 to 255 characters",
99
+ 0
100
+ );
101
+ }
102
+ const fingerprint = canonical({ ...input, idempotencyKey: key });
103
+ const prior = intents.get(key);
104
+ if (prior) {
105
+ if (prior.fingerprint !== fingerprint)
106
+ return fail("IDEMPOTENCY_CONFLICT", "The key belongs to a different simulated purchase");
107
+ return response(prior.id);
108
+ }
109
+ if (scenario === "denied" || input.amountMinor > maximumAmountMinor) {
110
+ return fail(
111
+ "COMMERCE_POLICY_DENIED",
112
+ "The local simulated policy denied this purchase",
113
+ 403
114
+ );
115
+ }
116
+ if (records.size >= 1e3)
117
+ return fail(
118
+ "SIMULATION_CAPACITY_REACHED",
119
+ "Create a new test client to start another simulation"
120
+ );
121
+ const id = `sim_link_${sessionId}_${++sequence}`;
122
+ const status = "AWAITING_APPROVAL";
123
+ const evidence = {
124
+ schemaVersion: "conto.commerce-evidence.v1",
125
+ traceId: `sim_trace_${sequence}`,
126
+ executionId: id,
127
+ intentId: `sim_intent_${sequence}`,
128
+ organizationId: "simulated_org",
129
+ actorUserId: "simulated_owner",
130
+ agentId: "simulated_agent",
131
+ provider: "STRIPE_LINK",
132
+ sourceKind: "STRIPE_LINK",
133
+ amountMinor: input.amountMinor,
134
+ currency: input.currency,
135
+ purpose: input.purpose,
136
+ counterpartyId: input.counterparty.canonicalId,
137
+ counterpartyName: "Simulated checkout",
138
+ intentFingerprint: `sim_fingerprint_${sequence}`,
139
+ policyDecisionId: `sim_policy_${sequence}`,
140
+ policySnapshotHash: "simulated_policy",
141
+ grantId: `sim_grant_${sequence}`,
142
+ status,
143
+ deliveryMode: "SERVER_ONLY_ARTIFACT",
144
+ artifactKind: "LINK_PAY_TOKEN",
145
+ providerStatus: "SIMULATED_AWAITING_APPROVAL"
146
+ };
147
+ records.set(id, {
148
+ id,
149
+ intentId: evidence.intentId,
150
+ organizationId: evidence.organizationId,
151
+ agentId: evidence.agentId,
152
+ provider: "STRIPE_LINK",
153
+ executionAdapter: "STRIPE_LINK",
154
+ executionChannel: "BROWSER",
155
+ checkoutMethod: "LINK",
156
+ status,
157
+ providerStatus: evidence.providerStatus,
158
+ artifactKind: "LINK_PAY_TOKEN",
159
+ deliveryMode: "SERVER_ONLY_ARTIFACT",
160
+ // Reserved .invalid domain. It cannot approve anything; call the test-only approve() method.
161
+ approvalUrl: `https://link-simulation.invalid/approve/${id}`,
162
+ amountMinor: input.amountMinor,
163
+ currency: input.currency,
164
+ purpose: input.purpose,
165
+ counterparty: { id: input.counterparty.canonicalId, name: "Simulated checkout" },
166
+ evidence,
167
+ evidenceFingerprint: `sim_evidence_${sequence}`
168
+ });
169
+ intents.set(key, { fingerprint, id });
170
+ return response(id);
171
+ },
172
+ async getCommerceExecution(id) {
173
+ return response(id);
174
+ },
175
+ async refreshCommerceExecution(id) {
176
+ return response(id);
177
+ },
178
+ async executeCommerceExecution(id) {
179
+ if (read(id).status !== "READY")
180
+ return fail("COMMERCE_EXECUTION_NOT_READY", "The simulated payment is not ready");
181
+ if (scenario === "interrupted") {
182
+ setStatus(id, "NEEDS_RECONCILIATION");
183
+ return fail(
184
+ "SIMULATED_EXECUTION_INTERRUPTED",
185
+ "Simulated lost execution response; inspect the existing execution",
186
+ 0
187
+ );
188
+ }
189
+ return {
190
+ ...setStatus(id, "COMPLETED"),
191
+ checkout: {
192
+ submitted: false,
193
+ accepted: false,
194
+ deliveryMode: "SERVER_ONLY_ARTIFACT"
195
+ }
196
+ };
197
+ },
198
+ async cancelCommerceExecution(id) {
199
+ if (["PROCESSING", "NEEDS_RECONCILIATION", "COMPLETED", "SETTLED"].includes(read(id).status)) {
200
+ return fail(
201
+ "SIMULATED_CANCELLATION_UNAVAILABLE",
202
+ "This simulated execution must be reconciled and cannot be cancelled"
203
+ );
204
+ }
205
+ return setStatus(id, "CANCELLED");
206
+ }
207
+ };
208
+ const client = new LinkClient(transport, {
209
+ connectionId: "simulated_connection",
210
+ paymentMethodId: "simulated_method"
211
+ });
212
+ const marked = async (operation) => {
213
+ try {
214
+ return { ...await operation(), mode: "SIMULATED", settlementVerified: false };
215
+ } catch (error) {
216
+ const safe = error instanceof ContoError ? error : new ContoError("Local simulation failed", { code: "SIMULATION_FAILED", status: 0 });
217
+ throw Object.assign(safe, { mode: "SIMULATED", settlementVerified: false });
218
+ }
219
+ };
220
+ return {
221
+ mode: "SIMULATED",
222
+ link: {
223
+ setup: (input) => marked(() => client.setup(input)),
224
+ paymentMethods: () => marked(() => client.paymentMethods()),
225
+ pay: (input) => marked(() => client.pay(input)),
226
+ resume: (id) => marked(() => client.resume(id)),
227
+ status: (id) => marked(() => client.status(id)),
228
+ cancel: (id) => marked(() => client.cancel(id)),
229
+ /** Test-only owner action. Never available on the live Link client. */
230
+ approve: (id) => marked(async () => {
231
+ if (read(id).status !== "AWAITING_APPROVAL")
232
+ return fail(
233
+ "SIMULATED_APPROVAL_UNAVAILABLE",
234
+ "This simulation is not awaiting approval"
235
+ );
236
+ setStatus(id, scenario === "expired" ? "EXPIRED" : "READY");
237
+ return client.status(id);
238
+ })
239
+ }
240
+ };
241
+ }
242
+ export {
243
+ createLinkTestClient
244
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conto_finance/sdk",
3
- "version": "0.5.4",
3
+ "version": "0.6.0-beta.1",
4
4
  "description": "Conto SDK for AI agent payment authorization",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -25,10 +25,16 @@
25
25
  "types": "./dist/checkout-verifier.d.ts",
26
26
  "import": "./dist/checkout-verifier.mjs",
27
27
  "require": "./dist/checkout-verifier.js"
28
+ },
29
+ "./link-testing": {
30
+ "types": "./dist/link-testing.d.ts",
31
+ "import": "./dist/link-testing.mjs",
32
+ "require": "./dist/link-testing.js"
28
33
  }
29
34
  },
30
35
  "publishConfig": {
31
- "access": "public"
36
+ "access": "public",
37
+ "tag": "beta"
32
38
  },
33
39
  "engines": {
34
40
  "node": ">=20.19.0"
@@ -37,11 +43,11 @@
37
43
  "dist"
38
44
  ],
39
45
  "scripts": {
40
- "build": "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts",
41
- "dev": "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts --watch",
46
+ "build": "tsup",
47
+ "dev": "tsup --watch",
42
48
  "lint": "tsc --noEmit",
43
49
  "test": "npm run lint && npm run test:contract && npm run test:consumer",
44
- "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",
50
+ "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/link.test.mjs test/link-testing.test.mjs",
45
51
  "test:consumer": "npm run build && node --test test/consumer.test.mjs",
46
52
  "verify:release": "node scripts/verify-release.mjs",
47
53
  "prepublishOnly": "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"