Payment Bugs

Refund Processed but Order Not Updated

A refund for order 601 is successfully processed at the payment provider — the charge is reversed and the provider's dashboard shows the refund as complete — but the application's order status remains 'paid' rather than updating to 'refunded'. The application's webhook handler processes payment events but has no handler for the refund event type, so the order state machine never transitions.

MediumBeginnerAPI testingManual testingExploratory testing

// UNDERSTAND

// Symptoms

  • The payment provider's dashboard shows the refund for order 601 as 'succeeded', but GET /api/orders/601 returns { "status": "paid" }
  • The customer can see the refund on their bank statement but the application still shows the order as paid
  • Customer support receives tickets about orders that show as 'paid' even after a refund was issued
  • Refund confirmation emails are not sent because the application never processes the refund event

// Root Cause

  • The webhook handler processes payment.succeeded events to transition orders from 'pending' to 'paid', but no handler is registered for the charge.refunded (or equivalent) event. Refund events are received by the webhook endpoint and discarded — no order state update is triggered.
  • Without a charge.refunded handler, the order state machine has no transition from 'paid' to 'refunded'. The order is permanently stuck in the 'paid' state regardless of what happens at the provider.

// Where It Appears

  • E-commerce checkout flows where refunds are processed manually through the payment provider dashboard
  • Subscription billing systems where partial or full refunds can be issued from the provider's portal
  • Any application whose webhook handler was built to support only the initial payment flow and not the full payment lifecycle

// REPRODUCE & TEST

// How to Reproduce

  1. 01Complete a payment for a test order and confirm the order status transitions to 'paid' via GET /api/orders/601
  2. 02Issue a full refund for order 601 via the payment provider's test dashboard — note the refund ID
  3. 03Confirm the refund is marked as 'succeeded' in the payment provider's test dashboard
  4. 04Send GET /api/orders/601 and read the status field
  5. 05If the status is still 'paid' rather than 'refunded', the bug is confirmed

// Test Data Needed

  • A test order 601 in 'paid' status
  • Access to the payment provider's test dashboard or test API to issue a refund
  • The order ID (601) to query the resulting status

// Manual Testing Ideas

  • Issue a full refund from the payment provider's test dashboard and confirm whether the order status updates within a reasonable processing window
  • Issue a partial refund and confirm whether the order status reflects the partial refund
  • Check the application's webhook event log: is a charge.refunded event being received? If it is received but the order is not updated, the handler is present but broken; if it is not received, the webhook endpoint may not be subscribed to refund events
  • Manually send a simulated charge.refunded webhook event to the application's webhook endpoint and observe whether the order status updates
  • Verify the full payment lifecycle: paid → refund initiated → refund succeeded → order status updated

// API Testing Ideas

  • Confirm GET /api/orders/601 returns status: 'paid'
  • Issue a refund via the payment provider's test API and confirm the provider returns a succeeded refund record
  • Wait 10 seconds for the webhook to be processed, then send GET /api/orders/601
  • Assert the status field is 'refunded' — if it is still 'paid', the refund event was not handled
  • Send a POST to the application's webhook endpoint with a manually constructed charge.refunded event body for order 601 and assert the order status updates to 'refunded'

// Automation Idea

In a payment sandbox, complete a charge for a test order 601. Programmatically issue a refund via the provider's test API. Wait 10 seconds, then send GET /api/orders/601 and assert the status is 'refunded'. If the status remains 'paid', separately send a mock charge.refunded webhook event to the webhook endpoint and assert the status updates — this isolates whether the bug is in the webhook subscription or the handler.

// Expected Result

After a refund is confirmed by the payment provider, the application updates the order status from 'paid' to 'refunded' within the expected webhook processing window.

// Actual Result (Example)

The payment provider's test dashboard shows a successful refund for order 601. GET /api/orders/601 returns { "status": "paid" } — the order status was never updated after the refund was processed.

// REPORT IT

Example Bug Report

Title
Order 601 remains 'paid' after full refund — charge.refunded webhook not handled
Severity
Medium
Environment
Staging environment Stripe test dashboard Test order 601 in 'paid' status
Steps to Reproduce
  1. 01Confirm GET /api/orders/601 returns status: 'paid'
  2. 02Issue a full refund for order 601 in the Stripe test dashboard
  3. 03Confirm the refund shows as 'succeeded' in the Stripe test dashboard
  4. 04Send GET /api/orders/601 and read the status field
Expected Result
The order status is 'refunded' after the refund is confirmed by Stripe.
Actual Result
GET /api/orders/601 returns { "status": "paid" } despite the Stripe test dashboard showing a successful refund. The order status was not updated.
Impact
Customers cannot confirm their refund status in the application. The order history is inaccurate. Downstream processes that depend on order status (fulfilment cancellation, refund confirmation emails) do not trigger.

// RELATED