Test the Payment Flow
James opened WhatsApp. He had spent the last two lessons building tier gating and Stripe checkout. The tools worked in isolation: check_tier blocked free users from premium content, and get_upgrade_url created a real Stripe checkout link. But he had never tested the full journey. Free learner hits a paywall, gets an upgrade link, pays, comes back, and the content unlocks.
"I want to try the whole thing," he told Emma. "Start to finish. As if I were a real customer."
"You are a real customer. You built the product and you are about to buy it from yourself." She pointed at his phone. "Go."
You are doing exactly what James is doing. The complete customer journey, from paywall to payment to unlocked content. Then you add payment tests to the pytest suite so the journey is verified automatically.
Step 1: Request Gated Content as a Free Learner
Make sure your TutorClaw server is running and connected to OpenClaw. Your test learner should be on the free tier. If you upgraded your test learner in Lesson 14, reset the tier to "free" in your JSON state file, or register a new learner.
Pick up your phone. Send this message to your agent via WhatsApp:
Teach me about data structures in Chapter 6
Chapter 6 is beyond the free tier limit (chapters 1 through 5). The agent should:
- Call
get_chapter_contentwith chapter 6 - Receive a tier error from the tool (free tier, chapter not accessible)
- Call
get_upgrade_urlto generate a Stripe checkout link - Respond with something like: "Chapter 6 requires a paid subscription. Here is your upgrade link: [URL]"
Open the dashboard. Find the message log. You should see two tool badges:
- get_chapter_content: called, returned a tier error
- get_upgrade_url: called, returned a checkout URL
Both tool badges confirm the agent made the correct decisions. It did not fabricate a response. It hit the paywall, recognized the error, and offered the upgrade path.
Step 2: Pay with the Test Card
Open the Stripe checkout link from the agent's response in your browser. You are now looking at a real Stripe checkout page running in test mode.
Enter the test card details:
| Field | Value |
|---|---|
| Card number | 4242 4242 4242 4242 |
| Expiration | Any future date (e.g., 12/30) |
| CVC | Any three digits (e.g., 123) |
| Name | Any name |
Click pay. Stripe processes the test payment and redirects you to the success page.
Behind the scenes, Stripe sends a checkout.session.completed webhook to your server. Your webhook handler (from Lesson 14) receives the event, extracts the learner ID from the session metadata, and updates the tier in your JSON state file from "free" to "paid."
Your Stripe CLI needs to be forwarding webhooks to your local server. If you stopped it since Lesson 14, restart it:
stripe listen --forward-to localhost:8000/webhook
Without the CLI forwarding, the webhook never reaches your server and the tier never updates.
Step 3: Verify the Tier Update
Before sending another WhatsApp message, confirm the tier actually changed. Ask Claude Code:
Check the learner's tier in the JSON state file. Has it changed
from free to paid?
Claude Code reads the JSON file and confirms the tier value. If the tier is still "free," the webhook did not fire or the handler did not update the file. Check the Stripe CLI terminal for webhook delivery logs and your server logs for errors.
Step 4: Request the Same Content Again
Send the same message via WhatsApp:
Teach me about data structures in Chapter 6
This time the agent should:
- Call
get_chapter_contentwith chapter 6 - Receive the content successfully (paid tier, all chapters accessible)
- Call
generate_guidanceto create a PRIMM-guided response - Respond with actual teaching content for Chapter 6
Open the dashboard. The tool badges for this second message should show:
- get_chapter_content: called, returned content successfully
- generate_guidance: called, returned PRIMM-guided teaching response
Compare the two messages side by side in the dashboard:
| Message | Tool Badges | Outcome |
|---|---|---|
| First request (free tier) | get_chapter_content (error), get_upgrade_url (success) | Paywall with upgrade link |
| Second request (paid tier) | get_chapter_content (success), generate_guidance (success) | PRIMM-guided teaching content |
That is the complete customer journey. A free learner hit a wall, paid for access, and received the product. All verified through tool badges, not just chat text.
Step 5: Add Payment Tests to the Suite
The WhatsApp journey proves the flow works today. Automated tests prove it keeps working tomorrow. Open Claude Code in your tutorclaw-mcp project and describe the payment test requirements:
Add Stripe payment tests to the pytest suite. Create a new file
tests/test_payment_flow.py with these tests:
1. test_get_upgrade_url_creates_session_for_free_learner:
Mock the Stripe API. Call get_upgrade_url for a free-tier learner.
Verify it returns a valid checkout URL.
2. test_get_upgrade_url_rejects_paid_learner:
Call get_upgrade_url for a learner who already has paid tier.
Verify it returns an error saying the learner is already upgraded.
3. test_webhook_upgrades_tier:
Simulate a checkout.session.completed webhook payload.
Call the webhook handler. Verify the learner tier in the JSON
file changed from free to paid.
4. test_gated_tool_after_upgrade:
Start with a free-tier learner. Simulate the webhook to upgrade
them. Then call get_chapter_content for chapter 6. Verify it
returns content instead of a tier error.
IMPORTANT: Mock the Stripe API in all tests. Use Stripe test mode
fixtures, not real API calls. No test should make network requests.
Use the same tmp_path isolation pattern from the existing test suite.
Notice what this prompt specifies:
- Four test scenarios covering the payment lifecycle
- Mock requirement for external API calls
- Isolation using the same temporary directory pattern from Lesson 11
- No real network calls in any test
Claude Code generates tests/test_payment_flow.py. The tests mock the Stripe API so they run without network access, without a Stripe account, and without the Stripe CLI.
Step 6: Run the Full Suite
uv run pytest -v
All existing tests from Lessons 11 and 12 should still pass. The four new payment tests should pass on top of them. If a payment test fails, describe the failure to Claude Code:
test_webhook_upgrades_tier is failing. Here is the error output:
[paste the error]
Fix the test or the webhook handler, whichever has the bug.
Same describe-steer-verify cycle. Same workflow. Now applied to payment logic.
James watched the test output scroll. Green lines. Every test from the original suite. Then four more green lines from the payment tests.
"Full green." He sat back. "Tools, content, tier gating, payment, tests. All passing."
Emma looked over his shoulder at the test count. "You just paid for your own product. With a test card, through a real checkout page, from your phone. And the tests prove the whole chain works even when you are not manually clicking through it."
"I built a product that makes money and the tests prove it works."
Emma nodded slowly. "You have a product. Tools, content, tier gating, payment. All working. All tested." She paused. "But the agent has no personality. Ask it who it is."
James typed into WhatsApp: "Who are you?"
The response came back generic. A default OpenClaw greeting. No name. No teaching philosophy. No identity.
"It does not know what it is," James said.
"Every product I have built, identity was the last thing I added. Every time I wished I had added it sooner." Emma shrugged. "The product works. The business logic is solid. But the agent sounds like every other agent. Lesson 16 fixes that."
Try With AI
Exercise 1: Debug a Failed Webhook
My TutorClaw payment flow has a problem: the Stripe test card
payment succeeds (I see the success page), but the learner tier
in the JSON file does not change. The Stripe CLI shows "webhook
delivered" but my server logs show nothing. Walk me through a
diagnostic checklist: what should I check at each stage, what
does each failure tell me, and what is the most common cause?
What you are learning: Webhook debugging follows a chain: Stripe sends the event, the CLI forwards it, your server receives it, the handler processes it, the JSON file updates. A break at any link produces the same symptom (tier unchanged). Learning to trace the chain stage by stage is the diagnostic skill that transfers to any webhook integration.
Exercise 2: Design Refund Tests
I want to add refund handling to TutorClaw. When Stripe sends a
charge.refunded webhook, the learner tier should revert from paid
to free. Describe the test scenarios I should add to
test_payment_flow.py. Do not write the tests. Just tell me:
what inputs, what expected outcomes, and what edge cases should
I consider (like partial refunds or refunding a learner who
already has free tier).
What you are learning: Designing test scenarios before writing test code is the same matrix-first approach from Lesson 11. Refunds introduce state transitions that go backward (paid to free), which creates edge cases that forward-only flows do not have. Thinking through edge cases on paper prevents gaps in coverage.
Exercise 3: Compare Manual and Automated Payment Tests
I tested the payment flow two ways today: manually via WhatsApp
with a real Stripe test card, and automatically via pytest with
mocked Stripe calls. What does each approach catch that the other
misses? If I could only run one, which should I choose and why?
What you are learning: Manual integration tests verify the full chain including external services (Stripe, OpenClaw gateway, WhatsApp). Automated tests verify internal logic without network dependencies. Neither replaces the other. Knowing when to invest in each saves time without sacrificing confidence.