powertools-x402 0.1.0 → 0.1.2
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 +72 -21
- package/dist/chunk-D46V2DLG.js +211 -0
- package/dist/chunk-FLK2T35N.js +7 -0
- package/dist/coinbase.cjs +31 -0
- package/dist/coinbase.d.cts +9 -0
- package/dist/coinbase.d.ts +9 -0
- package/dist/coinbase.js +6 -0
- package/dist/index.js +4 -204
- package/dist/stripe.cjs +316 -0
- package/dist/stripe.d.cts +38 -0
- package/dist/stripe.d.ts +38 -0
- package/dist/stripe.js +80 -0
- package/dist/testing.cjs +84 -0
- package/dist/testing.d.cts +29 -0
- package/dist/testing.d.ts +29 -0
- package/dist/testing.js +56 -0
- package/package.json +48 -2
package/dist/stripe.cjs
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/stripe.ts
|
|
31
|
+
var stripe_exports = {};
|
|
32
|
+
__export(stripe_exports, {
|
|
33
|
+
createStripeX402: () => createStripeX402
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(stripe_exports);
|
|
36
|
+
var import_stripe = __toESM(require("stripe"), 1);
|
|
37
|
+
|
|
38
|
+
// src/coinbase.ts
|
|
39
|
+
var import_x402 = require("@coinbase/x402");
|
|
40
|
+
var coinbaseFacilitator = (apiKeyId, apiKeySecret) => (0, import_x402.createFacilitatorConfig)(apiKeyId, apiKeySecret);
|
|
41
|
+
|
|
42
|
+
// src/index.ts
|
|
43
|
+
var import_metrics = require("@aws-lambda-powertools/metrics");
|
|
44
|
+
var import_server = require("@x402/core/server");
|
|
45
|
+
var import_server2 = require("@x402/evm/exact/server");
|
|
46
|
+
|
|
47
|
+
// src/adapter.ts
|
|
48
|
+
var PowertoolsAdapter = class {
|
|
49
|
+
constructor(request) {
|
|
50
|
+
this.request = request;
|
|
51
|
+
}
|
|
52
|
+
request;
|
|
53
|
+
getHeader(name) {
|
|
54
|
+
return this.request.headers.get(name) ?? void 0;
|
|
55
|
+
}
|
|
56
|
+
getMethod() {
|
|
57
|
+
return this.request.method;
|
|
58
|
+
}
|
|
59
|
+
getPath() {
|
|
60
|
+
return new URL(this.request.url).pathname;
|
|
61
|
+
}
|
|
62
|
+
getUrl() {
|
|
63
|
+
return this.request.url;
|
|
64
|
+
}
|
|
65
|
+
getAcceptHeader() {
|
|
66
|
+
return this.request.headers.get("accept") ?? "";
|
|
67
|
+
}
|
|
68
|
+
getUserAgent() {
|
|
69
|
+
return this.request.headers.get("user-agent") ?? "";
|
|
70
|
+
}
|
|
71
|
+
getQueryParams() {
|
|
72
|
+
const params = new URL(this.request.url).searchParams;
|
|
73
|
+
const result = {};
|
|
74
|
+
for (const key of new Set(params.keys())) {
|
|
75
|
+
const values = params.getAll(key);
|
|
76
|
+
result[key] = values.length === 1 ? values[0] : values;
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
getQueryParam(name) {
|
|
81
|
+
const values = new URL(this.request.url).searchParams.getAll(name);
|
|
82
|
+
if (values.length === 0) return void 0;
|
|
83
|
+
return values.length === 1 ? values[0] : values;
|
|
84
|
+
}
|
|
85
|
+
async getBody() {
|
|
86
|
+
try {
|
|
87
|
+
return await this.request.clone().json();
|
|
88
|
+
} catch {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/facilitator.ts
|
|
95
|
+
var CachingFacilitatorClient = class {
|
|
96
|
+
constructor(client) {
|
|
97
|
+
this.client = client;
|
|
98
|
+
}
|
|
99
|
+
client;
|
|
100
|
+
#supported;
|
|
101
|
+
verify(payload, requirements) {
|
|
102
|
+
return this.client.verify(payload, requirements);
|
|
103
|
+
}
|
|
104
|
+
settle(payload, requirements) {
|
|
105
|
+
return this.client.settle(payload, requirements);
|
|
106
|
+
}
|
|
107
|
+
getSupported() {
|
|
108
|
+
this.#supported ??= this.client.getSupported().catch((error) => {
|
|
109
|
+
this.#supported = void 0;
|
|
110
|
+
throw error;
|
|
111
|
+
});
|
|
112
|
+
return this.#supported;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/index.ts
|
|
117
|
+
var toFacilitatorClient = (facilitator) => {
|
|
118
|
+
if (typeof facilitator === "string") return new import_server.HTTPFacilitatorClient({ url: facilitator });
|
|
119
|
+
if ("getSupported" in facilitator) return facilitator;
|
|
120
|
+
return new import_server.HTTPFacilitatorClient(facilitator);
|
|
121
|
+
};
|
|
122
|
+
var toResponse = ({ status, headers, body, isHtml }) => new Response(isHtml ? String(body ?? "") : JSON.stringify(body ?? {}), { status, headers });
|
|
123
|
+
function createX402(options) {
|
|
124
|
+
const facilitator = new CachingFacilitatorClient(toFacilitatorClient(options.facilitator));
|
|
125
|
+
const resourceServer = new import_server.x402ResourceServer(facilitator);
|
|
126
|
+
for (const register of options.schemes ?? [import_server2.registerExactEvmScheme]) {
|
|
127
|
+
register(resourceServer);
|
|
128
|
+
}
|
|
129
|
+
const { logger } = options;
|
|
130
|
+
const metrics = options.enableMetrics ? options.metrics ?? new import_metrics.Metrics({ namespace: "x402" }) : void 0;
|
|
131
|
+
const count = (name) => metrics && (metrics.singleMetric?.() ?? metrics).addMetric(name, "Count", 1);
|
|
132
|
+
const payers = /* @__PURE__ */ new WeakMap();
|
|
133
|
+
resourceServer.onAfterVerify(async ({ paymentPayload, result }) => {
|
|
134
|
+
if (result.payer) payers.set(paymentPayload, result.payer);
|
|
135
|
+
});
|
|
136
|
+
function paid(route) {
|
|
137
|
+
const { price, accepts, onProtectedRequest, ...routeConfig } = route;
|
|
138
|
+
if (accepts === void 0 && price === void 0) {
|
|
139
|
+
throw new Error("paid() requires a price or an accepts configuration");
|
|
140
|
+
}
|
|
141
|
+
const httpServer = new import_server.x402HTTPResourceServer(resourceServer, {
|
|
142
|
+
...routeConfig,
|
|
143
|
+
mimeType: routeConfig.mimeType ?? "application/json",
|
|
144
|
+
accepts: accepts ?? {
|
|
145
|
+
scheme: "exact",
|
|
146
|
+
network: options.network,
|
|
147
|
+
payTo: options.payTo,
|
|
148
|
+
price
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
if (onProtectedRequest) httpServer.onProtectedRequest(onProtectedRequest);
|
|
152
|
+
let ready;
|
|
153
|
+
const initialize = () => ready ??= httpServer.initialize().catch((error) => {
|
|
154
|
+
ready = void 0;
|
|
155
|
+
throw error;
|
|
156
|
+
});
|
|
157
|
+
return async ({ reqCtx, next }) => {
|
|
158
|
+
await initialize();
|
|
159
|
+
const adapter = new PowertoolsAdapter(reqCtx.req);
|
|
160
|
+
const context = {
|
|
161
|
+
adapter,
|
|
162
|
+
path: adapter.getPath(),
|
|
163
|
+
method: adapter.getMethod()
|
|
164
|
+
};
|
|
165
|
+
const result = await httpServer.processHTTPRequest(context, options.paywall);
|
|
166
|
+
if (result.type === "no-payment-required") {
|
|
167
|
+
await next();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (result.type === "payment-error") {
|
|
171
|
+
count(adapter.getHeader("payment-signature") ? "PaymentRejected" : "PaymentRequired");
|
|
172
|
+
logger?.debug("x402 payment not accepted", { status: result.response.status });
|
|
173
|
+
reqCtx.res = toResponse(result.response);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const {
|
|
177
|
+
cancellationDispatcher,
|
|
178
|
+
beforeHandlerSettlement,
|
|
179
|
+
paymentPayload,
|
|
180
|
+
paymentRequirements,
|
|
181
|
+
declaredExtensions
|
|
182
|
+
} = result;
|
|
183
|
+
reqCtx.set("payment", {
|
|
184
|
+
network: paymentRequirements.network,
|
|
185
|
+
scheme: paymentRequirements.scheme,
|
|
186
|
+
asset: paymentRequirements.asset,
|
|
187
|
+
amount: paymentRequirements.amount,
|
|
188
|
+
payer: payers.get(paymentPayload)
|
|
189
|
+
});
|
|
190
|
+
count("PaymentVerified");
|
|
191
|
+
try {
|
|
192
|
+
await next();
|
|
193
|
+
} catch (error) {
|
|
194
|
+
await cancellationDispatcher.cancel({ reason: "handler_threw", error });
|
|
195
|
+
count("PaymentCancelled");
|
|
196
|
+
logger?.warn("x402 payment cancelled: handler threw", { path: context.path });
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
if (reqCtx.res.status >= 400) {
|
|
200
|
+
await cancellationDispatcher.cancel({
|
|
201
|
+
reason: "handler_failed",
|
|
202
|
+
responseStatus: reqCtx.res.status
|
|
203
|
+
});
|
|
204
|
+
count("PaymentCancelled");
|
|
205
|
+
logger?.warn("x402 payment cancelled: handler failed", {
|
|
206
|
+
path: context.path,
|
|
207
|
+
status: reqCtx.res.status
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const settlement = await httpServer.processSettlement(
|
|
212
|
+
paymentPayload,
|
|
213
|
+
paymentRequirements,
|
|
214
|
+
declaredExtensions,
|
|
215
|
+
{
|
|
216
|
+
request: context,
|
|
217
|
+
responseBody: Buffer.from(await reqCtx.res.clone().arrayBuffer()),
|
|
218
|
+
responseHeaders: Object.fromEntries(reqCtx.res.headers.entries())
|
|
219
|
+
},
|
|
220
|
+
void 0,
|
|
221
|
+
beforeHandlerSettlement
|
|
222
|
+
);
|
|
223
|
+
if (!settlement.success) {
|
|
224
|
+
count("SettlementFailed");
|
|
225
|
+
logger?.error("x402 settlement failed", {
|
|
226
|
+
path: context.path,
|
|
227
|
+
errorReason: settlement.errorReason
|
|
228
|
+
});
|
|
229
|
+
reqCtx.res = toResponse(settlement.response);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
count("PaymentSettled");
|
|
233
|
+
logger?.debug("x402 payment settled", { path: context.path, transaction: settlement.transaction });
|
|
234
|
+
for (const [name, value] of Object.entries(settlement.headers)) {
|
|
235
|
+
reqCtx.res.headers.set(name, value);
|
|
236
|
+
}
|
|
237
|
+
const cacheControl = reqCtx.res.headers.get("cache-control");
|
|
238
|
+
reqCtx.res.headers.set("cache-control", cacheControl ? `${cacheControl}, private` : "private");
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return { paid, resourceServer };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/stripe.ts
|
|
245
|
+
var BASE_MAINNET = "eip155:8453";
|
|
246
|
+
var BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
247
|
+
var STRIPE_PREVIEW_API_VERSION = "2026-05-27.preview";
|
|
248
|
+
function createStripeX402(options = {}) {
|
|
249
|
+
const {
|
|
250
|
+
depositAddress = process.env.STRIPE_DEPOSIT_ADDRESS,
|
|
251
|
+
stripeSecretKey = process.env.STRIPE_SECRET_KEY,
|
|
252
|
+
stripe,
|
|
253
|
+
cdpApiKeyId,
|
|
254
|
+
cdpApiKeySecret,
|
|
255
|
+
facilitator,
|
|
256
|
+
...rest
|
|
257
|
+
} = options;
|
|
258
|
+
if (!depositAddress) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
"createStripeX402 requires a depositAddress (or STRIPE_DEPOSIT_ADDRESS). Create one with POST /v1/crypto/deposit_addresses."
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (!stripe && !stripeSecretKey) {
|
|
264
|
+
throw new Error("createStripeX402 requires a stripe client or stripeSecretKey (or STRIPE_SECRET_KEY).");
|
|
265
|
+
}
|
|
266
|
+
const stripeClient = stripe ?? new import_stripe.default(stripeSecretKey, {
|
|
267
|
+
apiVersion: STRIPE_PREVIEW_API_VERSION
|
|
268
|
+
});
|
|
269
|
+
const x402 = createX402({
|
|
270
|
+
...rest,
|
|
271
|
+
network: BASE_MAINNET,
|
|
272
|
+
facilitator: facilitator ?? coinbaseFacilitator(cdpApiKeyId, cdpApiKeySecret),
|
|
273
|
+
payTo: depositAddress
|
|
274
|
+
});
|
|
275
|
+
x402.resourceServer.onAfterSettle(async ({ result, requirements }) => {
|
|
276
|
+
if (!result.success || !result.transaction) return;
|
|
277
|
+
if (requirements.network !== BASE_MAINNET) return;
|
|
278
|
+
if (requirements.asset.toLowerCase() !== BASE_USDC.toLowerCase()) return;
|
|
279
|
+
const amountInCents = Math.round(Number(requirements.amount) / 1e4);
|
|
280
|
+
if (amountInCents < 1) return;
|
|
281
|
+
try {
|
|
282
|
+
await stripeClient.paymentIntents.create(
|
|
283
|
+
{
|
|
284
|
+
amount: amountInCents,
|
|
285
|
+
currency: "usd",
|
|
286
|
+
confirm: true,
|
|
287
|
+
payment_method_data: { type: "crypto" },
|
|
288
|
+
payment_method_types: ["crypto"],
|
|
289
|
+
payment_method_options: {
|
|
290
|
+
// transaction_verification ships in the 2026-05-27.preview API
|
|
291
|
+
// version and is not yet in stripe-node's stable types
|
|
292
|
+
crypto: {
|
|
293
|
+
mode: "transaction_verification",
|
|
294
|
+
transaction_verification_options: {
|
|
295
|
+
network: "base",
|
|
296
|
+
transaction_hash: result.transaction
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{ idempotencyKey: result.transaction }
|
|
302
|
+
);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
(rest.logger ?? console).error("stripe payment intent recording failed", {
|
|
305
|
+
transaction: result.transaction,
|
|
306
|
+
amountInCents,
|
|
307
|
+
error: error instanceof Error ? error.message : String(error)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
return x402;
|
|
312
|
+
}
|
|
313
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
314
|
+
0 && (module.exports = {
|
|
315
|
+
createStripeX402
|
|
316
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as _x402_core_server from '@x402/core/server';
|
|
2
|
+
import * as _aws_lambda_powertools_event_handler_types from '@aws-lambda-powertools/event-handler/types';
|
|
3
|
+
import { CreateX402Options, PaidRouteOptions, X402Environment } from './index.cjs';
|
|
4
|
+
import Stripe from 'stripe';
|
|
5
|
+
import '@x402/core/http';
|
|
6
|
+
import '@x402/core/types';
|
|
7
|
+
|
|
8
|
+
interface StripeClient {
|
|
9
|
+
paymentIntents: {
|
|
10
|
+
create(params: Stripe.PaymentIntentCreateParams, options: {
|
|
11
|
+
idempotencyKey: string;
|
|
12
|
+
}): Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
interface StripeX402Options extends Omit<CreateX402Options, 'facilitator' | 'payTo' | 'network'> {
|
|
16
|
+
depositAddress?: string;
|
|
17
|
+
stripeSecretKey?: string;
|
|
18
|
+
stripe?: StripeClient;
|
|
19
|
+
cdpApiKeyId?: string;
|
|
20
|
+
cdpApiKeySecret?: string;
|
|
21
|
+
facilitator?: CreateX402Options['facilitator'];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* x402 routes that settle into your Stripe balance: payments go to a Stripe
|
|
25
|
+
* crypto deposit address via the Coinbase facilitator, and each settled
|
|
26
|
+
* payment is recorded as a Stripe PaymentIntent (transaction_verification).
|
|
27
|
+
*
|
|
28
|
+
* Intentionally pinned to USDC on Base mainnet; the PaymentIntent recording
|
|
29
|
+
* assumes that network and asset. A failed recording never fails the request:
|
|
30
|
+
* the payment already settled on-chain, so the failure is logged with the
|
|
31
|
+
* transaction hash for reconciliation instead.
|
|
32
|
+
*/
|
|
33
|
+
declare function createStripeX402(options?: StripeX402Options): {
|
|
34
|
+
paid: (route: PaidRouteOptions) => _aws_lambda_powertools_event_handler_types.Middleware<X402Environment>;
|
|
35
|
+
resourceServer: _x402_core_server.x402ResourceServer;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { type StripeClient, type StripeX402Options, createStripeX402 };
|
package/dist/stripe.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as _x402_core_server from '@x402/core/server';
|
|
2
|
+
import * as _aws_lambda_powertools_event_handler_types from '@aws-lambda-powertools/event-handler/types';
|
|
3
|
+
import { CreateX402Options, PaidRouteOptions, X402Environment } from './index.js';
|
|
4
|
+
import Stripe from 'stripe';
|
|
5
|
+
import '@x402/core/http';
|
|
6
|
+
import '@x402/core/types';
|
|
7
|
+
|
|
8
|
+
interface StripeClient {
|
|
9
|
+
paymentIntents: {
|
|
10
|
+
create(params: Stripe.PaymentIntentCreateParams, options: {
|
|
11
|
+
idempotencyKey: string;
|
|
12
|
+
}): Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
interface StripeX402Options extends Omit<CreateX402Options, 'facilitator' | 'payTo' | 'network'> {
|
|
16
|
+
depositAddress?: string;
|
|
17
|
+
stripeSecretKey?: string;
|
|
18
|
+
stripe?: StripeClient;
|
|
19
|
+
cdpApiKeyId?: string;
|
|
20
|
+
cdpApiKeySecret?: string;
|
|
21
|
+
facilitator?: CreateX402Options['facilitator'];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* x402 routes that settle into your Stripe balance: payments go to a Stripe
|
|
25
|
+
* crypto deposit address via the Coinbase facilitator, and each settled
|
|
26
|
+
* payment is recorded as a Stripe PaymentIntent (transaction_verification).
|
|
27
|
+
*
|
|
28
|
+
* Intentionally pinned to USDC on Base mainnet; the PaymentIntent recording
|
|
29
|
+
* assumes that network and asset. A failed recording never fails the request:
|
|
30
|
+
* the payment already settled on-chain, so the failure is logged with the
|
|
31
|
+
* transaction hash for reconciliation instead.
|
|
32
|
+
*/
|
|
33
|
+
declare function createStripeX402(options?: StripeX402Options): {
|
|
34
|
+
paid: (route: PaidRouteOptions) => _aws_lambda_powertools_event_handler_types.Middleware<X402Environment>;
|
|
35
|
+
resourceServer: _x402_core_server.x402ResourceServer;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { type StripeClient, type StripeX402Options, createStripeX402 };
|
package/dist/stripe.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
coinbaseFacilitator
|
|
3
|
+
} from "./chunk-FLK2T35N.js";
|
|
4
|
+
import {
|
|
5
|
+
createX402
|
|
6
|
+
} from "./chunk-D46V2DLG.js";
|
|
7
|
+
|
|
8
|
+
// src/stripe.ts
|
|
9
|
+
import Stripe from "stripe";
|
|
10
|
+
var BASE_MAINNET = "eip155:8453";
|
|
11
|
+
var BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
12
|
+
var STRIPE_PREVIEW_API_VERSION = "2026-05-27.preview";
|
|
13
|
+
function createStripeX402(options = {}) {
|
|
14
|
+
const {
|
|
15
|
+
depositAddress = process.env.STRIPE_DEPOSIT_ADDRESS,
|
|
16
|
+
stripeSecretKey = process.env.STRIPE_SECRET_KEY,
|
|
17
|
+
stripe,
|
|
18
|
+
cdpApiKeyId,
|
|
19
|
+
cdpApiKeySecret,
|
|
20
|
+
facilitator,
|
|
21
|
+
...rest
|
|
22
|
+
} = options;
|
|
23
|
+
if (!depositAddress) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"createStripeX402 requires a depositAddress (or STRIPE_DEPOSIT_ADDRESS). Create one with POST /v1/crypto/deposit_addresses."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (!stripe && !stripeSecretKey) {
|
|
29
|
+
throw new Error("createStripeX402 requires a stripe client or stripeSecretKey (or STRIPE_SECRET_KEY).");
|
|
30
|
+
}
|
|
31
|
+
const stripeClient = stripe ?? new Stripe(stripeSecretKey, {
|
|
32
|
+
apiVersion: STRIPE_PREVIEW_API_VERSION
|
|
33
|
+
});
|
|
34
|
+
const x402 = createX402({
|
|
35
|
+
...rest,
|
|
36
|
+
network: BASE_MAINNET,
|
|
37
|
+
facilitator: facilitator ?? coinbaseFacilitator(cdpApiKeyId, cdpApiKeySecret),
|
|
38
|
+
payTo: depositAddress
|
|
39
|
+
});
|
|
40
|
+
x402.resourceServer.onAfterSettle(async ({ result, requirements }) => {
|
|
41
|
+
if (!result.success || !result.transaction) return;
|
|
42
|
+
if (requirements.network !== BASE_MAINNET) return;
|
|
43
|
+
if (requirements.asset.toLowerCase() !== BASE_USDC.toLowerCase()) return;
|
|
44
|
+
const amountInCents = Math.round(Number(requirements.amount) / 1e4);
|
|
45
|
+
if (amountInCents < 1) return;
|
|
46
|
+
try {
|
|
47
|
+
await stripeClient.paymentIntents.create(
|
|
48
|
+
{
|
|
49
|
+
amount: amountInCents,
|
|
50
|
+
currency: "usd",
|
|
51
|
+
confirm: true,
|
|
52
|
+
payment_method_data: { type: "crypto" },
|
|
53
|
+
payment_method_types: ["crypto"],
|
|
54
|
+
payment_method_options: {
|
|
55
|
+
// transaction_verification ships in the 2026-05-27.preview API
|
|
56
|
+
// version and is not yet in stripe-node's stable types
|
|
57
|
+
crypto: {
|
|
58
|
+
mode: "transaction_verification",
|
|
59
|
+
transaction_verification_options: {
|
|
60
|
+
network: "base",
|
|
61
|
+
transaction_hash: result.transaction
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{ idempotencyKey: result.transaction }
|
|
67
|
+
);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
(rest.logger ?? console).error("stripe payment intent recording failed", {
|
|
70
|
+
transaction: result.transaction,
|
|
71
|
+
amountInCents,
|
|
72
|
+
error: error instanceof Error ? error.message : String(error)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return x402;
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
createStripeX402
|
|
80
|
+
};
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/testing.ts
|
|
21
|
+
var testing_exports = {};
|
|
22
|
+
__export(testing_exports, {
|
|
23
|
+
TEST_PAYER_ADDRESS: () => TEST_PAYER_ADDRESS,
|
|
24
|
+
TEST_TRANSACTION_HASH: () => TEST_TRANSACTION_HASH,
|
|
25
|
+
stubFacilitator: () => stubFacilitator,
|
|
26
|
+
testPayer: () => testPayer
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(testing_exports);
|
|
29
|
+
var import_client = require("@x402/core/client");
|
|
30
|
+
var import_client2 = require("@x402/evm/exact/client");
|
|
31
|
+
var import_accounts = require("viem/accounts");
|
|
32
|
+
var TEST_PAYER_ADDRESS = "0x0000000000000000000000000000000000C0FFEE";
|
|
33
|
+
var TEST_TRANSACTION_HASH = `0x${"ab".repeat(32)}`;
|
|
34
|
+
function stubFacilitator(options = {}) {
|
|
35
|
+
const networks = [options.network ?? "eip155:84532"].flat();
|
|
36
|
+
const payer = options.payer ?? TEST_PAYER_ADDRESS;
|
|
37
|
+
return {
|
|
38
|
+
async getSupported() {
|
|
39
|
+
return {
|
|
40
|
+
kinds: networks.map((network) => ({ x402Version: 2, scheme: "exact", network })),
|
|
41
|
+
extensions: [],
|
|
42
|
+
signers: {}
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
async verify() {
|
|
46
|
+
return { isValid: true, payer, ...options.verify };
|
|
47
|
+
},
|
|
48
|
+
async settle() {
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
transaction: TEST_TRANSACTION_HASH,
|
|
52
|
+
network: networks[0],
|
|
53
|
+
payer,
|
|
54
|
+
...options.settle
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
var getHeaderFrom = (challenge) => (name) => {
|
|
60
|
+
if (challenge.headers instanceof Headers) return challenge.headers.get(name) ?? void 0;
|
|
61
|
+
const value = challenge.headers?.[name.toLowerCase()];
|
|
62
|
+
return value === void 0 ? void 0 : String(value);
|
|
63
|
+
};
|
|
64
|
+
function testPayer(privateKey) {
|
|
65
|
+
const account = (0, import_accounts.privateKeyToAccount)(privateKey ?? (0, import_accounts.generatePrivateKey)());
|
|
66
|
+
const client = new import_client.x402HTTPClient(
|
|
67
|
+
(0, import_client2.registerExactEvmScheme)(new import_client.x402Client(), { signer: account })
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
address: account.address,
|
|
71
|
+
async payFor(challenge) {
|
|
72
|
+
const paymentRequired = client.getPaymentRequiredResponse(getHeaderFrom(challenge));
|
|
73
|
+
const payload = await client.createPaymentPayload(paymentRequired);
|
|
74
|
+
return client.encodePaymentSignatureHeader(payload);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
79
|
+
0 && (module.exports = {
|
|
80
|
+
TEST_PAYER_ADDRESS,
|
|
81
|
+
TEST_TRANSACTION_HASH,
|
|
82
|
+
stubFacilitator,
|
|
83
|
+
testPayer
|
|
84
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FacilitatorClient } from '@x402/core/server';
|
|
2
|
+
import { Network, VerifyResponse, SettleResponse } from '@x402/core/types';
|
|
3
|
+
|
|
4
|
+
declare const TEST_PAYER_ADDRESS = "0x0000000000000000000000000000000000C0FFEE";
|
|
5
|
+
declare const TEST_TRANSACTION_HASH: string;
|
|
6
|
+
interface StubFacilitatorOptions {
|
|
7
|
+
network?: Network | Network[];
|
|
8
|
+
payer?: string;
|
|
9
|
+
verify?: Partial<VerifyResponse>;
|
|
10
|
+
settle?: Partial<SettleResponse>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* In-memory facilitator that approves every payment. Spy on its methods or
|
|
14
|
+
* override verify/settle responses to exercise failure paths.
|
|
15
|
+
*/
|
|
16
|
+
declare function stubFacilitator(options?: StubFacilitatorOptions): FacilitatorClient;
|
|
17
|
+
type ChallengeLike = Response | {
|
|
18
|
+
headers?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A payment client backed by a throwaway account. Feed it a 402 response and
|
|
22
|
+
* it returns the headers that make the retried request a valid paid request.
|
|
23
|
+
*/
|
|
24
|
+
declare function testPayer(privateKey?: `0x${string}`): {
|
|
25
|
+
address: `0x${string}`;
|
|
26
|
+
payFor(challenge: ChallengeLike): Promise<Record<string, string>>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export { type StubFacilitatorOptions, TEST_PAYER_ADDRESS, TEST_TRANSACTION_HASH, stubFacilitator, testPayer };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FacilitatorClient } from '@x402/core/server';
|
|
2
|
+
import { Network, VerifyResponse, SettleResponse } from '@x402/core/types';
|
|
3
|
+
|
|
4
|
+
declare const TEST_PAYER_ADDRESS = "0x0000000000000000000000000000000000C0FFEE";
|
|
5
|
+
declare const TEST_TRANSACTION_HASH: string;
|
|
6
|
+
interface StubFacilitatorOptions {
|
|
7
|
+
network?: Network | Network[];
|
|
8
|
+
payer?: string;
|
|
9
|
+
verify?: Partial<VerifyResponse>;
|
|
10
|
+
settle?: Partial<SettleResponse>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* In-memory facilitator that approves every payment. Spy on its methods or
|
|
14
|
+
* override verify/settle responses to exercise failure paths.
|
|
15
|
+
*/
|
|
16
|
+
declare function stubFacilitator(options?: StubFacilitatorOptions): FacilitatorClient;
|
|
17
|
+
type ChallengeLike = Response | {
|
|
18
|
+
headers?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A payment client backed by a throwaway account. Feed it a 402 response and
|
|
22
|
+
* it returns the headers that make the retried request a valid paid request.
|
|
23
|
+
*/
|
|
24
|
+
declare function testPayer(privateKey?: `0x${string}`): {
|
|
25
|
+
address: `0x${string}`;
|
|
26
|
+
payFor(challenge: ChallengeLike): Promise<Record<string, string>>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export { type StubFacilitatorOptions, TEST_PAYER_ADDRESS, TEST_TRANSACTION_HASH, stubFacilitator, testPayer };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/testing.ts
|
|
2
|
+
import { x402Client, x402HTTPClient } from "@x402/core/client";
|
|
3
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
4
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
5
|
+
var TEST_PAYER_ADDRESS = "0x0000000000000000000000000000000000C0FFEE";
|
|
6
|
+
var TEST_TRANSACTION_HASH = `0x${"ab".repeat(32)}`;
|
|
7
|
+
function stubFacilitator(options = {}) {
|
|
8
|
+
const networks = [options.network ?? "eip155:84532"].flat();
|
|
9
|
+
const payer = options.payer ?? TEST_PAYER_ADDRESS;
|
|
10
|
+
return {
|
|
11
|
+
async getSupported() {
|
|
12
|
+
return {
|
|
13
|
+
kinds: networks.map((network) => ({ x402Version: 2, scheme: "exact", network })),
|
|
14
|
+
extensions: [],
|
|
15
|
+
signers: {}
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
async verify() {
|
|
19
|
+
return { isValid: true, payer, ...options.verify };
|
|
20
|
+
},
|
|
21
|
+
async settle() {
|
|
22
|
+
return {
|
|
23
|
+
success: true,
|
|
24
|
+
transaction: TEST_TRANSACTION_HASH,
|
|
25
|
+
network: networks[0],
|
|
26
|
+
payer,
|
|
27
|
+
...options.settle
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
var getHeaderFrom = (challenge) => (name) => {
|
|
33
|
+
if (challenge.headers instanceof Headers) return challenge.headers.get(name) ?? void 0;
|
|
34
|
+
const value = challenge.headers?.[name.toLowerCase()];
|
|
35
|
+
return value === void 0 ? void 0 : String(value);
|
|
36
|
+
};
|
|
37
|
+
function testPayer(privateKey) {
|
|
38
|
+
const account = privateKeyToAccount(privateKey ?? generatePrivateKey());
|
|
39
|
+
const client = new x402HTTPClient(
|
|
40
|
+
registerExactEvmScheme(new x402Client(), { signer: account })
|
|
41
|
+
);
|
|
42
|
+
return {
|
|
43
|
+
address: account.address,
|
|
44
|
+
async payFor(challenge) {
|
|
45
|
+
const paymentRequired = client.getPaymentRequiredResponse(getHeaderFrom(challenge));
|
|
46
|
+
const payload = await client.createPaymentPayload(paymentRequired);
|
|
47
|
+
return client.encodePaymentSignatureHeader(payload);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
TEST_PAYER_ADDRESS,
|
|
53
|
+
TEST_TRANSACTION_HASH,
|
|
54
|
+
stubFacilitator,
|
|
55
|
+
testPayer
|
|
56
|
+
};
|