Search code examples
javascriptpaypalpaypal-sandboxpaypal-rest-sdk

Simulate PayPal errors in a sandbox account


I'm integrating PayPal payment on a web application I'm developing. I need to create an authorization for a transaction where I lock an amount of money (let's say 20€), then at the end of the transaction I complete the transaction and I take only the money that I need to take (so if the transaction's final cost is 15€, I give back 5€ to the user).

This workflow is currently working on a sandbox account, but now I wanted to test some errors that may occur while starting a new transaction, like for instance when the user doesn't have the sufficient amount of money (20€) that I need to lock in order to start a new transaction.

I found this documentation (https://developer.paypal.com/docs/api/test-values/#invoke-negative-testing) where it is stated To trigger the SENDER_EMAIL_UNCONFIRMED simulation response, set the items[0]/note value to ERRPYO002 in the POST v1/payments/payouts call. with the following code:

curl -X POST https://api.sandbox.paypal.com/v1/payments/payouts \
  -H "content-type: application/json" \
  -H "Authorization: Bearer Access-Token" \
  -d '{
  "sender_batch_header": {
    "sender_batch_id": "1524086406556",
    "email_subject": "This email is related to simulation"
  },
  "items": [
  {
    "recipient_type": "EMAIL",
    "receiver": "payouts-simulator-receiver@paypal.com",
    "note": "ERRPYO002",
    "sender_item_id": "15240864065560",
    "amount": {
      "currency": "USD",
      "value": "1.00"
    }
  }]
}'

So I guess that I need to pass an error code (like ERRPYO002) to a note field in my request body.

I'm using the checkout sdk, and my js code currently looks like this:

const buttonOpts = {
    env: 'sandbox',
    client: { production: $scope.key, sandbox: $scope.key },
    style: {
        label: 'paypal',
        size: 'medium',
        shape: 'rect',
        color: 'blue',
        tagline: false,
    },

    validate: actions => {
        // stuff
    },
    payment: (data, actions) => {
        return actions.payment.create({
            intent: 'authorize',
            payer: { payment_method: 'paypal' },
            transactions: [
                {
                    amount: {
                        total: '20.00',
                        currency: 'EUR',
                    },
                    description: 'My description',
                },
            ],
        });
    },
    onAuthorize: data => {
        // Sending data.paymentID and data.payerID to my backend to confirm the new transaction
    },
    onCancel: () => {
        // stuff
    },
    onError: err => {
        console.log(err);
        // stuff
    },
};

Paypal.Button.render(buttonOpts, '#paypal-button');

I guess that I need to pass the code needed to simulate the error to my actions.payment.create object parameter, but I didn't find where exactly since my workflow is different that the one in the docs.

These are the codes that PayPal allows you to use for error testing: https://developer.paypal.com/docs/payouts/integrate/test-payouts/#test-values

Any help is appreciated. Thanks a lot.


Solution

  • Ok, I've actually found out how to solve this problem right after I posted this question. I'll just put my solution here for anyone that may have this problem in the future.

    The option object I posted is actually correct as it is now, so after the user confirms that he/she wants to start a new transaction I get the payerID and the paymentID to send to my backend. On my backend function I changed my code so that it is as follows:

    const paypal = require('paypal-rest-sdk');
    const paymentId = event.paymentID;
    const payerId = { payer_id: event.payerID };
    paypal.configure({
        mode: process.env.PAYPAL_ENVIRONMENT, //sandbox or live
        client_id: '<MY_CLIENT_ID>',
        client_secret: '<MY_CLIENT_SECRET>',
    });
    
    paypal.payment.execute(
        paymentId,
        payerId,
        // START NEW CODE
        {
            headers: {
                'Content-Type': 'application/json',
                'PayPal-Mock-Response': '{"mock_application_codes": "INSUFFICIENT_FUNDS"}',
            },
        },
        // END NEW CODE
        (error, payment) => {
            console.error(JSON.stringify(error));
            console.error(JSON.stringify(payment));
            if (error) {
                /*
                    {
                        "response": {
                            "name": "INSUFFICIENT_FUNDS",
                            "message": "Buyer cannot pay - insufficient funds.",
                            "information_link": "https://developer.paypal.com/docs/api/payments/#errors",
                            "debug_id": "a1b2c3d4e5f6g",
                            "details": [
                                {
                                    "issue": "The buyer must add a valid funding instrument, such as a credit card or bank account, to their PayPal account."
                                }
                            ],
                            "httpStatusCode": 400
                        },
                        "httpStatusCode": 400
                    }
                */
                return callback('unhandled_error', null);
            }
            if (payment.state === 'approved' && payment.transactions && payment.transactions[0].related_resources && payment.transactions[0].related_resources[0].authorization) {
                return callback(null, payment.transactions[0].related_resources[0].authorization.id);
            }
            console.log('payment not successful');
            return callback('unhandled_error', null);
        }
    );
    

    In the request headers you just have to put an header called PayPal-Mock-Response that contains the error code you want to test, and that's it.

    Hope this'll help somebody!