Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 3 additions & 30 deletions typescript/packages/http/hono/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,6 @@ let mockProcessSettlement: ReturnType<typeof vi.fn>;
let mockRegisterPaywallProvider: ReturnType<typeof vi.fn>;
let mockRequiresPayment: ReturnType<typeof vi.fn>;

type PaymentVerifiedResult = Extract<HTTPProcessResult, { type: "payment-verified" }>;
type MockHTTPProcessResult =
| Exclude<HTTPProcessResult, PaymentVerifiedResult>
| (Omit<PaymentVerifiedResult, "cancellationDispatcher"> & {
cancellationDispatcher?: PaymentVerifiedResult["cancellationDispatcher"];
});

/**
* Creates a mock payment cancellation dispatcher.
*
* @returns Mock payment cancellation dispatcher.
*/
function createMockPaymentCancellationDispatcher(): PaymentVerifiedResult["cancellationDispatcher"] {
return {
cancel: vi.fn().mockResolvedValue(undefined),
} as unknown as PaymentVerifiedResult["cancellationDispatcher"];
}

vi.mock("@x402/core/server", () => ({
SETTLEMENT_OVERRIDES_HEADER: "Settlement-Overrides",
FacilitatorResponseError: class FacilitatorResponseError extends Error {
Expand Down Expand Up @@ -99,7 +81,6 @@ vi.mock("@x402/core/server", () => ({
registerExtension: vi.fn(),
},
})),
checkIfBazaarNeeded: vi.fn().mockReturnValue(false),
}));

// --- Mock Factories ---
Expand All @@ -110,7 +91,7 @@ vi.mock("@x402/core/server", () => ({
* @param settlementResult - Result to return from processSettlement.
*/
function setupMockHttpServer(
processResult: MockHTTPProcessResult,
processResult: HTTPProcessResult,
settlementResult:
| { success: true; headers: Record<string, string> }
| {
Expand All @@ -123,15 +104,7 @@ function setupMockHttpServer(
headers: {},
},
): void {
const normalizedResult =
processResult.type === "payment-verified"
? {
...processResult,
cancellationDispatcher:
processResult.cancellationDispatcher ?? createMockPaymentCancellationDispatcher(),
}
: processResult;
mockProcessHTTPRequest.mockResolvedValue(normalizedResult);
mockProcessHTTPRequest.mockResolvedValue(processResult);
mockProcessSettlement.mockResolvedValue(settlementResult);
}

Expand Down Expand Up @@ -475,7 +448,7 @@ describe("paymentMiddleware", () => {
);
mockProcessHTTPRequest.mockResolvedValue({ type: "no-payment-required" });

const middleware = paymentMiddleware(mockRoutes, {} as unknown as x402ResourceServer);
const middleware = paymentMiddleware(mockRoutes, {} as unknown as x402ResourceServer, undefined, undefined, true);
const next = vi.fn().mockResolvedValue(undefined);

await middleware(createMockContext(), next);
Expand Down
48 changes: 26 additions & 22 deletions typescript/packages/http/hono/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
getFacilitatorResponseError,
SETTLEMENT_OVERRIDES_HEADER,
SettlementOverrides,
checkIfBazaarNeeded,
} from "@x402/core/server";
import { SchemeNetworkServer, Network } from "@x402/core/types";
import { Context, MiddlewareHandler } from "hono";
Expand All @@ -27,6 +26,24 @@ export function setSettlementOverrides(c: Context, overrides: SettlementOverride
c.header(SETTLEMENT_OVERRIDES_HEADER, JSON.stringify(overrides));
}

/**
* Check if any routes in the configuration declare bazaar extensions
*
* @param routes - Route configuration
* @returns True if any route has extensions.bazaar defined
*/
function checkIfBazaarNeeded(routes: RoutesConfig): boolean {
// Handle single route config
if ("accepts" in routes) {
return !!(routes.extensions && "bazaar" in routes.extensions);
}

// Handle multiple routes
return Object.values(routes).some(routeConfig => {
return !!(routeConfig.extensions && "bazaar" in routeConfig.extensions);
});
}

/**
* Configuration for registering a payment scheme with a specific network
*/
Expand Down Expand Up @@ -61,7 +78,7 @@ function facilitatorErrorResponse(c: Context, error: FacilitatorResponseError):
* @param httpServer - Pre-configured x402HTTPResourceServer instance
* @param paywallConfig - Optional configuration for the built-in paywall UI
* @param paywall - Optional custom paywall provider (overrides default)
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to false). Set to true only if your deployment environment guarantees warm instances (e.g., long-running servers). For serverless/edge environments (Cloudflare Workers, AWS Lambda), keep false to avoid cold-start timeouts.
* @returns Hono middleware handler
*
* @example
Expand All @@ -81,7 +98,7 @@ export function paymentMiddlewareFromHTTPServer(
httpServer: x402HTTPResourceServer,
paywallConfig?: PaywallConfig,
paywall?: PaywallProvider,
syncFacilitatorOnStart: boolean = true,
syncFacilitatorOnStart: boolean = false,
): MiddlewareHandler {
// Register custom paywall provider if provided
if (paywall) {
Expand Down Expand Up @@ -192,29 +209,16 @@ export function paymentMiddlewareFromHTTPServer(

case "payment-verified":
// Payment is valid, need to wrap response for settlement
const { cancellationDispatcher, paymentPayload, paymentRequirements, declaredExtensions } =
result;
const { paymentPayload, paymentRequirements, declaredExtensions } = result;

// Proceed to the next middleware or route handler
try {
await next();
} catch (error) {
await cancellationDispatcher.cancel({
reason: "handler_threw",
error,
});
throw error;
}
await next();

// Get the current response
let res = c.res;

// If the response from the protected route is >= 400, do not settle payment
if (res.status >= 400) {
await cancellationDispatcher.cancel({
reason: "handler_failed",
responseStatus: res.status,
});
return;
}

Expand Down Expand Up @@ -282,7 +286,7 @@ export function paymentMiddlewareFromHTTPServer(
* @param server - Pre-configured x402ResourceServer instance
* @param paywallConfig - Optional configuration for the built-in paywall UI
* @param paywall - Optional custom paywall provider (overrides default)
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to false). Set to true only if your deployment environment guarantees warm instances (e.g., long-running servers). For serverless/edge environments (Cloudflare Workers, AWS Lambda), keep false to avoid cold-start timeouts.
* @returns Hono middleware handler
*
* @example
Expand All @@ -300,7 +304,7 @@ export function paymentMiddleware(
server: x402ResourceServer,
paywallConfig?: PaywallConfig,
paywall?: PaywallProvider,
syncFacilitatorOnStart: boolean = true,
syncFacilitatorOnStart: boolean = false,
): MiddlewareHandler {
// Create the x402 HTTP server instance with the resource server
const httpServer = new x402HTTPResourceServer(server, routes);
Expand All @@ -324,7 +328,7 @@ export function paymentMiddleware(
* @param schemes - Optional array of scheme registrations for server-side payment processing
* @param paywallConfig - Optional configuration for the built-in paywall UI
* @param paywall - Optional custom paywall provider (overrides default)
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to false). Set to true only if your deployment environment guarantees warm instances (e.g., long-running servers). For serverless/edge environments (Cloudflare Workers, AWS Lambda), keep false to avoid cold-start timeouts.
* @returns Hono middleware handler
*
* @example
Expand All @@ -345,7 +349,7 @@ export function paymentMiddlewareFromConfig(
schemes?: SchemeRegistration[],
paywallConfig?: PaywallConfig,
paywall?: PaywallProvider,
syncFacilitatorOnStart: boolean = true,
syncFacilitatorOnStart: boolean = false,
): MiddlewareHandler {
const ResourceServer = new x402ResourceServer(facilitatorClients);

Expand Down
Loading