From c496c64320356937dea1565ae82aecec68b3b68a Mon Sep 17 00:00:00 2001 From: Baroshem Date: Sun, 12 Sep 2021 12:26:30 +0200 Subject: [PATCH 1/4] feat: #82 add checkout middleware --- packages/theme/helpers/checkout.ts | 9 ++++++ packages/theme/helpers/constants.ts | 1 + packages/theme/helpers/index.ts | 1 + packages/theme/middleware/checkout.js | 36 ++++++++++++++++++++++ packages/theme/nuxt.config.js | 3 +- packages/theme/pages/Checkout/Customer.vue | 16 +++++++++- 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 packages/theme/helpers/checkout.ts create mode 100644 packages/theme/middleware/checkout.js diff --git a/packages/theme/helpers/checkout.ts b/packages/theme/helpers/checkout.ts new file mode 100644 index 00000000..4a24427a --- /dev/null +++ b/packages/theme/helpers/checkout.ts @@ -0,0 +1,9 @@ +import { ARRANGING_PAYMENT } from './constants' + +export const canEnterPayment = cart => canEnterShipping(cart) && canEnterBilling(cart) && cart?.shipping && cart?.state === ARRANGING_PAYMENT; + +export const canEnterThankYou = query => Boolean(query?.order); + +export const canEnterShipping = cart => Boolean(cart?.customer); + +export const canEnterBilling = cart => cart?.shippingAddress?.streetLine1 && cart?.shippingAddress?.country diff --git a/packages/theme/helpers/constants.ts b/packages/theme/helpers/constants.ts index 9ab05586..36a034b6 100644 --- a/packages/theme/helpers/constants.ts +++ b/packages/theme/helpers/constants.ts @@ -4,3 +4,4 @@ export const COUNTRIES = [ ]; export const EMAIL_ADDRESS_CONFLICT_ERROR = 'EMAIL_ADDRESS_CONFLICT_ERROR'; +export const ARRANGING_PAYMENT = 'ArrangingPayment'; diff --git a/packages/theme/helpers/index.ts b/packages/theme/helpers/index.ts index 0ab8a7e9..ab32dd74 100644 --- a/packages/theme/helpers/index.ts +++ b/packages/theme/helpers/index.ts @@ -2,3 +2,4 @@ export * from './category'; export * from './shipping-billing'; export * from './constants'; export * from './product'; +export * from './checkout'; diff --git a/packages/theme/middleware/checkout.js b/packages/theme/middleware/checkout.js new file mode 100644 index 00000000..6a2f60ae --- /dev/null +++ b/packages/theme/middleware/checkout.js @@ -0,0 +1,36 @@ +import { canEnterShipping, canEnterBilling, canEnterPayment, canEnterThankYou } from '../helpers'; + +export default async ({ app, $vsf }) => { + const currentPath = app.context.route.fullPath.split('/checkout/')[1]; + const currentQuery = app.context.route.query; + + if (!currentPath) return; + + const cart = await $vsf.$vendure.api.getCart(); + const activeCart = cart?.data?.activeOrder; + + if (!cart?.data || !activeCart) return; + + switch (currentPath) { + case 'shipping': + if (!canEnterShipping(activeCart)) { + app.context.redirect('/'); + } + break; + case 'billing': + if (!canEnterBilling(activeCart)) { + app.context.redirect('/'); + } + break; + case 'payment': + if (!canEnterPayment(activeCart)) { + app.context.redirect('/'); + } + break; + case 'thank-you': + if (!canEnterThankYou(currentQuery)) { + app.context.redirect('/'); + } + break; + } +}; diff --git a/packages/theme/nuxt.config.js b/packages/theme/nuxt.config.js index 0baffd10..d350b00d 100644 --- a/packages/theme/nuxt.config.js +++ b/packages/theme/nuxt.config.js @@ -129,7 +129,8 @@ export default { extendRoutes(routes) { getRoutes(`${__dirname}/_theme`) .forEach((route) => routes.unshift(route)); - } + }, + middleware: ['checkout'], }, publicRuntimeConfig: { theme diff --git a/packages/theme/pages/Checkout/Customer.vue b/packages/theme/pages/Checkout/Customer.vue index 23751b7a..40c0d071 100644 --- a/packages/theme/pages/Checkout/Customer.vue +++ b/packages/theme/pages/Checkout/Customer.vue @@ -85,10 +85,11 @@ import { SfButton, SfSelect } from '@storefront-ui/vue'; -import { ref } from '@vue/composition-api'; +import { ref, onMounted } from '@vue/composition-api'; import { required, min, digits, email } from 'vee-validate/dist/rules'; import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'; import { useVSFContext } from '@vue-storefront/core'; +import { useCart } from '@vue-storefront/vendure'; import { EMAIL_ADDRESS_CONFLICT_ERROR } from '~/helpers'; extend('required', { @@ -122,6 +123,7 @@ export default { setup (_, { root }) { const isFormSubmitted = ref(false); const { $vendure } = useVSFContext(); + const { cart, load } = useCart(); const errorMessage = ref(''); const form = ref({ @@ -140,6 +142,18 @@ export default { isFormSubmitted.value = true; }; + onMounted(async () => { + await load(); + const customer = cart?.value.customer; + if (customer) { + form.value = { + firstName: customer?.firstName, + lastName: customer?.lastName, + emailAddress: customer?.emailAddress, + } + } + }); + return { isFormSubmitted, form, From bf64e70b9abbf4a2d54d0ef14d0584124262c514 Mon Sep 17 00:00:00 2001 From: Baroshem Date: Sun, 12 Sep 2021 12:32:25 +0200 Subject: [PATCH 2/4] feat: #82 update changelog and docs --- docs/.vuepress/config.js | 1 + docs/changelog/1.0.0-beta.2.md | 1 + docs/theme/checkout-middleware.md | 14 ++++++++++++++ docs/theme/helpers.md | 12 ++++++++++++ 4 files changed, 28 insertions(+) create mode 100644 docs/theme/checkout-middleware.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 321be3f8..8e767b0b 100755 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -59,6 +59,7 @@ module.exports = { ['/theme/customer-checkout', 'Customer step in checkout'], ['/theme/composables', 'Composables'], ['/theme/helpers', 'Helpers'], + ['/theme/checkout-middleware', 'Checkout middleware'], ] }, { diff --git a/docs/changelog/1.0.0-beta.2.md b/docs/changelog/1.0.0-beta.2.md index 563271b3..e018952e 100644 --- a/docs/changelog/1.0.0-beta.2.md +++ b/docs/changelog/1.0.0-beta.2.md @@ -9,3 +9,4 @@ * [Docs]: generate API Client reference in the docs [#45](https://github.com/vuestorefront/vendure/issues/45) * [Docs]: add section about theme in docs [#55](https://github.com/vuestorefront/vendure/issues/55) * [Feature]: update packages due to security report [#85](https://github.com/vuestorefront/vendure/issues/85) +* [Feature]: implement checkout middleware and fix payment error [#82](https://github.com/vuestorefront/vendure/issues/82) diff --git a/docs/theme/checkout-middleware.md b/docs/theme/checkout-middleware.md new file mode 100644 index 00000000..9cd39bba --- /dev/null +++ b/docs/theme/checkout-middleware.md @@ -0,0 +1,14 @@ +# Checkout middleware + +Router middleware responsible for checking if customer can visit certain steps of checkout page. +Uses following helpers to verify if a page can be displayed to the customer: + +* `canEnterPayment` + +* `canEnterThankYou` + +* `canEnterShipping` + +* `canEnterBilling` + +If a helper returns false, customer will be redirected to homepage. diff --git a/docs/theme/helpers.md b/docs/theme/helpers.md index 03ffd90d..757dc510 100644 --- a/docs/theme/helpers.md +++ b/docs/theme/helpers.md @@ -16,6 +16,8 @@ const getTreeWithoutEmptyCategories = (categoryTree: AgnosticCategoryTree[]): Ag * `EMAIL_ADDRESS_CONFLICT_ERROR` - Vendure error when a customer email was already used +* `ARRANGING_PAYMENT` - state in checkout that needs to be transitioned in order to correctly process checkout. + ## Product * `getProductVariantByConfiguration` - returns a product variant that was configures from options (i.e. `screen-size`, `ram`) @@ -37,3 +39,13 @@ const mapOrderAddressToAddressForm = (orderAddress: OrderAddress): AddressForm const getCalculatedPrice = (price: number): number ``` + +## Checkout + +* `canEnterPayment` - verifies if customer can enter this state + +* `canEnterThankYou` - verifies if customer can enter this state + +* `canEnterShipping` - verifies if customer can enter this state + +* `canEnterBilling` - verifies if customer can enter this state From e5a68455bc4c22fd97e6a37b034aaf4dc7331563 Mon Sep 17 00:00:00 2001 From: Baroshem Date: Sun, 12 Sep 2021 12:46:10 +0200 Subject: [PATCH 3/4] feat: #82 run lint --- packages/composables/src/types/types.ts | 2 +- packages/theme/helpers/checkout.ts | 12 +++++++----- packages/theme/middleware/checkout.js | 3 +-- packages/theme/pages/Checkout/Customer.vue | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/composables/src/types/types.ts b/packages/composables/src/types/types.ts index a6e28571..7e8e4917 100644 --- a/packages/composables/src/types/types.ts +++ b/packages/composables/src/types/types.ts @@ -67,7 +67,7 @@ export type SearchResultValue = { input?: SEARCH_INPUT; } -export { OrderAddress } from '@vue-storefront/vendure-api'; +export { OrderAddress, Order } from '@vue-storefront/vendure-api'; // TODO: Replace later with types from vendure-api after implementing api-client functions export interface ForgotPasswordResult { diff --git a/packages/theme/helpers/checkout.ts b/packages/theme/helpers/checkout.ts index 4a24427a..f272243a 100644 --- a/packages/theme/helpers/checkout.ts +++ b/packages/theme/helpers/checkout.ts @@ -1,9 +1,11 @@ -import { ARRANGING_PAYMENT } from './constants' +import { Context } from '@nuxt/types'; +import { Order } from '@vue-storefront/vendure'; +import { ARRANGING_PAYMENT } from './constants'; -export const canEnterPayment = cart => canEnterShipping(cart) && canEnterBilling(cart) && cart?.shipping && cart?.state === ARRANGING_PAYMENT; +export const canEnterThankYou = (context: Context): boolean => Boolean(context.route.query?.order); -export const canEnterThankYou = query => Boolean(query?.order); +export const canEnterShipping = (cart: Order): boolean => Boolean(cart?.customer); -export const canEnterShipping = cart => Boolean(cart?.customer); +export const canEnterBilling = (cart: Order): boolean => Boolean(cart?.shippingAddress?.streetLine1 && cart?.shippingAddress?.country); -export const canEnterBilling = cart => cart?.shippingAddress?.streetLine1 && cart?.shippingAddress?.country +export const canEnterPayment = (cart: Order): boolean => canEnterShipping(cart) && canEnterBilling(cart) && cart?.shipping && cart?.state === ARRANGING_PAYMENT; diff --git a/packages/theme/middleware/checkout.js b/packages/theme/middleware/checkout.js index 6a2f60ae..2f5bbea7 100644 --- a/packages/theme/middleware/checkout.js +++ b/packages/theme/middleware/checkout.js @@ -2,7 +2,6 @@ import { canEnterShipping, canEnterBilling, canEnterPayment, canEnterThankYou } export default async ({ app, $vsf }) => { const currentPath = app.context.route.fullPath.split('/checkout/')[1]; - const currentQuery = app.context.route.query; if (!currentPath) return; @@ -28,7 +27,7 @@ export default async ({ app, $vsf }) => { } break; case 'thank-you': - if (!canEnterThankYou(currentQuery)) { + if (!canEnterThankYou(app.context)) { app.context.redirect('/'); } break; diff --git a/packages/theme/pages/Checkout/Customer.vue b/packages/theme/pages/Checkout/Customer.vue index 40c0d071..05b3d4fd 100644 --- a/packages/theme/pages/Checkout/Customer.vue +++ b/packages/theme/pages/Checkout/Customer.vue @@ -149,8 +149,8 @@ export default { form.value = { firstName: customer?.firstName, lastName: customer?.lastName, - emailAddress: customer?.emailAddress, - } + emailAddress: customer?.emailAddress + }; } }); From d3c0ea3c9472e7e31e9ed8c2f878940d3bd92e2c Mon Sep 17 00:00:00 2001 From: Baroshem Date: Tue, 14 Sep 2021 17:07:53 +0200 Subject: [PATCH 4/4] feat: #82 add CheckoutSteps enum --- packages/theme/helpers/checkout.ts | 7 ++++++ packages/theme/middleware/checkout.js | 35 ++++++++++----------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/theme/helpers/checkout.ts b/packages/theme/helpers/checkout.ts index f272243a..76a8f903 100644 --- a/packages/theme/helpers/checkout.ts +++ b/packages/theme/helpers/checkout.ts @@ -9,3 +9,10 @@ export const canEnterShipping = (cart: Order): boolean => Boolean(cart?.customer export const canEnterBilling = (cart: Order): boolean => Boolean(cart?.shippingAddress?.streetLine1 && cart?.shippingAddress?.country); export const canEnterPayment = (cart: Order): boolean => canEnterShipping(cart) && canEnterBilling(cart) && cart?.shipping && cart?.state === ARRANGING_PAYMENT; + +export enum CheckoutSteps { + Shipping = 'shipping', + Billing = 'billing', + Payment = 'payment', + ThankYou = 'thank-you' +} diff --git a/packages/theme/middleware/checkout.js b/packages/theme/middleware/checkout.js index 2f5bbea7..48df4b91 100644 --- a/packages/theme/middleware/checkout.js +++ b/packages/theme/middleware/checkout.js @@ -1,4 +1,4 @@ -import { canEnterShipping, canEnterBilling, canEnterPayment, canEnterThankYou } from '../helpers'; +import { canEnterShipping, canEnterBilling, canEnterPayment, canEnterThankYou, CheckoutSteps } from '../helpers'; export default async ({ app, $vsf }) => { const currentPath = app.context.route.fullPath.split('/checkout/')[1]; @@ -10,26 +10,17 @@ export default async ({ app, $vsf }) => { if (!cart?.data || !activeCart) return; - switch (currentPath) { - case 'shipping': - if (!canEnterShipping(activeCart)) { - app.context.redirect('/'); - } - break; - case 'billing': - if (!canEnterBilling(activeCart)) { - app.context.redirect('/'); - } - break; - case 'payment': - if (!canEnterPayment(activeCart)) { - app.context.redirect('/'); - } - break; - case 'thank-you': - if (!canEnterThankYou(app.context)) { - app.context.redirect('/'); - } - break; + if (currentPath === CheckoutSteps.Shipping && !canEnterShipping(activeCart)) { + app.context.redirect('/'); + + } else if (currentPath === CheckoutSteps.Billing && !canEnterBilling(activeCart)) { + app.context.redirect('/'); + + } else if (currentPath === CheckoutSteps.Payment && !canEnterPayment(activeCart)) { + app.context.redirect('/'); + + } else if (currentPath === CheckoutSteps.ThankYou && !canEnterThankYou(app.context)) { + app.context.redirect('/'); + } };