Order form template that takes payment

Build & score

Short answer: this is a complete QuizGen JSON definition for a small-business order form — customer details, product picks, a pickup or delivery date — with a native Stripe payment question at the end so the order and the payment happen in the same step. POST it as-is (with your own products and prices) and you get a hosted order page at a short link. If you haven't connected Stripe yet, skip to the fallback section below — the rest of the template still works.

This is what an order form template is for: not a full storefront, but a single link you can text or post that takes someone from "I want one" to "paid" for a fixed, simple catalog — pre-orders, custom orders, small runs, event merch. For the wider case — what a hosted order form does that a printable one can't, and where it stops — see https://quizgen.dev/online-order-form.

The template

A print shop taking custom t-shirt orders, priced by quantity:

{
  "schema_version": 1,
  "kind": "form",
  "title": "Riverside Print Co. — order form",
  "description": "Custom screen-printed shirts. Payment is collected when you submit.",
  "settings": {
    "collect_respondent": "email_required",
    "completion": {
      "message": "Thanks! We'll confirm your order and pickup or delivery time by email."
    }
  },
  "sections": [
    {
      "key": "order",
      "title": "Your order",
      "questions": [
        { "id": "customer_name", "type": "short_text", "prompt": "Your name",
          "required": true },
        { "id": "phone", "type": "phone", "prompt": "Phone number",
          "required": true, "help": "In case we need to reach you about your order." },
        { "id": "style", "type": "choice", "prompt": "Which shirt are you ordering?",
          "required": true,
          "options": [
            { "value": "crew_tee", "label": "Crew neck tee" },
            { "value": "hoodie", "label": "Hoodie" },
            { "value": "tank", "label": "Tank top" },
            { "value": "long_sleeve", "label": "Long sleeve" }
          ] },
        { "id": "fulfillment", "type": "choice", "prompt": "Pickup or delivery?",
          "required": true,
          "options": [
            { "value": "pickup", "label": "Pickup at the shop" },
            { "value": "delivery", "label": "Local delivery" }
          ] },
        { "id": "delivery_address", "type": "short_text", "prompt": "Delivery address",
          "required": true,
          "show_if": { "question": "fulfillment", "op": "eq", "value": "delivery" } },
        { "id": "needed_by", "type": "date", "prompt": "Date you need it by",
          "required": true },
        { "id": "notes", "type": "long_text", "prompt": "Design details, logo placement, colors",
          "help": "Attach nothing here — just describe it. We'll follow up by email if we need a file.",
          "rows": 4 }
      ]
    },
    {
      "key": "payment",
      "title": "Payment",
      "questions": [
        { "id": "package", "type": "payment",
          "prompt": "Choose your quantity and pay now", "required": true,
          "price_options": [
            { "value": "single", "label": "1 shirt", "amount_cents": 2500 },
            { "value": "five_pack", "label": "5-pack", "amount_cents": 11000,
              "detail": "$22 each" },
            { "value": "ten_pack", "label": "10-pack", "amount_cents": 20000,
              "detail": "$20 each" },
            { "value": "twenty_pack", "label": "20-pack (event size)", "amount_cents": 38000,
              "detail": "$19 each" }
          ],
          "button_label": "Pay & confirm order" }
      ]
    }
  ]
}

What each piece is doing

  • kind: "form" — the order shows on one page (not one question per screen), which matches how people expect to fill in an order.
  • collect_respondent: "email_required" — this is the "customer contact" field. You don't need a separate email question; the address is captured automatically and attached to the response.
  • style is a choice question. Note that every style costs the same here — see the rule below about why it has to.
  • fulfillment is a choice, and delivery_address only appears when they pick delivery, via show_if.
  • needed_by is a date question — always prefer it over a text field for dates; the answer comes back as "YYYY-MM-DD".
  • package is the one payment question this quiz is allowed to have. price_options (2–10 of them) is the right choice here instead of a single amount_cents, because the respondent is picking their quantity and paying the exact amount for it — one dropdown, one charge, no separate invoice step.

The one rule to keep: price_options is the only place quantity lives

There is deliberately no separate "How many shirts?" question in this template, and that is the most important thing to copy. A form takes one payment question, and that question charges exactly the amount_cents of the option the customer picked — nothing else on the form can change the total. So the moment you add a free-entry field that also implies a price, the two can disagree and the charge always wins:

A number question asking "How many shirts?" alongside a price_options list is a trap. The customer types 20, picks the 1 shirt — $25 option, and the form accepts it. The order arrives with paid: true against $25 for twenty shirts, and the only way to catch it is for a human to read every row.

The same trap catches per-option surcharges. If style had a "Hoodie +$15" label, picking the hoodie would still charge the plain bundle price, because price_options is the entire pricing model — labels are text, not arithmetic. Either fold the surcharge into the price list (a "5-pack hoodies" option at its real price) or charge one price across styles, as this template does.

The rule that follows: anything that changes what someone owes belongs in price_options, and nowhere else. Questions outside the payment question are for things you need in order to fulfil the order — style, colour, address, deadline, notes — not for things that determine the total. If your combinations won't fit in ten price options, that is the signal to reach for a real store rather than to add a quantity box.

Payment needs Stripe — here's the fallback

The payment question type is live on every plan, including free, but only after the creator finishes Stripe onboarding at quizgen.dev/dashboard/payments. Until that's done, creating a quiz with a payment question returns a 402 payments_not_ready error. Money always goes to the creator's own Stripe account — QuizGen takes a 2% platform fee on top, nothing more.

If you haven't connected Stripe yet, the rest of this template still works — just drop the payment section and pick one of two fallbacks:

  1. Collect now, invoice after. Take the order with everything above, then send an invoice with the total and a payment link once you've confirmed details (custom sizing, design proofs, whatever needs a human look first).
  2. Redirect to an external payment link. Add "completion": { "redirect_url": "https://buy.stripe.com/YOUR_LINK" } to settings and the respondent lands on your existing Stripe Payment Link or PayPal.me page right after submitting. You lose the "pick your size, pay the right amount" convenience of price_options, but the order and the payment still happen in one flow.

Either fallback is a completely normal way to run this template — plenty of small shops will use redirect links even after they're eligible for native payment, especially if they already have Stripe Payment Links set up.

Reading orders back

GET /api/v1/quizzes/:id/responses returns each order's answers (style, package, fulfillment, notes) plus, for a paid order, paid: true and the Stripe payment_intent id — that's what you reconcile against your Stripe dashboard if a number ever looks off. Filter with ?complete=true to skip abandoned carts.

For same-day routing, don't poll — set a webhook on the quiz. Every completed order POSTs itself to your endpoint the moment it's paid, so you can push it straight into your fulfillment queue, a Slack channel, or a spreadsheet instead of checking the dashboard.

When a real store is the better tool

Be honest with yourself about the size of the catalog before reaching for this. QuizGen's order form is for a fixed, simple catalog — a handful of products or package sizes, no per-item options exploding into hundreds of SKUs. It has no inventory tracking, no variant matrix (size × color × style all at once), and no shipping-rate calculator. If you need any of those — real inventory counts, a cart with multiple line items, calculated shipping — use Shopify or Square; they're built for exactly that and it would be a mistake to fight this template into doing their job. Reach for this template instead for pre-orders, custom one-off orders, small production runs, and event merch — the cases where "one page, one payment, done" is the whole requirement.

Let an AI assistant fill it in

Connect QuizGen's MCP server and say:

Read https://quizgen.dev/blog/order-form-template.md and make me an order form for my candle shop: 3 scents, one size, $18 each, pickup only, needed-by date, and a notes field for gift messages. I'm already connected to Stripe.

The assistant adapts the products and prices, POSTs the definition, and hands back the live link.

FAQ

Can I sell more than one product in the same order? Change style to multi_choice so people can select several items, but keep the payment question's price_options as fixed bundles or package sizes — there's no per-line-item cart math, so price each option as a complete order. If selecting a second item is supposed to cost more, that difference has to appear as its own price option; a multi_choice selection can't add to the total on its own.

How do I let someone order a quantity I didn't list? You don't — not in the same submission. Add a "More than 20 / custom quantity" choice option with no price attached and show_if a notes field off it, then quote them separately with an invoice. A form charges one listed amount; anything negotiated needs a second step.

What happens if someone abandons before paying? The response is saved with complete: false and no paid field. Nothing is charged, and nothing ships — you can safely ignore incomplete responses when you tally orders.

Can I cap how many orders I take? Yes — "max_responses": 50 in settings closes the form automatically once you hit it, which is exactly what a limited production run needs.

Does the order form need to be a quiz? No — kind: "form" renders everything on one page, which is what people expect from an order form. kind is advisory only; anything you set in settings overrides it.

$ curl quizgen.dev/blog/order-form-template.md

This guide as raw markdown — no HTML for your agent to parse.

View .md

Keep reading