diff --git a/app-constants.js b/app-constants.js index 095a4d07..2c232275 100644 --- a/app-constants.js +++ b/app-constants.js @@ -97,6 +97,15 @@ const PaymentProcessingSwitch = { OFF: 'OFF' } +const PaymentSchedulerStatus = { + START_PROCESS: 'start-process', + CREATE_CHALLENGE: 'create-challenge', + ASSIGN_MEMBER: 'assign-member', + ACTIVATE_CHALLENGE: 'activate-challenge', + GET_USER_ID: 'get-userId', + CLOSE_CHALLENGE: 'close-challenge' +} + module.exports = { UserRoles, FullManagePermissionRoles, @@ -104,5 +113,6 @@ module.exports = { Interviews, ChallengeStatus, WorkPeriodPaymentStatus, + PaymentSchedulerStatus, PaymentProcessingSwitch } diff --git a/config/default.js b/config/default.js index 0e06f2d4..a3418181 100644 --- a/config/default.js +++ b/config/default.js @@ -202,11 +202,11 @@ module.exports = { PER_MINUTE_RESOURCE_REQUEST_MAX_COUNT: parseInt(process.env.PAYMENT_PROCESSING_PER_MINUTE_CHALLENGE_REQUEST_MAX_COUNT || 20), // the default step fix delay, unit: ms FIX_DELAY_STEP: parseInt(process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP || 500), - // the fix delay between step one and step two, unit: ms - FIX_DELAY_STEP_1_2: parseInt(process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP_1_2 || process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP || 500), - // the fix delay between step two and step three, unit: ms - FIX_DELAY_STEP_2_3: parseInt(process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP_2_3 || process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP || 500), - // the fix delay between step three and step four, unit: ms - FIX_DELAY_STEP_3_4: parseInt(process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP_3_4 || process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP || 500) + // the fix delay after step of create challenge, unit: ms + FIX_DELAY_STEP_CREATE_CHALLENGE: parseInt(process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP_CREATE_CHALLENGE || process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP || 500), + // the fix delay after step of assign member, unit: ms + FIX_DELAY_STEP_ASSIGN_MEMBER: parseInt(process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP_ASSIGN_MEMBER || process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP || 500), + // the fix delay after step of activate challenge, unit: ms + FIX_DELAY_STEP_ACTIVATE_CHALLENGE: parseInt(process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP_ACTIVATE_CHALLENGE || process.env.PAYMENT_PROCESSING_FIX_DELAY_STEP || 500) } } diff --git a/migrations/2021-05-29-create-payment-scheduler-table-add-status-details-to-payment.js b/migrations/2021-05-29-create-payment-scheduler-table-add-status-details-to-payment.js index 40c1596b..5eb2232d 100644 --- a/migrations/2021-05-29-create-payment-scheduler-table-add-status-details-to-payment.js +++ b/migrations/2021-05-29-create-payment-scheduler-table-add-status-details-to-payment.js @@ -2,6 +2,7 @@ const config = require('config') const _ = require('lodash') +const { PaymentSchedulerStatus } = require('../app-constants') /** * Create `payment_schedulers` table & relations. @@ -35,7 +36,7 @@ module.exports = { } }, step: { - type: Sequelize.INTEGER, + type: Sequelize.ENUM(_.values(PaymentSchedulerStatus)), allowNull: false }, status: { @@ -87,11 +88,13 @@ module.exports = { down: async (queryInterface, Sequelize) => { const table = { schema: config.DB_SCHEMA_NAME, tableName: 'payment_schedulers' } const statusTypeName = `${table.schema}.enum_${table.tableName}_status` + const stepTypeName = `${table.schema}.enum_${table.tableName}_step` const transaction = await queryInterface.sequelize.transaction() try { await queryInterface.dropTable(table, { transaction }) - // drop enum type for status column + // drop enum type for status and step column await queryInterface.sequelize.query(`DROP TYPE ${statusTypeName}`, { transaction }) + await queryInterface.sequelize.query(`DROP TYPE ${stepTypeName}`, { transaction }) await queryInterface.changeColumn({ tableName: 'work_period_payments', schema: config.DB_SCHEMA_NAME }, 'challenge_id', { type: Sequelize.UUID, allowNull: false }, diff --git a/src/models/PaymentScheduler.js b/src/models/PaymentScheduler.js index 7fd171aa..f8f64dfa 100644 --- a/src/models/PaymentScheduler.js +++ b/src/models/PaymentScheduler.js @@ -1,6 +1,8 @@ const { Sequelize, Model } = require('sequelize') const config = require('config') +const _ = require('lodash') const errors = require('../common/errors') +const { PaymentSchedulerStatus } = require('../../app-constants') module.exports = (sequelize) => { class PaymentScheduler extends Model { @@ -48,7 +50,7 @@ module.exports = (sequelize) => { allowNull: false }, step: { - type: Sequelize.INTEGER, + type: Sequelize.ENUM(_.values(PaymentSchedulerStatus)), allowNull: false }, status: { diff --git a/src/services/PaymentSchedulerService.js b/src/services/PaymentSchedulerService.js index 963f2e95..27774fb5 100644 --- a/src/services/PaymentSchedulerService.js +++ b/src/services/PaymentSchedulerService.js @@ -5,7 +5,7 @@ const models = require('../models') const { getV3MemberDetailsByHandle, getChallenge, getChallengeResource, sleep, postEvent } = require('../common/helper') const logger = require('../common/logger') const { createChallenge, addResourceToChallenge, activateChallenge, closeChallenge } = require('./PaymentService') -const { ChallengeStatus, PaymentProcessingSwitch } = require('../../app-constants') +const { ChallengeStatus, PaymentSchedulerStatus, PaymentProcessingSwitch } = require('../../app-constants') const WorkPeriodPayment = models.WorkPeriodPayment const WorkPeriod = models.WorkPeriod @@ -13,7 +13,7 @@ const PaymentScheduler = models.PaymentScheduler const { SWITCH, BATCH_SIZE, IN_PROGRESS_EXPIRED, MAX_RETRY_COUNT, RETRY_BASE_DELAY, RETRY_MAX_DELAY, PER_REQUEST_MAX_TIME, PER_PAYMENT_MAX_TIME, PER_MINUTE_PAYMENT_MAX_COUNT, PER_MINUTE_CHALLENGE_REQUEST_MAX_COUNT, PER_MINUTE_RESOURCE_REQUEST_MAX_COUNT, - FIX_DELAY_STEP_1_2, FIX_DELAY_STEP_2_3, FIX_DELAY_STEP_3_4 + FIX_DELAY_STEP_CREATE_CHALLENGE, FIX_DELAY_STEP_ASSIGN_MEMBER, FIX_DELAY_STEP_ACTIVATE_CHALLENGE } = config.PAYMENT_PROCESSING const processStatus = { perMin: { @@ -30,7 +30,6 @@ const processStatus = { paymentStartTime: 0, requestStartTime: 0 } -const stepEnum = ['start-process', 'create-challenge', 'assign-member', 'activate-challenge', 'get-userId', 'close-challenge'] const processResult = { SUCCESS: 'success', FAIL: 'fail', @@ -94,21 +93,21 @@ async function processPayment (workPeriodPayment) { await postEvent(config.TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC, updated.toJSON(), { oldValue }) } // Check whether the number of processed records per minute exceeds the specified number, if it exceeds, wait for the next minute before processing - await checkWait(stepEnum[0]) + await checkWait(PaymentSchedulerStatus.START_PROCESS) localLogger.info(`Processing workPeriodPayment ${workPeriodPayment.id}`, 'processPayment') const workPeriod = await WorkPeriod.findById(workPeriodPayment.workPeriodId) try { if (!paymentScheduler) { // 1. create challenge - const challengeId = await withRetry(createChallenge, [getCreateChallengeParam(workPeriod, workPeriodPayment)], validateError, stepEnum[1]) - paymentScheduler = await PaymentScheduler.create({ challengeId, step: 1, workPeriodPaymentId: workPeriodPayment.id, userHandle: workPeriod.userHandle, status: 'in-progress' }) + const challengeId = await withRetry(createChallenge, [getCreateChallengeParam(workPeriod, workPeriodPayment)], validateError, PaymentSchedulerStatus.CREATE_CHALLENGE) + paymentScheduler = await PaymentScheduler.create({ challengeId, step: PaymentSchedulerStatus.CREATE_CHALLENGE, workPeriodPaymentId: workPeriodPayment.id, userHandle: workPeriod.userHandle, status: 'in-progress' }) } else { // If the paymentScheduler already exists, it means that this is a record caused by an abnormal shutdown await setPaymentSchedulerStep(paymentScheduler) } // Start from unprocessed step, perform the process step by step - while (paymentScheduler.step < 5) { + while (paymentScheduler.step !== PaymentSchedulerStatus.CLOSE_CHALLENGE) { await processStep(paymentScheduler) } @@ -118,7 +117,7 @@ async function processPayment (workPeriodPayment) { // Update the modified status to es await postEvent(config.TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC, updated.toJSON(), { oldValue }) - await paymentScheduler.update({ step: 5, userId: paymentScheduler.userId, status: 'completed' }) + await paymentScheduler.update({ step: PaymentSchedulerStatus.CLOSE_CHALLENGE, userId: paymentScheduler.userId, status: 'completed' }) localLogger.info(`Processed workPeriodPayment ${workPeriodPayment.id} successfully`, 'processPayment') return processResult.SUCCESS @@ -132,7 +131,7 @@ async function processPayment (workPeriodPayment) { await postEvent(config.TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC, updated.toJSON(), { oldValue }) if (paymentScheduler) { - await paymentScheduler.update({ step: 5, userId: paymentScheduler.userId, status: 'failed' }) + await paymentScheduler.update({ step: PaymentSchedulerStatus.CLOSE_CHALLENGE, userId: paymentScheduler.userId, status: 'failed' }) } localLogger.error(`Processed workPeriodPayment ${workPeriodPayment.id} failed`, 'processPayment') return processResult.FAIL @@ -144,23 +143,23 @@ async function processPayment (workPeriodPayment) { * @param {Object} paymentScheduler the payment scheduler */ async function processStep (paymentScheduler) { - if (paymentScheduler.step === 1) { + if (paymentScheduler.step === PaymentSchedulerStatus.CREATE_CHALLENGE) { // 2. assign member to the challenge - await withRetry(addResourceToChallenge, [paymentScheduler.challengeId, paymentScheduler.userHandle], validateError, stepEnum[2]) - paymentScheduler.step = 2 - } else if (paymentScheduler.step === 2) { + await withRetry(addResourceToChallenge, [paymentScheduler.challengeId, paymentScheduler.userHandle], validateError, PaymentSchedulerStatus.ASSIGN_MEMBER) + paymentScheduler.step = PaymentSchedulerStatus.ASSIGN_MEMBER + } else if (paymentScheduler.step === PaymentSchedulerStatus.ASSIGN_MEMBER) { // 3. active the challenge - await withRetry(activateChallenge, [paymentScheduler.challengeId], validateError, stepEnum[3]) - paymentScheduler.step = 3 - } else if (paymentScheduler.step === 3) { + await withRetry(activateChallenge, [paymentScheduler.challengeId], validateError, PaymentSchedulerStatus.ACTIVATE_CHALLENGE) + paymentScheduler.step = PaymentSchedulerStatus.ACTIVATE_CHALLENGE + } else if (paymentScheduler.step === PaymentSchedulerStatus.ACTIVATE_CHALLENGE) { // 4.1. get user id - const { userId } = await withRetry(getV3MemberDetailsByHandle, [paymentScheduler.userHandle], validateError, stepEnum[4]) + const { userId } = await withRetry(getV3MemberDetailsByHandle, [paymentScheduler.userHandle], validateError, PaymentSchedulerStatus.GET_USER_ID) paymentScheduler.userId = userId - paymentScheduler.step = 4 - } else if (paymentScheduler.step === 4) { + paymentScheduler.step = PaymentSchedulerStatus.GET_USER_ID + } else if (paymentScheduler.step === PaymentSchedulerStatus.GET_USER_ID) { // 4.2. close the challenge - await withRetry(closeChallenge, [paymentScheduler.challengeId, paymentScheduler.userId, paymentScheduler.userHandle], validateError, stepEnum[5]) - paymentScheduler.step = 5 + await withRetry(closeChallenge, [paymentScheduler.challengeId, paymentScheduler.userId, paymentScheduler.userHandle], validateError, PaymentSchedulerStatus.CLOSE_CHALLENGE) + paymentScheduler.step = PaymentSchedulerStatus.CLOSE_CHALLENGE } } @@ -171,17 +170,17 @@ async function processStep (paymentScheduler) { async function setPaymentSchedulerStep (paymentScheduler) { const challenge = await getChallenge(paymentScheduler.challengeId) if (SWITCH === PaymentProcessingSwitch.OFF) { - paymentScheduler.step = 5 + paymentScheduler.step = PaymentSchedulerStatus.CLOSE_CHALLENGE } else if (challenge.status === ChallengeStatus.COMPLETED) { - paymentScheduler.step = 5 + paymentScheduler.step = PaymentSchedulerStatus.CLOSE_CHALLENGE } else if (challenge.status === ChallengeStatus.ACTIVE) { - paymentScheduler.step = 3 + paymentScheduler.step = PaymentSchedulerStatus.ACTIVATE_CHALLENGE } else { const resource = await getChallengeResource(paymentScheduler.challengeId, paymentScheduler.userHandle, config.ROLE_ID_SUBMITTER) if (resource) { - paymentScheduler.step = 2 + paymentScheduler.step = PaymentSchedulerStatus.ASSIGN_MEMBER } else { - paymentScheduler.step = 1 + paymentScheduler.step = PaymentSchedulerStatus.CREATE_CHALLENGE } } // The main purpose is updating the updatedAt of payment scheduler to avoid simultaneous processing @@ -213,26 +212,26 @@ function getCreateChallengeParam (workPeriod, workPeriodPayment) { async function checkWait (step, tryCount) { // When calculating the retry time later, we need to subtract the time that has been waited before let lapse = 0 - if (step === stepEnum[0]) { + if (step === PaymentSchedulerStatus.START_PROCESS) { lapse += await checkPerMinThreshold('paymentsProcessed') - } else if (step === stepEnum[1]) { + } else if (step === PaymentSchedulerStatus.CREATE_CHALLENGE) { await checkPerMinThreshold('challengeRequested') - } else if (step === stepEnum[2]) { + } else if (step === PaymentSchedulerStatus.ASSIGN_MEMBER) { // Only when tryCount = 0, it comes from the previous step, and it is necessary to wait for a fixed time - if (FIX_DELAY_STEP_1_2 > 0 && tryCount === 0) { - await sleep(FIX_DELAY_STEP_1_2) + if (FIX_DELAY_STEP_CREATE_CHALLENGE > 0 && tryCount === 0) { + await sleep(FIX_DELAY_STEP_CREATE_CHALLENGE) } lapse += await checkPerMinThreshold('resourceRequested') - } else if (step === stepEnum[3]) { + } else if (step === PaymentSchedulerStatus.ACTIVATE_CHALLENGE) { // Only when tryCount = 0, it comes from the previous step, and it is necessary to wait for a fixed time - if (FIX_DELAY_STEP_2_3 > 0 && tryCount === 0) { - await sleep(FIX_DELAY_STEP_2_3) + if (FIX_DELAY_STEP_ASSIGN_MEMBER > 0 && tryCount === 0) { + await sleep(FIX_DELAY_STEP_ASSIGN_MEMBER) } lapse += await checkPerMinThreshold('challengeRequested') - } else if (step === stepEnum[5]) { + } else if (step === PaymentSchedulerStatus.CLOSE_CHALLENGE) { // Only when tryCount = 0, it comes from the previous step, and it is necessary to wait for a fixed time - if (FIX_DELAY_STEP_3_4 > 0 && tryCount === 0) { - await sleep(FIX_DELAY_STEP_3_4) + if (FIX_DELAY_STEP_ACTIVATE_CHALLENGE > 0 && tryCount === 0) { + await sleep(FIX_DELAY_STEP_ACTIVATE_CHALLENGE) } lapse += await checkPerMinThreshold('challengeRequested') } @@ -305,9 +304,9 @@ async function withRetry (func, argArr, predictFunc, step) { if (SWITCH === PaymentProcessingSwitch.OFF) { // without actual API calls by adding delay (for example 1 second for each step), to simulate the act sleep(1000) - if (step === stepEnum[1]) { + if (step === PaymentSchedulerStatus.CREATE_CHALLENGE) { return '00000000-0000-0000-0000-000000000000' - } else if (step === stepEnum[4]) { + } else if (step === PaymentSchedulerStatus.GET_USER_ID) { return { userId: 100001 } } return