> ## Documentation Index
> Fetch the complete documentation index at: https://docs.x402r.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Merchant Server Quickstart

> Accept escrow-backed refundable payments on your Express server in 5 minutes

This guide walks you through setting up an Express server that accepts x402r escrow-backed payments. By the end, you'll have a paid API endpoint protected by the x402 payment middleware with refundable escrow support.

## Prerequisites

* Node.js 20+
* A deployed PaymentOperator contract ([Deploy Operator](/sdk/deploy-operator))
* A running facilitator service
* Base Sepolia ETH for testing

## Setup

<Steps>
  <Step title="Create project and install dependencies">
    ```bash theme={null}
    mkdir merchant-server && cd merchant-server
    npm init -y
    npm install express @x402/core @x402/express @x402r/evm dotenv
    ```
  </Step>

  <Step title="Configure environment variables">
    Create a `.env` file in the project root:

    ```bash theme={null}
    # Replace ADDRESS with your merchant address.
    ADDRESS=0x321651df4593DA57C413579c5b611D1A90168a3A
    # Replace OPERATOR_ADDRESS with the operator you deployed.
    OPERATOR_ADDRESS=0xa0d4734842df1690a5B33Cb21828c946e39D55a2
    FACILITATOR_URL=http://localhost:4022
    ```
  </Step>

  <Step title="Create the server">
    Create `index.ts`:

    ```typescript theme={null}
    import "dotenv/config";
    import express from "express";
    import { paymentMiddleware, x402ResourceServer } from "@x402/express";
    import { AuthCaptureEvmScheme } from "@x402r/evm/auth-capture/server";
    import { getChainConfig } from "@x402r/core";
    import { HTTPFacilitatorClient } from "@x402/core/server";

    const address = process.env.ADDRESS as `0x${string}`;
    const operatorAddress = process.env.OPERATOR_ADDRESS as `0x${string}`;
    if (!address || !operatorAddress) {
      console.error("Missing required environment variables: ADDRESS, OPERATOR_ADDRESS");
      process.exit(1);
    }

    const facilitatorUrl = process.env.FACILITATOR_URL;
    if (!facilitatorUrl) {
      console.error("FACILITATOR_URL environment variable is required");
      process.exit(1);
    }
    const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });

    const networkId = "eip155:84532";

    const app = express();

    const now = Math.floor(Date.now() / 1000);

    app.use(
      paymentMiddleware(
        {
          "GET /weather": {
            accepts: [
              {
                scheme: "auth-capture",
                price: "$0.01",
                network: networkId,
                payTo: address,
                maxTimeoutSeconds: 60,
                extra: {
                  name: "USDC",
                  version: "2",
                  captureAuthorizer: operatorAddress,
                  captureDeadline: now + 60 * 60,         // capture within 1 hour
                  refundDeadline: now + 24 * 60 * 60,     // refund window 24 hours
                  feeRecipient: operatorAddress,
                  minFeeBps: 0,
                  maxFeeBps: 500,
                  // assetTransferMethod defaults to "eip3009"
                  // autoCapture defaults to false (two-phase)
                },
              },
            ],
            description: "Weather data",
            mimeType: "application/json",
          },
        },
        new x402ResourceServer(facilitatorClient).register(
          networkId,
          new AuthCaptureEvmScheme(),
        ),
      ),
    );

    app.get("/weather", (req, res) => {
      res.send({
        report: { weather: "sunny", temperature: 70 },
      });
    });

    app.listen(4021, () => {
      console.log("Server listening at http://localhost:4021");
    });
    ```
  </Step>

  <Step title="Start the server">
    ```bash theme={null}
    npx tsx index.ts
    ```

    You should see:

    ```
    Server listening at http://localhost:4021
    ```
  </Step>

  <Step title="Test the endpoint">
    ```bash theme={null}
    curl http://localhost:4021/weather
    ```

    Without a valid payment header, the server responds with HTTP 402 and the auth-capture payment requirements:

    ```json theme={null}
    {
      "x402Version": 2,
      "accepts": [{
        "scheme": "auth-capture",
        "price": "$0.01",
        "network": "eip155:84532",
        "payTo": "0x...",
        "maxTimeoutSeconds": 60,
        "extra": {
          "name": "USDC",
          "version": "2",
          "captureAuthorizer": "0x...",
          "captureDeadline": 1740758554,
          "refundDeadline": 1741276954,
          "feeRecipient": "0x...",
          "minFeeBps": 0,
          "maxFeeBps": 500
        }
      }]
    }
    ```
  </Step>
</Steps>

## How it works

* **`extra` config** declares the captureAuthorizer, capture/refund deadlines, fee recipient, and fee bounds. The canonical `AuthCaptureEscrow` and token collector addresses are universal CREATE2 deploys, so routes do not need to repeat them.
* **`AuthCaptureEvmScheme`** registers the auth-capture payment scheme with the x402 resource server so it can verify auth-capture-backed payments.
* **`paymentMiddleware`** intercepts requests, checks for a valid payment header, and returns 402 when the caller has not provided one.
* **`HTTPFacilitatorClient`** connects to the facilitator service that verifies and settles payments on-chain.

## Next Steps

<CardGroup cols={2}>
  <Card title="forwardToArbiter()" icon="wrench" href="/sdk/helpers/forward-to-arbiter">
    Forward escrow settlements to an arbiter service.
  </Card>

  <Card title="Merchant SDK" icon="store" href="/sdk/merchant/quickstart">
    Capture payments, handle refunds, and manage escrow.
  </Card>

  <Card title="Deploy Operator" icon="rocket" href="/sdk/deploy-operator">
    Deploy your own PaymentOperator contract.
  </Card>
</CardGroup>
