Payment problem with Paypal Commerce plugin

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
2 anos atrás
Been trying to implement payments with the paypal commerce plugin and I think I have encountered a problem.

Brief setup:
Testing with sandbox credentials and testing against a direct credit/debit card number. I will note I have modified the plugin to use the Advanced checkout rather than Standard checkout (so I can use hosted fields and 3D secure).

Problem:
The problem is that even if the test is set up to reject the card (for example using 'CCREJECT-SF' in the name) the transaction still goes through sucessfully in nopcommerce and is marked as Paid. The response from paypal does show it is declined.

Investigation:
See https://developer.paypal.com/tools/sandbox/card-testing/ for other decline triggers and another sample response.
My test response
{
  "create_time": "2023-04-04T17:39:18Z",
  "id": "5T885433KX1697344",
  "intent": "CAPTURE",
  "links": [
    {
      "href": "https://api.sandbox.paypal.com/v2/checkout/orders/5T885433KX1697344",
      "method": "GET",
      "rel": "self"
    }
  ],
  "payment_source": {
    "card": {
      "brand": "VISA",
      "last_digits": "7704",
      "type": "CREDIT"
    }
  },
  "purchase_units": [
    {
      "amount": {
        "breakdown": {
          "discount": {
            "currency_code": "xxxxxx",
            "value": "xxxxxx"
          },
          "handling": {
            "currency_code": "AUD",
            "value": "0.00"
          },
          "insurance": {
            "currency_code": "AUD",
            "value": "0.00"
          },
          "item_total": {
            "currency_code": "AUD",
            "value": "245.00"
          },
          "shipping": {
            "currency_code": "AUD",
            "value": "0.00"
          },
          "shipping_discount": {
            "currency_code": "AUD",
            "value": "0.00"
          },
          "tax_total": {
            "currency_code": "AUD",
            "value": "0.00"
          }
        },
        "currency_code": "AUD",
        "value": "245.00"
      },
      "custom_id": "e9e1bdeb-39f3-4294-9065-802c4a602bfd",
      "description": "Purchase at 'Your store name'",
      "items": [
        {
          "description": "HTC - One (M8) 4G LTE Cell Phone with 32GB Memory - Gunmetal (Sprint)",
          "name": "HTC One M8 Android L 5.0 Lollipop",
          "quantity": "1",
          "sku": "M8_HTC_5L",
          "tax": {
            "currency_code": "AUD",
            "value": "0.00"
          },
          "unit_amount": {
            "currency_code": "AUD",
            "value": "245.00"
          }
        },
        {
          "description": "Gift wrapping - No",
          "name": "Gift wrapping",
          "quantity": "1",
          "tax": {
            "currency_code": "AUD",
            "value": "0.00"
          },
          "unit_amount": {
            "currency_code": "AUD",
            "value": "0.00"
          }
        }
      ],
      "payee": {
        "email_address": "[email protected]",
        "merchant_id": "DRA95KX5SXBGJ"
      },
      "payments": {
        "captures": [
          {
            "amount": {
              "currency_code": "AUD",
              "value": "245.00"
            },
            "create_time": "2023-04-04T17:50:40Z",
            "custom_id": "xxxxxx",
            "final_capture": true,
            "id": "80450137DK344141S",
            "links": [
              {
                "href": "https://api.sandbox.paypal.com/v2/payments/captures/80450137DK344141S",
                "method": "GET",
                "rel": "self"
              },
              {
                "href": "https://api.sandbox.paypal.com/v2/payments/captures/80450137DK344141S/refund",
                "method": "POST",
                "rel": "refund"
              },
              {
                "href": "https://api.sandbox.paypal.com/v2/checkout/orders/5T885433KX1697344",
                "method": "GET",
                "rel": "up"
              }
            ],
            "processor_response": {
              "avs_code": "G",
              "cvv_code": "P",
              "response_code": "9500"
            },
            "seller_protection": {
              "status": "NOT_ELIGIBLE"
            },
            "seller_receivable_breakdown": {
              "gross_amount": {
                "currency_code": "AUD",
                "value": "245.00"
              },
              "net_amount": {
                "currency_code": "AUD",
                "value": "238.82"
              },
              "paypal_fee": {
                "currency_code": "AUD",
                "value": "6.18"
              }
            },
            "status": "DECLINED",
            "update_time": "2023-04-04T17:50:40Z"
          }
        ]
      },
      "reference_id": "e9e1bdeb-39f3-4294-9065-802c4a602bfd",
      "shipping": {
        "address": {
          "address_line_1": "*****",
          "address_line_2": "*****",
          "admin_area_1": "XX",
          "admin_area_2": "Kalgoorlie",
          "country_code": "AU",
          "postal_code": "6430"
        },
        "name": {
          "full_name": "*****"
        }
      },
      "soft_descriptor": "Your store name"
    }
  ],
  "status": "COMPLETED",
  "update_time": "2023-04-04T17:39:18Z"
}


Notice the value of 'status' under captures (DECLINED). Also notice the response_Code under processor_response. The HTTP response code is 201 Created and there are no errors

From PayPalCommercePaymentMethod.cs in the ProcessPaymentAsync function we have the following


//authorize or capture the order
            var (order, error) = _settings.PaymentType == PaymentType.Capture
                ? await _serviceManager.CaptureAsync(_settings, orderId.ToString())
                : (_settings.PaymentType == PaymentType.Authorize
                ? await _serviceManager.AuthorizeAsync(_settings, orderId.ToString())
                : (default, default));

            if (!string.IsNullOrEmpty(error))
                return new ProcessPaymentResult { Errors = new[] { error } };

            //request succeeded
            var result = new ProcessPaymentResult();

            var purchaseUnit = order.PurchaseUnits
                .FirstOrDefault(item => item.ReferenceId.Equals(processPaymentRequest.OrderGuid.ToString()));
            var authorization = purchaseUnit.Payments?.Authorizations?.FirstOrDefault();
            if (authorization != null)
            {
                result.AuthorizationTransactionId = authorization.Id;
                result.AuthorizationTransactionResult = authorization.Status;
                result.NewPaymentStatus = PaymentStatus.Authorized;
            }
            var capture = purchaseUnit.Payments?.Captures?.FirstOrDefault();
            if (capture != null)
            {
                result.CaptureTransactionId = capture.Id;
                result.CaptureTransactionResult = capture.Status;
                result.NewPaymentStatus = PaymentStatus.Paid;
            }

Given the response of 201 Created and there are no error fields it continues and without checking the capture status it marks it as paid

The end result is it appears that irrespective of whether or not the card is successful the order is created and marked as paid which is clearly wrong
2 anos atrás
What version of the plugin?

Chris25 wrote:

I will note I have modified the plugin.


This is probably the issue.

I just tried to make such a payment with a declined card in our PayPalCommerce plugin, and PayPal client itself throws an exception, no further checks are needed in the plugin code. If you use name 'CCREJECT-SF' in the address info, then the exception will be at the time of creating PayPal order, if you use it in the payment card info, then the exception will be at the time of order confirmation.
As a result, the order is not created, an error is displayed to the customer during checkout.
2 anos atrás
Hi

I'm using nop 4.60.2

After a lot of testing it comes down to a difference in behaviour with the different payment_source options. My code was working perfectly

As it turns out, when using 'card' as the payment_source PayPal does NOT return the error. Instead, you have to look at the capture object and check its status and processor response code to determine if it succeeded. The capture API return a 201 Created irrespective of the processor response code is how Paypal does it. Incidentally the .Net SDK that is used does not deserialize the processor response.

When using the Standard integration, even when you are doing the Credit/Debit card section and entering the info there, Paypal actually sets that up as a guest paypal checkout, hence the payment_source set for that paypal order is actually 'paypal' and not 'card'

Also, btw with regards to the .Net SDK used it seem to be using fields that the current docs say are deprecated and it also doesn't deserialize all the json fields

.P.S. I needed to modify it as I needed 3D Secure on for every transaction and standard integration doesnt support that
2 anos atrás
We'll check this. Thanks for the info.
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.