@agentwonderland/mcp 0.1.45 → 0.1.47

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.
Files changed (38) hide show
  1. package/dist/core/__tests__/payments.test.js +55 -1
  2. package/dist/core/__tests__/principal.test.js +18 -0
  3. package/dist/core/config.d.ts +16 -0
  4. package/dist/core/config.js +37 -0
  5. package/dist/core/link-cli.d.ts +27 -0
  6. package/dist/core/link-cli.js +259 -0
  7. package/dist/core/mpp-client.d.ts +13 -1
  8. package/dist/core/mpp-client.js +45 -2
  9. package/dist/core/payments.js +135 -10
  10. package/dist/core/principal.js +2 -2
  11. package/dist/core/version.d.ts +1 -1
  12. package/dist/core/version.js +1 -1
  13. package/dist/index.js +6 -4
  14. package/dist/tools/__tests__/search.test.d.ts +1 -0
  15. package/dist/tools/__tests__/search.test.js +66 -0
  16. package/dist/tools/__tests__/wallet.test.js +153 -0
  17. package/dist/tools/passes.js +1 -1
  18. package/dist/tools/run.js +1 -1
  19. package/dist/tools/search.js +33 -0
  20. package/dist/tools/solve.js +1 -1
  21. package/dist/tools/wallet.js +195 -7
  22. package/package.json +1 -1
  23. package/src/core/__tests__/payments.test.ts +78 -1
  24. package/src/core/__tests__/principal.test.ts +23 -0
  25. package/src/core/config.ts +56 -0
  26. package/src/core/link-cli.ts +300 -0
  27. package/src/core/mpp-client.ts +69 -2
  28. package/src/core/payments.ts +153 -11
  29. package/src/core/principal.ts +2 -2
  30. package/src/core/version.ts +1 -1
  31. package/src/index.ts +6 -4
  32. package/src/tools/__tests__/search.test.ts +78 -0
  33. package/src/tools/__tests__/wallet.test.ts +190 -0
  34. package/src/tools/passes.ts +1 -1
  35. package/src/tools/run.ts +1 -1
  36. package/src/tools/search.ts +40 -0
  37. package/src/tools/solve.ts +1 -1
  38. package/src/tools/wallet.ts +229 -6
@@ -6,18 +6,22 @@ let currentCard = {
6
6
  last4: "1111",
7
7
  brand: "visa",
8
8
  };
9
+ let currentLink = null;
9
10
  let currentDefaultWallet;
10
11
  let currentWallets = [];
11
12
  let currentResolvedMethod = null;
13
+ let currentDefaultPaymentMethod = "card";
12
14
  const createdFetches = [vi.fn(), vi.fn(), vi.fn(), vi.fn()];
13
15
  const mockMppxCreate = vi.fn();
14
16
  const mockStripe = vi.fn((opts) => opts);
15
17
  const mockTempoChargeClient = vi.fn((..._args) => "tempo_method");
16
18
  const mockBaseChargeClient = vi.fn((..._args) => "base_method");
19
+ const mockCreateLinkSharedPaymentToken = vi.fn(async (_config) => "spt_test");
17
20
  vi.mock("../config.js", () => ({
18
21
  getApiUrl: () => "http://api.test",
19
22
  getCardConfig: () => currentCard,
20
- getConfig: () => ({ defaultPaymentMethod: "card" }),
23
+ getLinkConfig: () => currentLink,
24
+ getConfig: () => ({ defaultPaymentMethod: currentDefaultPaymentMethod }),
21
25
  getDefaultWallet: () => currentDefaultWallet,
22
26
  getWallets: () => currentWallets,
23
27
  resolveWalletAndChain: () => currentResolvedMethod,
@@ -34,10 +38,14 @@ vi.mock("../tempo-charge.js", () => ({
34
38
  vi.mock("../base-charge.js", () => ({
35
39
  baseChargeClient: (config) => mockBaseChargeClient(config),
36
40
  }));
41
+ vi.mock("../link-cli.js", () => ({
42
+ createLinkSharedPaymentToken: (config) => mockCreateLinkSharedPaymentToken(config),
43
+ }));
37
44
  describe("payment method initialization", () => {
38
45
  beforeEach(() => {
39
46
  vi.clearAllMocks();
40
47
  vi.resetModules();
48
+ delete process.env.AGENTWONDERLAND_LINK_APPROVAL_LIMIT_CENTS;
41
49
  currentCard = {
42
50
  consumerToken: "consumer_one",
43
51
  paymentMethodId: "pm_one",
@@ -47,6 +55,8 @@ describe("payment method initialization", () => {
47
55
  currentDefaultWallet = undefined;
48
56
  currentWallets = [];
49
57
  currentResolvedMethod = null;
58
+ currentLink = null;
59
+ currentDefaultPaymentMethod = "card";
50
60
  mockMppxCreate
51
61
  .mockReturnValueOnce({ fetch: createdFetches[0] })
52
62
  .mockReturnValueOnce({ fetch: createdFetches[1] })
@@ -93,4 +103,48 @@ describe("payment method initialization", () => {
93
103
  expect(mockBaseChargeClient).not.toHaveBeenCalled();
94
104
  expect(mockMppxCreate).toHaveBeenCalledWith(expect.objectContaining({ methods: ["tempo_method"], polyfill: false }));
95
105
  });
106
+ it("initializes Stripe SPT method when Link is requested", async () => {
107
+ currentLink = {
108
+ paymentMethodId: "csmrpd_link_123",
109
+ label: "Visa ****4242",
110
+ };
111
+ currentDefaultPaymentMethod = "link";
112
+ const { getPaymentFetch } = await import("../payments.js");
113
+ await getPaymentFetch("link");
114
+ expect(mockStripe).toHaveBeenCalledWith(expect.objectContaining({ paymentMethod: "csmrpd_link_123" }));
115
+ expect(mockMppxCreate).toHaveBeenCalledWith(expect.objectContaining({
116
+ methods: [expect.objectContaining({ paymentMethod: "csmrpd_link_123" })],
117
+ polyfill: false,
118
+ }));
119
+ const stripeConfig = mockStripe.mock.calls[0]?.[0];
120
+ await stripeConfig.createToken({
121
+ amount: "25",
122
+ currency: "usd",
123
+ expiresAt: 1778290000,
124
+ networkId: "profile_test",
125
+ });
126
+ expect(mockCreateLinkSharedPaymentToken).toHaveBeenCalledWith(expect.objectContaining({
127
+ amount: "10000",
128
+ currency: "usd",
129
+ networkId: "profile_test",
130
+ paymentMethodId: "csmrpd_link_123",
131
+ }));
132
+ const linkTokenRequest = mockCreateLinkSharedPaymentToken.mock.calls[0]?.[0];
133
+ expect(linkTokenRequest?.context).toContain("up to USD 100.00");
134
+ expect(linkTokenRequest?.context).toContain("quoted at USD 0.25");
135
+ });
136
+ it("advertises Link as Stripe SPT compatibility", async () => {
137
+ currentLink = {
138
+ paymentMethodId: "csmrpd_link_123",
139
+ };
140
+ currentDefaultPaymentMethod = "link";
141
+ const { getAcceptedPaymentMethods, getCompatiblePaymentMethods } = await import("../payments.js");
142
+ expect(getAcceptedPaymentMethods()).toEqual(["stripe_card", "card"]);
143
+ expect(getCompatiblePaymentMethods({
144
+ payment: { accepted_payments: ["stripe_card"] },
145
+ })).toEqual(["link"]);
146
+ expect(getCompatiblePaymentMethods({
147
+ payment: { accepted_payments: ["card"] },
148
+ })).toEqual(["link"]);
149
+ });
96
150
  });
@@ -88,6 +88,24 @@ describe("consumer principal helpers", () => {
88
88
  const principal = await getConsumerPrincipalForMethod("solana");
89
89
  expect(principal).toBe("did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:42W2HfLfveSm1T5et9WTLp2CZ2QXdF2EYCUvyJ2gPpxF");
90
90
  });
91
+ it("uses the default consumer principal for Link payments", async () => {
92
+ state.wallets = [
93
+ { id: "aw-main", keyType: "ows", owsWalletId: "ows-main", chains: ["tempo", "base"], defaultChain: "base" },
94
+ ];
95
+ state.addresses = {
96
+ "aw-main": "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
97
+ base: "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
98
+ };
99
+ const { getConsumerPrincipalForMethod } = await import("../principal.js");
100
+ const principal = await getConsumerPrincipalForMethod("link");
101
+ expect(principal).toBe("did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
102
+ });
103
+ it("creates a fallback identity wallet for Link payments when no wallet exists", async () => {
104
+ const { ensureConsumerPrincipalForMethod } = await import("../principal.js");
105
+ const principal = await ensureConsumerPrincipalForMethod("link");
106
+ expect(principal).toMatch(/^did:pkh:eip155:8453:0x[a-f0-9]{40}$/);
107
+ expect(state.addedWallets).toHaveLength(1);
108
+ });
91
109
  it("returns the Base rebate principal when an EVM wallet is available", async () => {
92
110
  state.wallets = [
93
111
  { id: "aw-main", keyType: "ows", owsWalletId: "ows-main", chains: ["tempo", "base", "solana"], defaultChain: "tempo" },
@@ -13,6 +13,15 @@ export interface CardConfig {
13
13
  last4: string;
14
14
  brand: string;
15
15
  }
16
+ export interface LinkConfig {
17
+ paymentMethodId: string;
18
+ label?: string;
19
+ }
20
+ export interface PendingLinkSetup {
21
+ verificationUrl: string;
22
+ phrase: string;
23
+ createdAt: string;
24
+ }
16
25
  export interface SpendPolicy {
17
26
  maxPerTxUsd?: number;
18
27
  maxPerDayUsd?: number;
@@ -32,6 +41,8 @@ export interface Config {
32
41
  defaultWallet: string | null;
33
42
  defaultPaymentMethod?: string;
34
43
  card: CardConfig | null;
44
+ link: LinkConfig | null;
45
+ pendingLinkSetup?: PendingLinkSetup | null;
35
46
  pendingCardSetupToken?: string | null;
36
47
  favorites: string[];
37
48
  /** Require user confirmation before spending. Default: true. Set false for headless/automated use. */
@@ -60,6 +71,7 @@ export declare function requiresSpendConfirmation(): boolean;
60
71
  export declare function getDefaultTipAmount(): number;
61
72
  export declare function getSpendPolicy(method: string): SpendPolicy | null;
62
73
  export declare function setSpendPolicy(method: string, policy: SpendPolicy): void;
74
+ export declare function setDefaultPaymentMethod(defaultPaymentMethod: string | undefined): void;
63
75
  export declare function getSpendLedger(): SpendLedgerEntry[];
64
76
  export declare function saveSpendLedger(entries: SpendLedgerEntry[]): void;
65
77
  /**
@@ -89,10 +101,14 @@ export declare function removeWallet(id: string): void;
89
101
  * Get card configuration.
90
102
  */
91
103
  export declare function getCardConfig(): CardConfig | null;
104
+ export declare function getLinkConfig(): LinkConfig | null;
105
+ export declare function getPendingLinkSetup(): PendingLinkSetup | null;
92
106
  /**
93
107
  * Save card configuration after setup.
94
108
  */
95
109
  export declare function setCardConfig(card: CardConfig | null): void;
110
+ export declare function setLinkConfig(link: LinkConfig | null): void;
111
+ export declare function setPendingLinkSetup(pendingLinkSetup: PendingLinkSetup | null): void;
96
112
  export declare function getPendingCardSetupToken(): string | null;
97
113
  export declare function setPendingCardSetupToken(token: string | null): void;
98
114
  /**
@@ -36,6 +36,8 @@ function migrateIfNeeded(raw) {
36
36
  defaultWallet: raw.defaultWallet ?? null,
37
37
  defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
38
38
  card: raw.card ?? null,
39
+ link: raw.link ?? null,
40
+ pendingLinkSetup: raw.link ? null : (raw.pendingLinkSetup ?? null),
39
41
  pendingCardSetupToken: r.pendingCardSetupToken ?? null,
40
42
  favorites: r.favorites ?? [],
41
43
  confirmBeforeSpend: r.confirmBeforeSpend !== false,
@@ -109,6 +111,8 @@ function migrateIfNeeded(raw) {
109
111
  defaultWallet,
110
112
  defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
111
113
  card,
114
+ link: raw.link ?? null,
115
+ pendingLinkSetup: raw.link ? null : (raw.pendingLinkSetup ?? null),
112
116
  pendingCardSetupToken: null,
113
117
  favorites: [],
114
118
  confirmBeforeSpend: true,
@@ -132,6 +136,8 @@ export function getConfig() {
132
136
  wallets: [],
133
137
  defaultWallet: null,
134
138
  card: null,
139
+ link: null,
140
+ pendingLinkSetup: null,
135
141
  pendingCardSetupToken: null,
136
142
  favorites: [],
137
143
  confirmBeforeSpend: true,
@@ -188,6 +194,9 @@ export function setSpendPolicy(method, policy) {
188
194
  policies[method] = policy;
189
195
  saveConfig({ spendPolicies: policies });
190
196
  }
197
+ export function setDefaultPaymentMethod(defaultPaymentMethod) {
198
+ saveConfig({ defaultPaymentMethod });
199
+ }
191
200
  export function getSpendLedger() {
192
201
  return getConfig().spendLedger ?? [];
193
202
  }
@@ -286,6 +295,12 @@ export function removeWallet(id) {
286
295
  export function getCardConfig() {
287
296
  return getConfig().card;
288
297
  }
298
+ export function getLinkConfig() {
299
+ return getConfig().link;
300
+ }
301
+ export function getPendingLinkSetup() {
302
+ return getConfig().pendingLinkSetup ?? null;
303
+ }
289
304
  /**
290
305
  * Save card configuration after setup.
291
306
  */
@@ -308,6 +323,28 @@ export function setCardConfig(card) {
308
323
  });
309
324
  }
310
325
  }
326
+ export function setLinkConfig(link) {
327
+ const current = getConfig();
328
+ if (link) {
329
+ saveConfig({
330
+ link,
331
+ defaultPaymentMethod: "link",
332
+ pendingLinkSetup: null,
333
+ });
334
+ }
335
+ else {
336
+ saveConfig({
337
+ link,
338
+ pendingLinkSetup: null,
339
+ defaultPaymentMethod: current.defaultPaymentMethod === "link"
340
+ ? undefined
341
+ : current.defaultPaymentMethod,
342
+ });
343
+ }
344
+ }
345
+ export function setPendingLinkSetup(pendingLinkSetup) {
346
+ saveConfig({ pendingLinkSetup });
347
+ }
311
348
  export function getPendingCardSetupToken() {
312
349
  return getConfig().pendingCardSetupToken ?? null;
313
350
  }
@@ -0,0 +1,27 @@
1
+ export interface LinkCliAuthStatus {
2
+ authenticated: boolean;
3
+ credentialsPath?: string;
4
+ pending?: boolean;
5
+ }
6
+ export interface LinkCliPaymentMethod {
7
+ id: string;
8
+ label?: string;
9
+ searchText?: string;
10
+ }
11
+ export interface LinkCliLogin {
12
+ verificationUrl: string;
13
+ phrase: string;
14
+ instruction?: string;
15
+ }
16
+ export declare function getLinkCliAuthStatus(): Promise<LinkCliAuthStatus>;
17
+ export declare function startLinkCliLogin(): Promise<LinkCliLogin>;
18
+ export declare function openLinkPaymentMethodAdd(): Promise<void>;
19
+ export declare function listLinkPaymentMethods(): Promise<LinkCliPaymentMethod[]>;
20
+ export declare function createLinkSharedPaymentToken(params: {
21
+ amount: string;
22
+ currency: string;
23
+ context: string;
24
+ expiresAt: number;
25
+ networkId: string;
26
+ paymentMethodId: string;
27
+ }): Promise<string>;
@@ -0,0 +1,259 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ const LINK_CLI_PACKAGE = "@stripe/link-cli";
5
+ const LINK_CLI_TIMEOUT_MS = 10 * 60 * 1000;
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ async function runLinkCli(args, timeout = LINK_CLI_TIMEOUT_MS) {
10
+ try {
11
+ const { stdout } = await execFileAsync("npx", ["--yes", LINK_CLI_PACKAGE, ...args, "--format", "json"], {
12
+ maxBuffer: 1024 * 1024,
13
+ timeout,
14
+ });
15
+ const trimmed = stdout.trim();
16
+ return trimmed ? JSON.parse(trimmed) : null;
17
+ }
18
+ catch (err) {
19
+ const error = err;
20
+ const output = error.stdout?.trim() || error.stderr?.trim();
21
+ if (output) {
22
+ try {
23
+ const parsed = JSON.parse(output);
24
+ const message = parsed.message ?? output;
25
+ throw new Error(parsed.code ? `${parsed.code}: ${message}` : message);
26
+ }
27
+ catch (parseErr) {
28
+ if (parseErr instanceof SyntaxError) {
29
+ throw new Error(output);
30
+ }
31
+ throw parseErr;
32
+ }
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ function asRecord(value) {
38
+ return typeof value === "object" && value !== null && !Array.isArray(value)
39
+ ? value
40
+ : null;
41
+ }
42
+ function walk(value, visit, key) {
43
+ const direct = visit(value, key);
44
+ if (direct)
45
+ return direct;
46
+ if (Array.isArray(value)) {
47
+ for (const item of value) {
48
+ const found = walk(item, visit);
49
+ if (found)
50
+ return found;
51
+ }
52
+ return null;
53
+ }
54
+ const record = asRecord(value);
55
+ if (record) {
56
+ for (const [childKey, childValue] of Object.entries(record)) {
57
+ const found = walk(childValue, visit, childKey);
58
+ if (found)
59
+ return found;
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ function extractSharedPaymentToken(output) {
65
+ return walk(output, (value, key) => {
66
+ if (typeof value !== "string")
67
+ return null;
68
+ if (value.startsWith("spt_"))
69
+ return value;
70
+ if (key && /shared.*payment.*token|spt/i.test(key) && value.includes("spt_")) {
71
+ return value.match(/spt_[A-Za-z0-9_]+/)?.[0] ?? null;
72
+ }
73
+ return null;
74
+ });
75
+ }
76
+ function extractSpendRequestApproval(output) {
77
+ const values = Array.isArray(output) ? output : [output];
78
+ for (const value of values) {
79
+ const record = asRecord(value);
80
+ if (!record)
81
+ continue;
82
+ const id = typeof record.id === "string" && record.id.startsWith("lsrq_")
83
+ ? record.id
84
+ : null;
85
+ if (!id)
86
+ continue;
87
+ return {
88
+ id,
89
+ approvalUrl: typeof record.approval_url === "string" ? record.approval_url : undefined,
90
+ status: typeof record.status === "string" ? record.status : undefined,
91
+ };
92
+ }
93
+ return null;
94
+ }
95
+ function normalizePaymentMethods(output) {
96
+ const values = Array.isArray(output)
97
+ ? output
98
+ : Array.isArray(asRecord(output)?.data)
99
+ ? asRecord(output)?.data
100
+ : Array.isArray(asRecord(output)?.payment_methods)
101
+ ? asRecord(output)?.payment_methods
102
+ : [];
103
+ return values
104
+ .map((value) => {
105
+ const record = asRecord(value);
106
+ if (!record)
107
+ return null;
108
+ const id = typeof record.id === "string"
109
+ ? record.id
110
+ : typeof record.payment_method_id === "string"
111
+ ? record.payment_method_id
112
+ : null;
113
+ if (!id)
114
+ return null;
115
+ const name = typeof record.name === "string" ? record.name : undefined;
116
+ const type = typeof record.type === "string" ? record.type : undefined;
117
+ const cardDetails = asRecord(record.card_details);
118
+ const bankDetails = asRecord(record.bank_account_details);
119
+ const brand = typeof cardDetails?.brand === "string"
120
+ ? cardDetails.brand
121
+ : typeof record.brand === "string"
122
+ ? record.brand
123
+ : undefined;
124
+ const last4 = typeof cardDetails?.last4 === "string"
125
+ ? cardDetails.last4
126
+ : typeof bankDetails?.last4 === "string"
127
+ ? bankDetails.last4
128
+ : typeof record.last4 === "string"
129
+ ? record.last4
130
+ : undefined;
131
+ const bankName = typeof bankDetails?.bank_name === "string" ? bankDetails.bank_name : undefined;
132
+ const typeLabel = type === "BANK_ACCOUNT" ? "bank" : type === "CARD" ? "card" : type?.toLowerCase();
133
+ const labelParts = [
134
+ name ?? bankName ?? brand ?? typeLabel,
135
+ brand && name?.toLowerCase().includes(brand.toLowerCase()) !== true ? brand : undefined,
136
+ last4 ? `****${last4}` : undefined,
137
+ ];
138
+ const label = labelParts.filter(Boolean).join(" ");
139
+ const searchText = [id, label, name, type, brand, bankName, last4].filter(Boolean).join(" ").toLowerCase();
140
+ return { id, ...(label ? { label } : {}), searchText };
141
+ })
142
+ .filter((value) => Boolean(value));
143
+ }
144
+ export async function getLinkCliAuthStatus() {
145
+ try {
146
+ const output = await runLinkCli(["auth", "status"], 30_000);
147
+ const status = Array.isArray(output) ? asRecord(output[0]) : asRecord(output);
148
+ return {
149
+ authenticated: status?.authenticated === true,
150
+ credentialsPath: typeof status?.credentials_path === "string" ? status.credentials_path : undefined,
151
+ pending: status?.pending === true,
152
+ };
153
+ }
154
+ catch {
155
+ return { authenticated: false };
156
+ }
157
+ }
158
+ export async function startLinkCliLogin() {
159
+ const output = await runLinkCli(["auth", "login", "--client-name", "Agent Wonderland MCP"]);
160
+ const login = Array.isArray(output) ? asRecord(output[0]) : asRecord(output);
161
+ const verificationUrl = typeof login?.verification_url === "string"
162
+ ? login.verification_url
163
+ : typeof login?.verificationUrl === "string"
164
+ ? login.verificationUrl
165
+ : null;
166
+ const phrase = typeof login?.phrase === "string" ? login.phrase : null;
167
+ if (!verificationUrl || !phrase) {
168
+ throw new Error("Link CLI did not return a verification URL.");
169
+ }
170
+ return {
171
+ verificationUrl,
172
+ phrase,
173
+ instruction: typeof login?.instruction === "string" ? login.instruction : undefined,
174
+ };
175
+ }
176
+ export async function openLinkPaymentMethodAdd() {
177
+ await runLinkCli(["payment-methods", "add"]);
178
+ }
179
+ export async function listLinkPaymentMethods() {
180
+ const output = await runLinkCli(["payment-methods", "list"], 60_000);
181
+ return normalizePaymentMethods(output);
182
+ }
183
+ export async function createLinkSharedPaymentToken(params) {
184
+ const args = [
185
+ "spend-request",
186
+ "create",
187
+ "--credential-type",
188
+ "shared_payment_token",
189
+ "--network-id",
190
+ params.networkId,
191
+ "--amount",
192
+ params.amount,
193
+ "--currency",
194
+ params.currency,
195
+ "--payment-method-id",
196
+ params.paymentMethodId,
197
+ "--context",
198
+ params.context,
199
+ "--request-approval",
200
+ ];
201
+ if (process.env.AGENTWONDERLAND_LINK_TEST_MODE === "1") {
202
+ args.push("--test");
203
+ }
204
+ let output;
205
+ try {
206
+ output = await runLinkCli(args);
207
+ }
208
+ catch (err) {
209
+ const message = err instanceof Error ? err.message : String(err);
210
+ if (/invalid network_id|could not retrieve merchant information/i.test(message)) {
211
+ throw new Error([
212
+ message,
213
+ "",
214
+ `Link CLI rejected the merchant network_id "${params.networkId}".`,
215
+ "For local Agent Wonderland testing, restart the gateway with a Stripe key whose live/test mode matches STRIPE_PROFILE_ID. If the modes already match, the Stripe profile likely is not provisioned for Link Agentic Commerce/SPT yet or Stripe needs to provide a different network id.",
216
+ ].join("\n"));
217
+ }
218
+ throw err;
219
+ }
220
+ const spt = extractSharedPaymentToken(output);
221
+ if (spt) {
222
+ return spt;
223
+ }
224
+ const approval = extractSpendRequestApproval(output);
225
+ if (approval?.id && approval.status === "pending_approval") {
226
+ if (approval.approvalUrl) {
227
+ console.error(`Link approval required: ${approval.approvalUrl}`);
228
+ }
229
+ let retrieved = await runLinkCli([
230
+ "spend-request",
231
+ "retrieve",
232
+ approval.id,
233
+ "--interval",
234
+ "2",
235
+ "--max-attempts",
236
+ "150",
237
+ ]);
238
+ let retrievedSpt = extractSharedPaymentToken(retrieved);
239
+ for (let attempt = 0; !retrievedSpt && attempt < 30; attempt += 1) {
240
+ await sleep(2_000);
241
+ retrieved = await runLinkCli([
242
+ "spend-request",
243
+ "retrieve",
244
+ approval.id,
245
+ ]);
246
+ retrievedSpt = extractSharedPaymentToken(retrieved);
247
+ }
248
+ if (retrievedSpt) {
249
+ return retrievedSpt;
250
+ }
251
+ throw new Error([
252
+ "Link spend request finished without a shared payment token.",
253
+ approval.approvalUrl ? `Approval URL: ${approval.approvalUrl}` : undefined,
254
+ ].filter(Boolean).join("\n"));
255
+ }
256
+ {
257
+ throw new Error("Link spend request completed without a shared payment token in the CLI response.");
258
+ }
259
+ }
@@ -59,4 +59,16 @@ export declare const Mppx: {
59
59
  rawFetch: typeof fetch;
60
60
  };
61
61
  };
62
- export declare function stripe(_parameters?: unknown): ClientMethod;
62
+ export declare function stripe(parameters: {
63
+ createToken: (parameters: {
64
+ amount: string;
65
+ challenge: Challenge;
66
+ currency: string;
67
+ expiresAt: number;
68
+ metadata?: Record<string, string>;
69
+ networkId: string;
70
+ paymentMethod?: string;
71
+ }) => Promise<string>;
72
+ externalId?: string;
73
+ paymentMethod?: string;
74
+ }): ClientMethod[];
@@ -41,8 +41,51 @@ export const Mppx = {
41
41
  };
42
42
  },
43
43
  };
44
- export function stripe(_parameters) {
45
- throw new Error("Stripe card payments are temporarily unavailable.");
44
+ export function stripe(parameters) {
45
+ const { createToken, externalId, paymentMethod: defaultPaymentMethod } = parameters;
46
+ return [
47
+ Method.toClient({
48
+ name: "stripe",
49
+ intent: "charge",
50
+ }, {
51
+ async createCredential({ challenge }) {
52
+ const paymentMethod = defaultPaymentMethod;
53
+ if (!paymentMethod) {
54
+ throw new Error("paymentMethod is required");
55
+ }
56
+ const amount = String(challenge.request.amount ?? "");
57
+ const currency = String(challenge.request.currency ?? "");
58
+ const methodDetails = challenge.request.methodDetails;
59
+ const networkId = methodDetails?.networkId;
60
+ if (!networkId) {
61
+ throw new Error("networkId is required in stripe payment challenge");
62
+ }
63
+ const metadata = methodDetails?.metadata;
64
+ if (metadata?.externalId) {
65
+ throw new Error("methodDetails.metadata.externalId is reserved");
66
+ }
67
+ const expiresAt = challenge.expires
68
+ ? Math.floor(new Date(challenge.expires).getTime() / 1000)
69
+ : Math.floor(Date.now() / 1000) + 3600;
70
+ const spt = await createToken({
71
+ amount,
72
+ challenge,
73
+ currency,
74
+ expiresAt,
75
+ metadata,
76
+ networkId,
77
+ paymentMethod,
78
+ });
79
+ return Credential.serialize({
80
+ challenge,
81
+ payload: {
82
+ spt,
83
+ ...(externalId ? { externalId } : {}),
84
+ },
85
+ });
86
+ },
87
+ }),
88
+ ];
46
89
  }
47
90
  function createPaymentFetch(baseFetch, methods) {
48
91
  const wrapped = (async (input, init) => {