collect_paypal_past_due_invoice.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. const RecurlyWrapper = require('../../app/src/Features/Subscription/RecurlyWrapper')
  2. const async = require('async')
  3. const minimist = require('minimist')
  4. const logger = require('@overleaf/logger')
  5. const slowCallback =
  6. require.main === module
  7. ? (callback, error, data) => setTimeout(() => callback(error, data), 80)
  8. : (callback, error, data) => callback(error, data)
  9. // NOTE: Errors are not propagated to the caller
  10. const handleAPIError = (source, id, error, callback) => {
  11. logger.warn(`Errors in ${source} with id=${id}`, error)
  12. if (typeof error === 'string' && error.match(/429$/)) {
  13. return setTimeout(callback, 1000 * 60 * 5)
  14. }
  15. slowCallback(callback)
  16. }
  17. /**
  18. * @returns {Promise<{
  19. * INVOICES_COLLECTED: string[],
  20. * INVOICES_COLLECTED_SUCCESS: string[],
  21. * USERS_COLLECTED: string[],
  22. * }>}
  23. */
  24. const main = async () => {
  25. const attemptInvoiceCollection = (invoice, callback) => {
  26. isAccountUsingPaypal(invoice, (error, isPaypal) => {
  27. if (error || !isPaypal) {
  28. return callback(error)
  29. }
  30. const accountId = invoice.account.url.match(/accounts\/(.*)/)[1]
  31. if (USERS_COLLECTED.indexOf(accountId) > -1) {
  32. logger.warn(`Skipping duplicate user ${accountId}`)
  33. return callback()
  34. }
  35. INVOICES_COLLECTED.push(invoice.invoice_number)
  36. USERS_COLLECTED.push(accountId)
  37. if (DRY_RUN) {
  38. return callback()
  39. }
  40. RecurlyWrapper.attemptInvoiceCollection(
  41. invoice.invoice_number,
  42. (error, response) => {
  43. if (error) {
  44. return handleAPIError(
  45. 'attemptInvoiceCollection',
  46. invoice.invoice_number,
  47. error,
  48. callback
  49. )
  50. }
  51. INVOICES_COLLECTED_SUCCESS.push(invoice.invoice_number)
  52. slowCallback(callback, null)
  53. }
  54. )
  55. })
  56. }
  57. const isAccountUsingPaypal = (invoice, callback) => {
  58. const accountId = invoice.account.url.match(/accounts\/(.*)/)[1]
  59. RecurlyWrapper.getBillingInfo(accountId, (error, response) => {
  60. if (error) {
  61. return handleAPIError('billing info', accountId, error, callback)
  62. }
  63. if (response.billing_info.paypal_billing_agreement_id) {
  64. return slowCallback(callback, null, true)
  65. }
  66. slowCallback(callback, null, false)
  67. })
  68. }
  69. const attemptInvoicesCollection = callback => {
  70. RecurlyWrapper.getPaginatedEndpoint(
  71. 'invoices',
  72. { state: 'past_due' },
  73. (error, invoices) => {
  74. logger.info('invoices', invoices.length)
  75. if (error) {
  76. return callback(error)
  77. }
  78. async.eachSeries(invoices, attemptInvoiceCollection, callback)
  79. }
  80. )
  81. }
  82. const argv = minimist(process.argv.slice(2))
  83. const DRY_RUN = argv.n !== undefined
  84. const INVOICES_COLLECTED = []
  85. const INVOICES_COLLECTED_SUCCESS = []
  86. const USERS_COLLECTED = []
  87. return new Promise(resolve => {
  88. attemptInvoicesCollection(error => {
  89. logger.info(
  90. `DONE (DRY_RUN=${DRY_RUN}). ${INVOICES_COLLECTED.length} invoices collection attempts for ${USERS_COLLECTED.length} users. ${INVOICES_COLLECTED_SUCCESS.length} successful collections`
  91. )
  92. console.dir(
  93. {
  94. INVOICES_COLLECTED,
  95. INVOICES_COLLECTED_SUCCESS,
  96. USERS_COLLECTED,
  97. },
  98. { maxArrayLength: null }
  99. )
  100. if (error) {
  101. throw error
  102. }
  103. if (INVOICES_COLLECTED_SUCCESS.length === 0) {
  104. throw new Error('No invoices collected')
  105. }
  106. resolve({
  107. INVOICES_COLLECTED,
  108. INVOICES_COLLECTED_SUCCESS,
  109. USERS_COLLECTED,
  110. })
  111. })
  112. })
  113. }
  114. if (require.main === module) {
  115. main()
  116. .then(() => {
  117. logger.error('Done.')
  118. process.exit(0)
  119. })
  120. .catch(err => {
  121. logger.error('Error', err)
  122. process.exit(1)
  123. })
  124. }
  125. module.exports = { main }