powertools-x402 0.1.1 → 0.2.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 +76 -54
- package/dist/chunk-4ZP7AJJC.js +231 -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.cjs +21 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +4 -204
- package/dist/stripe.cjs +336 -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 +46 -2
package/dist/index.js
CHANGED
|
@@ -1,208 +1,8 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { Metrics } from "@aws-lambda-powertools/metrics";
|
|
3
1
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "
|
|
8
|
-
import { registerExactEvmScheme } from "@x402/evm/exact/server";
|
|
9
|
-
|
|
10
|
-
// src/adapter.ts
|
|
11
|
-
var PowertoolsAdapter = class {
|
|
12
|
-
constructor(request) {
|
|
13
|
-
this.request = request;
|
|
14
|
-
}
|
|
15
|
-
request;
|
|
16
|
-
getHeader(name) {
|
|
17
|
-
return this.request.headers.get(name) ?? void 0;
|
|
18
|
-
}
|
|
19
|
-
getMethod() {
|
|
20
|
-
return this.request.method;
|
|
21
|
-
}
|
|
22
|
-
getPath() {
|
|
23
|
-
return new URL(this.request.url).pathname;
|
|
24
|
-
}
|
|
25
|
-
getUrl() {
|
|
26
|
-
return this.request.url;
|
|
27
|
-
}
|
|
28
|
-
getAcceptHeader() {
|
|
29
|
-
return this.request.headers.get("accept") ?? "";
|
|
30
|
-
}
|
|
31
|
-
getUserAgent() {
|
|
32
|
-
return this.request.headers.get("user-agent") ?? "";
|
|
33
|
-
}
|
|
34
|
-
getQueryParams() {
|
|
35
|
-
const params = new URL(this.request.url).searchParams;
|
|
36
|
-
const result = {};
|
|
37
|
-
for (const key of new Set(params.keys())) {
|
|
38
|
-
const values = params.getAll(key);
|
|
39
|
-
result[key] = values.length === 1 ? values[0] : values;
|
|
40
|
-
}
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
getQueryParam(name) {
|
|
44
|
-
const values = new URL(this.request.url).searchParams.getAll(name);
|
|
45
|
-
if (values.length === 0) return void 0;
|
|
46
|
-
return values.length === 1 ? values[0] : values;
|
|
47
|
-
}
|
|
48
|
-
async getBody() {
|
|
49
|
-
try {
|
|
50
|
-
return await this.request.clone().json();
|
|
51
|
-
} catch {
|
|
52
|
-
return void 0;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// src/facilitator.ts
|
|
58
|
-
var CachingFacilitatorClient = class {
|
|
59
|
-
constructor(client) {
|
|
60
|
-
this.client = client;
|
|
61
|
-
}
|
|
62
|
-
client;
|
|
63
|
-
#supported;
|
|
64
|
-
verify(payload, requirements) {
|
|
65
|
-
return this.client.verify(payload, requirements);
|
|
66
|
-
}
|
|
67
|
-
settle(payload, requirements) {
|
|
68
|
-
return this.client.settle(payload, requirements);
|
|
69
|
-
}
|
|
70
|
-
getSupported() {
|
|
71
|
-
this.#supported ??= this.client.getSupported().catch((error) => {
|
|
72
|
-
this.#supported = void 0;
|
|
73
|
-
throw error;
|
|
74
|
-
});
|
|
75
|
-
return this.#supported;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// src/index.ts
|
|
80
|
-
var toFacilitatorClient = (facilitator) => {
|
|
81
|
-
if (typeof facilitator === "string") return new HTTPFacilitatorClient({ url: facilitator });
|
|
82
|
-
if ("getSupported" in facilitator) return facilitator;
|
|
83
|
-
return new HTTPFacilitatorClient(facilitator);
|
|
84
|
-
};
|
|
85
|
-
var toResponse = ({ status, headers, body, isHtml }) => new Response(isHtml ? String(body ?? "") : JSON.stringify(body ?? {}), { status, headers });
|
|
86
|
-
function createX402(options) {
|
|
87
|
-
const facilitator = new CachingFacilitatorClient(toFacilitatorClient(options.facilitator));
|
|
88
|
-
const resourceServer = new x402ResourceServer(facilitator);
|
|
89
|
-
for (const register of options.schemes ?? [registerExactEvmScheme]) {
|
|
90
|
-
register(resourceServer);
|
|
91
|
-
}
|
|
92
|
-
const { logger } = options;
|
|
93
|
-
const metrics = options.enableMetrics ? options.metrics ?? new Metrics({ namespace: "x402" }) : void 0;
|
|
94
|
-
const count = (name) => metrics && (metrics.singleMetric?.() ?? metrics).addMetric(name, "Count", 1);
|
|
95
|
-
const payers = /* @__PURE__ */ new WeakMap();
|
|
96
|
-
resourceServer.onAfterVerify(async ({ paymentPayload, result }) => {
|
|
97
|
-
if (result.payer) payers.set(paymentPayload, result.payer);
|
|
98
|
-
});
|
|
99
|
-
function paid(route) {
|
|
100
|
-
const { price, accepts, onProtectedRequest, ...routeConfig } = route;
|
|
101
|
-
if (accepts === void 0 && price === void 0) {
|
|
102
|
-
throw new Error("paid() requires a price or an accepts configuration");
|
|
103
|
-
}
|
|
104
|
-
const httpServer = new x402HTTPResourceServer(resourceServer, {
|
|
105
|
-
...routeConfig,
|
|
106
|
-
mimeType: routeConfig.mimeType ?? "application/json",
|
|
107
|
-
accepts: accepts ?? {
|
|
108
|
-
scheme: "exact",
|
|
109
|
-
network: options.network,
|
|
110
|
-
payTo: options.payTo,
|
|
111
|
-
price
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
if (onProtectedRequest) httpServer.onProtectedRequest(onProtectedRequest);
|
|
115
|
-
let ready;
|
|
116
|
-
const initialize = () => ready ??= httpServer.initialize().catch((error) => {
|
|
117
|
-
ready = void 0;
|
|
118
|
-
throw error;
|
|
119
|
-
});
|
|
120
|
-
return async ({ reqCtx, next }) => {
|
|
121
|
-
await initialize();
|
|
122
|
-
const adapter = new PowertoolsAdapter(reqCtx.req);
|
|
123
|
-
const context = {
|
|
124
|
-
adapter,
|
|
125
|
-
path: adapter.getPath(),
|
|
126
|
-
method: adapter.getMethod()
|
|
127
|
-
};
|
|
128
|
-
const result = await httpServer.processHTTPRequest(context, options.paywall);
|
|
129
|
-
if (result.type === "no-payment-required") {
|
|
130
|
-
await next();
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (result.type === "payment-error") {
|
|
134
|
-
count(adapter.getHeader("payment-signature") ? "PaymentRejected" : "PaymentRequired");
|
|
135
|
-
logger?.debug("x402 payment not accepted", { status: result.response.status });
|
|
136
|
-
reqCtx.res = toResponse(result.response);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
const {
|
|
140
|
-
cancellationDispatcher,
|
|
141
|
-
beforeHandlerSettlement,
|
|
142
|
-
paymentPayload,
|
|
143
|
-
paymentRequirements,
|
|
144
|
-
declaredExtensions
|
|
145
|
-
} = result;
|
|
146
|
-
reqCtx.set("payment", {
|
|
147
|
-
network: paymentRequirements.network,
|
|
148
|
-
scheme: paymentRequirements.scheme,
|
|
149
|
-
asset: paymentRequirements.asset,
|
|
150
|
-
amount: paymentRequirements.amount,
|
|
151
|
-
payer: payers.get(paymentPayload)
|
|
152
|
-
});
|
|
153
|
-
count("PaymentVerified");
|
|
154
|
-
try {
|
|
155
|
-
await next();
|
|
156
|
-
} catch (error) {
|
|
157
|
-
await cancellationDispatcher.cancel({ reason: "handler_threw", error });
|
|
158
|
-
count("PaymentCancelled");
|
|
159
|
-
logger?.warn("x402 payment cancelled: handler threw", { path: context.path });
|
|
160
|
-
throw error;
|
|
161
|
-
}
|
|
162
|
-
if (reqCtx.res.status >= 400) {
|
|
163
|
-
await cancellationDispatcher.cancel({
|
|
164
|
-
reason: "handler_failed",
|
|
165
|
-
responseStatus: reqCtx.res.status
|
|
166
|
-
});
|
|
167
|
-
count("PaymentCancelled");
|
|
168
|
-
logger?.warn("x402 payment cancelled: handler failed", {
|
|
169
|
-
path: context.path,
|
|
170
|
-
status: reqCtx.res.status
|
|
171
|
-
});
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const settlement = await httpServer.processSettlement(
|
|
175
|
-
paymentPayload,
|
|
176
|
-
paymentRequirements,
|
|
177
|
-
declaredExtensions,
|
|
178
|
-
{
|
|
179
|
-
request: context,
|
|
180
|
-
responseBody: Buffer.from(await reqCtx.res.clone().arrayBuffer()),
|
|
181
|
-
responseHeaders: Object.fromEntries(reqCtx.res.headers.entries())
|
|
182
|
-
},
|
|
183
|
-
void 0,
|
|
184
|
-
beforeHandlerSettlement
|
|
185
|
-
);
|
|
186
|
-
if (!settlement.success) {
|
|
187
|
-
count("SettlementFailed");
|
|
188
|
-
logger?.error("x402 settlement failed", {
|
|
189
|
-
path: context.path,
|
|
190
|
-
errorReason: settlement.errorReason
|
|
191
|
-
});
|
|
192
|
-
reqCtx.res = toResponse(settlement.response);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
count("PaymentSettled");
|
|
196
|
-
logger?.debug("x402 payment settled", { path: context.path, transaction: settlement.transaction });
|
|
197
|
-
for (const [name, value] of Object.entries(settlement.headers)) {
|
|
198
|
-
reqCtx.res.headers.set(name, value);
|
|
199
|
-
}
|
|
200
|
-
const cacheControl = reqCtx.res.headers.get("cache-control");
|
|
201
|
-
reqCtx.res.headers.set("cache-control", cacheControl ? `${cacheControl}, private` : "private");
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return { paid, resourceServer };
|
|
205
|
-
}
|
|
2
|
+
CachingFacilitatorClient,
|
|
3
|
+
PowertoolsAdapter,
|
|
4
|
+
createX402
|
|
5
|
+
} from "./chunk-4ZP7AJJC.js";
|
|
206
6
|
export {
|
|
207
7
|
CachingFacilitatorClient,
|
|
208
8
|
PowertoolsAdapter,
|
package/dist/stripe.cjs
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
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 isBillable = async (predicate, reqCtx, path) => {
|
|
133
|
+
try {
|
|
134
|
+
return await predicate(reqCtx.res.clone(), reqCtx);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger?.error("x402 billable predicate threw", { path, error });
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const payers = /* @__PURE__ */ new WeakMap();
|
|
141
|
+
resourceServer.onAfterVerify(async ({ paymentPayload, result }) => {
|
|
142
|
+
if (result.payer) payers.set(paymentPayload, result.payer);
|
|
143
|
+
});
|
|
144
|
+
function paid(route) {
|
|
145
|
+
const { price, accepts, onProtectedRequest, billable, ...routeConfig } = route;
|
|
146
|
+
if (accepts === void 0 && price === void 0) {
|
|
147
|
+
throw new Error("paid() requires a price or an accepts configuration");
|
|
148
|
+
}
|
|
149
|
+
const httpServer = new import_server.x402HTTPResourceServer(resourceServer, {
|
|
150
|
+
...routeConfig,
|
|
151
|
+
mimeType: routeConfig.mimeType ?? "application/json",
|
|
152
|
+
accepts: accepts ?? {
|
|
153
|
+
scheme: "exact",
|
|
154
|
+
network: options.network,
|
|
155
|
+
payTo: options.payTo,
|
|
156
|
+
price
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (onProtectedRequest) httpServer.onProtectedRequest(onProtectedRequest);
|
|
160
|
+
let ready;
|
|
161
|
+
const initialize = () => ready ??= httpServer.initialize().catch((error) => {
|
|
162
|
+
ready = void 0;
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
return async ({ reqCtx, next }) => {
|
|
166
|
+
await initialize();
|
|
167
|
+
const adapter = new PowertoolsAdapter(reqCtx.req);
|
|
168
|
+
const context = {
|
|
169
|
+
adapter,
|
|
170
|
+
path: adapter.getPath(),
|
|
171
|
+
method: adapter.getMethod()
|
|
172
|
+
};
|
|
173
|
+
const result = await httpServer.processHTTPRequest(context, options.paywall);
|
|
174
|
+
if (result.type === "no-payment-required") {
|
|
175
|
+
await next();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (result.type === "payment-error") {
|
|
179
|
+
count(adapter.getHeader("payment-signature") ? "PaymentRejected" : "PaymentRequired");
|
|
180
|
+
logger?.debug("x402 payment not accepted", { status: result.response.status });
|
|
181
|
+
reqCtx.res = toResponse(result.response);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const {
|
|
185
|
+
cancellationDispatcher,
|
|
186
|
+
beforeHandlerSettlement,
|
|
187
|
+
paymentPayload,
|
|
188
|
+
paymentRequirements,
|
|
189
|
+
declaredExtensions
|
|
190
|
+
} = result;
|
|
191
|
+
reqCtx.set("payment", {
|
|
192
|
+
network: paymentRequirements.network,
|
|
193
|
+
scheme: paymentRequirements.scheme,
|
|
194
|
+
asset: paymentRequirements.asset,
|
|
195
|
+
amount: paymentRequirements.amount,
|
|
196
|
+
payer: payers.get(paymentPayload)
|
|
197
|
+
});
|
|
198
|
+
count("PaymentVerified");
|
|
199
|
+
try {
|
|
200
|
+
await next();
|
|
201
|
+
} catch (error) {
|
|
202
|
+
await cancellationDispatcher.cancel({ reason: "handler_threw", error });
|
|
203
|
+
count("PaymentCancelled");
|
|
204
|
+
logger?.warn("x402 payment cancelled: handler threw", { path: context.path });
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
if (reqCtx.res.status >= 400) {
|
|
208
|
+
await cancellationDispatcher.cancel({
|
|
209
|
+
reason: "handler_failed",
|
|
210
|
+
responseStatus: reqCtx.res.status
|
|
211
|
+
});
|
|
212
|
+
count("PaymentCancelled");
|
|
213
|
+
logger?.warn("x402 payment cancelled: handler failed", {
|
|
214
|
+
path: context.path,
|
|
215
|
+
status: reqCtx.res.status
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (billable && !await isBillable(billable, reqCtx, context.path)) {
|
|
220
|
+
if (beforeHandlerSettlement) {
|
|
221
|
+
logger?.error("x402 billable ignored: flow settles before the handler", {
|
|
222
|
+
path: context.path
|
|
223
|
+
});
|
|
224
|
+
} else {
|
|
225
|
+
await cancellationDispatcher.cancel({ reason: "after_verify_aborted" });
|
|
226
|
+
count("PaymentNotBillable");
|
|
227
|
+
logger?.warn("x402 payment not billed: route declined", { path: context.path });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const settlement = await httpServer.processSettlement(
|
|
232
|
+
paymentPayload,
|
|
233
|
+
paymentRequirements,
|
|
234
|
+
declaredExtensions,
|
|
235
|
+
{
|
|
236
|
+
request: context,
|
|
237
|
+
responseBody: Buffer.from(await reqCtx.res.clone().arrayBuffer()),
|
|
238
|
+
responseHeaders: Object.fromEntries(reqCtx.res.headers.entries())
|
|
239
|
+
},
|
|
240
|
+
void 0,
|
|
241
|
+
beforeHandlerSettlement
|
|
242
|
+
);
|
|
243
|
+
if (!settlement.success) {
|
|
244
|
+
count("SettlementFailed");
|
|
245
|
+
logger?.error("x402 settlement failed", {
|
|
246
|
+
path: context.path,
|
|
247
|
+
errorReason: settlement.errorReason
|
|
248
|
+
});
|
|
249
|
+
reqCtx.res = toResponse(settlement.response);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
count("PaymentSettled");
|
|
253
|
+
logger?.debug("x402 payment settled", { path: context.path, transaction: settlement.transaction });
|
|
254
|
+
for (const [name, value] of Object.entries(settlement.headers)) {
|
|
255
|
+
reqCtx.res.headers.set(name, value);
|
|
256
|
+
}
|
|
257
|
+
const cacheControl = reqCtx.res.headers.get("cache-control");
|
|
258
|
+
reqCtx.res.headers.set("cache-control", cacheControl ? `${cacheControl}, private` : "private");
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return { paid, resourceServer };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/stripe.ts
|
|
265
|
+
var BASE_MAINNET = "eip155:8453";
|
|
266
|
+
var BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
267
|
+
var STRIPE_PREVIEW_API_VERSION = "2026-05-27.preview";
|
|
268
|
+
function createStripeX402(options = {}) {
|
|
269
|
+
const {
|
|
270
|
+
depositAddress = process.env.STRIPE_DEPOSIT_ADDRESS,
|
|
271
|
+
stripeSecretKey = process.env.STRIPE_SECRET_KEY,
|
|
272
|
+
stripe,
|
|
273
|
+
cdpApiKeyId,
|
|
274
|
+
cdpApiKeySecret,
|
|
275
|
+
facilitator,
|
|
276
|
+
...rest
|
|
277
|
+
} = options;
|
|
278
|
+
if (!depositAddress) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
"createStripeX402 requires a depositAddress (or STRIPE_DEPOSIT_ADDRESS). Create one with POST /v1/crypto/deposit_addresses."
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
if (!stripe && !stripeSecretKey) {
|
|
284
|
+
throw new Error("createStripeX402 requires a stripe client or stripeSecretKey (or STRIPE_SECRET_KEY).");
|
|
285
|
+
}
|
|
286
|
+
const stripeClient = stripe ?? new import_stripe.default(stripeSecretKey, {
|
|
287
|
+
apiVersion: STRIPE_PREVIEW_API_VERSION
|
|
288
|
+
});
|
|
289
|
+
const x402 = createX402({
|
|
290
|
+
...rest,
|
|
291
|
+
network: BASE_MAINNET,
|
|
292
|
+
facilitator: facilitator ?? coinbaseFacilitator(cdpApiKeyId, cdpApiKeySecret),
|
|
293
|
+
payTo: depositAddress
|
|
294
|
+
});
|
|
295
|
+
x402.resourceServer.onAfterSettle(async ({ result, requirements }) => {
|
|
296
|
+
if (!result.success || !result.transaction) return;
|
|
297
|
+
if (requirements.network !== BASE_MAINNET) return;
|
|
298
|
+
if (requirements.asset.toLowerCase() !== BASE_USDC.toLowerCase()) return;
|
|
299
|
+
const amountInCents = Math.round(Number(requirements.amount) / 1e4);
|
|
300
|
+
if (amountInCents < 1) return;
|
|
301
|
+
try {
|
|
302
|
+
await stripeClient.paymentIntents.create(
|
|
303
|
+
{
|
|
304
|
+
amount: amountInCents,
|
|
305
|
+
currency: "usd",
|
|
306
|
+
confirm: true,
|
|
307
|
+
payment_method_data: { type: "crypto" },
|
|
308
|
+
payment_method_types: ["crypto"],
|
|
309
|
+
payment_method_options: {
|
|
310
|
+
// transaction_verification ships in the 2026-05-27.preview API
|
|
311
|
+
// version and is not yet in stripe-node's stable types
|
|
312
|
+
crypto: {
|
|
313
|
+
mode: "transaction_verification",
|
|
314
|
+
transaction_verification_options: {
|
|
315
|
+
network: "base",
|
|
316
|
+
transaction_hash: result.transaction
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{ idempotencyKey: result.transaction }
|
|
322
|
+
);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
(rest.logger ?? console).error("stripe payment intent recording failed", {
|
|
325
|
+
transaction: result.transaction,
|
|
326
|
+
amountInCents,
|
|
327
|
+
error: error instanceof Error ? error.message : String(error)
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
return x402;
|
|
332
|
+
}
|
|
333
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
334
|
+
0 && (module.exports = {
|
|
335
|
+
createStripeX402
|
|
336
|
+
});
|
|
@@ -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 };
|