Skip to content

[Bug]: Manual Payment Provider: Issues with Refund and Cancellation Workflows #14214

@grenzbotin

Description

@grenzbotin

Package.json file

{
  "name": "my-custom-store",
  "version": "0.0.1",
  "description": "A starter for Medusa projects.",
  "author": "Medusa (https://medusajs.com)",
  "license": "MIT",
  "keywords": [
    "sqlite",
    "postgres",
    "typescript",
    "ecommerce",
    "headless",
    "medusa"
  ],
  "scripts": {
    "build": "medusa build",
    "seed": "medusa exec ./src/scripts/seed.ts",
    "start": "medusa start",
    "dev": "medusa develop",
    "test:integration:http": "TEST_TYPE=integration:http NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit",
    "test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit",
    "test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit"
  },
  "dependencies": {
    "@medusajs/admin-sdk": "2.12.1",
    "@medusajs/cli": "2.12.1",
    "@medusajs/framework": "2.12.1",
    "@medusajs/medusa": "2.12.1",
    "@rokmohar/medusa-plugin-meilisearch": "^1.3.5",
    "jsonwebtoken": "^9.0.2",
    "medusa-plugin-tolgee": "^1.4.5"
  },
  "devDependencies": {
    "@medusajs/test-utils": "2.12.1",
    "@swc/core": "1.5.7",
    "@swc/jest": "^0.2.36",
    "@types/jest": "^29.5.13",
    "@types/jsonwebtoken": "^9.0.10",
    "@types/node": "^20.0.0",
    "@types/react": "^18.3.2",
    "@types/react-dom": "^18.2.25",
    "jest": "^29.7.0",
    "prop-types": "^15.8.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.6.2",
    "vite": "^5.2.11",
    "yalc": "^1.0.0-pre.53"
  },
  "engines": {
    "node": ">=20"
  },
  "packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

Node.js version

v22.16.0

Database and its version

PostgreSQL 17.4

Operating system name and version

n/a

Browser name

No response

What happended?

I tested out the Manual Payment Provider (pp_system_default) with refunding & cancellation flows and noticed some troubles. Still, problem could be on me – than I am more than thankful for a hint. 😃

Issue 1: Cancel First → Refund Fails in UI

Steps to Reproduce

  1. Create an order using the Manual Payment Provider.
  2. Manually capture the payment (mark as paid).
  3. Cancel the order via the Admin UI.
  4. Observe the order action log shows “Payment refunded,” and the payment status changes to refunded, even though no actual refund was processed.
  5. Attempt to refund the order manually by clicking “Refund € [amount]” in the Admin UI.

Expected Behavior

  • Cancellation should not automatically mark payment as refunded.
  • Admin should be able to select the payment method and proceed with a manual refund after cancellation.

Actual Behavior

  • Payment status is incorrectly set to refunded upon cancellation.
  • In the refund form, the payment method is visible but cannot be selected, blocking further action.
Image

Relevant Order Summary After Cancellation:

{
  ...,
  payment_status: "refunded",
  status: "canceled",
  summary: {
      "paid_total": 1009,
      "refunded_total": 0,
      "accounting_total": 0,
      "credit_line_total": 1009,
      "transaction_total": 1009,
      "pending_difference": -1009,
      "current_order_total": 0,
      "original_order_total": 1009,
  }
}

Issue 2: Refund First → Cancel Creates Inconsistent Balance

Now we tried the other way round. We first refund the complete order manually. That works flawlessly. Everything looks correct, refunded_total & pending_difference show the correct values.

But now, for bookkeeping reasons and general processing, we still want to mark the order as cancelled.
If we do so, we get into a weird state of being refunded, but still "owe" the customer the total order value:

Steps to Reproduce

  1. Create an order using the Manual Payment Provider.
  2. Manually refund the full order amount (works correctly).
  3. Cancel the order afterward.

Expected Behavior

  • Order should reflect a zero balance after full refund, and cancellation should not alter financial totals.

Actual Behavior

  • After cancellation, the order summary shows inconsistent totals, indicating the customer is still owed the order amount (pending_difference: -1009) despite a full refund already recorded.

Relevant Order Summary After Refund + Cancel

{
  ...,
  payment_status: "refunded",
  status: "canceled",
  summary: {
      "paid_total": 1009,
      "refunded_total": 1009,
      "accounting_total": -1009,
      "credit_line_total": 1009,
      "transaction_total": 0,
      "pending_difference": -1009,
      "current_order_total": -1009,
      "original_order_total": 0,
  }
}

Summary

Both workflows highlight synchronization issues between manual payment handling and order status transitions:

  • Issue 1: Cancellation incorrectly updates payment status and blocks manual refund in the UI.
  • Issue 2: Cancellation after a full refund creates contradictory accounting totals.

These inconsistencies disrupt manual payment workflows, especially for merchants who handle payments outside of medusa.

Possible Solution Direction

  • Ensure cancellation does not automatically set payment status to refunded for manual payments.
  • Allow manual refund selection even after cancellation.
  • Review how cancellation recalculates pending_difference and other summary fields when a refund has already been processed.

Thank you for looking into this issue. Let me know if further details or testing is needed.

Expected behavior

see above

Actual behavior

see above

Link to reproduction repo

n/a

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions