Skip to main content
Version: Next

Stripe Horoscope Agent

This example demonstrates 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")

The full runnable example lives in the examples repo:

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

Prerequisites

  • Python 3.11+
  • ASI:One API key: create one from the ASI:One developer page
  • Stripe test API keys (recommended first):
    • Use a Stripe sandbox for testing (no real money moves). 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

Run locally

git clone https://github.com/fetchai/innovation-lab-examples.git
cd innovation-lab-examples/stripe-horoscope-agent

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

cp .env.example .env
python3 agent.py

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 diagram

Stripe horoscope payment flow

If you haven’t 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)
from uagents import Context, Protocol
from uagents_core.contrib.protocols.payment import CommitPayment, 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.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’ll typically handle:

  • RequestPayment (seller → buyer/UI): “Here’s what to pay and how.”
  • CommitPayment (buyer/UI → seller): “I paid; here’s the transaction id.”
  • CompletePayment (seller → buyer/UI): “Payment verified and accepted.”
  • RejectPayment (either side): “Unsupported / cancelled / not paid yet.”

2) Add Stripe: create an embedded Checkout Session

Stripe integration in the example lives in stripe_payments.py:

  • create_embedded_checkout_session(...)
    • calls stripe.checkout.Session.create(ui_mode="embedded", ...)
    • returns a small dict containing the publishable key, client secret, and Checkout Session ID
def create_embedded_checkout_session(*, user_address: str, chat_session_id: str, description: str) -> dict:
session = stripe.checkout.Session.create(
ui_mode="embedded",
redirect_on_completion="if_required",
mode="payment",
payment_method_types=["card"],
return_url=STRIPE_SUCCESS_URL + "?session_id={CHECKOUT_SESSION_ID}",
line_items=[
{
"price_data": {
"currency": STRIPE_CURRENCY,
"product_data": {"name": STRIPE_PRODUCT_NAME, "description": description},
"unit_amount": STRIPE_AMOUNT_CENTS,
},
"quantity": 1,
}
],
)
return {
"client_secret": session.client_secret,
"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"
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.

Dynamic pricing (variable amounts)

There are two common patterns:

  • 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):

{
"stripe": {
"ui_mode": "embedded",
"publishable_key": "pk_test_...",
"client_secret": "cs_test_...",
"checkout_session_id": "cs_test_...",
"currency": "usd",
"amount_cents": 100
}
}

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"]:

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=...)
  3. Delivers the paid content (the horoscope)

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

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, RejectPayment(reason="Unsupported payment method (expected stripe)."))
return

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

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

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

  1. Start the agent locally (see Run locally above).
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
  1. Open the Agent inspector link printed in the terminal and click connect.

Connect your agent

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

Chat with agent

  1. The UI shows a Stripe payment card.
    • Use Stripe test card 4242 4242 4242 4242 (any future expiry, any CVC, any ZIP). See Stripe test cards.
    • Click PAY (this triggers CommitPayment to the agent).
Stripe embedded checkout
  1. The agent verifies Stripe payment, sends CompletePayment, and replies with your horoscope.

Horoscope delivered after payment