Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to catch INSTRUMENT_DECLINED issue #24

Open
lizeshakya opened this issue Dec 6, 2024 · 2 comments
Open

Unable to catch INSTRUMENT_DECLINED issue #24

lizeshakya opened this issue Dec 6, 2024 · 2 comments

Comments

@lizeshakya
Copy link

After checking out the documentation in Studio Checkout Standard Integrate, I found that these data should be handled?

const orderData = await response.json();
const errorDetail = orderData?.details?.[0];
if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
                    // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
                    // recoverable state, per
                    // https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
                    return actions.restart();
                } else if (errorDetail) {
                    // (2) Other non-recoverable errors -> Show a failure message
                    throw new Error(
                        `${errorDetail.description} (${orderData.debug_id})`
                    );
                } else if (!orderData.purchase_units) {
                    throw new Error(JSON.stringify(orderData));
                } else {
...

How do I handle the issue INSTRUMENT_DECLINED or any other form of issue?

The workflow I am currently implementing:

  1. User will click on the button PayPal button
  2. It will createOrder using Ajax fetch
    a. I create the order using the package and return the transaction_id and response_status back if everything is okay.
  3. After that, user will add their CC credentials
  4. User will be redirect back to onApprove function where it will Ajax Fetch to order.paypal.update with paypal_order_id
  5. Here, I retrieved the paypal order using the package as
$ordersController = $client->getOrdersController();
$getApiResponse = $ordersController->ordersGet([
    'id' => $paypalOrderId
]);
$getApiResponseResult = $getApiResponse->getResult();
$paypalOrders = $getApiResponseResult->getPurchaseUnits();
$paypalOrder = $paypalOrders[0];
if(empty($paypalOrder)){
    throw new InvalidArgumentException('Order not found');
}
....
$modOrder = $paypalOrder->getCustomId();
if(empty($modOrder)){
    throw new InvalidArgumentException('Order not found');
}
....
$apiResponse = $ordersController->ordersCapture([
    'id' => $paypalOrderId
]);

if (!$apiResponse->isSuccess()) {
    throw new InvalidArgumentException($apiResponse->getBody());
}

Once everything is good, I am updating the order in my DB.

Now rereading the documentation, do I have to return the capture and use the webhook to update in my DB? I am not too sure how to catch INSTRUMENT_DECLINED error? Also, is it flow okay without using webhook?

@lizeshakya
Copy link
Author

Or is it enough to check isSuccess?

$apiResponse = $ordersController->ordersCapture([
    'id' => $paypalOrderId
]);
$isSuccess = $apiResponse->isSuccess()

if(!$isSuccess){
....
}
...
//Save to the database

@LeonarthusMectus
Copy link

Hey @lizeshakya

Checking isSuccess() should happen before any result handling if you choose to use it, as that will indicate first if the attempt is considered a success. This works based on the HTTP Status code, so 200/201 would return true, while 3XX/4XX/5XX/missing codes would return false.

Then, you can access the actual result with getResult() and you'll know if it's an Order type or an Error Exception based on the boolean returned.

You can access the returned class instance properties as needed. Typically you'd first check the name, on the Error Exception using getName(). This name will match the Error column in the docs, for example the errors returned by the Orders Capture endpoint are here, so in a case where the issue is INSTRUMENT_DECLINED, the name would be UNPROCESSABLE_ENTITY. Other error names correspond to other types of problems, so checking this would be an important step to understanding if your error case is recoverable or not.

In the first code snippet you shared, it accesses the details property to get an Error Details instance, which contains fields containing specifics around the error.

NOTE: The integration docs are wrong here, you can't access the fields directly as they're private. You must use getters to access the fields listed in the docs. The getters follow the standard get<FieldName> style. We'll work with the docs team to correct this. In this case you'd adjust it like so: orderData?.details?.[0] becomesorderData?.getDetails()?.[0] and so on.

Once you have the details instance, you can call getIssue() and getDescription(). The issue value is somewhat analogous to a type, INSTRUMENT_DECLINED in this instance, and description is a more human readable string related to the issue, again in this instance it would be The requested action could not be performed, semantically incorrect, or failed business validation.. These strings are not always intended to be returned directly to the customer, so be aware.

Since the API can return numerous errors depending on the issue, and not all of them apply to every integration, there isn't a specific guide we can provide for what errors are recoverable as it depends on numerous variable. Instead, it's often suggested to check for issues you can handle that are recoverable for your integration and handle those cases, and fail anything that falls outside that list. You can still log failure types that you don't currently view as recoverable, and opt to build out additional logic and handling to support specific issues if necessary and possible.

I hope this helps clarify a bit, and we'll work on getting those docs sorted out asap!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants