From 00ab03f3a2b0c59049f5c5a2af2cb5eee9d4c72d Mon Sep 17 00:00:00 2001 From: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Sun, 19 Sep 2021 15:33:43 +0200 Subject: [PATCH] feat: Allow backorder on swaps (#404) --- .../admin/__snapshots__/product.js.snap | 382 +++++++++++++++ .../api/__tests__/admin/swaps.js | 112 ++--- .../store/__snapshots__/cart.js.snap | 8 + .../store/__snapshots__/swaps.js.snap | 2 + integration-tests/api/__tests__/store/cart.js | 451 ++++++++++-------- integration-tests/api/helpers/cart-seeder.js | 152 +++--- integration-tests/api/helpers/swap-seeder.js | 51 +- integration-tests/api/package.json | 6 +- integration-tests/api/yarn.lock | 104 ++-- .../api/routes/admin/orders/create-swap.js | 5 + .../api/routes/store/carts/complete-cart.js | 38 +- .../1630505790603-allow_backorder_swaps.ts | 24 + packages/medusa/src/models/cart.ts | 3 + packages/medusa/src/models/swap.ts | 7 + .../medusa/src/services/__tests__/order.js | 3 + .../medusa/src/services/__tests__/swap.js | 22 + packages/medusa/src/services/cart.js | 21 +- packages/medusa/src/services/order.js | 7 + .../medusa/src/services/payment-provider.js | 2 +- packages/medusa/src/services/swap.js | 31 +- 20 files changed, 1038 insertions(+), 393 deletions(-) create mode 100644 integration-tests/api/__tests__/admin/__snapshots__/product.js.snap create mode 100644 packages/medusa/src/migrations/1630505790603-allow_backorder_swaps.ts diff --git a/integration-tests/api/__tests__/admin/__snapshots__/product.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/product.js.snap new file mode 100644 index 0000000000..4cd2e9683e --- /dev/null +++ b/integration-tests/api/__tests__/admin/__snapshots__/product.js.snap @@ -0,0 +1,382 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`/admin/products GET /admin/products returns a list of products with child entities 1`] = ` +Array [ + Object { + "collection": Object { + "created_at": Any, + "deleted_at": null, + "handle": "test-collection", + "id": StringMatching /\\^test-\\*/, + "metadata": null, + "title": "Test collection", + "updated_at": Any, + }, + "collection_id": "test-collection", + "created_at": Any, + "deleted_at": null, + "description": "test-product-description", + "discountable": true, + "handle": "test-product", + "height": null, + "hs_code": null, + "id": StringMatching /\\^test-\\*/, + "images": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-\\*/, + "metadata": null, + "updated_at": Any, + "url": "test-image.png", + }, + ], + "is_giftcard": false, + "length": null, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-\\*/, + "metadata": null, + "product_id": StringMatching /\\^test-\\*/, + "title": "test-option", + "updated_at": Any, + }, + ], + "origin_country": null, + "profile_id": StringMatching /\\^sp_\\*/, + "subtitle": null, + "tags": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^tag\\*/, + "metadata": null, + "updated_at": Any, + "value": "123", + }, + ], + "thumbnail": null, + "title": "Test product", + "type": Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-\\*/, + "metadata": null, + "updated_at": Any, + "value": "test-type", + }, + "type_id": "test-type", + "updated_at": Any, + "variants": Array [ + Object { + "allow_backorder": false, + "barcode": "test-barcode", + "created_at": Any, + "deleted_at": null, + "ean": "test-ean", + "height": null, + "hs_code": null, + "id": "test-variant", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-variant-option\\*/, + "metadata": null, + "option_id": StringMatching /\\^test-opt\\*/, + "updated_at": Any, + "value": "Default variant", + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "origin_country": null, + "prices": Array [ + Object { + "amount": 100, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": StringMatching /\\^test-price\\*/, + "region_id": null, + "sale_amount": null, + "updated_at": Any, + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "product_id": StringMatching /\\^test-\\*/, + "sku": "test-sku", + "title": "Test variant", + "upc": "test-upc", + "updated_at": Any, + "weight": null, + "width": null, + }, + Object { + "allow_backorder": false, + "barcode": null, + "created_at": Any, + "deleted_at": null, + "ean": "test-ean2", + "height": null, + "hs_code": null, + "id": "test-variant_2", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-variant-option\\*/, + "metadata": null, + "option_id": StringMatching /\\^test-opt\\*/, + "updated_at": Any, + "value": "Default variant 2", + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "origin_country": null, + "prices": Array [ + Object { + "amount": 100, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": StringMatching /\\^test-price\\*/, + "region_id": null, + "sale_amount": null, + "updated_at": Any, + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "product_id": StringMatching /\\^test-\\*/, + "sku": "test-sku2", + "title": "Test variant rank (2)", + "upc": "test-upc2", + "updated_at": Any, + "weight": null, + "width": null, + }, + Object { + "allow_backorder": false, + "barcode": "test-barcode 1", + "created_at": Any, + "deleted_at": null, + "ean": "test-ean1", + "height": null, + "hs_code": null, + "id": "test-variant_1", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-variant-option\\*/, + "metadata": null, + "option_id": StringMatching /\\^test-opt\\*/, + "updated_at": Any, + "value": "Default variant 1", + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "origin_country": null, + "prices": Array [ + Object { + "amount": 100, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": StringMatching /\\^test-price\\*/, + "region_id": null, + "sale_amount": null, + "updated_at": Any, + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "product_id": StringMatching /\\^test-\\*/, + "sku": "test-sku1", + "title": "Test variant rank (1)", + "upc": "test-upc1", + "updated_at": Any, + "weight": null, + "width": null, + }, + ], + "weight": null, + "width": null, + }, + Object { + "collection": Object { + "created_at": Any, + "deleted_at": null, + "handle": "test-collection", + "id": StringMatching /\\^test-\\*/, + "metadata": null, + "title": "Test collection", + "updated_at": Any, + }, + "collection_id": "test-collection", + "created_at": Any, + "deleted_at": null, + "description": "test-product-description1", + "discountable": true, + "handle": "test-product1", + "height": null, + "hs_code": null, + "id": StringMatching /\\^test-\\*/, + "images": Array [], + "is_giftcard": false, + "length": null, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [], + "origin_country": null, + "profile_id": StringMatching /\\^sp_\\*/, + "subtitle": null, + "tags": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^tag\\*/, + "metadata": null, + "updated_at": Any, + "value": "123", + }, + ], + "thumbnail": null, + "title": "Test product1", + "type": Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-\\*/, + "metadata": null, + "updated_at": Any, + "value": "test-type", + }, + "type_id": "test-type", + "updated_at": Any, + "variants": Array [ + Object { + "allow_backorder": false, + "barcode": null, + "created_at": Any, + "deleted_at": null, + "ean": "test-ean4", + "height": null, + "hs_code": null, + "id": "test-variant_4", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-variant-option\\*/, + "metadata": null, + "option_id": StringMatching /\\^test-opt\\*/, + "updated_at": Any, + "value": "Default variant 4", + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "origin_country": null, + "prices": Array [ + Object { + "amount": 100, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": StringMatching /\\^test-price\\*/, + "region_id": null, + "sale_amount": null, + "updated_at": Any, + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "product_id": StringMatching /\\^test-\\*/, + "sku": "test-sku4", + "title": "Test variant rank (2)", + "upc": "test-upc4", + "updated_at": Any, + "weight": null, + "width": null, + }, + Object { + "allow_backorder": false, + "barcode": null, + "created_at": Any, + "deleted_at": null, + "ean": "test-ean3", + "height": null, + "hs_code": null, + "id": "test-variant_3", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^test-variant-option\\*/, + "metadata": null, + "option_id": StringMatching /\\^test-opt\\*/, + "updated_at": Any, + "value": "Default variant 3", + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "origin_country": null, + "prices": Array [ + Object { + "amount": 100, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": StringMatching /\\^test-price\\*/, + "region_id": null, + "sale_amount": null, + "updated_at": Any, + "variant_id": StringMatching /\\^test-variant\\*/, + }, + ], + "product_id": StringMatching /\\^test-\\*/, + "sku": "test-sku3", + "title": "Test variant rank (2)", + "upc": "test-upc3", + "updated_at": Any, + "weight": null, + "width": null, + }, + ], + "weight": null, + "width": null, + }, +] +`; diff --git a/integration-tests/api/__tests__/admin/swaps.js b/integration-tests/api/__tests__/admin/swaps.js index efcc142e65..78ec6db9e8 100644 --- a/integration-tests/api/__tests__/admin/swaps.js +++ b/integration-tests/api/__tests__/admin/swaps.js @@ -1,49 +1,49 @@ -const path = require("path"); +const path = require("path") -const setupServer = require("../../../helpers/setup-server"); -const { useApi } = require("../../../helpers/use-api"); -const { initDb, useDb } = require("../../../helpers/use-db"); +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") -const orderSeeder = require("../../helpers/order-seeder"); -const swapSeeder = require("../../helpers/swap-seeder"); -const adminSeeder = require("../../helpers/admin-seeder"); +const orderSeeder = require("../../helpers/order-seeder") +const swapSeeder = require("../../helpers/swap-seeder") +const adminSeeder = require("../../helpers/admin-seeder") -jest.setTimeout(30000); +jest.setTimeout(30000) describe("/admin/swaps", () => { - let medusaProcess; - let dbConnection; + let medusaProcess + let dbConnection beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")); - dbConnection = await initDb({ cwd }); - medusaProcess = await setupServer({ cwd }); - }); + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd }) + medusaProcess = await setupServer({ cwd }) + }) afterAll(async () => { - const db = useDb(); - await db.shutdown(); - medusaProcess.kill(); - }); + const db = useDb() + await db.shutdown() + medusaProcess.kill() + }) describe("GET /admin/swaps/:id", () => { beforeEach(async () => { try { - await adminSeeder(dbConnection); - await orderSeeder(dbConnection); - await swapSeeder(dbConnection); + await adminSeeder(dbConnection) + await orderSeeder(dbConnection) + await swapSeeder(dbConnection) } catch (err) { - throw err; + throw err } - }); + }) afterEach(async () => { - const db = useDb(); - await db.teardown(); - }); + const db = useDb() + await db.teardown() + }) it("gets a swap with cart and totals", async () => { - const api = useApi(); + const api = useApi() const response = await api .get("/admin/swaps/test-swap", { @@ -52,46 +52,46 @@ describe("/admin/swaps", () => { }, }) .catch((err) => { - console.log(err); - }); - expect(response.status).toEqual(200); + console.log(err) + }) + expect(response.status).toEqual(200) expect(response.data.swap).toEqual( expect.objectContaining({ id: "test-swap", }) - ); + ) expect(response.data.swap.cart).toEqual( expect.objectContaining({ - id: "test-cart", + id: "test-cart-w-swap", shipping_total: 1000, subtotal: 1000, total: 2000, }) - ); - expect(response.data.swap.cart).toHaveProperty("discount_total"); - expect(response.data.swap.cart).toHaveProperty("gift_card_total"); - }); - }); + ) + expect(response.data.swap.cart).toHaveProperty("discount_total") + expect(response.data.swap.cart).toHaveProperty("gift_card_total") + }) + }) describe("GET /admin/swaps/", () => { beforeEach(async () => { try { - await adminSeeder(dbConnection); - await orderSeeder(dbConnection); - await swapSeeder(dbConnection); + await adminSeeder(dbConnection) + await orderSeeder(dbConnection) + await swapSeeder(dbConnection) } catch (err) { - throw err; + throw err } - }); + }) afterEach(async () => { - const db = useDb(); - await db.teardown(); - }); + const db = useDb() + await db.teardown() + }) it("lists all swaps", async () => { - const api = useApi(); + const api = useApi() const response = await api .get("/admin/swaps/", { @@ -100,18 +100,18 @@ describe("/admin/swaps", () => { }, }) .catch((err) => { - console.log(err); - }); + console.log(err) + }) - expect(response.status).toEqual(200); - expect(response.data).toHaveProperty("count"); - expect(response.data.offset).toBe(0); - expect(response.data.limit).toBe(50); + expect(response.status).toEqual(200) + expect(response.data).toHaveProperty("count") + expect(response.data.offset).toBe(0) + expect(response.data.limit).toBe(50) expect(response.data.swaps).toContainEqual( expect.objectContaining({ id: "test-swap", }) - ); - }); - }); -}); + ) + }) + }) +}) diff --git a/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap b/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap index 9f6476ca0a..520fe3a3ae 100644 --- a/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap +++ b/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap @@ -8,6 +8,14 @@ Object { } `; +exports[`/store/carts POST /store/carts/:id fails to complete swap cart with items inventory not/partially covered 1`] = ` +Object { + "code": "insufficient_inventory", + "message": "Variant with id: test-variant-2 does not have the required inventory", + "type": "not_allowed", +} +`; + exports[`/store/carts POST /store/carts/:id returns early, if cart is already completed 1`] = ` Object { "code": "cart_incompatible_state", diff --git a/integration-tests/api/__tests__/store/__snapshots__/swaps.js.snap b/integration-tests/api/__tests__/store/__snapshots__/swaps.js.snap index befb1e4768..5f3522721f 100644 --- a/integration-tests/api/__tests__/store/__snapshots__/swaps.js.snap +++ b/integration-tests/api/__tests__/store/__snapshots__/swaps.js.snap @@ -91,6 +91,7 @@ Object { "parent_order_id": "test-order", "swap_id": StringMatching /\\^swap_\\*/, }, + "payment_authorized_at": null, "payment_id": null, "region_id": "test-region", "shipping_address_id": "test-shipping-address", @@ -260,6 +261,7 @@ Object { "parent_order_id": "test-order", "swap_id": StringMatching /\\^swap_\\*/, }, + "payment_authorized_at": null, "payment_id": null, "region_id": "test-region", "shipping_address_id": "test-shipping-address", diff --git a/integration-tests/api/__tests__/store/cart.js b/integration-tests/api/__tests__/store/cart.js index 8527218273..afd35f4e69 100644 --- a/integration-tests/api/__tests__/store/cart.js +++ b/integration-tests/api/__tests__/store/cart.js @@ -1,155 +1,161 @@ -const path = require("path"); -const { Region, LineItem, GiftCard } = require("@medusajs/medusa"); +const path = require("path") +const { Region, LineItem, GiftCard } = require("@medusajs/medusa") -const setupServer = require("../../../helpers/setup-server"); -const { useApi } = require("../../../helpers/use-api"); -const { initDb, useDb } = require("../../../helpers/use-db"); +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") -const cartSeeder = require("../../helpers/cart-seeder"); +const cartSeeder = require("../../helpers/cart-seeder") +const swapSeeder = require("../../helpers/swap-seeder") -jest.setTimeout(30000); +jest.setTimeout(30000) describe("/store/carts", () => { - let medusaProcess; - let dbConnection; + let medusaProcess + let dbConnection const doAfterEach = async () => { - const db = useDb(); - return await db.teardown(); - }; + const db = useDb() + return await db.teardown() + } beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")); - dbConnection = await initDb({ cwd }); - medusaProcess = await setupServer({ cwd }); - }); + const cwd = path.resolve(path.join(__dirname, "..", "..")) + try { + dbConnection = await initDb({ cwd }) + medusaProcess = await setupServer({ cwd }) + } catch (error) { + console.log(error) + } + }) afterAll(async () => { - const db = useDb(); - await db.shutdown(); - medusaProcess.kill(); - }); + const db = useDb() + await db.shutdown() + medusaProcess.kill() + }) describe("POST /store/carts", () => { beforeEach(async () => { - const manager = dbConnection.manager; + const manager = dbConnection.manager await manager.insert(Region, { id: "region", name: "Test Region", currency_code: "usd", tax_rate: 0, - }); + }) await manager.query( `UPDATE "country" SET region_id='region' WHERE iso_2 = 'us'` - ); - }); + ) + }) afterEach(async () => { - await doAfterEach(); - }); + await doAfterEach() + }) it("creates a cart", async () => { - const api = useApi(); + const api = useApi() - const response = await api.post("/store/carts"); - expect(response.status).toEqual(200); + const response = await api.post("/store/carts") + expect(response.status).toEqual(200) - const getRes = await api.post(`/store/carts/${response.data.cart.id}`); - expect(getRes.status).toEqual(200); - }); + const getRes = await api.post(`/store/carts/${response.data.cart.id}`) + expect(getRes.status).toEqual(200) + }) it("creates a cart with country", async () => { - const api = useApi(); + const api = useApi() const response = await api.post("/store/carts", { country_code: "us", - }); - expect(response.status).toEqual(200); - expect(response.data.cart.shipping_address.country_code).toEqual("us"); + }) + expect(response.status).toEqual(200) + expect(response.data.cart.shipping_address.country_code).toEqual("us") - const getRes = await api.post(`/store/carts/${response.data.cart.id}`); - expect(getRes.status).toEqual(200); - }); + const getRes = await api.post(`/store/carts/${response.data.cart.id}`) + expect(getRes.status).toEqual(200) + }) it("creates a cart with context", async () => { - const api = useApi(); + const api = useApi() const response = await api.post("/store/carts", { context: { test_id: "test", }, - }); - expect(response.status).toEqual(200); + }) + expect(response.status).toEqual(200) - const getRes = await api.post(`/store/carts/${response.data.cart.id}`); - expect(getRes.status).toEqual(200); + const getRes = await api.post(`/store/carts/${response.data.cart.id}`) + expect(getRes.status).toEqual(200) - const cart = getRes.data.cart; + const cart = getRes.data.cart expect(cart.context).toEqual({ ip: "::ffff:127.0.0.1", user_agent: "axios/0.21.1", test_id: "test", - }); - }); - }); + }) + }) + }) describe("POST /store/carts/:id", () => { beforeEach(async () => { try { - await cartSeeder(dbConnection); + await cartSeeder(dbConnection) + await swapSeeder(dbConnection) } catch (err) { - console.log(err); - throw err; + console.log(err) + throw err } - }); + }) afterEach(async () => { - await doAfterEach(); - }); + await doAfterEach() + }) it("fails on apply discount if limit has been reached", async () => { - const api = useApi(); + const api = useApi() try { await api.post("/store/carts/test-cart", { discounts: [{ code: "CREATED" }], - }); + }) } catch (error) { - expect(error.response.status).toEqual(400); + expect(error.response.status).toEqual(400) expect(error.response.data.message).toEqual( "Discount has been used maximum allowed times" - ); + ) } - }); + }) it("updates cart customer id", async () => { - const api = useApi(); + const api = useApi() const response = await api.post("/store/carts/test-cart", { customer_id: "test-customer-2", - }); + }) - expect(response.status).toEqual(200); - }); + expect(response.status).toEqual(200) + }) it("updates address using string id", async () => { - const api = useApi(); + const api = useApi() const response = await api.post("/store/carts/test-cart", { billing_address: "test-general-address", shipping_address: "test-general-address", - }); + }) expect(response.data.cart.shipping_address_id).toEqual( "test-general-address" - ); + ) expect(response.data.cart.billing_address_id).toEqual( "test-general-address" - ); - expect(response.status).toEqual(200); - }); + ) + expect(response.status).toEqual(200) + }) it("updates address", async () => { - const api = useApi(); + const api = useApi() const response = await api.post("/store/carts/test-cart", { shipping_address: { @@ -160,14 +166,14 @@ describe("/store/carts", () => { country_code: "us", postal_code: "something", }, - }); + }) - expect(response.data.cart.shipping_address.first_name).toEqual("clark"); - expect(response.status).toEqual(200); - }); + expect(response.data.cart.shipping_address.first_name).toEqual("clark") + expect(response.status).toEqual(200) + }) it("adds free shipping to cart then removes it again", async () => { - const api = useApi(); + const api = useApi() let cart = await api.post( "/store/carts/test-cart", @@ -175,10 +181,10 @@ describe("/store/carts", () => { discounts: [{ code: "FREE_SHIPPING" }, { code: "CREATED" }], }, { withCredentials: true } - ); + ) - expect(cart.data.cart.shipping_total).toBe(0); - expect(cart.status).toEqual(200); + expect(cart.data.cart.shipping_total).toBe(0) + expect(cart.status).toEqual(200) cart = await api.post( "/store/carts/test-cart", @@ -186,68 +192,68 @@ describe("/store/carts", () => { discounts: [{ code: "CREATED" }], }, { withCredentials: true } - ); + ) - expect(cart.data.cart.shipping_total).toBe(1000); - expect(cart.status).toEqual(200); - }); + expect(cart.data.cart.shipping_total).toBe(1000) + expect(cart.status).toEqual(200) + }) it("complete cart with giftcard total 0", async () => { - const manager = dbConnection.manager; + const manager = dbConnection.manager await manager.insert(GiftCard, { id: "gift_test", code: "GC_TEST", value: 20000, balance: 20000, region_id: "test-region", - }); + }) - const api = useApi(); + const api = useApi() await api.post(`/store/carts/test-cart-3`, { gift_cards: [{ code: "GC_TEST" }], - }); + }) const getRes = await api .post(`/store/carts/test-cart-3/complete`) .catch((err) => { - console.log(err.response.data); - }); + console.log(err.response.data) + }) - expect(getRes.status).toEqual(200); - expect(getRes.data.type).toEqual("order"); - }); + expect(getRes.status).toEqual(200) + expect(getRes.data.type).toEqual("order") + }) it("complete cart with items inventory covered", async () => { - const api = useApi(); - const getRes = await api.post(`/store/carts/test-cart-2/complete-cart`); + const api = useApi() + const getRes = await api.post(`/store/carts/test-cart-2/complete-cart`) - expect(getRes.status).toEqual(200); + expect(getRes.status).toEqual(200) - const variantRes = await api.get("/store/variants/test-variant"); - expect(variantRes.data.variant.inventory_quantity).toEqual(0); - }); + const variantRes = await api.get("/store/variants/test-variant") + expect(variantRes.data.variant.inventory_quantity).toEqual(0) + }) it("returns early, if cart is already completed", async () => { - const manager = dbConnection.manager; - const api = useApi(); + const manager = dbConnection.manager + const api = useApi() await manager.query( `UPDATE "cart" SET completed_at=current_timestamp WHERE id = 'test-cart-2'` - ); + ) try { - await api.post(`/store/carts/test-cart-2/complete-cart`); + await api.post(`/store/carts/test-cart-2/complete-cart`) } catch (error) { expect(error.response.data).toMatchSnapshot({ code: "not_allowed", message: "Cart has already been completed", code: "cart_incompatible_state", - }); - expect(error.response.status).toEqual(409); + }) + expect(error.response.status).toEqual(409) } - }); + }) it("fails to complete cart with items inventory not/partially covered", async () => { - const manager = dbConnection.manager; + const manager = dbConnection.manager const li = manager.create(LineItem, { id: "test-item", @@ -258,37 +264,110 @@ describe("/store/carts", () => { quantity: 99, variant_id: "test-variant-2", cart_id: "test-cart-2", - }); - await manager.save(li); + }) + await manager.save(li) - const api = useApi(); + const api = useApi() try { - await api.post(`/store/carts/test-cart-2/complete-cart`); + await api.post(`/store/carts/test-cart-2/complete-cart`) } catch (e) { expect(e.response.data).toMatchSnapshot({ code: "insufficient_inventory", - }); - expect(e.response.status).toBe(409); + }) + expect(e.response.status).toBe(409) } - //check to see if payment has been cancelled - const res = await api.get(`/store/carts/test-cart-2`); - expect(res.data.cart.payment.canceled_at).not.toBe(null); - }); - }); + //check to see if payment has been cancelled and cart is not completed + const res = await api.get(`/store/carts/test-cart-2`) + expect(res.data.cart.payment.canceled_at).not.toBe(null) + expect(res.data.cart.completed_at).toBe(null) + }) + + it("fails to complete swap cart with items inventory not/partially covered", async () => { + const manager = dbConnection.manager + + const li = manager.create(LineItem, { + id: "test-item", + title: "Line Item", + description: "Line Item Desc", + thumbnail: "https://test.js/1234", + unit_price: 8000, + quantity: 99, + variant_id: "test-variant-2", + cart_id: "swap-cart", + }) + await manager.save(li) + + await manager.query( + "UPDATE swap SET cart_id='swap-cart' where id='test-swap'" + ) + + const api = useApi() + + try { + await api.post(`/store/carts/swap-cart/complete-cart`) + } catch (e) { + expect(e.response.data).toMatchSnapshot({ + code: "insufficient_inventory", + }) + expect(e.response.status).toBe(409) + } + + //check to see if payment has been cancelled and cart is not completed + const res = await api.get(`/store/carts/swap-cart`) + expect(res.data.cart.payment_authorized_at).toBe(null) + expect(res.data.cart.payment.canceled_at).not.toBe(null) + }) + + it("successfully completes swap cart with items inventory not/partially covered due to backorder flag", async () => { + const manager = dbConnection.manager + + const li = manager.create(LineItem, { + id: "test-item", + title: "Line Item", + description: "Line Item Desc", + thumbnail: "https://test.js/1234", + unit_price: 8000, + quantity: 99, + variant_id: "test-variant-2", + cart_id: "swap-cart", + }) + await manager.save(li) + await manager.query( + "UPDATE swap SET cart_id='swap-cart' where id='test-swap'" + ) + await manager.query( + "UPDATE swap SET allow_backorder=true where id='test-swap'" + ) + await manager.query("DELETE FROM payment where swap_id='test-swap'") + + const api = useApi() + + try { + await api.post(`/store/carts/swap-cart/complete-cart`) + } catch (error) { + console.log(error) + } + + //check to see if payment is authorized and cart is completed + const res = await api.get(`/store/carts/swap-cart`) + expect(res.data.cart.payment_authorized_at).not.toBe(null) + expect(res.data.cart.completed_at).not.toBe(null) + }) + }) describe("POST /store/carts/:id/shipping-methods", () => { beforeEach(async () => { - await cartSeeder(dbConnection); - }); + await cartSeeder(dbConnection) + }) afterEach(async () => { - await doAfterEach(); - }); + await doAfterEach() + }) it("adds a shipping method to cart", async () => { - const api = useApi(); + const api = useApi() const cartWithShippingMethod = await api.post( "/store/carts/test-cart/shipping-methods", @@ -296,16 +375,16 @@ describe("/store/carts", () => { option_id: "test-option", }, { withCredentials: true } - ); + ) expect(cartWithShippingMethod.data.cart.shipping_methods).toContainEqual( expect.objectContaining({ shipping_option_id: "test-option" }) - ); - expect(cartWithShippingMethod.status).toEqual(200); - }); + ) + expect(cartWithShippingMethod.status).toEqual(200) + }) it("adds a giftcard to cart, but ensures discount only applied to discountable items", async () => { - const api = useApi(); + const api = useApi() // Add standard line item to cart await api.post( @@ -315,7 +394,7 @@ describe("/store/carts", () => { quantity: 1, }, { withCredentials: true } - ); + ) // Add gift card to cart await api.post( @@ -325,7 +404,7 @@ describe("/store/carts", () => { quantity: 1, }, { withCredentials: true } - ); + ) // Add a 10% discount to the cart const cartWithGiftcard = await api.post( @@ -334,16 +413,16 @@ describe("/store/carts", () => { discounts: [{ code: "10PERCENT" }], }, { withCredentials: true } - ); + ) // Ensure that the discount is only applied to the standard item - expect(cartWithGiftcard.data.cart.total).toBe(1900); // 1000 (giftcard) + 900 (standard item with 10% discount) - expect(cartWithGiftcard.data.cart.discount_total).toBe(100); - expect(cartWithGiftcard.status).toEqual(200); - }); + expect(cartWithGiftcard.data.cart.total).toBe(1900) // 1000 (giftcard) + 900 (standard item with 10% discount) + expect(cartWithGiftcard.data.cart.discount_total).toBe(100) + expect(cartWithGiftcard.status).toEqual(200) + }) it("adds no more than 1 shipping method per shipping profile", async () => { - const api = useApi(); + const api = useApi() const addShippingMethod = async (option_id) => { return await api.post( "/store/carts/test-cart/shipping-methods", @@ -351,17 +430,17 @@ describe("/store/carts", () => { option_id, }, { withCredentials: true } - ); - }; + ) + } - await addShippingMethod("test-option"); + await addShippingMethod("test-option") const cartWithAnotherShippingMethod = await addShippingMethod( "test-option-2" - ); + ) expect( cartWithAnotherShippingMethod.data.cart.shipping_methods.length - ).toEqual(1); + ).toEqual(1) expect( cartWithAnotherShippingMethod.data.cart.shipping_methods ).toContainEqual( @@ -369,30 +448,30 @@ describe("/store/carts", () => { shipping_option_id: "test-option-2", price: 500, }) - ); - expect(cartWithAnotherShippingMethod.status).toEqual(200); - }); - }); + ) + expect(cartWithAnotherShippingMethod.status).toEqual(200) + }) + }) describe("DELETE /store/carts/:id/discounts/:code", () => { beforeEach(async () => { try { - await cartSeeder(dbConnection); + await cartSeeder(dbConnection) await dbConnection.manager.query( `INSERT INTO "cart_discounts" (cart_id, discount_id) VALUES ('test-cart', 'free-shipping')` - ); + ) } catch (err) { - console.log(err); - throw err; + console.log(err) + throw err } - }); + }) afterEach(async () => { - await doAfterEach(); - }); + await doAfterEach() + }) it("removes free shipping and updates shipping total", async () => { - const api = useApi(); + const api = useApi() const cartWithFreeShipping = await api.post( "/store/carts/test-cart", @@ -400,36 +479,36 @@ describe("/store/carts", () => { discounts: [{ code: "FREE_SHIPPING" }], }, { withCredentials: true } - ); + ) - expect(cartWithFreeShipping.data.cart.shipping_total).toBe(0); - expect(cartWithFreeShipping.status).toEqual(200); + expect(cartWithFreeShipping.data.cart.shipping_total).toBe(0) + expect(cartWithFreeShipping.status).toEqual(200) const response = await api.delete( "/store/carts/test-cart/discounts/FREE_SHIPPING" - ); + ) - expect(response.data.cart.shipping_total).toBe(1000); - expect(response.status).toEqual(200); - }); - }); + expect(response.data.cart.shipping_total).toBe(1000) + expect(response.status).toEqual(200) + }) + }) describe("get-cart with session customer", () => { beforeEach(async () => { try { - await cartSeeder(dbConnection); + await cartSeeder(dbConnection) } catch (err) { - console.log(err); - throw err; + console.log(err) + throw err } - }); + }) afterEach(async () => { - await doAfterEach(); - }); + await doAfterEach() + }) it("updates empty cart.customer_id on cart retrieval", async () => { - const api = useApi(); + const api = useApi() let customer = await api.post( "/store/customers", @@ -440,29 +519,25 @@ describe("/store/carts", () => { last_name: "oli", }, { withCredentials: true } - ); + ) - const cookie = customer.headers["set-cookie"][0]; + const cookie = customer.headers["set-cookie"][0] - const cart = await api.post( - "/store/carts", - {}, - { withCredentials: true } - ); + const cart = await api.post("/store/carts", {}, { withCredentials: true }) const response = await api.get(`/store/carts/${cart.data.cart.id}`, { headers: { cookie, }, withCredentials: true, - }); + }) - expect(response.data.cart.customer_id).toEqual(customer.data.customer.id); - expect(response.status).toEqual(200); - }); + expect(response.data.cart.customer_id).toEqual(customer.data.customer.id) + expect(response.status).toEqual(200) + }) it("updates cart.customer_id on cart retrieval if cart.customer_id differ from session customer", async () => { - const api = useApi(); + const api = useApi() let customer = await api.post( "/store/customers", @@ -473,15 +548,15 @@ describe("/store/carts", () => { last_name: "oli", }, { withCredentials: true } - ); + ) - const cookie = customer.headers["set-cookie"][0]; + const cookie = customer.headers["set-cookie"][0] - const cart = await api.post("/store/carts"); + const cart = await api.post("/store/carts") const updatedCart = await api.post(`/store/carts/${cart.data.cart.id}`, { customer_id: "test-customer", - }); + }) const response = await api.get( `/store/carts/${updatedCart.data.cart.id}`, @@ -490,10 +565,10 @@ describe("/store/carts", () => { cookie, }, } - ); + ) - expect(response.data.cart.customer_id).toEqual(customer.data.customer.id); - expect(response.status).toEqual(200); - }); - }); -}); + expect(response.data.cart.customer_id).toEqual(customer.data.customer.id) + expect(response.status).toEqual(200) + }) + }) +}) diff --git a/integration-tests/api/helpers/cart-seeder.js b/integration-tests/api/helpers/cart-seeder.js index 1d769fb0c0..c59ea96e3f 100644 --- a/integration-tests/api/helpers/cart-seeder.js +++ b/integration-tests/api/helpers/cart-seeder.js @@ -14,31 +14,31 @@ const { LineItem, Payment, PaymentSession, -} = require("@medusajs/medusa"); +} = require("@medusajs/medusa") module.exports = async (connection, data = {}) => { - const manager = connection.manager; + const manager = connection.manager const defaultProfile = await manager.findOne(ShippingProfile, { type: "default", - }); + }) const gcProfile = await manager.findOne(ShippingProfile, { type: "gift_card", - }); + }) await manager.insert(Address, { id: "test-general-address", first_name: "superman", country_code: "us", - }); + }) const r = manager.create(Region, { id: "test-region", name: "Test Region", currency_code: "usd", tax_rate: 0, - }); + }) const freeRule = manager.create(DiscountRule, { id: "free-shipping-rule", @@ -46,18 +46,18 @@ module.exports = async (connection, data = {}) => { type: "free_shipping", value: 100, allocation: "total", - }); + }) const freeDisc = manager.create(Discount, { id: "free-shipping", code: "FREE_SHIPPING", is_dynamic: false, is_disabled: false, - }); + }) - freeDisc.regions = [r]; - freeDisc.rule = freeRule; - await manager.save(freeDisc); + freeDisc.regions = [r] + freeDisc.rule = freeRule + await manager.save(freeDisc) const tenPercentRule = manager.create(DiscountRule, { id: "tenpercent-rule", @@ -65,25 +65,25 @@ module.exports = async (connection, data = {}) => { type: "percentage", value: 10, allocation: "total", - }); + }) const tenPercent = manager.create(Discount, { id: "10Percent", code: "10PERCENT", is_dynamic: false, is_disabled: false, - }); + }) - tenPercent.regions = [r]; - tenPercent.rule = tenPercentRule; - await manager.save(tenPercent); + tenPercent.regions = [r] + tenPercent.rule = tenPercentRule + await manager.save(tenPercent) const d = await manager.create(Discount, { id: "test-discount", code: "CREATED", is_dynamic: false, is_disabled: false, - }); + }) const dr = await manager.create(DiscountRule, { id: "test-discount-rule", @@ -91,31 +91,31 @@ module.exports = async (connection, data = {}) => { type: "fixed", value: 10000, allocation: "total", - }); + }) - d.rule = dr; - d.regions = [r]; + d.rule = dr + d.regions = [r] - await manager.save(d); + await manager.save(d) await manager.query( `UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'` - ); + ) await manager.insert(Customer, { id: "test-customer", email: "test@email.com", - }); + }) await manager.insert(Customer, { id: "test-customer-2", email: "test-2@email.com", - }); + }) await manager.insert(Customer, { id: "some-customer", email: "some-customer@email.com", - }); + }) await manager.insert(ShippingOption, { id: "test-option", @@ -126,7 +126,7 @@ module.exports = async (connection, data = {}) => { price_type: "flat_rate", amount: 1000, data: {}, - }); + }) await manager.insert(ShippingOption, { id: "gc-option", @@ -137,7 +137,7 @@ module.exports = async (connection, data = {}) => { price_type: "flat_rate", amount: 0, data: {}, - }); + }) await manager.insert(ShippingOption, { id: "test-option-2", @@ -148,7 +148,7 @@ module.exports = async (connection, data = {}) => { price_type: "flat_rate", amount: 500, data: {}, - }); + }) await manager.insert(Product, { id: "giftcard-product", @@ -157,7 +157,7 @@ module.exports = async (connection, data = {}) => { discountable: false, profile_id: gcProfile.id, options: [{ id: "denom", title: "Denomination" }], - }); + }) await manager.insert(ProductVariant, { id: "giftcard-denom", @@ -170,14 +170,14 @@ module.exports = async (connection, data = {}) => { value: "1000", }, ], - }); + }) await manager.insert(Product, { id: "test-product", title: "test product", profile_id: defaultProfile.id, options: [{ id: "test-option", title: "Size" }], - }); + }) await manager.insert(ProductVariant, { id: "test-variant", @@ -190,7 +190,7 @@ module.exports = async (connection, data = {}) => { value: "Size", }, ], - }); + }) await manager.insert(ProductVariant, { id: "test-variant-2", @@ -203,31 +203,31 @@ module.exports = async (connection, data = {}) => { value: "Size", }, ], - }); + }) const ma = manager.create(MoneyAmount, { variant_id: "test-variant", currency_code: "usd", amount: 1000, - }); + }) - await manager.save(ma); + await manager.save(ma) const ma2 = manager.create(MoneyAmount, { variant_id: "test-variant-2", currency_code: "usd", amount: 8000, - }); + }) - await manager.save(ma2); + await manager.save(ma2) const ma3 = manager.create(MoneyAmount, { variant_id: "giftcard-denom", currency_code: "usd", amount: 1000, - }); + }) - await manager.save(ma3); + await manager.save(ma3) const cart = manager.create(Cart, { id: "test-cart", @@ -241,9 +241,9 @@ module.exports = async (connection, data = {}) => { region_id: "test-region", currency_code: "usd", items: [], - }); + }) - await manager.save(cart); + await manager.save(cart) const cart2 = manager.create(Cart, { id: "test-cart-2", @@ -258,7 +258,26 @@ module.exports = async (connection, data = {}) => { currency_code: "usd", completed_at: null, items: [], - }); + }) + + const swapCart = manager.create(Cart, { + id: "swap-cart", + type: "swap", + customer_id: "some-customer", + email: "some-customer@email.com", + shipping_address: { + id: "test-shipping-address", + first_name: "lebron", + country_code: "us", + }, + region_id: "test-region", + currency_code: "usd", + completed_at: null, + items: [], + metadata: { + swap_id: "test-swap", + }, + }) const pay = manager.create(Payment, { id: "test-payment", @@ -267,13 +286,25 @@ module.exports = async (connection, data = {}) => { amount_refunded: 0, provider_id: "test-pay", data: {}, - }); + }) - await manager.save(pay); + const swapPay = manager.create(Payment, { + id: "test-swap-payment", + amount: 10000, + currency_code: "usd", + amount_refunded: 0, + provider_id: "test-pay", + data: {}, + }) - cart2.payment = pay; + await manager.save(pay) + await manager.save(swapPay) - await manager.save(cart2); + cart2.payment = pay + swapCart.payment = swapPay + + await manager.save(cart2) + await manager.save(swapCart) await manager.insert(PaymentSession, { id: "test-session", @@ -282,7 +313,16 @@ module.exports = async (connection, data = {}) => { is_selected: true, data: {}, status: "authorized", - }); + }) + + await manager.insert(PaymentSession, { + id: "test-swap-session", + cart_id: "swap-cart", + provider_id: "test-pay", + is_selected: true, + data: {}, + status: "authorized", + }) await manager.insert(ShippingMethod, { id: "test-method", @@ -290,7 +330,7 @@ module.exports = async (connection, data = {}) => { cart_id: "test-cart", price: 1000, data: {}, - }); + }) const li = manager.create(LineItem, { id: "test-item", @@ -301,8 +341,8 @@ module.exports = async (connection, data = {}) => { quantity: 1, variant_id: "test-variant", cart_id: "test-cart-2", - }); - await manager.save(li); + }) + await manager.save(li) const cart3 = manager.create(Cart, { id: "test-cart-3", @@ -317,8 +357,8 @@ module.exports = async (connection, data = {}) => { currency_code: "usd", completed_at: null, items: [], - }); - await manager.save(cart3); + }) + await manager.save(cart3) await manager.insert(ShippingMethod, { id: "test-method-2", @@ -326,7 +366,7 @@ module.exports = async (connection, data = {}) => { cart_id: "test-cart-3", price: 0, data: {}, - }); + }) const li2 = manager.create(LineItem, { id: "test-item-2", @@ -337,6 +377,6 @@ module.exports = async (connection, data = {}) => { quantity: 1, variant_id: "test-variant", cart_id: "test-cart-3", - }); - await manager.save(li2); -}; + }) + await manager.save(li2) +} diff --git a/integration-tests/api/helpers/swap-seeder.js b/integration-tests/api/helpers/swap-seeder.js index 7fcc9e6395..e19203493f 100644 --- a/integration-tests/api/helpers/swap-seeder.js +++ b/integration-tests/api/helpers/swap-seeder.js @@ -14,10 +14,10 @@ const { Swap, Cart, Return, -} = require("@medusajs/medusa"); +} = require("@medusajs/medusa") module.exports = async (connection, data = {}) => { - const manager = connection.manager; + const manager = connection.manager let orderWithSwap = manager.create(Order, { id: "order-with-swap", @@ -50,12 +50,12 @@ module.exports = async (connection, data = {}) => { ], items: [], ...data, - }); + }) - orderWithSwap = await manager.save(orderWithSwap); + orderWithSwap = await manager.save(orderWithSwap) const cart = manager.create(Cart, { - id: "test-cart", + id: "test-cart-w-swap", customer_id: "test-customer", email: "test-customer@email.com", shipping_address_id: "test-shipping-address", @@ -66,16 +66,16 @@ module.exports = async (connection, data = {}) => { swap_id: "test-swap", parent_order_id: orderWithSwap.id, }, - }); + }) - await manager.save(cart); + await manager.save(cart) const swap = manager.create(Swap, { id: "test-swap", order_id: "order-with-swap", payment_status: "captured", fulfillment_status: "fulfilled", - cart_id: "test-cart", + cart_id: "test-cart-w-swap", payment: { id: "test-payment-swap", amount: 10000, @@ -94,12 +94,12 @@ module.exports = async (connection, data = {}) => { unit_price: 9000, quantity: 1, variant_id: "test-variant-2", - cart_id: "test-cart", + cart_id: "test-cart-w-swap", }, ], - }); + }) - await manager.save(swap); + await manager.save(swap) const cartTemplate = async (cartId) => { const cart = manager.create(Cart, { @@ -176,9 +176,9 @@ module.exports = async (connection, data = {}) => { variant_id: "test-variant", order_id: orderWithSwap.id, cart_id: cart.id, - }); + }) - await manager.save(li); + await manager.save(li) const li2 = manager.create(LineItem, { id: "test-item-many", @@ -190,34 +190,33 @@ module.exports = async (connection, data = {}) => { quantity: 4, variant_id: "test-variant", order_id: orderWithSwap.id, - }); + }) - await manager.save(li2); + await manager.save(li2) const swapReturn = await manager.create(Return, { swap_id: swap.id, order_id: orderWithSwap.id, item_id: li.id, refund_amount: li.quantity * li.unit_price, - // shipping_method_id: , - }); + }) - await manager.save(swapReturn); + await manager.save(swapReturn) const return_item1 = manager.create(LineItem, { ...li, unit_price: -1 * li.unit_price, - }); + }) - await manager.save(return_item1); + await manager.save(return_item1) await manager.insert(ShippingMethod, { id: "another-test-method", shipping_option_id: "test-option", - cart_id: "test-cart", + cart_id: "test-cart-w-swap", price: 1000, data: {}, - }); + }) const swapOnSwap = manager.create(Swap, { id: "swap-on-swap", @@ -255,9 +254,9 @@ module.exports = async (connection, data = {}) => { variant_id: "test-variant", }, ], - }); + }) - await manager.save(swapOnSwap); + await manager.save(swapOnSwap) await manager.insert(ShippingMethod, { id: "test-method-swap-order", @@ -265,5 +264,5 @@ module.exports = async (connection, data = {}) => { order_id: "order-with-swap", price: 1000, data: {}, - }); -}; + }) +} diff --git a/integration-tests/api/package.json b/integration-tests/api/package.json index adc7e6d917..7abd7c9b69 100644 --- a/integration-tests/api/package.json +++ b/integration-tests/api/package.json @@ -8,15 +8,15 @@ "build": "babel src -d dist --extensions \".ts,.js\"" }, "dependencies": { - "@medusajs/medusa": "1.1.40-dev-1631178030541", - "medusa-interfaces": "1.1.21", + "@medusajs/medusa": "1.1.40-dev-1631630701835", + "medusa-interfaces": "1.1.21-dev-1631630701835", "typeorm": "^0.2.31" }, "devDependencies": { "@babel/cli": "^7.12.10", "@babel/core": "^7.12.10", "@babel/node": "^7.12.10", - "babel-preset-medusa-package": "1.1.13", + "babel-preset-medusa-package": "1.1.13-dev-1631630701835", "jest": "^26.6.3" } } diff --git a/integration-tests/api/yarn.lock b/integration-tests/api/yarn.lock index afe977164c..2ba33d481a 100644 --- a/integration-tests/api/yarn.lock +++ b/integration-tests/api/yarn.lock @@ -1223,10 +1223,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@medusajs/medusa-cli@^1.1.16": - version "1.1.16" - resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.16.tgz#3ddcd5b16388a387c430116b962bb27a933ee85e" - integrity sha512-QvE7IYkR3NFiy4seZklfX+Xs/dJannVLbKfxLQbxCV2Sso3ZtJbSJt1BpTUwDxYjOFWXyTxRRjO1kEnA1yqCBA== +"@medusajs/medusa-cli@1.1.16-dev-1631630701835": + version "1.1.16-dev-1631630701835" + resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.16-dev-1631630701835.tgz#7fcb95cb9a45e0367cc5becfff7f5d1533b46b5f" + integrity sha512-UomtR8B1lBFDb3h1y060fOcWcZi812Jwt8Kgjxqtpn+aRj6Bu7+I3WJGHBVSx4VnUBINSYbtiQMpEwqVGTCKnw== dependencies: "@babel/polyfill" "^7.8.7" "@babel/runtime" "^7.9.6" @@ -1244,8 +1244,8 @@ is-valid-path "^0.1.1" joi-objectid "^3.0.1" meant "^1.0.1" - medusa-core-utils "^0.1.27" - medusa-telemetry "^0.0.3" + medusa-core-utils "1.1.20-dev-1631630701835" + medusa-telemetry "0.0.3-dev-1631630701835" netrc-parser "^3.1.6" open "^8.0.6" ora "^5.4.1" @@ -1259,13 +1259,13 @@ winston "^3.3.3" yargs "^15.3.1" -"@medusajs/medusa@1.1.40-dev-1631178030541": - version "1.1.40-dev-1631178030541" - resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.40-dev-1631178030541.tgz#d693bdd9461e2281d387b26ad54b72c30e53218b" - integrity sha512-hhh67dltQ9dZYXBzA8FB8NrCaQ8bdALEm48t7oZ0J+GueG0fa6kcjU7Ud0uyy1vdov2zN4NOZmxGpcAw3n4alg== +"@medusajs/medusa@1.1.40-dev-1631630701835": + version "1.1.40-dev-1631630701835" + resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.40-dev-1631630701835.tgz#fa67ceda5887fd31196b3bcfd3115a9e02d68448" + integrity sha512-svPsKonuBrwRgtYod7U7ho9bN84K7N/QorMJG9+wklEO4jp6zXG+U5DQcfVAKQ00cHHe50OcnfX1ZS0kVNovYw== dependencies: "@hapi/joi" "^16.1.8" - "@medusajs/medusa-cli" "^1.1.16" + "@medusajs/medusa-cli" "1.1.16-dev-1631630701835" "@types/lodash" "^4.14.168" awilix "^4.2.3" body-parser "^1.19.0" @@ -1286,8 +1286,8 @@ joi "^17.3.0" joi-objectid "^3.0.1" jsonwebtoken "^8.5.1" - medusa-core-utils "^1.1.20" - medusa-test-utils "^1.1.23" + medusa-core-utils "1.1.20-dev-1631630701835" + medusa-test-utils "1.1.23-dev-1631630701835" morgan "^1.9.1" multer "^1.4.2" passport "^0.4.0" @@ -1932,10 +1932,10 @@ babel-preset-jest@^26.6.2: babel-plugin-jest-hoist "^26.6.2" babel-preset-current-node-syntax "^1.0.0" -babel-preset-medusa-package@1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.13.tgz#9dc4e64e08436fb7b3536cef0f363a535e126474" - integrity sha512-Q9t06udxwMnfwyx7gyxoUKiZj/dtYSSXBtQ+K4ntY1hzMhOK2hBBInuiTgnLQS1cxc4j+FN2oYYPCpspX/acaw== +babel-preset-medusa-package@1.1.13-dev-1631630701835: + version "1.1.13-dev-1631630701835" + resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.13-dev-1631630701835.tgz#5b66b3738e4904e31b2db30a6ea8e68eb0f8f641" + integrity sha512-V7sXlktlvEON7FLhxe+Y3NVe8l8DQyB5oJTryG4Bhw8y1AaUFOiQ5Vat3XuoL3qRcUSVMGL4VHw0m0O78t0PuA== dependencies: "@babel/plugin-proposal-class-properties" "^7.12.1" "@babel/plugin-proposal-decorators" "^7.12.1" @@ -2867,6 +2867,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dom-walk@^0.1.0: + version "0.1.2" + resolved "http://localhost:4873/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + domexception@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" @@ -3588,6 +3593,14 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" +global@^4.4.0: + version "4.4.0" + resolved "http://localhost:4873/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -5091,50 +5104,43 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -medusa-core-utils@^0.1.27: - version "0.1.39" - resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-0.1.39.tgz#d57816c9bd43f9a92883650c1e66add1665291df" - integrity sha512-R8+U1ile7if+nR6Cjh5exunx0ETV0OfkWUUBUpz1KmHSDv0V0CcvQqU9lcZesPFDEbu3Y2iEjsCqidVA4nG2nQ== - dependencies: - "@hapi/joi" "^16.1.8" - joi-objectid "^3.0.1" - -medusa-core-utils@^1.1.20: - version "1.1.20" - resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.20.tgz#676c0dc863a206b80cc53299a984c532d07df65f" - integrity sha512-gf+/L5eeqHea3xgjwD7YZEzfUGlxbjfvaeiiGWi3Wfu0dLa+G1B4S0TsX+upR+oVeWPmk66VMqWC80h3e4csqw== +medusa-core-utils@1.1.20-dev-1631630701835: + version "1.1.20-dev-1631630701835" + resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.20-dev-1631630701835.tgz#1fa7ccd2551b7891127d4f07f708029c585f4ea8" + integrity sha512-KKBo6W1QI47Ig3KMV4UXQnQN5JilMfjR6Cx7hDNj4frJoNiWa/YKDYqUr6SmY2+iJtKetnLkrKaPsDyyhZrxcw== dependencies: joi "^17.3.0" joi-objectid "^3.0.1" -medusa-interfaces@1.1.21: - version "1.1.21" - resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-1.1.21.tgz#ca86808e939b7ecc21a6d316008a4e41f163619f" - integrity sha512-mlHHoMIOFBc+Exs+uVIQsfeEP2C1Pi6IZHcpbm7O00tYBdQdqRjJre9+Z/I/Z37wt5IwA28/TIoVkYG71iQYxw== +medusa-interfaces@1.1.21-dev-1631630701835: + version "1.1.21-dev-1631630701835" + resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.21-dev-1631630701835.tgz#af29b2ef0c987bded1b2d295ac6cf39880af551e" + integrity sha512-rTASRjOdcS3J9fP95p9vJzCpatMpUhTum5ddfAA0s42pZx2gsPlf1f+rUSNz5QfeC5RdIEzRfOmAGfvMpAbYGw== dependencies: - medusa-core-utils "^1.1.20" + medusa-core-utils "1.1.20-dev-1631630701835" -medusa-telemetry@^0.0.3: - version "0.0.3" - resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.3.tgz#c11e5e0f3cc969f3eaee41d1c24f78a5c0715362" - integrity sha512-Qb/sgOwO8t2Sjjo4nKyBa6hKZ/SjniT4eEWenygEaJDqXZhfogVYGhWc5gn4tLlFFNEHXzDTlrqX2LvzfEJWIw== +medusa-telemetry@0.0.3-dev-1631630701835: + version "0.0.3-dev-1631630701835" + resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.3-dev-1631630701835.tgz#d56c01d261fa30ccedc6d9976971b9744b9d8c0f" + integrity sha512-FS1L1DOIOSdRZgeIQWaM5nhFG5NtbnC/Pntfac51vQxLkzFuHy7ZEtg11CXKE+x6NWlqT1rqqgxq0EabFzEZzw== dependencies: axios "^0.21.1" axios-retry "^3.1.9" boxen "^5.0.1" ci-info "^3.2.0" configstore "5.0.1" + global "^4.4.0" is-docker "^2.2.1" remove-trailing-slash "^0.1.1" uuid "^8.3.2" -medusa-test-utils@^1.1.23: - version "1.1.23" - resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.23.tgz#e8380df499979cd0b97a5bb87779662f4da9d722" - integrity sha512-okyUgB4t7bqDieE0XO+HkbVVemn6hE1tTAtF9PXRi2igmKmcnyW/Ljk3lqrKYVhjei4z3Z/b+K2b0oNwhopbGQ== +medusa-test-utils@1.1.23-dev-1631630701835: + version "1.1.23-dev-1631630701835" + resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.23-dev-1631630701835.tgz#8995d636caf2dea9ebb184f1e15b0c364c4d1b93" + integrity sha512-A8xRL+sZS22qXZSHpVfdV8f/egZxXs4iExRO2xUkTP6I/OgMhFBSg6nEd/DXVdVfpsHZCDEv8PA3ewaeAkoYhQ== dependencies: "@babel/plugin-transform-classes" "^7.9.5" - medusa-core-utils "^1.1.20" + medusa-core-utils "1.1.20-dev-1631630701835" randomatic "^3.1.1" merge-descriptors@1.0.1: @@ -5218,6 +5224,13 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-document@^2.19.0: + version "2.19.0" + resolved "http://localhost:4873/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= + dependencies: + dom-walk "^0.1.0" + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -6060,6 +6073,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.10: + version "0.11.10" + resolved "http://localhost:4873/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + promise.prototype.finally@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" diff --git a/packages/medusa/src/api/routes/admin/orders/create-swap.js b/packages/medusa/src/api/routes/admin/orders/create-swap.js index 25ad45604e..16374a9a20 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-swap.js +++ b/packages/medusa/src/api/routes/admin/orders/create-swap.js @@ -48,6 +48,9 @@ import { defaultFields, defaultRelations } from "./" * no_notification: * description: If set to true no notification will be send related to this Swap. * type: boolean + * allow_backorder: + * description: If true, swaps can be completed with items out of stock + * type: boolean * tags: * - Order * responses: @@ -83,6 +86,7 @@ export default async (req, res) => { quantity: Validator.number().required(), }), no_notification: Validator.boolean().optional(), + allow_backorder: Validator.boolean().default(true), }) const { value, error } = schema.validate(req.body) @@ -141,6 +145,7 @@ export default async (req, res) => { { idempotency_key: idempotencyKey.idempotency_key, no_notification: value.no_notification, + allow_backorder: value.allow_backorder, } ) diff --git a/packages/medusa/src/api/routes/store/carts/complete-cart.js b/packages/medusa/src/api/routes/store/carts/complete-cart.js index df72d086fa..5ed859b16b 100644 --- a/packages/medusa/src/api/routes/store/carts/complete-cart.js +++ b/packages/medusa/src/api/routes/store/carts/complete-cart.js @@ -138,18 +138,36 @@ export default async (req, res) => { // If cart is part of swap, we register swap as complete switch (cart.type) { case "swap": { - const swapId = cart.metadata?.swap_id - let swap = await swapService - .withTransaction(manager) - .registerCartCompletion(swapId) + try { + const swapId = cart.metadata?.swap_id + let swap = await swapService + .withTransaction(manager) + .registerCartCompletion(swapId) - swap = await swapService - .withTransaction(manager) - .retrieve(swap.id, { relations: ["shipping_address"] }) + swap = await swapService + .withTransaction(manager) + .retrieve(swap.id, { relations: ["shipping_address"] }) - return { - response_code: 200, - response_body: { data: swap, type: "swap" }, + return { + response_code: 200, + response_body: { data: swap, type: "swap" }, + } + } catch (error) { + if ( + error && + error.code === MedusaError.Codes.INSUFFICIENT_INVENTORY + ) { + return { + response_code: 409, + response_body: { + message: error.message, + type: error.type, + code: error.code, + }, + } + } else { + throw error + } } } // case "payment_link": diff --git a/packages/medusa/src/migrations/1630505790603-allow_backorder_swaps.ts b/packages/medusa/src/migrations/1630505790603-allow_backorder_swaps.ts new file mode 100644 index 0000000000..1ecbfd4026 --- /dev/null +++ b/packages/medusa/src/migrations/1630505790603-allow_backorder_swaps.ts @@ -0,0 +1,24 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class allowBackorderSwaps1630505790603 implements MigrationInterface { + name = 'allowBackorderSwaps1630505790603' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "swap" ADD "allow_backorder" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "cart" ADD "payment_authorized_at" TIMESTAMP WITH TIME ZONE`); + await queryRunner.query(`ALTER TYPE "swap_payment_status_enum" RENAME TO "swap_payment_status_enum_old"`); + await queryRunner.query(`CREATE TYPE "swap_payment_status_enum" AS ENUM('not_paid', 'awaiting', 'captured', 'confirmed', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`); + await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum" USING "payment_status"::"text"::"swap_payment_status_enum"`); + await queryRunner.query(`DROP TYPE "swap_payment_status_enum_old"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TYPE "swap_payment_status_enum_old" AS ENUM('not_paid', 'awaiting', 'captured', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`); + await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum_old" USING "payment_status"::"text"::"swap_payment_status_enum_old"`); + await queryRunner.query(`DROP TYPE "swap_payment_status_enum"`); + await queryRunner.query(`ALTER TYPE "swap_payment_status_enum_old" RENAME TO "swap_payment_status_enum"`); + await queryRunner.query(`ALTER TABLE "cart" DROP COLUMN "payment_authorized_at"`); + await queryRunner.query(`ALTER TABLE "swap" DROP COLUMN "allow_backorder"`); + } + +} diff --git a/packages/medusa/src/models/cart.ts b/packages/medusa/src/models/cart.ts index 1786681024..d6c251ad40 100644 --- a/packages/medusa/src/models/cart.ts +++ b/packages/medusa/src/models/cart.ts @@ -230,6 +230,9 @@ export class Cart { @Column({ type: resolveDbType("timestamptz"), nullable: true }) completed_at: Date + @Column({ type: resolveDbType("timestamptz"), nullable: true }) + payment_authorized_at: Date + @CreateDateColumn({ type: resolveDbType("timestamptz") }) created_at: Date diff --git a/packages/medusa/src/models/swap.ts b/packages/medusa/src/models/swap.ts index 9628c81732..cb44a825cd 100644 --- a/packages/medusa/src/models/swap.ts +++ b/packages/medusa/src/models/swap.ts @@ -38,6 +38,7 @@ export enum PaymentStatus { NOT_PAID = "not_paid", AWAITING = "awaiting", CAPTURED = "captured", + CONFIRMED = "confirmed", CANCELED = "canceled", DIFFERENCE_REFUNDED = "difference_refunded", PARTIALLY_REFUNDED = "partially_refunded", @@ -137,6 +138,9 @@ export class Swap { @Column({ type: "boolean", nullable: true }) no_notification: Boolean + @Column({ type: "boolean", default: false }) + allow_backorder: Boolean + @DbAwareColumn({ type: "jsonb", nullable: true }) metadata: any @@ -219,6 +223,9 @@ export class Swap { * cart_id: * description: "The id of the Cart that the Customer will use to confirm the Swap." * type: string + * allow_backorder: + * description: "If true, swaps can be completed with items out of stock" + * type: boolean * confirmed_at: * description: "The date with timezone at which the Swap was confirmed by the Customer." * type: string diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index 5da04f644e..76db532917 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -203,6 +203,7 @@ describe("OrderService", () => { } orderService.cartService_.retrieve = jest.fn(() => Promise.resolve(cart)) + orderService.cartService_.update = jest.fn(() => Promise.resolve()) await orderService.createFromCart("cart_id") const order = { @@ -305,6 +306,7 @@ describe("OrderService", () => { orderService.cartService_.retrieve = () => { return Promise.resolve(cart) } + orderService.cartService_.update = () => Promise.resolve() await orderService.createFromCart("cart_id") const order = { @@ -453,6 +455,7 @@ describe("OrderService", () => { total: 100, } orderService.cartService_.retrieve = () => Promise.resolve(cart) + orderService.cartService_.update = () => Promise.resolve() const res = orderService.createFromCart(cart) await expect(res).rejects.toThrow( "Variant with id: variant-1 does not have the required inventory" diff --git a/packages/medusa/src/services/__tests__/swap.js b/packages/medusa/src/services/__tests__/swap.js index ebf9c2fd47..9cc4088174 100644 --- a/packages/medusa/src/services/__tests__/swap.js +++ b/packages/medusa/src/services/__tests__/swap.js @@ -741,6 +741,13 @@ describe("SwapService", () => { }, } + const cartService = { + update: jest.fn(), + withTransaction: function() { + return this + }, + } + const swapRepo = MockRepository({ findOneWithRelations: () => Promise.resolve(existing), }) @@ -752,6 +759,7 @@ describe("SwapService", () => { lineItemService, eventBusService, fulfillmentService, + cartService, }) it("creates a shipment", async () => { @@ -831,6 +839,15 @@ describe("SwapService", () => { }, } + const cartService = { + update: () => { + return Promise.resolve() + }, + withTransaction: function() { + return this + }, + } + const paymentProviderService = { getStatus: jest.fn(() => { return Promise.resolve("authorized") @@ -838,6 +855,9 @@ describe("SwapService", () => { updatePayment: jest.fn(() => { return Promise.resolve() }), + cancelPayment: jest.fn(() => { + return Promise.resolve() + }), withTransaction: function() { return this }, @@ -872,6 +892,7 @@ describe("SwapService", () => { eventBusService, swapRepository: swapRepo, totalsService, + cartService, paymentProviderService, eventBusService, shippingOptionService, @@ -933,6 +954,7 @@ describe("SwapService", () => { eventBusService, swapRepository: swapRepo, totalsService, + cartService, paymentProviderService, eventBusService, shippingOptionService, diff --git a/packages/medusa/src/services/cart.js b/packages/medusa/src/services/cart.js index 5052d57cce..1dfba8d51c 100644 --- a/packages/medusa/src/services/cart.js +++ b/packages/medusa/src/services/cart.js @@ -682,6 +682,14 @@ class CartService extends BaseService { } } + if ("completed_at" in update) { + cart.completed_at = update.completed_at + } + + if ("payment_authorized_at" in update) { + cart.payment_authorized_at = update.payment_authorized_at + } + const result = await cartRepo.save(cart) if ("email" in update || "customer_id" in update) { @@ -1027,7 +1035,7 @@ class CartService extends BaseService { // If cart total is 0, we don't perform anything payment related if (cart.total <= 0) { - cart.completed_at = new Date() + cart.payment_authorized_at = new Date() return cartRepository.save(cart) } @@ -1046,7 +1054,7 @@ class CartService extends BaseService { .createPayment(freshCart) freshCart.payment = payment - freshCart.completed_at = new Date() + freshCart.payment_authorized_at = new Date() } const updated = await cartRepository.save(freshCart) @@ -1352,7 +1360,7 @@ class CartService extends BaseService { * @return {Promise} the result of the update operation */ async setRegion_(cart, regionId, countryCode) { - if (cart.completed_at) { + if (cart.completed_at || cart.payment_authorized_at) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, "Cannot change the region of a completed cart" @@ -1494,6 +1502,13 @@ class CartService extends BaseService { ) } + if (cart.payment_authorized_at) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Can't delete a cart with an authorized payment" + ) + } + const cartRepo = manager.getCustomRepository(this.cartRepository_) return cartRepo.remove(cartId) }) diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index 374689742e..86d43f4d78 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -477,6 +477,9 @@ class OrderService extends BaseService { .withTransaction(manager) .cancelPayment(payment) } + await this.cartService_ + .withTransaction(manager) + .update(cart.id, { payment_authorized_at: null }) throw err } } @@ -595,6 +598,10 @@ class OrderService extends BaseService { no_notification: result.no_notification, }) + await this.cartService_ + .withTransaction(manager) + .update(cart.id, { completed_at: new Date() }) + return result }) } diff --git a/packages/medusa/src/services/payment-provider.js b/packages/medusa/src/services/payment-provider.js index 354985cb17..efbfff9f3d 100644 --- a/packages/medusa/src/services/payment-provider.js +++ b/packages/medusa/src/services/payment-provider.js @@ -328,7 +328,7 @@ class PaymentProviderService extends BaseService { payment.canceled_at = now.toISOString() const paymentRepo = manager.getCustomRepository(this.paymentRepository_) - return paymentRepo.save(payment) + return await paymentRepo.save(payment) }) } diff --git a/packages/medusa/src/services/swap.js b/packages/medusa/src/services/swap.js index 5559d04e35..886a6f70c0 100644 --- a/packages/medusa/src/services/swap.js +++ b/packages/medusa/src/services/swap.js @@ -665,20 +665,33 @@ class SwapService extends BaseService { } const cart = swap.cart + const { payment } = cart const items = swap.cart.items - for (const item of items) { - await this.inventoryService_ - .withTransaction(manager) - .confirmInventory(item.variant_id, item.quantity) + if (!swap.allow_backorder) { + for (const item of items) { + try { + await this.inventoryService_ + .withTransaction(manager) + .confirmInventory(item.variant_id, item.quantity) + } catch (err) { + if (payment) { + await this.paymentProviderService_ + .withTransaction(manager) + .cancelPayment(payment) + } + await this.cartService_ + .withTransaction(manager) + .update(cart.id, { payment_authorized_at: null }) + throw err + } + } } const total = await this.totalsService_.getTotal(cart) if (total > 0) { - const { payment } = cart - if (!payment) { throw new MedusaError( MedusaError.Types.INVALID_ARGUMENT, @@ -717,7 +730,7 @@ class SwapService extends BaseService { swap.shipping_address_id = cart.shipping_address_id swap.shipping_methods = cart.shipping_methods swap.confirmed_at = now.toISOString() - swap.payment_status = total === 0 ? "difference_refunded" : "awaiting" + swap.payment_status = total === 0 ? "confirmed" : "awaiting" const swapRepo = manager.getCustomRepository(this.swapRepository_) const result = await swapRepo.save(swap) @@ -737,6 +750,10 @@ class SwapService extends BaseService { no_notification: swap.no_notification, }) + await this.cartService_ + .withTransaction(manager) + .update(cart.id, { completed_at: new Date() }) + return result }) }