From e01df5730374aeab5769641201459fed76c3afec Mon Sep 17 00:00:00 2001 From: mkucmus Date: Wed, 12 Feb 2020 11:06:41 +0100 Subject: [PATCH] feat: dummy checkout (#357) * feat: enable placing an order for logged in user; redirect to success page * fix: global date format (minutes) * refactor(theme): split ReviewOrder into smaller parts * feat(client): place an order for logged in and guest users * feat(composables): refactor isLoggedIn, handle placing an order * refactor(defaul-theme): split views into components, delete unused imports * docs(client): createGuestOrder and createOrder added --- api/helpers.api.md | 6 +- api/shopware-6-client.api.md | 6 + .../composables/__tests__/useCart.spec.ts | 61 +- .../composables/src/hooks/useCart/index.ts | 31 +- packages/composables/src/hooks/useUser.ts | 2 +- packages/default-theme/.gitignore | 3 + packages/default-theme/components/SwCart.vue | 11 +- .../components/SwCartProduct.vue | 33 +- .../components/SwProductCard.vue | 2 +- .../components/TopNavigation.vue | 3 +- .../components/checkout/OrderReview.vue | 168 ++++++ .../components/checkout/OrderSummary.vue | 261 +++++++++ .../components/checkout/Payment.vue | 529 ++++++++++++++++++ .../components/checkout/PersonalDetails.vue | 226 ++++++++ .../components/checkout/ReviewOrder.vue | 197 +++++++ .../checkout/ReviewOrder/BillingAddress.vue | 44 ++ .../checkout/ReviewOrder/OrderItemsTable.vue | 121 ++++ .../checkout/ReviewOrder/PaymentMethod.vue | 32 ++ .../checkout/ReviewOrder/PersonalDetails.vue | 35 ++ .../checkout/ReviewOrder/ShippingAddress.vue | 43 ++ .../checkout/ReviewOrder/Summary.vue | 181 ++++++ .../components/checkout/Shipping.vue | 357 ++++++++++++ packages/default-theme/helpers/index.js | 2 +- packages/default-theme/helpers/pages.js | 4 + packages/default-theme/middleware/auth.js | 5 +- packages/default-theme/middleware/checkout.js | 19 + packages/default-theme/pages/account.vue | 5 +- packages/default-theme/pages/checkout.vue | 274 +++++++++ packages/default-theme/pages/login.vue | 3 +- packages/default-theme/pages/success-page.vue | 48 ++ packages/default-theme/static/img/debit.png | Bin 0 -> 2025 bytes .../default-theme/static/img/electron.png | Bin 0 -> 2230 bytes .../default-theme/static/img/mastercard.png | Bin 0 -> 2299 bytes .../product/getProductMainImageUrl.spec.ts | 12 +- .../src/product/getProductMainImageUrl.ts | 8 +- .../CheckoutService/createOrder.spec.ts | 73 +++ packages/shopware-6-client/src/index.ts | 2 + .../src/services/checkoutService.ts | 36 ++ 38 files changed, 2810 insertions(+), 33 deletions(-) create mode 100644 packages/default-theme/components/checkout/OrderReview.vue create mode 100644 packages/default-theme/components/checkout/OrderSummary.vue create mode 100644 packages/default-theme/components/checkout/Payment.vue create mode 100644 packages/default-theme/components/checkout/PersonalDetails.vue create mode 100644 packages/default-theme/components/checkout/ReviewOrder.vue create mode 100644 packages/default-theme/components/checkout/ReviewOrder/BillingAddress.vue create mode 100644 packages/default-theme/components/checkout/ReviewOrder/OrderItemsTable.vue create mode 100644 packages/default-theme/components/checkout/ReviewOrder/PaymentMethod.vue create mode 100644 packages/default-theme/components/checkout/ReviewOrder/PersonalDetails.vue create mode 100644 packages/default-theme/components/checkout/ReviewOrder/ShippingAddress.vue create mode 100644 packages/default-theme/components/checkout/ReviewOrder/Summary.vue create mode 100644 packages/default-theme/components/checkout/Shipping.vue create mode 100644 packages/default-theme/helpers/pages.js create mode 100644 packages/default-theme/middleware/checkout.js create mode 100644 packages/default-theme/pages/checkout.vue create mode 100644 packages/default-theme/pages/success-page.vue create mode 100644 packages/default-theme/static/img/debit.png create mode 100644 packages/default-theme/static/img/electron.png create mode 100644 packages/default-theme/static/img/mastercard.png create mode 100644 packages/shopware-6-client/__tests__/services/CheckoutService/createOrder.spec.ts create mode 100644 packages/shopware-6-client/src/services/checkoutService.ts diff --git a/api/helpers.api.md b/api/helpers.api.md index 934530164..8b416d750 100644 --- a/api/helpers.api.md +++ b/api/helpers.api.md @@ -74,10 +74,8 @@ export const getFilterSearchCriteria: (selectedFilters: any) => any[]; // @alpha (undocumented) export function getNavigationRoutes(navigationElements: NavigationElement[]): NavigationRoute[]; -// @alpha (undocumented) -export function getProductMainImageUrl({ product }?: { - product?: Product; -}): string; +// @alpha +export function getProductMainImageUrl(product: Product): string; // @alpha (undocumented) export function getProductMediaGallery({ product }?: { diff --git a/api/shopware-6-client.api.md b/api/shopware-6-client.api.md index 92786ad6b..c813a7387 100644 --- a/api/shopware-6-client.api.md +++ b/api/shopware-6-client.api.md @@ -51,6 +51,12 @@ export interface ConfigChangedArgs { // @alpha export function createCustomerAddress(params: CustomerAddressParam): Promise; +// @alpha +export function createGuestOrder(email: string): Promise; + +// @alpha +export function createOrder(): Promise; + // @alpha (undocumented) export interface CustomerAddressParam extends Partial { } diff --git a/packages/composables/__tests__/useCart.spec.ts b/packages/composables/__tests__/useCart.spec.ts index f22c7cf77..0f41f8ff7 100644 --- a/packages/composables/__tests__/useCart.spec.ts +++ b/packages/composables/__tests__/useCart.spec.ts @@ -17,14 +17,24 @@ const mockedShopwareClient = shopwareClient as jest.Mocked< describe("Composables - useCart", () => { const stateCart: Ref = ref(null); + const stateUser: Ref = ref(null); beforeEach(() => { // mock vuex store jest.resetAllMocks(); stateCart.value = null; + stateUser.value = null; setStore({ - getters: reactive({ getCart: computed(() => stateCart.value) }), + getters: reactive({ + getCart: computed(() => stateCart.value), + getUser: computed(() => stateUser.value) + }), commit: (name: string, value: any) => { - stateCart.value = value; + if (name === "SET_CART") { + stateCart.value = value; + } + if (name === "SET_USER") { + stateUser.value = value; + } } }); }); @@ -101,9 +111,56 @@ describe("Composables - useCart", () => { expect(totalPrice.value).toEqual(123); }); }); + describe("subtotal", () => { + it("should show items totalPrice as 0", () => { + const { subtotal } = useCart(); + expect(subtotal.value).toEqual(0); + }); + + it("should show 0 on empty price object", () => { + stateCart.value = { + price: {} + }; + const { subtotal } = useCart(); + expect(subtotal.value).toEqual(0); + }); + + it("should show correct subtotal price", () => { + stateCart.value = { + price: { positionPrice: 123 } + }; + const { subtotal } = useCart(); + expect(subtotal.value).toEqual(123); + }); + }); }); describe("methods", () => { + describe("placeOrder", () => { + it("should assign the appropriate error if user email is unknown or is not logged in", async () => { + const { error, placeOrder } = useCart(); + await placeOrder(); + expect(error.value).toStrictEqual({ + message: "Order cannot be placed" + }); + }); + it("should try to place an order if user is logged in and return the order object", async () => { + mockedShopwareClient.createOrder.mockResolvedValueOnce({ + id: "some-order-id-123456" + } as any); + stateUser.value = { + id: "user-id-8754321", + email: "test@email.com" + } as any; + + const { error, placeOrder } = useCart(); + const result = await placeOrder(); + expect(mockedShopwareClient.createOrder).toBeCalledTimes(1); + expect(mockedShopwareClient.createOrder).toBeCalledWith(); + expect(error.value).toBeFalsy(); + expect(result).toHaveProperty("id"); + }); + }); describe("refreshCart", () => { it("should correctly refresh the cart", async () => { const { count, refreshCart } = useCart(); diff --git a/packages/composables/src/hooks/useCart/index.ts b/packages/composables/src/hooks/useCart/index.ts index 4f204d636..3fed5cbd5 100644 --- a/packages/composables/src/hooks/useCart/index.ts +++ b/packages/composables/src/hooks/useCart/index.ts @@ -3,9 +3,12 @@ import { getCart, addProductToCart, removeCartItem, - changeCartItemQuantity + changeCartItemQuantity, + createOrder } from "@shopware-pwa/shopware-6-client"; import { getStore } from "../.."; +import { useUser } from "../useUser"; +import { Order } from "@shopware-pwa/shopware-6-client/src/interfaces/models/checkout/order/Order"; import { ClientApiError } from "@shopware-pwa/shopware-6-client/src/interfaces/errors/ApiError"; /** @@ -13,6 +16,7 @@ import { ClientApiError } from "@shopware-pwa/shopware-6-client/src/interfaces/e */ export const useCart = (): any => { let vuexStore = getStore(); + const { isLoggedIn } = useUser(); const loading: Ref = ref(false); const error: Ref = ref(null); @@ -45,6 +49,22 @@ export const useCart = (): any => { vuexStore.commit("SET_CART", result); } + /** + * todo: move this method to the separated composable after the implementation of dummy checkout. + */ + async function placeOrder(): Promise { + if (isLoggedIn.value) { + return createOrder(); + } + + // TODO: related https://github.com/DivanteLtd/shopware-pwa/issues/375 + // return createGuestOrder(guestUserEmail); + + error.value = { + message: "Order cannot be placed" + }; + } + const cart = computed(() => { return vuexStore.getters.getCart; }); @@ -66,6 +86,11 @@ export const useCart = (): any => { return cartPrice || 0; }); + const subtotal = computed(() => { + const cartPrice = cart.value?.price?.positionPrice; + return cartPrice || 0; + }); + return { addProduct, cart, @@ -76,6 +101,8 @@ export const useCart = (): any => { loading, refreshCart, removeProduct, - totalPrice + totalPrice, + subtotal, + placeOrder }; }; diff --git a/packages/composables/src/hooks/useUser.ts b/packages/composables/src/hooks/useUser.ts index d845da41b..504a528ad 100644 --- a/packages/composables/src/hooks/useUser.ts +++ b/packages/composables/src/hooks/useUser.ts @@ -231,7 +231,7 @@ export const useUser = (): UseUser => { return true; }; - const isLoggedIn = computed(() => !!user.value); + const isLoggedIn = computed(() => !!user.value?.id); return { login, diff --git a/packages/default-theme/.gitignore b/packages/default-theme/.gitignore index 20505dc3f..db6b863a1 100644 --- a/packages/default-theme/.gitignore +++ b/packages/default-theme/.gitignore @@ -88,3 +88,6 @@ sw.* # Vim swap files *.swp + +cypress/screenshots +cypress/videos \ No newline at end of file diff --git a/packages/default-theme/components/SwCart.vue b/packages/default-theme/components/SwCart.vue index ba6c91170..7b5bd092c 100644 --- a/packages/default-theme/components/SwCart.vue +++ b/packages/default-theme/components/SwCart.vue @@ -26,7 +26,7 @@ - Go to checkout + Go to checkout
@@ -49,6 +49,7 @@ import { SfSidebar, SfButton, SfProperty, SfPrice } from '@storefront-ui/vue' import { useCart, useCartSidebar } from '@shopware-pwa/composables' import SwCartProduct from './SwCartProduct' +import { PAGE_CHECKOUT } from '../helpers/pages' export default { name: 'Cart', @@ -76,7 +77,13 @@ export default { if (!price) return return `$${price}` } - } + }, + methods: { + goToCheckout() { + this.toggleSidebar(); + return this.$router.push(PAGE_CHECKOUT); + } + }, } diff --git a/packages/default-theme/components/checkout/OrderSummary.vue b/packages/default-theme/components/checkout/OrderSummary.vue new file mode 100644 index 000000000..eb65224ab --- /dev/null +++ b/packages/default-theme/components/checkout/OrderSummary.vue @@ -0,0 +1,261 @@ + + + diff --git a/packages/default-theme/components/checkout/Payment.vue b/packages/default-theme/components/checkout/Payment.vue new file mode 100644 index 000000000..43b4f4352 --- /dev/null +++ b/packages/default-theme/components/checkout/Payment.vue @@ -0,0 +1,529 @@ + + + diff --git a/packages/default-theme/components/checkout/PersonalDetails.vue b/packages/default-theme/components/checkout/PersonalDetails.vue new file mode 100644 index 000000000..2f29bf37a --- /dev/null +++ b/packages/default-theme/components/checkout/PersonalDetails.vue @@ -0,0 +1,226 @@ + + + diff --git a/packages/default-theme/components/checkout/ReviewOrder.vue b/packages/default-theme/components/checkout/ReviewOrder.vue new file mode 100644 index 000000000..e1046cd86 --- /dev/null +++ b/packages/default-theme/components/checkout/ReviewOrder.vue @@ -0,0 +1,197 @@ + + + + diff --git a/packages/default-theme/components/checkout/ReviewOrder/BillingAddress.vue b/packages/default-theme/components/checkout/ReviewOrder/BillingAddress.vue new file mode 100644 index 000000000..742f78772 --- /dev/null +++ b/packages/default-theme/components/checkout/ReviewOrder/BillingAddress.vue @@ -0,0 +1,44 @@ + + + diff --git a/packages/default-theme/components/checkout/ReviewOrder/OrderItemsTable.vue b/packages/default-theme/components/checkout/ReviewOrder/OrderItemsTable.vue new file mode 100644 index 000000000..e47c16630 --- /dev/null +++ b/packages/default-theme/components/checkout/ReviewOrder/OrderItemsTable.vue @@ -0,0 +1,121 @@ + + + diff --git a/packages/default-theme/components/checkout/ReviewOrder/PaymentMethod.vue b/packages/default-theme/components/checkout/ReviewOrder/PaymentMethod.vue new file mode 100644 index 000000000..211fc7c72 --- /dev/null +++ b/packages/default-theme/components/checkout/ReviewOrder/PaymentMethod.vue @@ -0,0 +1,32 @@ + + + diff --git a/packages/default-theme/components/checkout/ReviewOrder/PersonalDetails.vue b/packages/default-theme/components/checkout/ReviewOrder/PersonalDetails.vue new file mode 100644 index 000000000..b44af9d10 --- /dev/null +++ b/packages/default-theme/components/checkout/ReviewOrder/PersonalDetails.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/default-theme/components/checkout/ReviewOrder/ShippingAddress.vue b/packages/default-theme/components/checkout/ReviewOrder/ShippingAddress.vue new file mode 100644 index 000000000..17a24a05f --- /dev/null +++ b/packages/default-theme/components/checkout/ReviewOrder/ShippingAddress.vue @@ -0,0 +1,43 @@ + + + diff --git a/packages/default-theme/components/checkout/ReviewOrder/Summary.vue b/packages/default-theme/components/checkout/ReviewOrder/Summary.vue new file mode 100644 index 000000000..4a40e9b0b --- /dev/null +++ b/packages/default-theme/components/checkout/ReviewOrder/Summary.vue @@ -0,0 +1,181 @@ + + + diff --git a/packages/default-theme/components/checkout/Shipping.vue b/packages/default-theme/components/checkout/Shipping.vue new file mode 100644 index 000000000..dc4f5be77 --- /dev/null +++ b/packages/default-theme/components/checkout/Shipping.vue @@ -0,0 +1,357 @@ + + + diff --git a/packages/default-theme/helpers/index.js b/packages/default-theme/helpers/index.js index 6c22e5909..f14b44abf 100644 --- a/packages/default-theme/helpers/index.js +++ b/packages/default-theme/helpers/index.js @@ -11,7 +11,7 @@ const formatPrice = ( formatWithSymbol: true } ) => currency(price, options).format() -const formatDate = (date, format = `DD-MM-YYYY H:i:s`) => +const formatDate = (date, format = `DD-MM-YYYY H:m:s`) => dayjs(date).format(format) export default { diff --git a/packages/default-theme/helpers/pages.js b/packages/default-theme/helpers/pages.js new file mode 100644 index 000000000..0c95a6acf --- /dev/null +++ b/packages/default-theme/helpers/pages.js @@ -0,0 +1,4 @@ +export const PAGE_CHECKOUT = '/checkout'; +export const PAGE_ACCOUNT = '/account'; +export const PAGE_LOGIN = '/login'; +export const PAGE_SUCCESS_PAGE = '/success-page'; \ No newline at end of file diff --git a/packages/default-theme/middleware/auth.js b/packages/default-theme/middleware/auth.js index 835a6c2ff..d1f3f0e3d 100644 --- a/packages/default-theme/middleware/auth.js +++ b/packages/default-theme/middleware/auth.js @@ -1,6 +1,7 @@ import { useUser } from '@shopware-pwa/composables' +import { PAGE_LOGIN } from '../helpers/pages' const LOGIN_ROUTE_NAME = 'login' -const LOGIN_ROUTE_PATH = '/login' + const PAGES_FOR_LOGGED_IN_ONLY = [ 'account' // user's account page ] @@ -21,6 +22,6 @@ export default async function({ route, redirect }) { isLoggedIn && !isLoggedIn.value ) { - redirect(LOGIN_ROUTE_PATH) + redirect(PAGE_LOGIN) } } diff --git a/packages/default-theme/middleware/checkout.js b/packages/default-theme/middleware/checkout.js new file mode 100644 index 000000000..15b23bad2 --- /dev/null +++ b/packages/default-theme/middleware/checkout.js @@ -0,0 +1,19 @@ +import { useCart } from '@shopware-pwa/composables' +const CHECKOUT_ROUTE_NAME = 'checkout' +const NO_ITEMS_ROUTE_PATH = '/' + +/** + * 1. Check if there is an order (based on cart) stick to the current session + * 2. Redirect to home otherwise + */ +export default async function({ route, redirect }) { + if (route.name !== CHECKOUT_ROUTE_NAME) { + return; + } + + const { cartItems } = useCart(); + + if (cartItems.value < 1) { + redirect(NO_ITEMS_ROUTE_PATH) + } +} diff --git a/packages/default-theme/pages/account.vue b/packages/default-theme/pages/account.vue index 403b84770..f8196e94b 100644 --- a/packages/default-theme/pages/account.vue +++ b/packages/default-theme/pages/account.vue @@ -33,9 +33,12 @@ + diff --git a/packages/default-theme/pages/login.vue b/packages/default-theme/pages/login.vue index 387f95601..07632d9bc 100644 --- a/packages/default-theme/pages/login.vue +++ b/packages/default-theme/pages/login.vue @@ -5,6 +5,7 @@ + diff --git a/packages/default-theme/static/img/debit.png b/packages/default-theme/static/img/debit.png new file mode 100644 index 0000000000000000000000000000000000000000..1a3168b4671327ef9d0ef4a8272157271f5e7727 GIT binary patch literal 2025 zcmVL@Ls1MuLJ9rLNYGNX zfRs?;Go}%B94mGLYKi)gkm96m3M~POK#CK)!8sc@Zk&f5zwUOwVRn6cw|jS0Oq)ji z!*e=)ulZ)?H{X0S-vTVq0xi(TA+$SXU*DfLlc|eDhO!UFBfY&Xdg8pl@)zmO@lekX zrp>mnzQt$HErEw4XWLdroP0&K`~66sB?KZv0Wq#!oBmh-E6ua;p+s0--ljTdva&`f zW>ZCr3J&{Xj5B%OwP+Yy32`R^X4>=pMZRO)R5}F~Y(KCJrPjw?kEN36Ep9dhwzq#C zZW{k&H(8FljAxH?v(@Yu*S0>$Af9CI)#j?nKp-K!C~dsBHu$2U;7^8Ms6U?w$6wR7 zdlcb-b8Zw0FL1NGsQO@)5cnmiX?-YUG5>0x3 zW`nVKP<+p_PuEx3U`$eU4*F0i1$+SSF^VF4At}T{;hZ3=8HYH>XPxWCwe1@iLRB0v91nHYzZ2zs)WJGE zeH(_Xi#ee$E~b9d3N}QddJT(F9GwC%CcqhB!AIPcU*3q@a|5P&z^m&rhCOu4D_mu?RhODT87WE1`z zrDxJ*xC$jOUqF1+mmMR_o89D7gx3uNEijj}Sb;z^zP+rY)**48`oU%SJMm!h8X-lnc&bT1Mmx zDB=5YVQ_9;c=7Z0*nh6ze$z;MECGJ++<)nCQ#ao!K0z9BtAPj6P){t~+2Xb5IK8i6 zhdkzB``yqBO~cwXeaf{LF(2j&NRZLz94jbHkEO)`q4-V@Cy$tLMfsX*Kk9!>lsR;y z@nv6{=N?YqtuDg=*Bar%rkHF%^WsMVl2pU+oY^yruazNVHc$?vAT`yb&~VI%kTkFM zoD$mXFrk}6?a%HVW#3AcuM1$@?Z7|thiFZLN+fz%oq>G#w*Ft9EgK{Day0YlHW(rTe(>s{Gr`<3X+?oNQz+hfX~80}MwX+`;Fcd z)I~xrrewc7_)P%(%!!60ua&}l0a+U)jB>!0#l{Gih-RNjVnq0snHJ~z^#0WN_^IfQ zU$OyeoXyOTvLsQ91o8C9@++ahg~Au()4X8drs1RRw_Tv7^&LLml~IENR(5S^k@Et? zVBTgBu@^N;@tdI|%|kAxijyi)pTF(Q zIL-1McwHy_@km$w8~(b36|BoNY)%f)U-%VpKG}Ep4~cV6DAPj)1EuMFgB=14I{8Xx28LCLbsxehn9N18M#?XM9YzBxX3Ud3k(i-1v$LP z;7UGAQkh5-V$2B3>l4>UZ`|G7pYo&djsY6&UXeL1a!WLwR&!CjPw;mZUzz|XfEAp+ z^5Fj^qA}92e95=Z3_7P(_=M=co$E_TfpuKy*7LKc+%T|AlVp7?a`Lbeqvg$y>QcnT zifYUxPTy8QL!E2BstB=>joiw1-b>N1TEtH#5T>rCCxzX|0>qD09PV1bRtT!%lxluH z#Oe%((^Wo(bBv|WERYJq7#E#u5{#=V;uVVEc*HAbNLOS!=n7kwt||OEt}A)`NX+NY z`fL=G&3g*i9rv)D6|B$oI#4ralK_1r*G+VPIsc~N-NSflSy3s6(1-+jn`7xD61}TV zDQEK^e{%wEGlja>rF$ja8zE)25OP&P)mz_1jFokZ<_(DcTJCxOytW=x(F_JXz+QjD ze77lpCknQm8Gug--4f7TSZ&y$2x{c2-%pZ``F!c8a<}wehfg7WD4@a4jk%g4tI>Qg zlI&7^%E|n1-u^JU-INm0X!p7@BJq%+(kqF1et{Nffn4-I^$x`^&xcTH00000NkvXX Hu0mjfOH$Gm literal 0 HcmV?d00001 diff --git a/packages/default-theme/static/img/electron.png b/packages/default-theme/static/img/electron.png new file mode 100644 index 0000000000000000000000000000000000000000..4a405f4dae6463ef89c3cb154d732b3c1d3e16d5 GIT binary patch literal 2230 zcmV;n2ub&eP)0=fuf-&(A57R`H`p zO5VBmp6`3_Ip-RSe1X>i5Fs5%dOLEh`B&+dy}rP90(`_cM+)w{-g)2yukVR11j;(& z+NefbhI*TCCC7e$lXF52tP_aq31Af)QpOTo0Ac`9c1VdqLVEdnu;s6I`iuO5$Bf>K-&^PN@*3UFY^9qiat#WG!olxIxa$82QP%W4<2RDYlPxh#QHB<83@JK?3B;R&RAeommE{=se1kuQklxF>XH=8SEo+q)C8ymqDx0LsjRd)pskUvBf*(rlZX^R1>3k&zm?d#h=D!>Lvg_KMth4R4kpuV1iL*Fs+5dM3vQ$0B| zCQ}mQUG7<0k<&A20FsT`pBDS}JOpO}*Fr^B>XDnoe85uFJj@UY>0GwqZ zx6QLuKCPr{)z|q`U|z>xcXS*|+@ZY6MmO(Ng=PJ8TU(QwkmR5fHfoC{XzzLbfny8& zJN+;VpoC4vWW}fEL==^CjE)=0G)1g{bWZki9pg|3&4Te>!?4k~#wB1xM)42H@f^)j zl`2)7N@9^t&v|A#W~E@OGx%74TM%9-_67DQZ*)J`$NX2! zHc#dZTPG(K;$JKi%L)pf$}X){~7GT z%|ZV8SX_D4x{xXGo4kpJ!Vd#knerVyk#K<8O*#=Do-{8F#DU(lOyxPUqDx52)DzBh zgn1WL{&&Jwtx*qWr3%Yqmq(uWpNkuq?tyx zCa(#5^}caMAgx-uv?(*Kll{S#c8<)5Nu@(s$qucxke-PITh5t;1Ff{se0bx=izBI8 znq)ZG@^XAES}lQe<3F`h@Xgy;UA;9U;ZBBEZ$3(l>m=aV46d4j#dSDiXlSs^zJw%c z068twbiF}I+gR(D%vLC-zSg^NS1GBKYZgTB&!PvW0Br9Cf;b`nYTGIN!6|Ec_L9biCc+P=};+qr)L92Sx{~4Q);rQkL-s!1Y)a8-fkl}JhIVlb) zpn;T-i}xS8v;gi*sA%H;!-s5;|HGZ?>TgVpJYE^ z+*md^EVP&3m`AKkrKNk8YeKy^Baxi5-pK?B;c>fpjH@X$EBAnpX>2*6sRUPVN4pSpD>rUzrSCrQ>wD`Ry9e@+z(4lrK zElGttLTQRFikTqwbsu=O*tfHri}YihP*!mHcJpy{@kxm>KFAU6;SG3WQAJK$vIQ(C z-R|YW%a3pR=ZsLgax>Txv9mW4d1;Iv>j(JBvjo0Kpuk;(dVb9eK!e+vQ{${z=~PFd z;l8l}=S_fa3I9j9t4_wW^_wyge#BjQ{;lESTS9}a&dj`1V`Cr0((x1&Jf599e@*T1 z;F)f?L-@=)`FVrX@OY%RrB%n1ol8kYV1=f^CcdkE*MrT^7nSXD0{+~&&C zcu(2RA1j2FLV+c!qU;l@{644CLT;toBf(#`E-2mcGeuGIxz0yFGrNB7puh%BM$Z=cXM-8i~WtAJk{UR@~+^P4dV1$L9~cl zj^S|d;0^?(HoJIRzLcX?`~zKpGjaaicT$5tg@P@6lRtfE@%h}}lM8`O2lx%d$3tXj zMo`Jp?`xt4a(PpuLhXmRK^~+Sn6~ZE7kQD~ekbaYY#g6_jOQWCjL?*ZX~+YeI$Sw13X!d)vJC?tAy%ckk7x zCyszsL1zt8-(sm}3GJ_d6MdkhKJ%%KgyW$%8l6$Jk){YCi_mqU6Vz~= zNu+6-^wtAfT?39nlFT$-iW7vw>zJ%u#_p2hiWE3h+Zgo$eP!TGDLy0LMm!w3LVNJ? zOKl35>R7vW9G6X3!#pXzA%hz79U%;Dli$zk1$Lvpj^?tVO|s$-`0h~hw@PTKHkli> z4XBk7Im(U=;IC3_#bZF2DG*ti+-0q-qw#y{xY2sRE;Lx1$k}gpe2b9`_`WQ@!v~8R zu=PU4jjMgFM})3}BZQf~_cuN;x!gpe+{8gCHpT}CYzoa^GFJ%*6KfHNui&7Xe%e&N zzSD4*kt3$nI4l&${{RiF&~~;cTQt5x4gqx0UK6GSes@}hqj}1QOtc(>3XFX4#w`K( z67gcb=gN1EUNIIDk%||NvXyDa9~;4OsF)wst`mVa5wi$<1$nyUZ4>1;Zbbgg_Z1U5 z%I#?9SJd7Ynkb4m6JJ4p7G4vYW1QL%)2y|`2MCnYmfLtJ!aIm4_~R>-P)>}WXm?+! zv~)KNrOj`b8a?^TmmA-xndZ^mT)P%`R@Osj)5MqXi;5z+^({i4a6WXb^4?}7cBoi@ zpMt+U@L8+nICoHAlr?&WM(xBTo?PA-%jJ%`XtmQjj&ys8r>9LbS=_#zJ*@^Z<-U4Q zax#9A;$A6*Gd0y{4@^iPAST8rU$jIp_{+a=ZNUOgEn1ExFc52x9(YSjDL;4+OK2!T ziHTI?(Nf|T=hr>m6bqx`T<#0{rZ6e#T2$y9!aLXVDi-`x{@w9crcYNJ`i?Qm?sMnVk|jHha><1QYWJ-6 zYI^@(YV-5~>iqFz_ZF!rR2eH@Fv}&UmFx6YRdac_S@)Ew!a}D~j z2fRXJulB~xtQqh#Sp6|xx0{KrMw6O)o~9EgQj^rd=+DU42o4T3%He0;W>D))-W!=t z!PNqGez=l3qh4j&)Xj!@yBI#zyzL?zI}U}R#SUWcwm-A2>LapG_`Uav;rkYQx};|K}epTuK7$F$zTG+Q*Cprj-`njh!Vv&cApliq#XvT)US zPL7#?r+qT+%uM{#_mceVdTL4?EZaU8Z@l=5=Q4@v-pN?8aO^8IoW0CkzUc15%=E!| zSbD!DdNT;OG93$NrO9ySmUoEmJxk1C9{q|(b0TlJw2l^;KXY5@2(XCRD!M`#O_~JL zpv-tA88|SRn}6FyT%3*E>rR?&*u<%eB}{(qT}1R~DHEQZzl3@7cA(mK!M)-SL_~y8 zT~tItM7$_I?EUf#%T}fG-i&o@`^_ivw#4E-ae`p`XtvbY6~YW?sb!^2Olwf>uS_3C;Q)6KzjNm0@@}qyfsW2wUR4UR^m5r z#p+?Iw|g_~RZ_Z#bWux~*Wo+3|ERF_2rHI8P}rh_# zlti+(#4GDbp0u^X8)zlAGM`d{2o=vDOZtN}FCA1qF_f{BL_)0rJnuS&GclBZ6N}3l zMwF+R7S%C&jLQ#yzZwQ7=j!@VDgj$_<7F2UyhRG5@DbXG&W0rL|H3EY zYyIR7BYk!K#7Af-IvQZBy)Sx*kD2t6BJuoD!%^%8sp2&5#z&}-huCEoBUA7oRo@cN z_z8RmOM_2|8+<`8-i=2jR2U~l@to$P5I3k9w0_g)G06S6Ru$Cr*Ho43M|1pvm_b|> z@#2hl-47q2QPREGBeI_`7lUu0#7;F}nD#oK-)VXv21zkGjHz|3TR-Rt3X#J;LPJGHBK0(? ze0BF!j4Icq{g5!Qx0{h|t;Y`6)7=P&6weEZM<@=1o{v< { } } }; - const coverUrl = getProductMainImageUrl({ product }); + const coverUrl = getProductMainImageUrl(product); expect(coverUrl).toEqual(mediaUrl); }); @@ -22,7 +22,7 @@ describe("Helpers - getProductMainImageUrl", () => { url: mediaUrl } }; - const coverUrl = getProductMainImageUrl({ product }); + const coverUrl = getProductMainImageUrl(product); expect(coverUrl).toEqual(mediaUrl); }); @@ -36,23 +36,23 @@ describe("Helpers - getProductMainImageUrl", () => { } } }; - const coverUrl = getProductMainImageUrl({ product }); + const coverUrl = getProductMainImageUrl(product); expect(coverUrl).toEqual(mediaUrl); }); it("should return null for product without cover media and cover url", () => { const emptyProduct: any = {}; - const coverUrl = getProductMainImageUrl({ product: emptyProduct }); + const coverUrl = getProductMainImageUrl(emptyProduct); expect(coverUrl).toEqual(""); }); it("should return default negative value if argument wasn't provided", () => { - const coverUrl = getProductMainImageUrl(); + const coverUrl = getProductMainImageUrl(undefined as any); expect(coverUrl).toEqual(""); }); it("should return default value if product was null", () => { - const argument: any = { product: null }; + const argument: any = null; const coverUrl = getProductMainImageUrl(argument); expect(coverUrl).toEqual(""); }); diff --git a/packages/helpers/src/product/getProductMainImageUrl.ts b/packages/helpers/src/product/getProductMainImageUrl.ts index 985419a95..14f30ea4c 100644 --- a/packages/helpers/src/product/getProductMainImageUrl.ts +++ b/packages/helpers/src/product/getProductMainImageUrl.ts @@ -5,12 +5,6 @@ import { Product } from "@shopware-pwa/shopware-6-client/src/interfaces/models/c * * @alpha */ - -/** - * @alpha - */ -export function getProductMainImageUrl({ - product -}: { product?: Product } = {}): string { +export function getProductMainImageUrl(product: Product): string { return product?.cover?.media?.url || product?.cover?.url || ""; } diff --git a/packages/shopware-6-client/__tests__/services/CheckoutService/createOrder.spec.ts b/packages/shopware-6-client/__tests__/services/CheckoutService/createOrder.spec.ts new file mode 100644 index 000000000..62222d280 --- /dev/null +++ b/packages/shopware-6-client/__tests__/services/CheckoutService/createOrder.spec.ts @@ -0,0 +1,73 @@ +import { createOrder, createGuestOrder } from "@shopware-pwa/shopware-6-client"; +import { apiService } from "../../../src/apiService"; + +jest.mock("../../../src/apiService"); +const mockedAxios = apiService as jest.Mocked; + +describe("CheckoutService createOrder", () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + describe("createOrder", () => { + it("should return undefined when there is no data property in the response", async () => { + mockedAxios.post.mockResolvedValueOnce({}); + + const result = await createOrder(); + expect(mockedAxios.post).toBeCalledTimes(1); + expect(mockedAxios.post).toBeCalledWith("/checkout/order"); + expect(result).toBeUndefined(); + }); + it("should return newly added order object", async () => { + mockedAxios.post.mockResolvedValueOnce({ + data: { + data: { + id: "new-order-id" + } + } + }); + + const result = await createOrder(); + expect(mockedAxios.post).toBeCalledTimes(1); + expect(mockedAxios.post).toBeCalledWith("/checkout/order"); + expect(result).toHaveProperty("id"); + }); + }); + describe("createGuestOrder", () => { + it("should return undefined when there is no data property in the response", async () => { + mockedAxios.post.mockResolvedValueOnce({}); + + const result = await createGuestOrder("some@email.com"); + expect(mockedAxios.post).toBeCalledTimes(1); + expect(mockedAxios.post).toBeCalledWith("/checkout/guest-order", { + email: "some@email.com" + }); + expect(result).toBeUndefined(); + }); + it("should return newly added order object", async () => { + mockedAxios.post.mockResolvedValueOnce({ + data: { + data: { + id: "new-order-id" + } + } + }); + + const result = await createGuestOrder("dummy@email.com"); + expect(mockedAxios.post).toBeCalledTimes(1); + expect(mockedAxios.post).toBeCalledWith("/checkout/guest-order", { + email: "dummy@email.com" + }); + expect(result).toHaveProperty("id"); + }); + + it("should throws the error when email is not provided", async () => { + try { + await createGuestOrder(undefined as any); + } catch (e) { + expect(e.message).toBe( + "createGuestOrder method requires email to be provided as a parameter" + ); + } + }); + }); +}); diff --git a/packages/shopware-6-client/src/index.ts b/packages/shopware-6-client/src/index.ts index 3b96df4ca..7af275ab5 100644 --- a/packages/shopware-6-client/src/index.ts +++ b/packages/shopware-6-client/src/index.ts @@ -10,6 +10,8 @@ export * from "./services/contextService"; export * from "./services/cartService"; export * from "./services/navigationService"; export * from "./services/pageService"; +export * from "./services/checkoutService"; + /** * Setup configuration. Merge default values with provided in param. * This method will override existing config. For config update invoke **update** method. diff --git a/packages/shopware-6-client/src/services/checkoutService.ts b/packages/shopware-6-client/src/services/checkoutService.ts new file mode 100644 index 000000000..aecc1549e --- /dev/null +++ b/packages/shopware-6-client/src/services/checkoutService.ts @@ -0,0 +1,36 @@ +import { apiService } from "../apiService"; +import { + getCheckoutOrderEndpoint, + getCheckoutGuestOrderEndpoint +} from "../endpoints"; +import { Order } from "@shopware-pwa/shopware-6-client/src/interfaces/models/checkout/order/Order"; + +/** + * Creates an order for logged in users + * @alpha + */ +export async function createOrder(): Promise { + const resp = await apiService.post(getCheckoutOrderEndpoint()); + + return resp.data?.data; +} + +/** + * Creates an order for not logged in users + * Should be used when the user is logged out, but has items in the cart + * @param email - customers's email + * @alpha + */ +export async function createGuestOrder(email: string): Promise { + if (!email) { + throw new Error( + "createGuestOrder method requires email to be provided as a parameter" + ); + } + + const resp = await apiService.post(getCheckoutGuestOrderEndpoint(), { + email + }); + + return resp.data?.data; +}