Building E-commerce Platform with Next.js: Payment Gateway Integration, Lottie Animations & WordPress

Building NeoZAP: A Marketing and E-commerce Website with Next.js

I built the NeoZAP website at Antino Labs - a marketing and e-commerce platform for a fintech product. The site handles product marketing, email waitlist, payment processing through multiple gateways, blog articles, and timed discount campaigns.

Live site: neofinity.in


Overview

Role: Frontend Developer (Solo) Duration: ~6 months Tech Stack: Next.js, TypeScript, Tailwind CSS, Lottie, Framer Motion


Tech Stack

  • Framework: Next.js 14 with TypeScript
  • Styling: Tailwind CSS
  • Animations: Lottie, Framer Motion
  • Blog CMS: WordPress
  • Payment Gateways: Razorpay, Paytm, Cashfree
  • Analytics: Google Analytics 4, Facebook Pixel
  • Deployment: AWS Amplify with SSR

What I Built

Custom UI Components

The design was unique, so I built components from scratch instead of using a component library.

// Simple reusable button component
const Button = ({ children, variant = 'primary', ...props }) => {
  const styles = {
    primary: 'bg-blue-600 hover:bg-blue-700 text-white',
    secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900',
  }

  return (
    <button
      className={`rounded-lg px-6 py-3 font-medium transition ${styles[variant]}`}
      {...props}
    >
      {children}
    </button>
  )
}

Faster to build custom than fight with library theme overrides for this design.


Animations

Used Lottie for brand animations and Framer Motion for interactive elements.

Lottie animations:

import Lottie from 'lottie-react'
import heroAnimation from '@/animations/hero.json'

const HeroSection = () => {
  return (
    <div className="flex items-center gap-12">
      <div className="flex-1">
        <h1>Welcome to NeoZAP</h1>
        <p>Your financial companion</p>
      </div>
      <div className="flex-1">
        <Lottie animationData={heroAnimation} loop autoplay />
      </div>
    </div>
  )
}

Scroll animations with Framer Motion:

import { motion } from 'framer-motion'

const FeatureCard = ({ title, description }) => {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
      viewport={{ once: true }}
      className="rounded-lg bg-white p-6"
    >
      <h3>{title}</h3>
      <p>{description}</p>
    </motion.div>
  )
}

The animations made the site visually appealing but added to load time. On 4G connections, initial load was 2-3 seconds. Acceptable trade-off for a marketing site where visual impact matters.


Payment Gateway Integration

Built support for three payment gateways (Razorpay, Paytm, Cashfree) because we weren't sure which would get compliance approval first.

Created separate checkout components for each:

// Razorpay
const RazorpayCheckout = ({ amount }) => {
  const handlePayment = () => {
    const options = {
      key: process.env.NEXT_PUBLIC_RAZORPAY_KEY,
      amount: amount * 100,
      currency: 'INR',
      name: 'NeoZAP',
      handler: (response) => {
        console.log('Payment success:', response.razorpay_payment_id)
      },
    }

    const razorpay = new window.Razorpay(options)
    razorpay.open()
  }

  return <button onClick={handlePayment}>Pay ₹{amount}</button>
}

// Paytm
const PaytmCheckout = ({ amount }) => {
  const handlePayment = async () => {
    const res = await fetch('/api/paytm/initiate', {
      method: 'POST',
      body: JSON.stringify({ amount }),
    })

    const { paytmUrl, token } = await res.json()
    window.location.href = `${paytmUrl}?token=${token}`
  }

  return <button onClick={handlePayment}>Pay ₹{amount}</button>
}

To switch gateways, just changed which component rendered:

const PaymentButton = ({ amount }) => {
  const gateway = process.env.NEXT_PUBLIC_ACTIVE_GATEWAY

  if (gateway === 'razorpay') return <RazorpayCheckout amount={amount} />
  if (gateway === 'paytm') return <PaytmCheckout amount={amount} />
  if (gateway === 'cashfree') return <CashfreeCheckout amount={amount} />
}

When we needed to switch from Razorpay to Paytm, changed one environment variable and redeployed. Took about 2 hours.

Payment success rate: 70-80% across all gateways Transactions: 30-40 per week


Timed Discount Campaigns

Built an automatic discount system with countdown timers that revert to original pricing when campaigns end.

const CAMPAIGNS = {
  newYear: {
    startDate: '2024-12-25T00:00:00',
    endDate: '2024-12-31T23:59:59',
    discountPercent: 20,
  },
}

const getActivePrice = (originalPrice) => {
  const now = new Date()
  const campaign = CAMPAIGNS.newYear
  const start = new Date(campaign.startDate)
  const end = new Date(campaign.endDate)

  if (now >= start && now <= end) {
    return originalPrice * (1 - campaign.discountPercent / 100)
  }

  return originalPrice
}

const ProductPrice = () => {
  const originalPrice = 999
  const currentPrice = getActivePrice(originalPrice)
  const isDiscounted = currentPrice < originalPrice

  return (
    <div>
      {isDiscounted ? (
        <>
          <span className="text-gray-400 line-through">{originalPrice}</span>
          <span className="ml-2 text-3xl font-bold text-red-600">{currentPrice}
          </span>
          <span className="ml-2 text-sm">Limited time!</span>
        </>
      ) : (
        <span className="text-3xl font-bold">{originalPrice}</span>
      )}
    </div>
  )
}

Set the campaign dates, deploy once, and prices automatically discount and revert. No manual updates needed.


WordPress Blog Integration

Integrated WordPress to manage blog articles. Marketing team writes posts in WordPress, I fetch and display them on the site.

export async function getBlogPosts() {
  const res = await fetch(
    `${process.env.WORDPRESS_API_URL}/wp-json/wp/v2/posts`,
    { next: { revalidate: 3600 } },
  )
  return res.json()
}

const BlogPage = async () => {
  const posts = await getBlogPosts()

  return (
    <div className="mx-auto max-w-4xl">
      <h1>Blog</h1>
      <div className="mt-8 grid gap-8">
        {posts.map((post) => (
          <article key={post.id} className="border-b pb-8">
            <h2>{post.title.rendered}</h2>
            <div dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
            <a href={`/blog/${post.slug}`}>Read more →</a>
          </article>
        ))}
      </div>
    </div>
  )
}

WordPress handles the blog CMS, I handle the frontend display. Landing page content is still hardcoded.


Analytics Tracking

Integrated Google Analytics and Facebook Pixel for conversion tracking.

export const trackEvent = (eventName, properties) => {
  // Google Analytics
  if (window.gtag) {
    window.gtag('event', eventName, properties)
  }

  // Facebook Pixel
  if (window.fbq) {
    window.fbq('track', eventName, properties)
  }
}

// Track button clicks
const CTAButton = () => {
  const handleClick = () => {
    trackEvent('cta_click', {
      location: 'hero_section',
      text: 'Get Started',
    })
    router.push('/signup')
  }

  return <button onClick={handleClick}>Get Started</button>
}

Tracks user interactions, page views, and purchases. Helps marketing optimize campaigns.


Waitlist System

Simple email capture form for early users:

const WaitlistForm = () => {
  const [email, setEmail] = useState('')
  const [loading, setLoading] = useState(false)

  const handleSubmit = async (e) => {
    e.preventDefault()
    setLoading(true)

    await fetch('/api/waitlist', {
      method: 'POST',
      body: JSON.stringify({ email }),
    })

    setLoading(false)
    alert('Thanks for joining!')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email"
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Joining...' : 'Join Waitlist'}
      </button>
    </form>
  )
}

Collects emails, stores in database, sends confirmation.


What Worked Well

Custom Components Building from scratch was faster than adapting libraries for this design.

Payment Gateway Flexibility Having separate components for each gateway made switching quick during compliance delays.

WordPress for Blog Marketing can publish articles independently without developer involvement.

Next.js SSR Server-side rendering helped with SEO and initial page performance.


What Could Be Better

Animation Performance Lottie files were heavy. Load time on 4G was 2-3 seconds. Could have optimized animation files earlier or used simpler CSS animations.

Mobile Optimization Optimized desktop first. Mobile performance was okay but not great. Should have gone mobile-first.

Payment Error Messages 20-30% of payments failed. My error messages were generic: "Payment failed, try again." Should have shown specific reasons (insufficient funds, invalid card, etc.).

No Performance Budget Didn't set load time targets upfront. Would establish performance budgets before starting next time.


Key Metrics

  • Bundle Size: ~350kb (with animations)
  • Load Time: 2-3 seconds on 4G
  • Payment Success: 70-80%
  • Weekly Transactions: 30-40
  • Deployment: AWS Amplify with SSR

Takeaways

Set Performance Budgets Early Don't wait until site is slow. Set targets before building.

Build for Flexibility Payment gateway switching saved us when compliance got delayed.

Mobile-First Matters Most traffic is mobile. Optimize for mobile from day one.

Better Error Messages Generic errors reduce trust. Show users what went wrong.


Live Site

Check out the live site: neofinity.in


Connect

Questions about Next.js, payments, or e-commerce?

Building E-commerce Platform with Next.js: Payment Gateway Integration, Lottie Animations & WordPress - Ravi Ranjan