2

I am trying to send a post request to the App Store verifyReceipt endpoint in a Firebase cloud function. However, I get the following error in the cloud function log:

{ Error: read ECONNRESET
    at TLSWrap.onread (net.js:622:25)
  errno: 'ECONNRESET',
  code: 'ECONNRESET',
  syscall: 'read',

However, this error is only occasional. It does not happen every time, so the function obviously works, but something is going wrong sometimes and I'm not sure what.

Most other solutions relating to this were due to a promise error, but I do not believe that is the problem here. Below is the complete function:

exports.handleSubscriptionIAP_IOS_S2S = functions.https.onRequest((req, res) => {
    let data = req.body;

    console.log('received ios s2s notification with body:', data);

    let base64String = data.unified_receipt.latest_receipt;

    let boolStatus = true;
    // The user has changed the auto renewal status, store the change
    if(data.notification_type === 'DID_CHANGE_RENEWAL_STATUS') {
        boolStatus = (data.auto_renew_status === 'true');
    }

    console.log(data.notification_type);

    if(base64String) {
        var options = {
            method: 'post',
            url: 'https://buy.itunes.apple.com/verifyReceipt',
            data: ({
                "receipt-data" : base64String,
                "password" : "***",
                "exclude-old-transactions" : true
            })
        };

        var optionsSandbox = {
            method: 'post',
            url: 'https://sandbox.itunes.apple.com/verifyReceipt',
            data: ({
                "receipt-data" : base64String,
                "password" : "***",
                "exclude-old-transactions" : true
            })
        };

        return axios(options)
        .then((response) => {
            if(response.data.status === 21007) {
                return 'handle_sandbox';
            }

            // Got valid response from Apple, pass down chain
            else {
                return response;
            }
        })
        .then((response) => {
            // Send post request to sandbox endpoint
            if(response === 'handle_sandbox') {
                return axios(optionsSandbox);
            }

            // Pass response down chain
            else {
                return response;
            }
        })
        .then((response) => {
            // Handle response from Apple verifyReceipt endpoint here
            // Both production and sandbox will end up here
            // See here for values in response.data: https://developer.apple.com/documentation/appstorereceipts/responsebody/latest_receipt_info

            console.log('received ios s2s notification verify receipt response with body:', response.data);
            // Status 0 means request is valid
            if(response.data.status === 0) {
                // Get receipt info of latest receipt
                // Only one object in array is return since we exclude old transactions
                let latestReceipt = response.data.latest_receipt_info[0];

                // Save receipt into Firestore
                return db.collection('appStoreReceipts').doc(latestReceipt.original_transaction_id).set({
                    latest_receipt_info: latestReceipt,

                    expiresTimestamp: admin.firestore.Timestamp.fromMillis(parseInt(latestReceipt.expires_date_ms)),
                    originalTransactionID: latestReceipt.original_transaction_id,

                    autoRenewStatus: boolStatus,

                    base64Receipt: response.data.latest_receipt,
                }, { merge: true });
            }
            else {
                return null;
            }
        })
        .then((result) => {
            if(result) {
                return res.status(200).end();
            }
            else {
                return res.status(400).end();
            }
        })
        .catch((error) => {
            console.log('an error occured handling the subscription', error);

            return res.status(400).end();
        })
    }
    else {
        console.log('invalid receipt', data);

        return res.status(400).end();
    }
});

Thank you for any help that you can give!

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Jake Short
  • 57
  • 1
  • 3
  • 5
  • Is your Firebase project on a paid plan? If not, this is the expected behavior, as on the free plan your Cloud Function can only call Google-hosted services, which `https://buy.itunes.apple.com/` isn't. – Frank van Puffelen Mar 22 '20 at 02:08
  • Yes, it is on the paid plan. The function works a lot of the time, this error occurs a decent amount of times though. – Jake Short Mar 22 '20 at 02:57

1 Answers1

4

ECONNRESET just means that the other end of the connection closed it. That could mean any number of things. It's probably not your code's fault, unless you've done something so bad in this one request that Apple decided to close the connection. You should contact their support directly if you think they're doing this incorrectly.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • OK, thanks for your response. Based on Apple's documentation, they will send another S2S notification a little later if a 400 code is returned (which it will be if it gets the ECONNRESET error), and it appears that these subsequent requests are working. – Jake Short Mar 22 '20 at 03:01