Skip to content

Selling Products with Stripe

Stripe exposes your entire product catalog through an API, making it easy to fetch your products at build time, generate your store pages statically, and let Stripe host the checkout. All static; no backend required.

Manage your products, prices, and payment links entirely in the Stripe Dashboard. Every time your site builds, ZeroPoint fetches the latest product data and regenerates your store pages.

How it works

  1. Create products and Payment Links in the Stripe Dashboard
  2. At build time, ZeroPoint fetches your product catalog from the Stripe API
  3. Your store pages are generated as static HTML — no runtime server needed
  4. Customers click a link and are taken to Stripe's hosted checkout page to complete their purchase

Steps

  1. Create your products in Stripe

    In the Stripe Dashboard, create a product for each item you want to sell. Give each product a name, description, and image.

    For each product, create a Payment Link (Products → your product → Create payment link). This gives you a Stripe-hosted checkout URL. Payment Links support one-time payments, subscriptions, and donations.

  2. Store your Stripe secret key

    Your secret key is used only at build time to fetch your product catalog. It never appears in the generated HTML. Add it as an environment variable in your deployment platform:

    • Netlify: Site configuration → Environment variables → Add variable
    • Cloudflare Pages: Settings → Environment variables → Add variable
    • GitHub Actions: Repository settings → Secrets and variables → Actions → New repository secret

    Name the variable STRIPE_SECRET_KEY. For local development, add it to a .env file at the root of your project (ensure .env is in your .gitignore):

    STRIPE_SECRET_KEY=XXXXXXXXXXXXXXXXXXXXXXXX
  3. Install the Stripe SDK in ZeroPoint

    npm install --save-dev stripe
  4. Create a Stripe data file

    Create src/data/stripe.js to fetch your Payment Links and their associated product data from Stripe:

    src/data/stripe.js
    import Stripe from 'stripe';
    
    export default async function () {
      const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
    
      // Fetch all active Payment Links
      const { data: links } = await stripe.paymentLinks.list({ active: true });
    
      // For each link, fetch its line items and expand the product details
      const products = await Promise.all(
        links.map(async (link) => {
          const { data: lineItems } = await stripe.paymentLinks.listLineItems(
            link.id,
            { expand: ['data.price.product'] }
          );
    
          const item = lineItems[0];
          const product = item?.price?.product;
          const currency = item?.price?.currency ?? 'usd';
          const amount = (item?.price?.unit_amount ?? 0) / 100;
    
          return {
            url: link.url,
            name: product?.name,
            slug: product?.name?.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') ?? product?.id,
            description: product?.description,
            image: product?.images?.[0] ?? null,
            price: new Intl.NumberFormat('en-US', {
              style: 'currency',
              currency: currency,
            }).format(amount),
          };
        })
      );
    
      return products;
    }

    The stripe data is now available to all your ZeroPoint templates.

  5. Create the store listing page

    Create src/content/pages/store.njk to render a grid of product cards, each linking to its own detail page:

    src/content/pages/store.njk
    ---
    title: Store
    permalink: /store/
    ---
    
    <h1>Store</h1>
    
    <div class="product-grid">
    {% for product in stripe %}
      <article class="product-card">
        {% if product.image %}
        <a href="/store/{{ product.slug }}/">
          <img src="{{ product.image }}" alt="{{ product.name }}">
        </a>
        {% endif %}
        <h2><a href="/store/{{ product.slug }}/">{{ product.name }}</a></h2>
        <p class="product-price">{{ product.price }}</p>
        <a href="/store/{{ product.slug }}/" class="button button-secondary">View Product</a>
      </article>
    {% endfor %}
    </div>
  6. Generate individual product pages

    Pagination can generate one page per item in a dataset. Create src/content/pages/store-product.njk — ZeroPoint will output a separate page at /store/your-product-name/ for every product in your Stripe catalog:

    src/content/pages/store-product.njk
    ---
    pagination:
      data: stripe
      size: 1
      alias: product
    permalink: /store/{{ product.slug }}/
    eleventyExcludeFromCollections: true
    ---
    
    <article class="product-single">
      {% if product.image %}
      <div class="product-image">
        <img src="{{ product.image }}" alt="{{ product.name }}">
      </div>
      {% endif %}
    
      <div class="product-details">
        <h1>{{ product.name }}</h1>
    
        {% if product.description %}
        <div class="product-description">
          {{ product.description | markdown | safe }}
        </div>
        {% endif %}
    
        <p class="product-price">{{ product.price }}</p>
    
        <a href="{{ product.url }}">
          Buy Now
        </a>
      </div>
    </article>

    Add a product in Stripe, trigger a build, and it gets a card on /store/ and its own page at /store/its-name/. Deactivate its Payment Link in Stripe and both disappear on the next build.

Going Further