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

testnet

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.

javascript
// 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 customer

Checking Payment Status

View Your Payments
Track all your payment links in one place

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

Simple 3-Step Process
How payment links work
  1. Merchant generates link: Creates a payment link with amount and merchant wallet
  2. Customer opens link: Sees payment details and connects MetaMask
  3. 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

Dynamic Payment Links
Generate links on-the-fly with encoded payment data in the URL

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 = `
            <p>Amount: ${paymentData.amount} USDC</p>
            <p>Description: ${paymentData.description || 'N/A'}</p>
            <p>Merchant: ${paymentData.merchant.slice(0, 6)}...${paymentData.merchant.slice(-4)}</p>
          `;
          
          // 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

โœ… Secure Implementation
  • 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
โš ๏ธ What to Avoid
  • โŒ 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

Vercel

Deploy static files or Next.js app. Free tier available.

Netlify

Perfect for static sites. Drag and drop deployment.

GitHub Pages

Free hosting for static sites. Great for open source projects.

Complete Example

1

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.html
2

Create Merchant Dashboard

Use the HTML example above to create your merchant dashboard where links are generated.

3

Create Payment Page

Create a payment page that reads encoded data from URL parameters and processes payments.

4

Deploy

# Deploy to Vercel
vercel

# Or deploy to Netlify
netlify deploy --prod

Integration with ZynPay SDK

Processing Payments
Use the ZynPay SDK to process payments from links
// 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);
}