Completes Cart Service and store/carts endpoints (#18)
Completes Cart Service to allow shopping and checkout flows.
This commit is contained in:
1 parent
8e7b66a205
commit
51aaf5105c
31 files changed
+2091
-45
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
POST /carts
|
||||||
|
GET /carts/:id
|
||||||
|
POST /carts/:id
|
||||||
|
|
||||||
|
POST /carts/:id/line-items
|
||||||
|
POST /carts/:id/line-items/:line_id
|
||||||
|
|
||||||
|
GET /carts/:id/shipping-options
|
||||||
|
POST /carts/:id/payment-sessions
|
||||||
@@ -50,7 +50,7 @@ removes the line item with the given line id by calling `removeLineItem`.
|
|||||||
### Initializing the checkout
|
### Initializing the checkout
|
||||||
When the customer is ready to check out they will reach your checkout page. At
|
When the customer is ready to check out they will reach your checkout page. At
|
||||||
this point you want to display which payment and shipping options are offered.
|
this point you want to display which payment and shipping options are offered.
|
||||||
`POST /cart/payment-providers` and `POST /cart/fulfillment-providers` will
|
`POST /cart/payment-sessions` and `POST /cart/fulfillment-sessions` will
|
||||||
initialize payment sessions and shipping methods offered by fulfillment
|
initialize payment sessions and shipping methods offered by fulfillment
|
||||||
providers. The payment sessions will typically have to be updated if any further
|
providers. The payment sessions will typically have to be updated if any further
|
||||||
changes to the cart total happens (shipping fee, promo codes, user exits
|
changes to the cart total happens (shipping fee, promo codes, user exits
|
||||||
@@ -93,3 +93,89 @@ function `setPaymentMethod` which will fetch the cart, search the
|
|||||||
check that the payment session is authorized. When the authorization is verified
|
check that the payment session is authorized. When the authorization is verified
|
||||||
the payment method is set and the controller can safely call the Order service
|
the payment method is set and the controller can safely call the Order service
|
||||||
function `create`.
|
function `create`.
|
||||||
|
|
||||||
|
|
||||||
|
### How do payment sessions work?
|
||||||
|
|
||||||
|
When the customer first enters the checkout page you should initialize payment
|
||||||
|
sessions for each of the possible payment providers. This is done with a single
|
||||||
|
call to `POST /cart/payment-sessions`. Calls to `POST /cart/payment-sessions`
|
||||||
|
will either create payment sessions for each payment provider or if the payment
|
||||||
|
sessions have already been initialized ensure that the sessions are up to date (
|
||||||
|
i.e. that the cart amount corresponds to the payment sessions' amounts). When
|
||||||
|
the customer reaches the payment part of the checkout (or alternatively when she
|
||||||
|
decides to use one of the payment providers as a checkout provider) the payment
|
||||||
|
method will be saved once authorized.
|
||||||
|
|
||||||
|
### How do fulfillment sessions work?
|
||||||
|
|
||||||
|
When the customer first enters the checkout page, fulfillment sessions should be
|
||||||
|
initialized. The fulfillment session is responsible for fetching shipping
|
||||||
|
options with a fulfillment provider. E.g. your store has an integration with
|
||||||
|
your 3PL as a fulfillment provider. The 3PL has 4 shipping options: standard,
|
||||||
|
express and fragile shipping as well as a parcel shop service where orders are
|
||||||
|
delivered to a local store.
|
||||||
|
|
||||||
|
The store operator will have set up which shipping options are available in the
|
||||||
|
customer's region. I.e. the store operator may have created a shipping option
|
||||||
|
called Free Shipping, which uses the "Standard Shipping" method from the 3PL
|
||||||
|
integration, and which is free when the order value is above 100 USD. The store
|
||||||
|
operator may have also created an Express Shipping option which uses the
|
||||||
|
"Express Shipping" method from the 3PL integration and which costs 20 USD.
|
||||||
|
The store operator has also created a Fragile Shipping option which uses the
|
||||||
|
"Fragile Shipping" method from the 3PL integration and which has variable
|
||||||
|
pricing depending on the size of the shipment. The variable pricing is
|
||||||
|
calculated by the integration depending on cart. Finally, the store operator has
|
||||||
|
defined a parcel shop option, which uses the 3PL's parcel shop shipping method.
|
||||||
|
The customer needs to provide the ID of the local store that she wants her order
|
||||||
|
delivered to and the shipping method therefore takes some additional input to be
|
||||||
|
a valid shipping method for an order.
|
||||||
|
|
||||||
|
When the customer enters the checkout page the `POST /cart/shipping-options`
|
||||||
|
call will fetch each of the shipping options that the store operator has set up.
|
||||||
|
Extending the above example, an array of shipping options would be stored in the
|
||||||
|
cart in the format:
|
||||||
|
|
||||||
|
```
|
||||||
|
[
|
||||||
|
{
|
||||||
|
_id: [some-id],
|
||||||
|
provider_id: "3pl_integration",
|
||||||
|
name: "Free Shipping",
|
||||||
|
price: 0,
|
||||||
|
data: {
|
||||||
|
// This will contain data specific to the shipping method, i.e. the
|
||||||
|
// id that the 3PL needs in order to process the order with this shipping
|
||||||
|
// method
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: [some-id],
|
||||||
|
provider_id: "3pl_integration",
|
||||||
|
name: "Express Shipping",
|
||||||
|
price: 20,
|
||||||
|
data: {
|
||||||
|
// This will contain data specific to the shipping method, i.e. the
|
||||||
|
// id that the 3PL needs in order to process the order with this shipping
|
||||||
|
// method
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: [some-id],
|
||||||
|
provider_id: "3pl_integration",
|
||||||
|
name: "Fragile Shipping",
|
||||||
|
price: 120, // Calculated from the cart
|
||||||
|
data: {
|
||||||
|
// This will contain data specific to the shipping method, i.e. the
|
||||||
|
// id that the 3PL needs in order to process the order with this shipping
|
||||||
|
// method
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
If the customer changes her cart, all shipping options will be recalculated. For
|
||||||
|
example, if the customer removes something from the cart so that they no longer
|
||||||
|
qualify for free shipping, the free shipping method will be removed at the same
|
||||||
|
time the fragile shipping method's price will be updated.
|
||||||
@@ -7,6 +7,7 @@ export default () => {
|
|||||||
|
|
||||||
let statusCode = 500
|
let statusCode = 500
|
||||||
switch (err.name) {
|
switch (err.name) {
|
||||||
|
case MedusaError.Types.NOT_ALLOWED:
|
||||||
case MedusaError.Types.INVALID_DATA:
|
case MedusaError.Types.INVALID_DATA:
|
||||||
statusCode = 400
|
statusCode = 400
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
|
import { LineItemServiceMock } from "../../../../../services/__mocks__/line-item"
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id/shipping-methods", () => {
|
||||||
|
describe("successfully adds a shipping method", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cartId = IdMap.getId("fr-cart")
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${cartId}/shipping-methods`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
option_id: IdMap.getId("freeShipping"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService retrieveShippingOption", () => {
|
||||||
|
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("fr-cart"),
|
||||||
|
IdMap.getId("freeShipping")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService addShipping", () => {
|
||||||
|
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("fr-cart"),
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("fr-cart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("successfully adds a shipping method with additional data", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cartId = IdMap.getId("fr-cart")
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${cartId}/shipping-methods`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
option_id: IdMap.getId("freeShipping"),
|
||||||
|
data: {
|
||||||
|
extra_id: "id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService retrieveShippingOption", () => {
|
||||||
|
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("fr-cart"),
|
||||||
|
IdMap.getId("freeShipping")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService addShipping", () => {
|
||||||
|
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("fr-cart"),
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
data: {
|
||||||
|
extra_id: "id",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("fr-cart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("additional data without overwriting", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cartId = IdMap.getId("emptyCart")
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${cartId}/shipping-methods`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
option_id: IdMap.getId("withData"),
|
||||||
|
data: {
|
||||||
|
extra_id: "id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService retrieveShippingOption", () => {
|
||||||
|
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("emptyCart"),
|
||||||
|
IdMap.getId("withData")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService addShipping", () => {
|
||||||
|
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("emptyCart"),
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("withData"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
data: {
|
||||||
|
extra_id: "id",
|
||||||
|
some_data: "yes",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -26,12 +26,13 @@ describe("POST /store/carts", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 201", () => {
|
it("returns 200", () => {
|
||||||
expect(subject.status).toEqual(201)
|
expect(subject.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns the cart", () => {
|
it("returns the cart", () => {
|
||||||
expect(subject.body._id).toEqual(IdMap.getId("regionCart"))
|
expect(subject.body._id).toEqual(IdMap.getId("regionCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -97,8 +98,8 @@ describe("POST /store/carts", () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 201", () => {
|
it("returns 200", () => {
|
||||||
expect(subject.status).toEqual(201)
|
expect(subject.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls line item generate", () => {
|
it("calls line item generate", () => {
|
||||||
@@ -117,6 +118,7 @@ describe("POST /store/carts", () => {
|
|||||||
|
|
||||||
it("returns cart", () => {
|
it("returns cart", () => {
|
||||||
expect(subject.body._id).toEqual(IdMap.getId("regionCart"))
|
expect(subject.body._id).toEqual(IdMap.getId("regionCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
|
import { LineItemServiceMock } from "../../../../../services/__mocks__/line-item"
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id", () => {
|
||||||
|
describe("successfully creates a line item", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${IdMap.getId("emptyCart")}/line-items`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
variant_id: IdMap.getId("testVariant"),
|
||||||
|
quantity: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService create", () => {
|
||||||
|
expect(CartServiceMock.addLineItem).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls LineItemService generate", () => {
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("testVariant"),
|
||||||
|
3,
|
||||||
|
IdMap.getId("testRegion")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("handles unsuccessful line item generation", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${IdMap.getId("emptyCart")}/line-items`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
variant_id: IdMap.getId("fail"),
|
||||||
|
quantity: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls LineItemService generate", () => {
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("fail"),
|
||||||
|
3,
|
||||||
|
IdMap.getId("testRegion")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns error", () => {
|
||||||
|
expect(subject.body.message).toEqual("Doesn't exist")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id/payment-sessions", () => {
|
||||||
|
describe("creates payment sessions", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${IdMap.getId("emptyCart")}/payment-sessions`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls Cart service set payment sessions", () => {
|
||||||
|
expect(CartServiceMock.setPaymentSessions).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id/shipping-options", () => {
|
||||||
|
describe("creates shipping options", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${IdMap.getId("emptyCart")}/shipping-options`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls Cart service set shipping options", () => {
|
||||||
|
expect(CartServiceMock.setShippingOptions).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -23,6 +23,7 @@ describe("GET /store/carts", () => {
|
|||||||
|
|
||||||
it("returns products", () => {
|
it("returns products", () => {
|
||||||
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ describe("POST /store/carts/:id", () => {
|
|||||||
|
|
||||||
it("returns cart", () => {
|
it("returns cart", () => {
|
||||||
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
|
import { LineItemServiceMock } from "../../../../../services/__mocks__/line-item"
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||||
|
describe("successfully updates a line item", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cartId = IdMap.getId("emptyCart")
|
||||||
|
const lineId = IdMap.getId("existingLine")
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${cartId}/line-items/${lineId}`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
variant_id: IdMap.getId("can-cover"),
|
||||||
|
quantity: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService create", () => {
|
||||||
|
expect(CartServiceMock.updateLineItem).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls LineItemService generate", () => {
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("can-cover"),
|
||||||
|
3,
|
||||||
|
IdMap.getId("testRegion")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("handles unsuccessful line item generation", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cartId = IdMap.getId("emptyCart")
|
||||||
|
const lineId = IdMap.getId("existingLine")
|
||||||
|
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/carts/${cartId}/line-items/${lineId}`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
variant_id: IdMap.getId("fail"),
|
||||||
|
quantity: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls LineItemService generate", () => {
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("fail"),
|
||||||
|
3,
|
||||||
|
IdMap.getId("testRegion")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns error", () => {
|
||||||
|
expect(subject.body.message).toEqual("Doesn't exist")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
|
import { LineItemServiceMock } from "../../../../../services/__mocks__/line-item"
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id/payment-method", () => {
|
||||||
|
describe("successfully sets the payment method", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cartId = IdMap.getId("cartWithPaySessions")
|
||||||
|
subject = await request("POST", `/store/carts/${cartId}/payment-method`, {
|
||||||
|
payload: {
|
||||||
|
provider_id: "default_provider",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService retrievePaymentSession", () => {
|
||||||
|
expect(CartServiceMock.retrievePaymentSession).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrievePaymentSession).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("cartWithPaySessions"),
|
||||||
|
"default_provider"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService setPaymentMethod", () => {
|
||||||
|
expect(CartServiceMock.setPaymentMethod).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.setPaymentMethod).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("cartWithPaySessions"),
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
money_id: "success",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("cartWithPaySessions"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails when pay session not authorized", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cartId = IdMap.getId("cartWithPaySessions")
|
||||||
|
subject = await request("POST", `/store/carts/${cartId}/payment-method`, {
|
||||||
|
payload: {
|
||||||
|
provider_id: "nono",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService retrievePaymentSession", () => {
|
||||||
|
expect(CartServiceMock.retrievePaymentSession).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrievePaymentSession).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("cartWithPaySessions"),
|
||||||
|
"nono"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CartService setPaymentMethod", () => {
|
||||||
|
expect(CartServiceMock.setPaymentMethod).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.setPaymentMethod).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("cartWithPaySessions"),
|
||||||
|
{
|
||||||
|
provider_id: "nono",
|
||||||
|
data: {
|
||||||
|
money_id: "fail",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the cart", () => {
|
||||||
|
expect(subject.body.message).toEqual("Not allowed")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import _ from "lodash"
|
||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
option_id: Validator.string().required(),
|
||||||
|
data: Validator.object().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
const method = await cartService.retrieveShippingOption(id, value.option_id)
|
||||||
|
|
||||||
|
// If the option accepts additional data this will be added
|
||||||
|
if (!_.isEmpty(value.data)) {
|
||||||
|
method.data = {
|
||||||
|
...method.data,
|
||||||
|
...value.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await cartService.addShippingMethod(id, method)
|
||||||
|
|
||||||
|
let cart = await cartService.retrieve(id)
|
||||||
|
cart = await cartService.decorate(cart)
|
||||||
|
|
||||||
|
res.status(200).json(cart)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,8 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cart = await cartService.retrieve(cart._id)
|
cart = await cartService.retrieve(cart._id)
|
||||||
res.status(201).json(cart)
|
cart = await cartService.decorate(cart)
|
||||||
|
res.status(200).json(cart)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
variant_id: Validator.string().required(),
|
||||||
|
quantity: Validator.number().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lineItemService = req.scope.resolve("lineItemService")
|
||||||
|
const cartService = req.scope.resolve("cartService")
|
||||||
|
let cart = await cartService.retrieve(id)
|
||||||
|
|
||||||
|
const lineItem = await lineItemService.generate(
|
||||||
|
value.variant_id,
|
||||||
|
value.quantity,
|
||||||
|
cart.region_id
|
||||||
|
)
|
||||||
|
await cartService.addLineItem(cart._id, lineItem)
|
||||||
|
|
||||||
|
cart = await cartService.retrieve(cart._id)
|
||||||
|
cart = await cartService.decorate(cart)
|
||||||
|
|
||||||
|
res.status(200).json(cart)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
// Ask the cart service to set payment sessions
|
||||||
|
await cartService.setPaymentSessions(id)
|
||||||
|
|
||||||
|
// return the updated cart
|
||||||
|
let cart = await cartService.retrieve(id)
|
||||||
|
cart = await cartService.decorate(cart)
|
||||||
|
|
||||||
|
res.status(200).json(cart)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
// Ask the cart service to set payment sessions
|
||||||
|
await cartService.setShippingOptions(id)
|
||||||
|
|
||||||
|
// return the updated cart
|
||||||
|
let cart = await cartService.retrieve(id)
|
||||||
|
cart = await cartService.decorate(cart)
|
||||||
|
res.status(200).json(cart)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,14 @@ export default async (req, res) => {
|
|||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
|
||||||
const cartService = req.scope.resolve("cartService")
|
const cartService = req.scope.resolve("cartService")
|
||||||
const cart = await cartService.retrieve(id)
|
let cart = await cartService.retrieve(id)
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
res.sendStatus(404)
|
res.sendStatus(404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cart = await cartService.decorate(cart)
|
||||||
|
|
||||||
res.json(cart)
|
res.json(cart)
|
||||||
}
|
}
|
||||||
@@ -11,5 +11,35 @@ export default app => {
|
|||||||
route.post("/", middlewares.wrap(require("./create-cart").default))
|
route.post("/", middlewares.wrap(require("./create-cart").default))
|
||||||
route.post("/:id", middlewares.wrap(require("./update-cart").default))
|
route.post("/:id", middlewares.wrap(require("./update-cart").default))
|
||||||
|
|
||||||
|
// Line items
|
||||||
|
route.post(
|
||||||
|
"/:id/line-items",
|
||||||
|
middlewares.wrap(require("./create-line-item").default)
|
||||||
|
)
|
||||||
|
route.post(
|
||||||
|
"/:id/line-items/:line_id",
|
||||||
|
middlewares.wrap(require("./update-line-item").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Payment sessions
|
||||||
|
route.post(
|
||||||
|
"/:id/payment-sessions",
|
||||||
|
middlewares.wrap(require("./create-payment-sessions").default)
|
||||||
|
)
|
||||||
|
route.post(
|
||||||
|
"/:id/payment-method",
|
||||||
|
middlewares.wrap(require("./update-payment-method").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shipping Options
|
||||||
|
route.post(
|
||||||
|
"/:id/shipping-options",
|
||||||
|
middlewares.wrap(require("./create-shipping-options").default)
|
||||||
|
)
|
||||||
|
route.post(
|
||||||
|
"/:id/shipping-methods",
|
||||||
|
middlewares.wrap(require("./add-shipping-method").default)
|
||||||
|
)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,8 @@ export default async (req, res) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCart = await cartService.retrieve(id)
|
let newCart = await cartService.retrieve(id)
|
||||||
|
newCart = await cartService.decorate(newCart)
|
||||||
res.json(newCart)
|
res.json(newCart)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id, line_id } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
variant_id: Validator.objectId().required(),
|
||||||
|
quantity: Validator.number().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lineItemService = req.scope.resolve("lineItemService")
|
||||||
|
const cartService = req.scope.resolve("cartService")
|
||||||
|
let cart = await cartService.retrieve(id)
|
||||||
|
|
||||||
|
const lineItem = await lineItemService.generate(
|
||||||
|
value.variant_id,
|
||||||
|
value.quantity,
|
||||||
|
cart.region_id
|
||||||
|
)
|
||||||
|
|
||||||
|
await cartService.updateLineItem(cart._id, line_id, lineItem)
|
||||||
|
|
||||||
|
cart = await cartService.retrieve(cart._id)
|
||||||
|
cart = await cartService.decorate(cart)
|
||||||
|
|
||||||
|
res.status(200).json(cart)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
provider_id: Validator.string().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
const session = await cartService.retrievePaymentSession(
|
||||||
|
id,
|
||||||
|
value.provider_id
|
||||||
|
)
|
||||||
|
await cartService.setPaymentMethod(id, session)
|
||||||
|
|
||||||
|
let cart = await cartService.retrieve(id)
|
||||||
|
cart = await cartService.decorate(cart)
|
||||||
|
|
||||||
|
res.status(200).json(cart)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,117 @@ export const carts = {
|
|||||||
title: "test",
|
title: "test",
|
||||||
region_id: IdMap.getId("testRegion"),
|
region_id: IdMap.getId("testRegion"),
|
||||||
items: [],
|
items: [],
|
||||||
shippingAddress: {},
|
shipping_address: {},
|
||||||
billingAddress: {},
|
billing_address: {},
|
||||||
|
discounts: [],
|
||||||
|
customer_id: "",
|
||||||
|
},
|
||||||
|
withShippingOptions: {
|
||||||
|
_id: IdMap.getId("withShippingOptions"),
|
||||||
|
title: "test",
|
||||||
|
region_id: IdMap.getId("region-france"),
|
||||||
|
items: [],
|
||||||
|
shipping_options: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
name: "Free Shipping",
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
price: 10,
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("expensiveShipping"),
|
||||||
|
name: "Expensive Shipping",
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
price: 100,
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_address: {},
|
||||||
|
billing_address: {},
|
||||||
|
discounts: [],
|
||||||
|
customer_id: "",
|
||||||
|
},
|
||||||
|
cartWithPaySessionsDifRegion: {
|
||||||
|
_id: IdMap.getId("cartWithPaySessionsDifRegion"),
|
||||||
|
region_id: IdMap.getId("region-france"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider_id: "unregistered",
|
||||||
|
data: {
|
||||||
|
id: "unregistered_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_address: {},
|
||||||
|
billing_address: {},
|
||||||
|
discounts: [],
|
||||||
|
customer_id: "",
|
||||||
|
},
|
||||||
|
cartWithPaySessions: {
|
||||||
|
_id: IdMap.getId("cartWithPaySessions"),
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
shipping_methods: [],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider_id: "unregistered",
|
||||||
|
data: {
|
||||||
|
id: "unregistered_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_address: {},
|
||||||
|
billing_address: {},
|
||||||
discounts: [],
|
discounts: [],
|
||||||
customer_id: "",
|
customer_id: "",
|
||||||
},
|
},
|
||||||
@@ -34,8 +143,8 @@ export const carts = {
|
|||||||
quantity: 10,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
shippingAddress: {},
|
shipping_address: {},
|
||||||
billingAddress: {},
|
billing_address: {},
|
||||||
discounts: [],
|
discounts: [],
|
||||||
customer_id: "",
|
customer_id: "",
|
||||||
},
|
},
|
||||||
@@ -50,12 +159,14 @@ export const carts = {
|
|||||||
yes: "sir",
|
yes: "sir",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shipping_method: {
|
shipping_methods: [
|
||||||
provider_id: "gls",
|
{
|
||||||
data: {
|
provider_id: "gls",
|
||||||
yes: "sir",
|
data: {
|
||||||
|
yes: "sir",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
shipping_address: {
|
shipping_address: {
|
||||||
first_name: "hi",
|
first_name: "hi",
|
||||||
last_name: "you",
|
last_name: "you",
|
||||||
@@ -127,8 +238,20 @@ export const carts = {
|
|||||||
quantity: 10,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
shippingAddress: {},
|
shipping_methods: [
|
||||||
billingAddress: {},
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_options: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_address: {},
|
||||||
|
billing_address: {},
|
||||||
discounts: [],
|
discounts: [],
|
||||||
customer_id: "",
|
customer_id: "",
|
||||||
},
|
},
|
||||||
@@ -141,6 +264,15 @@ export const CartModelMock = {
|
|||||||
}),
|
}),
|
||||||
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
findOne: jest.fn().mockImplementation(query => {
|
findOne: jest.fn().mockImplementation(query => {
|
||||||
|
if (query._id === IdMap.getId("withShippingOptions")) {
|
||||||
|
return Promise.resolve(carts.withShippingOptions)
|
||||||
|
}
|
||||||
|
if (query._id === IdMap.getId("cartWithPaySessionsDifRegion")) {
|
||||||
|
return Promise.resolve(carts.cartWithPaySessionsDifRegion)
|
||||||
|
}
|
||||||
|
if (query._id === IdMap.getId("cartWithPaySessions")) {
|
||||||
|
return Promise.resolve(carts.cartWithPaySessions)
|
||||||
|
}
|
||||||
if (query._id === IdMap.getId("emptyCart")) {
|
if (query._id === IdMap.getId("emptyCart")) {
|
||||||
return Promise.resolve(carts.emptyCart)
|
return Promise.resolve(carts.emptyCart)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class CartModel extends BaseModel {
|
|||||||
region_id: { type: String, required: true },
|
region_id: { type: String, required: true },
|
||||||
discounts: { type: [String], default: [] },
|
discounts: { type: [String], default: [] },
|
||||||
customer_id: { type: String, default: "" },
|
customer_id: { type: String, default: "" },
|
||||||
|
payment_sessions: { type: [PaymentMethodSchema], default: [] },
|
||||||
|
shipping_options: { type: [ShippingMethodSchema], default: [] },
|
||||||
payment_method: { type: PaymentMethodSchema, default: {} },
|
payment_method: { type: PaymentMethodSchema, default: {} },
|
||||||
shipping_methods: { type: [ShippingMethodSchema], default: [] },
|
shipping_methods: { type: [ShippingMethodSchema], default: [] },
|
||||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
|
|||||||
@@ -5,12 +5,132 @@ export const carts = {
|
|||||||
emptyCart: {
|
emptyCart: {
|
||||||
_id: IdMap.getId("emptyCart"),
|
_id: IdMap.getId("emptyCart"),
|
||||||
items: [],
|
items: [],
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
shipping_options: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
data: {
|
||||||
|
some_data: "yes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
regionCart: {
|
regionCart: {
|
||||||
_id: IdMap.getId("regionCart"),
|
_id: IdMap.getId("regionCart"),
|
||||||
name: "Product 1",
|
name: "Product 1",
|
||||||
region_id: IdMap.getId("testRegion"),
|
region_id: IdMap.getId("testRegion"),
|
||||||
},
|
},
|
||||||
|
frCart: {
|
||||||
|
_id: IdMap.getId("fr-cart"),
|
||||||
|
title: "test",
|
||||||
|
region_id: IdMap.getId("region-france"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("line"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
unit_price: 8,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("eur-8-us-10"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unit_price: 10,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("eur-10-us-12"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 10,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("eur-10-us-12"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_options: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_address: {},
|
||||||
|
billing_address: {},
|
||||||
|
discounts: [],
|
||||||
|
customer_id: "",
|
||||||
|
},
|
||||||
|
cartWithPaySessions: {
|
||||||
|
_id: IdMap.getId("cartWithPaySessions"),
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider_id: "unregistered",
|
||||||
|
data: {
|
||||||
|
id: "unregistered_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_address: {},
|
||||||
|
billing_address: {},
|
||||||
|
discounts: [],
|
||||||
|
customer_id: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CartServiceMock = {
|
export const CartServiceMock = {
|
||||||
@@ -23,17 +143,33 @@ export const CartServiceMock = {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
retrieve: jest.fn().mockImplementation(cartId => {
|
retrieve: jest.fn().mockImplementation(cartId => {
|
||||||
|
if (cartId === IdMap.getId("fr-cart")) {
|
||||||
|
return Promise.resolve(carts.frCart)
|
||||||
|
}
|
||||||
if (cartId === IdMap.getId("regionCart")) {
|
if (cartId === IdMap.getId("regionCart")) {
|
||||||
return Promise.resolve(carts.regionCart)
|
return Promise.resolve(carts.regionCart)
|
||||||
}
|
}
|
||||||
if (cartId === IdMap.getId("emptyCart")) {
|
if (cartId === IdMap.getId("emptyCart")) {
|
||||||
return Promise.resolve(carts.emptyCart)
|
return Promise.resolve(carts.emptyCart)
|
||||||
}
|
}
|
||||||
|
if (cartId === IdMap.getId("cartWithPaySessions")) {
|
||||||
|
return Promise.resolve(carts.cartWithPaySessions)
|
||||||
|
}
|
||||||
return Promise.resolve(undefined)
|
return Promise.resolve(undefined)
|
||||||
}),
|
}),
|
||||||
addLineItem: jest.fn().mockImplementation((cartId, lineItem) => {
|
addLineItem: jest.fn().mockImplementation((cartId, lineItem) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
|
setPaymentMethod: jest.fn().mockImplementation((cartId, method) => {
|
||||||
|
if (method.provider_id === "default_provider") {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MedusaError(MedusaError.Types.NOT_ALLOWED, "Not allowed")
|
||||||
|
}),
|
||||||
|
updateLineItem: jest.fn().mockImplementation((cartId, lineItem) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
setRegion: jest.fn().mockImplementation((cartId, regionId) => {
|
setRegion: jest.fn().mockImplementation((cartId, regionId) => {
|
||||||
if (regionId === IdMap.getId("fail")) {
|
if (regionId === IdMap.getId("fail")) {
|
||||||
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Region not found")
|
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Region not found")
|
||||||
@@ -52,6 +188,55 @@ export const CartServiceMock = {
|
|||||||
applyPromoCode: jest.fn().mockImplementation((cartId, code) => {
|
applyPromoCode: jest.fn().mockImplementation((cartId, code) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
|
setPaymentSessions: jest.fn().mockImplementation(cartId => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
setShippingOptions: jest.fn().mockImplementation(cartId => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
decorate: jest.fn().mockImplementation(cart => {
|
||||||
|
cart.decorated = true
|
||||||
|
return cart
|
||||||
|
}),
|
||||||
|
addShippingMethod: jest.fn().mockImplementation(cartId => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
retrieveShippingOption: jest.fn().mockImplementation((cartId, optionId) => {
|
||||||
|
if (optionId === IdMap.getId("freeShipping")) {
|
||||||
|
return {
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (optionId === IdMap.getId("withData")) {
|
||||||
|
return {
|
||||||
|
_id: IdMap.getId("withData"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
data: {
|
||||||
|
some_data: "yes",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
retrievePaymentSession: jest.fn().mockImplementation((cartId, providerId) => {
|
||||||
|
if (providerId === "default_provider") {
|
||||||
|
return {
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
money_id: "success",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerId === "nono") {
|
||||||
|
return {
|
||||||
|
provider_id: "nono",
|
||||||
|
data: {
|
||||||
|
money_id: "fail",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const mock = jest.fn().mockImplementation(() => {
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
export const LineItemServiceMock = {
|
export const LineItemServiceMock = {
|
||||||
validate: jest.fn().mockImplementation(data => {
|
validate: jest.fn().mockImplementation(data => {
|
||||||
@@ -8,6 +9,10 @@ export const LineItemServiceMock = {
|
|||||||
return data
|
return data
|
||||||
}),
|
}),
|
||||||
generate: jest.fn().mockImplementation((variantId, quantity, regionId) => {
|
generate: jest.fn().mockImplementation((variantId, quantity, regionId) => {
|
||||||
|
if (variantId === IdMap.getId("fail") || regionId === IdMap.getId("fail")) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, "Doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
content: {
|
content: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -13,6 +13,24 @@ export const DefaultProviderMock = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PaymentProviderServiceMock = {
|
export const PaymentProviderServiceMock = {
|
||||||
|
updateSession: jest.fn().mockImplementation((session, cart) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
provider_id: session.provider_id,
|
||||||
|
data: {
|
||||||
|
...session.data,
|
||||||
|
id: `${session.data.id}_updated`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
createSession: jest.fn().mockImplementation((providerId, cart) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
provider_id: providerId,
|
||||||
|
data: {
|
||||||
|
id: `${providerId}_session`,
|
||||||
|
cartId: cart._id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
retrieveProvider: jest.fn().mockImplementation(providerId => {
|
retrieveProvider: jest.fn().mockImplementation(providerId => {
|
||||||
if (providerId === "default_provider") {
|
if (providerId === "default_provider") {
|
||||||
return DefaultProviderMock
|
return DefaultProviderMock
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const regions = {
|
|||||||
_id: IdMap.getId("region-france"),
|
_id: IdMap.getId("region-france"),
|
||||||
name: "France",
|
name: "France",
|
||||||
countries: ["FR"],
|
countries: ["FR"],
|
||||||
|
payment_providers: ["default_provider", "france-provider"],
|
||||||
currency_code: "eur",
|
currency_code: "eur",
|
||||||
},
|
},
|
||||||
regionUs: {
|
regionUs: {
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
|
||||||
|
export const shippingOptions = {
|
||||||
|
freeShipping: {
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
name: "Free Shipping",
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
profile_id: IdMap.getId("default-profile"),
|
||||||
|
data: {
|
||||||
|
id: "fs",
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: "flat_rate",
|
||||||
|
amount: 10,
|
||||||
|
},
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
},
|
||||||
|
expensiveShipping: {
|
||||||
|
_id: IdMap.getId("expensiveShipping"),
|
||||||
|
name: "Expensive Shipping",
|
||||||
|
profile_id: IdMap.getId("fragile-profile"),
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
data: {
|
||||||
|
id: "es",
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: "flat_rate",
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
},
|
||||||
|
franceShipping: {
|
||||||
|
_id: IdMap.getId("franceShipping"),
|
||||||
|
name: "FR Shipping",
|
||||||
|
profile_id: IdMap.getId("default-profile"),
|
||||||
|
region_id: IdMap.getId("region-france"),
|
||||||
|
data: {
|
||||||
|
id: "bonjour",
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: "flat_rate",
|
||||||
|
amount: 20,
|
||||||
|
},
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShippingOptionServiceMock = {
|
||||||
|
retrieve: jest.fn().mockImplementation(optionId => {
|
||||||
|
if (optionId === IdMap.getId("freeShipping")) {
|
||||||
|
return Promise.resolve(shippingOptions.freeShipping)
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
list: jest.fn().mockImplementation(data => {
|
||||||
|
if (data.region_id === IdMap.getId("region-france")) {
|
||||||
|
return Promise.resolve([shippingOptions.franceShipping])
|
||||||
|
}
|
||||||
|
if (data.region_id === IdMap.getId("testRegion")) {
|
||||||
|
return Promise.resolve([
|
||||||
|
shippingOptions.freeShipping,
|
||||||
|
shippingOptions.expensiveShipping,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
validateCartOption: jest.fn().mockImplementation((method, cart) => {
|
||||||
|
if (method._id === IdMap.getId("freeShipping")) {
|
||||||
|
return Promise.resolve(true)
|
||||||
|
}
|
||||||
|
if (method._id === IdMap.getId("franceShipping")) {
|
||||||
|
return Promise.resolve(true)
|
||||||
|
}
|
||||||
|
if (method._id === IdMap.getId("fail")) {
|
||||||
|
return Promise.resolve(false)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
fetchCartOptions: jest.fn().mockImplementation(cart => {
|
||||||
|
if (cart._id === IdMap.getId("cartWithLine")) {
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
name: "Free Shipping",
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
price: 10,
|
||||||
|
data: {
|
||||||
|
id: "fs",
|
||||||
|
},
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
return ShippingOptionServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mock
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from "../__mocks__/payment-provider"
|
} from "../__mocks__/payment-provider"
|
||||||
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
||||||
import { RegionServiceMock } from "../__mocks__/region"
|
import { RegionServiceMock } from "../__mocks__/region"
|
||||||
|
import { ShippingOptionServiceMock } from "../__mocks__/shipping-option"
|
||||||
import { CartModelMock, carts } from "../../models/__mocks__/cart"
|
import { CartModelMock, carts } from "../../models/__mocks__/cart"
|
||||||
import { LineItemServiceMock } from "../__mocks__/line-item"
|
import { LineItemServiceMock } from "../__mocks__/line-item"
|
||||||
|
|
||||||
@@ -204,20 +205,6 @@ describe("CartService", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws if line item not validated", async () => {
|
|
||||||
const lineItem = {
|
|
||||||
title: "invalid lineitem",
|
|
||||||
description: "This is a new line",
|
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await cartService.addLineItem(IdMap.getId("cartWithLine"), lineItem)
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).toEqual(`"content" is required`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws if inventory isn't covered", async () => {
|
it("throws if inventory isn't covered", async () => {
|
||||||
const lineItem = {
|
const lineItem = {
|
||||||
title: "merge line",
|
title: "merge line",
|
||||||
@@ -285,6 +272,85 @@ describe("CartService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("updateLineItem", () => {
|
||||||
|
const cartService = new CartService({
|
||||||
|
cartModel: CartModelMock,
|
||||||
|
productVariantService: ProductVariantServiceMock,
|
||||||
|
lineItemService: LineItemServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("successfully updates existing line item", async () => {
|
||||||
|
const lineItem = {
|
||||||
|
title: "update line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "https://test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
await cartService.updateLineItem(
|
||||||
|
IdMap.getId("cartWithLine"),
|
||||||
|
IdMap.getId("existingLine"),
|
||||||
|
lineItem
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("cartWithLine"),
|
||||||
|
"items._id": IdMap.getId("existingLine"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: { "items.$": lineItem },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws if inventory isn't covered", async () => {
|
||||||
|
const lineItem = {
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
quantity: 1,
|
||||||
|
content: {
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("cannot-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 1234,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cartService.updateLineItem(
|
||||||
|
IdMap.getId("cartWithLine"),
|
||||||
|
IdMap.getId("existingLine"),
|
||||||
|
lineItem
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).toEqual(
|
||||||
|
`Inventory doesn't cover the desired quantity`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("updateEmail", () => {
|
describe("updateEmail", () => {
|
||||||
const cartService = new CartService({
|
const cartService = new CartService({
|
||||||
cartModel: CartModelMock,
|
cartModel: CartModelMock,
|
||||||
@@ -459,6 +525,7 @@ describe("CartService", () => {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
region_id: IdMap.getId("region-us"),
|
region_id: IdMap.getId("region-us"),
|
||||||
|
shipping_methods: [],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("line"),
|
_id: IdMap.getId("line"),
|
||||||
@@ -526,7 +593,7 @@ describe("CartService", () => {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
region_id: IdMap.getId("region-us"),
|
region_id: IdMap.getId("region-us"),
|
||||||
shipping_method: undefined,
|
shipping_methods: [],
|
||||||
payment_method: undefined,
|
payment_method: undefined,
|
||||||
shipping_address: {
|
shipping_address: {
|
||||||
first_name: "hi",
|
first_name: "hi",
|
||||||
@@ -711,4 +778,491 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("setPaymentSessions", () => {
|
||||||
|
const cartService = new CartService({
|
||||||
|
cartModel: CartModelMock,
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
paymentProviderService: PaymentProviderServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("initializes payment sessions for each of the providers", async () => {
|
||||||
|
await cartService.setPaymentSessions(IdMap.getId("cartWithLine"))
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(2)
|
||||||
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledWith(
|
||||||
|
"default_provider",
|
||||||
|
carts.cartWithLine
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledWith(
|
||||||
|
"unregistered",
|
||||||
|
carts.cartWithLine
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("cartWithLine"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session",
|
||||||
|
cartId: IdMap.getId("cartWithLine"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider_id: "unregistered",
|
||||||
|
data: {
|
||||||
|
id: "unregistered_session",
|
||||||
|
cartId: IdMap.getId("cartWithLine"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates payment sessions for existing sessions", async () => {
|
||||||
|
await cartService.setPaymentSessions(IdMap.getId("cartWithPaySessions"))
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(2)
|
||||||
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
carts.cartWithPaySessions
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
provider_id: "unregistered",
|
||||||
|
data: {
|
||||||
|
id: "unregistered_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
carts.cartWithPaySessions
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("cartWithPaySessions"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session_updated",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider_id: "unregistered",
|
||||||
|
data: {
|
||||||
|
id: "unregistered_session_updated",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("filters sessions not available in the region", async () => {
|
||||||
|
await cartService.setPaymentSessions(
|
||||||
|
IdMap.getId("cartWithPaySessionsDifRegion")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
carts.cartWithPaySessionsDifRegion
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledWith(
|
||||||
|
"france-provider",
|
||||||
|
carts.cartWithPaySessionsDifRegion
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("cartWithPaySessionsDifRegion"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
id: "default_provider_session_updated",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider_id: "france-provider",
|
||||||
|
data: {
|
||||||
|
id: "france-provider_session",
|
||||||
|
cartId: IdMap.getId("cartWithPaySessionsDifRegion"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("setShippingOptions", () => {
|
||||||
|
const cartService = new CartService({
|
||||||
|
cartModel: CartModelMock,
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
shippingOptionService: ShippingOptionServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("gets shipping options from the cart's regions", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await cartService.setShippingOptions(IdMap.getId("cartWithLine"))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("gets shipping options from region", () => {
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.fetchCartOptions
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ShippingOptionServiceMock.fetchCartOptions).toHaveBeenCalledWith(
|
||||||
|
carts.cartWithLine
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates cart", () => {
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("cartWithLine"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
shipping_options: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
name: "Free Shipping",
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
price: 10,
|
||||||
|
data: {
|
||||||
|
id: "fs",
|
||||||
|
},
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("retrievePaymentSession", () => {
|
||||||
|
const cartService = new CartService({
|
||||||
|
cartModel: CartModelMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
let res
|
||||||
|
|
||||||
|
describe("it retrieves the correct payment session", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
res = await cartService.retrievePaymentSession(
|
||||||
|
IdMap.getId("cartWithPaySessions"),
|
||||||
|
"default_provider"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("retrieves the cart", () => {
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: IdMap.getId("cartWithPaySessions"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("finds the correct payment session", () => {
|
||||||
|
expect(res.provider_id).toEqual("default_provider")
|
||||||
|
expect(res.data).toEqual({
|
||||||
|
id: "default_provider_session",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("it fails when provider doesn't match open session", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
try {
|
||||||
|
await cartService.retrievePaymentSession(
|
||||||
|
IdMap.getId("cartWithPaySessions"),
|
||||||
|
"nono"
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
res = err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("retrieves the cart", () => {
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: IdMap.getId("cartWithPaySessions"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws invalid data errro", () => {
|
||||||
|
expect(res.message).toEqual(
|
||||||
|
"The provider_id did not match any open payment sessions"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("addShippingMethod", () => {
|
||||||
|
const cartService = new CartService({
|
||||||
|
cartModel: CartModelMock,
|
||||||
|
shippingOptionService: ShippingOptionServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("successfully adds the shipping method", () => {
|
||||||
|
const method = {
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
profile_id: "default_profile",
|
||||||
|
price: 20,
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
data: {
|
||||||
|
id: "testshipperid",
|
||||||
|
},
|
||||||
|
products: [IdMap.getId("product")],
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const cartId = IdMap.getId("cartWithPaySessions")
|
||||||
|
await cartService.addShippingMethod(cartId, method)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("checks availability", () => {
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledWith(method, carts.cartWithPaySessions)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates cart", () => {
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("cartWithPaySessions"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: { shipping_methods: [method] },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("successfully overrides existing profile shipping method", () => {
|
||||||
|
const method = {
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
profile_id: "default_profile",
|
||||||
|
price: 20,
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
data: {
|
||||||
|
id: "testshipperid",
|
||||||
|
},
|
||||||
|
products: [IdMap.getId("product")],
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const cartId = IdMap.getId("fr-cart")
|
||||||
|
await cartService.addShippingMethod(cartId, method)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("checks availability", () => {
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledWith(method, carts.frCart)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates cart", () => {
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("fr-cart"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: { shipping_methods: [method] },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("successfully adds additional shipping method", () => {
|
||||||
|
const method = {
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
provider_id: "test_shipper",
|
||||||
|
profile_id: "additional_profile",
|
||||||
|
price: 20,
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
data: {
|
||||||
|
id: "testshipperid",
|
||||||
|
},
|
||||||
|
products: [IdMap.getId("product")],
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const cartId = IdMap.getId("fr-cart")
|
||||||
|
await cartService.addShippingMethod(cartId, method)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("checks availability", () => {
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledWith(method, carts.frCart)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates cart", () => {
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("fr-cart"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
profile_id: "default_profile",
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("throws error on no availability", () => {
|
||||||
|
const method = {
|
||||||
|
_id: IdMap.getId("fail"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let res
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const cartId = IdMap.getId("fr-cart")
|
||||||
|
try {
|
||||||
|
await cartService.addShippingMethod(cartId, method)
|
||||||
|
} catch (err) {
|
||||||
|
res = err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("checks availability", () => {
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
ShippingOptionServiceMock.validateCartOption
|
||||||
|
).toHaveBeenCalledWith(method, carts.frCart)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throw error", () => {
|
||||||
|
expect(res.message).toEqual(
|
||||||
|
"The selected shipping method cannot be applied to the cart"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("retrieveShippingOption", () => {
|
||||||
|
const cartService = new CartService({
|
||||||
|
cartModel: CartModelMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
let res
|
||||||
|
|
||||||
|
describe("it retrieves the correct payment session", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
res = await cartService.retrieveShippingOption(
|
||||||
|
IdMap.getId("fr-cart"),
|
||||||
|
IdMap.getId("freeShipping")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("retrieves the cart", () => {
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: IdMap.getId("fr-cart"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("finds the correct payment session", () => {
|
||||||
|
expect(res._id).toEqual(IdMap.getId("freeShipping"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("it fails when provider doesn't match open session", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
try {
|
||||||
|
await cartService.retrieveShippingOption(
|
||||||
|
IdMap.getId("fr-cart"),
|
||||||
|
"nono"
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
res = err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("retrieves the cart", () => {
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: IdMap.getId("fr-cart"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws invalid data errro", () => {
|
||||||
|
expect(res.message).toEqual(
|
||||||
|
"The option id doesn't match any available shipping options"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -15,6 +15,7 @@ class CartService extends BaseService {
|
|||||||
productVariantService,
|
productVariantService,
|
||||||
regionService,
|
regionService,
|
||||||
lineItemService,
|
lineItemService,
|
||||||
|
shippingOptionService,
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -38,6 +39,9 @@ class CartService extends BaseService {
|
|||||||
|
|
||||||
/** @private @const {PaymentProviderService} */
|
/** @private @const {PaymentProviderService} */
|
||||||
this.paymentProviderService_ = paymentProviderService
|
this.paymentProviderService_ = paymentProviderService
|
||||||
|
|
||||||
|
/** @private @const {ShippingOptionsService} */
|
||||||
|
this.shippingOptionService_ = shippingOptionService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,9 +204,7 @@ class CartService extends BaseService {
|
|||||||
* @return {Cart} return the decorated cart.
|
* @return {Cart} return the decorated cart.
|
||||||
*/
|
*/
|
||||||
async decorate(cart, fields, expandFields = []) {
|
async decorate(cart, fields, expandFields = []) {
|
||||||
const requiredFields = ["_id", "metadata"]
|
return cart
|
||||||
const decorated = _.pick(cart, fields.concat(requiredFields))
|
|
||||||
return decorated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,9 +215,7 @@ class CartService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async addLineItem(cartId, lineItem) {
|
async addLineItem(cartId, lineItem) {
|
||||||
const validatedLineItem = this.lineItemService_.validate(lineItem)
|
const validatedLineItem = this.lineItemService_.validate(lineItem)
|
||||||
|
|
||||||
const cart = await this.retrieve(cartId)
|
const cart = await this.retrieve(cartId)
|
||||||
|
|
||||||
const currentItem = cart.items.find(line =>
|
const currentItem = cart.items.find(line =>
|
||||||
_.isEqual(line.content, validatedLineItem.content)
|
_.isEqual(line.content, validatedLineItem.content)
|
||||||
)
|
)
|
||||||
@@ -275,6 +275,61 @@ class CartService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a cart's existing line item.
|
||||||
|
* @param {string} cartId - the id of the cart to update
|
||||||
|
* @param {string} lineItemId - the id of the line item to update.
|
||||||
|
* @param {LineItem} lineItem - the line item to update. Must include an _id
|
||||||
|
* field.
|
||||||
|
* @return {Promise} the result of the update operation
|
||||||
|
*/
|
||||||
|
async updateLineItem(cartId, lineItemId, lineItem) {
|
||||||
|
const cart = await this.retrieve(cartId)
|
||||||
|
const validatedLineItem = this.lineItemService_.validate(lineItem)
|
||||||
|
|
||||||
|
if (!lineItemId) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Line Item must have an _id corresponding to an existing line item id"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the line item exists in the cart
|
||||||
|
const lineItemExists = cart.items.find(i => i._id === lineItemId)
|
||||||
|
if (!lineItemExists) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"A line item with the provided id doesn't exist in the cart"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that inventory covers the request
|
||||||
|
const hasInventory = await this.confirmInventory_(
|
||||||
|
validatedLineItem.content,
|
||||||
|
validatedLineItem.quantity
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!hasInventory) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Inventory doesn't cover the desired quantity"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the line item
|
||||||
|
return this.cartModel_.updateOne(
|
||||||
|
{
|
||||||
|
_id: cartId,
|
||||||
|
"items._id": lineItemId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
"items.$": validatedLineItem,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the email of a cart
|
* Sets the email of a cart
|
||||||
* @param {string} cartId - the id of the cart to add email to
|
* @param {string} cartId - the id of the cart to add email to
|
||||||
@@ -283,7 +338,6 @@ class CartService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async updateEmail(cartId, email) {
|
async updateEmail(cartId, email) {
|
||||||
const cart = await this.retrieve(cartId)
|
const cart = await this.retrieve(cartId)
|
||||||
|
|
||||||
const schema = Validator.string()
|
const schema = Validator.string()
|
||||||
.email()
|
.email()
|
||||||
.required()
|
.required()
|
||||||
@@ -313,7 +367,6 @@ class CartService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async updateBillingAddress(cartId, address) {
|
async updateBillingAddress(cartId, address) {
|
||||||
const cart = await this.retrieve(cartId)
|
const cart = await this.retrieve(cartId)
|
||||||
|
|
||||||
const { value, error } = Validator.address().validate(address)
|
const { value, error } = Validator.address().validate(address)
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -340,7 +393,6 @@ class CartService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async updateShippingAddress(cartId, address) {
|
async updateShippingAddress(cartId, address) {
|
||||||
const cart = await this.retrieve(cartId)
|
const cart = await this.retrieve(cartId)
|
||||||
|
|
||||||
const { value, error } = Validator.address().validate(address)
|
const { value, error } = Validator.address().validate(address)
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -360,12 +412,37 @@ class CartService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A payment method represents a way for the customer to pay. The payment
|
||||||
|
* method will typically come from one of the payment sessions.
|
||||||
* @typedef {object} PaymentMethod
|
* @typedef {object} PaymentMethod
|
||||||
* @property {string} provider_id - the identifier of the payment method's
|
* @property {string} provider_id - the identifier of the payment method's
|
||||||
* provider
|
* provider
|
||||||
* @property {object} data - the data associated with the payment method
|
* @property {object} data - the data associated with the payment method
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an open payment session from the list of payment sessions
|
||||||
|
* stored in the cart. If none is an INVALID_DATA error is thrown.
|
||||||
|
* @param {string} cartId - the id of the cart to retrieve the session from
|
||||||
|
* @param {string} providerId - the id of the provider the session belongs to
|
||||||
|
* @return {PaymentMethod} the session
|
||||||
|
*/
|
||||||
|
async retrievePaymentSession(cartId, providerId) {
|
||||||
|
const cart = await this.retrieve(cartId)
|
||||||
|
const session = cart.payment_sessions.find(
|
||||||
|
({ provider_id }) => provider_id === providerId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`The provider_id did not match any open payment sessions`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a payment method for a cart.
|
* Sets a payment method for a cart.
|
||||||
* @param {string} cartId - the id of the cart to add payment method to
|
* @param {string} cartId - the id of the cart to add payment method to
|
||||||
@@ -389,7 +466,8 @@ class CartService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the payment method has been authorized.
|
// The provider service will be able to perform operations on the
|
||||||
|
// session we are trying to set as the payment method.
|
||||||
const provider = this.paymentProviderService_.retrieveProvider(
|
const provider = this.paymentProviderService_.retrieveProvider(
|
||||||
paymentMethod.provider_id
|
paymentMethod.provider_id
|
||||||
)
|
)
|
||||||
@@ -413,6 +491,158 @@ class CartService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates, updates and sets payment sessions associated with the cart. The
|
||||||
|
* first time the method is called payment sessions will be created for each
|
||||||
|
* provider. Additional calls will ensure that payment sessions have correct
|
||||||
|
* amounts, currencies, etc. as well as make sure to filter payment sessions
|
||||||
|
* that are not available for the cart's region.
|
||||||
|
* @param {string} cartId - the id of the cart to set payment session for
|
||||||
|
* @returns {Promise} the result of the update operation.
|
||||||
|
*/
|
||||||
|
async setPaymentSessions(cartId) {
|
||||||
|
const cart = await this.retrieve(cartId)
|
||||||
|
const region = await this.regionService_.retrieve(cart.region_id)
|
||||||
|
|
||||||
|
// If there are existing payment sessions ensure that these are up to date
|
||||||
|
let sessions = []
|
||||||
|
if (cart.payment_sessions && cart.payment_sessions.length) {
|
||||||
|
sessions = await Promise.all(
|
||||||
|
cart.payment_sessions.map(async pSession => {
|
||||||
|
if (!region.payment_providers.includes(pSession.provider_id)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return this.paymentProviderService_.updateSession(pSession, cart)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter all null sessions
|
||||||
|
sessions = sessions.filter(s => !!s)
|
||||||
|
|
||||||
|
// For all the payment providers in the region make sure to either skip them
|
||||||
|
// if they already exist or create them if they don't yet exist.
|
||||||
|
let newSessions = await Promise.all(
|
||||||
|
region.payment_providers.map(async pId => {
|
||||||
|
if (sessions.find(s => s.provider_id === pId)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.paymentProviderService_.createSession(pId, cart)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter null sessions
|
||||||
|
newSessions = newSessions.filter(s => !!s)
|
||||||
|
|
||||||
|
// Update the payment sessions with the concatenated array of updated and
|
||||||
|
// newly created payment sessions
|
||||||
|
return this.cartModel_.updateOne(
|
||||||
|
{
|
||||||
|
_id: cart._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: { payment_sessions: sessions.concat(newSessions) },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves one of the open shipping options for the cart.
|
||||||
|
* @param {string} cartId - the id of the cart to retrieve the option from
|
||||||
|
* @param {string} optionId - the id of the option to retrieve
|
||||||
|
* @return {ShippingOption} the option that was found
|
||||||
|
*/
|
||||||
|
async retrieveShippingOption(cartId, optionId) {
|
||||||
|
const cart = await this.retrieve(cartId)
|
||||||
|
|
||||||
|
const option = cart.shipping_options.find(({ _id }) => _id === optionId)
|
||||||
|
|
||||||
|
if (!option) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`The option id doesn't match any available shipping options`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return option
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the shipping method to the list of shipping methods associated with
|
||||||
|
* the cart.
|
||||||
|
* @param {string} cartId - the id of the cart to add shipping method to
|
||||||
|
* @param {ShippingOption} method - the shipping method to add to the cart
|
||||||
|
* @return {Promise} the result of the update operation
|
||||||
|
*/
|
||||||
|
async addShippingMethod(cartId, method) {
|
||||||
|
const cart = await this.retrieve(cartId)
|
||||||
|
const { shipping_methods } = cart
|
||||||
|
|
||||||
|
const isValid = await this.shippingOptionService_.validateCartOption(
|
||||||
|
method,
|
||||||
|
cart
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"The selected shipping method cannot be applied to the cart"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all existing selected shipping methods and update the one
|
||||||
|
// that has the same profile as the selected shipping method.
|
||||||
|
let exists = false
|
||||||
|
const newMethods = shipping_methods.map(sm => {
|
||||||
|
if (sm.profile_id === method.profile_id) {
|
||||||
|
exists = true
|
||||||
|
return method
|
||||||
|
}
|
||||||
|
|
||||||
|
return sm
|
||||||
|
})
|
||||||
|
|
||||||
|
// If none of the selected methods are for the same profile as the new
|
||||||
|
// shipping method the exists flag will be false. Therefore we push the new
|
||||||
|
// method.
|
||||||
|
if (!exists) {
|
||||||
|
newMethods.push(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cartModel_.updateOne(
|
||||||
|
{
|
||||||
|
_id: cart._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: { shipping_methods: newMethods },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all shipping options that are available to the cart and stores them
|
||||||
|
* in shipping_options. The shipping options are retrieved from the shipping
|
||||||
|
* option service.
|
||||||
|
* @param {string} cartId - the id of the cart
|
||||||
|
* @return {Promse} the result of the update operation
|
||||||
|
*/
|
||||||
|
async setShippingOptions(cartId) {
|
||||||
|
const cart = await this.retrieve(cartId)
|
||||||
|
|
||||||
|
// Get the shipping options available in the region
|
||||||
|
const cartOptions = await this.shippingOptionService_.fetchCartOptions(cart)
|
||||||
|
|
||||||
|
return this.cartModel_.updateOne(
|
||||||
|
{
|
||||||
|
_id: cart._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: { shipping_options: cartOptions },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set's the region of a cart.
|
* Set's the region of a cart.
|
||||||
* @param {string} cartId - the id of the cart to set region on
|
* @param {string} cartId - the id of the cart to set region on
|
||||||
@@ -463,8 +693,8 @@ class CartService extends BaseService {
|
|||||||
|
|
||||||
// Shipping methods are determined by region so the user needs to find a
|
// Shipping methods are determined by region so the user needs to find a
|
||||||
// new shipping method
|
// new shipping method
|
||||||
if (!_.isEmpty(cart.shipping_method)) {
|
if (cart.shipping_methods && cart.shipping_methods.length) {
|
||||||
update.shipping_method = undefined
|
update.shipping_methods = []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payment methods are region specific so the user needs to find a
|
// Payment methods are region specific so the user needs to find a
|
||||||
|
|||||||
Reference in new issue
Block a user