From 1a522cc1b668dd630874fe9da793e310230ca310 Mon Sep 17 00:00:00 2001 From: Orkhan Huseynli Date: Wed, 1 Feb 2023 12:38:59 +0400 Subject: [PATCH] DASH-150 add payments job to check payment statuses from stripe and update order status --- package.json | 2 +- .../src/external/payment-processor/stripe.mjs | 8 +- server/src/jobs/emails.mjs | 4 - server/src/jobs/payments.mjs | 75 +++++++++++++++++++ server/src/jobs/tx-check.mjs | 4 - server/src/jobs/wallet-data.mjs | 4 - server/src/jobs/wrap-status.mjs | 4 - ...0230124150753-create-fio-api-urls-table.js | 1 + server/src/scheduler.mjs | 8 ++ 9 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 server/src/jobs/payments.mjs diff --git a/package.json b/package.json index 455f00fa1..c02224448 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "lint": "eslint ./server/src ./client/src --ext js,mjs,ts,tsx", "lint:fix": "eslint ./server/src ./client/src --ext js,mjs,ts,tsx --fix", "server:prod": "npm run migrate && node --es-module-specifier-resolution=node server/src/index.mjs", - "scheduler": "JOB_LIST=emails,orders,txCheck,wrapStatus node --es-module-specifier-resolution=node server/src/scheduler.mjs", + "scheduler": "JOB_LIST=emails,orders,txCheck,wrapStatus,payments node --es-module-specifier-resolution=node server/src/scheduler.mjs", "scheduler:wallet-data": "JOB_LIST=walletData node --es-module-specifier-resolution=node server/src/scheduler.mjs", "scheduler:wrap-status": "JOB_LIST=wrapStatus node --es-module-specifier-resolution=node server/src/scheduler.mjs", "migrate": "sequelize db:migrate", diff --git a/server/src/external/payment-processor/stripe.mjs b/server/src/external/payment-processor/stripe.mjs index d086a592f..30e62d086 100644 --- a/server/src/external/payment-processor/stripe.mjs +++ b/server/src/external/payment-processor/stripe.mjs @@ -30,10 +30,6 @@ const STRIPE_USER_AGENT = 'Stripe/1.0'; const stripe = new StripeLib(process.env.STRIPE_SECRET); class Stripe extends PaymentProcessor { - constructor() { - super(); - } - isWebhook(hostname, userAgent) { const checkRegex = new RegExp(`${STRIPE_USER_AGENT}`, 'i'); return checkRegex.exec(userAgent); @@ -239,6 +235,10 @@ class Stripe extends PaymentProcessor { return stripe.refunds.create(refundOptions); } + + async retrieveIntent(id) { + return await stripe.paymentIntents.retrieve(id); + } } export default new Stripe(); diff --git a/server/src/jobs/emails.mjs b/server/src/jobs/emails.mjs index 45fabe959..ec62aff85 100644 --- a/server/src/jobs/emails.mjs +++ b/server/src/jobs/emails.mjs @@ -26,10 +26,6 @@ const CONTENT_TYPE_EMAIL_TEMPLATE_MAP = { const OPT_IN_STATUS_SYNCED = 'OPT_IN_STATUS_SYNCED'; class EmailsJob extends CommonJob { - constructor() { - super(); - } - async allowToSendBalanceChangedEmail(notification) { const { id, userId, createdAt, data } = notification; diff --git a/server/src/jobs/payments.mjs b/server/src/jobs/payments.mjs new file mode 100644 index 000000000..de3f5c7bf --- /dev/null +++ b/server/src/jobs/payments.mjs @@ -0,0 +1,75 @@ +import CommonJob from './job.mjs'; + +import Stripe from '../external/payment-processor/stripe.mjs'; + +import logger from '../logger.mjs'; + +import { Order, Payment, OrderItem, OrderItemStatus } from '../models'; + +// https://stripe.com/docs/payments/intents#intent-statuses +const STRIPE_INTENT_SUCCESS = 'succeeded'; + +class PaymentsJob extends CommonJob { + async execute() { + const orders = await Order.findAll({ + where: { + status: Order.STATUS.PAYMENT_PENDING, + }, + include: [OrderItem, Payment], + }); + + const t = await Order.sequelize.transaction(); + + try { + for (const order of orders) { + const payments = await order.getPayments({ + where: { processor: Payment.PROCESSOR.STRIPE }, + }); + const paymentStatuses = []; + for (const payment of payments) { + const orderItemStatus = await OrderItemStatus.findOne({ + where: { + paymentId: payment.id, + }, + transaction: t, + }); + + const intentId = payment.externalId; + const intent = await Stripe.retrieveIntent(intentId); + paymentStatuses.push(intent.status); + if (intent.status === STRIPE_INTENT_SUCCESS) { + await payment.update( + { status: Payment.STATUS.COMPLETED }, + { transaction: t }, + ); + await orderItemStatus.update( + { paymentStatus: Payment.STATUS.COMPLETED }, + { transaction: t }, + ); + } else { + await payment.update({ status: Payment.STATUS.FAILED }, { transaction: t }); + await orderItemStatus.update( + { paymentStatus: Payment.STATUS.FAILED }, + { transaction: t }, + ); + } + } + + const allSucceeded = paymentStatuses.some( + status => status === STRIPE_INTENT_SUCCESS, + ); + if (allSucceeded) { + await order.update({ status: Order.STATUS.SUCCESS }, { transaction: t }); + } else { + await order.update({ status: Order.STATUS.FAILED }, { transaction: t }); + } + } + await t.commit(); + } catch (error) { + logger.error(`Update transaction failed error: ${error.message}`); + await t.rollback(); + } + } +} + +new PaymentsJob().execute(); diff --git a/server/src/jobs/tx-check.mjs b/server/src/jobs/tx-check.mjs index 3068fcef0..193a9e3f5 100644 --- a/server/src/jobs/tx-check.mjs +++ b/server/src/jobs/tx-check.mjs @@ -17,10 +17,6 @@ import logger from '../logger.mjs'; const MAX_CHECK_TIMES = 10; class TxCheckJob extends CommonJob { - constructor() { - super(); - } - async execute() { await fioApi.getRawAbi(); diff --git a/server/src/jobs/wallet-data.mjs b/server/src/jobs/wallet-data.mjs index 46560e6a0..89e32b1c7 100644 --- a/server/src/jobs/wallet-data.mjs +++ b/server/src/jobs/wallet-data.mjs @@ -44,10 +44,6 @@ const returnDayRange = timePeriod => { }; class WalletDataJob extends CommonJob { - constructor() { - super(); - } - logFioError(e, wallet, action = '-') { if (e && e.errorCode !== 404) { if (wallet && wallet.id) diff --git a/server/src/jobs/wrap-status.mjs b/server/src/jobs/wrap-status.mjs index 6184b2920..52838fcdc 100644 --- a/server/src/jobs/wrap-status.mjs +++ b/server/src/jobs/wrap-status.mjs @@ -34,10 +34,6 @@ const WRAPPED_TOKEN_ABI = JSON.parse( const UNWRAP_RETRIES_LIMIT = 3; class WrapStatusJob extends CommonJob { - constructor() { - super(); - } - handleErrorMessage(message) { // eslint-disable-next-line no-console console.log(message); diff --git a/server/src/migrations/20230124150753-create-fio-api-urls-table.js b/server/src/migrations/20230124150753-create-fio-api-urls-table.js index 2372c117b..422e49665 100644 --- a/server/src/migrations/20230124150753-create-fio-api-urls-table.js +++ b/server/src/migrations/20230124150753-create-fio-api-urls-table.js @@ -14,6 +14,7 @@ module.exports = { }, createdAt: { type: DT.DATE }, updatedAt: { type: DT.DATE }, + deletedAt: { type: DT.DATE }, }); return QI.sequelize.query( diff --git a/server/src/scheduler.mjs b/server/src/scheduler.mjs index c40e01d6a..5d696b484 100644 --- a/server/src/scheduler.mjs +++ b/server/src/scheduler.mjs @@ -47,6 +47,14 @@ const availableJobsParams = { closeWorkerAfterMs: parseInt(process.env.WRAP_STATUS_JOB_CLOSE_TIMEOUT) || 60 * 60 * 1000, // 60 min }, + payments: { + path: path.join(JOBS_PATH, 'payments.mjs'), + name: 'payments', + interval: process.env.PAYMENTS_JOB_INTERVAL || 30 * 1000, // 30 sec + timeout: 0, + closeWorkerAfterMs: + parseInt(process.env.PAYMENTS_JOB_CLOSE_TIMEOUT) || 60 * 60 * 1000, // 60 min + }, }; const jobsToLaunch = process.env.JOB_LIST;