woocommerce / woocommerce-paypal-payments

https://wordpress.org/plugins/woocommerce-paypal-payments/
GNU General Public License v2.0
62 stars 47 forks source link

Could not retrieve information. Try again later (706) #656

Open ifnull opened 2 years ago

ifnull commented 2 years ago

Describe the Bug

We are running v1.7.0. We have noticed that sometime in early May some of our payments stopped capturing. Yesterday roughly 20% of payments failed to capture. Selecting the "Capture authorized PayPal payment" order actions returns Could not retrieve information. Try again later. The order gets marked as "Paid" but also marked as "Not captured". The debug log does not show an error or even an attempt to capture when this error is shown. I see that it makes a request to DEBUG /v2/checkout/orders/:order_id but for the orders that are failing to capture, I never see a request to capture (e.g. POST /v2/checkout/orders/:order_id/capture).

To Reproduce

  1. Go to a completed order
  2. From the order actions drop-down select "Capture authorized PayPal payment"
  3. Click Update
  4. Error message "Could not retrieve information. Try again later." appears in the upper left.

Expected Behavior

The request is made to Paypal to capture the payment and the payment is captured.

Actual Behavior

No request is made to Paypal. Payment remains in Pending status in Paypal.

Environment

Screen Shot 2022-05-24 at 1 49 21 PM_annotated
ifnull commented 2 years ago

I saw someone else reported the same error on the WP forum about 2 weeks ago. I've also created a ticket on the Inpsyde website.

ifnull commented 2 years ago

It looks like the issue may have something to do with the all_authorizations() method. It looks like capture_authorization() assumes the response from all_authorizations() will contain at least one valid authorization. When it does not find at least one authorization, it fails silently.

I'm not sure why it would not be finding an authorization for only some orders. When we make a request to the https://api.paypal.com/v2/checkout/orders/:order_id endpoint on the orders failing to capture in this way, we still see purchase_units in the API response with payments->authorizations containing at least one authorization with a CREATED status.

https://github.com/woocommerce/woocommerce-paypal-payments/blob/ecf3feab62a7467d777722375f8bfdeb2a9f8031/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php#L286

Example response from https://api.paypal.com/v2/checkout/orders/***

{
    "id": "*********",
    "intent": "AUTHORIZE",
    "status": "COMPLETED",
    "purchase_units":
    [
        {
            "reference_id": "default",
            "amount":
            {
                "currency_code": "USD",
                "value": "32.58",
                "breakdown":
                {
                    "item_total":
                    {
                        "currency_code": "USD",
                        "value": "32.58"
                    },
                    "shipping":
                    {
                        "currency_code": "USD",
                        "value": "0.00"
                    },
                    "handling":
                    {
                        "currency_code": "USD",
                        "value": "0.00"
                    },
                    "insurance":
                    {
                        "currency_code": "USD",
                        "value": "0.00"
                    },
                    "shipping_discount":
                    {
                        "currency_code": "USD",
                        "value": "0.00"
                    }
                }
            },
            "payee":
            {
                "email_address": ""*********",
                "merchant_id": ""*********"
            },
            "custom_id": ""*********",
            "invoice_id": "WC-"*********",
            "soft_descriptor": "PAYPAL *"*********",
            "shipping":
            {
                "name":
                {
                    "full_name": ""*********"
                }
            },
            "payments":
            {
                "authorizations":
                [
                    {
                        "status": "CREATED",
                        "id": ""*********",
                        "amount":
                        {
                            "currency_code": "USD",
                            "value": "32.58"
                        },
                        "invoice_id": "WC-"*********",
                        "custom_id": ""*********",
                        "seller_protection":
                        {
                            "status": "ELIGIBLE",
                            "dispute_categories":
                            [
                                "ITEM_NOT_RECEIVED",
                                "UNAUTHORIZED_TRANSACTION"
                            ]
                        },
                        "expiration_time": "2022-06-21T20:20:24Z",
                        "links":
                        [
                            {
                                "href": "https://api.paypal.com/v2/payments/authorizations/"*********",
                                "rel": "self",
                                "method": "GET"
                            },
                            {
                                "href": "https://api.paypal.com/v2/payments/authorizations/"*********/capture",
                                "rel": "capture",
                                "method": "POST"
                            },
                            {
                                "href": "https://api.paypal.com/v2/payments/authorizations/"*********/void",
                                "rel": "void",
                                "method": "POST"
                            },
                            {
                                "href": "https://api.paypal.com/v2/payments/authorizations/"*********/reauthorize",
                                "rel": "reauthorize",
                                "method": "POST"
                            },
                            {
                                "href": "https://api.paypal.com/v2/checkout/orders/"*********",
                                "rel": "up",
                                "method": "GET"
                            }
                        ],
                        "create_time": "2022-05-23T20:20:24Z",
                        "update_time": "2022-05-23T20:20:24Z"
                    }
                ]
            }
        }
    ],
    "payer":
    {
        "name":
        {
            "given_name": ""*********",
            "surname": ""*********"
        },
        "email_address": ""*********",
        "payer_id": ""*********",
        "phone":
        {
            "phone_number":
            {
                "national_number": ""*********"
            }
        },
        "address":
        {}
    },
    "update_time": "2022-05-23T20:20:24Z",
    "links":
    [
        {
            "href": "https://api.paypal.com/v2/checkout/orders/"*********",
            "rel": "self",
            "method": "GET"
        }
    ]
}
ifnull commented 2 years ago

@InpsydeNiklas Do you need any more info?

InpsydeNiklas commented 2 years ago

All we can say is that the behavior seems to have been introduced by a change at PayPal as reports about it started coming in across several plugin versions at the same time. We haven't been able to reproduce it yet internally but have brought this up with PayPal. We haven't heard back yet though. We appreciate the detailed report in any case and will provide an update when we know more.

lmds commented 2 years ago

I'm having the same issue with WooCommerce 6.5.1 on WP 5.9.3 with WooCommerce PayPal Payments 1.8.0. The debug logs look like everything is successful, but we get the error message "Could not retrieve information. Try again later." when we try to do the capture, and it doesn't capture, on multiple different orders.

Dinamiko commented 2 years ago

Hello @ifnull @lmds

It seems that there is a problem getting the PayPal order from WC order here: https://github.com/woocommerce/woocommerce-paypal-payments/blob/trunk/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php#L137

When the exception is thrown it returns self::INACCESSIBLE which is then used to display the error message here: https://github.com/woocommerce/woocommerce-paypal-payments/blob/trunk/modules/ppcp-wc-gateway/src/Notice/AuthorizeOrderActionNotice.php#L49-L55

As we are not currently able to reproduce it will be great if you can add this line: $this->logger->error( 'Could not get PayPal order from WC order: ' . $exception->getMessage() );
here after line 133: https://github.com/woocommerce/woocommerce-paypal-payments/commit/6c36843069644699e4eafc51c4b4335310493524

I´m attaching the package here in case you can not add the above line manually: woocommerce-paypal-payments.zip

So after adding the above line it should display the concrete error message, will be great if you can let us know here what error message it does contains after "Could not get PayPal order from WC order:" entry log.

Thanks in advance.

ifnull commented 2 years ago

@Dinamiko We had to disable the plugin and all uncaptured payments were manually captured in the Paypal portal. Unfortunately, I do not have a way to test the change you suggested. If Paypal had returned anything other than a 200 response code, it would have been written to the log. Since we don't see that in the log, I think there may be an issue with parsing the response from Paypal. Is there an easy way to use the example order JSON I provided and pass it to $this->order_factory->from_paypal_response( $json ); to get the exception we need?

Dinamiko commented 2 years ago

@lmds I went able to reproduce by passing the json to the order factory as you suggested, it throws exception because 'No country given for address.' from here: https://github.com/woocommerce/woocommerce-paypal-payments/blob/trunk/modules/ppcp-api-client/src/Factory/AddressFactory.php#L72-L76

We had the same issue recently in refunds: https://github.com/woocommerce/woocommerce-paypal-payments/issues/639 and we fixed it by not throwing exception but just adding an empty string when creating the Address: https://github.com/woocommerce/woocommerce-paypal-payments/pull/672/files

The above fix will be included in the upcoming release, in the meantime I´m adding the package in case anyone wants to give it a try: woocommerce-paypal-payments.zip

ifnull commented 2 years ago

@Dinamiko We will plan to include this fix in our next release. I think it would be good to include https://github.com/woocommerce/woocommerce-paypal-payments/commit/6c36843069644699e4eafc51c4b4335310493524 in the next release too. This would be helpful if we need to debug any other factory's from_paypal_response() method.

ifnull commented 2 years ago

@Dinamiko I took another look and found another issue with the conditional to check if shipping is needed. I think the issue for us is PurchaseUnitFactory's shipping_needed() method which uses $item->category() !== Item::DIGITAL_GOODS to identify if shipping is needed. Since we have physical goods with an in-store pickup fulfillment option this check is failing. We use WC()->cart->needs_shipping() during the purchase process (see below) which works great. However, I do not think there is a similar method for WC Orders that we could use here. It would be great if there was a way for us to hook into your shipping_needed() method to overwrite it and check our custom fulfillment method to determine if shipping is needed.

use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;

add_filter( 'ppcp_request_args', array($this,'hide_ship_to_in_paypal_popup'), 11, 2);

public function hide_ship_to_in_paypal_popup(array $args, string $url): array {
    if ( ! isset( $args['body'] ) ) {
        return $args;
    }

    if ( ! is_null( WC()->cart ) && WC()->cart->needs_shipping() ) {
        return $args;
    }

    $args_body_obj = json_decode( $args['body'] );

    // If we have shipping_preference, let's set the value here. Otherwise, do nothing.
    if ( isset( $args_body_obj->application_context ) && isset( $args_body_obj->application_context->shipping_preference ) ) {
        $args_body_obj->application_context->shipping_preference = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING;
        $args['body'] = json_encode( $args_body_obj );
    }

    return $args;
}
ifnull commented 2 years ago

@Dinamiko Any word on when we can expect an official release with https://github.com/woocommerce/woocommerce-paypal-payments/pull/672 ?

InpsydeNiklas commented 2 years ago

A new release is expected in the coming days but a pre-release is available from here already: https://github.com/woocommerce/woocommerce-paypal-payments/releases Please give it a try and let us know how that works. Thanks!