A GET /api/joke request matches the first route. A POST /api/joke does not match. Routes not in the map pass through to the next middleware — they are free endpoints.
Code Paths
The middleware has four main code paths:
Path 1: Non-Paid Route (Passthrough)
Client → GET /api/health → No matching route → next() → Handler responds normally
If the route key (METHOD /path) is not in config.routes, the middleware calls next() and the request proceeds as normal. No payment logic runs.
Path 2: No Payment Header (402 Challenge)
Client → GET /api/joke (no payment header) → 402 Payment Required
If the route matches but the request has no payment header, the middleware sends a 402 response with:
The response includes the price (amount), minimum top-up (minTopUp), currency, and the server's Stripe publishable key so the client can tokenize a card.
Path 3: Client ID With Sufficient Credits
When the payment header contains a clientId:
The middleware calls store.deductBalance(clientId, amount)
If deduction succeeds (returns new balance), the middleware:
Sets the payment-response header with success: true and creditsRemaining
Optionally records a deduction transaction via store.recordTransaction()
Calls next() to serve the resource
If deduction fails (returns null — insufficient balance):
If no paymentMethodId is provided: sends a 402 with error: 'insufficient_credits'
If paymentMethodId is provided: falls through to Path 4 (new payment)
Path 4: New Payment (Card Charge)
This is the full payment flow:
Validate top-up amount: topUpAmount defaults to minTopUp if not specified. If topUpAmount < minTopUp, returns error top_up_below_minimum.
Get card fingerprint: Calls StripeService.getCardFingerprint(paymentMethodId) to retrieve the card's fingerprint from Stripe.
Derive client ID: Calls deriveClientId(fingerprint, serverSecret) — HMAC-SHA256 of the fingerprint using the server secret.
Check existing credits: Calls store.deductBalance(clientId, amount) to check if this client already has sufficient credits. If the deduction succeeds, the middleware skips charging entirely — it sets the payment-response header, records the deduction, and calls next(). This prevents double-charging when a client resends the same paymentMethodId on every request instead of switching to clientId after the first payment. The client's card is only charged when their balance is actually insufficient.
Find or create Stripe customer: Calls StripeService.findOrCreateCustomer(clientId, paymentMethodId). Searches for existing customer by metadata["stripe402_client_id"], creates one if not found.
Charge the card: Calls StripeService.createAndConfirmPayment() with:
amount: unitsToCents(topUpAmount) — converts units to Stripe cents (rounds up)
currency: from route config (default 'usd')
paymentMethodId: from the client's payment header
customerId: from step 4
description: "stripe402 top-up for {routeDescription or path}"
confirm: true and automatic_payment_methods.allow_redirects: 'never'
Verify payment: If paymentIntent.status !== 'succeeded', returns error payment_failed.
Create client record: If no existing client record, creates one with balance: 0.
Record top-up transaction: If store.recordTransaction exists, records a topup transaction.
Deduct for current request: Calls store.deductBalance(clientId, routeConfig.amount).
Record deduction transaction: If store.recordTransaction exists, records a deduction transaction.
Respond: Sets payment-response header with success: true, chargeId (PaymentIntent ID), creditsRemaining, and clientId. Calls next() to serve the resource.
Error Handling
Stripe Card Errors
If Stripe throws a StripeCardError (e.g., declined card, insufficient funds), the middleware returns:
Other Payment Errors
Any other error during payment processing returns:
Malformed Payment Header
If the payment header cannot be decoded (invalid base64 or invalid JSON):
Helper Functions
The middleware uses three internal helper functions:
Sends a 402 response with a PaymentResponse body indicating failure. Used for card declined, payment failed, invalid payment, and top-up below minimum errors.
setPaymentResponseHeader(res, paymentResponse)
Sets the payment-response header on a successful response (base64-encoded PaymentResponse).