Things go wrong with cards. This guide covers the common failure modes in Shop Rentals and how to handle each.
Decline codes you'll see
| Code | Meaning | Action |
|---|---|---|
card_declined (insufficient_funds) | Customer's card has no balance | Ask them to use another card or refund/cancel |
card_declined (do_not_honor) | Generic decline; bank's risk system flagged | Try a different card, or contact bank |
card_declined (lost_card / stolen_card) | Card reported lost/stolen | Refuse the rental; tell customer to replace card |
expired_card | Card past expiration | Update on customer profile, retry |
incorrect_cvc | CVC didn't match | Re-enter at the iPad |
processing_error | Stripe transient issue | Retry after 1-2 min |
authentication_required | 3DS / SCA challenge needed | Customer needs to complete the challenge on their phone |
card_not_supported | Card type isn't accepted (e.g., debit-only on a credit-only flow) | Use a different card |
Test cards (demo subaccount only)
| Card | Behavior |
|---|---|
4242 4242 4242 4242 | Succeeds |
4000 0027 6000 3184 | Requires 3DS authentication |
4000 0000 0000 9995 | Declined — insufficient funds |
4000 0000 0000 0002 | Declined — generic |
4000 0000 0000 0341 | Initial succeed, attach fails |
Demo subaccount automatically routes to test mode via per-subaccount Stripe routing — see the Stripe per-subaccount routing internal doc.
SetupIntent vs PaymentIntent
The walk-in and customer flows use SetupIntents to attach a card to the customer for later off-session charges. Different from a one-time charge:
- SetupIntent doesn't transfer money — it just authorizes the card for future use
- The customer enters card details, the SetupIntent is "confirmed"
- The resulting
paymentMethodIdis attached to the customer - Future charges (deposits, late fees, damage) use this saved PM off-session
If a SetupIntent fails:
- Re-create with
create_setup_intentaction - Have customer re-enter the card via Stripe Elements
- Common cause: customer typed the wrong CVC or card number
Off-session charge declines
When you charge the saved card off-session (operator-initiated, no customer present):
- Insufficient funds — common; the card was good when added but the balance dropped. Reach out to the customer.
- Authentication required — 3DS challenge can't be completed off-session. Set up a payment method update flow that prompts the customer to re-authenticate.
- Card declined (generic) — bank's risk algorithm flagged. Try a different card.
Webhook failures
Webhook signature verification failures show up as 400 errors in the
logs at /api/webhooks/stripe. Two common reasons:
- Wrong secret in
STRIPE_WEBHOOK_SECRET— re-pull from Stripe Dashboard - Test events arriving without
STRIPE_WEBHOOK_SECRET_TESTset — ensure the test secret is populated when running demo subaccount tests
The handler now tries both secrets (live first, then test). If both fail, the request is rejected with "Invalid signature."
Refund failures
| Error | Meaning | Action |
|---|---|---|
| "No eligible Stripe charges found" | Customer paid via wallet only | Refund to wallet instead |
| "Cannot refund directly to card while customer has negative wallet balance" | Customer owes their wallet | Refund to wallet first, or zero out manually |
| "Unable to refund full requested amount with available charges" | Multiple original charges, not enough remaining | Refund what's available; partial-refund the rest separately |
| "Invalid signature" on refund webhooks | Webhook secret mismatch | Re-verify config |
Dispute / chargeback
When a customer files a chargeback:
- Stripe sends a
charge.dispute.createdwebhook - The handler logs it and may auto-debit the customer's wallet (per project config)
- You have a defined window to respond with evidence (usually 7-21 days)
Evidence to gather:
- The signed waiver (PDF)
- The receipt PDF
- The booking timeline (created → confirmed → checked_in → completed)
- Any service log entries (damage photos, etc.)
- The customer's prior rental history (good rep, repeat customer)
- The cancellation policy text shown at checkout
The dashboard's Disputes section consolidates these into a submission package. See the dispute evidence article.
Auto-topup interactions
If your shop charges customers via auto-topup (wallet) and the auto-topup fails, the customer's wallet may go negative. This blocks new bookings and refunds-to-card. Resolution:
- Have the customer update their card via the manage flow
- Re-trigger the auto-topup
- Once positive, the customer can book again
For shops doing pure direct-to-card billing, this isn't an issue.
When Stripe is down
Stripe rarely has total outages, but partial failures happen. During an outage:
- New bookings via the public page can't collect cards → bookings fail at the create step
- Refunds time out
- The walk-in wizard can still create reservations (no card collection in the wizard yet); deposits can be attempted but may fail
Watch status.stripe.com and queue operations to retry once recovered.
Logging
All Stripe API failures are logged to Sentry with context. To dig in:
- Go to Sentry → search for
[ride refunds]or[reservation refunds] - Filter by user/customer if known
- Read the full error trace including Stripe's response body
For unmonitored failures (e.g., a webhook that didn't trigger Sentry), check the Vercel function logs at the relevant route.
When to escalate to support
- Persistent webhook signature failures despite re-pulling the secret
- Unexplained idempotency conflicts on wallet credits
- Refund flows that succeed in Stripe but don't update locally
- Per-subaccount routing returning the wrong client mode
These point to deeper config issues. Open a support ticket with the Stripe event ID and the booking number.