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 new file mode 100644 index 00000000..f3bc0312 --- /dev/null +++ b/src/Command/ProcessLatePaymongoTransactionsCommand.php @@ -0,0 +1,124 @@ +em = $em; + $this->paymongo = $paymongo; + $this->webhook_id = $webhook_id; + + 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.') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Ignore webhook status and process anyway.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $force = $input->getOption('force'); + + // if we aren't forcing, check webhook status first + if (!$force) { + $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 + $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('Insufficient payment amount (' . $amount_paid . '/' . $trans->getAmount() . ') for this transaction: ' . $trans->getID() . ''); + } + } 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; + } +} diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 694ffdd0..bb9c8546 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -413,7 +413,9 @@ class RiderAppController extends ApiController return new APIResponse(false, $msg); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // TODO: refactor this into a jo handler class, so we don't have to repeat for control center @@ -466,7 +468,9 @@ class RiderAppController extends ApiController return new APIResponse(true, $msg); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // requeue it, instead of cancelling it $jo->requeue(); @@ -527,7 +531,9 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB); @@ -570,7 +576,9 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_PRE_JO); @@ -613,7 +621,9 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_PRE_JO); @@ -656,7 +666,9 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_START); @@ -700,7 +712,9 @@ class RiderAppController extends ApiController $jo->setStatus(JOStatus::IN_PROGRESS); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE); @@ -761,7 +775,9 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB); @@ -854,7 +870,9 @@ class RiderAppController extends ApiController return new APIResponse(false, $msg); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // need to check if service type is battery sales // if so, serial is a required parameter @@ -1002,7 +1020,9 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_POST_JO); @@ -1046,7 +1066,9 @@ class RiderAppController extends ApiController $jo = $rider->getCurrentJobOrder(); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // set delivery status $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_POST_JO); @@ -1265,7 +1287,9 @@ class RiderAppController extends ApiController $jo = $em->getRepository(JobOrder::class)->find($jo_id); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // check if we have trade in items $ti_items = []; @@ -1376,7 +1400,9 @@ class RiderAppController extends ApiController return new APIResponse(false, $msg); // check if JO can be modified first - $this->checkJOProgressionAllowed($jo, $rider); + if (!$this->checkJOProgressionAllowed($em, $jo, $rider)) { + return new APIResponse(false, 'Job order can no longer be modified.'); + } // check service type $stype_id = $req->request->get('stype_id'); @@ -1740,22 +1766,40 @@ class RiderAppController extends ApiController return $msg; } - protected function checkJOProgressionAllowed(JobOrder $jo, $rider) + protected function checkJOProgressionAllowed(EntityManagerInterface $em, JobOrder $jo, &$rider) { + $allowed = true; + + error_log("JO delivery status is " . $jo->getDeliveryStatus() . " (not allowed: " . DeliveryStatus::CANCELLED . ")"); + error_log("JO status is " . $jo->getStatus() . " (not allowed: " . JOStatus::CANCELLED . ")"); + // TODO: add more statuses to block if needed, hence. this is a failsafe in case MQTT is not working. + // check delivery status + switch ($jo->getDeliveryStatus()) + { + case DeliveryStatus::CANCELLED: + $allowed = false; + break; + } + + // check JO status as well switch ($jo->getStatus()) { case JOStatus::CANCELLED: - // if this is the rider's current JO, set to null - if ($rider->getCurrentJobOrder() === $jo) { - $rider->setCurrentJobOrder(); - } - - return new APIResponse(false, 'Job order can no longer be modified.'); + $allowed = false; break; - default: - return true; } + + // if this is the rider's current JO, set to null + if (!$allowed) { + if ($rider->getCurrentJobOrder() === $jo) { + $rider->setCurrentJobOrder(); + $em->persist($rider); + $em->flush(); + } + } + + return $allowed; } protected function debugRequest(Request $req) 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([ diff --git a/src/EntityListener/GatewayTransactionListener.php b/src/EntityListener/GatewayTransactionListener.php index c9007aaf..530f0093 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']); + } } } } 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);