News:

AbanteCart v1.4.4 is released.

Main Menu
support

Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - RCodiaDavid

#1
Hi Basara, thanks for following up.

To answer your questions:
Customer Country: UK
Order Currency: GBP
Paypal Method: PayPal Commerce (standard button on the fast checkout page)
Checkout buttons enabled: No
Product type: Physical goods only

Regarding the specific incident, I have been waiting for payment confirmation from the customer as I don't have access to their PayPal directly. It has come to light that PayPal automatically detected and refunded the duplicate charges, so the customer was not out of pocket.

But to clarify the symptoms further, in some cases the order was created in Abantecart, and payment was taken, but the customer was not redirected to a confirmation page and their cart remained full. This suggests a browser side callback was not completing reliably, leaving the customer with no confirmation even though the order had gone through on the backend.

That said, looking into the issue made it clear that there are some edge cases in the checkout flow worth addressing. The fixes I have applied locally add a reliable fallback for situations where the browser side callback does not complete, for example: if a customer loses their connection or closes the tab after PayPal has taken payment. This ensures the order is still confirmed in Abantecart even if the browser callback fails.

I hope this helps is some way.
#2
AbanteCart version: 1.4.4
Extension: Paypal Commerce
Payment action: Capture (Sale)
File affected: extensions/paypal_commerce/storefront/controller/responses/extension/paypal_commerce.php

Background
After updating to 1.4.4, PayPal payments were being taken but orders were not appearing in the admin, and customers were being left on the checkout confirmation page with their cart still full. I want to stress that this may be specific to my own setup or update path. I'm not certain these are universal bugs, but I couldn't find any existing posts about it and wanted to share what I found in case it helps anyone else in the same situation.

Symptoms
Customer pays via PayPal successfully & money is taken
Order does not appear in AbanteCart admin (or appears under "Incomplete" status, which is filtered out of the default admin order view)
After payment, customer is left on the checkout confirmation page with their cart still full, no redirect to the order confirmation page
PayPal error log shows: Paypal webhook PAYMENT.CAPTURE.COMPLETED: order ID XXXXX / Paypal related OrderId: XXXXXXXXXXXXXXXXX but not found in the database

What I think was happening
After digging through the code I found three separate issues that combined to break the checkout flow. These may have been introduced during my update to 1.4.4 or may be specific to my configuration, I can't say for certain. I'm sharing them here in case anyone else hits the same problems.

Issue 1: array_merge null crash in captureOrder()

$this->session->data['fc'] appeared to be null in my checkout path, causing a fatal error that crashed the entire captureOrder() method:

array_merge(): Argument #2 must be of type array, null given

Issue 2: Possible race condition: orders stuck as "incomplete"

Even without Issue 1, captureOrder() doesn't confirm the AbanteCart order itself, it relies on the browser making a subsequent call to send() → processGenericOrder() to do that. In my case, PayPal's PAYMENT.CAPTURE.COMPLETED webhook was arriving before the browser's send() call ran. The webhook handler calls update() on the order, which appears to do nothing on an unconfirmed/incomplete order. The result was orders remaining in "Incomplete" status, hidden from the default admin filter. This timing may vary between setups.

Issue 3: Duplicate INSERT crashing processGenericOrder(), cart never clearing

Once I fixed Issue 2 by saving the PayPal order record in captureOrder(), the subsequent send() call reached processGenericOrder(), which unconditionally calls savePaypalOrder() again for the same order. Since savePaypalOrder() uses a plain INSERT with no duplicate handling, this threw a database error that silently killed the method, so the checkout/finalize redirect URL was never returned to the browser and the cart was never cleared.

What I did to fix it

Fix 1: array_merge null crash

In captureOrder(), find:

$this->session->data['fc'] = array_merge($order->data, $this->session->data['fc']);
Replace with:

$this->session->data['fc'] = array_merge((array)$order->data, (array)$this->session->data['fc']);
Fix 2: Confirm order and save PayPal record immediately in captureOrder()

In captureOrder(), find the closing of the if ($orderInfo) block followed by the catch:

          $order->buildOrderData($this->session->data['fc']);
                $order->saveOrder();
            }

        } catch (Exception|Error $e) {

Replace with:

          $order->buildOrderData($this->session->data['fc']);
                $order->saveOrder();
            }

            $confirmOrderId = (int)($orderId ?: $this->session->data['order_id']);
            if ($confirmOrderId && $result->getId()) {
                /** @var ModelCheckoutOrder $oMdl */
                $oMdl = $this->loadModel('checkout/order');
                $confirmedOrderInfo = $oMdl->getOrder($confirmOrderId);
                $incompleteStatusId = (int)$this->order_status->getStatusByTextId('incomplete');
                $currentStatusId    = (int)($confirmedOrderInfo['order_status_id'] ?? 0);
                if ($confirmedOrderInfo && (!$currentStatusId || $currentStatusId == $incompleteStatusId)) {
                    $settledStatusId = $this->config->get('paypal_commerce_transaction_type') == 'capture'
                        ? $this->config->get('paypal_commerce_status_success_settled')
                        : $this->config->get('paypal_commerce_status_success_unsettled');
                    $oMdl->confirm(
                        $confirmOrderId,
                        $settledStatusId ?: $this->order_status->getStatusByTextId('pending')
                    );
                }
                if (!$mdl->getPaypalOrder($confirmOrderId)) {
                    $mdl->savePaypalOrder($confirmOrderId, [
                        'id'             => $ppOrderId,
                        'transaction_id' => $result->getId(),
                    ]);
                }
            }

        } catch (Exception|Error $e) {

Fix 3: Guard duplicate savePaypalOrder in processGenericOrder()

In processGenericOrder(), find:

      $mdl->savePaypalCustomer($this->customer->getId(), $transactionDetails['payer']['payer_id']);
            $mdl->savePaypalOrder(
                $orderId,
                [
                    'id'             => $transactionDetails['id'],
                    'transaction_id' => $transactionDetails['id'],
                ]
            );

Replace with:

      $mdl->savePaypalCustomer($this->customer->getId(), $transactionDetails['payer']['payer_id']);
            if (!$mdl->getPaypalOrder($orderId)) {
                $mdl->savePaypalOrder(
                    $orderId,
                    [
                        'id'             => $transactionDetails['id'],
                        'transaction_id' => $transactionDetails['id'],
                    ]
                );
            }

Fix 4: Improve webhook fallback in processWebHook()

In processWebHook(), find:

  /** @var ModelCheckoutOrder $oMdl */
        $oMdl = $this->loadModel('checkout/order');
        $oMdl->update(
            $orderId,
            $this->data['order_status_id'],
            'Order updated by Paypal webhook request.'
        );
Replace with:

  /** @var ModelCheckoutOrder $oMdl */
        $oMdl = $this->loadModel('checkout/order');
        /** @var ModelExtensionPaypalCommerce $mdl */
        $mdl = $this->loadModel('extension/paypal_commerce');
        $webhookOrderInfo   = $oMdl->getOrder($orderId);
        $incompleteStatusId = (int)$this->order_status->getStatusByTextId('incomplete');
        $currentStatusId    = (int)($webhookOrderInfo['order_status_id'] ?? 0);
        if ($webhookOrderInfo && (!$currentStatusId || $currentStatusId == $incompleteStatusId)) {
            $oMdl->confirm($orderId, $this->data['order_status_id']);
            if (!$mdl->getPaypalOrder($orderId)) {
                $ppOrderId = $inData['parsed']['resource']['supplementary_data']['related_ids']['order_id'] ?? '';
                $mdl->savePaypalOrder($orderId, [
                    'id'             => $ppOrderId,
                    'transaction_id' => $inData['parsed']['resource']['id'] ?? '',
                ]);
            }
        } else {
            $oMdl->update(
                $orderId,
                $this->data['order_status_id'],
                'Order updated by Paypal webhook request.'
            );
        }

Bonus: Order numbers missing from PayPal transaction exports

I also noticed that the "Custom Number" column in PayPal's transaction export spreadsheet was blank for all orders placed after updating to 1.4.4 (or maybe earlier). In older versions this column showed the AbanteCart order number, which is useful for reconciliation. The custom_id field appears to have been removed from the PayPal purchase unit payload in 1.4.4, possibly intentionally since the webhook lookup mechanism changed to use reference_id instead. Adding it back as a plain order number restores the column in exports without affecting anything else.

In prepareOrderData(), find:

   $this->data['pp']['purchase_units'][0] = [
            'reference_id' => $ppOrderData['data']['reference_id'] ? : $this->session->data['reference_id'],
            'amount'       => [
Replace with:

   $this->data['pp']['purchase_units'][0] = [
            'reference_id' => $ppOrderData['data']['reference_id'] ? : $this->session->data['reference_id'],
            'custom_id'    => (string) $orderId,
            'amount'       => [

Note for existing incomplete orders

If you have orders already stuck in "Incomplete" status where payment was successfully taken, go to Sales → Orders, filter by "Incomplete" status, open each affected order, change the status to Processing (or your configured success status), and tick "Notify Customer" to send the confirmation email. I think this works, not fully tested yet.

I'm not a core developer so there may be good reasons some of this works differently by design, happy to be corrected. (Please don't flame me if I've gone overboard)

Posting in case it's useful to anyone else who updated to 1.4.4 and is seeing the same symptoms.
#3
It's strange as other emails come through okay.

I've asked the shop owner to contact PayPal and see if they can see anything strange at their end as their emails for this particular customer don't come through either.

I will update if I find out anything.
#4
Yes it appears in the list with all the other orders but just no emails triggered at all.
#5
Yes, the customer places the order but no notification is sent out to say the order has come in. The payment arrives at the PayPal end but no email from them either. It's only noticed when there is a mismatch with the payment amounts that show an order went missing.
#6
Ah ok thanks Basara! That puts my mind at rest.

Any idea why one customers orders don't send any notifications? Usually should get one from the shop and from PayPal but neither get sent but PayPal money arrives.

It only seems to happen with one customer at the moment, and it seems they always check out using a mobile phone and as guest. We have asked them to log in for future purchases to see if that fixes it, but it's happened like 3 times to the same customer.

Thanks again
#7
As an update, it seems that the customer that orders but doesn't trigger notification (PayPal payment goes through but no emails saying order came in get sent by PayPal or the store) has been checking out as a guest.

This doesn't sold the code in comments issue but could this be the reason for failed notifications? They have an account but don't use it when they order. But they use same email etc.
#8
Does anyone have any info on this?

It seems that every PayPal order has the same code in the Customer's Order Comment section after the order goes through.
Mostly the orders via PayPal work but for some of them no notification is coming through and they don't show up in the customers order history!

This is what we get: (any ***** is me censoring for security)

Date Added                Status Customer Notified
04/19/2024 07:37:03 PM  Pending Yes

Date Added                 Status Customer Notified
04/19/2024 07:37:15 PM Processing No
Customer's order comment
Order updated by Paypal webhook request.

Date Added                 Status Customer Notified
04/19/2024 07:37:15 PM Processing No
Customer's order comment
Paypal webhook PAYMENT.CAPTURE.COMPLETED:

Parsed data:
array (
'id' => '*************************', <
'event_version' => '1.0',
'create_time' => '2024-04-19T18:37:06.800Z',
'resource_type' => 'capture',
'resource_version' => '2.0',
'event_type' => 'PAYMENT.CAPTURE.COMPLETED',
'summary' => 'Payment completed for GBP 76.14 GBP',
'resource' =>
array (
'id' => '**************',
'amount' =>
array (
'currency_code' => 'GBP',
'value' => '76.14',
),
'final_capture' => true,
'seller_protection' =>
array (
'status' => 'ELIGIBLE',
'dispute_categories' =>
array (
0 => 'ITEM_NOT_RECEIVED',
1 => 'UNAUTHORIZED_TRANSACTION',
),
),
'seller_receivable_breakdown' =>
array (
'gross_amount' =>
array (
'currency_code' => 'GBP',
'value' => '76.14',
),
'paypal_fee' =>
array (
'currency_code' => 'GBP',
'value' => '2.51',
),
'net_amount' =>
array (
'currency_code' => 'GBP',
'value' => '73.63',
),
),
'custom_id' => '***************************',
'status' => 'COMPLETED',
'supplementary_data' =>
array (
'related_ids' =>
array (
'order_id' => '***************',
),
),
'payee' =>
array (
'email_address' => '*****************',
'merchant_id' => '**************',
),
'create_time' => '2024-04-19T18:37:01Z',
'update_time' => '2024-04-19T18:37:01Z',
'links' =>
array (
0 =>
array (
'href' => 'h t t p s : / / api . paypal . com /v2/payments/captures/************',
'rel' => 'self',
'method' => 'GET',
),
1 =>
array (
'href' => ' h t t p s : / / api . paypal . com /v2/payments/captures/**********/refund',
'rel' => 'refund',
'method' => 'POST',
),
2 =>
array (
'href' => 'h t t p s : / / api . paypal . com /v2/checkout/orders/************',
'rel' => 'up',
'method' => 'GET',
),
),
),
'links' =>
array (
0 =>
array (
'href' => 'h t t p s : / / api . paypal . com / v1 / notifications / webhooks-events / **************************',
'rel' => 'self',
'method' => 'GET',
),
1 =>
array (
'href' => 'h t t p s : / / api . paypal . com / v1 / notifications / webhooks-events / ******************** / resend',
'rel' => 'resend',
'method' => 'POST',
),
),
)
#9
We are using Abantecart 1.3.4 (bootstrap5 template) and it's the paypal_commerce 1.0.0

PayPal express is uninstalled and default_pp_standart 1.0.2 is disabled.
#10
Support / PayPal code is outputted to customer comments
February 27, 2024, 07:32:34 AM
Earlier this month I disabled "Fast Checkout" as whenever anyone pays using PayPal, we get a big block of code from PayPal webhook in the Status & Comments section of the order. Disabling fast checkout didn't fix this, but it did have the strange side-effect of not sending any purchase details for Google Ads so it looks like no purchases have been made through there.

My main worry is the block of text though, in the Customer's order comment we get this for every PayPal transaction:

Paypal webhook PAYMENT.CAPTURE.COMPLETED:

Parsed data:
array (
'id' => etc...

Any idea why this is happening and how to fix it?
#11
Wow, seems that the solution to another problem we started having may also have fixed purchases not coming through to GA4

https://forum.abantecart.com/index.php/topic,10379.0.html

"it appears that the PayPal Express Checkout payment module, even though it wasn't active, was the culprit.

Even though we've never used it, it's not compatible with the new store template and needed uninstalling completely"


Still keeping a close eye on everything but looks like payments are working and data is flowing. Good times!

Thanks for the help.
#12
Support / Re: cache.php and critical app errors
September 29, 2023, 08:32:29 AM
Okay, after support looked at it it appears that the PayPal Express Checkout payment module, even though it wasn't active, was the culprit.

Even though we've never used it, it's not compatible with the new store template and needed uninstalling completely.

Still early days but looking okay so far.
#13
Support / Re: cache.php and critical app errors
September 27, 2023, 09:08:05 AM
Yes I switched it out as I thought the new one might be causing the trouble. The new one is back on now.

I have done a Service Request now.
#14
Support / Re: cache.php and critical app errors
September 27, 2023, 08:31:07 AM
Well we have had another PayPal order come through but not register, no email from store but in orders under incomplete. Payment gone through to PayPal though.
And that is after switching to old PayPal module so it must be something deeper.

Also keep getting this error in the logs every 2 hours from the original missing order:
2023-09-26 18:17:51 - Paypal webhook PAYMENT.CAPTURE.COMPLETED: order ID 9540
Paypal related OrderId: 89U11368WC821835M but not found in the database.


Along with this one also:
2023-09-27 5:18:46 - Unknown Error: AbanteCart core v.1.3.4 Unknown named parameter $message in <b>/home/customer/www/ourstore.com/public_html/store/extensions/default_pp_standart/storefront/controller/responses/extension/default_pp_standart.php</b> on line <b>352</b>

Going to switch back to the newer PayPal module.
Client is pulling their hair out and not happy. I have no clue what is going on with it.
#15
Support / Re: cache.php and critical app errors
September 27, 2023, 04:51:15 AM
The transaction method is Capture
Success & Captured and also Not Captured is Processing

I think they are pretty much the default tbf.

Forum Rules Code of conduct
AbanteCart.com 2010 -