You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by le...@apache.org on 2009/08/11 17:20:34 UTC

svn commit: r803151 [1/2] - in /ofbiz/trunk: applications/accounting/ applications/accounting/data/ applications/accounting/entitydef/ applications/accounting/servicedef/ applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/ applications/...

Author: lektran
Date: Tue Aug 11 15:20:32 2009
New Revision: 803151

URL: http://svn.apache.org/viewvc?rev=803151&view=rev
Log:
Initial implementation of the standard PayPal Express Checkout (i.e. not using Payflow Pro) OFBIZ-934

There are still a couple of issues preventing the checkout from being 100% functional:
1. I'm having trouble getting Instant Update API to work correctly which is used by PayPal to retrieve shipping estimates from OFBiz
2. The shipping option label and name parameters aren't working as documented and means I'm currently stuck presenting the shipping option id (partyId@shipmentMethodTypeId) to the user

Added:
    ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml   (with props)
    ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java   (with props)
Modified:
    ofbiz/trunk/applications/accounting/build.xml
    ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml
    ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml
    ofbiz/trunk/applications/accounting/ofbiz-component.xml
    ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/CheckOutHelper.java
    ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java
    ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEstimateWrapper.java
    ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEvents.java
    ofbiz/trunk/applications/order/src/org/ofbiz/order/thirdparty/paypal/ExpressCheckoutEvents.java
    ofbiz/trunk/applications/party/servicedef/services.xml
    ofbiz/trunk/specialpurpose/ecommerce/data/DemoProduct.xml
    ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml
    ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/cart/microcart.ftl

Modified: ofbiz/trunk/applications/accounting/build.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/build.xml?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/accounting/build.xml (original)
+++ ofbiz/trunk/applications/accounting/build.xml Tue Aug 11 15:20:32 2009
@@ -60,6 +60,7 @@
             <!-- exclude the payment processor packages; comment this out to not exclude if you have libs -->
             <exclude name="org/ofbiz/accounting/thirdparty/verisign/**"/>
             <exclude name="org/ofbiz/accounting/thirdparty/cybersource/**"/>
+        	<exclude name="org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java"/>
         </javac15>
     </target>
 

Modified: ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml (original)
+++ ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml Tue Aug 11 15:20:32 2009
@@ -699,7 +699,7 @@
     
     <PaymentGatewayConfigType paymentGatewayConfigTypeId="PAYPAL" hasTable="N" description="PayPal Payment Gateway"/>
     <PaymentGatewayConfig paymentGatewayConfigId="PAYPAL_CONFIG" paymentGatewayConfigTypeId="PAYPAL" description="PayPal Config"/>
-    <PaymentGatewayPayPal paymentGatewayConfigId="PAYPAL_CONFIG" businessEmail="" notifyUrl="" returnUrl="" cancelReturnUrl="" imageUrl="" confirmTemplate="/order/emailconfirmation.ftl" redirectUrl="https://www.sandbox.paypal.com/us/cgi-bin/webscr" confirmUrl="https://www.sandbox.paypal.com/us/cgi-bin/webscr"/>
+    <PaymentGatewayPayPal paymentGatewayConfigId="PAYPAL_CONFIG" businessEmail="" apiUserName="" apiPassword="" apiSignature="" apiEnvironment="" notifyUrl="" returnUrl="" cancelReturnUrl="" imageUrl="" confirmTemplate="/order/emailconfirmation.ftl" redirectUrl="https://www.sandbox.paypal.com/webscr" confirmUrl="https://www.sandbox.paypal.com/us/cgi-bin/webscr" shippingCallbackUrl="" requireConfirmedShipping="N"/>
     
     <PaymentGatewayConfigType paymentGatewayConfigTypeId="CLEARCOMMERCE" hasTable="N" description="Clear Commerce Payment Gateway"/>
     <PaymentGatewayConfig paymentGatewayConfigId="CLEARCOMMERCE_CONFIG" paymentGatewayConfigTypeId="CLEARCOMMERCE" description="Clear Commerce Config"/>
@@ -812,9 +812,16 @@
     <CustomMethod customMethodId="GIFT_RELEASE_VALUEL" customMethodTypeId="GIFT_RELEASE" customMethodName="valueLinkRelease" description="GIFT card ValueLink release"/>
     
     <CustomMethod customMethodId="PAYPAL_AUTH_PFP" customMethodTypeId="PAYPAL_AUTH" customMethodName="payflowPayPalProcessor" description="Payflow PayPal payment authorize"/>
+    <CustomMethod customMethodId="PAYPAL_AUTH" customMethodTypeId="PAYPAL_AUTH" customMethodName="payPalProcessor" description="PayPal payment authorize"/>
+
     <CustomMethod customMethodId="PAYPAL_CAPTURE_PFP" customMethodTypeId="PAYPAL_CAPTURE" customMethodName="payflowPayPalCapture" description="Payflow PayPal payment capture"/>
+    <CustomMethod customMethodId="PAYPAL_CAPTURE" customMethodTypeId="PAYPAL_CAPTURE" customMethodName="payPalCapture" description="PayPal payment capture"/>
+
     <CustomMethod customMethodId="PAYPAL_REFUND_PFP" customMethodTypeId="PAYPAL_REFUND" customMethodName="payflowPayPalRefund" description="Payflow PayPal payment refund"/>
-    <CustomMethod customMethodId="PAYPAL_RELEASE_PFP" customMethodTypeId="PAYPAL_RELEASE" customMethodName="payflowPayPalVoid" description="CC Payflow Pro release"/>
+    <CustomMethod customMethodId="PAYPAL_REFUND" customMethodTypeId="PAYPAL_REFUND" customMethodName="payPalRefund" description="PayPal payment refund"/>
+
+    <CustomMethod customMethodId="PAYPAL_RELEASE_PFP" customMethodTypeId="PAYPAL_RELEASE" customMethodName="payflowPayPalVoid" description="Payflow PayPal Order release"/>
+    <CustomMethod customMethodId="PAYPAL_RELEASE" customMethodTypeId="PAYPAL_RELEASE" customMethodName="payPalVoid" description="PayPal Order Release"/>
 
     <!-- Credit Cart Types -->
     <EnumerationType description="Credit Card Type" enumTypeId="CREDIT_CARD_TYPE" hasTable="N" parentTypeId=""/>

Modified: ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml (original)
+++ ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml Tue Aug 11 15:20:32 2009
@@ -3150,6 +3150,10 @@
             title="PayPal Payment Gateway Config">
         <field name="paymentGatewayConfigId" type="id-ne"></field>
         <field name="businessEmail" type="value"><description>Business e-mail</description></field>
+        <field name="apiUserName" type="short-varchar"><description>PayPal API UserName</description></field>
+        <field name="apiPassword" type="short-varchar"><description>PayPal API Password</description></field>
+        <field name="apiSignature" type="short-varchar"><description>PayPal API Signature</description></field>
+        <field name="apiEnvironment" type="short-varchar"><description>PayPal API Environment (valid values are: live, sandbox or beta-sandbox)</description></field>
         <field name="notifyUrl" type="value"><description>Notify URL</description></field>
         <field name="returnUrl" type="value"><description>Return URL</description></field>
         <field name="cancelReturnUrl" type="value"><description>Return On Cancel URL</description></field>
@@ -3157,6 +3161,8 @@
         <field name="confirmTemplate" type="value"><description>Thank-You / Confirm Order Template (rendered via Freemarker)</description></field>
         <field name="redirectUrl" type="value"><description>PayPal Redirect URL (Sandbox/Production)</description></field>
         <field name="confirmUrl" type="value"><description>PayPal Confirm URL Sandbox/Production (JSSE must be configured to use SSL)</description></field>
+        <field name="shippingCallbackUrl" type="url"><description>Specific to Express Checkout which performs callbacks to our server to retrieve shipping estimates</description></field>
+        <field name="requireConfirmedShipping" type="indicator"><description>Indicates that you require that the customer’s shipping address on file with PayPal be a confirmed address.</description></field>
         <prim-key field="paymentGatewayConfigId"/>
         <relation type="one" fk-name="PGPP_PGC" rel-entity-name="PaymentGatewayConfig">
             <key-map field-name="paymentGatewayConfigId"/>

Modified: ofbiz/trunk/applications/accounting/ofbiz-component.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/ofbiz-component.xml?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/accounting/ofbiz-component.xml (original)
+++ ofbiz/trunk/applications/accounting/ofbiz-component.xml Tue Aug 11 15:20:32 2009
@@ -64,13 +64,14 @@
     <service-resource type="model" loader="main" location="servicedef/services_olap.xml"/>
 
     <!-- Payment Processor/Gateway Service Definitions -->
+    <service-resource type="model" loader="main" location="servicedef/services_authorizedotnet.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_clearcommerce.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_cybersource.xml"/>
+    <service-resource type="model" loader="main" location="servicedef/services_paypal.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_pccharge.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_rita.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_valuelink.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_verisign.xml"/>
-    <service-resource type="model" loader="main" location="servicedef/services_authorizedotnet.xml"/>
 
     <service-resource type="group" loader="main" location="servicedef/groups.xml"/>
     <service-resource type="eca" loader="main" location="servicedef/secas.xml"/>

Added: ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml?rev=803151&view=auto
==============================================================================
--- ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml (added)
+++ ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml Tue Aug 11 15:20:32 2009
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+
+<services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/services.xsd">
+    <description>PayPal Services</description>
+
+    <service name="payPalSetExpressCheckout" engine="java"
+             location="org.ofbiz.accounting.thirdparty.paypal.PayPalServices" invoke="setExpressCheckout">
+        <implements service="payPalSetExpressCheckoutInterface"/>
+    </service>
+    <service name="payPalGetExpressCheckout" engine="java"
+             location="org.ofbiz.accounting.thirdparty.paypal.PayPalServices" invoke="getExpressCheckout">
+        <implements service="payPalGetExpressCheckoutInterface"/>
+    </service>
+    <service name="payPalDoExpressCheckout" engine="java"
+             location="org.ofbiz.accounting.thirdparty.paypal.PayPalServices" invoke="doExpressCheckout">
+        <implements service="payPalDoExpressCheckoutInterface"/>
+    </service>
+    
+    <service name="payPalProcessor" engine="java"
+             location="org.ofbiz.accounting.thirdparty.paypal.PayPalServices" invoke="doAuthorization">
+        <implements service="payPalProcessInterface"/>
+    </service>
+    <service name="payPalCapture" engine="java"
+             location="org.ofbiz.accounting.thirdparty.paypal.PayPalServices" invoke="doCapture">
+        <implements service="payPalCaptureInterface"/>
+    </service>
+    <service name="payPalVoid" engine="java"
+             location="org.ofbiz.accounting.thirdparty.paypal.PayPalServices" invoke="doVoid">
+        <description>PayPal Order Payment Void</description>
+        <implements service="paymentReleaseInterface"/>
+    </service>
+    <service name="payPalRefund" engine="java"
+             location="org.ofbiz.accounting.thirdparty.paypal.PayPalServices" invoke="doRefund">
+        <implements service="paymentRefundInterface"/>
+    </service>
+</services>

Propchange: ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml
------------------------------------------------------------------------------
    svn:keywords = "Date Rev Author URL Id"

Propchange: ofbiz/trunk/applications/accounting/servicedef/services_paypal.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java?rev=803151&view=auto
==============================================================================
--- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java (added)
+++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java Tue Aug 11 15:20:32 2009
@@ -0,0 +1,999 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.ofbiz.accounting.thirdparty.paypal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.lang.ref.WeakReference;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javolution.util.FastMap;
+
+import org.ofbiz.accounting.payment.PaymentGatewayServices;
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.GeneralException;
+import org.ofbiz.base.util.StringUtil;
+import org.ofbiz.base.util.UtilDateTime;
+import org.ofbiz.base.util.UtilMisc;
+import org.ofbiz.base.util.UtilProperties;
+import org.ofbiz.base.util.UtilValidate;
+import org.ofbiz.entity.GenericDelegator;
+import org.ofbiz.entity.GenericEntityException;
+import org.ofbiz.entity.GenericValue;
+import org.ofbiz.entity.condition.EntityComparisonOperator;
+import org.ofbiz.entity.condition.EntityCondition;
+import org.ofbiz.entity.condition.EntityFunction;
+import org.ofbiz.entity.util.EntityUtil;
+import org.ofbiz.order.order.OrderReadHelper;
+import org.ofbiz.order.shoppingcart.CartItemModifyException;
+import org.ofbiz.order.shoppingcart.CheckOutHelper;
+import org.ofbiz.order.shoppingcart.ShoppingCart;
+import org.ofbiz.order.shoppingcart.ShoppingCartItem;
+import org.ofbiz.order.shoppingcart.ShoppingCart.CartShipInfo;
+import org.ofbiz.order.shoppingcart.shipping.ShippingEstimateWrapper;
+import org.ofbiz.order.shoppingcart.shipping.ShippingEvents;
+import org.ofbiz.product.store.ProductStoreWorker;
+import org.ofbiz.service.DispatchContext;
+import org.ofbiz.service.GenericServiceException;
+import org.ofbiz.service.LocalDispatcher;
+import org.ofbiz.service.ModelService;
+import org.ofbiz.service.ServiceUtil;
+
+import com.paypal.sdk.core.nvp.NVPDecoder;
+import com.paypal.sdk.core.nvp.NVPEncoder;
+import com.paypal.sdk.exceptions.PayPalException;
+import com.paypal.sdk.profiles.APIProfile;
+import com.paypal.sdk.profiles.ProfileFactory;
+import com.paypal.sdk.services.NVPCallerServices;
+
+/**
+ * PayPalServices for NVP API communication
+ */
+public class PayPalServices {
+
+    public static final String module = PayPalServices.class.getName();
+    // Used to maintain a weak reference to the ShoppingCart for customers who have gone to PayPal to checkout
+    // so that we can quickly grab the cart, perform shipment estimates and send the info back to PayPal.
+    // The weak key is a simple wrapper for the checkout token String and is stored as a cart attribute. The value
+    // is a weak reference to the ShoppingCart itself.  Entries will be removed as carts are removed from the 
+    // session (i.e. on cart clear or successful checkout) or when the session is destroyed
+    private static Map<TokenWrapper, WeakReference<ShoppingCart>> tokenCartMap = new WeakHashMap<TokenWrapper, WeakReference<ShoppingCart>>();
+    
+    public static Map<String, Object> setExpressCheckout(DispatchContext dctx, Map<String, ? extends Object> context) {
+        ShoppingCart cart = (ShoppingCart) context.get("cart");
+        Locale locale = cart.getLocale();
+        if (cart == null || cart.items().size() <= 0) {
+            return ServiceUtil.returnError(UtilProperties.getMessage("AccountingErrorUiLabels", "AccountingPayPalShoppingCartIsEmpty", locale));
+        }
+
+        GenericValue payPalConfig = getPaymentMethodGatewayPayPal(dctx, context, null);
+        if (payPalConfig == null) {
+            return ServiceUtil.returnError("Couldn't retrieve a PaymentGatewayConfigPayPal record for Express Checkout, cannot continue.");
+        }
+
+        
+        NVPEncoder encoder = new NVPEncoder();
+        
+        // Set Express Checkout Request Parameters
+        encoder.add("METHOD", "SetExpressCheckout");
+        String token = (String) cart.getAttribute("payPalCheckoutToken");
+        if (UtilValidate.isNotEmpty(token)) {
+            encoder.add("TOKEN", token);
+        }
+        encoder.add("RETURNURL", payPalConfig.getString("returnUrl"));
+        encoder.add("CANCELURL", payPalConfig.getString("cancelReturnUrl"));
+        if (!cart.shippingApplies()) {
+            encoder.add("NOSHIPPING", "1");
+        } else {
+            encoder.add("CALLBACK", payPalConfig.getString("shippingCallbackUrl"));
+            encoder.add("CALLBACKTIMEOUT", "6");
+            // Default to no
+            String reqConfirmShipping = "Y".equals(payPalConfig.getString("requireConfirmedShipping")) ? "1" : "0";
+            encoder.add("REQCONFIRMSHIPPING", reqConfirmShipping);
+            // Default shipment method
+            encoder.add("L_SHIPPINGOPTIONISDEFAULT0", "true");
+            encoder.add("L_SHIPPINGOPTIONNAME0", "NO_SHIPPING@_NA_");
+            //TODO: This isn't working
+            encoder.add("L_SHIPPINGOPTIONLABEL0", "Calculated Offline");
+            encoder.add("L_SHIPPINGOPTIONAMOUNT0", "0.00");
+        }
+        encoder.add("ALLOWNOTE", "1");
+        encoder.add("INSURANCEOPTIONOFFERED", "false");
+        if (UtilValidate.isNotEmpty(payPalConfig.getString("imageUrl")));
+        encoder.add("PAYMENTACTION", "Order");
+        
+        // Cart information
+        try {
+            addCartDetails(encoder, cart);
+        } catch (GenericEntityException e) {
+            Debug.logError(e, module);
+            ServiceUtil.returnError("An error occurred while retreiving cart details");
+        }
+
+        NVPDecoder decoder;
+        try {
+            decoder = sendNVPRequest(payPalConfig, encoder);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        
+        Map<String, String> errorMessages = getErrorMessageMap(decoder);
+        if (UtilValidate.isNotEmpty(errorMessages)) {
+            if (errorMessages.containsKey("10411")) {
+                // Token has expired, get a new one
+                cart.setAttribute("payPalCheckoutToken", null);
+                return PayPalServices.setExpressCheckout(dctx, context);
+            }
+            return ServiceUtil.returnError(UtilMisc.toList(errorMessages.values()));
+        }
+
+        token = decoder.get("TOKEN");
+        cart.setAttribute("payPalCheckoutToken", token);
+        cart.setAttribute("payPalCheckoutTokenObj", new TokenWrapper(token));
+        //PayPalServices.tokenCartMap.put(token, new ShoppingCartWrapper(cart));
+        return ServiceUtil.returnSuccess();
+    }
+
+    public Map<String, Object> payPalExpressCheckoutUpdate(DispatchContext dctx, Map<String, Object> context) {
+        LocalDispatcher dispatcher = dctx.getDispatcher();
+        GenericDelegator delegator = dctx.getDelegator();
+        HttpServletRequest request = (HttpServletRequest) context.get("request");
+        HttpServletResponse response = (HttpServletResponse) context.get("response");
+        String remoteHost = request.getRemoteHost();
+        if (!remoteHost.endsWith(".paypal.com")) {
+            try {
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                Debug.logError("An Express Checkout Update request was received from a host other than *.paypal.com, responded with 403 Forbidden", module);
+            } catch (IOException e) {
+                Debug.logError(e, module);
+            }
+            return ServiceUtil.returnSuccess();
+        }
+        
+        String requestMessage = null;
+        try {
+            BufferedReader reader = request.getReader();
+            requestMessage = reader.readLine();
+            reader.close();
+        } catch (IOException e) {
+            Debug.logError(e, module);
+        }
+        if (requestMessage == null) {
+            return ServiceUtil.returnSuccess();
+        }
+        
+        NVPDecoder decoder = new NVPDecoder();
+        try {
+            decoder.decode(requestMessage);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnSuccess();
+        }
+
+        String token = decoder.get("TOKEN");
+        WeakReference<ShoppingCart> weakCart = tokenCartMap.get(new TokenWrapper(token));
+        ShoppingCart customerCart = weakCart.get();
+        if (customerCart != null) {
+            // Since most if not all of the shipping estimate codes requires a persisted contactMechId we'll create one and
+            // then delete once we're done, now is not the time to worry about updating everything
+            GenericValue shipAddress = delegator.makeValue("PostalAddress");
+            String contactMechId = delegator.getNextSeqId("ContactMech");
+            shipAddress.put("contactMechId", contactMechId);
+            shipAddress.put("address1", decoder.get("SHIPTOSTREET"));
+            shipAddress.put("address2", decoder.get("SHIPTOSTREET2"));
+            shipAddress.put("city", decoder.get("SHIPTOCITY"));
+            shipAddress.put("stateProvinceGeoId", decoder.get("SHIPTOSTATE"));
+            shipAddress.put("postalCode", decoder.get("SHIPTOZIP"));
+            String countryGeoCode = decoder.get("SHIPTOCOUNTRY"); // PayPal says it is required so I'm not going to check
+            try {
+                String countryGeoId = PayPalServices.getCountryGeoIdFromGeoCode(countryGeoCode, delegator);
+                if (countryGeoId == null) {
+                    return ServiceUtil.returnSuccess();
+                }
+                shipAddress.put("countryGeoId", countryGeoId);
+                shipAddress.create();
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+            }
+            // clone the cart so we can modify it temporarily
+            ShoppingCart cart = new ShoppingCart(customerCart);
+            CheckOutHelper coh = new CheckOutHelper(dispatcher, delegator, cart);
+            coh.setCheckOutShippingAddress(contactMechId);
+            ShippingEstimateWrapper estWrapper = new ShippingEstimateWrapper(dispatcher, cart, 0);
+            int line = 0;
+            NVPEncoder encoder = new NVPEncoder();
+            encoder.add("METHOD", "CallbackResponse");
+            
+            for (GenericValue shipMethod : estWrapper.getShippingMethods()) {
+                BigDecimal estimate = estWrapper.getShippingEstimate(shipMethod);
+                //Check that we have a valid estimate (allowing zero value estimates for now)
+                if (estimate == null || estimate.compareTo(BigDecimal.ZERO) < 0) {
+                    continue;
+                }
+                cart.setShipmentMethodTypeId(shipMethod.getString("shipmentMethodTypeId"));
+                cart.setCarrierPartyId(shipMethod.getString("partyId"));
+                try {
+                    coh.calcAndAddTax();
+                } catch (GeneralException e) {
+                    Debug.logError(e, module);
+                    continue;
+                }
+                String estimateName = shipMethod.getString("shipmentMethodTypeId") + "@" + shipMethod.getString("partyId");
+                encoder.add("L_SHIPPINGOPTIONLABEL" + line, estimateName);
+                String estimateLabel = shipMethod.getString("partyId") + " " + shipMethod.getString("description");
+                encoder.add("L_SHIPPINGOPTIONNAME" + line, estimateLabel);
+                encoder.add("L_SHIPPINGOPTIONAMOUNT" + line, estimate.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+                // Just make this first one default for now
+                encoder.add("L_SHIPPINGOPTIONISDEFAULT" + line, line == 0 ? "true" : "false");
+                encoder.add("L_TAXAMT" + line, cart.getTotalSalesTax().setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+                line++;
+            }
+            String responseMsg = null;
+            try {
+                responseMsg = encoder.encode();
+            } catch (PayPalException e) {
+                Debug.logError(e, module);
+            }
+            if (responseMsg != null) {
+                try {
+                    response.setContentLength(responseMsg.getBytes("UTF-8").length);
+                } catch (UnsupportedEncodingException e) {
+                    Debug.logError(e, module);
+                }
+
+                try {
+                    Writer writer = response.getWriter();
+                    writer.write(responseMsg);
+                    writer.close();
+                } catch (IOException e) {
+                    Debug.logError(e, module);
+                }
+            }
+
+            // Remove the temporary ship address
+            try {
+                shipAddress.remove();
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+            }
+        }
+
+        return ServiceUtil.returnSuccess();
+    }
+
+    private static Map<String, String> getErrorMessageMap(NVPDecoder decoder) {
+        String ack = decoder.get("ACK");
+        Map<String, String> result = null;
+        if (!"Success".equals(ack)) {
+            result = FastMap.newInstance();
+            int i = 0;
+            while (UtilValidate.isNotEmpty(decoder.get("L_ERRORCODE" + i))) {
+                String errorCode = decoder.get("L_ERRORCODE" + i);
+                String longMsg = decoder.get("L_LONGMESSAGE" + i);
+                result.put(errorCode, "PayPal Response Error: [" + errorCode + "]" + longMsg);
+                i++;
+            }
+        }
+        return result;
+    }
+
+    private static void addCartDetails(NVPEncoder encoder, ShoppingCart cart) throws GenericEntityException {
+        encoder.add("CURRENCYCODE", cart.getCurrency());
+        int line = 0;
+        for (ShoppingCartItem item : cart.items()) {
+            encoder.add("L_NUMBER" + line, item.getProductId());
+            encoder.add("L_NAME" + line, item.getName());
+            encoder.add("L_AMT" + line, item.getBasePrice().setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+            encoder.add("L_QTY" + line, item.getQuantity().toBigInteger().toString());
+            line++;
+            BigDecimal otherAdjustments = item.getOtherAdjustments();
+            if (otherAdjustments.compareTo(BigDecimal.ZERO) != 0) {
+                encoder.add("L_NUMBER" + line, item.getProductId());
+                encoder.add("L_NAME" + line, item.getName() + " Adjustments");
+                encoder.add("L_AMT" + line, otherAdjustments.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+                encoder.add("L_QTY" + line, "1");
+                line++;
+            }
+        }
+        BigDecimal otherAdjustments = cart.getOrderOtherAdjustmentTotal();
+        if (otherAdjustments.compareTo(BigDecimal.ZERO) != 0) {
+            encoder.add("L_NUMBER" + line, "N/A");
+            encoder.add("L_NAME" + line, "Order Adjustments");
+            encoder.add("L_AMT" + line, otherAdjustments.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+            encoder.add("L_QTY" + line, "1");
+            line++;
+        }
+        encoder.add("ITEMAMT", cart.getSubTotal().add(otherAdjustments).setScale(2).toPlainString());
+        encoder.add("SHIPPINGAMT", "1.00");
+        encoder.add("TAXAMT", "0.00");
+        encoder.add("AMT", cart.getSubTotal().add(otherAdjustments).add(BigDecimal.ONE).setScale(2).toPlainString());
+        //NOTE: The docs say this is optional but then won't work without it
+        encoder.add("MAXAMT", cart.getSubTotal().add(otherAdjustments).add(BigDecimal.ONE).setScale(2).toPlainString());
+    }
+    
+    public static Map<String, Object> getExpressCheckout(DispatchContext dctx, Map<String, Object> context) {
+        LocalDispatcher dispatcher = dctx.getDispatcher();
+        GenericDelegator delegator = dctx.getDelegator();
+
+        ShoppingCart cart = (ShoppingCart) context.get("cart");
+        GenericValue payPalConfig = getPaymentMethodGatewayPayPal(dctx, context, null);
+        if (payPalConfig == null) {
+            return ServiceUtil.returnError("Couldn't retrieve a PaymentGatewayConfigPayPal record for Express Checkout, cannot continue.");
+        }
+
+        NVPEncoder encoder = new NVPEncoder();
+        encoder.add("METHOD", "GetExpressCheckoutDetails");
+        String token = (String) cart.getAttribute("payPalCheckoutToken");
+        if (UtilValidate.isNotEmpty(token)) {
+            encoder.add("TOKEN", token);
+        } else {
+            return ServiceUtil.returnError("Express Checkout token not present in cart, cannot get checkout details.");
+        }
+        
+        NVPDecoder decoder;
+        try {
+            decoder = sendNVPRequest(payPalConfig, encoder);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+
+        if (UtilValidate.isNotEmpty(decoder.get("NOTE"))) {
+            cart.addOrderNote(decoder.get("NOTE"));
+        }
+        
+        if (cart.getUserLogin() == null) {
+            try {
+                GenericValue userLogin = delegator.findOne("UserLogin", false, "userLoginId", "anonymous");
+                try {
+                    cart.setUserLogin(userLogin, dispatcher);
+                } catch (CartItemModifyException e) {
+                    Debug.logError(e, module);
+                    return ServiceUtil.returnError(e.getMessage());
+                }
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+                return ServiceUtil.returnError(e.getMessage());
+            }
+        }
+        boolean anon = "anonymous".equals(cart.getUserLogin().getString("userLoginId"));
+        // Even if anon, a party could already have been created
+        String partyId = cart.getOrderPartyId();
+        if (partyId == null && anon) {
+            // Check nothing has been set on the anon userLogin either
+            partyId = cart.getUserLogin() != null ? cart.getUserLogin().getString("partyId") : null;
+            cart.setOrderPartyId(partyId);
+        }
+        if (partyId != null) {
+            GenericValue party = null;
+            try {
+                party = delegator.findOne("Party", false, "partyId", partyId);
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+            }
+            if (party == null) {
+                partyId = null;
+            }
+        }
+
+        Map<String, Object> inMap = FastMap.newInstance();
+        Map<String, Object> outMap = null;
+        // Create the person if necessary
+        boolean newParty = false;
+        if (partyId == null) {
+            newParty = true;
+            inMap.put("userLogin", cart.getUserLogin());
+            inMap.put("personalTitle", decoder.get("SALUTATION"));
+            inMap.put("firstName", decoder.get("FIRSTNAME"));
+            inMap.put("middleName", decoder.get("MIDDLENAME"));
+            inMap.put("lastName", decoder.get("LASTNAME"));
+            inMap.put("suffix", decoder.get("SUFFIX"));
+            try {
+                outMap = dispatcher.runSync("createPerson", inMap);
+                partyId = (String) outMap.get("partyId");
+                cart.setOrderPartyId(partyId);
+                cart.getUserLogin().setString("partyId", partyId);
+                inMap.clear();
+                inMap.put("userLogin", cart.getUserLogin());
+                inMap.put("partyId", partyId);
+                inMap.put("roleTypeId", "CUSTOMER");
+                dispatcher.runSync("createPartyRole", inMap);
+            } catch (GenericServiceException e) {
+                Debug.logError(e, module);
+                return ServiceUtil.returnError(e.getMessage());
+            }
+        }
+        // Create a new email address if necessary
+        String emailContactMechId = null;
+        String emailContactPurposeTypeId = "PRIMARY_EMAIL";
+        String emailAddress = decoder.get("EMAIL");
+        if (!newParty) {
+            EntityCondition cond = EntityCondition.makeCondition(UtilMisc.toList(
+                    EntityCondition.makeCondition(UtilMisc.toMap("partyId", partyId, "contactMechTypeId", "EMAIL_ADDRESS")),
+                    EntityCondition.makeCondition(EntityFunction.UPPER_FIELD("infoString"), EntityComparisonOperator.EQUALS, EntityFunction.UPPER(emailAddress)),
+                    EntityUtil.getFilterByDateExpr()
+
+            ));
+            try {
+                GenericValue matchingEmail = EntityUtil.getFirst(delegator.findList("PartyAndContactMech", cond, null, UtilMisc.toList("fromDate"), null, false));
+                if (matchingEmail != null) {
+                    emailContactMechId = matchingEmail.getString("contactMechId");
+                } else {
+                    // No email found so we'll need to create one but first check if it should be PRIMARY or just BILLING
+                    cond = EntityCondition.makeCondition(UtilMisc.toList(
+                            EntityCondition.makeCondition(UtilMisc.toMap("partyId", partyId, "contactMechTypeId", "EMAIL_ADDRESS", "contactMechPurposeTypeId", "PRIMARY_EMAIL")),
+                            EntityCondition.makeConditionDate("contactFromDate", "contactThruDate"),
+                            EntityCondition.makeConditionDate("purposeFromDate", "purposeThruDate")));
+                    List<GenericValue> primaryEmails = delegator.findList("PartyContactWithPurpose", cond, null, null, null, false);
+                    if (UtilValidate.isNotEmpty(primaryEmails)) emailContactPurposeTypeId = "BILLING_EMAIL";
+                }
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+            }
+        }
+        if (emailContactMechId == null) {
+            inMap.clear();
+            inMap.put("userLogin", cart.getUserLogin());
+            inMap.put("contactMechPurposeTypeId", emailContactPurposeTypeId);
+            inMap.put("emailAddress", emailAddress);
+            inMap.put("partyId", partyId);
+            inMap.put("roleTypeId", "CUSTOMER");
+            inMap.put("verified", "Y");  // Going to assume PayPal has taken care of this for us
+            inMap.put("fromDate", UtilDateTime.nowTimestamp());
+            try {
+                outMap = dispatcher.runSync("createPartyEmailAddress", inMap);
+                emailContactMechId = (String) outMap.get("contactMechId");
+            } catch (GenericServiceException e) {
+                Debug.logError(e, module);
+                return ServiceUtil.returnError(e.getMessage());
+            }
+        }
+        cart.addContactMech("ORDER_EMAIL", emailContactMechId);
+
+        // Phone number
+        String phoneNumber = decoder.get("PHONENUM");
+        String phoneContactId = null;
+        if (phoneNumber != null) {
+            inMap.clear();
+            if (phoneNumber.startsWith("+")) {
+                // International, format is +XXX XXXXXXXX which we'll split into countryCode + contactNumber
+                String[] phoneNumbers = phoneNumber.split(" ");
+                inMap.put("countryCode", StringUtil.removeNonNumeric(phoneNumbers[0]));
+                inMap.put("contactNumber", phoneNumbers[1]);
+            } else {
+                // U.S., format is XXX-XXX-XXXX which we'll split into areaCode + contactNumber
+                inMap.put("countryCode", "1");
+                String[] phoneNumbers = phoneNumber.split("-");
+                inMap.put("areaCode", phoneNumbers[0]);
+                inMap.put("contactNumber", phoneNumbers[1] + phoneNumbers[2]);
+            }
+            inMap.put("userLogin", cart.getUserLogin());
+            inMap.put("partyId", partyId);
+            try {
+                outMap = dispatcher.runSync("createUpdatePartyTelecomNumber", inMap);
+                phoneContactId = (String) outMap.get("contactMechId");
+                cart.addContactMech("PHONE_BILLING", phoneContactId);
+            } catch (GenericServiceException e) {
+                Debug.logError(e, module);
+            }
+        }
+        // Create a new Postal Address if necessary
+        String postalContactId = null;
+        boolean needsShippingPurpose = true;
+        // if the cart for some reason already has a billing address, we'll leave it be
+        boolean needsBillingPurpose = (cart.getContactMech("BILLING_LOCATION") == null);
+        Map<String, Object> postalMap = FastMap.newInstance();
+        postalMap.put("toName", decoder.get("SHIPTONAME"));
+        postalMap.put("address1", decoder.get("SHIPTOSTREET"));
+        postalMap.put("address2", decoder.get("SHIPTOSTREET2"));
+        postalMap.put("city", decoder.get("SHIPTOCITY"));
+        postalMap.put("stateProvinceGeoId", decoder.get("SHIPTOSTATE"));
+        postalMap.put("postalCode", decoder.get("SHIPTOZIP"));
+        postalMap.put("countryGeoId", PayPalServices.getCountryGeoIdFromGeoCode(decoder.get("SHIPTOCOUNTRYCODE"), delegator));
+        if (!newParty) {
+            // We want an exact match only
+            EntityCondition cond = EntityCondition.makeCondition(UtilMisc.toList(
+                    EntityCondition.makeCondition(postalMap),
+                    EntityCondition.makeCondition(UtilMisc.toMap("attnName", null, "directions", null, "postalCodeExt", null,"postalCodeGeoId", null)),
+                    EntityUtil.getFilterByDateExpr(),
+                    EntityCondition.makeCondition("partyId", partyId)
+            ));
+            try {
+                GenericValue postalMatch = EntityUtil.getFirst(delegator.findList("PartyAndPostalAddress", cond, null, UtilMisc.toList("fromDate"), null, false));
+                if (postalMatch != null) {
+                    postalContactId = postalMatch.getString("contactMechId");
+                    EntityCondition purposeCond = EntityCondition.makeCondition(UtilMisc.toList(
+                            EntityCondition.makeCondition(UtilMisc.toMap("partyId", partyId, "contactMechId", postalContactId)),
+                            EntityUtil.getFilterByDateExpr()
+                    ));
+                    List<GenericValue> postalPurposes = delegator.findList("PartyContactMechPurpose", purposeCond, null, null, null, false);
+                    List<Object> purposeStrings = EntityUtil.getFieldListFromEntityList(postalPurposes, "contactMechPurposeTypeId", false);
+                    if (UtilValidate.isNotEmpty(purposeStrings) && purposeStrings.contains("SHIPPING_LOCATION")) {
+                        needsShippingPurpose = false;
+                    }
+                    if (needsBillingPurpose && UtilValidate.isNotEmpty(purposeStrings) && purposeStrings.contains("BILLING_LOCATION")) {
+                        needsBillingPurpose = false;
+                    }
+                }
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+            }
+        }
+        if (postalContactId == null) {
+            postalMap.put("userLogin", cart.getUserLogin());
+            postalMap.put("fromDate", UtilDateTime.nowTimestamp());
+            try {
+                outMap = dispatcher.runSync("createPartyPostalAddress", postalMap);
+                postalContactId = (String) outMap.get("contactMechId");
+            } catch (GenericServiceException e) {
+                Debug.logError(e, module);
+                return ServiceUtil.returnError(e.getMessage());
+            }
+        }
+        if (needsShippingPurpose || needsBillingPurpose) {
+            inMap.clear();
+            inMap.put("userLogin", cart.getUserLogin());
+            inMap.put("contactMechId", postalContactId);
+            inMap.put("partyId", partyId);
+            try {
+                if (needsShippingPurpose) {
+                    inMap.put("contactMechPurposeTypeId", "SHIPPING_LOCATION");
+                    dispatcher.runSync("createPartyContactMechPurpose", inMap);
+                }
+                if (needsBillingPurpose) {
+                    inMap.put("contactMechPurposeTypeId", "BILLING_LOCATION");
+                    dispatcher.runSync("createPartyContactMechPurpose", inMap);
+                }
+            } catch (GenericServiceException e) {
+                // Not the end of the world, we'll carry on
+                Debug.log(e.getMessage());
+            }
+        }
+        
+        // Load the selected shipping method
+        String shipMethod = decoder.get("SHIPPINGOPTIONNAME");
+        String[] shipMethodSplit = shipMethod.split("@");
+        cart.setShipmentMethodTypeId(shipMethodSplit[0]);
+        cart.setCarrierPartyId(shipMethodSplit[1]);
+        //Get rid of any excess ship groups
+        List<CartShipInfo> shipGroups = cart.getShipGroups();
+        for (int i = 1; i < shipGroups.size(); i++) {
+            Map<ShoppingCartItem, BigDecimal> items = cart.getShipGroupItems(i);
+            for (Map.Entry<ShoppingCartItem, BigDecimal> entry : items.entrySet()) {
+                cart.positionItemToGroup(entry.getKey(), entry.getValue(), i, 0, false);
+            }
+        }
+        cart.cleanUpShipGroups();
+        cart.setShippingContactMechId(postalContactId);
+        Map<String, Object> result = ShippingEvents.getShipGroupEstimate(dispatcher, delegator, cart, 0);
+        if (result.get(ModelService.RESPONSE_MESSAGE).equals(ModelService.RESPOND_ERROR)) {
+            return ServiceUtil.returnError((String) result.get(ModelService.ERROR_MESSAGE));
+        }
+
+        BigDecimal shippingTotal = (BigDecimal) result.get("shippingTotal");
+        if (shippingTotal == null) {
+            shippingTotal = BigDecimal.ZERO;
+        }
+        cart.setItemShipGroupEstimate(shippingTotal, 0);
+        CheckOutHelper cho = new CheckOutHelper(dispatcher, delegator, cart);
+        try {
+            cho.calcAndAddTax();
+        } catch (GeneralException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        
+        // Create the PayPal payment method
+        inMap.clear();
+        inMap.put("userLogin", cart.getUserLogin());
+        inMap.put("partyId", partyId);
+        inMap.put("contactMechId", postalContactId);
+        inMap.put("fromDate", UtilDateTime.nowTimestamp());
+        inMap.put("payerId", decoder.get("PAYERID"));
+        inMap.put("expressCheckoutToken", token);
+        inMap.put("payerStatus", decoder.get("PAYERSTATUS"));
+
+        try {
+            outMap = dispatcher.runSync("createPayPalPaymentMethod", inMap);
+        } catch (GenericServiceException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        String paymentMethodId = (String) outMap.get("paymentMethodId");
+        
+        cart.clearPayments();
+        BigDecimal maxAmount = cart.getGrandTotal().setScale(2, BigDecimal.ROUND_HALF_UP);
+        cart.addPaymentAmount(paymentMethodId, maxAmount, true);
+        
+        return ServiceUtil.returnSuccess();
+
+    }
+
+    // Note we're not doing a lot of error checking here as this method is really only used
+    // to confirm the order with PayPal, the subsequent authorizations will handle any errors
+    // that may occur.
+    public static Map<String, Object> doExpressCheckout(DispatchContext dctx, Map<String, Object> context) {
+        LocalDispatcher dispatcher = dctx.getDispatcher();
+        GenericDelegator delegator = dctx.getDelegator();
+        GenericValue userLogin = (GenericValue) context.get("userLogin");
+        GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
+        OrderReadHelper orh = new OrderReadHelper(delegator, paymentPref.getString("orderId"));
+
+        GenericValue payPalPaymentSetting = getPaymentMethodGatewayPayPal(dctx, context, null);
+        GenericValue payPalPaymentMethod = null;
+        try {
+            payPalPaymentMethod = paymentPref.getRelatedOne("PaymentMethod");
+            payPalPaymentMethod = payPalPaymentMethod.getRelatedOne("PayPalPaymentMethod");
+        } catch (GenericEntityException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        BigDecimal processAmount = paymentPref.getBigDecimal("maxAmount");
+
+        NVPEncoder encoder = new NVPEncoder();
+        encoder.add("METHOD", "DoExpressCheckoutPayment");
+        encoder.add("TOKEN", payPalPaymentMethod.getString("expressCheckoutToken"));
+        encoder.add("PAYMENTACTION", "Order");
+        encoder.add("PAYERID", payPalPaymentMethod.getString("payerId"));
+        // set the amount
+        encoder.add("AMT", processAmount.setScale(2).toPlainString());
+        encoder.add("CURRENCYCODE", orh.getCurrency());
+        BigDecimal grandTotal = orh.getOrderGrandTotal();
+        BigDecimal shippingTotal = orh.getShippingTotal().setScale(2, BigDecimal.ROUND_HALF_UP);
+        BigDecimal taxTotal = orh.getTaxTotal().setScale(2, BigDecimal.ROUND_HALF_UP);
+        BigDecimal subTotal = grandTotal.subtract(shippingTotal).subtract(taxTotal).setScale(2, BigDecimal.ROUND_HALF_UP);
+        encoder.add("ITEMAMT", subTotal.toPlainString());
+        encoder.add("SHIPPINGAMT", shippingTotal.toPlainString());
+        encoder.add("TAXAMT", taxTotal.toPlainString());
+        
+        NVPDecoder decoder = null;
+        try {
+            decoder = sendNVPRequest(payPalPaymentSetting, encoder);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        if (decoder == null) {
+            return ServiceUtil.returnError("An error occurred while communicating with PayPal");
+        }
+
+        Map<String, String> errorMessages = getErrorMessageMap(decoder);
+        if (UtilValidate.isNotEmpty(errorMessages)) {
+            if (errorMessages.containsKey("10417")) {
+                // "The transaction cannot complete successfully,  Instruct the customer to use an alternative payment method"
+                // I've only encountered this once and there's no indication of the cause so the temporary solution is to try again
+                boolean retry = context.get("_RETRY_") == null || (Boolean) context.get("_RETRY_");
+                if (retry) {
+                    context.put("_RETRY_", false);
+                    return PayPalServices.doExpressCheckout(dctx, context);
+                }
+            }
+            return ServiceUtil.returnError(UtilMisc.toList(errorMessages.values()));
+        }
+
+        Map<String, Object> inMap = FastMap.newInstance();
+        inMap.put("userLogin", userLogin);
+        inMap.put("paymentMethodId", payPalPaymentMethod.get("paymentMethodId"));
+        inMap.put("transactionId", decoder.get("TRANSACTIONID"));
+        
+        Map<String, Object> outMap = null;
+        try {
+            outMap = dispatcher.runSync("updatePayPalPaymentMethod", inMap);
+        } catch (GenericServiceException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        if (ServiceUtil.isError(outMap)) {
+            Debug.logError(ServiceUtil.getErrorMessage(outMap), module);
+            return outMap;
+        }
+        return ServiceUtil.returnSuccess();
+    }
+
+    public static Map<String, Object> doAuthorization(DispatchContext dctx, Map<String, Object> context) {
+        GenericDelegator delegator = dctx.getDelegator();
+        String orderId = (String) context.get("orderId");
+        BigDecimal processAmount = (BigDecimal) context.get("processAmount");
+        GenericValue payPalPaymentMethod = (GenericValue) context.get("payPalPaymentMethod");
+        OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
+        GenericValue payPalConfig = getPaymentMethodGatewayPayPal(dctx, context, PaymentGatewayServices.AUTH_SERVICE_TYPE);
+        
+        NVPEncoder encoder = new NVPEncoder();
+        encoder.add("METHOD", "DoAuthorization");
+        encoder.add("TRANSACTIONID", payPalPaymentMethod.getString("transactionId"));
+        encoder.add("AMT", processAmount.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+        encoder.add("TRANSACTIONENTITY", "Order");
+        String currency = (String) context.get("currency");
+        if (currency == null) {
+            currency = orh.getCurrency();
+        }
+        encoder.add("CURRENCYCODE", currency);
+
+        NVPDecoder decoder = null;
+        try {
+            decoder = sendNVPRequest(payPalConfig, encoder);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        
+        if (decoder == null) {
+            return ServiceUtil.returnError("An unknown error occurred while contacting PayPal");
+        }
+        
+        Map<String, Object> result = ServiceUtil.returnSuccess();
+        Map<String, String> errors = getErrorMessageMap(decoder);
+        if (UtilValidate.isNotEmpty(errors)) {
+            result.put("authResult", false);
+            result.put("authRefNum", "N/A");
+            result.put("processAmount", BigDecimal.ZERO);
+            if (errors.size() == 1) {
+                result.put("responseMessage", errors.get(0));
+            } else {
+                result.put("responseMessage", "Multiple errors occurred, please refer to the gateway response messages");
+                result.put("internalRespMsgs", errors);
+            }
+        } else {
+            result.put("authResult", true);
+            result.put("processAmount", new BigDecimal(decoder.get("AMT")));
+            result.put("authRefNum", decoder.get("TRANSACTIONID"));
+        }
+        //TODO: Look into possible PAYMENTSTATUS and PENDINGREASON return codes, it is unclear what should be checked for this type of transaction
+        return result;
+    }
+
+    public static Map<String, Object> doCapture(DispatchContext dctx, Map<String, Object> context) {
+        GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
+        BigDecimal captureAmount = (BigDecimal) context.get("captureAmount");
+        GenericValue payPalConfig = getPaymentMethodGatewayPayPal(dctx, context, PaymentGatewayServices.AUTH_SERVICE_TYPE);
+        GenericValue authTrans = (GenericValue) context.get("authTrans");
+        if (authTrans == null) {
+            authTrans = PaymentGatewayServices.getAuthTransaction(paymentPref);
+        }
+        
+        NVPEncoder encoder = new NVPEncoder();
+        encoder.add("METHOD", "DoCapture");
+        encoder.add("AUTHORIZATIONID", authTrans.getString("referenceNum"));
+        encoder.add("AMT", captureAmount.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+        encoder.add("CURRENCYCODE", authTrans.getString("currencyUomId"));
+        encoder.add("COMPLETETYPE", "NotComplete");
+        
+        NVPDecoder decoder = null;
+        try {
+            decoder = sendNVPRequest(payPalConfig, encoder);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        
+        if (decoder == null) {
+            return ServiceUtil.returnError("An unknown error occurred while contacting PayPal");
+        }
+        
+        Map<String, Object> result = ServiceUtil.returnSuccess();
+        Map<String, String> errors = getErrorMessageMap(decoder);
+        if (UtilValidate.isNotEmpty(errors)) {
+            result.put("captureResult", false);
+            result.put("captureRefNum", "N/A");
+            result.put("captureAmount", BigDecimal.ZERO);
+            if (errors.size() == 1) {
+                result.put("responseMessage", errors.get(0));
+            } else {
+                result.put("responseMessage", "Multiple errors occurred, please refer to the gateway response messages");
+                result.put("internalRespMsgs", errors);
+            }
+        } else {
+            result.put("captureResult", true);
+            result.put("captureAmount", new BigDecimal(decoder.get("AMT")));
+            result.put("captureRefNum", decoder.get("AUTHORIZATIONID"));
+        }
+        //TODO: Look into possible PAYMENTSTATUS and PENDINGREASON return codes, it is unclear what should be checked for this type of transaction
+        return result;
+    }
+
+    public static Map<String, Object> doVoid(DispatchContext dctx, Map<String, Object> context) {
+        GenericValue payPalConfig = getPaymentMethodGatewayPayPal(dctx, context, null);
+        if (payPalConfig == null) {
+            return ServiceUtil.returnError("Couldn't retrieve a PaymentGatewayConfigPayPal record for Express Checkout, cannot continue.");
+        }
+        GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
+        GenericValue authTrans = PaymentGatewayServices.getAuthTransaction(orderPaymentPreference);
+        NVPEncoder encoder = new NVPEncoder();
+        encoder.add("METHOD", "DoVoid");
+        encoder.add("AUTHORIZATIONID", authTrans.getString("referenceNum"));
+        NVPDecoder decoder = null;
+        try {
+            decoder = sendNVPRequest(payPalConfig, encoder);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+    
+        if (decoder == null) {
+            return ServiceUtil.returnError("An unknown error occurred while contacting PayPal");
+        }
+
+        Map<String, Object> result = ServiceUtil.returnSuccess();
+        Map<String, String> errors = getErrorMessageMap(decoder);
+        if (UtilValidate.isNotEmpty(errors)) {
+            result.put("releaseResult", false);
+            result.put("releaseRefNum", authTrans.getString("referenceNum"));
+            result.put("releaseAmount", BigDecimal.ZERO);
+            if (errors.size() == 1) {
+                result.put("responseMessage", errors.get(0));
+            } else {
+                result.put("responseMessage", "Multiple errors occurred, please refer to the gateway response messages");
+                result.put("internalRespMsgs", errors);
+            }
+        } else {
+            result.put("releaseResult", true);
+            // PayPal voids the entire order amount minus any captures, that's a little difficult to figure out here
+            // so until further testing proves we should do otherwise I'm just going to return requested void amount
+            result.put("releaseAmount", context.get("releaseAmount"));
+            result.put("releaseRefNum", decoder.get("AUTHORIZATIONID"));
+        }        
+        return result;
+    }
+
+    public static Map<String, Object> doRefund (DispatchContext dctx, Map<String, Object> context) {
+        GenericValue payPalConfig = getPaymentMethodGatewayPayPal(dctx, context, null);
+        if (payPalConfig == null) {
+            return ServiceUtil.returnError("Couldn't retrieve a PaymentGatewayConfigPayPal record for Express Checkout, cannot continue.");
+        }
+        GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
+        GenericValue authTrans = PaymentGatewayServices.getAuthTransaction(orderPaymentPreference);
+        BigDecimal refundAmount = (BigDecimal) context.get("refundAmount");
+        NVPEncoder encoder = new NVPEncoder();
+        encoder.add("METHOD", "RefundTransaction");
+        encoder.add("TRANSACTIONID", authTrans.getString("referenceNum"));
+        encoder.add("REFUNDTYPE", "Partial");
+        encoder.add("AMT", refundAmount.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+        encoder.add("NOTE", "Order #" + orderPaymentPreference.getString("orderId"));
+        NVPDecoder decoder = null;
+        try {
+            decoder = sendNVPRequest(payPalConfig, encoder);
+        } catch (PayPalException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+    
+        if (decoder == null) {
+            return ServiceUtil.returnError("An unknown error occurred while contacting PayPal");
+        }
+
+        Map<String, Object> result = ServiceUtil.returnSuccess();
+        Map<String, String> errors = getErrorMessageMap(decoder);
+        if (UtilValidate.isNotEmpty(errors)) {
+            result.put("refundResult", false);
+            result.put("refundRefNum", authTrans.getString("referenceNum"));
+            result.put("refundAmount", BigDecimal.ZERO);
+            if (errors.size() == 1) {
+                result.put("responseMessage", errors.get(0));
+            } else {
+                result.put("responseMessage", "Multiple errors occurred, please refer to the gateway response messages");
+                result.put("internalRespMsgs", errors);
+            }
+        } else {
+            result.put("refundResult", true);
+            result.put("refundAmount", new BigDecimal(decoder.get("NETREFUNDAMT")));
+            result.put("refundRefNum", decoder.get("REFUNDTRANSACTIONID"));
+        }        
+        return result;
+    }
+
+    private static GenericValue getPaymentMethodGatewayPayPal(DispatchContext dctx, Map<String, ? extends Object> context, String paymentServiceTypeEnumId) {
+        GenericDelegator delegator = dctx.getDelegator();
+        String paymentGatewayConfigId = (String) context.get("paymentGatewayConfigId");
+        GenericValue payPalGatewayConfig = null;
+
+        if (paymentGatewayConfigId == null) {
+            String productStoreId = null;
+            GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
+            if (orderPaymentPreference != null) {
+                OrderReadHelper orh = new OrderReadHelper(delegator, orderPaymentPreference.getString("orderId"));
+                productStoreId = orh.getProductStoreId();
+            } else {
+                ShoppingCart cart = (ShoppingCart) context.get("cart");
+                if (cart != null) {
+                    productStoreId = cart.getProductStoreId();
+                }
+            }
+            if (productStoreId != null) {
+                GenericValue payPalPaymentSetting = ProductStoreWorker.getProductStorePaymentSetting(delegator, productStoreId, "EXT_PAYPAL", paymentServiceTypeEnumId, true);
+                if (payPalPaymentSetting != null) {
+                    paymentGatewayConfigId = payPalPaymentSetting.getString("paymentGatewayConfigId");
+                }
+            }
+        }
+        if (paymentGatewayConfigId != null) {
+            try {
+                    payPalGatewayConfig = delegator.findOne("PaymentGatewayPayPal", true, "paymentGatewayConfigId", paymentGatewayConfigId);
+            } catch (GenericEntityException e) {
+                Debug.logError(e, module);
+            }
+        }
+        return payPalGatewayConfig;
+    }
+
+    private static NVPDecoder sendNVPRequest(GenericValue payPalConfig, NVPEncoder encoder) throws PayPalException {
+        NVPCallerServices caller = new NVPCallerServices();
+        try {
+            APIProfile profile = ProfileFactory.createSignatureAPIProfile();
+            profile.setAPIUsername(payPalConfig.getString("apiUserName"));
+            profile.setAPIPassword(payPalConfig.getString("apiPassword"));
+            profile.setSignature(payPalConfig.getString("apiSignature"));
+            profile.setEnvironment(payPalConfig.getString("apiEnvironment"));
+            caller.setAPIProfile(profile);
+        } catch (PayPalException e) {
+            Debug.logError(e.getMessage(), module);
+        }
+
+        String requestMessage = encoder.encode();
+        String responseMessage = caller.call(requestMessage);
+
+        NVPDecoder decoder = new NVPDecoder();
+        decoder.decode(responseMessage);
+        if (!"Success".equals(decoder.get("ACK"))) {
+            Debug.logError("A response other than success was received from PayPal: " + responseMessage, module);
+        }
+
+        return decoder;
+    }
+    
+    private static String getCountryGeoIdFromGeoCode(String geoCode, GenericDelegator delegator) {
+        String geoId = null;
+        try {
+            EntityCondition cond =EntityCondition.makeCondition(UtilMisc.toMap("geoTypeId", "COUNTRY", "geoCode", geoCode));
+            GenericValue countryGeo = EntityUtil.getFirst(delegator.findList("Geo", cond, null, null, null, true));
+            if (countryGeo != null) {
+                geoId = countryGeo.getString("geoId");
+            }
+        } catch (GenericEntityException e) {
+            Debug.logError(e, module);
+        }
+        return geoId;
+    }
+
+    public static class TokenWrapper implements Serializable {
+        String theString;
+        public TokenWrapper(String theString) {
+            this.theString = theString;
+        }
+        
+        @Override
+        public boolean equals(Object o) {
+            return theString.equals(o);
+        }
+        @Override
+        public int hashCode() {
+            return theString.hashCode();
+        }
+    }
+}

Propchange: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java
------------------------------------------------------------------------------
    svn:keywords = "Date Rev Author URL Id"

Propchange: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/paypal/PayPalServices.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/CheckOutHelper.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/CheckOutHelper.java?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/CheckOutHelper.java (original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/CheckOutHelper.java Tue Aug 11 15:20:32 2009
@@ -799,9 +799,9 @@
 
         // Debug.logInfo("====== makeTaxContext passed in shipAddress=" + shipAddress, module);
 
-        Iterator it = csi.shipItemInfo.keySet().iterator();
+        Iterator<ShoppingCartItem> it = csi.shipItemInfo.keySet().iterator();
         for (int i = 0; i < totalItems; i++) {
-            ShoppingCartItem cartItem = (ShoppingCartItem) it.next();
+            ShoppingCartItem cartItem = it.next();
             ShoppingCart.CartShipInfo.CartShipItemInfo itemInfo = csi.getShipItemInfo(cartItem);
 
             //Debug.logInfo("In makeTaxContext for item [" + i + "] in ship group [" + shipGroup + "] got cartItem: " + cartItem, module);

Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java (original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java Tue Aug 11 15:20:32 2009
@@ -1917,7 +1917,7 @@
         return (shipInfo.size() - 1);
     }
 
-    public List getShipGroups() {
+    public List<CartShipInfo> getShipGroups() {
         return this.shipInfo;
     }
 
@@ -1926,10 +1926,10 @@
         if (item != null) {
             for (int i = 0; i < this.shipInfo.size(); i++) {
                 CartShipInfo csi = (CartShipInfo) shipInfo.get(i);
-                CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) csi.shipItemInfo.get(item);
+                CartShipInfo.CartShipItemInfo csii = csi.shipItemInfo.get(item);
                 if (csii != null) {
                     if (this.checkShipItemInfo(csi, csii)) {
-                        shipGroups.put(Integer.valueOf(i), csii.quantity);
+                        shipGroups.put(i, csii.quantity);
                     }
                 }
             }
@@ -1960,13 +1960,11 @@
     }
 
     /** Returns the ShoppingCartItem (key) and quantity (value) associated with the ship group */
-    public Map getShipGroupItems(int idx) {
+    public Map<ShoppingCartItem, BigDecimal> getShipGroupItems(int idx) {
         CartShipInfo csi = this.getShipInfo(idx);
-        Map qtyMap = new HashMap();
-        Iterator i = csi.shipItemInfo.keySet().iterator();
-        while (i.hasNext()) {
-            ShoppingCartItem item = (ShoppingCartItem) i.next();
-            CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) csi.shipItemInfo.get(item);
+        Map<ShoppingCartItem, BigDecimal> qtyMap = FastMap.newInstance();
+        for(ShoppingCartItem item : csi.shipItemInfo.keySet()) {
+            CartShipInfo.CartShipItemInfo csii = csi.shipItemInfo.get(item);
             qtyMap.put(item, csii.quantity);
         }
         return qtyMap;
@@ -2044,7 +2042,7 @@
     public BigDecimal getItemShipGroupQty(ShoppingCartItem item, int idx) {
         if (item != null) {
             CartShipInfo csi = this.getShipInfo(idx);
-            CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) csi.shipItemInfo.get(item);
+            CartShipInfo.CartShipItemInfo csii = csi.shipItemInfo.get(item);
             if (csii != null) {
                 return csii.quantity;
             }
@@ -2110,9 +2108,9 @@
 
     public void cleanUpShipGroups() {
         for (CartShipInfo csi : this.shipInfo) {
-            Iterator si = csi.shipItemInfo.keySet().iterator();
+            Iterator<ShoppingCartItem> si = csi.shipItemInfo.keySet().iterator();
             while (si.hasNext()) {
-                ShoppingCartItem item = (ShoppingCartItem) si.next();
+                ShoppingCartItem item = si.next();
                 if (item.getQuantity().compareTo(BigDecimal.ZERO) == 0) {
                     si.remove();
                 }
@@ -2771,9 +2769,7 @@
         CartShipInfo info = this.getShipInfo(idx);
         BigDecimal itemTotal = BigDecimal.ZERO;
 
-        Iterator i = info.shipItemInfo.keySet().iterator();
-        while (i.hasNext()) {
-            ShoppingCartItem item = (ShoppingCartItem) i.next();
+        for (ShoppingCartItem item : info.shipItemInfo.keySet()) {
             CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) info.shipItemInfo.get(item);
             if (csii != null && csii.quantity.compareTo(BigDecimal.ZERO) > 0) {
                 if (item.shippingApplies()) {
@@ -2790,10 +2786,8 @@
         CartShipInfo info = this.getShipInfo(idx);
         BigDecimal count = BigDecimal.ZERO;
 
-        Iterator i = info.shipItemInfo.keySet().iterator();
-        while (i.hasNext()) {
-            ShoppingCartItem item = (ShoppingCartItem) i.next();
-            CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) info.shipItemInfo.get(item);
+        for (ShoppingCartItem item : info.shipItemInfo.keySet()) {
+            CartShipInfo.CartShipItemInfo csii = info.shipItemInfo.get(item);
             if (csii != null && csii.quantity.compareTo(BigDecimal.ZERO) > 0) {
                 if (item.shippingApplies()) {
                     count = count.add(csii.quantity);
@@ -2809,10 +2803,8 @@
         CartShipInfo info = this.getShipInfo(idx);
         BigDecimal weight = BigDecimal.ZERO;
 
-        Iterator i = info.shipItemInfo.keySet().iterator();
-        while (i.hasNext()) {
-            ShoppingCartItem item = (ShoppingCartItem) i.next();
-            CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) info.shipItemInfo.get(item);
+        for (ShoppingCartItem item : info.shipItemInfo.keySet()) {
+            CartShipInfo.CartShipItemInfo csii = info.shipItemInfo.get(item);
             if (csii != null && csii.quantity.compareTo(BigDecimal.ZERO) > 0) {
                 if (item.shippingApplies()) {
                     weight = weight.add(item.getWeight().multiply(csii.quantity));
@@ -2828,10 +2820,8 @@
         CartShipInfo info = this.getShipInfo(idx);
         List shippableSizes = new LinkedList();
 
-        Iterator i = info.shipItemInfo.keySet().iterator();
-        while (i.hasNext()) {
-            ShoppingCartItem item = (ShoppingCartItem) i.next();
-            CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) info.shipItemInfo.get(item);
+        for (ShoppingCartItem item : info.shipItemInfo.keySet()) {
+            CartShipInfo.CartShipItemInfo csii = info.shipItemInfo.get(item);
             if (csii != null && csii.quantity.compareTo(BigDecimal.ZERO) > 0) {
                 if (item.shippingApplies()) {
                     shippableSizes.add(item.getSize());
@@ -2847,10 +2837,8 @@
         CartShipInfo info = this.getShipInfo(idx);
         List itemInfos = new LinkedList();
 
-        Iterator i = info.shipItemInfo.keySet().iterator();
-        while (i.hasNext()) {
-            ShoppingCartItem item = (ShoppingCartItem) i.next();
-            CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) info.shipItemInfo.get(item);
+        for (ShoppingCartItem item : info.shipItemInfo.keySet()) {
+            CartShipInfo.CartShipItemInfo csii = info.shipItemInfo.get(item);
             if (csii != null && csii.quantity.compareTo(BigDecimal.ZERO) > 0) {
                 if (item.shippingApplies()) {
                     Map itemInfo = item.getItemProductInfo();
@@ -2896,9 +2884,7 @@
         CartShipInfo info = this.getShipInfo(idx);
         Map featureMap = new HashMap();
 
-        Iterator i = info.shipItemInfo.keySet().iterator();
-        while (i.hasNext()) {
-            ShoppingCartItem item = (ShoppingCartItem) i.next();
+        for (ShoppingCartItem item : info.shipItemInfo.keySet()) {
             CartShipInfo.CartShipItemInfo csii = (CartShipInfo.CartShipItemInfo) info.shipItemInfo.get(item);
             if (csii != null && csii.quantity.compareTo(BigDecimal.ZERO) > 0) {
                 featureMap.putAll(item.getFeatureIdQtyMap(csii.quantity));
@@ -4279,7 +4265,7 @@
     }
 
     public static class CartShipInfo implements Serializable {
-        public Map<Object, Object> shipItemInfo = FastMap.newInstance();
+        public Map<ShoppingCartItem, CartShipItemInfo> shipItemInfo = FastMap.newInstance();
         public List<GenericValue> shipTaxAdj = FastList.newInstance();
         public String orderTypeId = null;
         private String internalContactMechId = null;
@@ -4361,8 +4347,7 @@
 
             //set estimated ship dates
             FastList estimatedShipDates = FastList.newInstance();
-            for (Map.Entry<Object, Object> entry : shipItemInfo.entrySet()) {
-                ShoppingCartItem item = (ShoppingCartItem) entry.getKey();
+            for (ShoppingCartItem item : shipItemInfo.keySet()) {
                 Timestamp estimatedShipDate = item.getEstimatedShipDate();
                 if (estimatedShipDate != null) {
                     estimatedShipDates.add(estimatedShipDate);
@@ -4376,8 +4361,7 @@
 
             //set estimated delivery dates
             FastList estimatedDeliveryDates = FastList.newInstance();
-            for (Map.Entry <Object, Object> entry : shipItemInfo.entrySet()) {
-                ShoppingCartItem item = (ShoppingCartItem) entry.getKey();
+            for (ShoppingCartItem item : shipItemInfo.keySet()) {
                 Timestamp estimatedDeliveryDate = item.getDesiredDeliveryDate();
                 if (estimatedDeliveryDate != null) {
                     estimatedDeliveryDates.add(estimatedDeliveryDate);
@@ -4432,7 +4416,7 @@
         }
 
         public CartShipItemInfo setItemInfo(ShoppingCartItem item, BigDecimal quantity, List taxAdj) {
-            CartShipItemInfo itemInfo = (CartShipItemInfo) shipItemInfo.get(item);
+            CartShipItemInfo itemInfo = shipItemInfo.get(item);
             if (itemInfo == null) {
                 if (!isShippableToAddress(item)) {
                     throw new IllegalArgumentException("The shipping address is not compatible with ProductGeos rules.");
@@ -4460,10 +4444,10 @@
         }
 
         public CartShipItemInfo getShipItemInfo(ShoppingCartItem item) {
-            return (CartShipItemInfo) shipItemInfo.get(item);
+            return shipItemInfo.get(item);
         }
 
-        public Set getShipItems() {
+        public Set<ShoppingCartItem> getShipItems() {
             return shipItemInfo.keySet();
         }
 
@@ -4529,9 +4513,7 @@
 
         public BigDecimal getTotal() {
             BigDecimal shipItemTotal = ZERO;
-            Iterator iter = shipItemInfo.values().iterator();
-            while (iter.hasNext()) {
-                CartShipItemInfo info = (CartShipItemInfo) iter.next();
+            for (CartShipItemInfo info : shipItemInfo.values()) {
                 shipItemTotal = shipItemTotal.add(info.getItemSubTotal());
             }
 

Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEstimateWrapper.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEstimateWrapper.java?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEstimateWrapper.java (original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEstimateWrapper.java Tue Aug 11 15:20:32 2009
@@ -24,6 +24,8 @@
 import java.util.List;
 import java.util.Map;
 
+import javolution.util.FastMap;
+
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.entity.GenericDelegator;
 import org.ofbiz.entity.GenericValue;
@@ -39,8 +41,8 @@
     protected GenericDelegator delegator = null;
     protected LocalDispatcher dispatcher = null;
 
-    protected Map shippingEstimates = null;
-    protected List shippingMethods = null;
+    protected Map<GenericValue, BigDecimal> shippingEstimates = null;
+    protected List<GenericValue> shippingMethods = null;
 
     protected GenericValue shippingAddress = null;
     protected Map shippableItemFeatures = null;
@@ -86,11 +88,9 @@
     }
 
     protected void loadEstimates() {
-        this.shippingEstimates = new HashMap();
+        this.shippingEstimates = FastMap.newInstance();
         if (shippingMethods != null) {
-            Iterator i = shippingMethods.iterator();
-            while (i.hasNext()) {
-                GenericValue shipMethod = (GenericValue) i.next();
+            for (GenericValue shipMethod : shippingMethods) {
                 String shippingMethodTypeId = shipMethod.getString("shipmentMethodTypeId");
                 String carrierRoleTypeId = shipMethod.getString("roleTypeId");
                 String carrierPartyId = shipMethod.getString("partyId");
@@ -109,11 +109,11 @@
         }
     }
 
-    public List getShippingMethods() {
+    public List<GenericValue> getShippingMethods() {
         return shippingMethods;
     }
 
-    public Map getAllEstimates() {
+    public Map<GenericValue, BigDecimal> getAllEstimates() {
         return shippingEstimates;
     }
 

Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEvents.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEvents.java?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEvents.java (original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/shipping/ShippingEvents.java Tue Aug 11 15:20:32 2009
@@ -90,10 +90,10 @@
         return "success";
     }
 
-    public static Map getShipGroupEstimate(LocalDispatcher dispatcher, GenericDelegator delegator, ShoppingCart cart, int groupNo) {
+    public static Map<String, Object> getShipGroupEstimate(LocalDispatcher dispatcher, GenericDelegator delegator, ShoppingCart cart, int groupNo) {
         // check for shippable items
         if (!cart.shippingApplies()) {
-            Map responseResult = ServiceUtil.returnSuccess();
+            Map<String, Object> responseResult = ServiceUtil.returnSuccess();
             responseResult.put("shippingTotal", BigDecimal.ZERO);
             return responseResult;
         }

Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/thirdparty/paypal/ExpressCheckoutEvents.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/thirdparty/paypal/ExpressCheckoutEvents.java?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/thirdparty/paypal/ExpressCheckoutEvents.java (original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/thirdparty/paypal/ExpressCheckoutEvents.java Tue Aug 11 15:20:32 2009
@@ -47,7 +47,7 @@
 
     public static final String resourceErr = "AccountingErrorUiLabels";
     public static final String module = ExpressCheckoutEvents.class.getName();
-    public static enum CheckoutType {PAYFLOW, NONE};
+    public static enum CheckoutType {PAYFLOW, STANDARD, NONE};
     
     public static String setExpressCheckout(HttpServletRequest request, HttpServletResponse response) {
         Locale locale = UtilHttp.getLocale(request);
@@ -55,22 +55,28 @@
         
         ShoppingCart cart = ShoppingCartEvents.getCartObject(request);
         CheckoutType checkoutType = determineCheckoutType(request);
-        if (checkoutType.equals(CheckoutType.PAYFLOW)) {
+        if (!checkoutType.equals(CheckoutType.NONE)) {
+            String serviceName = null;
+            if (checkoutType.equals(CheckoutType.PAYFLOW)) {
+                serviceName = "payflowSetExpressCheckout";
+            } else if (checkoutType.equals(CheckoutType.STANDARD)) {
+                serviceName = "payPalSetExpressCheckout";
+            }
             Map<String, ? extends Object> inMap = UtilMisc.toMap("userLogin", cart.getUserLogin(), "cart", cart);
             Map<String, Object> result = null;
             try {
-                result = dispatcher.runSync("payflowSetExpressCheckout", inMap);
+                result = dispatcher.runSync(serviceName, inMap);
             } catch (GenericServiceException e) {
+                Debug.log(e, module);
                 request.setAttribute("_EVENT_MESSAGE_", UtilProperties.getMessage(resourceErr, "AccountingPayPalCommunicationError", locale));
                 return "error";
             }
             if (ServiceUtil.isError(result)) {
                 Debug.logError(ServiceUtil.getErrorMessage(result), module);
-                request.setAttribute("_EVENT_MESSAGE_", ServiceUtil.getErrorMessage(result));
+                request.setAttribute("_EVENT_MESSAGE_", UtilProperties.getMessage(resourceErr, "AccountingPayPalCommunicationError", locale));
                 return "error";
             }
         }
-            
         return "success";
     }
 
@@ -99,11 +105,17 @@
         
         ShoppingCart cart = ShoppingCartEvents.getCartObject(request);
         CheckoutType checkoutType = determineCheckoutType(request);
-        if (checkoutType.equals(CheckoutType.PAYFLOW)) {
+        if (!checkoutType.equals(CheckoutType.NONE)) {
+            String serviceName = null;
+            if (checkoutType.equals(CheckoutType.PAYFLOW)) {
+                serviceName = "payflowGetExpressCheckout";
+            } else if (checkoutType.equals(CheckoutType.STANDARD)) {
+                serviceName = "payPalGetExpressCheckout";
+            }
             Map<String, ? extends Object> inMap = UtilMisc.toMap("userLogin", cart.getUserLogin(), "cart", cart);
             Map<String, Object> result = null;
             try {
-                result = dispatcher.runSync("payflowGetExpressCheckout", inMap);
+                result = dispatcher.runSync(serviceName, inMap);
             } catch (GenericServiceException e) {
                 request.setAttribute("_EVENT_MESSAGE_", UtilProperties.getMessage(resourceErr, "AccountingPayPalCommunicationError", locale));
                 return "error";
@@ -120,13 +132,19 @@
     
     public static Map<String, Object> doExpressCheckout(String productStoreId, String orderId, GenericValue paymentPref, GenericValue userLogin, GenericDelegator delegator, LocalDispatcher dispatcher) {
         CheckoutType checkoutType = determineCheckoutType(delegator, productStoreId);
-        if (checkoutType.equals(CheckoutType.PAYFLOW)) {
+        if (!checkoutType.equals(CheckoutType.NONE)) {
+            String serviceName = null;
+            if (checkoutType.equals(CheckoutType.PAYFLOW)) {
+                serviceName = "payflowDoExpressCheckout";
+            } else if (checkoutType.equals(CheckoutType.STANDARD)) {
+                serviceName = "payPalDoExpressCheckout";
+            }
             Map<String, Object> inMap = FastMap.newInstance();
             inMap.put("userLogin", userLogin);
             inMap.put("orderPaymentPreference", paymentPref);
             Map<String, Object> result = null;
             try {
-                result = dispatcher.runSync("payflowDoExpressCheckout", inMap);
+                result = dispatcher.runSync(serviceName, inMap);
             } catch (GenericServiceException e) {
                 return ServiceUtil.returnError(e.getMessage());
             }
@@ -141,7 +159,7 @@
         cart.removeAttribute("payPalCheckoutToken");
         return "success";
     }
-    
+
     public static CheckoutType determineCheckoutType(HttpServletRequest request) {
         GenericDelegator delegator = (GenericDelegator) request.getAttribute("delegator");
         ShoppingCart cart = ShoppingCartEvents.getCartObject(request);
@@ -153,8 +171,17 @@
         if (payPalPaymentSetting != null && payPalPaymentSetting.getString("paymentGatewayConfigId") != null) {
             try {
                 GenericValue paymentGatewayConfig = payPalPaymentSetting.getRelatedOne("PaymentGatewayConfig");
-                if (paymentGatewayConfig != null && "PAYFLOWPRO".equals(paymentGatewayConfig.getString("paymentGatewayConfigTypeId"))) {
-                    return CheckoutType.PAYFLOW;
+                String paymentGatewayConfigTypeId = paymentGatewayConfig.getString("paymentGatewayConfigTypeId");
+                if (paymentGatewayConfig != null) {
+                    if ("PAYFLOWPRO".equals(paymentGatewayConfigTypeId)) {
+                        return CheckoutType.PAYFLOW;
+                    } else if ("PAYPAL".equals(paymentGatewayConfigTypeId)) {
+                        GenericValue payPalConfig = paymentGatewayConfig.getRelatedOne("PaymentGatewayPayPal");
+                        // TODO: Probably better off with an indicator field to indicate Express Checkout use
+                        if (UtilValidate.isNotEmpty(payPalConfig.get("apiUserName"))) {
+                            return CheckoutType.STANDARD;
+                        }
+                    }
                 }
             } catch (GenericEntityException e) {
                 Debug.logError(e, module);

Modified: ofbiz/trunk/applications/party/servicedef/services.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/party/servicedef/services.xml?rev=803151&r1=803150&r2=803151&view=diff
==============================================================================
--- ofbiz/trunk/applications/party/servicedef/services.xml (original)
+++ ofbiz/trunk/applications/party/servicedef/services.xml Tue Aug 11 15:20:32 2009
@@ -72,7 +72,8 @@
     <service name="createPerson" engine="java" default-entity-name="Person"
             location="org.ofbiz.party.party.PartyServices" invoke="createPerson" auth="false">
         <description>Create a Person</description>
-        <auto-attributes mode="INOUT" include="pk" optional="true"/>
+        <auto-attributes mode="IN" include="pk" optional="true"/>
+        <auto-attributes mode="OUT" include="pk" optional="false"/>
         <auto-attributes mode="IN" include="nonpk" optional="true"/>
         <attribute name="preferredCurrencyUomId" type="String" mode="IN" optional="true"/>
         <attribute name="description" type="String" mode="IN" optional="true"/>