<?php

/**
 * @file classes/payment/ojs/OJSPaymentManager.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2003-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class OJSPaymentManager
 *
 * @ingroup payment
 *
 * @see QueuedPayment
 *
 * @brief Provides payment management functions.
 *
 */

namespace APP\payment\ojs;

use APP\core\Application;
use APP\core\Request;
use APP\facades\Repo;
use APP\mail\mailables\SubscriptionPurchaseIndividual;
use APP\mail\mailables\SubscriptionPurchaseInstitutional;
use APP\mail\mailables\SubscriptionRenewIndividual;
use APP\mail\mailables\SubscriptionRenewInstitutional;
use APP\subscription\IndividualSubscriptionDAO;
use APP\subscription\InstitutionalSubscriptionDAO;
use APP\subscription\Subscription;
use APP\subscription\SubscriptionAction;
use APP\subscription\SubscriptionTypeDAO;
use PKP\db\DAORegistry;
use PKP\payment\CompletedPayment;
use PKP\payment\PaymentManager;
use PKP\payment\QueuedPayment;
use PKP\payment\QueuedPaymentDAO;
use PKP\plugins\PaymethodPlugin;
use PKP\plugins\PluginRegistry;

class OJSPaymentManager extends PaymentManager
{
    public const PAYMENT_TYPE_MEMBERSHIP = 1;
    public const PAYMENT_TYPE_RENEW_SUBSCRIPTION = 2;
    public const PAYMENT_TYPE_PURCHASE_ARTICLE = 3;
    public const PAYMENT_TYPE_DONATION = 4;
    public const PAYMENT_TYPE_SUBMISSION = 5;
    public const PAYMENT_TYPE_FASTTRACK = 6;
    // public const PAYMENT_TYPE_PUBLICATION = 7; // FIXME: Defined in PaymentManager (used in pkp-lib); should be moved here
    public const PAYMENT_TYPE_PURCHASE_SUBSCRIPTION = 8;
    public const PAYMENT_TYPE_PURCHASE_ISSUE = 9;

    /**
     * Determine whether the payment system is configured.
     *
     * @return bool true iff configured
     */
    public function isConfigured()
    {
        return parent::isConfigured() && $this->_context->getData('paymentsEnabled');
    }

    /**
     * Create a queued payment.
     *
     * @param Request $request
     * @param int $type OJSPaymentManager::PAYMENT_TYPE_...
     * @param int $userId ID of user responsible for payment
     * @param ?int $assocId ID of associated entity
     * @param float $amount Amount of currency $currencyCode
     * @param string $currencyCode optional ISO 4217 currency code
     *
     * @return QueuedPayment
     */
    public function createQueuedPayment($request, $type, $userId, $assocId, $amount, $currencyCode = null)
    {
        if (is_null($currencyCode)) {
            $currencyCode = $this->_context->getData('currency');
        }
        $payment = new QueuedPayment($amount, $currencyCode, $userId, $assocId);
        $payment->setContextId($this->_context->getId());
        $payment->setType($type);
        $router = $request->getRouter();
        $dispatcher = $router->getDispatcher();

        switch ($type) {
            case self::PAYMENT_TYPE_PURCHASE_ARTICLE:
                $payment->setRequestUrl($dispatcher->url($request, Application::ROUTE_PAGE, null, 'article', 'view', $assocId));
                break;
            case self::PAYMENT_TYPE_PURCHASE_ISSUE:
                $payment->setRequestUrl($dispatcher->url($request, Application::ROUTE_PAGE, null, 'issue', 'view', $assocId));
                break;
            case self::PAYMENT_TYPE_PURCHASE_SUBSCRIPTION:
                $payment->setRequestUrl($dispatcher->url($request, Application::ROUTE_PAGE, null, 'issue', 'current'));
                break;
            case self::PAYMENT_TYPE_RENEW_SUBSCRIPTION:
                $payment->setRequestUrl($dispatcher->url($request, Application::ROUTE_PAGE, null, 'user', 'subscriptions'));
                break;
            case self::PAYMENT_TYPE_PUBLICATION:
                $submission = Repo::submission()->get($assocId);
                if ($submission->getSubmissionProgress()) {
                    $payment->setRequestUrl($dispatcher->url($request, Application::ROUTE_PAGE, null, 'submission', null, null, ['id' => $assocId]));
                } else {
                    $payment->setRequestUrl($dispatcher->url($request, Application::ROUTE_PAGE, null, 'authorDashboard', 'submission', $submission->getId()));
                }
                break;
            case self::PAYMENT_TYPE_MEMBERSHIP: // Deprecated
            case self::PAYMENT_TYPE_DONATION: // Deprecated
            case self::PAYMENT_TYPE_FASTTRACK: // Deprecated
            case self::PAYMENT_TYPE_SUBMISSION: // Deprecated
            default:
                // Invalid payment type
                error_log('Invalid payment type "' . $type . '"');
                assert(false);
                break;
        }

        return $payment;
    }

    /**
     * Create a completed payment from a queued payment.
     *
     * @param QueuedPayment $queuedPayment Payment to complete.
     * @param string $payMethod Name of payment plugin used.
     * @param int $userId User ID to attribute payment to (if unspecified, will be taken from queued payment)
     *
     * @return CompletedPayment
     */
    public function createCompletedPayment($queuedPayment, $payMethod, $userId = null)
    {
        $payment = new CompletedPayment();
        $payment->setContextId($queuedPayment->getContextId());
        $payment->setType($queuedPayment->getType());
        $payment->setAmount($queuedPayment->getAmount());
        $payment->setCurrencyCode($queuedPayment->getCurrencyCode());

        if ($userId) {
            $payment->setUserId($userId);
        } else {
            $payment->setUserId($queuedPayment->getUserId());
        }

        $payment->setAssocId($queuedPayment->getAssocId());
        $payment->setPayMethodPluginName($payMethod);

        return $payment;
    }

    /**
     * Determine whether publication fees are enabled.
     *
     * @return bool true iff this fee is enabled.
     */
    public function publicationEnabled()
    {
        return $this->isConfigured() && $this->_context->getData('publicationFee') > 0;
    }

    /**
     * Determine whether publication fees are enabled.
     *
     * @return bool true iff this fee is enabled.
     */
    public function membershipEnabled()
    {
        return $this->isConfigured() && $this->_context->getData('membershipFee') > 0;
    }

    /**
     * Determine whether article purchase fees are enabled.
     *
     * @return bool true iff this fee is enabled.
     */
    public function purchaseArticleEnabled()
    {
        return $this->isConfigured() && $this->_context->getData('purchaseArticleFee') > 0;
    }

    /**
     * Determine whether issue purchase fees are enabled.
     *
     * @return bool true iff this fee is enabled.
     */
    public function purchaseIssueEnabled()
    {
        return $this->isConfigured() && $this->_context->getData('purchaseIssueFee') > 0;
    }

    /**
     * Determine whether PDF-only article purchase fees are enabled.
     *
     * @return bool true iff this fee is enabled.
     */
    public function onlyPdfEnabled()
    {
        return $this->isConfigured() && $this->_context->getData('restrictOnlyPdf');
    }

    /**
     * Get the payment plugin.
     *
     * @return PaymethodPlugin
     */
    public function getPaymentPlugin()
    {
        $paymentMethodPluginName = $this->_context->getData('paymentPluginName');
        $paymentMethodPlugin = null;
        if (!empty($paymentMethodPluginName)) {
            $plugins = PluginRegistry::loadCategory('paymethod');
            if (isset($plugins[$paymentMethodPluginName])) {
                $paymentMethodPlugin = $plugins[$paymentMethodPluginName];
            }
        }
        return $paymentMethodPlugin;
    }

    /**
     * Fulfill a queued payment.
     *
     * @param Request $request
     * @param QueuedPayment $queuedPayment
     * @param string $payMethodPluginName Name of payment plugin.
     *
     * @return mixed Dependent on payment type.
     */
    public function fulfillQueuedPayment($request, $queuedPayment, $payMethodPluginName = null)
    {
        $returner = false;
        $journal = $request->getContext();
        if ($queuedPayment) {
            switch ($queuedPayment->getType()) {
                case self::PAYMENT_TYPE_MEMBERSHIP:
                    $user = Repo::user()->get($queuedPayment->getUserId(), true);
                    $dateEnd = $user->getSetting('dateEndMembership', 0);
                    if (!$dateEnd) {
                        $dateEnd = 0;
                    }

                    // if the membership is expired, extend it to today + 1 year
                    $time = time();
                    if ($dateEnd < $time) {
                        $dateEnd = $time;
                    }

                    $dateEnd = mktime(23, 59, 59, date('m', $dateEnd), date('d', $dateEnd), date('Y', $dateEnd) + 1);
                    $user->updateSetting('dateEndMembership', $dateEnd, 'date', 0);
                    $returner = true;
                    break;
                case self::PAYMENT_TYPE_PURCHASE_SUBSCRIPTION:
                    $subscriptionId = $queuedPayment->getAssocId();
                    $institutionalSubscriptionDao = DAORegistry::getDAO('InstitutionalSubscriptionDAO'); /** @var InstitutionalSubscriptionDAO $institutionalSubscriptionDao */
                    $individualSubscriptionDao = DAORegistry::getDAO('IndividualSubscriptionDAO'); /** @var IndividualSubscriptionDAO $individualSubscriptionDao */
                    $subscriptionTypeDao = DAORegistry::getDAO('SubscriptionTypeDAO'); /** @var SubscriptionTypeDAO $subscriptionTypeDao */
                    if ($institutionalSubscriptionDao->subscriptionExists($subscriptionId)) {
                        $subscription = $institutionalSubscriptionDao->getById($subscriptionId);
                        $institutional = true;
                    } else {
                        $subscription = $individualSubscriptionDao->getById($subscriptionId);
                        $institutional = false;
                    }
                    if (!$subscription || $subscription->getUserId() != $queuedPayment->getUserId() || $subscription->getJournalId() != $queuedPayment->getContextId()) {
                        fatalError('Subscription integrity checks fail!');
                        return false;
                    }
                    $subscriptionType = $subscriptionTypeDao->getById($subscription->getTypeId(), $journal->getId());
                    // Update subscription end date now that payment is completed
                    if ($institutional) {
                        // Still requires approval from JM/SM since includes domain and IP ranges
                        $subscription->setStatus(Subscription::SUBSCRIPTION_STATUS_NEEDS_APPROVAL);
                        if ($subscription->isNonExpiring()) {
                            $institutionalSubscriptionDao->updateObject($subscription);
                        } else {
                            $institutionalSubscriptionDao->renewSubscription($subscription);
                        }

                        // Notify JM/SM of completed online purchase
                        if ($journal->getData('enableSubscriptionOnlinePaymentNotificationPurchaseInstitutional')) {
                            $institution = Repo::institution()->get($subscription->getInstitutionId());
                            SubscriptionAction::sendOnlinePaymentNotificationEmail(
                                $request,
                                new SubscriptionPurchaseInstitutional($journal, $subscription, $subscriptionType, $institution)
                            );
                        }
                    } else {
                        $subscription->setStatus(Subscription::SUBSCRIPTION_STATUS_ACTIVE);
                        if ($subscription->isNonExpiring()) {
                            $individualSubscriptionDao->updateObject($subscription);
                        } else {
                            $individualSubscriptionDao->renewSubscription($subscription);
                        }
                        // Notify JM/SM of completed online purchase
                        if ($journal->getData('enableSubscriptionOnlinePaymentNotificationPurchaseIndividual')) {
                            SubscriptionAction::sendOnlinePaymentNotificationEmail(
                                $request,
                                new SubscriptionPurchaseIndividual($journal, $subscription, $subscriptionType)
                            );
                        }
                    }
                    $returner = true;
                    break;
                case self::PAYMENT_TYPE_RENEW_SUBSCRIPTION:
                    $subscriptionId = $queuedPayment->getAssocId();
                    $institutionalSubscriptionDao = DAORegistry::getDAO('InstitutionalSubscriptionDAO'); /** @var InstitutionalSubscriptionDAO $institutionalSubscriptionDao */
                    $individualSubscriptionDao = DAORegistry::getDAO('IndividualSubscriptionDAO'); /** @var IndividualSubscriptionDAO $individualSubscriptionDao */
                    $subscriptionTypeDao = DAORegistry::getDAO('SubscriptionTypeDAO'); /** @var SubscriptionTypeDAO $subscriptionTypeDao */
                    if ($institutionalSubscriptionDao->subscriptionExists($subscriptionId)) {
                        $subscription = $institutionalSubscriptionDao->getById($subscriptionId);
                        $institutional = true;
                    } else {
                        $subscription = $individualSubscriptionDao->getById($subscriptionId);
                        $institutional = false;
                    }
                    if (!$subscription || $subscription->getUserId() != $queuedPayment->getUserId() || $subscription->getJournalId() != $queuedPayment->getContextId()) {
                        return false;
                    }
                    $subscriptionType = $subscriptionTypeDao->getById($subscription->getTypeId(), $journal->getId());
                    if ($institutional) {
                        $institutionalSubscriptionDao->renewSubscription($subscription);

                        // Notify JM/SM of completed online purchase
                        if ($journal->getData('enableSubscriptionOnlinePaymentNotificationRenewInstitutional')) {
                            $institution = Repo::institution()->get($subscription->getInstitutionId());
                            SubscriptionAction::sendOnlinePaymentNotificationEmail(
                                $request,
                                new SubscriptionRenewInstitutional($journal, $subscription, $subscriptionType, $institution)
                            );
                        }
                    } else {
                        $individualSubscriptionDao->renewSubscription($subscription);

                        // Notify JM/SM of completed online purchase
                        if ($journal->getData('enableSubscriptionOnlinePaymentNotificationRenewIndividual')) {
                            SubscriptionAction::sendOnlinePaymentNotificationEmail(
                                $request,
                                new SubscriptionRenewIndividual($journal, $subscription, $subscriptionType)
                            );
                        }
                    }
                    $returner = true;
                    break;
                case self::PAYMENT_TYPE_DONATION:
                    assert(false); // Deprecated
                    $returner = true;
                    break;
                case self::PAYMENT_TYPE_PURCHASE_ARTICLE:
                case self::PAYMENT_TYPE_PURCHASE_ISSUE:
                case self::PAYMENT_TYPE_SUBMISSION:
                case self::PAYMENT_TYPE_PUBLICATION:
                    $returner = true;
                    break;
                default:
                    // Invalid payment type
                    assert(false);
            }
        }
        $completedPaymentDao = DAORegistry::getDAO('OJSCompletedPaymentDAO'); /** @var OJSCompletedPaymentDAO $completedPaymentDao */
        $completedPayment = $this->createCompletedPayment($queuedPayment, $payMethodPluginName, $request->getUser()->getId());
        $completedPaymentDao->insertObject($completedPayment);

        $queuedPaymentDao = DAORegistry::getDAO('QueuedPaymentDAO'); /** @var QueuedPaymentDAO $queuedPaymentDao */
        $queuedPaymentDao->deleteById($queuedPayment->getId());

        return $returner;
    }

    /**
     * Fetch the name of the description.
     *
     * @return string
     */
    public function getPaymentName($payment)
    {
        switch ($payment->getType()) {
            case self::PAYMENT_TYPE_PURCHASE_SUBSCRIPTION:
            case self::PAYMENT_TYPE_RENEW_SUBSCRIPTION:
                $institutionalSubscriptionDao = DAORegistry::getDAO('InstitutionalSubscriptionDAO'); /** @var InstitutionalSubscriptionDAO $institutionalSubscriptionDao */

                if ($institutionalSubscriptionDao->subscriptionExists($payment->getAssocId())) {
                    $subscription = $institutionalSubscriptionDao->getById($payment->getAssocId());
                } else {
                    $individualSubscriptionDao = DAORegistry::getDAO('IndividualSubscriptionDAO'); /** @var IndividualSubscriptionDAO $individualSubscriptionDao */
                    $subscription = $individualSubscriptionDao->getById($payment->getAssocId());
                }
                if (!$subscription) {
                    return __('payment.type.subscription');
                }

                $subscriptionTypeDao = DAORegistry::getDAO('SubscriptionTypeDAO'); /** @var SubscriptionTypeDAO $subscriptionTypeDao */
                $subscriptionType = $subscriptionTypeDao->getById($subscription->getTypeId());

                return __('payment.type.subscription') . ' (' . $subscriptionType->getLocalizedName() . ')';
            case self::PAYMENT_TYPE_DONATION:
                // DEPRECATED: This is only for display of OJS 2.x data.
                return __('payment.type.donation');
            case self::PAYMENT_TYPE_MEMBERSHIP:
                return __('payment.type.membership');
            case self::PAYMENT_TYPE_PURCHASE_ARTICLE:
                return __('payment.type.purchaseArticle');
            case self::PAYMENT_TYPE_PURCHASE_ISSUE:
                return __('payment.type.purchaseIssue');
            case self::PAYMENT_TYPE_SUBMISSION:
                // DEPRECATED: This is only for display of OJS 2.x data.
                return __('payment.type.submission');
            case self::PAYMENT_TYPE_FASTTRACK:
                // DEPRECATED: This is only for display of OJS 2.x data.
                return __('payment.type.fastTrack');
            case self::PAYMENT_TYPE_PUBLICATION:
                return __('payment.type.publication');
            default:
                // Invalid payment type
                assert(false);
        }
    }
}

if (!PKP_STRICT_MODE) {
    class_alias('\APP\payment\ojs\OJSPaymentManager', '\OJSPaymentManager');
    foreach ([
        'PAYMENT_TYPE_MEMBERSHIP',
        'PAYMENT_TYPE_RENEW_SUBSCRIPTION',
        'PAYMENT_TYPE_PURCHASE_ARTICLE',
        'PAYMENT_TYPE_DONATION',
        'PAYMENT_TYPE_SUBMISSION',
        'PAYMENT_TYPE_FASTTRACK',
        'PAYMENT_TYPE_PUBLICATION',
        'PAYMENT_TYPE_PURCHASE_SUBSCRIPTION',
        'PAYMENT_TYPE_PURCHASE_ISSUE',
    ] as $constantName) {
        define($constantName, constant('\OJSPaymentManager::' . $constantName));
    }
}
