Payments API
Reference for payment endpoints: Stripe Checkout, Stripe Connect onboarding, webhook handling, and the secure purchase flow.
MyClaude processes payments through Stripe Connect Express. The platform never handles raw card data -- all payment processing is delegated to Stripe (PCI DSS Level 1 certified). Orders are created exclusively by Stripe webhooks, never by client-side code.
Purchase flow
The complete purchase sequence involves four components: the buyer's browser, the MyClaude API, Stripe, and the webhook handler.
Buyer clicks "Buy"
|
v
POST /stripe/checkout (Bearer token + productId)
|
+-- Verify auth token
+-- Verify product exists, is published, is paid
+-- Verify buyer has not already purchased
+-- Verify seller has active Stripe account
+-- Calculate platform fee (8%)
+-- Create Stripe Checkout Session
|
v
302 Redirect to Stripe Checkout
|
v
Buyer completes payment on stripe.com
|
v
Stripe fires webhook: checkout.session.completed
|
v
POST /stripe/webhooks (Stripe signature verification)
|
+-- Verify webhook signature
+-- Re-verify price against database
+-- Create order in Firestore (atomic, idempotent)
+-- Update product stats (purchaseCount)
+-- Update seller stats (totalRevenue, totalSales, +50 XP)
+-- Update buyer stats (productsBought, +10 XP)
|
v
Buyer returns to product page
|
v
Download button appears (order verified server-side)Create checkout session
Create a Stripe Checkout Session for purchasing a paid product. Returns a URL that redirects the buyer to Stripe's hosted checkout page.
POST /stripe/checkoutAuth: Required (Bearer token) Rate limit: 10/min (strict)
Request body
{
"productId": "abc123"
}| Field | Type | Required | Description |
|---|---|---|---|
productId | string | Yes | ID of the product to purchase |
Response
{
"url": "https://checkout.stripe.com/c/pay/cs_live_..."
}Validation checks
The server performs these checks before creating a checkout session:
| Check | Error if failed |
|---|---|
| Product exists and is published | 404 Product not found |
| Product price > 0 | 400 Product is free |
| Buyer is not the product author | 400 Cannot purchase your own product |
| Buyer has not already purchased | 409 Already purchased |
| Seller has connected Stripe | 400 Seller has not connected Stripe |
| Seller's charges are enabled | 400 Seller's payment account is not active |
| Seller's payouts are enabled | 400 Seller's account verification is pending |
Platform fee
The platform fee is calculated server-side as a constant:
Platform fee = 8% of product price
Seller receives = 92% of product priceThe fee is set as application_fee_amount on the Stripe PaymentIntent with transfer_data.destination pointing to the seller's connected account.
Errors
| Status | Condition |
|---|---|
| 400 | Missing productId, free product, own product, seller not connected, or seller account issues |
| 404 | Product not found or not published |
| 409 | Already purchased |
Stripe Connect onboarding
Create a Stripe Connect Express account for the authenticated user, or generate a login/onboarding link if an account already exists.
POST /stripe/connectAuth: Required (Bearer token) Rate limit: 5/min (strict)
Request body
None.
Response (new account)
{
"url": "https://connect.stripe.com/express/onboarding/...",
"accountId": "acct_1234567890"
}Response (existing account, fully onboarded)
{
"url": "https://connect.stripe.com/express/login/..."
}Response (existing account, onboarding incomplete)
{
"url": "https://connect.stripe.com/express/onboarding/...",
"accountId": "acct_1234567890"
}Behavior by account state
| State | Action |
|---|---|
| No Stripe account | Creates Express account, returns onboarding link |
| Account exists, not onboarded | Returns new onboarding link for existing account |
| Account exists, fully onboarded | Returns Stripe dashboard login link |
The created Stripe Express account is configured with card_payments and transfers capabilities. The firebaseUid is stored in the account metadata for webhook reconciliation.
Errors
| Status | Condition |
|---|---|
| 401 | Missing or invalid token |
Stripe callback
Redirect endpoint called by Stripe after the user completes (or abandons) Connect onboarding. This is not called by API consumers directly.
GET /stripe/callback?account_id=acct_...&uid=...Auth: None (redirect handler)
Behavior
- Retrieves the Stripe account and verifies
metadata.firebaseUidmatches theuidparameter. - Updates the user profile with
stripeAccountIdandstripeOnboardedstatus. - Redirects the user to
/sales?stripe=successor/sales?stripe=error.
Stripe webhooks
Receives and processes events from Stripe. Webhook signature verification is mandatory -- unsigned or tampered events are rejected.
POST /stripe/webhooksAuth: Stripe webhook signature (stripe-signature header)
Rate limit: None (Stripe-initiated)
Handled events
| Event | Action |
|---|---|
checkout.session.completed | Creates a new order in Firestore, updates product and user stats |
charge.refunded | Marks the order as refunded, reverses seller/buyer stats |
account.updated | Updates seller onboarding status when Stripe account becomes active |
Order creation (checkout.session.completed)
Orders are created inside a Firestore transaction with the Stripe session ID as the document ID, making the operation idempotent. If Stripe retries the webhook, duplicate orders are detected and skipped.
The server re-verifies the product price against the database and cross-checks with session.amount_total from Stripe. If amounts diverge (e.g., price changed between checkout creation and webhook delivery), the discrepancy is logged but the order is still created using Stripe's actual charged amount.
Order document structure:
{
"buyerUid": "uid_buyer",
"sellerUid": "uid_seller",
"productId": "abc123",
"productTitle": "Code Review Skill",
"productCategory": "skills",
"amount": 999,
"platformFee": 80,
"sellerAmount": 919,
"currency": "usd",
"stripeSessionId": "cs_live_...",
"stripePaymentIntentId": "pi_...",
"status": "completed",
"createdAt": "2026-03-24T12:00:00.000Z",
"updatedAt": "2026-03-24T12:00:00.000Z"
}All monetary values are in cents (e.g., 999 = $9.99).
Refund processing (charge.refunded)
When a charge is refunded through Stripe:
- The order status is updated to
"refunded"with arefundedAttimestamp. - Seller stats are decremented (
totalSales,totalRevenue). - Buyer stats are decremented (
productsBought). - The seller receives a notification about the refund.
Refund processing is also idempotent -- already-refunded orders are skipped.
Error handling
| Scenario | Response | Stripe behavior |
|---|---|---|
| Invalid signature | 400 | Stripe does not retry |
| Missing metadata | 400 | Stripe does not retry |
| Processing error (Firestore down) | 503 | Stripe retries with exponential backoff |
| Duplicate order | 200 (skipped) | Stripe considers delivered |
Pricing model
| Component | Value |
|---|---|
| Platform fee | 8% of product price |
| Seller receives | 92% of product price |
| Currency | USD only |
| Minimum paid price | $1.00 (free products use price = 0) |
| Maximum price | $9,999.00 |
| Stripe processing fee | Standard Stripe fees apply (deducted by Stripe before payout) |
The 8% platform fee is a server-side constant. It is never read from environment variables or client-side configuration.
Security guarantees
| Guarantee | Implementation |
|---|---|
| Orders created server-side only | Webhook handler is the sole order creation path |
| Idempotent order creation | Firestore transaction with session ID as document ID |
| Price verification | Server re-fetches product price, cross-checks Stripe amount |
| Signature verification | stripe.webhooks.constructEvent() validates every webhook |
| No raw card data | All payment UI is Stripe-hosted Checkout |
| Seller verification | Charges and payouts must be enabled before checkout session creation |
Related pages
- API Overview -- Auth model, rate limits, error format
- Downloads API -- Post-purchase download flow
- Products API -- Product operations
- Creator Monetization -- Revenue and payout details for creators
- Security Model -- Platform security architecture