Skip to content

Order edit cancels payment authorization instead of allowing partial capture #14984

@seajones7

Description

@seajones7

Bug Description

When an order edit is confirmed that changes the order total, Medusa cancels the existing authorized payment and creates a new empty payment collection. This leaves the order in a state where payment cannot be collected without the customer re-initiating checkout.

Expected behavior: Keep the original authorization alive and capture only the new (lower) amount at capture time (partial capture). This is how Shopify, WooCommerce, and BigCommerce handle it.

Root Cause

In packages/core/core-flows/src/order/workflows/create-or-update-order-payment-collection.ts, the workflow checks if the existing payment collection is AUTHORIZED or PARTIALLY_AUTHORIZED and sets shouldRecreate = true (lines ~60-63):

shouldRecreate = 
  existingPaymentCollection?.status === AUTHORIZED ||
  existingPaymentCollection?.status === PARTIALLY_AUTHORIZED

This triggers:

  1. Cancel the old payment collection (voids the Stripe PaymentIntent)
  2. Create a new empty payment collection with the updated amount

The code comment states this is because authorized payments can't be modified. However, this is incorrect — Stripe (and most payment providers) natively support partial capture.

How Partial Capture Works

From Stripe's documentation on partial capture:

You can capture an amount less than or equal to the authorized amount. If you capture less, the remaining hold is released.

// Authorize $139.92
const intent = await stripe.paymentIntents.create({
  amount: 13992,
  capture_method: 'manual',
  // ...
});

// Order edited — item removed, new total $109.29
// Capture only the new amount:
await stripe.paymentIntents.capture(intent.id, {
  amount_to_capture: 10929,
});
// Remaining $30.63 hold is automatically released

Medusa's own capture endpoint already accepts an amount parameter, so the downstream plumbing already supports this.

Proposed Fix

In create-or-update-order-payment-collection, when the existing collection status is AUTHORIZED:

Instead of: cancel + recreate
Do: update the payment collection amount (keep the authorization alive)

The capture step already accepts a custom amount, so when the merchant captures payment, they capture the new (lower) total. Stripe releases the remaining hold automatically.

This is a minimal change — the shouldRecreate logic for AUTHORIZED status should instead follow the "update" path (Path 1), updating the collection amount without touching the payment/authorization.

Impact

  • Current behavior: After order edit, payment is canceled. Merchant cannot collect payment without customer re-initiating checkout.
  • Fixed behavior: Authorization stays active. Merchant captures the updated amount. Customer experience is seamless.

This affects any payment provider that supports partial capture (Stripe, Adyen, PayPal, etc.).

Steps to Reproduce

  1. Create an order with Stripe payment (manual capture mode: capture: false)
  2. Stripe authorizes the full amount (e.g., $79.80)
  3. Edit the order — remove an item (new total: $58.80)
  4. Confirm the edit
  5. Observe: original Stripe PaymentIntent is voided, new payment collection has no payment session
  6. The "Capture" button disappears from the admin — no way to collect payment

Environment

  • Medusa version: 2.13.1
  • Payment provider: @medusajs/payment-stripe
  • Stripe capture mode: capture: false (manual capture)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions