skip to Main Content

Several weeks ago, we started seeing failed payments with our old Paypal Express integration without making any changes to it. They did not fail 100% but maybe 80%-90%. It seemed to fail arbitrarily.

We followed the upgrade path for the new Checkout integration, but kept seeing the same errors.

Finally we went all in on the new Standard (not Advanced!) Checkout integration using the Orders v2 API, replacing the old Payments V1 API. But that did not change anything either.

Side note: All of the integrations (old, updated, new) work totally fine in Sandbox.

But in production, almost every capture request fails with a PAYER_ACTION_REQUIRED error.

Here are the the steps:

User clicks on the checkout with paypal button.

The button’s createOrder() method calls our server and we create an order with a POST to https://api.paypal.com/v2/checkout/orders, with the following response:

{
  id: '1KC867444K565180X',
  status: 'CREATED',
  links: [
    {
      href: 'https://api.paypal.com/v2/checkout/orders/1KC867444K565180X',
      rel: 'self',
      method: 'GET'
    },
    {
      href: 'https://www.paypal.com/checkoutnow?token=1KC867444K565180X',
      rel: 'approve',
      method: 'GET'
    },
    {
      href: 'https://api.paypal.com/v2/checkout/orders/1KC867444K565180X',
      rel: 'update',
      method: 'PATCH'
    },
    {
      href: 'https://api.paypal.com/v2/checkout/orders/1KC867444K565180X/capture',
      rel: 'capture',
      method: 'POST'
    }
  ]
}

We do not redirect to the approve URL (since the button should handle it on the frontend, right?). So we just hand the ID back to the button’s createOrder() method.

The onApprove() callback then calls our server again with the orderId and the paymentId. We do not capture the payment yet. Instead, we fetch the details to get the shipping address. After calculating the shipping costs, we PATCH the order. Then we fetch details again and verify that all changes have been applied correctly – which ist the case.

Side note: When fetching details after creating the order (so after it went through the buttons onApprove() callback, and right before capturing), there is no more approve link listed:

{
  id: '1KC867444K565180X',
  intent: 'CAPTURE',
  status: 'APPROVED',
  purchase_units: [
    {
      //...
    }
  ],
  payer: {
     //...
  },
  create_time: '2022-07-14T14:41:16Z',
  links: [
    {
      href: 'https://api.paypal.com/v2/checkout/orders/1KC867444K565180X',
      rel: 'self',
      method: 'GET'
    },
    {
      href: 'https://api.paypal.com/v2/checkout/orders/1KC867444K565180X',
      rel: 'update',
      method: 'PATCH'
    },
    {
      href: 'https://api.paypal.com/v2/checkout/orders/1KC867444K565180X/capture',
      rel: 'capture',
      method: 'POST'
    }
  ]
}

Up until now, sandbox and production behave excatly the same. But when we now try to capture the payment, sandbox works fine, but production returns this:

{
  name: 'UNPROCESSABLE_ENTITY',
  details: [
    {
      issue: 'PAYER_ACTION_REQUIRED',
      description: 'Payer needs to perform the following action before proceeding with payment.'
    }
  ],
  message: 'The requested action could not be performed, semantically incorrect, or failed business validation.',
  debug_id: '160e81e8cc53f',
  links: [
    {
      href: 'https://developer.paypal.com/docs/api/orders/v2/#error-PAYER_ACTION_REQUIRED',
      rel: 'information_link',
      method: 'GET'
    }
  ]
}

We completely ran out of ideas what to do with this message. The docs suggest that "The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the "rel":"payer-action" HATEOAS link returned as part of the response prior to authorizing or capturing the order." – but there is no such link. Plus, the order status is already APPROVED.

Any help would be highly appreciated.

2

Answers


  1. It’s probably related to having patched the order before capture, requiring re-approval.


    Adapt the error handling code in this demo to also call actions.restart() when the issue is PAYER_ACTION_REQUIRED .

    Essentially,

    if (errorDetail && ['INSTRUMENT_DECLINED', 'PAYER_ACTION_REQUIRED'].includes(errorDetail.issue) ) {
    

    Alternatively, instead of the button JS you can use the rel:approve href from the original order creation and redirect to that.

    Login or Signup to reply.
  2. this error is due to the EBA PSD2.0 regulation. any order is paid through credit card issued from EMEA region, and the capture amount is higher than the create order amount, it will be declined with the error code. here’s the PayPal change about the over capture:
    https://www.paypal.com/ae/smarthelp/article/FAQ4645

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search