# E-Commerce Crypto Checkout

> Accept crypto at checkout with the embedded SDK and order webhooks.

Section: Build with AI
Source: https://coincircuit.io/docs/guides/ecommerce-checkout/
Interactive version: https://coincircuit.io/api-reference?tab=guides&guide=ecommerce-checkout

Tags: Checkout SDK, Webhooks, Node.js

Paste this prompt into Claude, ChatGPT, or any AI tool. It works best with the CoinCircuit MCP server connected: https://mcp.coincircuit.io

---

You are helping me integrate CoinCircuit crypto payments into my existing e-commerce website. I have the CoinCircuit MCP server connected.

**If you have access to the CoinCircuit MCP server, call these tools for the most accurate and detailed schema outputs:**
- Call `get_api_overview` in the CoinCircuit MCP for base URLs, auth method, and available features.
- Call `get_endpoint` with method `post` and path `/api/v1/payments` to get the full request/response schema for creating payment sessions.
- Call `search_api` with query `webhook` and type `schemas` to get all webhook payload schemas.
- Call `get_schema` with name `PaymentCompletedWebhookDto` for the exact payment.completed webhook payload.
- Call `get_schema` with name `TransactionReceivedWebhookDto` for the transaction.received webhook payload.

Use the live MCP data as your source of truth. The details below are a guide, but if the MCP returns something different, trust the MCP.

## API Basics

- **Base URL (production):** `https://api.coincircuit.io`
- **Base URL (sandbox):** `https://sandbox-api.coincircuit.io`
- **Auth:** Pass your API key in the `x-api-key` header on every request.

## Project Context

I have an e-commerce site and I'm adding crypto payments at checkout using CoinCircuit. The flow:

1. Customer clicks "Pay with Crypto" on my checkout page
2. My server creates a CoinCircuit payment session via `POST /api/v1/payments`
3. The embedded checkout modal opens on the frontend using the session `reference`
4. Customer pays, my server gets a webhook, and the order is fulfilled

## What I Need You to Implement

### 1. Server-side: Create a payment session

**Endpoint:** `POST /api/v1/payments`
*(Call `get_endpoint` with method `post`, path `/api/v1/payments`, section `example` in the CoinCircuit MCP for a ready-to-use sample request.)*

**Required fields:**
- `title` (string) - e.g. "Order #1234"
- `description` (string) - e.g. "Payment for order #1234"
- `amount` (string) - fiat amount as a string, e.g. `"150.00"` (NOT a number)
- `currency` (string) - `"NGN"` or `"USD"`
- `customer` (object) - must include `email` (required). Optional: `firstName`, `lastName`, `phone` (E.164 format), `telegramId`

**Optional fields:**
- `asset` (string) - lock to a specific crypto: `"BTC"`, `"ETH"`, `"USDT"`, `"USDC"`, `"SOL"`, `"BNB"`, `"TRX"`. If omitted, the customer chooses on the checkout page.
- `chain` (string) - lock to a specific blockchain: `"bitcoin"`, `"ethereum"`, `"solana"`, `"bsc"`, `"tron"`, `"base"`, `"arbitrum"`. If omitted, the customer chooses.
- `metadata` (object) - store your internal `orderId` here for webhook reconciliation
- `successUrl` (string) - redirect after successful payment (must be HTTPS)
- `cancelUrl` (string) - redirect if customer cancels
- `webhookUrl` (string) - per-session webhook URL override (must be HTTPS)
- `feePaidBy` (string) - `"customer"` or `"merchant"`, controls who pays network gas fees. If omitted, uses your dashboard default.

**Example request body:**
```json
{
  "title": "Order #1234",
  "description": "2x Widget Pro",
  "amount": "150.00",
  "currency": "USD",
  "customer": {
    "email": "buyer@example.com",
    "firstName": "Jane",
    "lastName": "Smith"
  },
  "metadata": {
    "orderId": "ORD-1234",
    "items": "2x Widget Pro"
  },
  "successUrl": "https://mystore.com/order/1234/success",
  "cancelUrl": "https://mystore.com/cart"
}
```

**Response (201):** Returns a session object. The key fields you need:
- `data.reference` - pass this to the frontend checkout SDK
- `data.url` - hosted checkout page URL (e.g. `https://checkout.coincircuit.io/pay/cs_ref_abc123`)
- `data.payment.status` - starts as `"pending"`
- `data.payment.address` - the deposit address (if asset/chain were specified)
- `data.expiresAt` - ISO 8601 expiration timestamp

*(Call `get_endpoint` with method `post`, path `/api/v1/payments`, section `success` in the CoinCircuit MCP for the full response schema.)*

**Error responses:** 400 (invalid input or unsupported asset/chain combo), 401 (bad API key)

### 2. Client-side: Embedded checkout

Install the checkout SDK:

```bash
npm install @coincircuit/checkout
```

Use `CoinCircuitCheckout` to open a modal with the session `reference` from the server response. Handle these callbacks:
- `onPaymentComplete` - show success state, redirect to order confirmation
- `onPaymentFailed` - show error, offer retry
- `onClose` - user dismissed the modal without paying

**Alternative:** Skip the SDK entirely and redirect the customer to `data.url` from the session response. This is the hosted checkout page and requires zero frontend code.

### 3. Webhook handler: Order fulfillment

Set up an endpoint to receive CoinCircuit webhooks.

*(Call `search_api` with query `payment webhook` and type `schemas` in the CoinCircuit MCP to list all payment and transaction webhook schemas. Then call `get_schema` on any specific one for full field details.)*

**Payment events** (envelope: `{ event: string, data: { session: PaymentSession, failureReason?: string } }`):
- `payment.completed` - full payment confirmed. The `data.session` object contains the full session with `payment.status: "completed"`, `payment.amountReceived`, and a `transaction` object with `txHash`, `chain`, `asset`, `fromAddress`, `toAddress`, `amount`, `explorerUrl`, `confirmations`. **This is your trigger to fulfill the order.**
- `payment.partial` - customer sent crypto but not enough. `data.session.payment.amountReceived` shows what you got. The session stays open for more payments.
- `payment.expired` - session expired before full payment. Cancel or hold the order.
- `payment.underpaid` - session closed with less than the required amount. Check `data.session.payment.amountReceived` vs `data.session.payment.amount`.

*(Call `get_schema` with name `PaymentCompletedWebhookDto` in the CoinCircuit MCP for every field in the payment.completed payload.)*

**Transaction events** (envelope: `{ event: string, data: { session: PaymentSession } }`, the session includes a `transaction` field):
- `transaction.received` - payment detected on-chain but unconfirmed. Good for showing "payment detected" UI. The `transaction` object has `status: "pending"`, `txHash`, `chain`, `asset`, `amount`, `fromAddress`, `toAddress`.
- `transaction.confirmed` - payment confirmed on the blockchain. The `transaction` object has `status: "confirmed"`, `confirmations`, `confirmedAt`.

*(Call `get_schema` with name `TransactionReceivedWebhookDto` or `TransactionConfirmedWebhookDto` in the CoinCircuit MCP for exact field details.)*

**Webhook security:**
- Verify the HMAC-SHA256 signature in the `x-coincircuit-signature` header using your webhook secret
- Use the `x-coincircuit-delivery-id` header for idempotency (store processed delivery IDs and skip duplicates)

**Key webhook fields for reconciliation:**
- `data.session.reference` - matches the session you created
- `data.session.metadata` - your `orderId` is here
- `data.session.settlements.net.amount` - the amount you actually receive after all fees

## Constraints

- `amount` is a **string** in fiat, not a number
- Always verify webhook signatures before processing
- Store the session `reference` alongside the order for reconciliation
- Handle partial payments gracefully. If the session state is `"open"`, the customer can still make additional payments. If `"closed"`, no further payments are accepted.
- `successUrl` and `cancelUrl` must be HTTPS
- Supported currencies: NGN, USD. Supported assets: BTC, ETH, USDT, USDC, SOL, BNB, TRX. Supported chains: bitcoin, ethereum, solana, bsc, tron, base, arbitrum.
