James looked at the upgrade prompt in WhatsApp. "Upgrade to unlock all chapters." Below it: a placeholder URL that went nowhere.
"The tier gating works," he said. "Free learners get blocked at chapter six. The upgrade message appears. But the link is a dead end."
Emma pulled up the Stripe dashboard on her screen. "Stripe. You create a checkout page on their servers. They handle the card, the receipt, the PCI compliance. You get a webhook when the payment clears."
James thought for a moment. "Like a cash register. I ring up the item, the point-of-sale terminal handles the card, and the receipt updates the inventory."
"Exactly. Your server creates the session. Stripe handles the money. Their webhook tells your server the payment went through. You update the JSON. Three parties, one flow."
You are doing exactly what James is doing. Your tier gating works from Module 9.3, Chapter 13, but the upgrade link is still a mock from Module 9.3, Chapter 6. Time to replace it with a real Stripe Checkout session and a webhook that upgrades the tier when payment completes.
Three steps. First, you set up Stripe yourself (this is product work, not code). Second, you describe the integration to Claude Code. Third, you verify the full flow end-to-end with a test card payment.
Stripe account setup is a business decision. You choose the product name, the price, the currency. Claude Code does not make these decisions for you.
Go to dashboard.stripe.com/register and create a free account. No credit card required for test mode.
In the Stripe Dashboard, toggle Test mode in the top-right corner. Every action you take in test mode uses fake money and fake cards. No real charges.
Navigate to Product catalog and create a new product:
After saving, find the Price ID on the product page. It starts with price_. Copy it.
Navigate to Developers then API keys. You need two keys:
After installing, authenticate with your Stripe account:
Follow the browser prompt to link the CLI to your test-mode account.
Your TutorClaw server needs three environment variables. Add them to a .env file in your project root (or set them in your terminal session):
Never Commit Secrets
Add .env to your .gitignore if it is not already there. The sk_test_ key gives full access to your Stripe test account. Committing it to git is a security mistake even in test mode, because the habit carries over to production keys.
With Stripe set up, the coding work is a describe-steer-verify cycle. You tell Claude Code what changed about get_upgrade_url, and you describe a new webhook endpoint.
Open Claude Code in your tutorclaw-mcp project and describe the change:
Notice what you described: the behavior (create a checkout session), the data flow (learner_id in metadata), the security constraint (keys from environment variables), and the scope (replace the mock, keep everything else). You did not describe the Stripe API syntax. Claude Code handles that.
When Claude Code returns the spec, check:
If anything looks off, steer it:
Once the spec looks right, approve the build.
The webhook is a separate endpoint on your server. When Stripe processes a payment, it sends an HTTP POST to this endpoint with the event details. Describe it to Claude Code:
Check:
Approve the build when the spec looks right.
Testing a Stripe integration locally requires the Stripe CLI to forward webhook events to your server. This is the standard local development workflow for any webhook-based integration.
In a separate terminal, start the webhook forwarding:
The CLI prints a webhook signing secret that starts with whsec_. Copy this value and set it as your STRIPE_WEBHOOK_SECRET environment variable. This secret is different from your API key. The API key authenticates your server's requests to Stripe. The webhook secret verifies that incoming webhook requests actually came from Stripe.
In another terminal, start the server with the environment variables loaded:
Confirm the server starts without errors.
Use Claude Code or a direct HTTP call to invoke get_upgrade_url for a free-tier learner. The tool should return a URL that starts with https://checkout.stripe.com/.
Open that URL in your browser. You should see a Stripe Checkout page with your product name and price.
Stripe provides test card numbers that simulate successful payments. Enter:
Click Pay. Stripe processes the test payment instantly.
Switch to the terminal running stripe listen. You should see output showing the events Stripe sent, including checkout.session.completed.
The Stripe CLI forwarded that event to your server's /webhook endpoint. Your handler verified the signature, extracted the learner_id from the metadata, and updated the JSON state.
Open your learner's JSON state file (in the data/ directory). The learner's tier should now be "paid" instead of "free".
This is the complete monetization flow: learner hits a paywall, gets a checkout URL, pays with a card, webhook fires, tier upgrades, content unlocks. One payment, one webhook, one JSON update.
Run the test suite to make sure nothing broke:
The existing tests should still pass. The mock behavior for already-paid learners is the same (return an error). The difference is that free-tier learners now get a real Stripe URL instead of a placeholder.
Two Secrets, Two Directions
If you get confused about which key is which, remember the direction of traffic. The API secret key (sk_test_) authenticates requests going OUT from your server to Stripe. The webhook signing secret (whsec_) verifies requests coming IN from Stripe to your server. Outbound: API key. Inbound: webhook secret.
Step back and look at what you built. Three parties are involved in every payment:
Your server never sees a credit card number. Stripe's hosted Checkout page handles all of that. Your server only needs to create the session (outbound API call) and handle the result (inbound webhook).
The webhook is the critical piece. The checkout URL alone does not confirm payment: the learner might close the browser, the redirect might fail, network errors happen. The webhook is Stripe's server-to-server confirmation that money moved. Your handler trusts it (after verifying the signature) and updates the tier.
Stripe provides test card numbers that simulate failures. Ask Claude Code about them:
What you are learning: A payment integration is not complete until you know how failures behave. Successful payments are the happy path. Declined cards, expired cards, and insufficient funds are the realistic path.
Ask Claude Code to add logging so you can see what Stripe sends:
Run another test payment after adding the logging. Check the server output.
What you are learning: Webhook payloads are your debugging tool for payment integrations. When something goes wrong (tier not updating, wrong learner upgraded), the payload tells you exactly what Stripe sent and what your handler processed.
What happens if Stripe sends the same webhook event twice? (This can happen during network retries.) Ask Claude Code:
What you are learning: Webhook handlers must be idempotent: processing the same event twice should produce the same result as processing it once. Stripe guarantees at-least-once delivery, not exactly-once. Your handler must handle duplicates gracefully.
James watched the terminal. The stripe listen output showed checkout.session.completed. He opened the JSON file. The tier field read "paid".
"One payment. One webhook. One JSON update." He closed the file. "The product makes money."
Emma nodded. "That is the entire monetization flow. A learner hits the paywall, gets a URL, pays, and unlocks everything. Three parties, three steps, done."
Emma smiled. "My first test suite for a 9-tool server had 11 tests. I wrote each one by hand. You described a test matrix to Claude Code and got 28 on the first pass. I spent a weekend writing 11. You spent one chapter describing requirements and one chapter finding edges."
"That feels uneven."
"It is. The skill is not writing tests. The skill is knowing what to test. You identified the edges Claude Code missed: first run, confidence limits, duplicate names, empty input. That is the human value. Describing the matrix is the mechanical part." She closed her laptop. "That suite is your safety net. Every change from now on runs against these 29 tests. When you add tier gating in Module 9.3, Chapter 15, the gating logic gets new tests. The existing ones prove nothing else broke."
James nodded. Twenty-nine guards. All standing.