diff --git a/CHANGELOG.md b/CHANGELOG.md index b2c5a05349..4fce9a0be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix an issue where the index.html template within a theme is ignored - @EnthrallRecords (#2489) - Inconsistent filters behaviour - clear filters on page load - @patzick (#2435) - fix price is never below 0 and user can't add 0 or below 0 products to cart @RakowskiPrzemyslaw (#2437) +- Check for placing single order in case of error in any payment module - @patzick (#2409) ### Deprecated / Removed - `@vue-storefront/store` package deprecated - @filrak diff --git a/core/modules/checkout/store/checkout/actions.ts b/core/modules/checkout/store/checkout/actions.ts index ef84492316..cbbbbcb1b1 100644 --- a/core/modules/checkout/store/checkout/actions.ts +++ b/core/modules/checkout/store/checkout/actions.ts @@ -2,7 +2,6 @@ import Vue from 'vue' import { ActionTree } from 'vuex' import * as types from './mutation-types' import i18n from '@vue-storefront/i18n' -import rootStore from '@vue-storefront/core/store' import RootState from '@vue-storefront/core/types/RootState' import CheckoutState from '../../types/CheckoutState' import { Logger } from '@vue-storefront/core/lib/logger' @@ -13,25 +12,24 @@ const actions: ActionTree = { * @param {Object} commit method * @param {Object} order order data to be send */ - placeOrder (context, { order }) { + async placeOrder ({ state, commit, dispatch }, { order }) { try { - return context.dispatch('order/placeOrder', order, {root: true}).then(result => { - if (!result.resultCode || result.resultCode === 200) { - Vue.prototype.$db.usersCollection.setItem('last-cart-bypass-ts', new Date().getTime()) - context.dispatch('cart/clear', {}, {root: true}) - if (context.state.personalDetails.createAccount) { - context.commit(types.CHECKOUT_DROP_PASSWORD) - } + const result = await dispatch('order/placeOrder', order, {root: true}) + if (!result.resultCode || result.resultCode === 200) { + Vue.prototype.$db.usersCollection.setItem('last-cart-bypass-ts', new Date().getTime()) + dispatch('cart/clear', {}, {root: true}) + if (state.personalDetails.createAccount) { + commit(types.CHECKOUT_DROP_PASSWORD) } - }) + } } catch (e) { if (e.name === 'ValidationError') { Logger.error('Internal validation error; Order entity is not compliant with the schema' + e.messages, 'checkout')() - rootStore.dispatch('notification/spawnNotification', { + dispatch('notification/spawnNotification', { type: 'error', message: i18n.t('Internal validation error. Please check if all required fields are filled in. Please contact us on contributors@vuestorefront.io'), action1: { label: i18n.t('OK') } - }) + }, {root: true}) } else { Logger.error(e, 'checkout')() } diff --git a/core/modules/checkout/store/checkout/getters.ts b/core/modules/checkout/store/checkout/getters.ts index 855fe5ecaf..993819c590 100644 --- a/core/modules/checkout/store/checkout/getters.ts +++ b/core/modules/checkout/store/checkout/getters.ts @@ -1,5 +1,9 @@ -export default { - isThankYouPage (state) { - return state.isThankYouPage - } +import { GetterTree } from 'vuex' +import CheckoutState from '../../types/CheckoutState' +import RootState from '@vue-storefront/core/types/RootState' + +const getters: GetterTree = { + isThankYouPage: state => state.isThankYouPage } + +export default getters diff --git a/core/modules/order/store/actions.ts b/core/modules/order/store/actions.ts index ea9f093e23..57b3e02cde 100644 --- a/core/modules/order/store/actions.ts +++ b/core/modules/order/store/actions.ts @@ -9,13 +9,21 @@ import rootStore from '@vue-storefront/core/store' import { isOnline } from '@vue-storefront/core/lib/search' import i18n from '@vue-storefront/i18n' import { TaskQueue } from '@vue-storefront/core/lib/sync' +import { sha3_224 } from 'js-sha3' + const actions: ActionTree = { /** * Place order - send it to service worker queue * @param {Object} commit method * @param {Order} order order data to be send */ - async placeOrder ({ commit }, order:Order) { + async placeOrder ({ commit, getters }, order:Order) { + // Check if order is already processed/processing + const currentOrderHash = sha3_224(JSON.stringify(order)) + const isAlreadyProcessed = getters.getSessionOrderHashes.includes(currentOrderHash) + if (isAlreadyProcessed) return + commit(types.ORDER_ADD_SESSION_ORDER_HASH, currentOrderHash) + const storeView = currentStoreView() if (storeView.storeCode) { order.store_code = storeView.storeCode @@ -48,6 +56,7 @@ const actions: ActionTree = { } return task } catch (e) { + commit(types.ORDER_REMOVE_SESSION_ORDER_HASH, currentOrderHash) rootStore.dispatch('notification/spawnNotification', { type: 'error', message: i18n.t('The order can not be transfered because of server error. Order has been queued'), diff --git a/core/modules/order/store/getters.ts b/core/modules/order/store/getters.ts new file mode 100644 index 0000000000..8b73ef39c3 --- /dev/null +++ b/core/modules/order/store/getters.ts @@ -0,0 +1,9 @@ +import { GetterTree } from 'vuex' +import OrderState from '../types/OrderState' +import RootState from '@vue-storefront/core/types/RootState' + +const getters: GetterTree = { + getSessionOrderHashes: state => state.session_order_hashes +} + +export default getters diff --git a/core/modules/order/store/index.ts b/core/modules/order/store/index.ts index e075c81fdc..39cfb9779c 100644 --- a/core/modules/order/store/index.ts +++ b/core/modules/order/store/index.ts @@ -1,15 +1,18 @@ import { Module } from 'vuex' import actions from './actions' import mutations from './mutations' +import getters from './getters' import RootState from '@vue-storefront/core/types/RootState' import OrderState from '../types/OrderState' export const module: Module = { namespaced: true, state: { - last_order_confirmation: null + last_order_confirmation: null, + session_order_hashes: [] }, actions, - mutations + mutations, + getters } diff --git a/core/modules/order/store/mutation-types.ts b/core/modules/order/store/mutation-types.ts index 2f00db7984..cb9dba69a1 100644 --- a/core/modules/order/store/mutation-types.ts +++ b/core/modules/order/store/mutation-types.ts @@ -1,4 +1,6 @@ export const SN_ORDER = 'order' export const ORDER_PLACE_ORDER = SN_ORDER + '/PLACE_ORDER' export const ORDER_PROCESS_QUEUE = SN_ORDER + '/PROCESS_QUEUE' -export const ORDER_LAST_ORDER_WITH_CONFIRMATION = SN_ORDER + '/LAST_ORDER_CONFIRMATION' \ No newline at end of file +export const ORDER_LAST_ORDER_WITH_CONFIRMATION = SN_ORDER + '/LAST_ORDER_CONFIRMATION' +export const ORDER_ADD_SESSION_ORDER_HASH = SN_ORDER + '/ADD_SESSION_ORDER_HASH' +export const ORDER_REMOVE_SESSION_ORDER_HASH = SN_ORDER + '/REMOVE_SESSION_ORDER_HASH' \ No newline at end of file diff --git a/core/modules/order/store/mutations.ts b/core/modules/order/store/mutations.ts index ef766181d6..bb6780e52c 100644 --- a/core/modules/order/store/mutations.ts +++ b/core/modules/order/store/mutations.ts @@ -30,6 +30,12 @@ const mutations: MutationTree = { }, [types.ORDER_LAST_ORDER_WITH_CONFIRMATION] (state, payload) { state.last_order_confirmation = payload + }, + [types.ORDER_ADD_SESSION_ORDER_HASH] (state, hash: string) { + state.session_order_hashes.push(hash) + }, + [types.ORDER_REMOVE_SESSION_ORDER_HASH] (state, hash: string) { + state.session_order_hashes = state.session_order_hashes.filter(sessionHash => sessionHash !== hash) } } diff --git a/core/modules/order/types/OrderState.ts b/core/modules/order/types/OrderState.ts index 93cedffff4..b46794fc79 100644 --- a/core/modules/order/types/OrderState.ts +++ b/core/modules/order/types/OrderState.ts @@ -1,3 +1,4 @@ export default interface OrderState { - last_order_confirmation: any + last_order_confirmation: any, + session_order_hashes: Array, } diff --git a/src/themes/default/components/core/blocks/Form/BaseCheckbox.vue b/src/themes/default/components/core/blocks/Form/BaseCheckbox.vue index f0f3745230..5a308c3e25 100644 --- a/src/themes/default/components/core/blocks/Form/BaseCheckbox.vue +++ b/src/themes/default/components/core/blocks/Form/BaseCheckbox.vue @@ -41,7 +41,7 @@ export default { required: true }, validations: { - type: Object, + type: Array, default: () => [] }, disabled: { diff --git a/src/themes/default/components/core/blocks/Form/BaseRadiobutton.vue b/src/themes/default/components/core/blocks/Form/BaseRadiobutton.vue index 71df89c2e0..19edd7ace0 100644 --- a/src/themes/default/components/core/blocks/Form/BaseRadiobutton.vue +++ b/src/themes/default/components/core/blocks/Form/BaseRadiobutton.vue @@ -41,7 +41,7 @@ export default { required: true }, validations: { - type: Object, + type: Array, default: () => [] }, disabled: {