Skip to main content
Version: Next

Stripe Horoscope Agent

Payment Protocol + Stripe Checkout. This example is a seller agent that:

  • Chats using the Agent Chat Protocol
  • Requests a $1 USD Stripe payment using the Agent Payment Protocol (payment_method="stripe")
  • After payment, generates a "horoscope of the day" using ASI:One (model="asi1")
Supported client

This flow needs a UI that understands RequestPayment.metadata["stripe"] and can render Stripe embedded Checkout. Use the Agentverse inspector (the path in End-to-end below). Plain ASI:One chat often will not show the checkout card.

The full runnable example lives in the examples repo:

Keeping code in the examples repo avoids overwhelming docs with large code blocks.

Compare payment rails:

Protocol reference: Agent Payment Protocol

Prerequisites

  • Python 3.11+
  • ASI:One API key: create one from the ASI:One developer page
  • Stripe test API keys (required for this tutorial):
    • Use a Stripe sandbox or test mode (no real money). See Stripe Sandboxes.
    • Get your test keys (publishable + secret). See Stripe API keys.
    • Copy:
      • Secret key (sk_test_...) → STRIPE_SECRET_KEY
      • Publishable key (pk_test_...) → STRIPE_PUBLISHABLE_KEY
  • Agentverse account (logged in): local mailbox=True agents connect through the inspector. Open the inspector URL printed at startup and choose Connect → Mailbox. Agentverse issues the mailbox token in that UI. The example .env.example does not use AGENTVERSE_API_KEY. See Mailbox Agents.
  • Unique AGENT_SEED: the default seed stripe-horoscope-agent-test produces a shared agent address for every reader who leaves it unchanged. Set your own seed in .env before running so you do not collide with other local copies.
Test keys only

Use sk_test_... / pk_test_... while following this page. Do not put sk_live_ keys in .env until you have completed Stripe's go-live checklist and production hardening.

Tested versions

Pin these versions (same as other Payment Protocol examples in this lab). After pip install -r requirements.txt, install the pins if the examples repo still lists older uagents numbers:

requirements.txt
uagents==0.25.5
uagents-core==0.4.9
python-dotenv==1.0.1
openai
stripe

Run locally

  1. Clone the example.
clone.sh
git clone https://github.com/fetchai/innovation-lab-examples.git
cd innovation-lab-examples/stripe-horoscope-agent
  1. Create a virtualenv and install deps.

macOS / Linux:

setup-unix.sh
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install "uagents==0.25.5"

Windows (cmd):

setup-windows.bat
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
pip install "uagents==0.25.5"
  1. Copy the env template, then edit .env before starting. config.py raises if keys are missing.

macOS / Linux:

cp .env.example .env

Windows (cmd):

copy .env.example .env

Fill at least:

.env
ASI_ONE_API_KEY=your_asi_one_api_key
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
AGENT_SEED=change-this-to-a-unique-seed
AGENT_PORT=8012
  1. Start the agent.
python3 agent.py

Optional Stripe config

These live in .env.example and are read by config.py / stripe_payments.py. The example always charges a fixed amount (STRIPE_AMOUNT_CENTS, default 100 = $1.00). Dynamic pricing is an extension, not the default path.

VariableDefaultPurpose
STRIPE_AMOUNT_CENTS100Checkout unit_amount (cents)
STRIPE_CURRENCYusdCheckout currency
STRIPE_PRODUCT_NAMEDaily horoscopeProduct name on the session
STRIPE_SUCCESS_URLhttps://agentverse.ai/payment-successBase for return_url
STRIPE_CHECKOUT_EXPIRES_SECONDS1800Session expiry; Stripe requires at least ~30 minutes (clamped to [1800, 86400])

Key files (what to read first)

  • agent.py: tiny entrypoint; loads env and includes the chat + payment protocols. (GitHub)
  • handlers.py: the main state machine (sign → payment → horoscope). (GitHub)
  • stripe_payments.py: creates embedded Stripe Checkout sessions and verifies payment_status. (GitHub)
  • llm.py: ASI:One calls + prompts (normal reply vs horoscope generation).
  • state.py: short-lived ctx.storage state + zodiac parsing helpers.
  • chat_proto.py / payment_proto.py: protocol wrappers that keep boilerplate out of the handlers.

How payments are enabled (Payment Protocol + Stripe)

This example combines:

  • the Agent Payment Protocol (for requesting/committing/completing payments), and
  • Stripe embedded Checkout (as the payment rail when Funds.payment_method == "stripe").

Flow

Steps: RequestPaymentembedded CheckoutCommitPaymentCompletePayment (or CancelPayment) → horoscope.

The screenshot below is the Payment Protocol workflow (RequestPayment through CompletePayment / CancelPayment).

Generic Payment Protocol workflow

If you have not read the protocol docs yet, start here:

1) Enable the Agent Payment Protocol (seller)

To make an agent "payable", you include the payment protocol with seller role:

  • payment_proto.py: wraps payment_protocol_spec with role="seller" and routes payment messages to handlers. (GitHub)
payment_proto.py
from uagents import Context, Protocol
from uagents_core.contrib.protocols.payment import (
CancelPayment,
CommitPayment,
CompletePayment,
RejectPayment,
payment_protocol_spec,
)

def build_payment_proto(on_commit, on_reject) -> Protocol:
proto = Protocol(spec=payment_protocol_spec, role="seller")

@proto.on_message(CommitPayment)
async def _on_commit(ctx: Context, sender: str, msg: CommitPayment):
await on_commit(ctx, sender, msg)

@proto.on_message(RejectPayment)
async def _on_reject(ctx: Context, sender: str, msg: RejectPayment):
await on_reject(ctx, sender, msg)

return proto
  • agent.py: includes the payment protocol in the agent. (GitHub)
agent.py
agent.include(build_chat_proto(on_chat), publish_manifest=True)
agent.include(build_payment_proto(on_commit, on_reject), publish_manifest=True)

In this flow you will typically handle:

  • RequestPayment (seller → buyer/UI): "Here is what to pay and how."
  • CommitPayment (buyer/UI → seller): "I paid; here is the transaction id."
  • CompletePayment (seller → buyer/UI): "Payment verified and accepted."
  • CancelPayment (seller → buyer/UI): after CommitPayment, the seller aborts (unpaid, wrong method, verify failed).
  • RejectPayment (buyer → seller): the buyer declines a RequestPayment. The seller handles it; the seller does not send RejectPayment in reply to CommitPayment.

Valid edges from payment_protocol_spec in this repo:

  • RequestPayment{CommitPayment, RejectPayment}
  • CommitPayment{CompletePayment, CancelPayment}

See Agent Payment Protocol.

2) Add Stripe: create an embedded Checkout Session

Stripe integration in the example lives in stripe_payments.py. Match the repo: expires_at (Stripe minimum ~30 minutes), Checkout metadata, and extra return_url query params (chat_session_id, user). Env: STRIPE_SUCCESS_URL, STRIPE_CHECKOUT_EXPIRES_SECONDS.

  • create_embedded_checkout_session(...)
    • calls stripe.checkout.Session.create(ui_mode="embedded", ...)
    • returns a dict containing the publishable key, client secret, and Checkout Session ID
stripe_payments.py
def create_embedded_checkout_session(*, user_address: str, chat_session_id: str, description: str) -> dict:
return_url = (
f"{STRIPE_SUCCESS_URL}"
f"?session_id={{CHECKOUT_SESSION_ID}}"
f"&chat_session_id={chat_session_id}"
f"&user={user_address}"
)

session = stripe.checkout.Session.create(
ui_mode="embedded",
redirect_on_completion="if_required",
payment_method_types=["card"],
mode="payment",
return_url=return_url,
expires_at=_stripe_expires_at(),
line_items=[
{
"price_data": {
"currency": STRIPE_CURRENCY,
"product_data": {"name": STRIPE_PRODUCT_NAME, "description": description},
"unit_amount": STRIPE_AMOUNT_CENTS,
},
"quantity": 1,
}
],
metadata={
"user_address": user_address,
"session_id": chat_session_id,
"service": "daily_horoscope",
},
)
return {
"client_secret": session.client_secret,
"id": session.id,
"checkout_session_id": session.id,
"publishable_key": STRIPE_PUBLISHABLE_KEY,
"currency": STRIPE_CURRENCY,
"amount_cents": STRIPE_AMOUNT_CENTS,
"ui_mode": "embedded",
}
  • verify_checkout_session_paid(checkout_session_id)
    • calls stripe.checkout.Session.retrieve(checkout_session_id)
    • checks payment_status == "paid"
stripe_payments.py
def verify_checkout_session_paid(checkout_session_id: str) -> bool:
session = stripe.checkout.Session.retrieve(checkout_session_id)
return getattr(session, "payment_status", None) == "paid"

See the implementation:

Embedded Checkout (what "embedded" means)

  • ui_mode="embedded" means your UI renders Stripe Checkout inside the page (instead of redirecting to a hosted checkout page).
  • Stripe returns a client_secret (for the embedded checkout UI) and a Checkout Session ID (checkout_session_id).
  • Your UI uses the publishable key + client secret to render the checkout.
  • Your agent uses the Checkout Session ID to verify payment status after CommitPayment.

Default amount vs dynamic pricing (extend)

This example always uses STRIPE_AMOUNT_CENTS from .env / config.py (default 100) when creating the session in stripe_payments.py. Change that env var to change the price.

Dynamic pricing is an extension, not what the sample ships:

  • Server-side computed amount (ad-hoc): compute amount_cents in your agent (based on plan/tier/promo) and pass it into stripe.checkout.Session.create(... unit_amount=amount_cents ...).
  • Pre-created Stripe Prices: create Prices in Stripe and pass line_items=[{"price": "price_...", "quantity": 1}] (recommended when you have a small set of fixed tiers).

If you implement dynamic amounts, keep it safe by:

  • computing price on the seller/agent side (never trust client/UI-supplied price)
  • optionally verifying amount_total / currency on the retrieved Checkout Session before delivering the paid content.

3) Put Stripe session details into RequestPayment.metadata

The Payment Protocol is rail-agnostic. The "how to render and complete payment" details go into RequestPayment.metadata.

For Stripe embedded Checkout, the convention used in this example is:

  • Funds(payment_method="stripe")
  • RequestPayment.metadata["stripe"] = <embedded checkout payload>

Payload shape (example):

request-payment-metadata.json
{
"stripe": {
"ui_mode": "embedded",
"publishable_key": "pk_test_...",
"client_secret": "cs_test_...",
"checkout_session_id": "cs_test_...",
"currency": "usd",
"amount_cents": 100
}
}
Never send the Stripe secret key

STRIPE_PUBLISHABLE_KEY belongs in RequestPayment.metadata so the UI can render Checkout. Never put STRIPE_SECRET_KEY in metadata, chat, or any buyer-visible payload.

This is what lets Agentverse (or any compatible UI) render the embedded checkout for the user.

See where the agent constructs RequestPayment and attaches metadata["stripe"]:

handlers.py
checkout = create_embedded_checkout_session(...)

req = RequestPayment(
accepted_funds=[Funds(currency="USD", amount="1.00", payment_method="stripe")],
recipient=str(ctx.agent.address),
description="Pay $1 to receive your horoscope of the day.",
metadata={"stripe": checkout, "service": "daily_horoscope"},
)
await ctx.send(sender, req)

4) Commit → verify → complete (the critical contract)

After the user completes checkout, the buyer/UI sends:

  • CommitPayment(funds.payment_method="stripe", transaction_id="<checkout_session_id>")

In this example, transaction_id is the Stripe Checkout Session ID (e.g. cs_test_...).

Then the seller agent:

  1. Verifies with Stripe that payment_status == "paid"
  2. Sends CompletePayment(transaction_id=...) or CancelPayment(transaction_id=..., reason=...) if verify fails
  3. Delivers the paid content (the horoscope) only after CompletePayment

The UI may send CommitPayment before Stripe marks the session paid. If verify fails, reply with CancelPayment and ask the user to finish checkout, then Approve/Pay again.

See the commit handler that verifies, completes, then responds:

Use CancelPayment on failed verify (protocol: after CommitPayment the seller may send CompletePayment or CancelPayment only):

handlers.py
from uagents_core.contrib.protocols.payment import (
CancelPayment,
CommitPayment,
CompletePayment,
)

async def on_commit(ctx: Context, sender: str, msg: CommitPayment):
if msg.funds.payment_method != "stripe" or not msg.transaction_id:
await ctx.send(
sender,
CancelPayment(
transaction_id=msg.transaction_id,
reason="Unsupported payment method (expected stripe).",
),
)
return

paid = verify_checkout_session_paid(msg.transaction_id)
if not paid:
await ctx.send(
sender,
CancelPayment(
transaction_id=msg.transaction_id,
reason="Stripe payment not completed yet. Please finish checkout.",
),
)
return

await ctx.send(sender, CompletePayment(transaction_id=msg.transaction_id))
# ... generate + send horoscope ...

Keep RejectPayment on the buyer side (declining RequestPayment) and in the seller's @proto.on_message(RejectPayment) handler.

End-to-end run + test (Agentverse inspector)

This is the required test path: local agent + Agentverse inspector + Stripe test card.

  1. Fill .env (see Run locally) and start the agent.
abhimanyugangani@Abhimanyus-MacBook-Pro stripe-horoscope-agent % python3 agent.py 
INFO: [stripe-horoscope-agent]: Starting agent with address: agent1q2t7gnf3wymv6g7mxghm8df5cvfcgncuf057gpngrhgggv82ynxvj0r7alj
INFO: [stripe-horoscope-agent]: Agent inspector available at https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A8012&address=agent1q2t7gnf3wymv6g7mxghm8df5cvfcgncuf057gpngrhgggv82ynxvj0r7alj
INFO: [stripe-horoscope-agent]: Starting server on http://0.0.0.0:8012 (Press CTRL+C to quit)
INFO: [stripe-horoscope-agent]: Starting mailbox client for https://agentverse.ai
INFO: [stripe-horoscope-agent]: Manifest published successfully: AgentChatProtocol
INFO: [stripe-horoscope-agent]: Manifest published successfully: AgentPaymentProtocol
INFO: [uagents.registration]: Registration on Almanac API successful

The address in your logs depends on AGENT_SEED. Do not expect the shared sample address from the default seed.

  1. Open the Agent inspector link printed in the terminal (while logged into Agentverse) and click Connect → Mailbox.

Connect your agent

  1. In inspector chat:
    • Send give me my horoscope
    • Reply with a sign (e.g. Leo)

Chat with agent

  1. The inspector shows a Stripe payment card.
    • Use Stripe test card 4242 4242 4242 4242 (any future expiry, any CVC, any ZIP). See Stripe test cards.
    • Finish checkout first, then click PAY (this triggers CommitPayment to the agent). If PAY happens before Stripe reports paid, the seller should send CancelPayment; complete checkout and Approve/Pay again.

Stripe embedded checkout

  1. The agent verifies Stripe payment, sends CompletePayment, and replies with your horoscope.

Horoscope delivered after payment

Troubleshooting

SymptomWhat to do
RuntimeError: Missing ASI_ONE_API_KEYEdit .env and set ASI_ONE_API_KEY. Copying .env.example is not enough.
RuntimeError: Missing STRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEYSet both Stripe test keys in .env.
Mailbox client starts but inspector cannot chatLog into Agentverse, open the printed inspector URL, Connect → Mailbox.
CancelPayment / "not completed yet" after PAYFinish embedded checkout, wait for Stripe to mark the session paid, then Approve/Pay again.
Same agent address as the sample logsChange AGENT_SEED in .env and restart.
No Stripe card in ASI:One chatUse Agentverse inspector; this tutorial requires a UI that renders metadata["stripe"].