From 75b2ada03f1a6b0a73988578ac3aef6f886454ac Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 31 May 2024 06:48:44 +0800 Subject: [PATCH 1/7] Add command for processing late pending paymongo transactions #801 --- ...ProcessLatePaymongoTransactionsCommand.php | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/Command/ProcessLatePaymongoTransactionsCommand.php diff --git a/src/Command/ProcessLatePaymongoTransactionsCommand.php b/src/Command/ProcessLatePaymongoTransactionsCommand.php new file mode 100644 index 00000000..6f65dde1 --- /dev/null +++ b/src/Command/ProcessLatePaymongoTransactionsCommand.php @@ -0,0 +1,101 @@ +em = $em; + $this->paymongo = $paymongo; + $this->insurance = $insurance; + + parent::__construct(); + } + + protected function configure() + { + $this->setName('paymongo:checkpending') + ->setDescription('Check for any late PayMongo transactions and process if needed.') + ->setHelp('Check for any late PayMongo transactions and process if needed.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Fetching all late pending transactions...'); + + // set date threshold to 24 hours ago + $date_threshold = (new DateTime())->modify('-24 hours'); + + $transactions = $this->em->getRepository(GatewayTransaction::class) + ->createQueryBuilder('t') + ->select('t') + ->where('t.status = :status') + ->andWhere('t.date_create <= :date_threshold') + ->setParameter('status', TransactionStatus::PENDING) + ->setParameter('date_threshold', $date_threshold) + ->getQuery() + ->getResult(); + + $output->writeln('Found '. count($transactions) . ' rows matching criteria.'); + + $x = 0; + + foreach ($transactions as $trans) { + // check paymongo status + $checkout = $this->paymongo->getCheckout($trans->getExtTransactionId()); + + if ($checkout['success']) { + // check if we have any payments made + $payments = $checkout['response']['data']['attributes']['payments'] ?? []; + + if (!empty($payments)) { + $amount_paid = 0; + + // for good measure, we get all successful payments and add them up + foreach ($payments as $payment) { + if ($payment['attributes']['status'] === TransactionStatus::PAID) { + $amount_paid = bcadd($amount_paid, $payment['attributes']['amount']); + } + } + + // this transaction is fully paid, so we mark it as paid + if (bccomp($trans->getAmount(), $amount_paid) <= 0) { + $trans->setStatus(TransactionStatus::PAID); + $trans->setDatePay(new DateTime()); + $this->em->flush(); + + $output->writeln('Marked transaction '. $trans->getID() .'as paid.'); + $x++; + } + } else { + $output->writeln('No payments found for transaction: ' . $trans->getID() . ''); + } + } else { + $output->writeln('Checkout not found: ' . $checkout['error']['message'] . ''); + } + } + + $output->writeln('Done! Processed ' . $x . ' rows.'); + + return 0; + } +} From ad841a7e258f4b4813a3cef2360a45508d12e2ec Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 31 May 2024 06:49:55 +0800 Subject: [PATCH 2/7] Prevent success handler from catching failed paymongo payment attempts #801 --- src/Controller/PayMongoController.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Controller/PayMongoController.php b/src/Controller/PayMongoController.php index 21cd864c..af698acd 100644 --- a/src/Controller/PayMongoController.php +++ b/src/Controller/PayMongoController.php @@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use DateTime; + class PayMongoController extends Controller { protected $pm; @@ -45,10 +47,8 @@ class PayMongoController extends Controller switch ($event_name) { case "payment.paid": return $this->handlePaymentPaid($event); - break; case "payment.failed": - return $this->handlePaymentPaid($event); - break; + return $this->handlePaymentFailed($event); case "payment.refunded": // TODO: handle refunds case "payment.refund.updated": case "checkout_session.payment.paid": @@ -69,6 +69,7 @@ class PayMongoController extends Controller if (!empty($obj)) { // mark as paid $obj->setStatus(TransactionStatus::PAID); + $obj->setDatePay(new DateTime()); $this->em->flush(); } @@ -77,7 +78,7 @@ class PayMongoController extends Controller ]); } - protected function handlePaymentFailed(Request $req) + protected function handlePaymentFailed($event) { // TODO: do something about failed payments? return $this->json([ From 952122a39e06948a8871d26d32536784937d668e Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 31 May 2024 06:50:34 +0800 Subject: [PATCH 3/7] Prevent insurance applications from being flagged as paid more than once #801 --- src/EntityListener/GatewayTransactionListener.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/EntityListener/GatewayTransactionListener.php b/src/EntityListener/GatewayTransactionListener.php index c9007aaf..0dd6966d 100644 --- a/src/EntityListener/GatewayTransactionListener.php +++ b/src/EntityListener/GatewayTransactionListener.php @@ -58,17 +58,20 @@ class GatewayTransactionListener 'gateway_transaction' => $gt_obj, ]); - if (!empty($obj)) { + // make sure the object exists and has not been processed yet + if (!empty($obj) && $obj->getStatus === InsuranceApplicationStatus::CREATED) { // mark as paid $obj->setDatePay(new DateTime()); $obj->setStatus(InsuranceApplicationStatus::PAID); $this->em->flush(); - } - // flag on api as paid - $result = $this->ic->tagApplicationPaid($obj->getExtTransactionId()); - if (!$result['success'] || $result['response']['transaction_code'] !== 'GR004') { - error_log("INSURANCE MARK AS PAID FAILED FOR " . $obj->getID() . ": " . $result['error']['message']); + // flag on api as paid + $result = $this->ic->tagApplicationPaid($obj->getExtTransactionId()); + + // something went wrong with insurance api + if (!$result['success'] || $result['response']['transaction_code'] !== 'GR004') { + error_log("INSURANCE MARK AS PAID FAILED FOR " . $obj->getID() . ": " . $result['error']['message']); + } } } } From e97cebd2b214e7eff7430401dcb210aeb7b14579 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 31 May 2024 12:11:13 +0800 Subject: [PATCH 4/7] Fix typo in log message for new command #801 --- src/Command/ProcessLatePaymongoTransactionsCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/ProcessLatePaymongoTransactionsCommand.php b/src/Command/ProcessLatePaymongoTransactionsCommand.php index 6f65dde1..c09545e5 100644 --- a/src/Command/ProcessLatePaymongoTransactionsCommand.php +++ b/src/Command/ProcessLatePaymongoTransactionsCommand.php @@ -83,7 +83,7 @@ class ProcessLatePaymongoTransactionsCommand extends Command $trans->setDatePay(new DateTime()); $this->em->flush(); - $output->writeln('Marked transaction '. $trans->getID() .'as paid.'); + $output->writeln('Marked transaction '. $trans->getID() . ' as paid.'); $x++; } } else { From 191a02f4c43fc45bf952b5376f3c8352aa391a1f Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 31 May 2024 12:28:43 +0800 Subject: [PATCH 5/7] Fix typo on getStatus call on entity listener #801 --- src/Command/ProcessLatePaymongoTransactionsCommand.php | 2 ++ src/EntityListener/GatewayTransactionListener.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Command/ProcessLatePaymongoTransactionsCommand.php b/src/Command/ProcessLatePaymongoTransactionsCommand.php index c09545e5..5397a7a2 100644 --- a/src/Command/ProcessLatePaymongoTransactionsCommand.php +++ b/src/Command/ProcessLatePaymongoTransactionsCommand.php @@ -85,6 +85,8 @@ class ProcessLatePaymongoTransactionsCommand extends Command $output->writeln('Marked transaction '. $trans->getID() . ' as paid.'); $x++; + } else { + $output->writeln('Insufficient payment amount (' . $amount_paid . '/' . $trans->getAmount() . ') for this transaction: ' . $trans->getID() . ''); } } else { $output->writeln('No payments found for transaction: ' . $trans->getID() . ''); diff --git a/src/EntityListener/GatewayTransactionListener.php b/src/EntityListener/GatewayTransactionListener.php index 0dd6966d..530f0093 100644 --- a/src/EntityListener/GatewayTransactionListener.php +++ b/src/EntityListener/GatewayTransactionListener.php @@ -59,7 +59,7 @@ class GatewayTransactionListener ]); // make sure the object exists and has not been processed yet - if (!empty($obj) && $obj->getStatus === InsuranceApplicationStatus::CREATED) { + if (!empty($obj) && $obj->getStatus() === InsuranceApplicationStatus::CREATED) { // mark as paid $obj->setDatePay(new DateTime()); $obj->setStatus(InsuranceApplicationStatus::PAID); From 3846ad5a43568cea5ee18bcc313c88b542af6e6b Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 31 May 2024 18:45:16 +0800 Subject: [PATCH 6/7] Add checker for paymongo webhook status before running manual processing command #801 --- config/services.yaml | 6 +++++ ...ProcessLatePaymongoTransactionsCommand.php | 26 ++++++++++++++----- src/Service/PayMongoConnector.php | 5 ++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/config/services.yaml b/config/services.yaml index 00085811..12c32894 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -109,6 +109,12 @@ services: arguments: $callback_url: "%env(WARRANTY_SERIAL_CALLBACK_URL)%" + App\Command\ProcessLatePaymongoTransactionsCommand: + arguments: + $em: "@doctrine.orm.entity_manager" + $paymongo: "@App\\Service\\PayMongoConnector" + $webhook_id: "%env(PAYMONGO_WEBHOOK_ID)%" + # rider tracker service App\Service\RiderTracker: arguments: diff --git a/src/Command/ProcessLatePaymongoTransactionsCommand.php b/src/Command/ProcessLatePaymongoTransactionsCommand.php index 5397a7a2..2c86d3ab 100644 --- a/src/Command/ProcessLatePaymongoTransactionsCommand.php +++ b/src/Command/ProcessLatePaymongoTransactionsCommand.php @@ -10,8 +10,6 @@ use Doctrine\ORM\EntityManagerInterface; use App\Ramcar\TransactionStatus; use App\Entity\GatewayTransaction; - -use App\Service\InsuranceConnector; use App\Service\PayMongoConnector; use DateTime; @@ -20,13 +18,14 @@ class ProcessLatePaymongoTransactionsCommand extends Command { protected $em; protected $paymongo; - protected $insurance; - public function __construct(EntityManagerInterface $em, PayMongoConnector $paymongo, InsuranceConnector $insurance) + protected $webhook_id; + + public function __construct(EntityManagerInterface $em, PayMongoConnector $paymongo, $webhook_id) { $this->em = $em; $this->paymongo = $paymongo; - $this->insurance = $insurance; + $this->webhook_id = $webhook_id; parent::__construct(); } @@ -40,6 +39,21 @@ class ProcessLatePaymongoTransactionsCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { + $output->writeln('Checking webhook status...'); + + // check if webhook is disabled + $webhook = $this->paymongo->getWebhook($this->webhook_id); + + if ($webhook['success'] && $webhook['response']['data']['attributes']['status'] === 'enabled') { + $output->writeln('Webhook is enabled, no need to do anything.'); + + return 0; + } else { + $output->writeln('Webhook is disabled! Logging event and proceeding...'); + + $this->paymongo->log('WEBHOOK', "[]", json_encode($webhook['response'], JSON_PRETTY_PRINT), 'webhook'); + } + $output->writeln('Fetching all late pending transactions...'); // set date threshold to 24 hours ago @@ -96,7 +110,7 @@ class ProcessLatePaymongoTransactionsCommand extends Command } } - $output->writeln('Done! Processed ' . $x . ' rows.'); + $output->writeln('Done! Processed ' . $x . ' rows.'); return 0; } diff --git a/src/Service/PayMongoConnector.php b/src/Service/PayMongoConnector.php index c34a94e7..25f380ca 100644 --- a/src/Service/PayMongoConnector.php +++ b/src/Service/PayMongoConnector.php @@ -74,6 +74,11 @@ class PayMongoConnector return $this->doRequest('/v1/checkout_sessions/' . $checkout_id, 'GET'); } + public function getWebhook($id) + { + return $this->doRequest('/v1/webhooks/'. $id, 'GET'); + } + protected function generateHash() { return base64_encode($this->secret_key); From 08084f682c32df9d90f74a8ad88ff801a8a466f4 Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Fri, 31 May 2024 18:51:55 +0800 Subject: [PATCH 7/7] Add force option to enable or disable webhook status checking, defaults to false #801 --- ...ProcessLatePaymongoTransactionsCommand.php | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Command/ProcessLatePaymongoTransactionsCommand.php b/src/Command/ProcessLatePaymongoTransactionsCommand.php index 2c86d3ab..f3bc0312 100644 --- a/src/Command/ProcessLatePaymongoTransactionsCommand.php +++ b/src/Command/ProcessLatePaymongoTransactionsCommand.php @@ -5,6 +5,7 @@ namespace App\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; use Doctrine\ORM\EntityManagerInterface; @@ -34,24 +35,30 @@ class ProcessLatePaymongoTransactionsCommand extends Command { $this->setName('paymongo:checkpending') ->setDescription('Check for any late PayMongo transactions and process if needed.') - ->setHelp('Check for any late PayMongo transactions and process if needed.'); + ->setHelp('Check for any late PayMongo transactions and process if needed.') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Ignore webhook status and process anyway.'); } protected function execute(InputInterface $input, OutputInterface $output) { - $output->writeln('Checking webhook status...'); + $force = $input->getOption('force'); - // check if webhook is disabled - $webhook = $this->paymongo->getWebhook($this->webhook_id); + // if we aren't forcing, check webhook status first + if (!$force) { + $output->writeln('Checking webhook status...'); - if ($webhook['success'] && $webhook['response']['data']['attributes']['status'] === 'enabled') { - $output->writeln('Webhook is enabled, no need to do anything.'); + // check if webhook is disabled + $webhook = $this->paymongo->getWebhook($this->webhook_id); - return 0; - } else { - $output->writeln('Webhook is disabled! Logging event and proceeding...'); + if ($webhook['success'] && $webhook['response']['data']['attributes']['status'] === 'enabled') { + $output->writeln('Webhook is enabled, no need to do anything.'); - $this->paymongo->log('WEBHOOK', "[]", json_encode($webhook['response'], JSON_PRETTY_PRINT), 'webhook'); + return 0; + } else { + $output->writeln('Webhook is disabled! Logging event and proceeding...'); + + $this->paymongo->log('WEBHOOK', "[]", json_encode($webhook['response'], JSON_PRETTY_PRINT), 'webhook'); + } } $output->writeln('Fetching all late pending transactions...');