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:
- Cancel the old payment collection (voids the Stripe PaymentIntent)
- 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
- Create an order with Stripe payment (manual capture mode:
capture: false)
- Stripe authorizes the full amount (e.g., $79.80)
- Edit the order — remove an item (new total: $58.80)
- Confirm the edit
- Observe: original Stripe PaymentIntent is voided, new payment collection has no payment session
- 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)
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 isAUTHORIZEDorPARTIALLY_AUTHORIZEDand setsshouldRecreate = true(lines ~60-63):This triggers:
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:
Medusa's own capture endpoint already accepts an
amountparameter, so the downstream plumbing already supports this.Proposed Fix
In
create-or-update-order-payment-collection, when the existing collection status isAUTHORIZED: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
shouldRecreatelogic forAUTHORIZEDstatus should instead follow the "update" path (Path 1), updating the collection amount without touching the payment/authorization.Impact
This affects any payment provider that supports partial capture (Stripe, Adyen, PayPal, etc.).
Steps to Reproduce
capture: false)Environment
capture: false(manual capture)