Skip to main content
Version: 1.0.5

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

Sample chat (what it looks like)

User: give me my horoscope
Agent: Sure — what’s your star sign? (e.g. Aries, Taurus, Gemini, ...)
User: Leo
Agent: Pay $1 to receive your horoscope of the day.
UI: (Stripe embedded checkout + Approve button)
Agent: Once payment completes, I’ll reply here with your horoscope.