com.squarespace.template.plugins.platform.CommerceUtils Maven / Gradle / Ivy
/**
* Copyright (c) 2015 SQUARESPACE, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squarespace.template.plugins.platform;
import static com.squarespace.template.GeneralUtils.isTruthy;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.squarespace.cldr.CLDR;
import com.squarespace.template.JsonUtils;
import com.squarespace.template.plugins.PluginUtils;
import com.squarespace.template.plugins.platform.enums.ProductType;
/**
* Extracted from Commons library at commit ab4ba7a6f2b872a31cb6449ae9e96f5f5b30f471
*/
public class CommerceUtils {
private static final ArrayNode EMPTY_ARRAY = JsonUtils.createArrayNode();
private static final String DEFAULT_CURRENCY = "USD";
private static final BigDecimal DEFAULT_MONEY_AMOUNT = new BigDecimal(0);
private static final JsonNode DEFAULT_MONEY_NODE = new ObjectMapper()
.createObjectNode()
.put("currency", "USD")
.put("value", "0");
private CommerceUtils() {
}
public static ProductType getProductType(JsonNode item) {
JsonNode structuredContent = item.path("structuredContent");
JsonNode node = structuredContent.path("productType");
return ProductType.fromCode(node.asInt());
}
public static JsonNode getFromPriceMoneyNode(JsonNode item) {
ProductType type = getProductType(item);
JsonNode structuredContent = item.path("structuredContent");
switch (type) {
case PHYSICAL:
case SERVICE:
case GIFT_CARD:
JsonNode variants = structuredContent.path("variants");
if (variants.size() == 0) {
return DEFAULT_MONEY_NODE;
}
JsonNode first = variants.get(0);
JsonNode moneyNode = isTruthy(first.path("onSale"))
? first.path("salePriceMoney")
: first.path("priceMoney");
BigDecimal price = getAmountFromMoneyNode(moneyNode);
for (int i = 1; i < variants.size(); i++) {
JsonNode var = variants.get(i);
JsonNode currentMoneyNode = isTruthy(var.path("onSale"))
? var.path("salePriceMoney")
: var.path("priceMoney");
BigDecimal current = getAmountFromMoneyNode(currentMoneyNode);
if (current.compareTo(price) < 0) {
price = current;
moneyNode = currentMoneyNode;
}
}
return moneyNode;
case DIGITAL:
JsonNode digitalMoneyNode = structuredContent.path("priceMoney");
return digitalMoneyNode.isMissingNode() ? DEFAULT_MONEY_NODE : digitalMoneyNode;
default:
return DEFAULT_MONEY_NODE;
}
}
public static JsonNode getNormalPriceMoneyNode(JsonNode item) {
ProductType type = getProductType(item);
JsonNode structuredContent = item.path("structuredContent");
switch (type) {
case PHYSICAL:
case SERVICE:
case GIFT_CARD:
JsonNode variants = structuredContent.path("variants");
if (variants.size() == 0) {
return DEFAULT_MONEY_NODE;
}
JsonNode moneyNode = variants.get(0).path("priceMoney");
BigDecimal price = getAmountFromMoneyNode(moneyNode);
for (int i = 1; i < variants.size(); i++) {
JsonNode currMoneyNode = variants.get(i).path("priceMoney");
BigDecimal curr = getAmountFromMoneyNode(currMoneyNode);
if (curr.compareTo(price) > 0) {
price = curr;
moneyNode = currMoneyNode;
}
}
return moneyNode;
case DIGITAL:
JsonNode digitalMoneyNode = structuredContent.path("priceMoney");
return digitalMoneyNode.isMissingNode() ? DEFAULT_MONEY_NODE : digitalMoneyNode;
default:
return DEFAULT_MONEY_NODE;
}
}
public static JsonNode getSalePriceMoneyNode(JsonNode item) {
ProductType type = getProductType(item);
JsonNode structuredContent = item.path("structuredContent");
switch (type) {
case PHYSICAL:
case SERVICE:
JsonNode variants = structuredContent.path("variants");
if (variants.size() == 0) {
return DEFAULT_MONEY_NODE;
}
BigDecimal salePrice = null;
JsonNode salePriceNode = null;
for (int i = 0; i < variants.size(); i++) {
JsonNode variant = variants.path(i);
JsonNode priceMoney = variant.path("salePriceMoney");
BigDecimal price = getAmountFromMoneyNode(priceMoney);
if (isTruthy(variant.path("onSale")) && (salePriceNode == null || price.compareTo(salePrice) < 0)) {
salePrice = price;
salePriceNode = priceMoney;
}
}
return (salePriceNode == null) ? DEFAULT_MONEY_NODE : salePriceNode;
case DIGITAL:
JsonNode digitalMoneyNode = structuredContent.path("salePriceMoney");
return digitalMoneyNode.isMissingNode() ? DEFAULT_MONEY_NODE : digitalMoneyNode;
case GIFT_CARD:
// this should never happen
default:
return DEFAULT_MONEY_NODE;
}
}
public static BigDecimal getAmountFromMoneyNode(JsonNode moneyNode) {
String value = StringUtils.trimToNull(moneyNode.path("value").asText());
return value == null ? DEFAULT_MONEY_AMOUNT : new BigDecimal(value);
}
public static String getCurrencyFromMoneyNode(JsonNode moneyNode) {
String currency = StringUtils.trimToNull(moneyNode.path("currency").asText());
return currency == null ? DEFAULT_CURRENCY : currency;
}
public static double getLegacyPriceFromMoneyNode(JsonNode moneyNode) {
BigDecimal price = getAmountFromMoneyNode(moneyNode);
return price.movePointRight(2).doubleValue();
}
public static double getTotalStockRemaining(JsonNode item) {
ProductType type = getProductType(item);
JsonNode structuredContent = item.path("structuredContent");
if (EnumSet.of(ProductType.DIGITAL, ProductType.GIFT_CARD).contains(type)) {
return Double.POSITIVE_INFINITY;
} else {
int total = 0;
JsonNode variants = structuredContent.path("variants");
for (int i = 0; i < variants.size(); i++) {
JsonNode variant = variants.get(i);
if (isTruthy(variant.path("unlimited"))) {
return Double.POSITIVE_INFINITY;
} else {
total += variant.path("qtyInStock").asLong();
}
}
return total;
}
}
public static boolean hasVariedPrices(JsonNode item) {
ProductType type = getProductType(item);
JsonNode structuredContent = item.path("structuredContent");
switch (type) {
case PHYSICAL:
case SERVICE:
case GIFT_CARD:
JsonNode variants = structuredContent.path("variants");
JsonNode first = variants.get(0);
for (int i = 1; i < variants.size(); i++) {
JsonNode var = variants.get(i);
boolean flag1 = !var.path("onSale").equals(first.path("onSale"));
boolean flag2 = isTruthy(first.path("onSale")) && !var.path("salePrice").equals(first.path("salePrice"));
boolean flag3 = !var.path("price").equals(first.path("price"));
if (flag1 || flag2 || flag3) {
return true;
}
}
return false;
case DIGITAL:
default:
return false;
}
}
public static boolean hasVariants(JsonNode item) {
JsonNode structuredContent = item.path("structuredContent");
JsonNode variants = structuredContent.path("variants");
ProductType type = getProductType(item);
return type.equals(ProductType.DIGITAL) ? false : variants.size() > 1;
}
public static boolean isOnSale(JsonNode item) {
ProductType type = getProductType(item);
JsonNode structuredContent = item.path("structuredContent");
switch (type) {
case PHYSICAL:
case SERVICE:
JsonNode variants = structuredContent.path("variants");
int size = variants.size();
for (int i = 0; i < size; i++) {
JsonNode variant = variants.get(i);
if (isTruthy(variant.path("onSale"))) {
return true;
}
}
break;
case DIGITAL:
return isTruthy(structuredContent.path("onSale"));
case GIFT_CARD:
default:
break;
}
return false;
}
public static boolean isSoldOut(JsonNode item) {
ProductType type = getProductType(item);
JsonNode structuredContent = item.path("structuredContent");
switch (type) {
case PHYSICAL:
case SERVICE:
JsonNode variants = structuredContent.path("variants");
int size = variants.size();
for (int i = 0; i < size; i++) {
JsonNode variant = variants.get(i);
if (isTruthy(variant.path("unlimited")) || variant.path("qtyInStock").asInt() > 0) {
return false;
}
}
return true;
case DIGITAL:
case GIFT_CARD:
return false;
default:
return true;
}
}
/**
* Format money using legacy currency formatter
*/
public static void writeLegacyMoneyString(double value, StringBuilder buf) {
String formatted = PluginUtils.formatMoney(value, Locale.US);
buf.append("").append(formatted).append("");
}
/**
* Format money using CLDR currency formatter
*/
public static String getCLDRMoneyString(BigDecimal amount, String currencyCode, CLDR.Locale locale) {
return PluginUtils.formatMoney(amount, currencyCode, locale);
}
public static void writeVariantFormat(JsonNode variant, StringBuilder buf) {
ArrayNode optionValues = (ArrayNode) variant.get("optionValues");
if (optionValues == null) {
return;
}
Iterator iterator = optionValues.elements();
List values = new ArrayList<>();
while (iterator.hasNext()) {
JsonNode option = iterator.next();
values.add(option.get("value").asText());
}
int size = values.size();
for (int i = 0; i < size; i++) {
if (i > 0) {
buf.append(" / ");
}
buf.append(values.get(i));
}
}
public static ArrayNode getItemVariantOptions(JsonNode item) {
JsonNode structuredContent = item.path("structuredContent");
JsonNode variants = structuredContent.path("variants");
if (variants.size() <= 1) {
return EMPTY_ARRAY;
}
ArrayNode userDefinedOptions = JsonUtils.createArrayNode();
JsonNode ordering = structuredContent.path("variantOptionOrdering");
for (int i = 0; i < ordering.size(); i++) {
String optionName = ordering.path(i).asText();
ObjectNode option = JsonUtils.createObjectNode();
option.put("name", optionName);
option.put("values", JsonUtils.createArrayNode());
userDefinedOptions.add(option);
}
for (int i = 0; i < variants.size(); i++) {
JsonNode variant = variants.path(i);
JsonNode attributes = variant.get("attributes");
if (attributes == null) {
continue;
}
Iterator fields = attributes.fieldNames();
while (fields.hasNext()) {
String field = fields.next();
String variantOptionValue = attributes.get(field).asText();
ObjectNode userDefinedOption = null;
for (int j = 0; j < userDefinedOptions.size(); j++) {
ObjectNode current = (ObjectNode)userDefinedOptions.get(j);
if (current.get("name").asText().equals(field)) {
userDefinedOption = current;
}
}
if (userDefinedOption != null) {
boolean hasOptionValue = false;
ArrayNode optionValues = (ArrayNode)userDefinedOption.get("values");
for (int k = 0; k < optionValues.size(); k++) {
String optionValue = optionValues.get(k).asText();
if (optionValue.equals(variantOptionValue)) {
hasOptionValue = true;
break;
}
}
if (!hasOptionValue) {
optionValues.add(variantOptionValue);
}
}
}
}
return userDefinedOptions;
}
public static boolean isMultipleQuantityAllowedForServices(JsonNode websiteSettings) {
boolean defaultValue = true;
String fieldName = "multipleQuantityAllowedForServices";
JsonNode storeSettings = websiteSettings.get("storeSettings");
if (storeSettings == null || !storeSettings.hasNonNull(fieldName)) {
return defaultValue;
}
return storeSettings.get(fieldName).asBoolean(defaultValue);
}
}