From b9a5237e6063f528055833d5df9fe834aa1f2fc4 Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Mon, 17 Apr 2017 10:30:46 +0800 Subject: [PATCH 01/71] Allow customer to move past validation after one pass --- client/modules/accounts/templates/addressBook/add/add.js | 3 ++- .../modules/accounts/templates/addressBook/edit/edit.js | 3 ++- lib/collections/schemas/address.js | 6 ++++++ server/methods/accounts/accounts.js | 8 ++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/client/modules/accounts/templates/addressBook/add/add.js b/client/modules/accounts/templates/addressBook/add/add.js index 9b86e6c4a89..679eaef5cd1 100644 --- a/client/modules/accounts/templates/addressBook/add/add.js +++ b/client/modules/accounts/templates/addressBook/add/add.js @@ -96,7 +96,8 @@ AutoForm.hooks({ const addressBook = $(this.template.firstNode).closest(".address-book"); Meteor.call("accounts/validateAddress", insertDoc, function (err, res) { - if (res.validated) { + // if the address is validated OR the address has already been through the validation process, pass it on + if (res.validated || typeof res.validatedAddress.isValidated === "boolean") { Meteor.call("accounts/addressBookAdd", insertDoc, function (error, result) { if (error) { Alerts.toast(i18next.t("addressBookAdd.failedToAddAddress", { err: error.message }), "error"); diff --git a/client/modules/accounts/templates/addressBook/edit/edit.js b/client/modules/accounts/templates/addressBook/edit/edit.js index b0dd5605f14..aaa1a9e5a0a 100644 --- a/client/modules/accounts/templates/addressBook/edit/edit.js +++ b/client/modules/accounts/templates/addressBook/edit/edit.js @@ -15,7 +15,8 @@ AutoForm.hooks({ const addressBook = $(this.template.firstNode).closest(".address-book"); Meteor.call("accounts/validateAddress", insertDoc, function (err, res) { - if (res.validated) { + // if the address is validated OR the address has already been through the validation process, pass it on + if (res.validated || typeof res.validatedAddress.isValidated === "boolean") { Meteor.call("accounts/addressBookUpdate", insertDoc, (error, result) => { if (error) { Alerts.toast(i18next.t("addressBookEdit.somethingWentWrong", { err: error.message }), "error"); diff --git a/lib/collections/schemas/address.js b/lib/collections/schemas/address.js index c177adfb3c5..eba408c0af9 100644 --- a/lib/collections/schemas/address.js +++ b/lib/collections/schemas/address.js @@ -62,6 +62,12 @@ export const Address = new SimpleSchema({ label: "Make this your default shipping address?", type: Boolean }, + isValidated: { + label: "Has address been validated", + type: Boolean, + defaultValue: false, + optional: true + }, metafields: { type: [Metafield], optional: true diff --git a/server/methods/accounts/accounts.js b/server/methods/accounts/accounts.js index cce0e9bc93c..b456f3d12a0 100644 --- a/server/methods/accounts/accounts.js +++ b/server/methods/accounts/accounts.js @@ -126,15 +126,18 @@ function validateAddress(address) { if (validator) { const validationResult = Meteor.call(validator, address); validatedAddress = validationResult.validatedAddress; + validatedAddress.isValidated = true; formErrors = validationResult.errors; if (validatedAddress) { validationErrors = compareAddress(address, validatedAddress); if (validationErrors.length || formErrors.length) { validated = false; + validatedAddress.isValidated = false; } } else { // No address, fail validation validated = false; + validatedAddress.isValidated = false; } } const validationResults = { validated, fieldErrors: validationErrors, formErrors, validatedAddress }; @@ -151,10 +154,7 @@ Meteor.methods({ */ "accounts/currentUserHasPassword": function () { const user = Meteor.users.findOne(Meteor.userId()); - if (user.services.password) { - return true; - } - return false; + return !!user.services.password; }, /** From 6917f5c6e9451ed57c441e212d0f4b62b69f6bf5 Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Mon, 17 Apr 2017 13:37:46 +0800 Subject: [PATCH 02/71] Silencing needless warning --- imports/plugins/included/jobcontrol/server/jobs/cart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/included/jobcontrol/server/jobs/cart.js b/imports/plugins/included/jobcontrol/server/jobs/cart.js index a8eadc23e16..051c6edeb63 100644 --- a/imports/plugins/included/jobcontrol/server/jobs/cart.js +++ b/imports/plugins/included/jobcontrol/server/jobs/cart.js @@ -23,7 +23,7 @@ Hooks.Events.add("afterCoreInit", () => { cancelRepeats: true }); } else { - Logger.warn("No cart cleanup schedule"); + Logger.debug("No cart cleanup schedule"); } }); From cca678af2169fa2a5336f2f44e179deb2153e258 Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Mon, 17 Apr 2017 15:49:58 +0800 Subject: [PATCH 03/71] Add missing imports --- imports/plugins/core/checkout/server/methods/workflow.js | 4 ++-- server/methods/core/cart.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/imports/plugins/core/checkout/server/methods/workflow.js b/imports/plugins/core/checkout/server/methods/workflow.js index b75e232a6db..5c2339e5607 100644 --- a/imports/plugins/core/checkout/server/methods/workflow.js +++ b/imports/plugins/core/checkout/server/methods/workflow.js @@ -1,5 +1,6 @@ import _ from "lodash"; import { Meteor } from "meteor/meteor"; +import { check, Match } from "meteor/check"; import { Cart, Orders, Packages, Shops } from "/lib/collections"; import { Logger, Reaction } from "/server/api"; @@ -125,8 +126,7 @@ Meteor.methods({ // check to see if the next step has already been processed. // templateProcessedinWorkflow boolean gotoNextWorkflowStep = nextWorkflowStep.template; - templateProcessedinWorkflow = _.includes(currentCart.workflow.workflow, - nextWorkflowStep.template); + templateProcessedinWorkflow = _.includes(currentCart.workflow.workflow, nextWorkflowStep.template); // debug info Logger.debug("currentWorkflowStatus: ", currentWorkflowStatus); diff --git a/server/methods/core/cart.js b/server/methods/core/cart.js index e595cf6a612..42c39faf282 100644 --- a/server/methods/core/cart.js +++ b/server/methods/core/cart.js @@ -1,6 +1,6 @@ import _ from "lodash"; import { Meteor } from "meteor/meteor"; -import { check } from "meteor/check"; +import { check, Match } from "meteor/check"; import * as Collections from "/lib/collections"; import { Logger, Reaction } from "/server/api"; From 433bef618b6f2eca8f4c7c1553d75dfda382549e Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Mon, 17 Apr 2017 15:50:48 +0800 Subject: [PATCH 04/71] When we get a "GetTax" error, revert to addressbook --- .../included/taxes-avalara/server/hooks/hooks.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/imports/plugins/included/taxes-avalara/server/hooks/hooks.js b/imports/plugins/included/taxes-avalara/server/hooks/hooks.js index 6d8e32860f2..71ca5ba7986 100644 --- a/imports/plugins/included/taxes-avalara/server/hooks/hooks.js +++ b/imports/plugins/included/taxes-avalara/server/hooks/hooks.js @@ -28,11 +28,20 @@ MethodHooks.after("taxes/calculate", (options) => { if (pkg && pkg.settings.avalara.enabled && pkg.settings.avalara.performTaxCalculation) { taxCalc.estimateCart(cartToCalc, function (result) { // we don't use totalTax, that just tells us we have a valid tax calculation - if (result && result.totalTax && typeof result.totalTax === "number" && result.lines) { + if (result && !result.error && result.totalTax && typeof result.totalTax === "number" && result.lines) { const taxes = linesToTaxes(result.lines); const taxAmount = taxes.reduce((totalTaxes, tax) => totalTaxes + tax.tax, 0); const taxRate = taxAmount / taxCalc.calcTaxable(cartToCalc); Meteor.call("taxes/setRate", cartId, taxRate, taxes); + } else { + if (result.error.errorCode === 300) { + // could not get tax, typically because of address, but who knows could be anything + Meteor.call("workflow/revertCartWorkflow", "checkoutAddressBook"); + } else if (result.error.errorCode = 503) { + Logger.error("timeout error: do nothing here"); + } else { + Logger.error("Unknown error", result.error.errorCode); + } } }); } From 5b64575cbd56ca366c130ef71dcf3b8533db4b4a Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Mon, 17 Apr 2017 15:57:37 +0800 Subject: [PATCH 05/71] Capture and parse errors --- .../taxes-avalara/server/methods/taxCalc.js | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js b/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js index 461bfbab04c..c55e1888862 100644 --- a/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js +++ b/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js @@ -85,6 +85,34 @@ function getTaxSettings(userId) { return _.get(Accounts.findOne({ _id: userId }), "taxSettings"); } +/** + * @summary: Break Avalara error object into consistent format + * @param {Object} error The error result from Avalara + * @returns {Object} Error object with code and errorDetails + */ +function parseError(error) { + let errorData; + // The Avalara API constantly times out, so handle this special case first + if (error.code === "ETIMEDOUT") { + errorData = { errorCode: 503, errorDetails: { message: "ETIMEDOUT", description: "The request timeod out" } }; + return errorData; + } + const errorDetails = []; + if (error.response.data.error.details) { + const details = error.response.data.error.details; + for (const detail of details) { + if (detail.severity === "Error") { + errorDetails.push({ message: detail.message, description: detail.description }); + } + } + errorData = { errorCode: details[0].number, errorDetails }; + } else { + Avalogger.error("Unknown error or error format"); + throw new Meteor.Error("bad-error", "Unknown error or error format"); + } + return errorData; +} + /** * @summary function to get HTTP data and pass in extra Avalara-specific headers * @param {String} requestUrl - The URL to make the request to @@ -119,14 +147,16 @@ function avaGet(requestUrl, options = {}, testCredentials = true) { logObject.request = allOptions; } + let result; try { result = HTTP.get(requestUrl, allOptions); } catch (error) { - result = error; Logger.error(`Encountered error while calling Avalara API endpoint ${requestUrl}`); Logger.error(error); logObject.error = error; Avalogger.error(logObject); + const parsedError = parseError(error); + result = { error: parsedError }; } if (pkgData.settings.avalara.enableLogging) { @@ -166,7 +196,6 @@ function avaPost(requestUrl, options) { } let result; - try { result = HTTP.post(requestUrl, allOptions); } catch (error) { @@ -175,7 +204,8 @@ function avaPost(requestUrl, options) { logObject.error = error; // whether logging is enabled or not we log out errors Avalogger.error(logObject); - result = {}; + const parsedError = parseError(error); + result = { error: parsedError }; } if (pkgData.settings.avalara.enableLogging) { @@ -420,7 +450,10 @@ taxCalc.estimateCart = function (cart, callback) { const baseUrl = getUrl(); const requestUrl = `${baseUrl}transactions/create`; const result = avaPost(requestUrl, { data: salesOrder }); - return callback(result.data); + if (!result.error) { + return callback(result.data); + } + return callback(result); } }; From 564dfdf9e316f497f48cfe5869b26596dc5b61fc Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Thu, 20 Apr 2017 16:08:33 +0800 Subject: [PATCH 06/71] Creating a "review and fix" address review screen --- .../accounts/templates/addressBook/add/add.js | 28 ++--- .../templates/addressBook/addressBook.html | 1 + .../templates/addressBook/addressBook.js | 20 +++- .../templates/addressBook/review/review.html | 108 ++++++++++++++++++ .../templates/addressBook/review/review.js | 34 ++++++ .../taxes-avalara/server/methods/taxCalc.js | 10 +- server/methods/accounts/accounts.js | 4 +- 7 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 client/modules/accounts/templates/addressBook/review/review.html create mode 100644 client/modules/accounts/templates/addressBook/review/review.js diff --git a/client/modules/accounts/templates/addressBook/add/add.js b/client/modules/accounts/templates/addressBook/add/add.js index 679eaef5cd1..60f87fbbc1b 100644 --- a/client/modules/accounts/templates/addressBook/add/add.js +++ b/client/modules/accounts/templates/addressBook/add/add.js @@ -1,3 +1,4 @@ +import { $ } from "meteor/jquery"; import { i18next } from "/client/api"; import * as Collections from "/lib/collections"; import { Session } from "meteor/session"; @@ -97,7 +98,7 @@ AutoForm.hooks({ Meteor.call("accounts/validateAddress", insertDoc, function (err, res) { // if the address is validated OR the address has already been through the validation process, pass it on - if (res.validated || typeof res.validatedAddress.isValidated === "boolean") { + if (res.validated) { Meteor.call("accounts/addressBookAdd", insertDoc, function (error, result) { if (error) { Alerts.toast(i18next.t("addressBookAdd.failedToAddAddress", { err: error.message }), "error"); @@ -111,21 +112,16 @@ AutoForm.hooks({ } }); } else { - if (res.validatedAddress) { - setValidatedAddress(res); - Alerts.inline("Made changes to your address based upon validation. Please ensure this is correct", "warning", { - placement: "addressBookAdd", - i18nKey: "addressBookAdd.validatedAddress" - }); - } - if (res.formErrors) { - for (const error of res.formErrors) { - Alerts.inline(error.details, "error", { - placement: "addressBookAdd" - }); - } - } - that.done("Validation failed"); // renable Save and Continue button + // set addressState and kick it back to review + const addressState = { + requiresReview: true, + address: insertDoc, + validatedAddress: res.validatedAddress, + formErrors: res.formErrors, + fieldErrors: res.fieldErrors + }; + Session.set("addressState", addressState); + addressBook.trigger($.Event("addressRequiresReview")); } }); } diff --git a/client/modules/accounts/templates/addressBook/addressBook.html b/client/modules/accounts/templates/addressBook/addressBook.html index ed726403095..1737f9dbabe 100644 --- a/client/modules/accounts/templates/addressBook/addressBook.html +++ b/client/modules/accounts/templates/addressBook/addressBook.html @@ -10,6 +10,7 @@

Address Book

diff --git a/client/modules/accounts/templates/addressBook/addressBook.js b/client/modules/accounts/templates/addressBook/addressBook.js index 5946ea1a02b..c91eecb2b1a 100644 --- a/client/modules/accounts/templates/addressBook/addressBook.js +++ b/client/modules/accounts/templates/addressBook/addressBook.js @@ -1,4 +1,5 @@ import { Meteor } from "meteor/meteor"; +import { Session } from "meteor/session"; import { ReactiveVar } from "meteor/reactive-var"; import { i18next } from "/client/api"; import * as Collections from "/lib/collections"; @@ -9,10 +10,20 @@ import { Template } from "meteor/templating"; * template determines which view should be used: * addAddress (edit or add) * addressBookView (view) + * addressBookReview (review errors) */ Template.addressBook.onCreated(function () { - this.currentViewTemplate = ReactiveVar("addressBookAdd"); + let addressState = Session.get("addressState"); + if (!addressState) { + addressState = { requiresReview: false }; + Session.setDefault("addressState", addressState); + } + if (addressState && addressState.requiresReview) { + this.currentViewTemplate = ReactiveVar("addressBookReview"); + } else { + this.currentViewTemplate = ReactiveVar("addressBookAdd"); + } this.templateData = ReactiveVar({}); this.autorun(() => { @@ -22,7 +33,7 @@ Template.addressBook.onCreated(function () { userId: Meteor.userId() }); - if (account) { + if (account && !addressState.requiresReview) { if (account.profile) { if (account.profile.addressBook) { if (account.profile.addressBook.length === 0) { @@ -118,5 +129,10 @@ Template.addressBook.events({ event.stopPropagation(); Template.instance().currentViewTemplate.set("addressBookGrid"); + }, + "addressRequiresReview": (event) => { + event.preventDefault(); + event.stopPropagation(); + Template.instance().currentViewTemplate.set("addressBookReview"); } }); diff --git a/client/modules/accounts/templates/addressBook/review/review.html b/client/modules/accounts/templates/addressBook/review/review.html new file mode 100644 index 00000000000..e7fdfc2d6ed --- /dev/null +++ b/client/modules/accounts/templates/addressBook/review/review.html @@ -0,0 +1,108 @@ + diff --git a/client/modules/accounts/templates/addressBook/review/review.js b/client/modules/accounts/templates/addressBook/review/review.js new file mode 100644 index 00000000000..0947d860b31 --- /dev/null +++ b/client/modules/accounts/templates/addressBook/review/review.js @@ -0,0 +1,34 @@ +import { Template } from "meteor/templating"; +import { ReactiveDict } from "meteor/reactive-dict"; +import { Session } from "meteor/session"; + + +Template.addressBookReview.onCreated(function () { + const addressState = Session.get("addressState"); + console.log("addressState", addressState); + this.state = new ReactiveDict(); + this.state.set("address", addressState.address); + this.state.set("validatedAddress", addressState.validatedAddress); + this.state.set("formErrors", addressState.formErrors); + this.state.set("fieldErrors", addressState.fieldErrors); +}); + +Template.addressBookReview.helpers({ + address: function () { + const address = Template.instance().state.get("address"); + return address; + }, + validatedAddress: function () { + const validatedAddress = Template.instance().state.get("validatedAddress"); + return validatedAddress; + }, + formErrors: function () { + const formErrors = Template.instance().state.get("formErrors"); + return formErrors; + }, + hasAddress2: function () { + const address = Template.instance().state.get("address"); + const validatedAddress = Template.instance().state.get("validatedAddress"); + return !!address.address2 || !!validatedAddress.address2; + } +}); diff --git a/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js b/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js index c55e1888862..6d9a757bd09 100644 --- a/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js +++ b/imports/plugins/included/taxes-avalara/server/methods/taxCalc.js @@ -290,13 +290,7 @@ taxCalc.validateAddress = function (address) { const baseUrl = getUrl(); const requestUrl = `${baseUrl}addresses/resolve`; const result = avaPost(requestUrl, { data: addressToValidate }); - let content; - - try { - content = JSON.parse(result.content); - } catch (error) { - content = result.content; - } + const content = result.data; if (content && content.messages) { messages = content.messages; } @@ -315,7 +309,7 @@ taxCalc.validateAddress = function (address) { postal: resultAddress.postalCode, country: resultAddress.country }; - if (result.data.address.line2) { + if (resultAddress.line2) { validatedAddress.addresss2 = resultAddress.line2; } } diff --git a/server/methods/accounts/accounts.js b/server/methods/accounts/accounts.js index b456f3d12a0..f161842a0ae 100644 --- a/server/methods/accounts/accounts.js +++ b/server/methods/accounts/accounts.js @@ -126,11 +126,12 @@ function validateAddress(address) { if (validator) { const validationResult = Meteor.call(validator, address); validatedAddress = validationResult.validatedAddress; - validatedAddress.isValidated = true; formErrors = validationResult.errors; if (validatedAddress) { + console.log("got validated address"); validationErrors = compareAddress(address, validatedAddress); if (validationErrors.length || formErrors.length) { + console.log("failing validation"); validated = false; validatedAddress.isValidated = false; } @@ -141,6 +142,7 @@ function validateAddress(address) { } } const validationResults = { validated, fieldErrors: validationErrors, formErrors, validatedAddress }; + console.log("validation results", validationResults); return validationResults; } From 7f7a02afae801a4daca90766401fac41f9ba720e Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Fri, 21 Apr 2017 11:01:34 +0800 Subject: [PATCH 07/71] Show highlited diff. Copy over values on click. --- .../templates/addressBook/review/review.html | 37 ++++++++----- .../templates/addressBook/review/review.js | 20 ++++++- .../client/styles/cart/addressBook.less | 10 ++++ server/methods/accounts/accounts.js | 52 +++++++++++++------ 4 files changed, 87 insertions(+), 32 deletions(-) diff --git a/client/modules/accounts/templates/addressBook/review/review.html b/client/modules/accounts/templates/addressBook/review/review.html index e7fdfc2d6ed..54b97b0c95e 100644 --- a/client/modules/accounts/templates/addressBook/review/review.html +++ b/client/modules/accounts/templates/addressBook/review/review.html @@ -1,7 +1,7 @@