Paypal Express Hook Paypal_Express_Get_Order_Data

Update:
Grr… I found the issue. Seems like PAYMENTREQUEST_0_ITEMAMT must be incremented by the amount being added.
This isn’t part of the gift certificate hook so I’ve no idea why the GC hook doesn’t have to make this adjustment.
If I modified my code below to add:

 if( !empty($order_data[‘PAYMENTREQUEST_0_ITEMAMT’]) )
$order_data[‘PAYMENTREQUEST_0_ITEMAMT’] += $order_data[‘L_PAYMENTREQUEST_0_AMT’ . $product_index];
Then it seems to go to paypal without error…
/Update

I’m using this hook to add an additional fee in cs-cart V4.2.1.
The resulting order_data at the end of the hook is:
order_data:Array
(
[L_PAYMENTREQUEST_0_NAME0] => Blessed - Floral Design V-Neck
[L_PAYMENTREQUEST_0_NUMBER0] => SG497
[L_PAYMENTREQUEST_0_DESC0] => Size: Small
[L_PAYMENTREQUEST_0_QTY0] => 1
[L_PAYMENTREQUEST_0_AMT0] => 21.00
[PAYMENTREQUEST_0_ITEMAMT] => 21
[PAYMENTREQUEST_0_TAXAMT] => 0
[PAYMENTREQUEST_0_SHIPPINGAMT] => 5.00
[PAYMENTREQUEST_0_AMT] => 36.00
[L_PAYMENTREQUEST_0_NAME1] => Charity donation
[L_PAYMENTREQUEST_0_NUMBER1] => Charity
[L_PAYMENTREQUEST_0_DESC1] => Charity donation, Test Charity
[L_PAYMENTREQUEST_0_QTY1] => 1
[L_PAYMENTREQUEST_0_AMT1] => 10.00
)
I am using exactly the same code to generate the NAME1, NUMBER1, etc. as is used by the gift_certificate addon.

However I still get an error back from paypal stating:
Error 10413 Transaction refused because of an invalid argument. See additional error messages for details.: The totals of the cart item amounts do not match order amounts.
It all sure looks correct to me. Can anyone see what might be wrong?

The GC hook code is:
function fn_gift_certificates_paypal_express_get_order_data(&$data, &$order_data, &$product_index)
{
if (!empty($data[‘gift_certificates’])) {
foreach ($data[‘gift_certificates’] as $cart_id => $gift_certificate) {
$order_data[‘L_PAYMENTREQUEST_0_NAME’ . $product_index] = __(‘gift_certificate’);
$order_data[‘L_PAYMENTREQUEST_0_NUMBER’ . $product_index] = $cart_id;
$order_data[‘L_PAYMENTREQUEST_0_DESC’ . $product_index] = fn_paypal_express_get_certificate_data($gift_certificate);
$order_data[‘L_PAYMENTREQUEST_0_QTY’ . $product_index] = 1;
$order_data[‘L_PAYMENTREQUEST_0_AMT’ . $product_index] = $gift_certificate[‘amount’];

        $product_index++;
    }
}

}

And mine is:
function fn_coch_paypal_express_get_order_data(&$data, &$order_data, &$product_index) {
if( !isset($data[‘coch_amount’]) ) {
// Do this the long way just to be sure
$coch_amount = coch_get_order_data($data[‘order_id’]);
if( $coch_amount )
$data[‘coch_amount’] = $coch_amount;
}
if( !empty($data[‘coch_amount’])) {
// Build the paypal express item for charity amount.
$coch_config = coch_config();
$order_data[‘L_PAYMENTREQUEST_0_NAME’ . $product_index] = __(“coch_charity_donation”);
$order_data[‘L_PAYMENTREQUEST_0_NUMBER’ . $product_index] = “Charity”;
// Note: fn_paypal_substr() not used in 4.2.1
$order_data[‘L_PAYMENTREQUEST_0_DESC’ . $product_index] = (“coch_charity_donation”) . ", ". $coch_config[‘name’]; //fn_paypal_substr((“coch_charity_donation”) . ", ". $coch_config[‘name’]);
$order_data[‘L_PAYMENTREQUEST_0_QTY’ . $product_index] = 1;
$order_data[‘L_PAYMENTREQUEST_0_AMT’ . $product_index] = fn_format_price($data[‘coch_amount’]);

	$product_index++;
}

}

The error is caused by the issue that PayPal expect total amount to be 46, but you send 36. Just increase the value of the PAYMENTREQUEST_0_AMT parameter by additional fee

I updated and that’s what I ended up doing (before I saw your response). But I don’t understand why the GC addon doesn’t need to adjust that parameter.

I updated and that's what I ended up doing (before I saw your response). But I don't understand why the GC addon doesn't need to adjust that parameter.

Because order total amount already takes into account GC value. It is default behavior

But order total is already set to the proper amount (done during a cart recalculate when charity amount is selected), it is the item total that is not adjusted and seems to be set before the hook is called.
I’m going to assume they setup an item in products array but then not sure why a hook would be needed.

Paypal sets PAYMENTREQUEST_0_ITEMAMT to the order subtotal less discounts. fn_gift_certificates_calculate_cart adds the gift certificate amounts to the subtotal so they show in the subtotal in the cart and order. So GC addon doesn't need need to add to PAYMENTREQUEST_0_ITEMAMT in the Paypal hook.

That would do it. I probably did not set the subtotal in my cart_recalculate hook… I believe I only adjusted the cart total.

Yup, it all depends on where you want to display the fee - up with the products or below the subtotal like taxes. If above the subtotal calculate cart needs to add to the subtotal. If below the subtotal you need to add to PAYMENTREQUEST_0_ITEMAMT in the Paypal Express hook.

You should also use the amazon_products hook for amazon checkout to add your fee as a product - no subtotal adding needed.

As for other checkout methods that pass line item detail (Paypal standard, 2checkout, etc.) it gets ugly without making core mods as adding GC's to the line items is hard coded in them and isn't done through a (nonexistent) hook by the GC addon. I pulled it off without core mods but its nasty hooking get_order_info using debug_backtrace to check if its called from fn_start_payment then adding the fee to the products array. Also hooked summary_get_payment_method and summary_get_payment_method_data to set a global constant to minimize the need for the get_order_info hook to call debug_backtrace and its performance hit and to only add to the product array if its not paypal express / BML or amazon. I don't know why they seem headed down the road of a hook for each payment method. Why can't they just give us a hook just before calling the module (and one after) to modify the data passed to the payment module if needed?

Why can't they just give us a hook just before calling the module (and one after) to modify the data passed to the payment module if needed?

Because every payment provider is different. You would think it would make sense to normalize/standardize the payment data to the highest detail required....
I don't think paypal standard cares about items... Only totals. It's only express that seems to want items listed.
Yes, this is a 'fee' (after subtotal) so it's not in the product list. Client doesn't use Amazon so won't bother with that one. It's not a production addon, purely custom.

Paypal standard does pass line item detail. From fn_pp_standart_prepare_products, called by paypal.php:

        if (!empty($order_info['products'])) {
            foreach ($order_info['products'] as $k => $v) {
                $suffix = '_'.($i++);
                $v['product'] = htmlspecialchars(strip_tags($v['product']));
                $v['price'] = fn_format_price(($v['subtotal'] - fn_external_discounts($v)) / $v['amount'], $paypal_currency);
                $post_data["item_name$suffix"] = $v['product'];
                $post_data["amount$suffix"] = $v['price'];
                $post_data["quantity$suffix"] = $v['amount'];
                if (!empty($v['product_options'])) {
                    foreach ($v['product_options'] as $_k => $_v) {
                        $_v['option_name'] = htmlspecialchars(strip_tags($_v['option_name']));
                        $_v['variant_name'] = htmlspecialchars(strip_tags($_v['variant_name']));
                        $post_data["on$_k$suffix"] = $_v['option_name'];
                        $post_data["os$_k$suffix"] = $_v['variant_name'];
                    }
                }
            }
        }
    if (!empty($order_info['taxes']) && Registry::get('settings.General.tax_calculation') == 'subtotal') {
        foreach ($order_info['taxes'] as $tax_id => $tax) {
            if ($tax['price_includes_tax'] == 'Y') {
                continue;
            }
            $suffix = '_' . ($i++);
            $item_name = htmlspecialchars(strip_tags($tax['description']));
            $item_price = fn_format_price($tax['tax_subtotal'], $paypal_currency);
            $post_data["item_name$suffix"] = $item_name;
            $post_data["amount$suffix"] = $item_price;
            $post_data["quantity$suffix"] = '1';
        }
    }

    // Gift Certificates
    if (!empty($order_info['gift_certificates'])) {
        foreach ($order_info['gift_certificates'] as $k => $v) {
            $suffix = '_' . ($i++);
            $v['gift_cert_code'] = htmlspecialchars($v['gift_cert_code']);
            $v['amount'] = (!empty($v['extra']['exclude_from_calculate'])) ? 0 : fn_format_price($v['amount'], $paypal_currency);
            $post_data["item_name$suffix"] = $v['gift_cert_code'];
            $post_data["amount$suffix"] = $v['amount'];
            $post_data["quantity$suffix"] = '1';
        }
    }

Thanks for hardcoding addon code in a core file.

But I don't think it does any validation on the Paypal site like express does. I.e. it's more for invoice information than validation.

No, its not for invoice information. Paypal standard doesn't do any validation but it ignores the order total and adds up the items passed to it and that's what it will charge the customer. If you don't pass your fee to Paypal standard as a line item the customer won't pay for it. There are a few cases (using GC or points as partial payment, discounts applied or more than 100 items) when the cs-cart paypal addon won't pass the line item detail to Paypal standard and then it will use the order total but you can't count on it.