# Invoice template with online payment

Most invoice templates are a document you fill in, export to PDF, and email
into the void. This one is different: it's a complete QuizGen JSON
definition. POST it (or hand it to an AI assistant) and you get a hosted
invoice page at a short link — your client opens it, confirms the charges
are right, approves, and pays by card without leaving the page. You get a
timestamped approval and the payment in your own Stripe account.

Card payment inside the invoice needs the Business plan and a one-time
Stripe connection. Everything else here — the itemized invoice, the
recorded approval, the paper trail — works on any plan, and there's a
payment-link fallback below for plans without native payments.

## How the flow works

1. You (or your AI assistant) fill in the line items and POST the template.
2. Your client gets a short link — `https://quizgen.dev/q/x7km2p`.
3. They review the itemized charges, approve, and pay by card in place.
4. The money lands in your Stripe account. The response records
   `paid: true` and the `payment_intent` id, so it reconciles cleanly
   against your Stripe dashboard.
5. You read approvals back via `GET /quizzes/:id/responses`, or get a
   [webhook](/blog/quiz-response-webhooks) the moment one completes.

An invoice, an approval record, and the payment — without asking the client
to create an account or open an attachment.

## The template

Replace the line items and amounts before posting. `amount_cents` is the
total in cents, so $3,300.00 is `330000`:

```json
{
  "schema_version": 1,
  "kind": "form",
  "title": "Invoice #2026-014 — Acme Design Co.",
  "description": "Due within 14 days. Questions? Reply to the email this came in.",
  "settings": {
    "collect_respondent": "email_required",
    "completion": { "message": "Thanks — payment received. A receipt is on its way." }
  },
  "sections": [
    {
      "key": "invoice",
      "title": "Invoice details",
      "questions": [
        { "id": "line_items", "type": "statement",
          "title": "Invoice #2026-014 · due August 19, 2026",
          "body": [
            "Website redesign — homepage and pricing page: $2,400",
            "Contact form + CRM integration: $600",
            "Rush delivery (agreed July 28): $300",
            "Total due: $3,300"
          ] },
        { "id": "details_correct", "type": "yes_no",
          "prompt": "Do the charges above match what we agreed?",
          "required": true },
        { "id": "correction", "type": "long_text",
          "prompt": "What looks off? I'll send a corrected invoice.",
          "show_if": { "question": "details_correct", "op": "eq", "value": "no" } },
        { "id": "approval", "type": "consent",
          "prompt": "I approve this invoice for $3,300.",
          "show_if": { "question": "details_correct", "op": "eq", "value": "yes" },
          "required": true },
        { "id": "pay", "type": "payment",
          "prompt": "Pay this invoice",
          "amount_cents": 330000,
          "currency": "usd",
          "button_label": "Pay $3,300",
          "show_if": { "question": "details_correct", "op": "eq", "value": "yes" },
          "required": true }
      ]
    }
  ]
}
```

What each piece is doing:

- `kind: "form"` — tells QuizGen this is a form rather than a quiz, so it
  renders as one page instead of one question at a time. Advisory only:
  anything you put in `settings` still wins.
- `collect_respondent: "email_required"` — the approval is tied to your
  client's email address, so the response is attributable.
- The `statement` holds the line items. It's display-only; respondents
  can't edit it, which is exactly what you want on an invoice.
- `details_correct` + `show_if` — a "no" opens a correction box instead
  of pushing a confused client toward paying; a "yes" reveals the approval
  and the payment button.
- `consent` — the approval is a required, timestamped agreement stored on
  the response, separate from the payment itself.
- `payment` — the card field. Because it's hidden by `show_if` when the
  charges are disputed, and hidden questions are never required, a client
  who flags a problem can still submit without paying.

## What you need for card payments

- The **Business plan**, and Stripe onboarding finished once at
  https://quizgen.dev/dashboard/payments. Without it the create call returns
  402 `payments_not_ready`.
- Money goes to **your own Stripe account** — QuizGen never holds it. QuizGen
  takes a 2% platform fee; Stripe's own processing fees apply as normal.
- **One payment question per quiz.** Use `amount_cents` for a fixed total,
  or `price_options` (2–10 entries of `{value, label, amount_cents}`) when
  the client picks — handy for "pay in full" versus "50% deposit now".

For the non-invoice cases — order forms, paid registrations, deposits taken
alongside an intake questionnaire — the same `payment` question is covered
more generally at https://quizgen.dev/form-with-payment.

### If you're not on Business yet

Drop the `pay` question and send the client onward to a payment link you
already have:

```json
"completion": {
  "message": "Thanks — taking you to payment now.",
  "redirect_url": "https://buy.stripe.com/YOUR_PAYMENT_LINK"
}
```

You still get the itemized invoice and the recorded approval; you just
reconcile "approved" against "paid" yourself.

## Let an AI assistant do the filling in

The fastest path isn't editing JSON by hand. Connect
[QuizGen's MCP server](/mcp)
and say:

> Read https://quizgen.dev/blog/invoice-template.md and make me an invoice
> for Jordan at Acme: 12 hours of consulting at $150/hr and one $200 setup
> fee, due in 14 days, payable by card. Give me the link to send.

The assistant adapts the template, POSTs it, and hands back the live link —
and can tell you later whether it's been paid.

## Knowing when you've been paid

Set a [webhook](/blog/quiz-response-webhooks) and each completed invoice
POSTs itself to your endpoint — client email, answers, `paid`, and the
`payment_intent` id — so you can route it to Slack, your CRM, or your
bookkeeping without polling. The `payment_intent` is the same id you'll
see in Stripe, which makes reconciliation a lookup rather than a guess.

## FAQ

**Is this a real invoice, legally?** It's an itemized bill with a recorded,
timestamped approval and a matching payment record — for most freelance and
small-business work that's what matters. If your jurisdiction requires
numbered tax invoices with specific fields, put them in the `statement`
body and keep your own numbering.

**Can I take a deposit instead of the full amount?** Yes — use
`price_options` with "50% deposit" and "Pay in full" as the two choices.
A [quote](/blog/quote-template) with a deposit is the natural step before
this one.

**Can I limit it to one client?** Send the link only to them —
`email_required` records who responded. Add `"max_responses": 1` to
`settings` if you want the link to close after the first completion.

**Can it expire?** Yes — `"close_at": "2026-08-19T00:00:00Z"` in
`settings` closes it at the due date.

**What about recurring invoices?** POST the same template monthly with
updated line items — one API call, or one sentence to your assistant.
