Payment Links
Create shareable payment links that customers can pay directly with MetaMask. Perfect for invoices, donations, subscriptions, and one-time payments.
Live Demo
Try generating a payment link below. This is a simulated demo running on Testnet mode.
Link Generator
Test network with testnet USDC (no real value)
The wallet where you'll receive payments
You'll receive 9.70 USDC (97%)
How It Works
Payment links encode payment data in the URL, allowing customers to pay directly from their MetaMask wallet without any backend infrastructure.
// Generate payment link
const paymentData = {
merchant: '0xYourMerchantWallet',
amount: 10.00,
description: 'Product purchase',
timestamp: Date.now()
};
// Encode to base64 for URL
const encodedData = btoa(JSON.stringify(paymentData));
const paymentLink = `https://yoursite.com/pay?p=${encodedData}`;
// Share the link with customerChecking Payment Status
After creating payment links, you can check their status in the merchant dashboard. Simply connect your wallet to see:
- All payments received to your merchant wallet
- Payment status (Pending, Completed, Failed, Refunded)
- Transaction hashes with links to block explorers
- Payment statistics and analytics
Payment Flow
- Merchant generates link: Creates a payment link with amount and merchant wallet
- Customer opens link: Sees payment details and connects MetaMask
- Customer pays: Approves transaction in MetaMask, payment goes to merchant wallet
Key Benefits:
- โ No backend required - works with static hosting
- โ Share via email, SMS, social media, QR codes
- โ Customers pay directly from their wallet
- โ Works on any device with MetaMask
Implementation Approaches
This approach encodes payment data in the URL parameters, allowing you to generate links dynamically without creating separate HTML files.
Merchant Dashboard (Link Generator)
Create an HTML page where merchants can generate payment links:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ZynPay - Generate Payment Link</title>
<style>
/* Add your styles here */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 40px 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
/* Add remaining styles from your example */
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>๐ณ ZynPay Link Generator</h1>
<p>Create secure payment links for your customers</p>
</div>
<form id="paymentForm">
<div class="form-group">
<label for="merchantWallet">Your Wallet Address *</label>
<input
type="text"
id="merchantWallet"
placeholder="0x..."
required
pattern="^0x[a-fA-F0-9]{40}$"
/>
</div>
<div class="form-group">
<label for="amount">Amount (USDC) *</label>
<input
type="number"
id="amount"
placeholder="10.00"
step="0.01"
min="0.01"
required
/>
</div>
<div class="form-group">
<label for="description">Description (Optional)</label>
<input
type="text"
id="description"
placeholder="Product/Service name"
maxlength="100"
/>
</div>
<button type="submit" class="btn-generate">
๐ Generate Payment Link
</button>
</form>
<div id="result" class="result" style="display: none">
<h3>โ
Payment Link Generated!</h3>
<div class="payment-link" id="paymentLink"></div>
<div class="action-buttons">
<button class="btn-action btn-copy" onclick="copyLink()">
๐ Copy Link
</button>
<button class="btn-action btn-open" onclick="openLink()">
๐ Open Link
</button>
</div>
</div>
</div>
<script>
let generatedLink = "";
document.getElementById("paymentForm").addEventListener("submit", function (e) {
e.preventDefault();
const merchantWallet = document.getElementById("merchantWallet").value.trim();
const amount = parseFloat(document.getElementById("amount").value);
const description = document.getElementById("description").value.trim();
// Validate wallet address
if (!/^0x[a-fA-F0-9]{40}$/.test(merchantWallet)) {
alert("โ Invalid wallet address");
return;
}
// Validate amount
if (isNaN(amount) || amount <= 0) {
alert("โ Invalid amount");
return;
}
// Create payment data object
const paymentData = {
merchant: merchantWallet,
amount: amount,
description: description || "",
timestamp: Date.now(),
};
// Encode payment data to base64 for URL
const encodedData = btoa(JSON.stringify(paymentData));
// Generate the payment link
const baseUrl = window.location.origin;
generatedLink = `${baseUrl}/pay?p=${encodedData}`;
// Display the link
document.getElementById("paymentLink").textContent = generatedLink;
document.getElementById("result").style.display = "block";
});
function copyLink() {
navigator.clipboard.writeText(generatedLink);
}
function openLink() {
window.open(generatedLink, "_blank");
}
</script>
</body>
</html>Payment Page (Dynamic)
Create a payment page that reads data from URL parameters:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Pay with ZynPay</title>
<script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js"></script>
</head>
<body>
<div id="app">
<h1>๐ณ Payment Request</h1>
<div id="paymentDetails"></div>
<button id="payBtn" onclick="processPayment()">Pay with MetaMask</button>
</div>
<script>
// Decode payment data from URL
const urlParams = new URLSearchParams(window.location.search);
const encodedData = urlParams.get('p');
if (!encodedData) {
document.getElementById('app').innerHTML = '<p>โ Invalid payment link</p>';
} else {
try {
const paymentData = JSON.parse(atob(encodedData));
// Display payment details
document.getElementById('paymentDetails').innerHTML = ``;
// Store payment data for processing
window.paymentData = paymentData;
} catch (error) {
document.getElementById('app').innerHTML = '<p>โ Invalid payment data</p>';
}
}
async function processPayment() {
if (!window.paymentData) return;
// Connect MetaMask and process payment
// (Use your payment processing code here)
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = await provider.getSigner();
// Create payment with ZynPay SDK
// ... payment processing logic
}
</script>
</body>
</html>Security Best Practices
- Never hardcode wallet addresses: Always use environment variables or user input
- Validate all inputs: Check wallet address format and amount ranges
- Use HTTPS: Always serve payment pages over HTTPS in production
- Verify payment data: Validate decoded payment data before processing
- Set expiration: Consider adding timestamp validation for payment links
- โ Don't expose merchant wallet addresses in public repositories
- โ Don't store private keys anywhere in your code
- โ Don't trust client-side validation alone - verify on backend
- โ Don't use payment links for sensitive data without encryption
Hosting Options
Deploy static files or Next.js app. Free tier available.
Perfect for static sites. Drag and drop deployment.
Free hosting for static sites. Great for open source projects.
Complete Example
Set Up Project
# Create project directory
mkdir zynpay-payment-links
cd zynpay-payment-links
# Create files
touch merchant-dashboard.html
touch payment-page.html
touch payment-template.htmlCreate Merchant Dashboard
Use the HTML example above to create your merchant dashboard where links are generated.
Create Payment Page
Create a payment page that reads encoded data from URL parameters and processes payments.
Deploy
# Deploy to Vercel
vercel
# Or deploy to Netlify
netlify deploy --prodIntegration with ZynPay SDK
// In your payment page JavaScript
import { ZynPayClient, Environment } from '@zyntrialabs/zynpay-sdk';
import { BrowserProvider } from 'ethers';
// Decode payment data from URL
const urlParams = new URLSearchParams(window.location.search);
const encodedData = urlParams.get('p');
const paymentData = JSON.parse(atob(encodedData));
// Initialize ZynPay client
const client = new ZynPayClient({
merchantWallet: paymentData.merchant,
environment: Environment.TESTNET,
defaultChain: 'base',
});
// Process payment
async function processPayment() {
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const payment = client.createPayment(paymentData.amount, 'base');
const txHash = await client.pay(payment, signer);
console.log('Payment successful!', txHash);
}