Recommended single price (simple + competitive)

$14.99 USD for a Cover + Divider template (editable in PowerPoint / Keynote / Google Slides).

Why $14.99?

Sits right in the GraphicRiver sweet spot (not bargain, not premium) and includes both cover + divider, so it feels like a deal vs single-slide packs at $11–$15. 
GraphicRiver
+1

Higher conversion than $19.99 while leaving margin for creator split.

If you want a Pro perk: give Pro users 20% off (checkout coupon) but keep MSRP at $14.99 for everyone else.

Stripe: implement one global price

Add to your env:

COVER_TEMPLATE_PRICE_CENTS=1499
COVER_TEMPLATE_CURRENCY=usd

1) Create a single product + price (idempotent)

server/stripe/coverCheckout.ts – replace ensurePriceForTemplate with a global price:

// server/stripe/coverCheckout.ts
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: "2024-06-20" });

const PRODUCT_ID = "ibrandbiz_cover_divider_template"; // fixed
const PRICE_LOOKUP_KEY = "ibrandbiz_cover_divider_price_v1"; // bump if price changes

export async function ensureGlobalCoverPrice() {
  // Ensure product exists
  const product = await stripe.products.create({
    id: PRODUCT_ID,
    name: "Cover + Divider Template (Editable)",
    description: "Editable presentation cover & section divider. PPTX / Keynote / Google Slides.",
  }).catch(async (e) => {
    if (e?.raw?.code === "resource_already_exists") {
      return stripe.products.retrieve(PRODUCT_ID);
    }
    throw e;
  });

  // Try to find an active price by lookup_key
  const prices = await stripe.prices.list({ product: product.id, active: true, limit: 100 });
  const existing = prices.data.find(p => p.lookup_key === PRICE_LOOKUP_KEY);
  if (existing) return existing;

  // Create the canonical price
  const unit_amount = parseInt(process.env.COVER_TEMPLATE_PRICE_CENTS || "1499", 10);
  const currency = (process.env.COVER_TEMPLATE_CURRENCY || "usd").toLowerCase();

  return stripe.prices.create({
    product: product.id,
    unit_amount,
    currency,
    lookup_key: PRICE_LOOKUP_KEY,
  });
}

export async function createCheckoutSession(args: {
  successUrl: string; cancelUrl: string; metadata?: Record<string, string>; coupon?: string;
}) {
  const price = await ensureGlobalCoverPrice();
  return stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{ price: price.id, quantity: 1 }],
    success_url: args.successUrl,
    cancel_url: args.cancelUrl,
    discounts: args.coupon ? [{ coupon: args.coupon }] : undefined,
    metadata: args.metadata,
  });
}

2) Use the global price in the checkout route

server/routes.coverTemplates.ts – swap the per-template price call:

// ...
import { createCheckoutSession } from "../stripe/coverCheckout";

router.post("/api/cover-templates/:id/checkout", authenticate, async (req: any, res) => {
  const tpl = await getCoverTemplate(req.params.id);
  if (!tpl || !tpl.is_active) return res.status(404).json({ error: "Not found" });

  const { customImageUrl, applyProDiscount } = req.body || {};
  const successUrl = `${process.env.APP_BASE_URL}/business-assets/templates/cover-dividers?success=true`;
  const cancelUrl = `${process.env.APP_BASE_URL}/business-assets/templates/cover-dividers?cancel=true`;

  // Optional: pass a coupon id when user is Pro
  const coupon = applyProDiscount ? process.env.STRIPE_COUPON_PRO_20 : undefined;

  const session = await createCheckoutSession({
    successUrl,
    cancelUrl,
    coupon,
    metadata: { templateId: tpl.id, customImageUrl: customImageUrl || "" }
  });

  await createPurchase({
    user_id: req.user.id,
    template_id: tpl.id,
    custom_image_url: customImageUrl || null,
    amount_cents: parseInt(process.env.COVER_TEMPLATE_PRICE_CENTS || "1499", 10),
    currency: process.env.COVER_TEMPLATE_CURRENCY || "usd",
    stripe_session_id: session.id
  });

  res.json({ checkoutUrl: session.url });
});


Your existing webhook stays the same (it marks the purchase paid and sets the download_url).

Watermark overlay (already in place)

You’ve got the CSS watermark overlay in the gallery & modal. That keeps previews safe for non-buyers. (We can add server-side stamping later if you want.)

Filters & categories (already included)

The gallery page I shipped includes category and price filters plus a search box. You just need to seed your first 20 items with category, preview_image_url, and at least one of pptx_url / keynote_url / gslides_url (or a zip download_bundle_url).