Gift Card

A gift card or store credit to check and show at the register, or a new gift card to send, handed off to the host's payment flow.

Experience · Shares patterns with Account Balances, QR Code

≈ 1,649 tokens per use (tool 1,272 · guidance 317 · typical payload 60)How token costs are estimated

Example: Gift Card

What's left on my bookstore gift card?

Paper Lantern Books

Gift card, card ending in 4821

Balance $37.50

Expires Dec 31, 2027

When to use

  • The user asks what's left on a gift card or store credit the agent found in their account or email.
  • They want to show a card at a register to pay with it.
  • They want to send someone a gift card from a store: amount, design, recipient and message, then payment.

When not to use

  • The agent doesn't have the card from the issuer. Never invent balances, codes or card digits.
  • The user wants to pay for something in chat. Send mode only hands off to the host's payment flow; card details never go through the conversation.
  • It's a bank card or account balance. Use Account Balances.

How the agent should use it

  • Pass only the last 4 digits of the card. The schema rejects anything but exactly 4 digits. Never pass the full number or the PIN; set pin: { masked: true } when the card has one, and the card says "PIN on the back".
  • Pass code.value exactly as the issuer gave it. Never build a code yourself.
  • In send mode, prefill amount, recipient and message only from what the user said. On send, start the host's payment flow with the returned values; nothing is bought until payment succeeds there.
  • On refresh, fetch the balance again and show a new card. On add-to-wallet, open the host's wallet pass.
  • Lifecycle: a balance is live data, so in balance mode set fetchedAt to when the balance was read and staleAfterMs (e.g. an hour) on the call; the Renderer then marks an old balance as stale and offers a refresh. Supersede the card with the new one after refresh. Set expiresAt to the card's expiry only if an expired card can't be used at all. Send mode needs none of these.

Balance mode

The store's name, then one quiet line with the kind and the masked number ("Gift card · •••• 4821"). The balance is the hero, in large type with dimmed cents, with the expiry ("Expires Dec 31, 2027", "Expires today" or "Expired Mar 1, 2026") and the PIN note beneath.

Show to cashier (the primary action when there's a code) expands the issuer's code as a QR code, always black on a white tile (in dark mode too, so scanners can read it). It isn't expanded by default, so the code isn't on screen until the user wants it. Turning the screen brightness up helps scanners; say so in your reply if useful. The code is encoded locally with qrcode-generator (UTF-8, level M); nothing is sent anywhere.

Actions: Add to wallet and Check balance again (in a "More actions" menu when there's a code), each reported once.

Send mode

  • Card preview on top: the chosen design (its artwork, or its pattern), the amount in large type (on artwork the type is white with a soft shadow over a light top and bottom scrim, so it needs no background box) and the recipient, updating as the user edits.
  • Amount: preset chips (radio buttons), plus an "Other" input when custom is set, validated against its min and max in integer minor units ("The minimum is $10.", "Use at most 2 decimal places.").
  • Design: radio thumbnails, named for screen readers. A design with an image shows the store's artwork (cropped to fit, here a Warsaw poster, white roses and poppies); one without shows a neutral pattern (stripes, dots, grid) drawn as thin lines in the border color. A broken image link drops to the plain card.
  • Recipient: name and email, prefilled and editable. Email is checked for a sane shape ("Enter a valid email, like dana@example.com.").
  • Message: a textarea with a live counter ("42/250"), limited to messageMax characters (emoji count once). Screen readers hear the remaining count near the limit.
  • Delivery: send now, or schedule with a date from today on.

Errors show after a field loses focus or on Continue to payment, which then focuses the first problem. Continue to payment returns send and the footer confirms "Sent to payment"; Cancel returns dismiss. Nothing is charged in the card; payment happens in the host's next step. After either, the form locks.

Installation

npx shadcn@latest add https://your-site.com/r/gift-card.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { giftCardTool } from "@/components/genui/gift-card.schema"

const tools = {
  // No execute: the user's choice is the tool result.
  [giftCardTool.name]: tool({
    description: giftCardTool.description,
    inputSchema: giftCardTool.inputSchema,
  }),
}

Render the tool call and hand the result to your own flow:

components/message.tsx
import { GiftCard } from "@/components/genui/gift-card"

function ToolPart({ part, addToolResult }) {
  if (part.type !== "tool-show_gift_card") return null

  return (
    <GiftCard
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={(result) => {
        if (result.action === "send") openGiftCardCheckout(result)
        if (part.state !== "output-available") {
          addToolResult({ tool: "show_gift_card", toolCallId: part.toolCallId, output: result })
        } else {
          sendMessage({ text: `Gift card action: ${JSON.stringify(result)}` })
        }
      }}
    />
  )
}

The agent receives one of:

// Balance mode, once per action
{ action: "add-to-wallet" }
{ action: "refresh" }

// Send mode, once
{
  action: "send",
  amount: 50,
  currency: "USD",
  designId: "warsaw",
  recipient: { name: "Dana Levi", email: "dana.levi@mail.example" },
  message: "Happy birthday, Dana!",
  deliverAt: "now", // or "2026-10-23"
}
{ action: "dismiss" }

With LangGraph

Push it from a graph node with ui.push({ name: "show_gift_card", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

When a conversation is reloaded, pass the stored result back as result (an array in balance mode when both actions were taken). Balance actions show as done; a send restores the chosen amount, design, recipient, message and delivery, read-only. Neither calls onResult again.

<GiftCard
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
mode"balance" | "send"
brand{ name, initials? }Initials derived from the name when absent. Data-bound
cardCard?Required in balance mode. Data-bound
card.kind"gift-card" | "store-credit"Default gift-card
card.last4stringExactly 4 digits, shown as "•••• 4821"
card.balance, card.currencynumber, stringMajor units; ISO 4217 code
card.expiresAtstring?ISO 8601 date
card.pin{ masked: true }?Never the PIN itself
card.code{ format: "qr", value }?The issuer's scannable payload, up to 512 characters
sendSend?Required in send mode
send.amountsnumber[]1–6 presets, e.g. [25, 50, 100]. Data-bound
send.custom{ min, max }?Allows a typed amount in this range
send.currencystringISO 4217 code
send.designs{ id, label, pattern?, image? }[]?Up to 6. image is data-bound artwork (http(s) or a same-origin path). pattern: stripes, dots, grid, plain; the fallback, and it cycles when absent
send.designIdstring?Preselected design; default the first
send.amountnumber?The amount the user asked for; selects a preset or fills the custom amount
send.deliverAt"now" | string?now (default) or a date
send.recipient{ name?, email? }?Prefilled, editable
send.messagestring?A draft message, editable
send.messageMaxnumber?20–1000. Default 250

Host props: status (streaming, ready, submitting, done, sent, dismissed), now, onResult, result and className.

Source

On this page