Payment Gateway Integration
Accept cryptocurrency payments in your application using Veridex passkeys.
💳 Try it yourself: Clone and run the full payment gateway example:
git clone https://github.com/Veridex-Protocol/examples.git cd examples && npm install && npx ts-node integrations/payment-gateway/index.tsView source on GitHub (opens in a new tab) · Browse all examples
Overview
Veridex enables:
- Gasless payments - Customers don't need gas tokens
- One-click checkout - FaceID/TouchID to confirm
- Multi-chain - Accept payments on any supported chain
- Session-based - Optional smooth checkout flows
Basic Payment Flow
import { createSDK } from '@veridex/sdk';
import { parseUnits } from 'ethers';
const sdk = createSDK('base', {
network: 'mainnet',
relayerUrl: 'https://relayer.veridex.network',
});
// Customer authenticates
await sdk.passkey.authenticate();
// Process payment
const payment = await sdk.transferViaRelayer({
token: USDC_ADDRESS,
recipient: MERCHANT_WALLET,
amount: parseUnits('29.99', 6), // $29.99
});
console.log('Payment complete:', payment.txHash);React Checkout Component
import { useState } from 'react';
import { createSDK } from '@veridex/sdk';
import { parseUnits } from 'ethers';
const sdk = createSDK('base', {
network: 'mainnet',
relayerUrl: 'https://relayer.veridex.network',
});
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // Base Mainnet USDC
const MERCHANT = '0xYourMerchantWallet';
interface CheckoutProps {
amount: string; // e.g., "29.99"
orderId: string;
onSuccess: (txHash: string) => void;
onError: (error: Error) => void;
}
export function VeridexCheckout({ amount, orderId, onSuccess, onError }: CheckoutProps) {
const [loading, setLoading] = useState(false);
const [step, setStep] = useState<'connect' | 'confirm' | 'processing' | 'done'>('connect');
const handleCheckout = async () => {
setLoading(true);
try {
// Step 1: Authenticate
setStep('connect');
const stored = sdk.passkey.getAllStoredCredentials();
if (stored.length > 0) {
await sdk.passkey.authenticate();
} else {
await sdk.passkey.register('customer@example.com', 'Checkout');
}
// Step 2: Confirm payment
setStep('confirm');
// Step 3: Process
setStep('processing');
const result = await sdk.transferViaRelayer({
token: USDC,
recipient: MERCHANT,
amount: parseUnits(amount, 6),
});
setStep('done');
onSuccess(result.txHash);
} catch (error) {
onError(error instanceof Error ? error : new Error('Payment failed'));
} finally {
setLoading(false);
}
};
return (
<div className="max-w-md mx-auto p-6 border rounded-xl">
<div className="text-center mb-6">
<h2 className="text-2xl font-bold">${amount}</h2>
<p className="text-gray-500">Order #{orderId}</p>
</div>
{step === 'done' ? (
<div className="text-center text-green-600">
<span className="text-4xl">✓</span>
<p className="font-medium">Payment Complete!</p>
</div>
) : (
<button
onClick={handleCheckout}
disabled={loading}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium"
>
{loading ? (
<span>
{step === 'connect' && 'Authenticating...'}
{step === 'confirm' && 'Confirming...'}
{step === 'processing' && 'Processing...'}
</span>
) : (
'Pay with Passkey'
)}
</button>
)}
<p className="text-center text-sm text-gray-500 mt-4">
Powered by Veridex • No gas fees
</p>
</div>
);
}Session-Based Checkout
For smoother repeat purchases:
// First purchase: Create session for future payments
const session = await sdk.sessions.create({
duration: 1800, // 30 minutes
maxValue: parseUnits('100', 6), // Max $100
allowedTokens: [USDC],
allowedRecipients: [MERCHANT],
});
// Subsequent purchases in session: No biometric prompt
await sdk.executeTransfer({ token: USDC, recipient: MERCHANT, amount: parseUnits('9.99', 6) });
await sdk.executeTransfer({ token: USDC, recipient: MERCHANT, amount: parseUnits('14.99', 6) });
// Both auto-signed! ✓Server-Side Verification
Verify payments on your backend:
// pages/api/verify-payment.ts (Next.js)
import { ethers } from 'ethers';
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const MERCHANT_ADDRESS = '0xYourMerchantWallet';
export async function POST(req: Request) {
const { txHash, orderId, expectedAmount } = await req.json();
// Connect to Base
const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
// Get transaction receipt
const receipt = await provider.getTransactionReceipt(txHash);
if (!receipt || receipt.status !== 1) {
return Response.json({ valid: false, error: 'Transaction not confirmed' });
}
// Parse transfer event
const iface = new ethers.Interface([
'event Transfer(address indexed from, address indexed to, uint256 value)'
]);
const transferLog = receipt.logs.find(log =>
log.address.toLowerCase() === USDC_ADDRESS.toLowerCase()
);
if (!transferLog) {
return Response.json({ valid: false, error: 'No USDC transfer found' });
}
const parsed = iface.parseLog(transferLog);
const { to, value } = parsed.args;
// Verify recipient and amount
if (to.toLowerCase() !== MERCHANT_ADDRESS.toLowerCase()) {
return Response.json({ valid: false, error: 'Wrong recipient' });
}
if (value < ethers.parseUnits(expectedAmount, 6)) {
return Response.json({ valid: false, error: 'Insufficient amount' });
}
// Payment verified - update order status
await updateOrderStatus(orderId, 'paid', txHash);
return Response.json({ valid: true, txHash });
}Webhook Integration
Receive payment notifications:
// Configure webhook with relayer (if using managed relayer)
await sdk.webhooks.register({
url: 'https://your-app.com/api/webhooks/veridex',
events: ['payment.completed', 'payment.failed'],
secret: process.env.WEBHOOK_SECRET,
});Handle webhooks:
// pages/api/webhooks/veridex.ts
import { createHmac } from 'crypto';
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get('x-veridex-signature');
// Verify signature
const expectedSig = createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(body)
.digest('hex');
if (signature !== expectedSig) {
return Response.json({ error: 'Invalid signature' }, { status: 401 });
}
const event = JSON.parse(body);
switch (event.type) {
case 'payment.completed':
await handlePaymentComplete(event.data);
break;
case 'payment.failed':
await handlePaymentFailed(event.data);
break;
}
return Response.json({ received: true });
}Multi-Currency Support
Accept multiple stablecoins:
const TOKENS = {
USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
USDT: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',
DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
};
function CurrencySelector({ onSelect }: { onSelect: (token: string) => void }) {
return (
<div className="flex gap-2">
{Object.entries(TOKENS).map(([symbol, address]) => (
<button
key={symbol}
onClick={() => onSelect(address)}
className="px-4 py-2 border rounded-lg"
>
{symbol}
</button>
))}
</div>
);
}Invoice Links
Generate payment links:
// Generate payment URL
function generatePaymentLink(orderId: string, amount: string, currency: string) {
const params = new URLSearchParams({
orderId,
amount,
currency,
merchant: MERCHANT_ADDRESS,
});
return `https://your-app.com/pay?${params}`;
}
// Payment page reads params and initiates checkout
// /pay?orderId=123&amount=29.99¤cy=USDC&merchant=0x...Best Practices
- Verify on server - Always verify payments server-side
- Use webhooks - Don't rely solely on client-side callbacks
- Set timeouts - Expire payment sessions after reasonable time
- Show progress - Clear UI for each checkout step
- Handle errors - Graceful fallbacks for failed payments
Security Considerations
- Never trust client-side payment confirmations alone
- Verify transaction on-chain before fulfilling orders
- Use unique order IDs to prevent replay attacks
- Implement rate limiting on payment endpoints