sickn33/agentic-awesome-skills · critical · Error

Payment failed

Error message

Payment failed

What it means

Illustrative domain error from the payment step of processOrder: chargePayment returned { success: false } and the guard throws 'Payment failed'. In the BEFORE shape this leaves the worst ambiguity — inventory may be reserved and the next try block must guess whether to refund or log.

Source

Thrown at skills/fp-async/SKILL.md:152

```typescript
// BEFORE: Deeply nested, hard to follow
async function processOrder(orderId: string) {
  try {
    const order = await fetchOrder(orderId)
    if (!order) throw new Error('Order not found')

    try {
      const user = await fetchUser(order.userId)
      if (!user) throw new Error('User not found')

      try {
        const inventory = await checkInventory(order.items)
        if (!inventory.available) throw new Error('Out of stock')

        try {
          const payment = await chargePayment(user, order.total)
          if (!payment.success) throw new Error('Payment failed')

          try {
            const shipment = await createShipment(order, user)
            return { order, shipment, payment }
          } catch (e) {
            // Refund payment? Log? What's the state now?
            await refundPayment(payment.id)
            throw e
          }
        } catch (e) {
          throw e
        }
      } catch (e) {
        throw e
      }
    } catch (e) {
      throw e
    }

View on GitHub (pinned to 58d857988f)

Solutions

  1. Log the payment processor's decline code — the generic 'Payment failed' message hides the real reason
  2. Make chargePayment idempotent with an idempotency key so retries are safe
  3. Sequence compensation explicitly (refund on shipment failure) instead of nested catch blocks — or use the skill's TaskEither pipeline with explicit rollback in error paths
  4. Validate the payment method (expiry, billing info) before charging

Example fix

// before
const payment = await chargePayment(user, order.total)
if (!payment.success) throw new Error('Payment failed')
// after
pipe(
  chargePaymentTask(user, order.total),
  TE.chain(payment => payment.success
    ? TE.right(payment)
    : TE.left(Object.assign(
        new Error('Payment failed'),
        { code: payment.declineCode }
      )))
)
Defensive patterns

Strategy: retry

Validate before calling

// pre-charge validation
if (!user.paymentMethod || user.paymentMethod.expiry < new Date())
  return { kind: 'InvalidPaymentMethod' }

Type guard

const isPaymentResult = (r: unknown): r is { success: boolean; declineCode?: string } =>
  typeof r === 'object' && r !== null && typeof (r as any).success === 'boolean'

Try / catch

try {
  const payment = await chargePayment(user, order.total)
  if (!payment.success) throw Object.assign(new Error('Payment failed'), { code: payment.declineCode })
} catch (e) {
  // compensating action: release reserved inventory, then rethrow or queue retry
  await releaseInventory(order.items)
  throw e
}

Prevention

When it happens

Trigger: Card declined by the processor; insufficient funds; gateway timeout after an uncertain capture; amount/currency mismatch causing processor rejection.

Common situations: Sandbox/test card numbers used against production gateways; expired stored payment methods; gateway outages; naive retries double-charging when the first attempt actually succeeded.

Related errors


AI-assisted analysis of sickn33/agentic-awesome-skills@58d857988f (2026-08-26). Data as JSON: /api/errors/067d203a414605f8. Report an issue: GitHub.