overleaf/services/web/scripts/recurly/collect_paypal_past_due_invoice.js
Antoine Clausse 01188589f8 Add some JSDoc types to @overleaf/logger (#19153)
* Add some JSDoc types to `@overleaf/logger`

* Update `logger.error` calls

* Fixup `logger.err` JSDoc

* Update `logger.err` calls

* Fix `args` type

* Remove "Error message" description

* Replace `arguments` by actual arguments of the method

* Fix: "ESLint: Unnecessary '.apply()'.(no-useless-call)"

* Add JSDoc params to `debug` `info` `warn`

* Remove extra `args` param in JSDoc so developers aren't invited to use it

Not sure if this is the best thing to do because it creates a warning in the IDE: "Parameter args is not described in JSDoc"

* Add comment about serialization of `err` `req` `res`

* Allow strings as first param in `debug` `info` `warn`

* Fix syntax for optional parameters in JSDoc

* Add 2 signatures, to avoid "string, string" params

* Fix `@signature` names copy-pastes

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>

* Revert the double `@param attributes`. It doesn't work

---------

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 086dee8bbf30d577c5e1f844a9df5e518c46aca7
2024-06-28 08:04:25 +00:00

131 lines
3.5 KiB
JavaScript

const RecurlyWrapper = require('../../app/src/Features/Subscription/RecurlyWrapper')
const minimist = require('minimist')
const logger = require('@overleaf/logger')
const waitMs =
require.main === module
? timeout => new Promise(resolve => setTimeout(() => resolve(), timeout))
: () => Promise.resolve()
// NOTE: Errors are not propagated to the caller
const handleAPIError = async (source, id, error) => {
logger.warn(`Errors in ${source} with id=${id}`, error)
if (typeof error === 'string' && error.match(/429$/)) {
return waitMs(1000 * 60 * 5)
}
await waitMs(80)
}
/**
* @returns {Promise<{
* INVOICES_COLLECTED: string[],
* INVOICES_COLLECTED_SUCCESS: string[],
* USERS_COLLECTED: string[],
* }>}
*/
const main = async () => {
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',
invoice.invoice_number,
error
)
}
}
const isAccountUsingPaypal = async invoice => {
const accountId = invoice.account.url.match(/accounts\/(.*)/)[1]
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)
}
}
const attemptInvoicesCollection = async () => {
let getPage = await RecurlyWrapper.promises.getPaginatedEndpointIterator(
'invoices',
{ state: 'past_due' }
)
while (getPage) {
const { items, getNextPage } = await getPage()
logger.info('invoices', items?.length)
for (const invoice of items) {
await attemptInvoiceCollection(invoice)
}
getPage = getNextPage
}
}
const argv = minimist(process.argv.slice(2))
const DRY_RUN = argv.n !== undefined
const INVOICES_COLLECTED = []
const INVOICES_COLLECTED_SUCCESS = []
const USERS_COLLECTED = []
try {
await attemptInvoicesCollection()
const diff = INVOICES_COLLECTED.length - INVOICES_COLLECTED_SUCCESS.length
if (diff !== 0) {
logger.warn(`Invoices collection failed for ${diff} invoices`)
}
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(
{
INVOICES_COLLECTED,
INVOICES_COLLECTED_SUCCESS,
USERS_COLLECTED,
},
{ maxArrayLength: null }
)
}
}
if (require.main === module) {
main()
.then(() => {
logger.info('Done.')
process.exit(0)
})
.catch(err => {
logger.error({ err }, 'Error')
process.exit(1)
})
}
module.exports = { main }