diff --git a/imports/plugins/core/shipping/client/templates/checkout/shipping.js b/imports/plugins/core/shipping/client/templates/checkout/shipping.js index 7fd30a08ec5..faacc7de81d 100644 --- a/imports/plugins/core/shipping/client/templates/checkout/shipping.js +++ b/imports/plugins/core/shipping/client/templates/checkout/shipping.js @@ -1,10 +1,33 @@ import _ from "lodash"; import { Meteor } from "meteor/meteor"; +import { EJSON } from "meteor/ejson"; import { Template } from "meteor/templating"; import { ReactiveDict } from "meteor/reactive-dict"; import { Reaction } from "/client/api"; import { Cart } from "/lib/collections"; + +// Because we are duplicating shipment quotes across shipping records +// we will get duplicate shipping quotes but we only want to diplay one +// So this function eliminates duplicates +/** + * Return a unique list of objects + * @param {Array} objs - An array of objects + * @returns {Array} An array of object only containing unique members + * @private + */ +function uniqObjects(objs) { + const jsonBlobs = objs.map((obj) => { + return JSON.stringify(obj); + }); + const uniqueBlobs = _.uniq(jsonBlobs); + return uniqueBlobs.map((blob) => { + return EJSON.parse(blob); + }); +} + +// cartShippingQuotes +// returns multiple methods /** * cartShippingQuotes - returns a list of all the shipping costs/quotations * of each available shipping carrier like UPS, Fedex etc. @@ -16,24 +39,18 @@ import { Cart } from "/lib/collections"; function cartShippingQuotes(currentCart) { const cart = currentCart || Cart.findOne(); const shipmentQuotes = []; - const primaryShopId = Reaction.getPrimaryShopId(); if (cart) { if (cart.shipping) { for (const shipping of cart.shipping) { if (shipping.shipmentQuotes) { for (const quote of shipping.shipmentQuotes) { - if (shipping.shopId === primaryShopId) { - if (quote.carrier === "Flat Rate" || quote.requestStatus !== "error") { - shipmentQuotes.push(quote); - } - } + shipmentQuotes.push(quote); } } } } } - - return shipmentQuotes; + return uniqObjects(shipmentQuotes); } function shippingMethodsQueryStatus(currentCart) { @@ -66,13 +83,10 @@ function shippingMethodsQueryStatus(currentCart) { function cartShipmentMethods() { const cart = Cart.findOne(); const shipmentMethods = []; - const primaryShopId = Reaction.getPrimaryShopId(); if (cart) { if (cart.shipping) { for (const shipping of cart.shipping) { - if (shipping.shipmentMethod && shipping.shopId === primaryShopId) { - shipmentMethods.push(shipping.shipmentMethod); - } + shipmentMethods.push(shipping.shipmentMethod); } } } diff --git a/imports/plugins/included/shipping-rates/server/hooks/hooks.js b/imports/plugins/included/shipping-rates/server/hooks/hooks.js index 73cf1ddf4b3..c64eedcdefb 100644 --- a/imports/plugins/included/shipping-rates/server/hooks/hooks.js +++ b/imports/plugins/included/shipping-rates/server/hooks/hooks.js @@ -1,4 +1,5 @@ import { check } from "meteor/check"; +import { Meteor } from "meteor/meteor"; import { Shipping, Packages } from "/lib/collections"; import { Logger, Reaction, Hooks } from "/server/api"; import { Cart as CartSchema } from "/lib/collections/schemas"; @@ -34,20 +35,35 @@ function getShippingRates(previousQueryResults, cart) { return previousQueryResults; } } - if (!(cart.shipping && cart.shipping[0] && cart.shipping[0].address)) { + + // Verify that we have shipping records + if (!cart.shipping || !cart.shipping.length) { const errorDetails = { requestStatus: "error", - shippingProvider: "shippo", - message: "The 'shipping' property of this cart is either missing or incomplete." + shippingProvider: "flat-rate-shipping", + message: "this cart is missing shipping records" }; - rates.push(errorDetails); - return [rates, retrialTargets]; + return [[errorDetails], []]; } - if (!(cart.items && cart.items[0] && cart.items[0].parcel)) { + + // Verify that we have a valid address to work with + let shippingErrorDetails; + if (cart.shipping.find((shippingRecord) => !shippingRecord.address)) { + shippingErrorDetails = { + requestStatus: "error", + shippingProvider: "flat-rate-shipping", + message: "The address property on one or more shipping records are incomplete" + }; + return [[shippingErrorDetails], []]; + } + + // Validate that we have valid items to work with. We should never get here since we filter for this + // at the cart level + if (!cart.items || !cart.items.length) { const errorDetails = { requestStatus: "error", - shippingProvider: "shippo", - message: "This cart has no items, or the first item has no 'parcel' property." + shippingProvider: "flat-rate-shipping", + message: "this cart has no items" }; return [[errorDetails], []]; } @@ -58,21 +74,20 @@ function getShippingRates(previousQueryResults, cart) { merchantShippingRates = marketplaceSettings.public.merchantShippingRates; } - // TODO: Check to see if the merchantShippingRates flag is set in - // marketplace and get rates from the correct shop. - const pkgData = Packages.findOne({ - name: "reaction-shipping-rates", - shopId: Reaction.getPrimaryShopId() - }); + let pkgData; + if (merchantShippingRates) { + // TODO this needs to be rewritten to handle getting rates from each shops that's represented on the order + Logger.fatal("Multiple shipping providers is currently not supported"); + throw new Meteor.Error("not-implemented", "Multiple shipping providers is currently not supported"); + } else { + pkgData = Packages.findOne({ + name: "reaction-shipping-rates", + shopId: Reaction.getPrimaryShopId() + }); + } + if (!pkgData || !cart.items || pkgData.settings.flatRates.enabled !== true) { - const errorDetails = { - requestStatus: "error", - shippingProvider: "flat-rate-shipping", - message: "Error. Flat rate shipping might be uninstalled or disabled, or your cart is empty." - }; - // There's no need for a retry in this case. - rates.push(errorDetails); return [rates, retrialTargets]; } @@ -82,6 +97,8 @@ function getShippingRates(previousQueryResults, cart) { "provider.enabled": true }; + // Get rates from shops if merchantShippingRates is enabled + // Otherwise just get them from the primaryShop if (merchantShippingRates) { // create an array of shops, allowing // the cart to have products from multiple shops @@ -90,7 +107,6 @@ function getShippingRates(previousQueryResults, cart) { shops.push(product.shopId); } } - // if we have multiple shops in cart if ((shops !== null ? shops.length : void 0) > 0) { selector = { diff --git a/imports/plugins/included/shippo/client/index.js b/imports/plugins/included/shipping-shippo/client/index.js similarity index 100% rename from imports/plugins/included/shippo/client/index.js rename to imports/plugins/included/shipping-shippo/client/index.js diff --git a/imports/plugins/included/shippo/client/settings/carriers.html b/imports/plugins/included/shipping-shippo/client/settings/carriers.html similarity index 100% rename from imports/plugins/included/shippo/client/settings/carriers.html rename to imports/plugins/included/shipping-shippo/client/settings/carriers.html diff --git a/imports/plugins/included/shippo/client/settings/carriers.js b/imports/plugins/included/shipping-shippo/client/settings/carriers.js similarity index 100% rename from imports/plugins/included/shippo/client/settings/carriers.js rename to imports/plugins/included/shipping-shippo/client/settings/carriers.js diff --git a/imports/plugins/included/shippo/client/settings/shippo.html b/imports/plugins/included/shipping-shippo/client/settings/shippo.html similarity index 100% rename from imports/plugins/included/shippo/client/settings/shippo.html rename to imports/plugins/included/shipping-shippo/client/settings/shippo.html diff --git a/imports/plugins/included/shippo/client/settings/shippo.js b/imports/plugins/included/shipping-shippo/client/settings/shippo.js similarity index 100% rename from imports/plugins/included/shippo/client/settings/shippo.js rename to imports/plugins/included/shipping-shippo/client/settings/shippo.js diff --git a/imports/plugins/included/shippo/client/settings/shippoTableColumn.js b/imports/plugins/included/shipping-shippo/client/settings/shippoTableColumn.js similarity index 100% rename from imports/plugins/included/shippo/client/settings/shippoTableColumn.js rename to imports/plugins/included/shipping-shippo/client/settings/shippoTableColumn.js diff --git a/imports/plugins/included/shippo/lib/collections/schemas/index.js b/imports/plugins/included/shipping-shippo/lib/collections/schemas/index.js similarity index 100% rename from imports/plugins/included/shippo/lib/collections/schemas/index.js rename to imports/plugins/included/shipping-shippo/lib/collections/schemas/index.js diff --git a/imports/plugins/included/shippo/lib/collections/schemas/shippo.js b/imports/plugins/included/shipping-shippo/lib/collections/schemas/shippo.js similarity index 100% rename from imports/plugins/included/shippo/lib/collections/schemas/shippo.js rename to imports/plugins/included/shipping-shippo/lib/collections/schemas/shippo.js diff --git a/imports/plugins/included/shippo/register.js b/imports/plugins/included/shipping-shippo/register.js similarity index 100% rename from imports/plugins/included/shippo/register.js rename to imports/plugins/included/shipping-shippo/register.js diff --git a/imports/plugins/included/shippo/server/hooks/index.js b/imports/plugins/included/shipping-shippo/server/hooks/index.js similarity index 100% rename from imports/plugins/included/shippo/server/hooks/index.js rename to imports/plugins/included/shipping-shippo/server/hooks/index.js diff --git a/imports/plugins/included/shippo/server/hooks/rates.js b/imports/plugins/included/shipping-shippo/server/hooks/rates.js similarity index 60% rename from imports/plugins/included/shippo/server/hooks/rates.js rename to imports/plugins/included/shipping-shippo/server/hooks/rates.js index 9a100f2a17f..a7dd0464592 100644 --- a/imports/plugins/included/shippo/server/hooks/rates.js +++ b/imports/plugins/included/shipping-shippo/server/hooks/rates.js @@ -4,24 +4,30 @@ import { Logger, Reaction, Hooks } from "/server/api"; // callback ran on getShippingRates hook function getShippingRates(previousQueryResults, cart) { + const marketplaceSettings = Reaction.getMarketplaceSettings(); + let merchantShippingRates = false; + if (marketplaceSettings && marketplaceSettings.public && marketplaceSettings.public.merchantShippingRates) { + merchantShippingRates = marketplaceSettings.public.merchantShippingRates; + } + const [rates, retrialTargets] = previousQueryResults; const shops = []; const products = cart.items; - const pkgData = Packages.findOne({ - name: "reaction-shippo", - shopId: Reaction.getShopId() - }); + let pkgData; + if (merchantShippingRates) { + Logger.fatal("Multiple shipping providers is currently not implemented"); + throw new Meteor.Error("not-implemented", "Multiple shipping providers is currently not implemented"); + } else { + pkgData = Packages.findOne({ + name: "reaction-shippo", + shopId: Reaction.getPrimaryShopId() + }); + } + // must have cart items and package enabled to calculate shipping if (!pkgData || !cart.items || pkgData.settings.shippo.enabled !== true) { - const errorDetails = { - requestStatus: "error", - shippingProvider: "shippo", - message: "Error. The Shippo package may be uninstalled or disabled, or your cart is empty." - }; - // There's no need for a retry in this case. - rates.push(errorDetails); return [rates, retrialTargets]; } @@ -31,22 +37,24 @@ function getShippingRates(previousQueryResults, cart) { "provider.enabled": true }; - // create an array of shops, allowing - // the cart to have products from multiple shops - for (const product of products) { - if (product.shopId) { - shops.push(product.shopId); + // if we don't have merchant shipping rates enabled, only grab rates from primary shop + if (!merchantShippingRates) { + shops.push(Reaction.getPrimaryShopId()); + } else { + // create an array of shops, allowing + // the cart to have products from multiple shops + for (const product of products) { + if (product.shopId) { + shops.push(product.shopId); + } } } - // if we have multiple shops in cart - if ((shops !== null ? shops.length : void 0) > 0) { - selector = { - "shopId": { - $in: shops - }, - "provider.enabled": true - }; - } + selector = { + "shopId": { + $in: shops + }, + "provider.enabled": true + }; const shippingCollection = Shipping.find(selector); const shippoDocs = {}; diff --git a/imports/plugins/included/shippo/server/hooks/tracking.js b/imports/plugins/included/shipping-shippo/server/hooks/tracking.js similarity index 100% rename from imports/plugins/included/shippo/server/hooks/tracking.js rename to imports/plugins/included/shipping-shippo/server/hooks/tracking.js diff --git a/imports/plugins/included/shippo/server/i18n/ar.json b/imports/plugins/included/shipping-shippo/server/i18n/ar.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/ar.json rename to imports/plugins/included/shipping-shippo/server/i18n/ar.json diff --git a/imports/plugins/included/shippo/server/i18n/bg.json b/imports/plugins/included/shipping-shippo/server/i18n/bg.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/bg.json rename to imports/plugins/included/shipping-shippo/server/i18n/bg.json diff --git a/imports/plugins/included/shippo/server/i18n/cs.json b/imports/plugins/included/shipping-shippo/server/i18n/cs.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/cs.json rename to imports/plugins/included/shipping-shippo/server/i18n/cs.json diff --git a/imports/plugins/included/shippo/server/i18n/de.json b/imports/plugins/included/shipping-shippo/server/i18n/de.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/de.json rename to imports/plugins/included/shipping-shippo/server/i18n/de.json diff --git a/imports/plugins/included/shippo/server/i18n/el.json b/imports/plugins/included/shipping-shippo/server/i18n/el.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/el.json rename to imports/plugins/included/shipping-shippo/server/i18n/el.json diff --git a/imports/plugins/included/shippo/server/i18n/en.json b/imports/plugins/included/shipping-shippo/server/i18n/en.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/en.json rename to imports/plugins/included/shipping-shippo/server/i18n/en.json diff --git a/imports/plugins/included/shippo/server/i18n/es.json b/imports/plugins/included/shipping-shippo/server/i18n/es.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/es.json rename to imports/plugins/included/shipping-shippo/server/i18n/es.json diff --git a/imports/plugins/included/shippo/server/i18n/fr.json b/imports/plugins/included/shipping-shippo/server/i18n/fr.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/fr.json rename to imports/plugins/included/shipping-shippo/server/i18n/fr.json diff --git a/imports/plugins/included/shippo/server/i18n/he.json b/imports/plugins/included/shipping-shippo/server/i18n/he.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/he.json rename to imports/plugins/included/shipping-shippo/server/i18n/he.json diff --git a/imports/plugins/included/shippo/server/i18n/hr.json b/imports/plugins/included/shipping-shippo/server/i18n/hr.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/hr.json rename to imports/plugins/included/shipping-shippo/server/i18n/hr.json diff --git a/imports/plugins/included/shippo/server/i18n/hu.json b/imports/plugins/included/shipping-shippo/server/i18n/hu.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/hu.json rename to imports/plugins/included/shipping-shippo/server/i18n/hu.json diff --git a/imports/plugins/included/shippo/server/i18n/index.js b/imports/plugins/included/shipping-shippo/server/i18n/index.js similarity index 100% rename from imports/plugins/included/shippo/server/i18n/index.js rename to imports/plugins/included/shipping-shippo/server/i18n/index.js diff --git a/imports/plugins/included/shippo/server/i18n/it.json b/imports/plugins/included/shipping-shippo/server/i18n/it.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/it.json rename to imports/plugins/included/shipping-shippo/server/i18n/it.json diff --git a/imports/plugins/included/shippo/server/i18n/my.json b/imports/plugins/included/shipping-shippo/server/i18n/my.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/my.json rename to imports/plugins/included/shipping-shippo/server/i18n/my.json diff --git a/imports/plugins/included/shippo/server/i18n/nb.json b/imports/plugins/included/shipping-shippo/server/i18n/nb.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/nb.json rename to imports/plugins/included/shipping-shippo/server/i18n/nb.json diff --git a/imports/plugins/included/shippo/server/i18n/nl.json b/imports/plugins/included/shipping-shippo/server/i18n/nl.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/nl.json rename to imports/plugins/included/shipping-shippo/server/i18n/nl.json diff --git a/imports/plugins/included/shippo/server/i18n/pl.json b/imports/plugins/included/shipping-shippo/server/i18n/pl.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/pl.json rename to imports/plugins/included/shipping-shippo/server/i18n/pl.json diff --git a/imports/plugins/included/shippo/server/i18n/pt.json b/imports/plugins/included/shipping-shippo/server/i18n/pt.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/pt.json rename to imports/plugins/included/shipping-shippo/server/i18n/pt.json diff --git a/imports/plugins/included/shippo/server/i18n/ro.json b/imports/plugins/included/shipping-shippo/server/i18n/ro.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/ro.json rename to imports/plugins/included/shipping-shippo/server/i18n/ro.json diff --git a/imports/plugins/included/shippo/server/i18n/ru.json b/imports/plugins/included/shipping-shippo/server/i18n/ru.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/ru.json rename to imports/plugins/included/shipping-shippo/server/i18n/ru.json diff --git a/imports/plugins/included/shippo/server/i18n/sl.json b/imports/plugins/included/shipping-shippo/server/i18n/sl.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/sl.json rename to imports/plugins/included/shipping-shippo/server/i18n/sl.json diff --git a/imports/plugins/included/shippo/server/i18n/sv.json b/imports/plugins/included/shipping-shippo/server/i18n/sv.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/sv.json rename to imports/plugins/included/shipping-shippo/server/i18n/sv.json diff --git a/imports/plugins/included/shippo/server/i18n/tr.json b/imports/plugins/included/shipping-shippo/server/i18n/tr.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/tr.json rename to imports/plugins/included/shipping-shippo/server/i18n/tr.json diff --git a/imports/plugins/included/shippo/server/i18n/vi.json b/imports/plugins/included/shipping-shippo/server/i18n/vi.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/vi.json rename to imports/plugins/included/shipping-shippo/server/i18n/vi.json diff --git a/imports/plugins/included/shippo/server/i18n/zh.json b/imports/plugins/included/shipping-shippo/server/i18n/zh.json similarity index 100% rename from imports/plugins/included/shippo/server/i18n/zh.json rename to imports/plugins/included/shipping-shippo/server/i18n/zh.json diff --git a/imports/plugins/included/shippo/server/index.js b/imports/plugins/included/shipping-shippo/server/index.js similarity index 100% rename from imports/plugins/included/shippo/server/index.js rename to imports/plugins/included/shipping-shippo/server/index.js diff --git a/imports/plugins/included/shippo/server/jobs/index.js b/imports/plugins/included/shipping-shippo/server/jobs/index.js similarity index 100% rename from imports/plugins/included/shippo/server/jobs/index.js rename to imports/plugins/included/shipping-shippo/server/jobs/index.js diff --git a/imports/plugins/included/shippo/server/jobs/shippo.js b/imports/plugins/included/shipping-shippo/server/jobs/shippo.js similarity index 100% rename from imports/plugins/included/shippo/server/jobs/shippo.js rename to imports/plugins/included/shipping-shippo/server/jobs/shippo.js diff --git a/imports/plugins/included/shippo/server/lib/roles.js b/imports/plugins/included/shipping-shippo/server/lib/roles.js similarity index 100% rename from imports/plugins/included/shippo/server/lib/roles.js rename to imports/plugins/included/shipping-shippo/server/lib/roles.js diff --git a/imports/plugins/included/shippo/server/lib/shippoApiSchema.js b/imports/plugins/included/shipping-shippo/server/lib/shippoApiSchema.js similarity index 100% rename from imports/plugins/included/shippo/server/lib/shippoApiSchema.js rename to imports/plugins/included/shipping-shippo/server/lib/shippoApiSchema.js diff --git a/imports/plugins/included/shippo/server/methods/carriers.js b/imports/plugins/included/shipping-shippo/server/methods/carriers.js similarity index 100% rename from imports/plugins/included/shippo/server/methods/carriers.js rename to imports/plugins/included/shipping-shippo/server/methods/carriers.js diff --git a/imports/plugins/included/shippo/server/methods/index.js b/imports/plugins/included/shipping-shippo/server/methods/index.js similarity index 100% rename from imports/plugins/included/shippo/server/methods/index.js rename to imports/plugins/included/shipping-shippo/server/methods/index.js diff --git a/imports/plugins/included/shippo/server/methods/shippo.js b/imports/plugins/included/shipping-shippo/server/methods/shippo.js similarity index 97% rename from imports/plugins/included/shippo/server/methods/shippo.js rename to imports/plugins/included/shipping-shippo/server/methods/shippo.js index 5abfd1a1fb9..3d0ee68d883 100644 --- a/imports/plugins/included/shippo/server/methods/shippo.js +++ b/imports/plugins/included/shipping-shippo/server/methods/shippo.js @@ -32,12 +32,12 @@ function createShippoAddress(reactionAddress, email, purpose) { // Creates a parcel object suitable for Shippo Api Calls given // a reaction product's parcel and units of measure for mass and distance -function createShippoParcel(reactionParcel, reactionMassUnit, reactionDistanceUnit) { +function createShippoParcel(reactionParcel, cartWeight, reactionMassUnit, reactionDistanceUnit) { const shippoParcel = { width: reactionParcel.width || "", length: reactionParcel.length || "", height: reactionParcel.height || "", - weight: reactionParcel.weight || "", + weight: cartWeight, distance_unit: reactionDistanceUnit, mass_unit: reactionMassUnit }; @@ -45,6 +45,14 @@ function createShippoParcel(reactionParcel, reactionMassUnit, reactionDistanceUn return shippoParcel; } +function getTotalCartweight(cart) { + const totalWeight = cart.items.reduce((sum, cartItem) => { + const itemWeight = cartItem.quantity * cartItem.parcel.weight; + return sum + itemWeight; + }, 0); + return totalWeight; +} + // converts the Rates List fetched from the Shippo Api to Reaction Shipping Rates form function ratesParser(shippoRates, shippoDocs) { return shippoRates.map(rate => { @@ -411,7 +419,8 @@ export const methods = { if (cart.items && cart.items[0] && cart.items[0].parcel) { const unitOfMeasure = shop && shop.baseUOM || "kg"; const unitOfLength = shop && shop.baseUOL || "cm"; - shippoParcel = createShippoParcel(cart.items[0].parcel, unitOfMeasure, unitOfLength); + const cartWeight = getTotalCartweight(cart); + shippoParcel = createShippoParcel(cart.items[0].parcel, cartWeight, unitOfMeasure, unitOfLength); } else { errorDetails.message = "This cart has no items, or the first item has no 'parcel' property."; return [[errorDetails], []]; diff --git a/imports/plugins/included/shippo/server/methods/shippoapi.js b/imports/plugins/included/shipping-shippo/server/methods/shippoapi.js similarity index 100% rename from imports/plugins/included/shippo/server/methods/shippoapi.js rename to imports/plugins/included/shipping-shippo/server/methods/shippoapi.js diff --git a/imports/plugins/included/shippo/server/hooks/hooks.js b/imports/plugins/included/shippo/server/hooks/hooks.js deleted file mode 100644 index 9a100f2a17f..00000000000 --- a/imports/plugins/included/shippo/server/hooks/hooks.js +++ /dev/null @@ -1,77 +0,0 @@ -import { Meteor } from "meteor/meteor"; -import { Shipping, Packages } from "/lib/collections"; -import { Logger, Reaction, Hooks } from "/server/api"; - -// callback ran on getShippingRates hook -function getShippingRates(previousQueryResults, cart) { - const [rates, retrialTargets] = previousQueryResults; - const shops = []; - const products = cart.items; - - const pkgData = Packages.findOne({ - name: "reaction-shippo", - shopId: Reaction.getShopId() - }); - - // must have cart items and package enabled to calculate shipping - if (!pkgData || !cart.items || pkgData.settings.shippo.enabled !== true) { - const errorDetails = { - requestStatus: "error", - shippingProvider: "shippo", - message: "Error. The Shippo package may be uninstalled or disabled, or your cart is empty." - }; - // There's no need for a retry in this case. - rates.push(errorDetails); - return [rates, retrialTargets]; - } - - // default selector is current shop - let selector = { - "shopId": Reaction.getShopId(), - "provider.enabled": true - }; - - // create an array of shops, allowing - // the cart to have products from multiple shops - for (const product of products) { - if (product.shopId) { - shops.push(product.shopId); - } - } - // if we have multiple shops in cart - if ((shops !== null ? shops.length : void 0) > 0) { - selector = { - "shopId": { - $in: shops - }, - "provider.enabled": true - }; - } - - const shippingCollection = Shipping.find(selector); - const shippoDocs = {}; - if (shippingCollection) { - shippingCollection.forEach(function (doc) { - // If provider is from Shippo, put it in an object to get rates dynamically(shippoApi) for all of them after. - if (doc.provider.shippoProvider) { - shippoDocs[doc.provider.shippoProvider.carrierAccountId] = doc; - } - }); - - // Get shippingRates from Shippo - if (Object.keys(shippoDocs).length > 0) { - const targets = retrialTargets.slice(); - const shippingRatesInfo = - Meteor.call("shippo/getShippingRatesForCart", cart._id, shippoDocs, targets); - const [shippoRates, singleRetrialTarget] = shippingRatesInfo; - rates.push(...shippoRates); - retrialTargets.push(...singleRetrialTarget); - } - } - - Logger.debug("Shippo onGetShippingRates", [rates, retrialTargets]); - return [rates, retrialTargets]; -} - -// run getShippingRates when the onGetShippingRates event runs -Hooks.Events.add("onGetShippingRates", getShippingRates); diff --git a/private/data/Products.json b/private/data/Products.json index bd19dbb332a..c39d2ad908d 100644 --- a/private/data/Products.json +++ b/private/data/Products.json @@ -31,13 +31,7 @@ "updatedAt": { "$date": "2014-06-01T12:17:13.949-0700" }, - "vendor": "Example Manufacturer", - "parcel": { - "length": 10, - "width": 5, - "height": 3, - "weight": 20 - } + "vendor": "Example Manufacturer" }, { "_id": "6qiqPwBkeJdtdQc4G", "ancestors": [ @@ -82,7 +76,10 @@ "createdAt": { "$date": "2014-04-03T13:46:52.411-0700" }, - "weight": 35, + "weight": 25, + "length": 10, + "height": 3, + "width": 10, "metafields": [{ "key": null, "value": null @@ -109,7 +106,10 @@ "createdAt": { "$date": "2014-04-03T13:46:52.411-0700" }, - "weight": 35, + "weight": 25, + "length": 10, + "height": 3, + "width": 10, "metafields": [{ "key": null, "value": null diff --git a/server/methods/core/cart.js b/server/methods/core/cart.js index 0a6e773e570..91ee194bc44 100644 --- a/server/methods/core/cart.js +++ b/server/methods/core/cart.js @@ -83,6 +83,18 @@ function getSessionCarts(userId, sessionId, shopId) { return allowedCarts; } +function removeShippingAddresses(cart) { + const cartShipping = cart.shipping; + cartShipping.map((sRecord) => { + delete sRecord.address; + }); + Collections.Cart.update({ + _id: cart._id + }, { + $set: { shipping: cartShipping } + }); +} + /** * Reaction Cart Methods */ @@ -500,15 +512,8 @@ Meteor.methods({ throw new Meteor.Error("cart-item-not-found", "Unable to find an item with such id in cart."); } - // refresh shipping quotes - Meteor.call("shipping/updateShipmentQuotes", cart._id); - // revert workflow to checkout shipping step. - Meteor.call("workflow/revertCartWorkflow", "coreCheckoutShipping"); - // reset selected shipment method - Meteor.call("cart/resetShipmentMethod", cart._id); - if (!quantity || quantity >= cartItem.quantity) { - return Collections.Cart.update({ + const cartResult = Collections.Cart.update({ _id: cart._id }, { $pull: { @@ -521,18 +526,28 @@ Meteor.methods({ }, (error, result) => { if (error) { Logger.error(error); - Logger.error(Collections.Cart.simpleSchema().namedContext().invalidKeys(), - "error removing from cart"); + Logger.error(Collections.Cart.simpleSchema().namedContext().invalidKeys(), "error removing from cart"); return error; } Logger.debug(`cart: deleted cart item variant id ${cartItem.variants._id}`); return result; }); + // TODO: HACK: When calling update shipping the changes to the cart have not taken place yet + // TODO: But calling this findOne seems to force this record to update. Extra weird since we aren't + // TODO: passing the Cart but just the cartId and regrabbing it so you would think that would work but it does not + Collections.Cart.findOne(cart._id); + // refresh shipping quotes + Meteor.call("shipping/updateShipmentQuotes", cart._id); + // revert workflow + Meteor.call("workflow/revertCartWorkflow", "coreCheckoutShipping"); + // reset selected shipment method + Meteor.call("cart/resetShipmentMethod", cart._id); + return cartResult; } // if quantity lets convert to negative and increment const removeQuantity = Math.abs(quantity) * -1; - return Collections.Cart.update({ + const cartResult = Collections.Cart.update({ "_id": cart._id, "items._id": cartItem._id }, { @@ -549,6 +564,13 @@ Meteor.methods({ Logger.debug(`cart: removed variant ${cartItem._id} quantity of ${quantity}`); return result; }); + // refresh shipping quotes + Meteor.call("shipping/updateShipmentQuotes", cart._id); + // revert workflow + Meteor.call("workflow/revertCartWorkflow", "coreCheckoutShipping"); + // reset selected shipment method + Meteor.call("cart/resetShipmentMethod", cart._id); + return cartResult; }, /** * cart/setShipmentMethod @@ -721,45 +743,102 @@ Meteor.methods({ throw new Meteor.Error(404, "Cart not found", "Cart not found for user with such id"); } - + // TODO: When we have a front end for doing more than one address + // TODO: we need to not use the same address for every record + // TODO: this is a temporary workaround so that we have a valid address + // TODO: for every shipping record let selector; let update; - const primaryShopId = Reaction.getPrimaryShopId(); - // temp hack until we build out multiple shipment handlers - // if we have an existing item update it, otherwise add to set. - if (Array.isArray(cart.shipping) && cart.shipping.length > 0) { - selector = { - "_id": cartId, - "shipping._id": cart.shipping[0]._id - }; - update = { - $set: { - "shipping.$.address": address, - "shopId": primaryShopId + let updated = false; // if we update inline set to true, otherwise fault to update at the end + // We have two behaviors depending on if we have existing shipping records and if we + // have items in the cart. + if (cart.shipping && cart.shipping.length > 0 && cart.items) { + // if we have shipping records and cart.items, update each one by shop + const shopIds = Object.keys(cart.getItemsByShop()); + shopIds.forEach((shopId) => { + selector = { + "_id": cartId, + "shipping.shopId": shopId + }; + + update = { + $set: { + "shipping.$.address": address + } + }; + try { + Collections.Cart.update(selector, update); + updated = true; + } catch (error) { + Logger.error("An error occurred adding the address", error); + throw new Meteor.Error("An error occurred adding the address", error); } - }; + }); } else { - selector = { - _id: cartId - }; - update = { - $addToSet: { - shipping: { - address: address, - shopId: primaryShopId + // if no items in cart just add or modify one record for the carts shop + if (!cart.items) { + // add a shipping record if it doesn't exist + if (!cart.shipping) { + selector = { + _id: cartId + }; + update = { + $push: { + shipping: { + address: address, + shopId: cart.shopId + } + } + }; + + try { + Collections.Cart.update(selector, update); + updated = true; + } catch (error) { + Logger.error(error); + throw new Meteor.Error("An error occurred adding the address"); } + } else { + // modify an existing record if we have one already + selector = { + "_id": cartId, + "shipping.shopId": cart.shopId + }; + + update = { + $set: { + "shipping.$.address": address + } + }; } - }; + } else { + // if we have items in the cart but we didn't have existing shipping records + // add a record for each shop that's represented in the items + const shopIds = Object.keys(cart.getItemsByShop()); + shopIds.map((shopId) => { + selector = { + _id: cartId + }; + update = { + $addToSet: { + shipping: { + address: address, + shopId: shopId + } + } + }; + }); + } } - - // add / or set the shipping address - try { - Collections.Cart.update(selector, update); - } catch (e) { - Logger.error(e); - throw new Meteor.Error("An error occurred adding the address"); + if (!updated) { + // if we didn't do one of the inline updates, then run the update here + try { + Collections.Cart.update(selector, update); + } catch (error) { + Logger.error(error); + throw new Meteor.Error("An error occurred adding the address"); + } } - // refresh shipping quotes Meteor.call("shipping/updateShipmentQuotes", cartId); @@ -839,7 +918,6 @@ Meteor.methods({ return Collections.Cart.update(selector, update); }, - /** * cart/unsetAddresses * @description removes address from cart. @@ -885,10 +963,8 @@ Meteor.methods({ update.$unset["billing.0.address"] = ""; needToUpdate = true; } - if (cart.shipping && typeof cart.shipping[0].address === "object" && - cart.shipping[0].address._id === addressId) { - update.$unset["shipping.0.address"] = ""; - needToUpdate = true; + if (cart.shipping && typeof cart.shipping[0].address === "object" && cart.shipping[0].address._id === addressId) { + removeShippingAddresses(cart); isShippingDeleting = true; } } diff --git a/server/methods/core/shipping.js b/server/methods/core/shipping.js index 600d99c3fd6..072d8d35703 100644 --- a/server/methods/core/shipping.js +++ b/server/methods/core/shipping.js @@ -1,22 +1,23 @@ import { Meteor } from "meteor/meteor"; import { check } from "meteor/check"; -import { Cart } from "/lib/collections"; +import { Cart, Accounts } from "/lib/collections"; import { Logger, Hooks } from "/server/api"; import { Cart as CartSchema } from "/lib/collections/schemas"; -function createShipmentQuotes(cartId, shopId, rates, selector) { +function createShipmentQuotes(cartId, shopId, rates) { let update = { $push: { shipping: { shopId: shopId, - shipmentQuotes: rates, + shipmentQuotes: [], shipmentQuotesQueryStatus: { requestStatus: "pending" } } } }; - Cart.update(selector, update, function (error) { + + Cart.update({ _id: cartId }, update, function (error) { if (error) { Logger.warn(`Error in setting shipping query status to "pending" for ${cartId}`, error); return; @@ -27,15 +28,12 @@ function createShipmentQuotes(cartId, shopId, rates, selector) { if (rates.length === 1 && rates[0].requestStatus === "error") { const errorDetails = rates[0]; update = { - $push: { - shipping: { - shopId: shopId, - shipmentQuotes: [], - shipmentQuotesQueryStatus: { - requestStatus: errorDetails.requestStatus, - shippingProvider: errorDetails.shippingProvider, - message: errorDetails.message - } + $set: { + "shipping.$.shipmentQuotes": [], + "shipping.$.shipmentQuotesQueryStatus": { + requestStatus: errorDetails.requestStatus, + shippingProvider: errorDetails.shippingProvider, + message: errorDetails.message } } }; @@ -43,14 +41,11 @@ function createShipmentQuotes(cartId, shopId, rates, selector) { if (rates.length > 0 && rates[0].requestStatus === undefined) { update = { - $push: { - shipping: { - shopId: shopId, - shipmentQuotes: rates, - shipmentQuotesQueryStatus: { - requestStatus: "success", - numOfShippingMethodsFound: rates.length - } + $set: { + "shipping.$.shipmentQuotes": rates, + "shipping.$.shipmentQuotesQueryStatus": { + requestStatus: "success", + numOfShippingMethodsFound: rates.length } } }; @@ -59,27 +54,74 @@ function createShipmentQuotes(cartId, shopId, rates, selector) { return update; } -function createShippingRecordByShop(cart, rates) { - const cartId = cart._id; - const itemsByShop = cart.getItemsByShop(); - const shops = Object.keys(itemsByShop); - const selector = { _id: cartId }; - shops.map((shopId) => { - const update = createShipmentQuotes(cartId, shopId, rates, selector); - return Cart.update(selector, update, (error) => { - if (error) { - Logger.error(`Error adding rates to cart from createShippingRecordByShop ${cartId}`, error); - return; + +/** + * @summary if we have items in the cart, ensure that we only have shipping records for shops currently represented in the cart + * @param {Object} cart - The cart to operate on + * @returns {undefined} undefined + * @private + */ +function pruneShippingRecordsByShop(cart) { + if (cart.items) { + const cartId = cart._id; + const itemsByShop = cart.getItemsByShop(); + const shops = Object.keys(itemsByShop); + if (shops.length > 0 && cart.items.length > 0) { + Cart.update({ _id: cartId }, + { + $pull: { + shipping: { shopId: { $nin: shops } } + } + } + ); + } else { + Cart.update({ _id: cartId }, + { + $unset: { + shipping: "" + } + } + ); + } + } +} + +/** + * @summary - When adding shipping records, ensure that each record has an address + * @param {Object} cart - The Cart object we need to operate on + * @returns {undefined} undefined + */ +function normalizeAddresses(cart) { + if (cart.shipping && cart.shipping.length > 0) { + const shipping = cart.shipping; + const cartId = cart._id; + let address; // we can only have one address so whatever was the last assigned + shipping.forEach((shippingRecord) => { + if (shippingRecord.address) { + address = shippingRecord.address; } - Logger.debug(`Success adding rates to cart ${cartId}`, rates); }); - }); + const shopIds = Object.keys(cart.getItemsByShop()); + shopIds.forEach((shopId) => { + const selector = { + "_id": cartId, + "shipping.shopId": shopId + }; + + const update = { + $set: { + "shipping.$.address": address + } + }; + Cart.update(selector, update); + }); + } } function updateShipmentQuotes(cartId, rates, selector) { let update = { $set: { - "shipping.0.shipmentQuotesQueryStatus": { + "shipping.$.shipmentQuotesQueryStatus": { requestStatus: "pending" } } @@ -96,8 +138,8 @@ function updateShipmentQuotes(cartId, rates, selector) { const errorDetails = rates[0]; update = { $set: { - "shipping.0.shipmentQuotes": [], - "shipping.0.shipmentQuotesQueryStatus": { + "shipping.$.shipmentQuotes": [], + "shipping.$.shipmentQuotesQueryStatus": { requestStatus: errorDetails.requestStatus, shippingProvider: errorDetails.shippingProvider, message: errorDetails.message @@ -127,19 +169,17 @@ function updateShippingRecordByShop(cart, rates) { const shops = Object.keys(itemsByShop); let update; let selector; - shops.map((shopId) => { + shops.forEach((shopId) => { selector = { "_id": cartId, "shipping.shopId": shopId }; - const shippingRecord = Cart.findOne(selector); + const cartForShipping = Cart.findOne(selector); // we may have added a new shop since the last time we did this, if so we need to add a new record - - if (shippingRecord) { + if (cartForShipping) { update = updateShipmentQuotes(cartId, rates, selector); } else { - selector = { _id: cartId }; - update = createShipmentQuotes(cartId, shopId, rates, selector); + update = createShipmentQuotes(cartId, shopId, rates); } Cart.update(selector, update, function (error) { @@ -150,6 +190,42 @@ function updateShippingRecordByShop(cart, rates) { Logger.debug(`Success updating rates for cart ${cartId}`, rates); }); }); + pruneShippingRecordsByShop(cart); + normalizeAddresses(cart); +} + +function getDefaultAddress(cart) { + const userId = cart.userId; + const account = Accounts.findOne(userId); + if (account && account.profile && account.profile.addressBook) { + const address = account.profile.addressBook.find((addressEntry) => addressEntry.isShippingDefault === true); + return address; + } +} + +/** + * Add the default address to the cart + * @param {Object} cart - the cart to modify + * @returns {undefined} + * @private + */ +function addAddresses(cart) { + const address = getDefaultAddress(cart); + if (address) { + const shopIds = Object.keys(cart.getItemsByShop()); + shopIds.forEach((shopId) => { + Cart.update({ + _id: cart._id + }, { + $push: { + shipping: { + shopId: shopId, + address: address + } + } + }); + }); + } } /* * Reaction Shipping Methods @@ -169,16 +245,16 @@ export const methods = { return []; } this.unblock(); - const cart = Cart.findOne(cartId); + let cart = Cart.findOne(cartId); check(cart, CartSchema); if (cart) { - const rates = Meteor.call("shipping/getShippingRates", cart); - if (cart.shipping) { - updateShippingRecordByShop(cart, rates); - } else { - createShippingRecordByShop(cart, rates); + if (!cart.shipping || cart.shipping.length === 0) { + addAddresses(cart); + cart = Cart.findOne(cartId); } + const rates = Meteor.call("shipping/getShippingRates", cart); + updateShippingRecordByShop(cart, rates); } }, @@ -193,7 +269,7 @@ export const methods = { const rates = []; const retrialTargets = []; // must have items to calculate shipping - if (!cart.items) { + if (!cart.items || !cart.items.length) { return rates; } // hooks for other shipping rate events