From 3df466dadde91b24f52504097c07e1ced4b2b8ff Mon Sep 17 00:00:00 2001 From: Nicolas Gorga <62995075+NicolasGorga@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:03:49 -0300 Subject: [PATCH] feat(core-flows): Allow payment session status captured to be processable upon cart completion (#14527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary **What** — What changes are introduced in this PR? Allow payment session `captured` to be processable upon cart completion. **Why** — Why are these changes relevant or necessary? Without it it is impossible to complete a cart that has an already captured payment session. **How** — How have these changes been implemented? Added `captured` to the processable payment session status list in the complete cart workflow payment validation step. **Testing** — How have these changes been tested, or how can the reviewer test the feature? Integration tests. --- ## Examples Provide examples or code snippets that demonstrate how this feature works, or how it can be used in practice. This helps with documentation and ensures maintainers can quickly understand and verify the change. ```ts // Example usage ``` --- ## Checklist Please ensure the following before requesting a review: - [x] I have added a **changeset** for this PR - Every non-breaking change should be marked as a **patch** - To add a changeset, run `yarn changeset` and follow the prompts - [x] The changes are covered by relevant **tests** - [x] I have verified the code works as intended locally - [x] I have linked the related issue(s) if applicable --- ## Additional Context Add any additional context, related issues, or references that might help the reviewer understand this PR. Closes CORE-1361 --- .changeset/upset-keys-deny.md | 5 ++ .../http/__tests__/cart/store/cart.spec.ts | 63 +++++++++++++++++++ .../src/cart/steps/validate-cart-payments.ts | 3 +- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 .changeset/upset-keys-deny.md diff --git a/.changeset/upset-keys-deny.md b/.changeset/upset-keys-deny.md new file mode 100644 index 0000000000..182bef3129 --- /dev/null +++ b/.changeset/upset-keys-deny.md @@ -0,0 +1,5 @@ +--- +"@medusajs/core-flows": patch +--- + +feat(core-flows): Allow payment session status captured to be processable upon cart completion diff --git a/integration-tests/http/__tests__/cart/store/cart.spec.ts b/integration-tests/http/__tests__/cart/store/cart.spec.ts index d6a2421a4b..323efeb82b 100644 --- a/integration-tests/http/__tests__/cart/store/cart.spec.ts +++ b/integration-tests/http/__tests__/cart/store/cart.spec.ts @@ -2,6 +2,7 @@ import { createCartCreditLinesWorkflow } from "@medusajs/core-flows" import { medusaIntegrationTestRunner } from "@medusajs/test-utils" import { Modules, + PaymentSessionStatus, PriceListStatus, PriceListType, ProductStatus, @@ -2003,6 +2004,68 @@ medusaIntegrationTestRunner({ ) }) + it("should successfully complete cart with pre existing captured payment session", async () => { + const paymentModule = appContainer.resolve(Modules.PAYMENT) + + const paymentCollection = ( + await api.post( + `/store/payment-collections`, + { cart_id: cart.id }, + storeHeaders + ) + ).data.payment_collection + + const paymentSession = await api + .post( + `/store/payment-collections/${paymentCollection.id}/payment-sessions`, + { provider_id: "pp_system_default" }, + storeHeaders + ) + .then((res) => res.data.payment_collection.payment_sessions[0]) + + // Authorize the payment session (creates a payment) + const payment = await paymentModule.authorizePaymentSession( + paymentSession.id, + {} + ) + + // Capture the payment + await paymentModule.capturePayment({ + payment_id: payment.id, + }) + + const updatedPaymentSession = + await paymentModule.retrievePaymentSession(paymentSession.id, { + relations: ["payment", "payment.captures"], + }) + expect(updatedPaymentSession.payment.captures).toHaveLength(1) + expect(updatedPaymentSession.status).toBe( + PaymentSessionStatus.AUTHORIZED + ) + + // Complete the cart + const response = await api.post( + `/store/carts/${cart.id}/complete`, + {}, + storeHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.order).toEqual( + expect.objectContaining({ + id: expect.any(String), + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 1500, + compare_at_unit_price: null, + quantity: 1, + }), + ]), + }) + ) + }) + it("should successfully complete cart with credit lines alone", async () => { const oldCart = ( await api.get(`/store/carts/${cart.id}`, storeHeaders) diff --git a/packages/core/core-flows/src/cart/steps/validate-cart-payments.ts b/packages/core/core-flows/src/cart/steps/validate-cart-payments.ts index 4f3e64ef22..f1a9535dbd 100644 --- a/packages/core/core-flows/src/cart/steps/validate-cart-payments.ts +++ b/packages/core/core-flows/src/cart/steps/validate-cart-payments.ts @@ -20,7 +20,7 @@ export interface ValidateCartPaymentsStepInput { export const validateCartPaymentsStepId = "validate-cart-payments" /** * This step validates a cart's payment sessions. Their status must - * be `pending` or `requires_more`. If not valid, the step throws an error. + * be `pending`, `requires_more`, `authorized`, or `captured`. If not valid, the step throws an error. * * :::tip * @@ -62,6 +62,7 @@ export const validateCartPaymentsStep = createStep( PaymentSessionStatus.PENDING, PaymentSessionStatus.REQUIRES_MORE, PaymentSessionStatus.AUTHORIZED, // E.g. payment was authorized, but the cart was not completed + PaymentSessionStatus.CAPTURED, // E.g. payment was captured, but the cart was not completed ] const paymentsToProcess = paymentCollection.payment_sessions?.filter((ps) =>