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.
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.
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 |
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:
- Fetches your integration config (allowed chains and tokens)
- Opens the wallet picker (or auto-selects if only one wallet is available)
- Detects the wallet's current chain — if it's an allowed chain, uses it; otherwise auto-switches to the first allowed chain
- Shows a token selector with real wallet balances for available tokens
- 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 |
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 */
}
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:
- Wallet connection -- prompts the customer to connect their wallet (MetaMask, Rabby, etc.)
- Token selection -- scans balances across allowed chains and shows a token selector
- Transaction signing -- customer approves the transaction in their wallet
- Modal closes -- once the transaction is submitted, the widget modal closes automatically
- 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. :::