# Gift cards with GOAT AgentKit

Use current responses from the [Actions schema](https://giftcard-api.goat.network/api/v1/actions)
and [OpenAPI contract](https://giftcard-api.goat.network/api/v1/openapi.json). The links use the
same configured API origin as this storefront.

## Published package and repository extensions

Inspect the published CLI before choosing commands or flags:

```sh
npx -p @goatnetwork/agentkit agentkit-giftcard --help
```

The package snapshot verified for this repository release is
`@goatnetwork/agentkit@0.2.3`. It publishes the `agentkit-giftcard` binary and
eight `goat.giftcard.*` tools: `listBrands`, `getBrand`, `listCategories`,
`listSupportedTokens`, `createOrder`, `payOrder`, `getOrder`, and `listOrders`.
Its CLI includes a wallet payment and polling workflow. In 0.2.3, `buy` asks for
confirmation before order creation and then transfers without a second review of
the returned amount, token contract, recipient, and expiry. Its `payOrder`
duplicate exclusion is process-local, so an uncertain sequential or
cross-process retry can transfer twice. Do not use 0.2.3 `buy` or `payOrder` for
an audited live payment, and never retry `payOrder` after an uncertain broadcast;
reconcile manually. Recheck the installed version because published behavior can
change.

The source plugin in this repository is a separate local integration. It exposes
14 `gift-card.*` actions. Version `2.0.0-rc.2` returns an inpage payment
instruction or a hosted URL requiring human action, selected by the server mode. It does not contain a hosted MCP service and does not
transfer funds. The additive repository actions are:

- `gift-card.catalog.regions.list`
- `gift-card.catalog.brands.list`
- `gift-card.catalog.brands.get`
- `gift-card.catalog.availability`
- `gift-card.catalog.offers.list`
- `gift-card.payments.tokens.list`
- `gift-card.orders.quote`

The backend discovery endpoint uses the corresponding unprefixed IDs. The
[manifest](https://giftcard-api.goat.network/api/v1/manifest) records this namespace binding. Do
not assume the additive actions exist in the published package until its current
help or exported tool list confirms them.

## Resolve an exact source offer

1. Call `catalog.regions.list` and explicitly choose a redemption destination.
   WW/GLC are supplier source codes, not proof of worldwide redemption. Use each
   offer's reviewed scope and restrictions. Region-level `purchasable=false`
   alone does not mean all products lack a quote.
2. Call `catalog.brands.list` with an explicit country. A supplier filter can
   narrow the result to `zypto` or `snaplii`.
3. Call `catalog.availability` with brand, country, currency and FIXED/VARIABLE
   type. Read cached results while missing amounts are checked in the background;
   wait for a ready amount without rounding or silently replacing an explicit
   choice. Then call `catalog.offers.list` with the canonical brand UUID and exact country,
   currency, and face value. Choose one returned offer. Its `id` is the source
   product UUID accepted by `orders.create`; its `brand_id` is the source brand
   UUID. Preserve both identities and never substitute a same-name offer.
4. Check `products.availability` again with that source product UUID and face
   value. For a multi-value `FIXED` offer, the value must be an exact member of
   `values[]`. For `VARIABLE`, it must stay within the current range and supply
   cap. Explicit product/amount ineligibility blocks purchase. A legacy
   `SUPPLIER_QUOTE_REQUIRED` response requires `orders.quote`; it is not SOLD OUT.
   Region `purchasable` is deprecated discovery metadata. A successful discovery
   check still requires fresh checkout validation; wait while `quote_state` is
   `checking`, honoring `retry_after_seconds` / Retry-After and backing off on errors.
5. Call `payments.tokens.list` or GET
   [`/api/v1/supported-tokens`](https://giftcard-api.goat.network/api/v1/supported-tokens). Select
   the checkout path first. An absent `checkout_mode` means `inpage`, where you
   select one exact chain, token symbol, contract, and decimals tuple. In
   `hosted` mode, GOAT Flow asks the person to choose the supported network and
   USDT or USDC on its secure page; the create request does not choose them.

A source UUID binds the supplier and source routing region. Reviewed redemption
coverage can include other countries; browsing never changes that source identity. Catalog presence and a
brand count do not prove stock. Zypto can deliver asynchronously, so a successful
payment is not yet a delivered card.

Snaplii and Zypto share reviewed canonical brand entries. Compare only offers
with equivalent redemption rights in the requested region, currency and amount.
Prefer a currently purchasable offer with the lowest customer total; preserve an
explicit supplier choice. `price_source`, `quoted_at` and `expires_at` describe
the purchase quote. Optional `display_price_usd` and `display_expires_at` allow
15-minute browsing reuse without extending purchase validity. A stale supplier
quote leaves `price_usd` null. Always obtain an authenticated `orders.quote`; never replace a selected source
product or use an old quote to authorize payment. Snaplii prices use configured
platform pricing and FX, while Zypto uses its supplier USD quote. A temporary
quote or funding failure does not prove that a brand has been discontinued.

## Fresh purchase quote

After selecting an exact source offer, authenticate and call `orders.quote` with
its `product_id` and native `face_value`. To explicitly request the inpage path
on a hosted server, send `checkout_mode:"inpage"` on both quote and create; this
requires server `flow.allow_inpage_requests=true`. A rejected override must stop
with a clear error, never silently change the payment path. Review country, local currency, face
value, `pay_amount_usd` and `expires_at`; send the reviewed total as
`expected_pay_amount_usd` to `orders.create`. Quote-enabled Zypto orders require
this field. Quoting creates no order or reservation and does not lock a supplier
price. A price change or unavailable quote requires a fresh review before retry.


## Coupon compatibility

This release has no coupon purchase or management capability. Legacy capability
requests return disabled; explicit coupon inputs are rejected, never dropped to
create a full-price order. Existing order snapshots remain readable.

## Create, review, and pay

Generate one idempotency key of at most 128 characters for one logical purchase
and reuse it for every create retry. The order snapshots its checkout mode; a
later feature-flag change never changes how that order is paid.

For `inpage`, after `orders.create`, compare the returned source product, country,
currency, face value, USD total, chain, token contract, base-unit amount,
recipient, and provider expiry with the reviewed intent before any wallet transfer.
Require at least 30 seconds of remaining validity.

For `hosted`, compare the same product/country/currency/face-value/USD intent,
require `PAYMENT_PENDING` and at least 30 seconds before order/session expiry,
and validate that `checkout_url` uses the configured GOAT Flow origin and its
single `cs` query equals `checkout_id`. Use plugin `checkoutOrigin` or CLI
`--checkout-origin` / `GIFT_CLI_CHECKOUT_ORIGIN` as the independent trust anchor
(default `https://flow-quickpay.goat.network`). Return the URL as a
human action: a person opens it, chooses the network and USDT or USDC, and then
the client polls `orders.get`. Do not expose the handle in logs, do not treat a
browser success callback as payment proof, and resume the exact same session
instead of creating another order. An agent cannot autonomously complete this
human-action checkout. Hosted plugin results include `human_action_required:true`
and `next_action:"open checkout_url, then poll orders.get"` only while a valid
payable URL exists; terminal/capability-free reads do not prompt another payment.
CLI `--checkout-mode auto|inpage` defaults to auto, prints the validated URL and
polls every five seconds by default for hosted table output (`--wait=false` opts
out; JSON remains one-shot). Inpage polling still requires `--wait`. Hosted quote
totals are rounded upward to cents, and chain/token
are chosen on Flow. A hosted payer may differ from the signed-in owner; only the
owner reads the card, and refunds go to the confirmed payer.

Creating an order does not authorize payment. Inpage mode leaves the transfer to
the host wallet: show and separately confirm the exact returned instruction before
that wallet broadcasts. Hosted mode leaves payment to the person at the validated
GOAT Flow URL. If either result is uncertain, stop for reconciliation; never
automatically retry payment or pay the same order twice.

If create returns `PAYMENT_RECOVERY_PENDING`, wait and retry with the same
idempotency key. If it returns `PAYMENT_RECOVERY_UNAVAILABLE`, preserve the same
key and local order details and use manual reconciliation. The currently vendored
x402 adapter cannot look up an order by dApp order ID, so automatic recovery is
not available through that adapter. Do not create a replacement order.
A `CREATED` order whose payment result is unknown retains its payment identity;
leave it intact and reconcile with the same idempotency key.

An `EXPIRED` order can still transition to `PAYMENT_CONFIRMED` during the five
minutes after provider expiry. Keep reconciling the same order during that
window and prohibit a replacement purchase.

## Track Zypto delivery and refunds

Poll `orders.get` by `order_id` with a bounded interval. Zypto deposit settlement
can advance through `ORDER_CREATED`, `DEPOSIT_PENDING`, `ISSUING`, and `DELIVERED`
while the public order is fulfilling. Continue until `FULFILLED` or a final
failure/refund state. A paid failure can continue through `REFUNDING`; track it to
`REFUNDED` or `REFUND_FAILED`. On timeout, retain the order ID and last settlement
and fulfillment states so polling can resume without creating a new order.
A refund transaction hash only records submission; wait for the API to report a
final refund state.

Card codes and PINs appear only after fulfillment and are private redeemable
value. An order ID, payment transaction, or supplier acceptance alone is not
proof of delivery.

## Local repository CLI

The source tree's `gift-cli` is separate from the published
`agentkit-giftcard` binary. Its buy command returns and validates an instruction;
it never transfers funds.

```sh
gift-cli tokens
gift-cli buy --product <source-product-uuid> --country CA --face-value 25 --yes --idempotency-key <stable-intent-key>
```

Review the returned region, face value,
net amount, token tuple, recipient, and expiry before paying through a separately
authorized wallet. Use `gift-cli orders get <order-id>` to resume a timed-out poll.
For JSON automation, create without `--wait`, separately confirm and pay the
returned instruction, then poll that same order with repeated `orders get`
calls. The CLI rejects JSON `buy --wait` before creating an order because it
cannot expose the instruction and retain a single-JSON stdout contract at the
same time.
