payloadcms/payload · error · APIError
Failed to delete Stripe document with ID: '${doc.stripeID}':
Error message
Failed to delete Stripe document with ID: '${doc.stripeID}': ${msg} What it means
Thrown by the Stripe plugin's `deleteFromStripe` after-delete hook when either `stripe.[stripeResourceType].retrieve(stripeID)` or `.del(stripeID)` rejects. The original Stripe SDK error message is appended so the cause is visible. Note: a missing Stripe resource is NOT an error — the hook logs and skips — this throw is for genuine failures (auth, network, invalid ID format).
Source
Thrown at packages/plugin-stripe/src/hooks/deleteFromStripe.ts:63
const found = await stripe?.[syncConfig.stripeResourceType]?.retrieve(doc.stripeID)
if (found) {
await stripe?.[syncConfig.stripeResourceType]?.del(doc.stripeID)
if (logs) {
payload.logger.info(
`✅ Successfully deleted Stripe document with ID: '${doc.stripeID}'.`,
)
}
} else {
if (logs) {
payload.logger.info(
`- Stripe document with ID: '${doc.stripeID}' not found, skipping...`,
)
}
}
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : error
throw new APIError(`Failed to delete Stripe document with ID: '${doc.stripeID}': ${msg}`)
}
}
}
}
View on GitHub (pinned to 00c58b35c0)
Solutions
- Read the `${msg}` suffix — it names the Stripe error (e.g. 'Cannot delete customer with active subscription')
- Resolve the upstream Stripe constraint (close subscriptions, etc.) before deleting the Payload doc
- Verify `stripeSecretKey` is valid for the account that owns the resource
- Confirm `stripeResourceType` supports `.retrieve` and `.del`
Example fix
// before — customer still has subscription, Stripe rejects delete
await payload.delete({ collection: 'users', id })
// after — cancel subscriptions first
await stripe.subscriptions.cancel(sub.id)
await payload.delete({ collection: 'users', id }) Defensive patterns
Strategy: try-catch
Validate before calling
// Before deleting, confirm the Stripe resource can be deleted (e.g. customer has no subs)
if (stripeResourceType === 'customers') {
const subs = await stripe.customers.listSubscriptions(stripeID)
if (subs.data.length) throw new Error('customer has active subscriptions — cancel first')
} Type guard
import { APIError } from 'payload'
function isStripeDeleteError(e: unknown): e is APIError {
return e instanceof APIError && /^Failed to delete Stripe document with ID:/.test(e.message)
} Try / catch
import { APIError } from 'payload'
try {
await payload.delete({ collection: 'users', id })
} catch (e) {
if (e instanceof APIError && /Failed to delete Stripe document/.test(e.message)) {
// suffix holds the Stripe reason; resolve the upstream constraint (cancel subs, etc.)
}
throw e
} Prevention
- Keep `stripeSecretKey` in sync across environments
- Ensure `stripeResourceType` supports `.retrieve` and `.del`
- Resolve Stripe-side constraints (active subscriptions, unpaid invoices) before deleting the Payload doc
When it happens
Trigger: Deleting a Payload document whose `stripeID` points to a resource Stripe refuses to delete (e.g. a customer with an active subscription); `stripeSecretKey` invalid; `stripeResourceType` does not support `.retrieve`/`.del`; network failure mid-call.
Common situations: `stripeSecretKey` rotated but plugin config not updated; deleting a Stripe customer that still has subscriptions/invoices (Stripe rejects); `stripeResourceType` misconfigured to a non-deletable resource; stale `stripeID` left over from a deleted Stripe resource that the retrieve call cannot reach due to permissions.
Related errors
- Failed to create new '${syncConfig.stripeResourceType}' reso
- Failed to sync document with ID: '${data.id}' to Stripe: ${m
- The collection with slug ${String(collectionSlug)} can't be
- Operator handler "${handler.name}" cannot define both "build
- Operator handlers "${handlerA.name}" and "${handlerB.name}"
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/198bd310443e68cc.
Report an issue: GitHub.