@agent-score/commerce 1.8.1 → 2.0.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 +73 -9
- package/dist/{_response-9yp6Fit2.d.mts → _response-BFYN3b6i.d.mts} +17 -19
- package/dist/{_response-CC6jNb8q.d.ts → _response-_iPD5AIj.d.ts} +17 -19
- package/dist/challenge/index.d.mts +106 -198
- package/dist/challenge/index.d.ts +106 -198
- package/dist/challenge/index.js +238 -111
- package/dist/challenge/index.js.map +1 -1
- package/dist/challenge/index.mjs +238 -111
- package/dist/challenge/index.mjs.map +1 -1
- package/dist/checkout-BoFwnVsj.d.ts +931 -0
- package/dist/checkout-DRbQ0Fsh.d.mts +931 -0
- package/dist/core.d.mts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/core.mjs.map +1 -1
- package/dist/discovery/index.d.mts +453 -51
- package/dist/discovery/index.d.ts +453 -51
- package/dist/discovery/index.js +1092 -58
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +1060 -57
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/identity/express.d.mts +3 -3
- package/dist/identity/express.d.ts +3 -3
- package/dist/identity/express.js +30 -19
- package/dist/identity/express.js.map +1 -1
- package/dist/identity/express.mjs +30 -19
- package/dist/identity/express.mjs.map +1 -1
- package/dist/identity/fastify.d.mts +4 -4
- package/dist/identity/fastify.d.ts +4 -4
- package/dist/identity/fastify.js +30 -19
- package/dist/identity/fastify.js.map +1 -1
- package/dist/identity/fastify.mjs +30 -19
- package/dist/identity/fastify.mjs.map +1 -1
- package/dist/identity/hono.d.mts +3 -3
- package/dist/identity/hono.d.ts +3 -3
- package/dist/identity/hono.js +30 -19
- package/dist/identity/hono.js.map +1 -1
- package/dist/identity/hono.mjs +30 -19
- package/dist/identity/hono.mjs.map +1 -1
- package/dist/identity/nextjs.d.mts +6 -7
- package/dist/identity/nextjs.d.ts +6 -7
- package/dist/identity/nextjs.js +30 -19
- package/dist/identity/nextjs.js.map +1 -1
- package/dist/identity/nextjs.mjs +30 -19
- package/dist/identity/nextjs.mjs.map +1 -1
- package/dist/identity/policy.d.mts +41 -4
- package/dist/identity/policy.d.ts +41 -4
- package/dist/identity/policy.js +3662 -18
- package/dist/identity/policy.js.map +1 -1
- package/dist/identity/policy.mjs +3648 -3
- package/dist/identity/policy.mjs.map +1 -1
- package/dist/identity/web.d.mts +3 -3
- package/dist/identity/web.d.ts +3 -3
- package/dist/identity/web.js +30 -19
- package/dist/identity/web.js.map +1 -1
- package/dist/identity/web.mjs +30 -19
- package/dist/identity/web.mjs.map +1 -1
- package/dist/index.d.mts +72 -329
- package/dist/index.d.ts +72 -329
- package/dist/index.js +3651 -373
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3628 -361
- package/dist/index.mjs.map +1 -1
- package/dist/payment/index.d.mts +256 -265
- package/dist/payment/index.d.ts +256 -265
- package/dist/payment/index.js +586 -149
- package/dist/payment/index.js.map +1 -1
- package/dist/payment/index.mjs +573 -148
- package/dist/payment/index.mjs.map +1 -1
- package/dist/{agent_instructions-DiMSGkdm.d.mts → pricing-CQ9DIFaw.d.ts} +109 -56
- package/dist/{agent_instructions-DiMSGkdm.d.ts → pricing-CxzwyiO6.d.mts} +109 -56
- package/dist/rail_spec-XP0wKgJV.d.mts +132 -0
- package/dist/rail_spec-XP0wKgJV.d.ts +132 -0
- package/dist/{signer-CFVQsWjL.d.mts → signer-3FAit11j.d.mts} +27 -1
- package/dist/{signer-CFVQsWjL.d.ts → signer-3FAit11j.d.ts} +27 -1
- package/dist/solana-Cds87OTu.d.mts +67 -0
- package/dist/solana-Cds87OTu.d.ts +67 -0
- package/dist/stripe-multichain/index.d.mts +55 -66
- package/dist/stripe-multichain/index.d.ts +55 -66
- package/dist/stripe-multichain/index.js +68 -42
- package/dist/stripe-multichain/index.js.map +1 -1
- package/dist/stripe-multichain/index.mjs +68 -41
- package/dist/stripe-multichain/index.mjs.map +1 -1
- package/dist/{wwwauthenticate-CU1eNvMQ.d.mts → wwwauthenticate-D_FMnPgU.d.mts} +9 -10
- package/dist/{wwwauthenticate-CU1eNvMQ.d.ts → wwwauthenticate-D_FMnPgU.d.ts} +9 -10
- package/dist/x402_server-hgQzWQwB.d.mts +81 -0
- package/dist/x402_server-hgQzWQwB.d.ts +81 -0
- package/package.json +9 -7
|
@@ -35,30 +35,36 @@ __export(stripe_multichain_exports, {
|
|
|
35
35
|
createMppxStripe: () => createMppxStripe,
|
|
36
36
|
createMultichainPaymentIntent: () => createMultichainPaymentIntent,
|
|
37
37
|
createPiCache: () => createPiCache,
|
|
38
|
-
getDepositAddress: () => getDepositAddress,
|
|
39
38
|
simulateCryptoDeposit: () => simulateCryptoDeposit,
|
|
40
39
|
simulateDepositIfTestMode: () => simulateDepositIfTestMode
|
|
41
40
|
});
|
|
42
41
|
module.exports = __toCommonJS(stripe_multichain_exports);
|
|
43
42
|
|
|
44
43
|
// src/stripe-multichain/payment_intent.ts
|
|
45
|
-
async function createMultichainPaymentIntent(
|
|
46
|
-
|
|
44
|
+
async function createMultichainPaymentIntent({
|
|
45
|
+
stripe,
|
|
46
|
+
amount,
|
|
47
|
+
currency = "usd",
|
|
48
|
+
networks,
|
|
49
|
+
metadata,
|
|
50
|
+
idempotencyKey
|
|
51
|
+
}) {
|
|
52
|
+
const pi = await stripe.paymentIntents.create(
|
|
47
53
|
{
|
|
48
|
-
amount
|
|
49
|
-
currency
|
|
54
|
+
amount,
|
|
55
|
+
currency,
|
|
50
56
|
payment_method_types: ["crypto"],
|
|
51
57
|
payment_method_data: { type: "crypto" },
|
|
52
58
|
payment_method_options: {
|
|
53
59
|
crypto: {
|
|
54
60
|
mode: "deposit",
|
|
55
|
-
deposit_options: { networks:
|
|
61
|
+
deposit_options: { networks: networks ?? ["tempo", "base", "solana"] }
|
|
56
62
|
}
|
|
57
63
|
},
|
|
58
64
|
confirm: true,
|
|
59
|
-
...
|
|
65
|
+
...metadata ? { metadata } : {}
|
|
60
66
|
},
|
|
61
|
-
|
|
67
|
+
idempotencyKey ? { idempotencyKey } : void 0
|
|
62
68
|
);
|
|
63
69
|
const depositAddresses = {};
|
|
64
70
|
const addrs = pi.next_action?.crypto_display_details?.deposit_addresses ?? {};
|
|
@@ -70,9 +76,6 @@ async function createMultichainPaymentIntent(input) {
|
|
|
70
76
|
}
|
|
71
77
|
return { paymentIntentId: pi.id, depositAddresses };
|
|
72
78
|
}
|
|
73
|
-
function getDepositAddress(result, network) {
|
|
74
|
-
return result.depositAddresses[network];
|
|
75
|
-
}
|
|
76
79
|
|
|
77
80
|
// src/stripe-multichain/simulate_deposit.ts
|
|
78
81
|
var STRIPE_TEST_TX_HASH_SUCCESS = "0x00000000000000000000000000000000000000000000000000000testsuccess";
|
|
@@ -82,57 +85,79 @@ var DEFAULT_BUYER_WALLET = {
|
|
|
82
85
|
tempo: "0x0000000000000000000000000000000000000001",
|
|
83
86
|
solana: "11111111111111111111111111111111"
|
|
84
87
|
};
|
|
85
|
-
async function simulateCryptoDeposit(
|
|
86
|
-
|
|
88
|
+
async function simulateCryptoDeposit({
|
|
89
|
+
paymentIntentId,
|
|
90
|
+
network,
|
|
91
|
+
buyerWallet,
|
|
92
|
+
tokenCurrency,
|
|
93
|
+
transactionHash,
|
|
94
|
+
stripeSecretKey,
|
|
95
|
+
stripeVersion,
|
|
96
|
+
stripeApiBase,
|
|
97
|
+
extra
|
|
98
|
+
}) {
|
|
99
|
+
const url = `${stripeApiBase ?? "https://api.stripe.com"}/v1/test_helpers/payment_intents/${paymentIntentId}/simulate_crypto_deposit`;
|
|
87
100
|
const params = new URLSearchParams({
|
|
88
|
-
network
|
|
89
|
-
buyer_wallet:
|
|
101
|
+
network,
|
|
102
|
+
buyer_wallet: buyerWallet ?? DEFAULT_BUYER_WALLET[network] ?? ""
|
|
90
103
|
});
|
|
91
|
-
if (
|
|
92
|
-
if (
|
|
93
|
-
for (const [k, v] of Object.entries(
|
|
104
|
+
if (tokenCurrency) params.set("token_currency", tokenCurrency);
|
|
105
|
+
if (transactionHash) params.set("transaction_hash", transactionHash);
|
|
106
|
+
for (const [k, v] of Object.entries(extra ?? {})) {
|
|
94
107
|
params.set(k, v);
|
|
95
108
|
}
|
|
96
109
|
const headers = {
|
|
97
|
-
Authorization: `Bearer ${
|
|
110
|
+
Authorization: `Bearer ${stripeSecretKey}`,
|
|
98
111
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
99
112
|
};
|
|
100
|
-
if (
|
|
113
|
+
if (stripeVersion) headers["Stripe-Version"] = stripeVersion;
|
|
101
114
|
const res = await fetch(url, { method: "POST", headers, body: params.toString() });
|
|
102
115
|
if (!res.ok) {
|
|
103
116
|
throw new Error(`Stripe simulate_crypto_deposit failed: ${res.status} ${await res.text()}`);
|
|
104
117
|
}
|
|
105
118
|
}
|
|
106
|
-
async function simulateDepositIfTestMode(
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
async function simulateDepositIfTestMode({
|
|
120
|
+
getPaymentIntentId,
|
|
121
|
+
depositAddress,
|
|
122
|
+
network,
|
|
123
|
+
buyerWallet,
|
|
124
|
+
tokenCurrency,
|
|
125
|
+
stripeSecretKey,
|
|
126
|
+
stripeVersion
|
|
127
|
+
}) {
|
|
128
|
+
if (!stripeSecretKey.startsWith("sk_test_")) return;
|
|
129
|
+
const piId = getPaymentIntentId(depositAddress);
|
|
109
130
|
if (!piId) {
|
|
110
131
|
console.warn(
|
|
111
|
-
`[stripe] Skipping deposit simulation \u2014 no PI cached for deposit address ${
|
|
132
|
+
`[stripe] Skipping deposit simulation \u2014 no PI cached for deposit address ${depositAddress.slice(0, 10)}\u2026 (network=${network}). The PI cache TTL may have expired between 402 emission and settlement.`
|
|
112
133
|
);
|
|
113
134
|
return;
|
|
114
135
|
}
|
|
115
136
|
try {
|
|
116
137
|
await simulateCryptoDeposit({
|
|
117
138
|
paymentIntentId: piId,
|
|
118
|
-
network
|
|
119
|
-
...
|
|
120
|
-
tokenCurrency:
|
|
139
|
+
network,
|
|
140
|
+
...buyerWallet !== void 0 && { buyerWallet },
|
|
141
|
+
tokenCurrency: tokenCurrency ?? "usdc",
|
|
121
142
|
transactionHash: STRIPE_TEST_TX_HASH_SUCCESS,
|
|
122
|
-
stripeSecretKey
|
|
123
|
-
...
|
|
143
|
+
stripeSecretKey,
|
|
144
|
+
...stripeVersion !== void 0 && { stripeVersion }
|
|
124
145
|
});
|
|
125
|
-
console.warn(`[stripe] \u2713 Simulated ${
|
|
146
|
+
console.warn(`[stripe] \u2713 Simulated ${network} deposit for PI ${piId}`);
|
|
126
147
|
} catch (err) {
|
|
127
148
|
console.error(
|
|
128
|
-
`[stripe] \u2717 Failed to simulate ${
|
|
149
|
+
`[stripe] \u2717 Failed to simulate ${network} deposit for PI ${piId}:`,
|
|
129
150
|
err instanceof Error ? err.message : err
|
|
130
151
|
);
|
|
131
152
|
}
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
// src/stripe-multichain/mppx_stripe.ts
|
|
135
|
-
async function createMppxStripe(
|
|
156
|
+
async function createMppxStripe({
|
|
157
|
+
profileId,
|
|
158
|
+
secretKey,
|
|
159
|
+
paymentMethodTypes
|
|
160
|
+
}) {
|
|
136
161
|
const moduleName = "mppx/server";
|
|
137
162
|
const mppx = await import(moduleName).catch(() => null);
|
|
138
163
|
if (!mppx?.stripe?.charge) {
|
|
@@ -141,16 +166,18 @@ async function createMppxStripe(input) {
|
|
|
141
166
|
);
|
|
142
167
|
}
|
|
143
168
|
return mppx.stripe.charge({
|
|
144
|
-
networkId:
|
|
145
|
-
paymentMethodTypes:
|
|
146
|
-
secretKey
|
|
169
|
+
networkId: profileId,
|
|
170
|
+
paymentMethodTypes: paymentMethodTypes ?? ["card", "link"],
|
|
171
|
+
secretKey
|
|
147
172
|
});
|
|
148
173
|
}
|
|
149
174
|
|
|
150
175
|
// src/stripe-multichain/pi-cache.ts
|
|
151
|
-
function createPiCache(
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
function createPiCache({
|
|
177
|
+
redisUrl,
|
|
178
|
+
ttlSeconds = 300,
|
|
179
|
+
keyPrefix = "payto:"
|
|
180
|
+
} = {}) {
|
|
154
181
|
let redis = null;
|
|
155
182
|
const addrMemCache = /* @__PURE__ */ new Map();
|
|
156
183
|
const piCache = /* @__PURE__ */ new Map();
|
|
@@ -169,17 +196,17 @@ function createPiCache(opts = {}) {
|
|
|
169
196
|
}, 6e4);
|
|
170
197
|
if (typeof evict.unref === "function") evict.unref();
|
|
171
198
|
async function getRedis() {
|
|
172
|
-
if (!
|
|
199
|
+
if (!redisUrl) return null;
|
|
173
200
|
if (redis) return redis;
|
|
174
201
|
const mod = await import("ioredis").catch(() => null);
|
|
175
202
|
if (!mod) {
|
|
176
203
|
console.error("[pi-cache] redisUrl set but `ioredis` is not installed. Run `npm install ioredis` or unset redisUrl.");
|
|
177
204
|
return null;
|
|
178
205
|
}
|
|
179
|
-
redis = new mod.default(
|
|
206
|
+
redis = new mod.default(redisUrl, {
|
|
180
207
|
connectTimeout: 5e3,
|
|
181
208
|
maxRetriesPerRequest: 1,
|
|
182
|
-
tls:
|
|
209
|
+
tls: redisUrl.startsWith("rediss://") ? {} : void 0
|
|
183
210
|
});
|
|
184
211
|
redis.on("error", (err) => console.error("[pi-cache] Redis error:", err.message));
|
|
185
212
|
return redis;
|
|
@@ -236,7 +263,6 @@ function createPiCache(opts = {}) {
|
|
|
236
263
|
createMppxStripe,
|
|
237
264
|
createMultichainPaymentIntent,
|
|
238
265
|
createPiCache,
|
|
239
|
-
getDepositAddress,
|
|
240
266
|
simulateCryptoDeposit,
|
|
241
267
|
simulateDepositIfTestMode
|
|
242
268
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stripe-multichain/index.ts","../../src/stripe-multichain/payment_intent.ts","../../src/stripe-multichain/simulate_deposit.ts","../../src/stripe-multichain/mppx_stripe.ts","../../src/stripe-multichain/pi-cache.ts"],"sourcesContent":["export * from './payment_intent';\nexport * from './simulate_deposit';\nexport * from './mppx_stripe';\nexport * from './pi-cache';\n","/**\n * Minimal Stripe client surface — only the methods we use. Vendors pass their actual\n * `Stripe` instance (peer dep on the `stripe` package); this interface keeps the SDK\n * decoupled from any specific Stripe version.\n */\nexport interface StripeClientLike {\n paymentIntents: {\n create(\n params: Record<string, unknown>,\n opts?: { idempotencyKey?: string },\n ): Promise<StripePaymentIntent>;\n };\n}\n\nexport interface StripePaymentIntent {\n id: string;\n next_action?: {\n crypto_display_details?: {\n deposit_addresses?: Record<string, { address?: string } | undefined>;\n };\n } | null;\n [key: string]: unknown;\n}\n\nexport interface CreateMultichainPaymentIntentInput {\n /** A configured Stripe SDK instance. */\n stripe: StripeClientLike;\n /** Amount in cents (Stripe convention — $1.00 = 100). */\n amount: number;\n /** Currency code. Default 'usd'. */\n currency?: string;\n /** Networks to advertise to Stripe deposit_options. Default ['tempo', 'base', 'solana']. */\n networks?: string[];\n /** Metadata to attach to the PI (visible in Stripe dashboard). */\n metadata?: Record<string, string>;\n /** Idempotency key — agent retries of the same purchase won't create duplicate PIs. */\n idempotencyKey?: string;\n}\n\nexport interface MultichainPaymentIntentResult {\n /** Stripe PaymentIntent ID. */\n paymentIntentId: string;\n /** Map of network name → on-chain deposit address. e.g., { tempo: '0x...', base: '0x...', solana: '...' }. */\n depositAddresses: Record<string, string>;\n}\n\n/**\n * Create a Stripe PaymentIntent with `deposit_options.networks` set to multiple chains,\n * returning the PI id + deposit addresses per network. The agent sends funds to the\n * address on whichever chain they prefer (via x402 or MPP), and Stripe auto-captures\n * the PI when funds land.\n *\n * This is the canonical path for the multi-chain x402 + Tempo flow.\n * Distinct from the Stripe SPT (Shared Payment Token) flow, which is handled via\n * `createMppxStripe` + the agent's own Stripe account or `link-cli`.\n */\nexport async function createMultichainPaymentIntent(\n input: CreateMultichainPaymentIntentInput,\n): Promise<MultichainPaymentIntentResult> {\n const pi = await input.stripe.paymentIntents.create(\n {\n amount: input.amount,\n currency: input.currency ?? 'usd',\n payment_method_types: ['crypto'],\n payment_method_data: { type: 'crypto' },\n payment_method_options: {\n crypto: {\n mode: 'deposit',\n deposit_options: { networks: input.networks ?? ['tempo', 'base', 'solana'] },\n },\n },\n confirm: true,\n ...(input.metadata ? { metadata: input.metadata } : {}),\n },\n input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : undefined,\n );\n\n const depositAddresses: Record<string, string> = {};\n const addrs = pi.next_action?.crypto_display_details?.deposit_addresses ?? {};\n for (const [network, info] of Object.entries(addrs)) {\n if (info?.address) depositAddresses[network] = info.address;\n }\n\n if (Object.keys(depositAddresses).length === 0) {\n throw new Error('No deposit addresses returned from Stripe PaymentIntent');\n }\n\n return { paymentIntentId: pi.id, depositAddresses };\n}\n\n/**\n * Convenience accessor: get the deposit address for a specific network from a\n * createMultichainPaymentIntent result. Returns undefined if Stripe didn't issue\n * an address for that network.\n */\nexport function getDepositAddress(\n result: MultichainPaymentIntentResult,\n network: string,\n): string | undefined {\n return result.depositAddresses[network];\n}\n","/**\n * Stripe's documented magic test_helpers transaction hash that resolves the\n * PaymentIntent to `succeeded` within 15 seconds. Same value across all networks —\n * Stripe normalizes the format internally. Anything else (including network-shaped\n * placeholder bytes) is rejected with \"not a valid testmode transaction hash\".\n *\n * See: https://docs.stripe.com/payments/deposit-mode-stablecoin-payments\n */\nexport const STRIPE_TEST_TX_HASH_SUCCESS =\n '0x00000000000000000000000000000000000000000000000000000testsuccess';\n\n/**\n * Stripe's documented magic test_helpers transaction hash that fails the charge\n * (PaymentIntent returns to `requires_payment_method` within 15 seconds).\n */\nexport const STRIPE_TEST_TX_HASH_FAILED =\n '0x000000000000000000000000000000000000000000000000000000testfailed';\n\nexport interface SimulateCryptoDepositInput {\n /** Stripe PaymentIntent id to simulate a deposit on. */\n paymentIntentId: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet address. Defaults to a sensible placeholder per network. */\n buyerWallet?: string;\n /** Token currency (e.g., 'usdc'). Optional — passed as a form param if set. */\n tokenCurrency?: string;\n /** Simulated transaction hash. Optional — passed as a form param if set. */\n transactionHash?: string;\n /** Stripe secret key (for the test-helpers Authorization header). Must be a `sk_test_...` key. */\n stripeSecretKey: string;\n /** Stripe API version to request via the `Stripe-Version` header. Useful for preview APIs. */\n stripeVersion?: string;\n /** Override the Stripe API base URL. Default 'https://api.stripe.com'. */\n stripeApiBase?: string;\n /** Arbitrary additional form params to merge into the request body. */\n extra?: Record<string, string>;\n}\n\nconst DEFAULT_BUYER_WALLET: Record<string, string> = {\n base: '0x0000000000000000000000000000000000000001',\n tempo: '0x0000000000000000000000000000000000000001',\n solana: '11111111111111111111111111111111',\n};\n\n/**\n * Call Stripe's `test_helpers/payment_intents/{id}/simulate_crypto_deposit` endpoint. Used\n * in testnet/dev to simulate a deposit landing on a PaymentIntent so the integration\n * end-to-end can be exercised without on-chain transfers.\n *\n * Throws on non-2xx responses (returns Stripe's error body in the message).\n */\nexport async function simulateCryptoDeposit(input: SimulateCryptoDepositInput): Promise<void> {\n const url = `${input.stripeApiBase ?? 'https://api.stripe.com'}/v1/test_helpers/payment_intents/${input.paymentIntentId}/simulate_crypto_deposit`;\n const params = new URLSearchParams({\n network: input.network,\n buyer_wallet: input.buyerWallet ?? DEFAULT_BUYER_WALLET[input.network] ?? '',\n });\n if (input.tokenCurrency) params.set('token_currency', input.tokenCurrency);\n if (input.transactionHash) params.set('transaction_hash', input.transactionHash);\n for (const [k, v] of Object.entries(input.extra ?? {})) {\n params.set(k, v);\n }\n const headers: Record<string, string> = {\n Authorization: `Bearer ${input.stripeSecretKey}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (input.stripeVersion) headers['Stripe-Version'] = input.stripeVersion;\n const res = await fetch(url, { method: 'POST', headers, body: params.toString() });\n if (!res.ok) {\n throw new Error(`Stripe simulate_crypto_deposit failed: ${res.status} ${await res.text()}`);\n }\n}\n\nexport interface SimulateDepositIfTestModeInput {\n /** Stripe PaymentIntent id resolver — given a deposit address, return the PI id (or undefined\n * if the cache TTL expired between 402 emit and settlement). Typically `cache.getPaymentIntentId`. */\n getPaymentIntentId: (depositAddress: string) => string | undefined;\n /** The deposit address that was paid to (recipient). */\n depositAddress: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet (defaults per network in `simulateCryptoDeposit`). */\n buyerWallet?: string;\n /** Token currency to pass through to Stripe (typically `'usdc'`). */\n tokenCurrency?: string;\n /** Stripe secret key. The wrapper checks this starts with `sk_test_` and skips otherwise. */\n stripeSecretKey: string;\n /** Stripe API version (e.g. `'2026-03-04.preview'` for the deposit-mode preview). */\n stripeVersion?: string;\n}\n\n/**\n * Higher-level wrapper around {@link simulateCryptoDeposit} for the testnet/dev path.\n * Bundles the three steps every Stripe-multichain merchant repeats:\n *\n * 1. Gate on `sk_test_` key prefix — production keys reject the test_helpers endpoint\n * with 400; live deposits reach Stripe's real crypto-deposit watcher instead.\n * 2. Resolve the PaymentIntent id from the deposit address (cache lookup).\n * 3. Call `simulate_crypto_deposit` with Stripe's documented success magic hash.\n *\n * Logs `[stripe] ✓ Simulated <network> deposit for PI <id>` on success and\n * `[stripe] ✗ Failed to simulate <network> deposit for PI <id>: <err>` on failure.\n * Errors are caught + logged (never thrown) so a sim hiccup doesn't fail the order.\n *\n * Use case is exclusively dev/testnet end-to-end — production servers (sk_live_) no-op.\n */\nexport async function simulateDepositIfTestMode(input: SimulateDepositIfTestModeInput): Promise<void> {\n if (!input.stripeSecretKey.startsWith('sk_test_')) return;\n const piId = input.getPaymentIntentId(input.depositAddress);\n if (!piId) {\n console.warn(\n `[stripe] Skipping deposit simulation — no PI cached for deposit address ${input.depositAddress.slice(0, 10)}… (network=${input.network}). The PI cache TTL may have expired between 402 emission and settlement.`,\n );\n return;\n }\n try {\n await simulateCryptoDeposit({\n paymentIntentId: piId,\n network: input.network,\n ...(input.buyerWallet !== undefined && { buyerWallet: input.buyerWallet }),\n tokenCurrency: input.tokenCurrency ?? 'usdc',\n transactionHash: STRIPE_TEST_TX_HASH_SUCCESS,\n stripeSecretKey: input.stripeSecretKey,\n ...(input.stripeVersion !== undefined && { stripeVersion: input.stripeVersion }),\n });\n console.warn(`[stripe] ✓ Simulated ${input.network} deposit for PI ${piId}`);\n } catch (err) {\n console.error(\n `[stripe] ✗ Failed to simulate ${input.network} deposit for PI ${piId}:`,\n err instanceof Error ? err.message : err,\n );\n }\n}\n","export interface CreateMppxStripeInput {\n /** Stripe profile_id / network_id (the value advertised in your `stripe/charge` accepted_methods entry). */\n profileId: string;\n /** Stripe secret key — mppx uses it to validate inbound SharedPaymentTokens. */\n secretKey: string;\n /** Payment method types this stripe rail accepts. Default ['card', 'link']. */\n paymentMethodTypes?: string[];\n}\n\n/**\n * Wraps the `mppStripe.charge(...)` boilerplate from `mppx/server`. Returns the value\n * vendors pass into `Mppx.create({ methods: [...] })`. mppx is an OPTIONAL peer dependency —\n * vendors who don't use Stripe SPT don't need to install it.\n *\n * Example:\n *\n * import { Mppx, tempo } from 'mppx/server';\n * import { createMppxStripe } from '@agent-score/commerce/stripe-multichain';\n *\n * const stripeMethod = await createMppxStripe({\n * profileId: process.env.STRIPE_PROFILE_ID!,\n * secretKey: process.env.STRIPE_SECRET_KEY!,\n * });\n *\n * const mppx = Mppx.create({\n * methods: [tempo.charge({...}), stripeMethod],\n * secretKey: process.env.MPP_SECRET_KEY!,\n * });\n *\n * Throws if mppx is not installed.\n */\nexport async function createMppxStripe(input: CreateMppxStripeInput): Promise<unknown> {\n const moduleName = 'mppx/server';\n const mppx = (await import(moduleName).catch(() => null)) as {\n stripe?: {\n charge: (config: {\n networkId: string;\n paymentMethodTypes?: string[];\n secretKey: string;\n }) => unknown;\n };\n } | null;\n /* v8 ignore start -- peer-dep-absence guard; mppx is installed in the test env so this branch can't be exercised without mocking the dynamic import */\n if (!mppx?.stripe?.charge) {\n throw new Error(\n 'mppx not installed — install with `npm install mppx` to use createMppxStripe.',\n );\n }\n /* v8 ignore stop */\n return mppx.stripe.charge({\n networkId: input.profileId,\n paymentMethodTypes: input.paymentMethodTypes ?? ['card', 'link'],\n secretKey: input.secretKey,\n });\n}\n","/**\n * Stripe PaymentIntent + deposit-address cache.\n *\n * Stripe-multichain merchants need three lookups during a request lifecycle:\n *\n * 1. **Is this on-chain `pay_to` address one we minted?** — when an MPP credential\n * arrives with a `recipient`, verify it matches a recently-minted Stripe deposit\n * address. Validates the credential's deposit address against the addresses the\n * merchant has actually minted.\n *\n * 2. **Which PaymentIntent owns this deposit address?** — when settling, the\n * `simulate_crypto_deposit` test_helpers call needs the PaymentIntent id for the\n * deposit address that was paid to.\n *\n * 3. **Which sibling deposit addresses belong to the same PaymentIntent?** — when\n * enriching a 402 with x402 entries, the merchant needs the Base + Solana addresses\n * Stripe minted alongside the original Tempo address (one PI carries up to three).\n *\n * All three are TTL-bounded (default 300s — long enough for an agent to retry, short\n * enough to bound memory). Backed by Redis when `redisUrl` is set, falls back to\n * in-process Map otherwise. Single-instance servers can use the in-memory cache;\n * multi-instance deployments need a shared cache (Redis) so a deposit lands on\n * whichever instance settles it.\n */\n\n// ioredis is an optional peer dep — typed structurally to avoid pulling its types into\n// the build for merchants that run in-process without Redis. The structural type covers\n// only the methods we call (set with EX/get/on); merchants using Redis install ioredis\n// themselves.\ninterface RedisLike {\n set: (key: string, value: string, mode: 'EX', ttl: number) => Promise<unknown>;\n get: (key: string) => Promise<string | null>;\n on: (event: 'error', cb: (err: Error) => void) => unknown;\n}\n\nexport interface PiCacheOptions {\n /** Redis connection URL (e.g. `rediss://…cache.amazonaws.com:6379`). When omitted,\n * the cache falls back to in-process Maps with the same API. */\n redisUrl?: string;\n /** TTL for cached entries in seconds. Default 300. */\n ttlSeconds?: number;\n /** Prefix for Redis keys. Default `'payto:'`. */\n keyPrefix?: string;\n}\n\nexport interface PiCache {\n /** Mark an on-chain address as one this merchant minted. Idempotent + TTL-bounded. */\n cacheAddress(address: string): Promise<void>;\n /** Return true when the address was minted by this merchant within TTL. */\n hasAddress(address: string): Promise<boolean>;\n /** Associate an on-chain deposit address with the Stripe PaymentIntent that minted it. */\n cachePaymentIntent(depositAddress: string, paymentIntentId: string): void;\n /** Get the Stripe PaymentIntent id for a previously-minted deposit address, or undefined. */\n getPaymentIntentId(depositAddress: string): string | undefined;\n /** Associate a PaymentIntent id with the full set of sibling deposit addresses (one per network). */\n cacheNetworkAddresses(paymentIntentId: string, addresses: Record<string, string>): void;\n /** Look up the deposit address Stripe minted on a specific network for a given PaymentIntent. */\n getNetworkDepositAddress(paymentIntentId: string, network: string): string | undefined;\n /** Stop the background TTL-eviction loop. Call from server shutdown handlers. */\n stop(): void;\n}\n\ninterface Entry<T> { value: T; expiresAt: number }\n\nexport function createPiCache(opts: PiCacheOptions = {}): PiCache {\n const ttlSeconds = opts.ttlSeconds ?? 300;\n const keyPrefix = opts.keyPrefix ?? 'payto:';\n\n let redis: RedisLike | null = null;\n const addrMemCache = new Map<string, number>();\n const piCache = new Map<string, Entry<string>>();\n const networkAddressCache = new Map<string, Entry<Record<string, string>>>();\n\n const evict = setInterval(() => {\n const now = Date.now();\n for (const [k, v] of piCache) { if (v.expiresAt < now) piCache.delete(k); }\n for (const [k, v] of networkAddressCache) { if (v.expiresAt < now) networkAddressCache.delete(k); }\n for (const [k, expires] of addrMemCache) { if (expires < now) addrMemCache.delete(k); }\n }, 60_000);\n // Don't keep the event loop alive on test shutdown / one-shot scripts.\n if (typeof evict.unref === 'function') evict.unref();\n\n async function getRedis(): Promise<RedisLike | null> {\n if (!opts.redisUrl) return null;\n if (redis) return redis;\n // Dynamic import keeps ioredis as an optional peer dep — merchants without\n // Redis don't pay the install cost.\n const mod = await import('ioredis' as string).catch(() => null) as\n | { default: new (url: string, opts: unknown) => RedisLike }\n | null;\n if (!mod) {\n console.error('[pi-cache] redisUrl set but `ioredis` is not installed. Run `npm install ioredis` or unset redisUrl.');\n return null;\n }\n redis = new mod.default(opts.redisUrl, {\n connectTimeout: 5000,\n maxRetriesPerRequest: 1,\n tls: opts.redisUrl.startsWith('rediss://') ? {} : undefined,\n });\n redis.on('error', (err: Error) => console.error('[pi-cache] Redis error:', err.message));\n return redis;\n }\n\n return {\n async cacheAddress(address) {\n const r = await getRedis();\n if (r) await r.set(`${keyPrefix}${address}`, '1', 'EX', ttlSeconds).catch(() => {});\n addrMemCache.set(address, Date.now() + ttlSeconds * 1000);\n },\n async hasAddress(address) {\n const r = await getRedis();\n if (r) {\n const val = await r.get(`${keyPrefix}${address}`).catch(() => null);\n if (val) return true;\n }\n const expiry = addrMemCache.get(address);\n return !!expiry && expiry > Date.now();\n },\n cachePaymentIntent(depositAddress, paymentIntentId) {\n piCache.set(depositAddress, { value: paymentIntentId, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getPaymentIntentId(depositAddress) {\n const entry = piCache.get(depositAddress);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { piCache.delete(depositAddress); return undefined; }\n return entry.value;\n },\n cacheNetworkAddresses(paymentIntentId, addresses) {\n networkAddressCache.set(paymentIntentId, { value: addresses, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getNetworkDepositAddress(paymentIntentId, network) {\n const entry = networkAddressCache.get(paymentIntentId);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { networkAddressCache.delete(paymentIntentId); return undefined; }\n return entry.value[network];\n },\n stop() {\n clearInterval(evict);\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/stripe-multichain/index.ts","../../src/stripe-multichain/payment_intent.ts","../../src/stripe-multichain/simulate_deposit.ts","../../src/stripe-multichain/mppx_stripe.ts","../../src/stripe-multichain/pi-cache.ts"],"sourcesContent":["export * from './payment_intent';\nexport * from './simulate_deposit';\nexport * from './mppx_stripe';\nexport * from './pi-cache';\n","/**\n * Minimal Stripe client surface — only the methods we use. Vendors pass their actual\n * `Stripe` instance (peer dep on the `stripe` package); this interface keeps the SDK\n * decoupled from any specific Stripe version.\n */\nexport interface StripeClientLike {\n paymentIntents: {\n create(\n params: Record<string, unknown>,\n opts?: { idempotencyKey?: string },\n ): Promise<StripePaymentIntent>;\n };\n}\n\nexport interface StripePaymentIntent {\n id: string;\n next_action?: {\n crypto_display_details?: {\n deposit_addresses?: Record<string, { address?: string } | undefined>;\n };\n } | null;\n [key: string]: unknown;\n}\n\nexport interface MultichainPaymentIntentResult {\n /** Stripe PaymentIntent ID. */\n paymentIntentId: string;\n /** Map of network name → on-chain deposit address. e.g., { tempo: '0x...', base: '0x...', solana: '...' }. */\n depositAddresses: Record<string, string>;\n}\n\n/**\n * Create a Stripe PaymentIntent with `deposit_options.networks` set to multiple chains,\n * returning the PI id + deposit addresses per network. The agent sends funds to the\n * address on whichever chain they prefer (via x402 or MPP), and Stripe auto-captures\n * the PI when funds land.\n *\n * This is the canonical path for the multi-chain x402 + Tempo flow.\n * Distinct from the Stripe SPT (Shared Payment Token) flow, which is handled via\n * `createMppxStripe` + the agent's own Stripe account or `link-cli`.\n */\nexport async function createMultichainPaymentIntent({\n stripe,\n amount,\n currency = 'usd',\n networks,\n metadata,\n idempotencyKey,\n}: {\n /** A configured Stripe SDK instance. */\n stripe: StripeClientLike;\n /** Amount in cents (Stripe convention — $1.00 = 100). */\n amount: number;\n /** Currency code. Default 'usd'. */\n currency?: string;\n /** Networks to advertise to Stripe deposit_options. Default ['tempo', 'base', 'solana']. */\n networks?: string[];\n /** Metadata to attach to the PI (visible in Stripe dashboard). */\n metadata?: Record<string, string>;\n /** Idempotency key — agent retries of the same purchase won't create duplicate PIs. */\n idempotencyKey?: string;\n}): Promise<MultichainPaymentIntentResult> {\n const pi = await stripe.paymentIntents.create(\n {\n amount,\n currency,\n payment_method_types: ['crypto'],\n payment_method_data: { type: 'crypto' },\n payment_method_options: {\n crypto: {\n mode: 'deposit',\n deposit_options: { networks: networks ?? ['tempo', 'base', 'solana'] },\n },\n },\n confirm: true,\n ...(metadata ? { metadata } : {}),\n },\n idempotencyKey ? { idempotencyKey } : undefined,\n );\n\n const depositAddresses: Record<string, string> = {};\n const addrs = pi.next_action?.crypto_display_details?.deposit_addresses ?? {};\n for (const [network, info] of Object.entries(addrs)) {\n if (info?.address) depositAddresses[network] = info.address;\n }\n\n if (Object.keys(depositAddresses).length === 0) {\n throw new Error('No deposit addresses returned from Stripe PaymentIntent');\n }\n\n return { paymentIntentId: pi.id, depositAddresses };\n}\n","/**\n * Stripe's documented magic test_helpers transaction hash that resolves the\n * PaymentIntent to `succeeded` within 15 seconds. Same value across all networks —\n * Stripe normalizes the format internally. Anything else (including network-shaped\n * placeholder bytes) is rejected with \"not a valid testmode transaction hash\".\n *\n * See: https://docs.stripe.com/payments/deposit-mode-stablecoin-payments\n */\nexport const STRIPE_TEST_TX_HASH_SUCCESS =\n '0x00000000000000000000000000000000000000000000000000000testsuccess';\n\n/**\n * Stripe's documented magic test_helpers transaction hash that fails the charge\n * (PaymentIntent returns to `requires_payment_method` within 15 seconds).\n */\nexport const STRIPE_TEST_TX_HASH_FAILED =\n '0x000000000000000000000000000000000000000000000000000000testfailed';\n\nconst DEFAULT_BUYER_WALLET: Record<string, string> = {\n base: '0x0000000000000000000000000000000000000001',\n tempo: '0x0000000000000000000000000000000000000001',\n solana: '11111111111111111111111111111111',\n};\n\n/**\n * Call Stripe's `test_helpers/payment_intents/{id}/simulate_crypto_deposit` endpoint. Used\n * in testnet/dev to simulate a deposit landing on a PaymentIntent so the integration\n * end-to-end can be exercised without on-chain transfers.\n *\n * Throws on non-2xx responses (returns Stripe's error body in the message).\n */\nexport async function simulateCryptoDeposit({\n paymentIntentId,\n network,\n buyerWallet,\n tokenCurrency,\n transactionHash,\n stripeSecretKey,\n stripeVersion,\n stripeApiBase,\n extra,\n}: {\n /** Stripe PaymentIntent id to simulate a deposit on. */\n paymentIntentId: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet address. Defaults to a sensible placeholder per network. */\n buyerWallet?: string;\n /** Token currency (e.g., 'usdc'). Optional — passed as a form param if set. */\n tokenCurrency?: string;\n /** Simulated transaction hash. Optional — passed as a form param if set. */\n transactionHash?: string;\n /** Stripe secret key (for the test-helpers Authorization header). Must be a `sk_test_...` key. */\n stripeSecretKey: string;\n /** Stripe API version to request via the `Stripe-Version` header. Useful for preview APIs. */\n stripeVersion?: string;\n /** Override the Stripe API base URL. Default 'https://api.stripe.com'. */\n stripeApiBase?: string;\n /** Arbitrary additional form params to merge into the request body. */\n extra?: Record<string, string>;\n}): Promise<void> {\n const url = `${stripeApiBase ?? 'https://api.stripe.com'}/v1/test_helpers/payment_intents/${paymentIntentId}/simulate_crypto_deposit`;\n const params = new URLSearchParams({\n network,\n buyer_wallet: buyerWallet ?? DEFAULT_BUYER_WALLET[network] ?? '',\n });\n if (tokenCurrency) params.set('token_currency', tokenCurrency);\n if (transactionHash) params.set('transaction_hash', transactionHash);\n for (const [k, v] of Object.entries(extra ?? {})) {\n params.set(k, v);\n }\n const headers: Record<string, string> = {\n Authorization: `Bearer ${stripeSecretKey}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (stripeVersion) headers['Stripe-Version'] = stripeVersion;\n const res = await fetch(url, { method: 'POST', headers, body: params.toString() });\n if (!res.ok) {\n throw new Error(`Stripe simulate_crypto_deposit failed: ${res.status} ${await res.text()}`);\n }\n}\n\n/**\n * Higher-level wrapper around {@link simulateCryptoDeposit} for the testnet/dev path.\n * Bundles the three steps every Stripe-multichain merchant repeats:\n *\n * 1. Gate on `sk_test_` key prefix — production keys reject the test_helpers endpoint\n * with 400; live deposits reach Stripe's real crypto-deposit watcher instead.\n * 2. Resolve the PaymentIntent id from the deposit address (cache lookup).\n * 3. Call `simulate_crypto_deposit` with Stripe's documented success magic hash.\n *\n * Logs `[stripe] ✓ Simulated <network> deposit for PI <id>` on success and\n * `[stripe] ✗ Failed to simulate <network> deposit for PI <id>: <err>` on failure.\n * Errors are caught + logged (never thrown) so a sim hiccup doesn't fail the order.\n *\n * Use case is exclusively dev/testnet end-to-end — production servers (sk_live_) no-op.\n */\nexport async function simulateDepositIfTestMode({\n getPaymentIntentId,\n depositAddress,\n network,\n buyerWallet,\n tokenCurrency,\n stripeSecretKey,\n stripeVersion,\n}: {\n /** Stripe PaymentIntent id resolver — given a deposit address, return the PI id (or undefined\n * if the cache TTL expired between 402 emit and settlement). Typically `cache.getPaymentIntentId`. */\n getPaymentIntentId: (depositAddress: string) => string | undefined;\n /** The deposit address that was paid to (recipient). */\n depositAddress: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet (defaults per network in `simulateCryptoDeposit`). */\n buyerWallet?: string;\n /** Token currency to pass through to Stripe (typically `'usdc'`). */\n tokenCurrency?: string;\n /** Stripe secret key. The wrapper checks this starts with `sk_test_` and skips otherwise. */\n stripeSecretKey: string;\n /** Stripe API version (e.g. `'2026-03-04.preview'` for the deposit-mode preview). */\n stripeVersion?: string;\n}): Promise<void> {\n if (!stripeSecretKey.startsWith('sk_test_')) return;\n const piId = getPaymentIntentId(depositAddress);\n if (!piId) {\n console.warn(\n `[stripe] Skipping deposit simulation — no PI cached for deposit address ${depositAddress.slice(0, 10)}… (network=${network}). The PI cache TTL may have expired between 402 emission and settlement.`,\n );\n return;\n }\n try {\n await simulateCryptoDeposit({\n paymentIntentId: piId,\n network,\n ...(buyerWallet !== undefined && { buyerWallet }),\n tokenCurrency: tokenCurrency ?? 'usdc',\n transactionHash: STRIPE_TEST_TX_HASH_SUCCESS,\n stripeSecretKey,\n ...(stripeVersion !== undefined && { stripeVersion }),\n });\n console.warn(`[stripe] ✓ Simulated ${network} deposit for PI ${piId}`);\n } catch (err) {\n console.error(\n `[stripe] ✗ Failed to simulate ${network} deposit for PI ${piId}:`,\n err instanceof Error ? err.message : err,\n );\n }\n}\n","/**\n * Wraps the `mppStripe.charge(...)` boilerplate from `mppx/server`. Returns the value\n * vendors pass into `Mppx.create({ methods: [...] })`. mppx is an OPTIONAL peer dependency —\n * vendors who don't use Stripe SPT don't need to install it.\n *\n * Example:\n *\n * import { Mppx, tempo } from 'mppx/server';\n * import { createMppxStripe } from '@agent-score/commerce/stripe-multichain';\n *\n * const stripeMethod = await createMppxStripe({\n * profileId: process.env.STRIPE_PROFILE_ID!,\n * secretKey: process.env.STRIPE_SECRET_KEY!,\n * });\n *\n * const mppx = Mppx.create({\n * methods: [tempo.charge({...}), stripeMethod],\n * secretKey: process.env.MPP_SECRET_KEY!,\n * });\n *\n * Throws if mppx is not installed.\n */\nexport async function createMppxStripe({\n profileId,\n secretKey,\n paymentMethodTypes,\n}: {\n /** Stripe profile_id / network_id (the value advertised in your `stripe/charge` accepted_methods entry). */\n profileId: string;\n /** Stripe secret key — mppx uses it to validate inbound SharedPaymentTokens. */\n secretKey: string;\n /** Payment method types this stripe rail accepts. Default ['card', 'link']. */\n paymentMethodTypes?: string[];\n}): Promise<unknown> {\n const moduleName = 'mppx/server';\n const mppx = (await import(moduleName).catch(() => null)) as {\n stripe?: {\n charge: (config: {\n networkId: string;\n paymentMethodTypes?: string[];\n secretKey: string;\n }) => unknown;\n };\n } | null;\n /* v8 ignore start -- peer-dep-absence guard; mppx is installed in the test env so this branch can't be exercised without mocking the dynamic import */\n if (!mppx?.stripe?.charge) {\n throw new Error(\n 'mppx not installed — install with `npm install mppx` to use createMppxStripe.',\n );\n }\n /* v8 ignore stop */\n return mppx.stripe.charge({\n networkId: profileId,\n paymentMethodTypes: paymentMethodTypes ?? ['card', 'link'],\n secretKey,\n });\n}\n","/**\n * Stripe PaymentIntent + deposit-address cache.\n *\n * Stripe-multichain merchants need three lookups during a request lifecycle:\n *\n * 1. **Is this on-chain `pay_to` address one we minted?** — when an MPP credential\n * arrives with a `recipient`, verify it matches a recently-minted Stripe deposit\n * address. Validates the credential's deposit address against the addresses the\n * merchant has actually minted.\n *\n * 2. **Which PaymentIntent owns this deposit address?** — when settling, the\n * `simulate_crypto_deposit` test_helpers call needs the PaymentIntent id for the\n * deposit address that was paid to.\n *\n * 3. **Which sibling deposit addresses belong to the same PaymentIntent?** — when\n * enriching a 402 with x402 entries, the merchant needs the Base + Solana addresses\n * Stripe minted alongside the original Tempo address (one PI carries up to three).\n *\n * All three are TTL-bounded (default 300s — long enough for an agent to retry, short\n * enough to bound memory). Backed by Redis when `redisUrl` is set, falls back to\n * in-process Map otherwise. Single-instance servers can use the in-memory cache;\n * multi-instance deployments need a shared cache (Redis) so a deposit lands on\n * whichever instance settles it.\n */\n\n// ioredis is an optional peer dep — typed structurally to avoid pulling its types into\n// the build for merchants that run in-process without Redis. The structural type covers\n// only the methods we call (set with EX/get/on); merchants using Redis install ioredis\n// themselves.\ninterface RedisLike {\n set: (key: string, value: string, mode: 'EX', ttl: number) => Promise<unknown>;\n get: (key: string) => Promise<string | null>;\n on: (event: 'error', cb: (err: Error) => void) => unknown;\n}\n\n\nexport interface PiCache {\n /** Mark an on-chain address as one this merchant minted. Idempotent + TTL-bounded. */\n cacheAddress(address: string): Promise<void>;\n /** Return true when the address was minted by this merchant within TTL. */\n hasAddress(address: string): Promise<boolean>;\n /** Associate an on-chain deposit address with the Stripe PaymentIntent that minted it. */\n cachePaymentIntent(depositAddress: string, paymentIntentId: string): void;\n /** Get the Stripe PaymentIntent id for a previously-minted deposit address, or undefined. */\n getPaymentIntentId(depositAddress: string): string | undefined;\n /** Associate a PaymentIntent id with the full set of sibling deposit addresses (one per network). */\n cacheNetworkAddresses(paymentIntentId: string, addresses: Record<string, string>): void;\n /** Look up the deposit address Stripe minted on a specific network for a given PaymentIntent. */\n getNetworkDepositAddress(paymentIntentId: string, network: string): string | undefined;\n /** Stop the background TTL-eviction loop. Call from server shutdown handlers. */\n stop(): void;\n}\n\ninterface Entry<T> { value: T; expiresAt: number }\n\nexport function createPiCache({\n redisUrl,\n ttlSeconds = 300,\n keyPrefix = 'payto:',\n}: {\n /** Redis connection URL (e.g. `rediss://…cache.amazonaws.com:6379`). When omitted,\n * the cache falls back to in-process Maps with the same API. */\n redisUrl?: string;\n /** TTL for cached entries in seconds. Default 300. */\n ttlSeconds?: number;\n /** Prefix for Redis keys. Default `'payto:'`. */\n keyPrefix?: string;\n} = {}): PiCache {\n\n let redis: RedisLike | null = null;\n const addrMemCache = new Map<string, number>();\n const piCache = new Map<string, Entry<string>>();\n const networkAddressCache = new Map<string, Entry<Record<string, string>>>();\n\n const evict = setInterval(() => {\n const now = Date.now();\n for (const [k, v] of piCache) { if (v.expiresAt < now) piCache.delete(k); }\n for (const [k, v] of networkAddressCache) { if (v.expiresAt < now) networkAddressCache.delete(k); }\n for (const [k, expires] of addrMemCache) { if (expires < now) addrMemCache.delete(k); }\n }, 60_000);\n // Don't keep the event loop alive on test shutdown / one-shot scripts.\n if (typeof evict.unref === 'function') evict.unref();\n\n async function getRedis(): Promise<RedisLike | null> {\n if (!redisUrl) return null;\n if (redis) return redis;\n // Dynamic import keeps ioredis as an optional peer dep — merchants without\n // Redis don't pay the install cost.\n const mod = await import('ioredis' as string).catch(() => null) as\n | { default: new (url: string, opts: unknown) => RedisLike }\n | null;\n if (!mod) {\n console.error('[pi-cache] redisUrl set but `ioredis` is not installed. Run `npm install ioredis` or unset redisUrl.');\n return null;\n }\n redis = new mod.default(redisUrl, {\n connectTimeout: 5000,\n maxRetriesPerRequest: 1,\n tls: redisUrl.startsWith('rediss://') ? {} : undefined,\n });\n redis.on('error', (err: Error) => console.error('[pi-cache] Redis error:', err.message));\n return redis;\n }\n\n return {\n async cacheAddress(address) {\n const r = await getRedis();\n if (r) await r.set(`${keyPrefix}${address}`, '1', 'EX', ttlSeconds).catch(() => {});\n addrMemCache.set(address, Date.now() + ttlSeconds * 1000);\n },\n async hasAddress(address) {\n const r = await getRedis();\n if (r) {\n const val = await r.get(`${keyPrefix}${address}`).catch(() => null);\n if (val) return true;\n }\n const expiry = addrMemCache.get(address);\n return !!expiry && expiry > Date.now();\n },\n cachePaymentIntent(depositAddress, paymentIntentId) {\n piCache.set(depositAddress, { value: paymentIntentId, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getPaymentIntentId(depositAddress) {\n const entry = piCache.get(depositAddress);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { piCache.delete(depositAddress); return undefined; }\n return entry.value;\n },\n cacheNetworkAddresses(paymentIntentId, addresses) {\n networkAddressCache.set(paymentIntentId, { value: addresses, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getNetworkDepositAddress(paymentIntentId, network) {\n const entry = networkAddressCache.get(paymentIntentId);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { networkAddressCache.delete(paymentIntentId); return undefined; }\n return entry.value[network];\n },\n stop() {\n clearInterval(evict);\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
// src/stripe-multichain/payment_intent.ts
|
|
2
|
-
async function createMultichainPaymentIntent(
|
|
3
|
-
|
|
2
|
+
async function createMultichainPaymentIntent({
|
|
3
|
+
stripe,
|
|
4
|
+
amount,
|
|
5
|
+
currency = "usd",
|
|
6
|
+
networks,
|
|
7
|
+
metadata,
|
|
8
|
+
idempotencyKey
|
|
9
|
+
}) {
|
|
10
|
+
const pi = await stripe.paymentIntents.create(
|
|
4
11
|
{
|
|
5
|
-
amount
|
|
6
|
-
currency
|
|
12
|
+
amount,
|
|
13
|
+
currency,
|
|
7
14
|
payment_method_types: ["crypto"],
|
|
8
15
|
payment_method_data: { type: "crypto" },
|
|
9
16
|
payment_method_options: {
|
|
10
17
|
crypto: {
|
|
11
18
|
mode: "deposit",
|
|
12
|
-
deposit_options: { networks:
|
|
19
|
+
deposit_options: { networks: networks ?? ["tempo", "base", "solana"] }
|
|
13
20
|
}
|
|
14
21
|
},
|
|
15
22
|
confirm: true,
|
|
16
|
-
...
|
|
23
|
+
...metadata ? { metadata } : {}
|
|
17
24
|
},
|
|
18
|
-
|
|
25
|
+
idempotencyKey ? { idempotencyKey } : void 0
|
|
19
26
|
);
|
|
20
27
|
const depositAddresses = {};
|
|
21
28
|
const addrs = pi.next_action?.crypto_display_details?.deposit_addresses ?? {};
|
|
@@ -27,9 +34,6 @@ async function createMultichainPaymentIntent(input) {
|
|
|
27
34
|
}
|
|
28
35
|
return { paymentIntentId: pi.id, depositAddresses };
|
|
29
36
|
}
|
|
30
|
-
function getDepositAddress(result, network) {
|
|
31
|
-
return result.depositAddresses[network];
|
|
32
|
-
}
|
|
33
37
|
|
|
34
38
|
// src/stripe-multichain/simulate_deposit.ts
|
|
35
39
|
var STRIPE_TEST_TX_HASH_SUCCESS = "0x00000000000000000000000000000000000000000000000000000testsuccess";
|
|
@@ -39,57 +43,79 @@ var DEFAULT_BUYER_WALLET = {
|
|
|
39
43
|
tempo: "0x0000000000000000000000000000000000000001",
|
|
40
44
|
solana: "11111111111111111111111111111111"
|
|
41
45
|
};
|
|
42
|
-
async function simulateCryptoDeposit(
|
|
43
|
-
|
|
46
|
+
async function simulateCryptoDeposit({
|
|
47
|
+
paymentIntentId,
|
|
48
|
+
network,
|
|
49
|
+
buyerWallet,
|
|
50
|
+
tokenCurrency,
|
|
51
|
+
transactionHash,
|
|
52
|
+
stripeSecretKey,
|
|
53
|
+
stripeVersion,
|
|
54
|
+
stripeApiBase,
|
|
55
|
+
extra
|
|
56
|
+
}) {
|
|
57
|
+
const url = `${stripeApiBase ?? "https://api.stripe.com"}/v1/test_helpers/payment_intents/${paymentIntentId}/simulate_crypto_deposit`;
|
|
44
58
|
const params = new URLSearchParams({
|
|
45
|
-
network
|
|
46
|
-
buyer_wallet:
|
|
59
|
+
network,
|
|
60
|
+
buyer_wallet: buyerWallet ?? DEFAULT_BUYER_WALLET[network] ?? ""
|
|
47
61
|
});
|
|
48
|
-
if (
|
|
49
|
-
if (
|
|
50
|
-
for (const [k, v] of Object.entries(
|
|
62
|
+
if (tokenCurrency) params.set("token_currency", tokenCurrency);
|
|
63
|
+
if (transactionHash) params.set("transaction_hash", transactionHash);
|
|
64
|
+
for (const [k, v] of Object.entries(extra ?? {})) {
|
|
51
65
|
params.set(k, v);
|
|
52
66
|
}
|
|
53
67
|
const headers = {
|
|
54
|
-
Authorization: `Bearer ${
|
|
68
|
+
Authorization: `Bearer ${stripeSecretKey}`,
|
|
55
69
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
56
70
|
};
|
|
57
|
-
if (
|
|
71
|
+
if (stripeVersion) headers["Stripe-Version"] = stripeVersion;
|
|
58
72
|
const res = await fetch(url, { method: "POST", headers, body: params.toString() });
|
|
59
73
|
if (!res.ok) {
|
|
60
74
|
throw new Error(`Stripe simulate_crypto_deposit failed: ${res.status} ${await res.text()}`);
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
|
-
async function simulateDepositIfTestMode(
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
async function simulateDepositIfTestMode({
|
|
78
|
+
getPaymentIntentId,
|
|
79
|
+
depositAddress,
|
|
80
|
+
network,
|
|
81
|
+
buyerWallet,
|
|
82
|
+
tokenCurrency,
|
|
83
|
+
stripeSecretKey,
|
|
84
|
+
stripeVersion
|
|
85
|
+
}) {
|
|
86
|
+
if (!stripeSecretKey.startsWith("sk_test_")) return;
|
|
87
|
+
const piId = getPaymentIntentId(depositAddress);
|
|
66
88
|
if (!piId) {
|
|
67
89
|
console.warn(
|
|
68
|
-
`[stripe] Skipping deposit simulation \u2014 no PI cached for deposit address ${
|
|
90
|
+
`[stripe] Skipping deposit simulation \u2014 no PI cached for deposit address ${depositAddress.slice(0, 10)}\u2026 (network=${network}). The PI cache TTL may have expired between 402 emission and settlement.`
|
|
69
91
|
);
|
|
70
92
|
return;
|
|
71
93
|
}
|
|
72
94
|
try {
|
|
73
95
|
await simulateCryptoDeposit({
|
|
74
96
|
paymentIntentId: piId,
|
|
75
|
-
network
|
|
76
|
-
...
|
|
77
|
-
tokenCurrency:
|
|
97
|
+
network,
|
|
98
|
+
...buyerWallet !== void 0 && { buyerWallet },
|
|
99
|
+
tokenCurrency: tokenCurrency ?? "usdc",
|
|
78
100
|
transactionHash: STRIPE_TEST_TX_HASH_SUCCESS,
|
|
79
|
-
stripeSecretKey
|
|
80
|
-
...
|
|
101
|
+
stripeSecretKey,
|
|
102
|
+
...stripeVersion !== void 0 && { stripeVersion }
|
|
81
103
|
});
|
|
82
|
-
console.warn(`[stripe] \u2713 Simulated ${
|
|
104
|
+
console.warn(`[stripe] \u2713 Simulated ${network} deposit for PI ${piId}`);
|
|
83
105
|
} catch (err) {
|
|
84
106
|
console.error(
|
|
85
|
-
`[stripe] \u2717 Failed to simulate ${
|
|
107
|
+
`[stripe] \u2717 Failed to simulate ${network} deposit for PI ${piId}:`,
|
|
86
108
|
err instanceof Error ? err.message : err
|
|
87
109
|
);
|
|
88
110
|
}
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
// src/stripe-multichain/mppx_stripe.ts
|
|
92
|
-
async function createMppxStripe(
|
|
114
|
+
async function createMppxStripe({
|
|
115
|
+
profileId,
|
|
116
|
+
secretKey,
|
|
117
|
+
paymentMethodTypes
|
|
118
|
+
}) {
|
|
93
119
|
const moduleName = "mppx/server";
|
|
94
120
|
const mppx = await import(moduleName).catch(() => null);
|
|
95
121
|
if (!mppx?.stripe?.charge) {
|
|
@@ -98,16 +124,18 @@ async function createMppxStripe(input) {
|
|
|
98
124
|
);
|
|
99
125
|
}
|
|
100
126
|
return mppx.stripe.charge({
|
|
101
|
-
networkId:
|
|
102
|
-
paymentMethodTypes:
|
|
103
|
-
secretKey
|
|
127
|
+
networkId: profileId,
|
|
128
|
+
paymentMethodTypes: paymentMethodTypes ?? ["card", "link"],
|
|
129
|
+
secretKey
|
|
104
130
|
});
|
|
105
131
|
}
|
|
106
132
|
|
|
107
133
|
// src/stripe-multichain/pi-cache.ts
|
|
108
|
-
function createPiCache(
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
function createPiCache({
|
|
135
|
+
redisUrl,
|
|
136
|
+
ttlSeconds = 300,
|
|
137
|
+
keyPrefix = "payto:"
|
|
138
|
+
} = {}) {
|
|
111
139
|
let redis = null;
|
|
112
140
|
const addrMemCache = /* @__PURE__ */ new Map();
|
|
113
141
|
const piCache = /* @__PURE__ */ new Map();
|
|
@@ -126,17 +154,17 @@ function createPiCache(opts = {}) {
|
|
|
126
154
|
}, 6e4);
|
|
127
155
|
if (typeof evict.unref === "function") evict.unref();
|
|
128
156
|
async function getRedis() {
|
|
129
|
-
if (!
|
|
157
|
+
if (!redisUrl) return null;
|
|
130
158
|
if (redis) return redis;
|
|
131
159
|
const mod = await import("ioredis").catch(() => null);
|
|
132
160
|
if (!mod) {
|
|
133
161
|
console.error("[pi-cache] redisUrl set but `ioredis` is not installed. Run `npm install ioredis` or unset redisUrl.");
|
|
134
162
|
return null;
|
|
135
163
|
}
|
|
136
|
-
redis = new mod.default(
|
|
164
|
+
redis = new mod.default(redisUrl, {
|
|
137
165
|
connectTimeout: 5e3,
|
|
138
166
|
maxRetriesPerRequest: 1,
|
|
139
|
-
tls:
|
|
167
|
+
tls: redisUrl.startsWith("rediss://") ? {} : void 0
|
|
140
168
|
});
|
|
141
169
|
redis.on("error", (err) => console.error("[pi-cache] Redis error:", err.message));
|
|
142
170
|
return redis;
|
|
@@ -192,7 +220,6 @@ export {
|
|
|
192
220
|
createMppxStripe,
|
|
193
221
|
createMultichainPaymentIntent,
|
|
194
222
|
createPiCache,
|
|
195
|
-
getDepositAddress,
|
|
196
223
|
simulateCryptoDeposit,
|
|
197
224
|
simulateDepositIfTestMode
|
|
198
225
|
};
|