package com.salesmanager.core.business.modules.integration.shipping.impl; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.apache.commons.digester.Digester; import org.apache.commons.lang.Validate; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.salesmanager.core.business.utils.DataUtils; import com.salesmanager.core.model.common.Delivery; import com.salesmanager.core.model.merchant.MerchantStore; import com.salesmanager.core.model.reference.country.Country; import com.salesmanager.core.model.shipping.PackageDetails; import com.salesmanager.core.model.shipping.ShippingConfiguration; import com.salesmanager.core.model.shipping.ShippingOption; import com.salesmanager.core.model.shipping.ShippingOrigin; import com.salesmanager.core.model.shipping.ShippingQuote; import com.salesmanager.core.model.system.CustomIntegrationConfiguration; import com.salesmanager.core.model.system.IntegrationConfiguration; import com.salesmanager.core.model.system.IntegrationModule; import com.salesmanager.core.model.system.ModuleConfig; import com.salesmanager.core.modules.integration.IntegrationException; import com.salesmanager.core.modules.integration.shipping.model.ShippingQuoteModule; /** * Integrates with UPS online API * @author casams1 * */ public class UPSShippingQuote implements ShippingQuoteModule { private static final Logger LOGGER = LoggerFactory.getLogger(UPSShippingQuote.class); @Override public void validateModuleConfiguration( IntegrationConfiguration integrationConfiguration, MerchantStore store) throws IntegrationException { List<String> errorFields = null; //validate integrationKeys['accessKey'] Map<String,String> keys = integrationConfiguration.getIntegrationKeys(); if(keys==null || StringUtils.isBlank(keys.get("accessKey"))) { errorFields = new ArrayList<String>(); errorFields.add("accessKey"); } if(keys==null || StringUtils.isBlank(keys.get("userId"))) { errorFields = new ArrayList<String>(); errorFields.add("userId"); } if(keys==null || StringUtils.isBlank(keys.get("password"))) { errorFields = new ArrayList<String>(); errorFields.add("password"); } //validate at least one integrationOptions['packages'] Map<String,List<String>> options = integrationConfiguration.getIntegrationOptions(); if(options==null) { errorFields = new ArrayList<String>(); errorFields.add("packages"); } List<String> packages = options.get("packages"); if(packages==null || packages.size()==0) { if(errorFields==null) { errorFields = new ArrayList<String>(); } errorFields.add("packages"); } /* List<String> services = options.get("services"); if(services==null || services.size()==0) { if(errorFields==null) { errorFields = new ArrayList<String>(); } errorFields.add("services"); } if(services!=null && services.size()>3) { if(errorFields==null) { errorFields = new ArrayList<String>(); } errorFields.add("services"); }*/ if(errorFields!=null) { IntegrationException ex = new IntegrationException(IntegrationException.ERROR_VALIDATION_SAVE); ex.setErrorFields(errorFields); throw ex; } } @Override public List<ShippingOption> getShippingQuotes( ShippingQuote shippingQuote, List<PackageDetails> packages, BigDecimal orderTotal, Delivery delivery, ShippingOrigin origin, MerchantStore store, IntegrationConfiguration configuration, IntegrationModule module, ShippingConfiguration shippingConfiguration, Locale locale) throws IntegrationException { Validate.notNull(configuration, "IntegrationConfiguration must not be null for USPS shipping module"); if(StringUtils.isBlank(delivery.getPostalCode())) { return null; } BigDecimal total = orderTotal; if (packages == null) { return null; } List<ShippingOption> options = null; // only applies to Canada and US Country country = delivery.getCountry(); if(!(country.getIsoCode().equals("US") || country.getIsoCode().equals("CA"))) { return null; //throw new IntegrationException("UPS Not configured for shipping in country " + country.getIsoCode()); } // supports en and fr String language = locale.getLanguage(); if (!language.equals(Locale.FRENCH.getLanguage()) && !language.equals(Locale.ENGLISH.getLanguage())) { language = Locale.ENGLISH.getLanguage(); } String pack = configuration.getIntegrationOptions().get("packages").get(0); Map<String,String> keys = configuration.getIntegrationKeys(); String accessKey = keys.get("accessKey"); String userId = keys.get("userId"); String password = keys.get("password"); String host = null; String protocol = null; String port = null; String url = null; StringBuilder xmlbuffer = new StringBuilder(); HttpPost httppost = null; BufferedReader reader = null; try { String env = configuration.getEnvironment(); Set<String> regions = module.getRegionsSet(); if(!regions.contains(store.getCountry().getIsoCode())) { throw new IntegrationException("Can't use the service for store country code "); } Map<String, ModuleConfig> moduleConfigsMap = module.getModuleConfigs(); for(String key : moduleConfigsMap.keySet()) { ModuleConfig moduleConfig = (ModuleConfig)moduleConfigsMap.get(key); if(moduleConfig.getEnv().equals(env)) { host = moduleConfig.getHost(); protocol = moduleConfig.getScheme(); port = moduleConfig.getPort(); url = moduleConfig.getUri(); } } StringBuilder xmlreqbuffer = new StringBuilder(); xmlreqbuffer.append("<?xml version=\"1.0\"?>"); xmlreqbuffer.append("<AccessRequest>"); xmlreqbuffer.append("<AccessLicenseNumber>"); xmlreqbuffer.append(accessKey); xmlreqbuffer.append("</AccessLicenseNumber>"); xmlreqbuffer.append("<UserId>"); xmlreqbuffer.append(userId); xmlreqbuffer.append("</UserId>"); xmlreqbuffer.append("<Password>"); xmlreqbuffer.append(password); xmlreqbuffer.append("</Password>"); xmlreqbuffer.append("</AccessRequest>"); String xmlhead = xmlreqbuffer.toString(); String weightCode = store.getWeightunitcode(); String measureCode = store.getSeizeunitcode(); if (weightCode.equals("KG")) { weightCode = "KGS"; } else { weightCode = "LBS"; } String xml = "<?xml version=\"1.0\"?><RatingServiceSelectionRequest><Request><TransactionReference><CustomerContext>Shopizer</CustomerContext><XpciVersion>1.0001</XpciVersion></TransactionReference><RequestAction>Rate</RequestAction><RequestOption>Shop</RequestOption></Request>"; StringBuffer xmldatabuffer = new StringBuffer(); /** * <Shipment> * * <Shipper> <Address> <City></City> * <StateProvinceCode>QC</StateProvinceCode> * <CountryCode>CA</CountryCode> <PostalCode></PostalCode> * </Address> </Shipper> * * <ShipTo> <Address> <City>Redwood Shores</City> * <StateProvinceCode>CA</StateProvinceCode> * <CountryCode>US</CountryCode> <PostalCode></PostalCode> * <ResidentialAddressIndicator/> </Address> </ShipTo> * * <Package> <PackagingType> <Code>21</Code> </PackagingType> * <PackageWeight> <UnitOfMeasurement> <Code>LBS</Code> * </UnitOfMeasurement> <Weight>1.1</Weight> </PackageWeight> * <PackageServiceOptions> <InsuredValue> * <CurrencyCode>CAD</CurrencyCode> * <MonetaryValue>100</MonetaryValue> </InsuredValue> * </PackageServiceOptions> </Package> * * * </Shipment> * * <CustomerClassification> <Code>03</Code> * </CustomerClassification> </RatingServiceSelectionRequest> * **/ /**Map countriesMap = (Map) RefCache.getAllcountriesmap(LanguageUtil .getLanguageNumberCode(locale.getLanguage())); Map zonesMap = (Map) RefCache.getAllZonesmap(LanguageUtil .getLanguageNumberCode(locale.getLanguage())); Country storeCountry = (Country) countriesMap.get(store .getCountry()); Country customerCountry = (Country) countriesMap.get(customer .getCustomerCountryId()); int sZone = -1; try { sZone = Integer.parseInt(store.getZone()); } catch (Exception e) { // TODO: handle exception } Zone storeZone = (Zone) zonesMap.get(sZone); Zone customerZone = (Zone) zonesMap.get(customer .getCustomerZoneId());**/ xmldatabuffer.append("<PickupType><Code>03</Code></PickupType>"); // xmldatabuffer.append("<Description>Daily Pickup</Description>"); xmldatabuffer.append("<Shipment><Shipper>"); xmldatabuffer.append("<Address>"); xmldatabuffer.append("<City>"); xmldatabuffer.append(store.getStorecity()); xmldatabuffer.append("</City>"); // if(!StringUtils.isBlank(store.getStorestateprovince())) { if (store.getZone() != null) { xmldatabuffer.append("<StateProvinceCode>"); xmldatabuffer.append(store.getZone().getCode());// zone code xmldatabuffer.append("</StateProvinceCode>"); } xmldatabuffer.append("<CountryCode>"); xmldatabuffer.append(store.getCountry().getIsoCode()); xmldatabuffer.append("</CountryCode>"); xmldatabuffer.append("<PostalCode>"); xmldatabuffer.append(DataUtils .trimPostalCode(store.getStorepostalcode())); xmldatabuffer.append("</PostalCode></Address></Shipper>"); // ship to xmldatabuffer.append("<ShipTo>"); xmldatabuffer.append("<Address>"); xmldatabuffer.append("<City>"); xmldatabuffer.append(delivery.getCity()); xmldatabuffer.append("</City>"); // if(!StringUtils.isBlank(customer.getCustomerState())) { if (delivery.getZone() != null) { xmldatabuffer.append("<StateProvinceCode>"); xmldatabuffer.append(delivery.getZone().getCode());// zone code xmldatabuffer.append("</StateProvinceCode>"); } xmldatabuffer.append("<CountryCode>"); xmldatabuffer.append(delivery.getCountry().getIsoCode()); xmldatabuffer.append("</CountryCode>"); xmldatabuffer.append("<PostalCode>"); xmldatabuffer.append(DataUtils .trimPostalCode(delivery.getPostalCode())); xmldatabuffer.append("</PostalCode></Address></ShipTo>"); // xmldatabuffer.append("<Service><Code>11</Code></Service>");//TODO service codes (next day ...) for(PackageDetails packageDetail : packages){ xmldatabuffer.append("<Package>"); xmldatabuffer.append("<PackagingType>"); xmldatabuffer.append("<Code>"); xmldatabuffer.append(pack); xmldatabuffer.append("</Code>"); xmldatabuffer.append("</PackagingType>"); // weight xmldatabuffer.append("<PackageWeight>"); xmldatabuffer.append("<UnitOfMeasurement>"); xmldatabuffer.append("<Code>"); xmldatabuffer.append(weightCode); xmldatabuffer.append("</Code>"); xmldatabuffer.append("</UnitOfMeasurement>"); xmldatabuffer.append("<Weight>"); xmldatabuffer.append(new BigDecimal(packageDetail.getShippingWeight()) .setScale(1, BigDecimal.ROUND_HALF_UP)); xmldatabuffer.append("</Weight>"); xmldatabuffer.append("</PackageWeight>"); // dimension xmldatabuffer.append("<Dimensions>"); xmldatabuffer.append("<UnitOfMeasurement>"); xmldatabuffer.append("<Code>"); xmldatabuffer.append(measureCode); xmldatabuffer.append("</Code>"); xmldatabuffer.append("</UnitOfMeasurement>"); xmldatabuffer.append("<Length>"); xmldatabuffer.append(new BigDecimal(packageDetail.getShippingLength()) .setScale(2, BigDecimal.ROUND_HALF_UP)); xmldatabuffer.append("</Length>"); xmldatabuffer.append("<Width>"); xmldatabuffer.append(new BigDecimal(packageDetail.getShippingWidth()) .setScale(2, BigDecimal.ROUND_HALF_UP)); xmldatabuffer.append("</Width>"); xmldatabuffer.append("<Height>"); xmldatabuffer.append(new BigDecimal(packageDetail.getShippingHeight()) .setScale(2, BigDecimal.ROUND_HALF_UP)); xmldatabuffer.append("</Height>"); xmldatabuffer.append("</Dimensions>"); xmldatabuffer.append("</Package>"); } xmldatabuffer.append("</Shipment>"); xmldatabuffer.append("</RatingServiceSelectionRequest>"); xmlbuffer.append(xmlhead).append(xml).append( xmldatabuffer.toString()); LOGGER.debug("UPS QUOTE REQUEST " + xmlbuffer.toString()); CloseableHttpClient httpclient = HttpClients.createDefault(); //HttpClient client = new HttpClient(); httppost = new HttpPost(protocol + "://" + host + ":" + port + url); StringEntity entity = new StringEntity(xmlbuffer.toString(),ContentType.APPLICATION_ATOM_XML); //RequestEntity entity = new StringRequestEntity( // xmlbuffer.toString(), "text/plain", "UTF-8"); httppost.setEntity(entity); // Create a custom response handler ResponseHandler<String> responseHandler = new ResponseHandler<String>() { @Override public String handleResponse( final HttpResponse response) throws ClientProtocolException, IOException { int status = response.getStatusLine().getStatusCode(); if (status >= 200 && status < 300) { HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; } else { LOGGER.error("Communication Error with ups quote " + status); throw new ClientProtocolException("UPS quote communication error " + status); } } }; String data = httpclient.execute(httppost, responseHandler); //int result = response.getStatusLine().getStatusCode(); //int result = client.executeMethod(httppost); /* if (result != 200) { LOGGER.error("Communication Error with ups quote " + result + " " + protocol + "://" + host + ":" + port + url); throw new Exception("UPS quote communication error " + result); }*/ LOGGER.debug("ups quote response " + data); UPSParsedElements parsed = new UPSParsedElements(); Digester digester = new Digester(); digester.push(parsed); digester.addCallMethod( "RatingServiceSelectionResponse/Response/Error", "setErrorCode", 0); digester.addCallMethod( "RatingServiceSelectionResponse/Response/ErrorDescriprion", "setError", 0); digester .addCallMethod( "RatingServiceSelectionResponse/Response/ResponseStatusCode", "setStatusCode", 0); digester .addCallMethod( "RatingServiceSelectionResponse/Response/ResponseStatusDescription", "setStatusMessage", 0); digester .addCallMethod( "RatingServiceSelectionResponse/Response/Error/ErrorDescription", "setError", 0); digester.addObjectCreate( "RatingServiceSelectionResponse/RatedShipment", ShippingOption.class); // digester.addSetProperties( // "RatingServiceSelectionResponse/RatedShipment", "sequence", // "optionId" ); digester .addCallMethod( "RatingServiceSelectionResponse/RatedShipment/Service/Code", "setOptionId", 0); digester .addCallMethod( "RatingServiceSelectionResponse/RatedShipment/TotalCharges/MonetaryValue", "setOptionPriceText", 0); //digester // .addCallMethod( // "RatingServiceSelectionResponse/RatedShipment/TotalCharges/CurrencyCode", // "setCurrency", 0); digester .addCallMethod( "RatingServiceSelectionResponse/RatedShipment/Service/Code", "setOptionCode", 0); digester .addCallMethod( "RatingServiceSelectionResponse/RatedShipment/GuaranteedDaysToDelivery", "setEstimatedNumberOfDays", 0); digester.addSetNext("RatingServiceSelectionResponse/RatedShipment", "addOption"); // <?xml // version="1.0"?><AddressValidationResponse><Response><TransactionReference><CustomerContext>SalesManager // Data</CustomerContext><XpciVersion>1.0</XpciVersion></TransactionReference><ResponseStatusCode>0</ResponseStatusCode><ResponseStatusDescription>Failure</ResponseStatusDescription><Error><ErrorSeverity>Hard</ErrorSeverity><ErrorCode>10002</ErrorCode><ErrorDescription>The // XML document is well formed but the document is not // valid</ErrorDescription><ErrorLocation><ErrorLocationElementName>AddressValidationRequest</ErrorLocationElementName></ErrorLocation></Error></Response></AddressValidationResponse> Reader xmlreader = new StringReader(data); digester.parse(xmlreader); if (!StringUtils.isBlank(parsed.getErrorCode())) { LOGGER.error("Can't process UPS statusCode=" + parsed.getErrorCode() + " message= " + parsed.getError()); throw new IntegrationException(parsed.getError()); } if (!StringUtils.isBlank(parsed.getStatusCode()) && !parsed.getStatusCode().equals("1")) { throw new IntegrationException(parsed.getError()); } if (parsed.getOptions() == null || parsed.getOptions().size() == 0) { throw new IntegrationException("No shipping options available for the configuration"); } /*String carrier = getShippingMethodDescription(locale); // cost is in CAD, need to do conversion boolean requiresCurrencyConversion = false; String storeCurrency = store.getCurrency(); if(!storeCurrency.equals(Constants.CURRENCY_CODE_CAD)) { requiresCurrencyConversion = true; } LabelUtil labelUtil = LabelUtil.getInstance(); Map serviceMap = com.salesmanager.core.util.ShippingUtil .buildServiceMap("upsxml", locale); *//** Details on whit RT quote information to display **//* MerchantConfiguration rtdetails = config .getMerchantConfiguration(ShippingConstants.MODULE_SHIPPING_DISPLAY_REALTIME_QUOTES); int displayQuoteDeliveryTime = ShippingConstants.NO_DISPLAY_RT_QUOTE_TIME; if (rtdetails != null) { if (!StringUtils.isBlank(rtdetails.getConfigurationValue1())) {// display // or // not // quotes try { displayQuoteDeliveryTime = Integer.parseInt(rtdetails .getConfigurationValue1()); } catch (Exception e) { log.error("Display quote is not an integer value [" + rtdetails.getConfigurationValue1() + "]"); } } }*/ List<ShippingOption> shippingOptions = parsed.getOptions(); if(shippingOptions!=null) { Map<String,String> details = module.getDetails(); for(ShippingOption option : shippingOptions) { String name = details.get(option.getOptionCode()); option.setOptionName(name); if(option.getOptionPrice()==null) { String priceText = option.getOptionPriceText(); if(StringUtils.isBlank(priceText)) { throw new IntegrationException("Price text is null for option " + name); } try { BigDecimal price = new BigDecimal(priceText); option.setOptionPrice(price); } catch(Exception e) { throw new IntegrationException("Can't convert to numeric price " + priceText); } } } } /* if (options != null) { Map selectedintlservices = (Map) config .getConfiguration("service-global-upsxml"); Iterator i = options.iterator(); while (i.hasNext()) { ShippingOption option = (ShippingOption) i.next(); // option.setCurrency(store.getCurrency()); StringBuffer description = new StringBuffer(); String code = option.getOptionCode(); option.setOptionCode(code); // get description String label = (String) serviceMap.get(code); if (label == null) { log .warn("UPSXML cannot find description for service code " + code); } option.setOptionName(label); description.append(option.getOptionName()); if (displayQuoteDeliveryTime == ShippingConstants.DISPLAY_RT_QUOTE_TIME) { if (!StringUtils.isBlank(option .getEstimatedNumberOfDays())) { description.append(" (").append( option.getEstimatedNumberOfDays()).append( " ").append( labelUtil.getText(locale, "label.generic.days.lowercase")) .append(")"); } } option.setDescription(description.toString()); // get currency if (!option.getCurrency().equals(store.getCurrency())) { option.setOptionPrice(CurrencyUtil.convertToCurrency( option.getOptionPrice(), option.getCurrency(), store.getCurrency())); } if (!selectedintlservices.containsKey(option .getOptionCode())) { if (returnColl == null) { returnColl = new ArrayList(); } returnColl.add(option); // options.remove(option); } } if (options.size() == 0) { LogMerchantUtil .log( store.getMerchantId(), " none of the service code returned by UPS [" + selectedintlservices .keySet() .toArray( new String[selectedintlservices .size()]) + "] for this shipping is in your selection list"); } }*/ return shippingOptions; } catch (Exception e1) { LOGGER.error("UPS quote error",e1); throw new IntegrationException(e1); } finally { if (reader != null) { try { reader.close(); } catch (Exception ignore) { } } if (httppost != null) { httppost.releaseConnection(); } } } @Override public CustomIntegrationConfiguration getCustomModuleConfiguration( MerchantStore store) throws IntegrationException { //nothing to do return null; }} class UPSParsedElements { private String statusCode; private String statusMessage; private String error = ""; private String errorCode = ""; private List<ShippingOption> options = new ArrayList<ShippingOption>(); public void addOption(ShippingOption option) { options.add(option); } public List<ShippingOption> getOptions() { return options; } public String getStatusCode() { return statusCode; } public void setStatusCode(String statusCode) { this.statusCode = statusCode; } public String getStatusMessage() { return statusMessage; } public void setStatusMessage(String statusMessage) { this.statusMessage = statusMessage; } public String getError() { return error; } public void setError(String error) { this.error = error; } public String getErrorCode() { return errorCode; } public void setErrorCode(String errorCode) { this.errorCode = errorCode; } }