DirectCryptoPay Docs

Developer Integration (Snippet)

The DirectCryptoPay widget gives developers full programmatic control over the payment flow. Load the widget script, and call DCP.Payment() with your Integration ID and amount. The widget handles wallet connection, token selection, transaction signing, gas estimation, and confirmation.

Looking for a simpler approach? If you have fixed prices and don't need JavaScript callbacks, use Payment Tools (No-Code) instead. See Choose Your Method for a comparison.

Quick Start#

Step 1: Add the Script#

<script src="https://api.directcryptopay.com/widget/dcp-widget.umd.js"></script>

This exposes the global window.DCP object with all payment methods.

Step 2: Trigger a Payment#

<button id="pay-button">Pay $49.99 with Crypto</button>

<script>
  document.getElementById('pay-button').addEventListener('click', function() {
    DCP.Payment({
      integrationId: 'YOUR_INTEGRATION_ID',
      amount_usd: '49.99',
      metadata: { orderId: 'ORD-12345' },
      onTxSubmitted: function(txHash) {
        console.log('Transaction submitted:', txHash);
      },
      onStatus: function(status) {
        if (status.type === 'confirmed') {
          window.location.href = '/order-confirmation';
        }
      },
      onError: function(error) {
        console.error('Payment failed:', error);
      }
    });
  });
</script>

That's the complete integration. The widget automatically connects the wallet, detects the network, shows a token selector, and handles transaction signing and confirmation — all based on your integration's configuration.

Integration ID: Create an integration in your DCP Dashboard > Integrations. Click Get Code to see your Integration ID and ready-to-use code snippets. The Integration ID encapsulates your merchant wallet, accepted chains, and token preferences.

Global API Reference#

The widget script (dcp-widget.umd.js) exposes window.DCP with the following methods:

Method Description Use Case
DCP.Payment(params) Integration-based payment (recommended) E-commerce, SaaS billing
DCP.payWithTool(params) Payment Tool based payment with token selector No-code tools via JS
DCP.init(config) Initialize with API key and base URL Advanced / headless usage
DCP.pay(options) Low-level payment with pre-created intent Backend-created intents
DCP.payUSD(options) USD payment with token selection Legacy
The global variable is DCP, not DirectCryptoPay. After loading the script, use DCP.Payment(), DCP.init(), etc.

Complete Example#

Here is a full HTML page with the widget integrated:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Checkout - My Store</title>
  <script src="https://api.directcryptopay.com/widget/dcp-widget.umd.js"></script>
</head>
<body>
  <h1>Complete Your Purchase</h1>
  <p>Order Total: $49.99</p>

  <button id="pay-button">Pay with Crypto</button>

  <script>
    document.getElementById('pay-button').addEventListener('click', function() {
      DCP.Payment({
        integrationId: 'YOUR_INTEGRATION_ID',
        amount_usd: '49.99',
        metadata: {
          orderId: 'ORD-12345',
          customerEmail: '[email protected]'
        },
        onTxSubmitted: function(txHash) {
          console.log('Transaction submitted:', txHash);
        },
        onStatus: function(status) {
          console.log('Payment status:', status.type);
          if (status.type === 'confirmed') {
            window.location.href = '/order-confirmation';
          }
        },
        onError: function(error) {
          console.error('Payment failed:', error);
          alert('Payment failed: ' + error.message);
        },
        onCancel: function() {
          console.log('Payment cancelled by user');
        }
      });
    });
  </script>
</body>
</html>

`DCP.Payment(params)` Reference#

This is the recommended method for integration-based payments. No DCP.init() call is required -- it auto-initializes with the default API URL (https://api.directcryptopay.com).

Parameter Type Required Description
integrationId string Yes Your integration ID (e.g., int_xxxxxxxxxxxx)
amount_usd string Yes* Amount in USD (e.g., "49.99")
amount string | number Yes* Amount in token units (alternative to amount_usd)
currency string No Token symbol: "USDC", "USDT", "ETH", etc. Not a fiat currency -- do not pass "USD". If omitted, shows a token selector.
chainId number No Blockchain chain ID (see table below). If omitted, auto-detects from the connected wallet.
metadata object No Custom key-value pairs passed to your webhook
onTxSubmitted function No Called when the transaction is signed and submitted
onStatus function No Called on status changes (submitted, confirmed, failed)
onError function No Called if the payment fails or is rejected
onCancel function No Called when the user cancels

*Provide either amount_usd or amount, not both.

Automatic Token Selection#

When currency and chainId are omitted, the widget automatically:

  1. Fetches your integration config (allowed chains and tokens)
  2. Opens the wallet picker (or auto-selects if only one wallet is available)
  3. Detects the wallet's current chain — if it's an allowed chain, uses it; otherwise auto-switches to the first allowed chain
  4. Shows a token selector with real wallet balances for available tokens
  5. Creates the payment intent with the user's selection

No network selector is shown. The chain is determined automatically from the wallet's current network, keeping the flow fast and simple.

// Minimal integration - widget handles everything
DCP.Payment({
  integrationId: 'int_xxxxxxxxxxxx',
  amount_usd: '49.99',
  metadata: { orderId: 'ORD-12345' }
});

Fixed Token Mode#

If you want to force a specific chain and token (skip the selector), provide both currency and chainId:

// Fixed token - no selector shown
DCP.Payment({
  integrationId: 'int_xxxxxxxxxxxx',
  amount_usd: '49.99',
  currency: 'USDC',
  chainId: 137,       // Polygon Mainnet
  metadata: { orderId: 'ORD-12345' }
});

Supported Chain IDs#

Chain Mainnet Testnet
Ethereum 1 11155111 (Sepolia)
Polygon 137 80002 (Amoy)
BNB Chain 56 97
Base 8453 84532 (Sepolia)
Arbitrum 42161 421614 (Sepolia)
Optimism 10 11155420 (Sepolia)
Avalanche 43114 43113 (Fuji)
Solana 900 901 (Devnet)
Tron 800 801 (Nile)
Ton 1000 1001 (Testnet)

Metadata#

The metadata field accepts any JSON-serializable object. Use it to attach order IDs, customer emails, or any reference data. It will be included in your webhook payload.

DCP.Payment({
  integrationId: 'int_xxxxxxxxxxxx',
  amount_usd: '99.99',
  metadata: {
    orderId: 'ORD-12345',
    productName: 'Premium Plan',
    customerEmail: '[email protected]',
    userId: 'usr_abc123'
  },
  onTxSubmitted: function(txHash) {
    console.log('TX:', txHash);
  }
});
onTxSubmitted does not mean the payment is confirmed. It means the customer signed the transaction and it was submitted to the blockchain. Final confirmation comes via the webhook. Never fulfill orders based solely on client-side callbacks.

`DCP.payWithTool(params)` Reference#

Use this method to trigger a payment from a Payment Tool programmatically (instead of using the auto-init button). The widget displays a token/chain selection modal automatically.

DCP.init({ baseURL: 'https://api.directcryptopay.com' });

DCP.payWithTool({
  toolId: 'pt_7K8mN9oPqRsT',
  onTxSubmitted: function(txHash) {
    console.log('Transaction submitted:', txHash);
  },
  onStatus: function(status) {
    if (status.type === 'confirmed') {
      window.location.href = '/thank-you';
    }
  },
  onError: function(error) {
    console.error('Payment error:', error);
  }
});
Parameter Type Required Description
toolId string Yes Payment tool ID (e.g., pt_7K8mN9oPqRsT)
onTxSubmitted function No Called when the transaction is submitted
onStatus function No Called on status changes
onError function No Called on error
onCancel function No Called on user cancellation
Difference from DCP.Payment(): payWithTool() uses a Payment Tool (fixed price, configured in dashboard) and shows a chain/token selection modal. DCP.Payment() uses an Integration with amount specified in code.

React / Next.js Integration#

React Component#

// components/CryptoPayButton.jsx
import { useEffect, useRef } from 'react';

export default function CryptoPayButton({ amount, orderId }) {
  const scriptLoaded = useRef(false);

  useEffect(() => {
    if (scriptLoaded.current) return;
    const script = document.createElement('script');
    script.src = 'https://api.directcryptopay.com/widget/dcp-widget.umd.js';
    script.async = true;
    script.onload = () => { scriptLoaded.current = true; };
    document.head.appendChild(script);
  }, []);

  const handlePay = () => {
    window.DCP.Payment({
      integrationId: process.env.NEXT_PUBLIC_DCP_INTEGRATION_ID,
      amount_usd: String(amount),
      metadata: { orderId },
      onTxSubmitted: (txHash) => {
        console.log('Transaction submitted:', txHash);
      },
      onStatus: (status) => {
        if (status.type === 'confirmed') {
          window.location.href = `/success?order=${orderId}`;
        }
      },
      onError: (error) => {
        console.error('Payment error:', error);
      }
    });
  };

  return (
    <button onClick={handlePay}>
      Pay ${amount} with Crypto
    </button>
  );
}

Next.js App Router (TypeScript)#

// components/CryptoPayButton.tsx
'use client';

import { useEffect, useCallback, useRef } from 'react';

declare global {
  interface Window {
    DCP: {
      Payment: (params: any) => Promise<void>;
      init: (config: any) => void;
    };
  }
}

interface Props {
  amount: number;
  orderId: string;
}

export default function CryptoPayButton({ amount, orderId }: Props) {
  const scriptLoaded = useRef(false);

  useEffect(() => {
    if (scriptLoaded.current) return;
    const script = document.createElement('script');
    script.src = 'https://api.directcryptopay.com/widget/dcp-widget.umd.js';
    script.async = true;
    script.onload = () => { scriptLoaded.current = true; };
    document.head.appendChild(script);
  }, []);

  const handlePay = useCallback(() => {
    window.DCP.Payment({
      integrationId: process.env.NEXT_PUBLIC_DCP_INTEGRATION_ID!,
      amount_usd: String(amount),
      metadata: { orderId },
      onTxSubmitted: (txHash: string) => {
        console.log('TX submitted:', txHash);
      },
      onStatus: (status: { type: string }) => {
        if (status.type === 'confirmed') {
          window.location.href = `/success?order=${orderId}`;
        }
      }
    });
  }, [amount, orderId]);

  return (
    <button onClick={handlePay} className="bg-emerald-600 text-white px-6 py-3 rounded-lg">
      Pay ${amount} with Crypto
    </button>
  );
}

Usage:

<CryptoPayButton amount={49.99} orderId="ORD-12345" />

ES Module Import#

If you prefer ES module imports:

<script type="module">
  import DCP from 'https://api.directcryptopay.com/widget/dcp-widget.es.js';

  document.getElementById('pay-button').addEventListener('click', () => {
    DCP.Payment({
      integrationId: 'YOUR_INTEGRATION_ID',
      amount_usd: '49.99',
      onTxSubmitted: (txHash) => console.log('TX:', txHash)
    });
  });
</script>

Widget Customization#

The widget modal inherits a clean dark theme by default. The following CSS custom properties can be overridden for basic customization:

/* Override widget theme (applied to the host page) */
:root {
  --dcp-accent: #00d4aa;        /* Primary accent color */
  --dcp-bg: #0c1019;            /* Modal background */
  --dcp-text: #e8ecf4;          /* Text color */
  --dcp-border: #1a2236;        /* Border color */
}
Widget Size: The widget renders as a centered modal overlay. It is fully responsive and adapts to mobile screens automatically.

Price Guarantee & Slippage#

Price Timer#

When the payment modal opens, the widget scans your wallet and displays an estimated crypto amount. This estimate is time-limited:

  • Stablecoins (USDC, USDT, DAI): 15 minutes
  • Volatile tokens (ETH, BNB, POL): 10 minutes

A discreet countdown shows the remaining time (e.g., "Guaranteed price for 09:42"). When the timer expires, the pay button changes to "Refresh Price" — clicking it re-scans balances and restarts the timer. The flow continues seamlessly without closing the modal.

Slippage Tolerance#

DirectCryptoPay applies a 2% tolerance on payment validation. If the received amount is within 98% of the expected amount, the payment is accepted as PAID. This covers:

  • Gas rounding differences
  • Minor price drift during block confirmation
  • Wallet-specific rounding in transaction amounts

This means a payment of $49.00 against an expected $50.00 would be accepted, but $48.99 would be rejected.

Payment Flow & Status Types#

When a customer clicks pay, the widget handles the full payment flow:

  1. Wallet connection -- prompts the customer to connect their wallet (MetaMask, Rabby, etc.)
  2. Token selection -- scans balances across allowed chains and shows a token selector
  3. Transaction signing -- customer approves the transaction in their wallet
  4. Modal closes -- once the transaction is submitted, the widget modal closes automatically
  5. Checkout page -- the checkout page shows "Processing Payment" (blue) while waiting for blockchain confirmation, then "Payment Confirmed" (green) when verified

`onStatus` Callback Types#

Status Type Description
submitted Transaction sent to blockchain, backend is monitoring (not yet confirmed)
confirmed Transaction fully confirmed on-chain by the DCP backend
failed Transaction reverted or payment validation failed
rejected Customer rejected the transaction in their wallet
onStatus: function(status) {
  console.log('Status:', status.type);

  if (status.type === 'submitted') {
    // Transaction is on-chain, waiting for confirmations
    // status.txHash is available
  }

  if (status.type === 'confirmed') {
    // Payment verified by DCP backend — safe to fulfill
    // But always use webhooks for server-side verification
    window.location.href = '/order-confirmation';
  }
}
submitted does not mean the payment is confirmed. It means the transaction was sent to the blockchain. The DCP backend independently monitors and confirms it. Always use webhooks for server-side fulfillment.

Security Considerations#

  • The widget is loaded from the DirectCryptoPay CDN with SRI (Subresource Integrity) hashes
  • All payment verification is done server-side -- the widget cannot fake a successful payment
  • Use webhooks to confirm payments before fulfilling orders
  • The widget communicates with the DCP backend over HTTPS
  • Integration payments validate the page Origin against your integration's domain allowlist (DCP-hosted payment pages at directcryptopay.com/pay/* are automatically allowed)

:::tip Optional: Set up Webhook Verification to receive server-side payment confirmations. :::