2019-11-12 03:56:08 -05:00
|
|
|
const RecurlyWrapper = require('../../app/src/Features/Subscription/RecurlyWrapper')
|
|
|
|
const minimist = require('minimist')
|
2024-05-14 04:20:35 -04:00
|
|
|
const logger = require('@overleaf/logger')
|
2019-11-12 03:56:08 -05:00
|
|
|
|
2024-05-23 03:06:46 -04:00
|
|
|
const waitMs =
|
2024-05-14 04:20:35 -04:00
|
|
|
require.main === module
|
2024-05-23 03:06:46 -04:00
|
|
|
? timeout => new Promise(resolve => setTimeout(() => resolve(), timeout))
|
|
|
|
: () => Promise.resolve()
|
2019-11-12 03:56:08 -05:00
|
|
|
|
2024-05-14 04:20:35 -04:00
|
|
|
// NOTE: Errors are not propagated to the caller
|
2024-05-23 03:06:46 -04:00
|
|
|
const handleAPIError = async (source, id, error) => {
|
2024-05-14 04:20:35 -04:00
|
|
|
logger.warn(`Errors in ${source} with id=${id}`, error)
|
2019-11-12 03:56:08 -05:00
|
|
|
if (typeof error === 'string' && error.match(/429$/)) {
|
2024-05-23 03:06:46 -04:00
|
|
|
return waitMs(1000 * 60 * 5)
|
2019-11-12 03:56:08 -05:00
|
|
|
}
|
2024-05-23 03:06:46 -04:00
|
|
|
await waitMs(80)
|
2019-11-12 03:56:08 -05:00
|
|
|
}
|
|
|
|
|
2024-05-14 04:20:35 -04:00
|
|
|
/**
|
|
|
|
* @returns {Promise<{
|
|
|
|
* INVOICES_COLLECTED: string[],
|
|
|
|
* INVOICES_COLLECTED_SUCCESS: string[],
|
|
|
|
* USERS_COLLECTED: string[],
|
|
|
|
* }>}
|
|
|
|
*/
|
|
|
|
const main = async () => {
|
2024-05-23 03:06:46 -04:00
|
|
|
const attemptInvoiceCollection = async invoice => {
|
|
|
|
const isPaypal = await isAccountUsingPaypal(invoice)
|
|
|
|
|
|
|
|
if (!isPaypal) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const accountId = invoice.account.url.match(/accounts\/(.*)/)[1]
|
|
|
|
if (USERS_COLLECTED.indexOf(accountId) > -1) {
|
|
|
|
logger.warn(`Skipping duplicate user ${accountId}`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
INVOICES_COLLECTED.push(invoice.invoice_number)
|
|
|
|
USERS_COLLECTED.push(accountId)
|
|
|
|
if (DRY_RUN) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
await RecurlyWrapper.promises.attemptInvoiceCollection(
|
|
|
|
invoice.invoice_number
|
|
|
|
)
|
|
|
|
INVOICES_COLLECTED_SUCCESS.push(invoice.invoice_number)
|
|
|
|
await waitMs(80)
|
|
|
|
} catch (error) {
|
|
|
|
return handleAPIError(
|
|
|
|
'attemptInvoiceCollection',
|
2024-05-14 04:20:35 -04:00
|
|
|
invoice.invoice_number,
|
2024-05-23 03:06:46 -04:00
|
|
|
error
|
2024-05-14 04:20:35 -04:00
|
|
|
)
|
2024-05-23 03:06:46 -04:00
|
|
|
}
|
2024-05-14 04:20:35 -04:00
|
|
|
}
|
|
|
|
|
2024-05-23 03:06:46 -04:00
|
|
|
const isAccountUsingPaypal = async invoice => {
|
2019-11-12 03:56:08 -05:00
|
|
|
const accountId = invoice.account.url.match(/accounts\/(.*)/)[1]
|
2024-05-23 03:06:46 -04:00
|
|
|
try {
|
|
|
|
const response = await RecurlyWrapper.promises.getBillingInfo(accountId)
|
|
|
|
await waitMs(80)
|
|
|
|
return !!response.billing_info.paypal_billing_agreement_id
|
|
|
|
} catch (error) {
|
|
|
|
return handleAPIError('billing info', accountId, error)
|
|
|
|
}
|
2024-05-14 04:20:35 -04:00
|
|
|
}
|
|
|
|
|
2024-05-23 03:06:46 -04:00
|
|
|
const attemptInvoicesCollection = async () => {
|
|
|
|
let getPage = await RecurlyWrapper.promises.getPaginatedEndpointIterator(
|
2024-05-14 04:20:35 -04:00
|
|
|
'invoices',
|
2024-05-23 03:06:46 -04:00
|
|
|
{ state: 'past_due' }
|
2019-11-12 03:56:08 -05:00
|
|
|
)
|
2024-05-23 03:06:46 -04:00
|
|
|
|
|
|
|
while (getPage) {
|
|
|
|
const { items, getNextPage } = await getPage()
|
|
|
|
logger.info('invoices', items?.length)
|
|
|
|
for (const invoice of items) {
|
|
|
|
await attemptInvoiceCollection(invoice)
|
|
|
|
}
|
|
|
|
getPage = getNextPage
|
|
|
|
}
|
2024-05-14 04:20:35 -04:00
|
|
|
}
|
2024-05-23 03:06:46 -04:00
|
|
|
|
2024-05-14 04:20:35 -04:00
|
|
|
const argv = minimist(process.argv.slice(2))
|
|
|
|
const DRY_RUN = argv.n !== undefined
|
|
|
|
const INVOICES_COLLECTED = []
|
|
|
|
const INVOICES_COLLECTED_SUCCESS = []
|
|
|
|
const USERS_COLLECTED = []
|
2019-11-12 03:56:08 -05:00
|
|
|
|
2024-05-23 03:06:46 -04:00
|
|
|
try {
|
|
|
|
await attemptInvoicesCollection()
|
2019-11-12 03:56:08 -05:00
|
|
|
|
2024-05-29 08:18:58 -04:00
|
|
|
const diff = INVOICES_COLLECTED.length - INVOICES_COLLECTED_SUCCESS.length
|
|
|
|
if (diff !== 0) {
|
2024-06-10 04:40:29 -04:00
|
|
|
logger.warn(`Invoices collection failed for ${diff} invoices`)
|
2024-05-23 03:06:46 -04:00
|
|
|
}
|
2024-05-14 04:20:35 -04:00
|
|
|
|
2024-05-23 03:06:46 -04:00
|
|
|
return {
|
|
|
|
INVOICES_COLLECTED,
|
|
|
|
INVOICES_COLLECTED_SUCCESS,
|
|
|
|
USERS_COLLECTED,
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
logger.info(
|
|
|
|
`DONE (DRY_RUN=${DRY_RUN}). ${INVOICES_COLLECTED.length} invoices collection attempts for ${USERS_COLLECTED.length} users. ${INVOICES_COLLECTED_SUCCESS.length} successful collections`
|
|
|
|
)
|
|
|
|
console.dir(
|
|
|
|
{
|
2024-05-14 04:20:35 -04:00
|
|
|
INVOICES_COLLECTED,
|
|
|
|
INVOICES_COLLECTED_SUCCESS,
|
|
|
|
USERS_COLLECTED,
|
2024-05-23 03:06:46 -04:00
|
|
|
},
|
|
|
|
{ maxArrayLength: null }
|
|
|
|
)
|
|
|
|
}
|
2019-11-12 03:56:08 -05:00
|
|
|
}
|
|
|
|
|
2024-05-14 04:20:35 -04:00
|
|
|
if (require.main === module) {
|
|
|
|
main()
|
|
|
|
.then(() => {
|
2024-06-27 04:10:31 -04:00
|
|
|
logger.info('Done.')
|
2024-05-14 04:20:35 -04:00
|
|
|
process.exit(0)
|
|
|
|
})
|
|
|
|
.catch(err => {
|
2024-06-26 03:06:00 -04:00
|
|
|
logger.error({ err }, 'Error')
|
2024-05-14 04:20:35 -04:00
|
|
|
process.exit(1)
|
|
|
|
})
|
|
|
|
}
|
2021-07-01 11:07:26 -04:00
|
|
|
|
2024-05-14 04:20:35 -04:00
|
|
|
module.exports = { main }
|