@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.
- package/dist/core/__tests__/payments.test.js +55 -1
- package/dist/core/__tests__/principal.test.js +18 -0
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +37 -0
- package/dist/core/link-cli.d.ts +27 -0
- package/dist/core/link-cli.js +259 -0
- package/dist/core/mpp-client.d.ts +13 -1
- package/dist/core/mpp-client.js +45 -2
- package/dist/core/payments.js +135 -10
- package/dist/core/principal.js +2 -2
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/index.js +6 -4
- package/dist/tools/__tests__/search.test.d.ts +1 -0
- package/dist/tools/__tests__/search.test.js +66 -0
- package/dist/tools/__tests__/wallet.test.js +153 -0
- package/dist/tools/passes.js +1 -1
- package/dist/tools/run.js +1 -1
- package/dist/tools/search.js +33 -0
- package/dist/tools/solve.js +1 -1
- package/dist/tools/wallet.js +195 -7
- package/package.json +1 -1
- package/src/core/__tests__/payments.test.ts +78 -1
- package/src/core/__tests__/principal.test.ts +23 -0
- package/src/core/config.ts +56 -0
- package/src/core/link-cli.ts +300 -0
- package/src/core/mpp-client.ts +69 -2
- package/src/core/payments.ts +153 -11
- package/src/core/principal.ts +2 -2
- package/src/core/version.ts +1 -1
- package/src/index.ts +6 -4
- package/src/tools/__tests__/search.test.ts +78 -0
- package/src/tools/__tests__/wallet.test.ts +190 -0
- package/src/tools/passes.ts +1 -1
- package/src/tools/run.ts +1 -1
- package/src/tools/search.ts +40 -0
- package/src/tools/solve.ts +1 -1
- 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
|
-
|
|
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" },
|
package/dist/core/config.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/core/config.js
CHANGED
|
@@ -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(
|
|
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[];
|
package/dist/core/mpp-client.js
CHANGED
|
@@ -41,8 +41,51 @@ export const Mppx = {
|
|
|
41
41
|
};
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
|
-
export function stripe(
|
|
45
|
-
|
|
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) => {
|