@armory-sh/middleware-express 0.3.11 → 0.4.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/package.json +11 -13
- package/src/index.ts +89 -0
- package/src/payment-utils.ts +168 -0
- package/src/types.ts +44 -0
- package/dist/index.d.ts +0 -43
- package/dist/index.js +0 -142
package/package.json
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@armory-sh/middleware-express",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"main": "./
|
|
8
|
-
"types": "./
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"types": "./
|
|
12
|
-
"
|
|
13
|
-
"default": "./dist/index.js"
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"default": "./src/index.ts"
|
|
14
13
|
}
|
|
15
14
|
},
|
|
16
15
|
"files": [
|
|
17
|
-
"
|
|
16
|
+
"src"
|
|
18
17
|
],
|
|
19
18
|
"publishConfig": {
|
|
20
19
|
"access": "public"
|
|
@@ -25,20 +24,19 @@
|
|
|
25
24
|
"directory": "packages/middleware-express"
|
|
26
25
|
},
|
|
27
26
|
"peerDependencies": {
|
|
28
|
-
"express": "^
|
|
27
|
+
"express": "^5"
|
|
29
28
|
},
|
|
30
29
|
"dependencies": {
|
|
31
|
-
"@armory-sh/base": "
|
|
32
|
-
"@armory-sh/facilitator": "0.2.12"
|
|
30
|
+
"@armory-sh/base": "workspace:*"
|
|
33
31
|
},
|
|
34
32
|
"devDependencies": {
|
|
35
33
|
"bun-types": "latest",
|
|
36
34
|
"typescript": "5.9.3",
|
|
37
|
-
"express": "^
|
|
38
|
-
"@types/express": "^
|
|
35
|
+
"express": "^5",
|
|
36
|
+
"@types/express": "^5"
|
|
39
37
|
},
|
|
40
38
|
"scripts": {
|
|
41
|
-
"build": "tsup",
|
|
39
|
+
"build": "rm -rf dist && tsup",
|
|
42
40
|
"test": "bun test"
|
|
43
41
|
}
|
|
44
42
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
import type {
|
|
3
|
+
PaymentPayloadV2,
|
|
4
|
+
PaymentRequirements,
|
|
5
|
+
} from "@armory-sh/base";
|
|
6
|
+
import {
|
|
7
|
+
decodePaymentV2,
|
|
8
|
+
createPaymentRequiredHeaders,
|
|
9
|
+
createSettlementHeaders,
|
|
10
|
+
PAYMENT_SIGNATURE_HEADER,
|
|
11
|
+
} from "@armory-sh/base";
|
|
12
|
+
|
|
13
|
+
export interface PaymentMiddlewareConfig {
|
|
14
|
+
requirements: PaymentRequirements;
|
|
15
|
+
facilitatorUrl?: string;
|
|
16
|
+
skipVerification?: boolean;
|
|
17
|
+
network?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AugmentedRequest extends Request {
|
|
21
|
+
payment?: {
|
|
22
|
+
payload: PaymentPayloadV2;
|
|
23
|
+
payerAddress: string;
|
|
24
|
+
verified: boolean;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const paymentMiddleware = (config: PaymentMiddlewareConfig) => {
|
|
29
|
+
const { requirements, facilitatorUrl, skipVerification = false, network = "base" } = config;
|
|
30
|
+
|
|
31
|
+
return async (req: AugmentedRequest, res: Response, next: NextFunction): Promise<void> => {
|
|
32
|
+
try {
|
|
33
|
+
const paymentHeader = req.headers[PAYMENT_SIGNATURE_HEADER.toLowerCase()] as string | undefined;
|
|
34
|
+
|
|
35
|
+
if (!paymentHeader) {
|
|
36
|
+
const requiredHeaders = createPaymentRequiredHeaders(requirements);
|
|
37
|
+
res.statusCode = 402;
|
|
38
|
+
for (const [key, value] of Object.entries(requiredHeaders)) {
|
|
39
|
+
res.setHeader(key, value);
|
|
40
|
+
}
|
|
41
|
+
res.json({
|
|
42
|
+
error: "Payment required",
|
|
43
|
+
accepts: [requirements],
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let paymentPayload: PaymentPayloadV2;
|
|
49
|
+
try {
|
|
50
|
+
paymentPayload = decodePaymentV2(paymentHeader);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
res.statusCode = 400;
|
|
53
|
+
res.json({
|
|
54
|
+
error: "Invalid payment payload",
|
|
55
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const payerAddress = paymentPayload.payload.authorization.from;
|
|
61
|
+
|
|
62
|
+
// TODO: Add verification with facilitator if not skipVerification
|
|
63
|
+
// if (!skipVerification && facilitatorUrl) {
|
|
64
|
+
// const result = await verifyWithFacilitator(...);
|
|
65
|
+
// if (!result.success) { ... }
|
|
66
|
+
// }
|
|
67
|
+
|
|
68
|
+
const settlement = {
|
|
69
|
+
success: true,
|
|
70
|
+
transaction: "",
|
|
71
|
+
network,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const settlementHeaders = createSettlementHeaders(settlement);
|
|
75
|
+
for (const [key, value] of Object.entries(settlementHeaders)) {
|
|
76
|
+
res.setHeader(key, value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
req.payment = { payload: paymentPayload, payerAddress, verified: !skipVerification };
|
|
80
|
+
next();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
res.statusCode = 500;
|
|
83
|
+
res.json({
|
|
84
|
+
error: "Payment middleware error",
|
|
85
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
X402PaymentPayload,
|
|
3
|
+
X402PaymentRequirements,
|
|
4
|
+
} from "@armory-sh/base";
|
|
5
|
+
import { decodePayment, isPaymentPayload, isExactEvmPayload } from "@armory-sh/base";
|
|
6
|
+
|
|
7
|
+
// Legacy V2 payload type for backward compatibility
|
|
8
|
+
export interface LegacyPaymentPayloadV2 {
|
|
9
|
+
to: string;
|
|
10
|
+
from: string;
|
|
11
|
+
amount: string;
|
|
12
|
+
chainId: string;
|
|
13
|
+
assetId: string;
|
|
14
|
+
nonce: string;
|
|
15
|
+
expiry: number;
|
|
16
|
+
signature: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Union type for V2 payload formats
|
|
20
|
+
export type AnyPaymentPayload = X402PaymentPayload | LegacyPaymentPayloadV2;
|
|
21
|
+
|
|
22
|
+
// Re-export types for use in index.ts
|
|
23
|
+
export type { X402PaymentRequirements as PaymentRequirements } from "@armory-sh/base";
|
|
24
|
+
|
|
25
|
+
export interface PaymentVerificationResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
payload?: AnyPaymentPayload;
|
|
28
|
+
payerAddress?: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// V2 hardcoded headers
|
|
33
|
+
export const PAYMENT_HEADERS = {
|
|
34
|
+
PAYMENT: "PAYMENT-SIGNATURE",
|
|
35
|
+
REQUIRED: "PAYMENT-REQUIRED",
|
|
36
|
+
RESPONSE: "PAYMENT-RESPONSE",
|
|
37
|
+
payment: "PAYMENT-SIGNATURE",
|
|
38
|
+
required: "PAYMENT-REQUIRED",
|
|
39
|
+
response: "PAYMENT-RESPONSE",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export const encodeRequirements = (requirements: X402PaymentRequirements): string =>
|
|
43
|
+
JSON.stringify(requirements);
|
|
44
|
+
|
|
45
|
+
function isLegacyV2(payload: unknown): payload is LegacyPaymentPayloadV2 {
|
|
46
|
+
return (
|
|
47
|
+
typeof payload === "object" &&
|
|
48
|
+
payload !== null &&
|
|
49
|
+
"chainId" in payload &&
|
|
50
|
+
"assetId" in payload &&
|
|
51
|
+
"signature" in payload &&
|
|
52
|
+
typeof (payload as LegacyPaymentPayloadV2).signature === "string"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const decodePayload = (
|
|
57
|
+
headerValue: string
|
|
58
|
+
): { payload: AnyPaymentPayload } => {
|
|
59
|
+
let payload: unknown;
|
|
60
|
+
|
|
61
|
+
// Try JSON first (for test compatibility)
|
|
62
|
+
if (headerValue.startsWith("{")) {
|
|
63
|
+
try {
|
|
64
|
+
payload = JSON.parse(headerValue);
|
|
65
|
+
} catch {
|
|
66
|
+
throw new Error("Invalid payment payload: not valid JSON");
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// Use core's decodePayment for proper Base64URL handling
|
|
70
|
+
try {
|
|
71
|
+
payload = decodePayment(headerValue);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error("Invalid payment payload: not valid Base64");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for x402 V2 format
|
|
78
|
+
if (isPaymentPayload(payload)) {
|
|
79
|
+
return { payload };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isLegacyV2(payload)) {
|
|
83
|
+
return { payload };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new Error("Unrecognized payment payload format");
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const extractPayerAddress = (payload: AnyPaymentPayload): string => {
|
|
90
|
+
// Check for x402 format with nested payload
|
|
91
|
+
if (isPaymentPayload(payload)) {
|
|
92
|
+
const p = payload.payload;
|
|
93
|
+
if (isExactEvmPayload(p) && p.authorization?.from) {
|
|
94
|
+
return p.authorization.from;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check for direct `from` field (legacy format)
|
|
99
|
+
if ("from" in payload && typeof payload.from === "string") {
|
|
100
|
+
return payload.from;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new Error("Unable to extract payer address from payload");
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const verifyWithFacilitator = async (
|
|
107
|
+
facilitatorUrl: string,
|
|
108
|
+
payload: AnyPaymentPayload,
|
|
109
|
+
requirements: X402PaymentRequirements
|
|
110
|
+
): Promise<PaymentVerificationResult> => {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(`${facilitatorUrl}/verify`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify({ payload, requirements }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const error = await response.json();
|
|
120
|
+
return { success: false, error: JSON.stringify(error) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = await response.json() as { success: boolean; error?: string; payerAddress?: string };
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
return { success: false, error: result.error ?? "Verification failed" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { success: true, payerAddress: result.payerAddress ?? "" };
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: error instanceof Error ? error.message : "Unknown facilitator error",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const verifyLocally = async (
|
|
138
|
+
payload: AnyPaymentPayload,
|
|
139
|
+
requirements: X402PaymentRequirements
|
|
140
|
+
): Promise<PaymentVerificationResult> => {
|
|
141
|
+
if (isLegacyV2(payload)) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: "Local verification not supported for legacy payload format. Use a facilitator.",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!isPaymentPayload(payload) || !isExactEvmPayload(payload.payload)) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
error: "Invalid payment payload format",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
payerAddress: payload.payload.authorization.from,
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const verifyPaymentWithRetry = async (
|
|
162
|
+
payload: AnyPaymentPayload,
|
|
163
|
+
requirements: X402PaymentRequirements,
|
|
164
|
+
facilitatorUrl?: string
|
|
165
|
+
): Promise<PaymentVerificationResult> =>
|
|
166
|
+
facilitatorUrl
|
|
167
|
+
? verifyWithFacilitator(facilitatorUrl, payload, requirements)
|
|
168
|
+
: verifyLocally(payload, requirements);
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Address, CAIP2ChainId, CAIPAssetId } from "@armory-sh/base";
|
|
2
|
+
|
|
3
|
+
export interface FacilitatorConfig {
|
|
4
|
+
url: string;
|
|
5
|
+
createHeaders?: () => Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type SettlementMode = "verify" | "settle" | "async";
|
|
9
|
+
export type PayToAddress = Address | CAIP2ChainId | CAIPAssetId;
|
|
10
|
+
|
|
11
|
+
export interface MiddlewareConfig {
|
|
12
|
+
payTo: PayToAddress;
|
|
13
|
+
network: string | number;
|
|
14
|
+
amount: string;
|
|
15
|
+
facilitator?: FacilitatorConfig;
|
|
16
|
+
settlementMode?: SettlementMode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface HttpRequest {
|
|
20
|
+
headers: Record<string, string | string[] | undefined>;
|
|
21
|
+
body?: unknown;
|
|
22
|
+
method?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface HttpResponse {
|
|
27
|
+
status: number;
|
|
28
|
+
headers: Record<string, string>;
|
|
29
|
+
body?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FacilitatorVerifyResult {
|
|
33
|
+
success: boolean;
|
|
34
|
+
payerAddress?: string;
|
|
35
|
+
balance?: string;
|
|
36
|
+
requiredAmount?: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface FacilitatorSettleResult {
|
|
41
|
+
success: boolean;
|
|
42
|
+
txHash?: string;
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { X402PaymentPayload, X402PaymentRequirements } from '@armory-sh/base';
|
|
3
|
-
import { VerifyPaymentOptions } from '@armory-sh/facilitator';
|
|
4
|
-
|
|
5
|
-
interface LegacyPaymentPayloadV1 {
|
|
6
|
-
amount: string;
|
|
7
|
-
network: string;
|
|
8
|
-
contractAddress: string;
|
|
9
|
-
payTo: string;
|
|
10
|
-
from: string;
|
|
11
|
-
expiry: number;
|
|
12
|
-
signature: string;
|
|
13
|
-
}
|
|
14
|
-
interface LegacyPaymentPayloadV2 {
|
|
15
|
-
to: string;
|
|
16
|
-
from: string;
|
|
17
|
-
amount: string;
|
|
18
|
-
chainId: string;
|
|
19
|
-
assetId: string;
|
|
20
|
-
nonce: string;
|
|
21
|
-
expiry: number;
|
|
22
|
-
signature: string;
|
|
23
|
-
}
|
|
24
|
-
type AnyPaymentPayload = X402PaymentPayload | LegacyPaymentPayloadV1 | LegacyPaymentPayloadV2;
|
|
25
|
-
type PaymentPayload = AnyPaymentPayload;
|
|
26
|
-
|
|
27
|
-
interface PaymentMiddlewareConfig {
|
|
28
|
-
requirements: X402PaymentRequirements;
|
|
29
|
-
facilitatorUrl?: string;
|
|
30
|
-
verifyOptions?: VerifyPaymentOptions;
|
|
31
|
-
skipVerification?: boolean;
|
|
32
|
-
}
|
|
33
|
-
interface AugmentedRequest extends Request {
|
|
34
|
-
payment?: {
|
|
35
|
-
payload: PaymentPayload;
|
|
36
|
-
payerAddress: string;
|
|
37
|
-
version: 1 | 2;
|
|
38
|
-
verified: boolean;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
declare const paymentMiddleware: (config: PaymentMiddlewareConfig) => (req: AugmentedRequest, res: Response, next: NextFunction) => Promise<void>;
|
|
42
|
-
|
|
43
|
-
export { type AugmentedRequest, type PaymentMiddlewareConfig, paymentMiddleware };
|
package/dist/index.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
// src/payment-utils.ts
|
|
2
|
-
import { extractPaymentFromHeaders, X402_HEADERS } from "@armory-sh/base";
|
|
3
|
-
import { verifyX402Payment as verifyPayment } from "@armory-sh/facilitator";
|
|
4
|
-
var getHeadersForVersion = (version) => version === 1 ? { payment: "X-PAYMENT", required: "X-PAYMENT-REQUIRED", response: "X-PAYMENT-RESPONSE" } : { payment: "PAYMENT-SIGNATURE", required: "PAYMENT-REQUIRED", response: "PAYMENT-RESPONSE" };
|
|
5
|
-
var getRequirementsVersion = (requirements) => "contractAddress" in requirements && "network" in requirements ? 1 : 2;
|
|
6
|
-
var encodeRequirements = (requirements) => JSON.stringify(requirements);
|
|
7
|
-
function isLegacyV1(payload) {
|
|
8
|
-
return typeof payload === "object" && payload !== null && "contractAddress" in payload && "network" in payload && "signature" in payload && typeof payload.signature === "string";
|
|
9
|
-
}
|
|
10
|
-
function isLegacyV2(payload) {
|
|
11
|
-
return typeof payload === "object" && payload !== null && "chainId" in payload && "assetId" in payload && "signature" in payload && typeof payload.signature === "string";
|
|
12
|
-
}
|
|
13
|
-
var decodePayload = (headerValue) => {
|
|
14
|
-
let parsed;
|
|
15
|
-
try {
|
|
16
|
-
if (headerValue.startsWith("{")) {
|
|
17
|
-
parsed = JSON.parse(headerValue);
|
|
18
|
-
} else {
|
|
19
|
-
parsed = JSON.parse(atob(headerValue));
|
|
20
|
-
}
|
|
21
|
-
} catch {
|
|
22
|
-
throw new Error("Invalid payment payload");
|
|
23
|
-
}
|
|
24
|
-
const headers = new Headers();
|
|
25
|
-
headers.set(X402_HEADERS.PAYMENT, headerValue);
|
|
26
|
-
const x402Payload = extractPaymentFromHeaders(headers);
|
|
27
|
-
if (x402Payload) {
|
|
28
|
-
return { payload: x402Payload, version: 2 };
|
|
29
|
-
}
|
|
30
|
-
if (isLegacyV1(parsed)) {
|
|
31
|
-
return { payload: parsed, version: 1 };
|
|
32
|
-
}
|
|
33
|
-
if (isLegacyV2(parsed)) {
|
|
34
|
-
return { payload: parsed, version: 2 };
|
|
35
|
-
}
|
|
36
|
-
throw new Error("Unrecognized payment payload format");
|
|
37
|
-
};
|
|
38
|
-
var verifyWithFacilitator = async (facilitatorUrl, payload, requirements, verifyOptions) => {
|
|
39
|
-
try {
|
|
40
|
-
const response = await fetch(`${facilitatorUrl}/verify`, {
|
|
41
|
-
method: "POST",
|
|
42
|
-
headers: { "Content-Type": "application/json" },
|
|
43
|
-
body: JSON.stringify({ payload, requirements, options: verifyOptions })
|
|
44
|
-
});
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
const error = await response.json();
|
|
47
|
-
return { success: false, error: JSON.stringify(error) };
|
|
48
|
-
}
|
|
49
|
-
const result = await response.json();
|
|
50
|
-
if (!result.success) {
|
|
51
|
-
return { success: false, error: result.error ?? "Verification failed" };
|
|
52
|
-
}
|
|
53
|
-
return { success: true, payerAddress: result.payerAddress ?? "" };
|
|
54
|
-
} catch (error) {
|
|
55
|
-
return {
|
|
56
|
-
success: false,
|
|
57
|
-
error: error instanceof Error ? error.message : "Unknown facilitator error"
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
var verifyLocally = async (payload, requirements, verifyOptions) => {
|
|
62
|
-
if (isLegacyV1(payload) || isLegacyV2(payload)) {
|
|
63
|
-
return {
|
|
64
|
-
success: false,
|
|
65
|
-
error: "Local verification not supported for legacy payload formats. Use a facilitator."
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
const result = await verifyPayment(payload, requirements, verifyOptions);
|
|
69
|
-
if (!result.success) {
|
|
70
|
-
return {
|
|
71
|
-
success: false,
|
|
72
|
-
error: JSON.stringify({
|
|
73
|
-
error: "Payment verification failed",
|
|
74
|
-
reason: result.error.name,
|
|
75
|
-
message: result.error.message
|
|
76
|
-
})
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
return { success: true, payerAddress: result.payerAddress };
|
|
80
|
-
};
|
|
81
|
-
var verifyPaymentWithRetry = async (payload, requirements, facilitatorUrl, verifyOptions) => facilitatorUrl ? verifyWithFacilitator(facilitatorUrl, payload, requirements, verifyOptions) : verifyLocally(payload, requirements, verifyOptions);
|
|
82
|
-
var extractPayerAddress = (payload) => {
|
|
83
|
-
if ("payload" in payload) {
|
|
84
|
-
const x402Payload = payload;
|
|
85
|
-
if ("authorization" in x402Payload.payload) {
|
|
86
|
-
return x402Payload.payload.authorization.from;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if ("from" in payload && typeof payload.from === "string") {
|
|
90
|
-
return payload.from;
|
|
91
|
-
}
|
|
92
|
-
throw new Error("Unable to extract payer address from payload");
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// src/index.ts
|
|
96
|
-
var sendError = (res, status, headers, body) => {
|
|
97
|
-
res.status(status);
|
|
98
|
-
Object.entries(headers).forEach(([k, v]) => res.setHeader(k, v));
|
|
99
|
-
res.json(body);
|
|
100
|
-
};
|
|
101
|
-
var paymentMiddleware = (config) => {
|
|
102
|
-
const { requirements, facilitatorUrl, verifyOptions, skipVerification = false } = config;
|
|
103
|
-
const version = getRequirementsVersion(requirements);
|
|
104
|
-
const headers = getHeadersForVersion(version);
|
|
105
|
-
return async (req, res, next) => {
|
|
106
|
-
try {
|
|
107
|
-
const paymentHeader = req.headers[headers.payment.toLowerCase()];
|
|
108
|
-
if (!paymentHeader) {
|
|
109
|
-
sendError(res, 402, { [headers.required]: encodeRequirements(requirements), "Content-Type": "application/json" }, { error: "Payment required", requirements });
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
let payload;
|
|
113
|
-
let payloadVersion;
|
|
114
|
-
try {
|
|
115
|
-
({ payload, version: payloadVersion } = decodePayload(paymentHeader));
|
|
116
|
-
} catch (error) {
|
|
117
|
-
sendError(res, 400, {}, { error: "Invalid payment payload", message: error instanceof Error ? error.message : "Unknown error" });
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (payloadVersion !== version) {
|
|
121
|
-
sendError(res, 400, {}, { error: "Payment version mismatch", expected: version, received: payloadVersion });
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const payerAddress = skipVerification ? extractPayerAddress(payload) : await (async () => {
|
|
125
|
-
const result = await verifyPaymentWithRetry(payload, requirements, facilitatorUrl, verifyOptions);
|
|
126
|
-
if (!result.success) {
|
|
127
|
-
sendError(res, 402, { [headers.required]: encodeRequirements(requirements) }, { error: result.error });
|
|
128
|
-
throw new Error("Verification failed");
|
|
129
|
-
}
|
|
130
|
-
return result.payerAddress;
|
|
131
|
-
})();
|
|
132
|
-
req.payment = { payload, payerAddress, version, verified: !skipVerification };
|
|
133
|
-
res.setHeader(headers.response, JSON.stringify({ status: "verified", payerAddress, version }));
|
|
134
|
-
next();
|
|
135
|
-
} catch (error) {
|
|
136
|
-
sendError(res, 500, {}, { error: "Payment middleware error", message: error instanceof Error ? error.message : "Unknown error" });
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
export {
|
|
141
|
-
paymentMiddleware
|
|
142
|
-
};
|