<commit_before>const debug = require('ghost-ignition').debug('stripe-request');
const LeakyBucket = require('leaky-bucket');
const EXPECTED_API_EFFICIENCY = 0.95;
const liveBucket = new LeakyBucket(EXPECTED_API_EFFICIENCY * 100, 1);
const testBucket = new LeakyBucket(EXPECTED_API_EFFICIENCY * 25, 1);

module.exports = function createStripeRequest(makeRequest) {
    return async function stripeRequest(stripe, ...args) {
        if (stripe.__TEST_MODE__) {
            await testBucket.throttle();
        } else {
            await liveBucket.throttle();
        }
        const errorHandler = (err) => {
            switch (err.type) {
            case 'StripeCardError':
                // Card declined
                debug('StripeCardError');
                throw err;
            case 'RateLimitError':
                // Ronseal
                debug('RateLimitError');
                return exponentiallyBackoff(makeRequest, ...args).catch((err) => {
                    // We do not want to recurse further if we get RateLimitError
                    // after running the exponential backoff
                    if (err.type === 'RateLimitError') {
                        throw err;
                    }
                    return errorHandler(err);
                });
            case 'StripeInvalidRequestError':
                debug('StripeInvalidRequestError');
                // Invalid params to the request
                throw err;
            case 'StripeAPIError':
                debug('StripeAPIError');
                // Rare internal server error from stripe
                throw err;
            case 'StripeConnectionError':
                debug('StripeConnectionError');
                // Weird network/https issue
                throw err;
            case 'StripeAuthenticationError':
                debug('StripeAuthenticationError');
                // Invalid API Key (probably)
                throw err;
            default:
                throw err;
            }
        };
        return makeRequest(stripe, ...args).catch(errorHandler);
    };
};

function exponentiallyBackoff(makeRequest, ...args) {
    function backoffRequest(timeout, ...args) {
        return new Promise(resolve => setTimeout(resolve, timeout)).then(() => {
            return makeRequest(...args).catch((err) => {
                if (err.type !== 'RateLimitError') {
                    throw err;
                }

                if (timeout > 30000) {
                    throw err;
                }

                return backoffRequest(timeout * 2, ...args);
            });
        });
    }

    return backoffRequest(1000, ...args);
}
<commit_msg>Fixed throttling of Stripe API requests<commit_after>const debug = require('ghost-ignition').debug('stripe-request');
const LeakyBucket = require('leaky-bucket');
const EXPECTED_API_EFFICIENCY = 0.95;
const liveBucket = new LeakyBucket(EXPECTED_API_EFFICIENCY * 100, 1);
const testBucket = new LeakyBucket(EXPECTED_API_EFFICIENCY * 25, 1);

module.exports = function createStripeRequest(makeRequest) {
    return async function stripeRequest(stripe, ...args) {
        const throttledMakeRequest = async (stripe, ...args) => {
            if (stripe.__TEST_MODE__) {
                await testBucket.throttle();
            } else {
                await liveBucket.throttle();
            }
            return await makeRequest(stripe, ...args);
        };
        const errorHandler = (err) => {
            switch (err.type) {
            case 'StripeCardError':
                // Card declined
                debug('StripeCardError');
                throw err;
            case 'RateLimitError':
                // Ronseal
                debug('RateLimitError');
                return exponentiallyBackoff(throttledMakeRequest, stripe, ...args).catch((err) => {
                    // We do not want to recurse further if we get RateLimitError
                    // after running the exponential backoff
                    if (err.type === 'RateLimitError') {
                        throw err;
                    }
                    return errorHandler(err);
                });
            case 'StripeInvalidRequestError':
                debug('StripeInvalidRequestError');
                // Invalid params to the request
                throw err;
            case 'StripeAPIError':
                debug('StripeAPIError');
                // Rare internal server error from stripe
                throw err;
            case 'StripeConnectionError':
                debug('StripeConnectionError');
                // Weird network/https issue
                throw err;
            case 'StripeAuthenticationError':
                debug('StripeAuthenticationError');
                // Invalid API Key (probably)
                throw err;
            default:
                throw err;
            }
        };
        return throttledMakeRequest(stripe, ...args).catch(errorHandler);
    };
};

function exponentiallyBackoff(makeRequest, ...args) {
    function backoffRequest(timeout, ...args) {
        return new Promise(resolve => setTimeout(resolve, timeout)).then(() => {
            return makeRequest(...args).catch((err) => {
                if (err.type !== 'RateLimitError') {
                    throw err;
                }

                if (timeout > 30000) {
                    throw err;
                }

                return backoffRequest(timeout * 2, ...args);
            });
        });
    }

    return backoffRequest(1000, ...args);
}

{'fraction_non_alphanumeric': 0.1025089605734767, 'fraction_numerical': 0.007168458781362007, 'mean_word_length': 1.5162308385933274, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2977305', 'n_tokens_mistral': 1456, 'n_tokens_neox': 1344, 'n_words': 319}