<?php

namespace Altum\Controllers;

use Altum\Alerts;
use Altum\Database\Database;
use Altum\Date;
use Altum\Middlewares\Authentication;
use Altum\Middlewares\Csrf;
use Altum\Models\User;
use Altum\Response;
use Exception;
use PayPal\Api\Agreement;
use PayPal\Api\Amount;
use PayPal\Api\Currency;
use PayPal\Api\FlowConfig;
use PayPal\Api\InputFields;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\MerchantPreferences;
use PayPal\Api\Patch;
use PayPal\Api\PatchRequest;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\PaymentDefinition;
use PayPal\Api\PaymentExecution;
use PayPal\Api\Plan;
use PayPal\Api\Presentation;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
use PayPal\Api\WebProfile;
use PayPal\Common\PayPalModel;

class Pay extends Controller {
    public $plan_id;
    public $return_type;
    public $payment_processor;
    public $plan;
    public $plan_taxes;
    public $applied_taxes_ids = [];
    public $code;

    public function index() {

        Authentication::guard();

        if(!settings()->payment->is_enabled) {
            redirect();
        }

        $this->plan_id = isset($this->params[0]) ? $this->params[0] : null;
        $this->return_type = isset($_GET['return_type']) && in_array($_GET['return_type'], ['success', 'cancel']) ? $_GET['return_type'] : null;
        $this->payment_processor = isset($_GET['payment_processor']) && in_array($_GET['payment_processor'], ['paypal', 'stripe', 'offline_payment']) ? $_GET['payment_processor'] : null;

        /* Make sure it is either the trial / free plan or normal plans */
        switch($this->plan_id) {

            case 'free':

                /* Get the current settings for the free plan */
                $this->plan = settings()->plan_free;

                break;

            case 'trial':

                /* Get the current settings for the trial plan */
                $this->plan = settings()->plan_trial;

                break;

            default:

                $this->plan_id = (int) $this->plan_id;

                /* Check if plan exists */
                $this->plan = (new \Altum\Models\Plan())->get_plan_by_id($this->plan_id);

                if($this->plan->plan_id == 'custom') {
                    redirect('plan');
                }

                /* Check for potential taxes */
                $this->plan_taxes = (new \Altum\Models\Plan())->get_plan_taxes_by_taxes_ids($this->plan->taxes_ids);

                /* Filter them out */
                if($this->plan_taxes) {
                    foreach ($this->plan_taxes as $key => $value) {

                        /* Type */
                        if ($value->billing_type != $this->user->billing->type && $value->billing_type != 'both') {
                            unset($this->plan_taxes[$key]);
                        }

                        /* Countries */
                        if ($value->countries && !in_array($this->user->billing->country, $value->countries)) {
                            unset($this->plan_taxes[$key]);
                        }

                        if (isset($this->plan_taxes[$key])) {
                            $this->applied_taxes_ids[] = $value->tax_id;
                        }

                    }

                    $this->plan_taxes = array_values($this->plan_taxes);
                }

                break;
        }

        /* Make sure the plan is enabled */
        if(!$this->plan->status) {
            redirect('plan');
        }

        /* More checks depending on the user plan and what it has been chosen */
        if($this->plan_id == 'free') {
            if($this->user->plan_id == 'free') {
                Alerts::add_info(language()->pay->free->free_already);
            } else {
                Alerts::add_info(language()->pay->free->other_plan_not_expired);
            }

            redirect('plan');
        }

        elseif($this->plan_id == 'trial') {
            if($this->user->plan_trial_done) {
                Alerts::add_info(language()->pay->trial->trial_done);
                redirect('plan');
            }
        }

        /* Form submission processing */
        /* Make sure that this only runs on user click submit post and not on callbacks / webhooks */
        if(!empty($_POST) && !$this->return_type) {

            /* Check for any errors */
            if(!Csrf::check()) {
                Alerts::add_error(language()->global->error_message->invalid_csrf_token);
            }

            switch($this->plan_id) {

                case 'free':

                    redirect('pay/' . $this->plan_id);

                    break;

                case 'trial':

                    if($this->user->plan_trial_done) {
                        redirect('pay/' . $this->plan_id);
                    }

                    break;

                default:

                    $_POST['payment_frequency'] = Database::clean_string($_POST['payment_frequency']);
                    $_POST['payment_processor'] = Database::clean_string($_POST['payment_processor']);
                    $_POST['payment_type'] = Database::clean_string($_POST['payment_type']);

                    /* Make sure the chosen option comply */
                    if(!in_array($_POST['payment_frequency'], ['monthly', 'annual', 'lifetime'])) {
                        redirect('pay/' . $this->plan_id);
                    }

                    if(!in_array($_POST['payment_processor'], ['paypal', 'stripe', 'offline_payment'])) {
                        redirect('pay/' . $this->plan_id);
                    } else {

                        /* Make sure the payment processor is active */
                        switch($_POST['payment_processor']) {
                            case 'paypal':

                                if(!settings()->paypal->is_enabled) {
                                    redirect('pay/' . $this->plan_id);
                                }

                                break;

                            case 'stripe':

                                if(!settings()->stripe->is_enabled) {
                                    redirect('pay/' . $this->plan_id);
                                }

                                break;

                            case 'offline_payment':

                                if(!settings()->offline_payment->is_enabled) {
                                    redirect('pay/' . $this->plan_id);
                                }

                                break;
                        }

                    }

                    if(!in_array($_POST['payment_type'], ['one_time', 'recurring'])) {
                        redirect('pay/' . $this->plan_id);
                    }

                    if(settings()->payment->taxes_and_billing_is_enabled && (empty($this->user->billing->name) || empty($this->user->billing->address) || empty($this->user->billing->city) || empty($this->user->billing->county) || empty($this->user->billing->zip))) {
                        $_POST['billing_type'] = in_array($_POST['billing_type'], ['personal', 'business']) ? Database::clean_string($_POST['billing_type']) : 'personal';
                        $_POST['billing_name'] = trim(Database::clean_string($_POST['billing_name']));
                        $_POST['billing_address'] = trim(Database::clean_string($_POST['billing_address']));
                        $_POST['billing_city'] = trim(Database::clean_string($_POST['billing_city']));
                        $_POST['billing_county'] = trim(Database::clean_string($_POST['billing_county']));
                        $_POST['billing_zip'] = trim(Database::clean_string($_POST['billing_zip']));
                        $_POST['billing_country'] = array_key_exists($_POST['billing_country'], get_countries_array()) ? Database::clean_string($_POST['billing_country']) : 'US';
                        $_POST['billing_phone'] = trim(Database::clean_string($_POST['billing_phone']));
                        $_POST['billing_tax_id'] = $_POST['billing_type'] == 'business' ? trim(Database::clean_string($_POST['billing_tax_id'])) : '';
                        $_POST['billing'] = json_encode([
                            'type' => $_POST['billing_type'],
                            'name' => $_POST['billing_name'],
                            'address' => $_POST['billing_address'],
                            'city' => $_POST['billing_city'],
                            'county' => $_POST['billing_county'],
                            'zip' => $_POST['billing_zip'],
                            'country' => $_POST['billing_country'],
                            'phone' => $_POST['billing_phone'],
                            'tax_id' => $_POST['billing_tax_id'],
                        ]);

                        if(empty($_POST['billing_name']) || empty($_POST['billing_address']) || empty($_POST['billing_city']) || empty($_POST['billing_county']) || empty($_POST['billing_zip'])) {
                            Alerts::add_info(language()->pay->custom_plan->billing_required);
                            redirect('pay/' . $this->plan_id);
                        }

                        /* Database query */
                        db()->where('user_id', $this->user->user_id)->update('users', ['billing' => $_POST['billing']]);
                    }

                    /* Lifetime */
                    if($_POST['payment_frequency'] == 'lifetime') {
                        $_POST['payment_type'] = 'one_time';
                    }

                    /* Offline payment */
                    if($_POST['payment_processor'] == 'offline_payment') {
                        $_POST['payment_type'] = 'one_time';
                    }

                    break;
            }

            if(!Alerts::has_field_errors() && !Alerts::has_errors()) {

                switch($this->plan_id) {

                    case 'trial':

                        /* Determine the expiration date of the plan */
                        $plan_expiration_date = (new \DateTime())->modify('+' . $this->plan->days . ' days')->format('Y-m-d H:i:s');
                        $plan_settings = json_encode(settings()->plan_trial->settings);

                        /* Database query */
                        db()->where('user_id', $this->user->user_id)->update('users', [
                            'plan_id' => 'trial',
                            'plan_settings' => $plan_settings,
                            'plan_expiration_date' => $plan_expiration_date,
                            'plan_trial_done' => 1,
                        ]);

                        /* Clear the cache */
                        \Altum\Cache::$adapter->deleteItemsByTag('user_id=' . $this->user->user_id);

                        /* Success message and redirect */
                        $this->redirect_pay_thank_you();

                        break;

                    default:

                        /* Check for code usage */
                        $this->code = false;

                        if(settings()->payment->codes_is_enabled && isset($_POST['code'])) {

                            $_POST['code'] = Database::clean_string($_POST['code']);

                            $this->code = database()->query("SELECT `code_id`, `code`, `discount` FROM `codes` WHERE (`plan_id` IS NULL OR `plan_id` = '{$this->plan_id}') AND `code` = '{$_POST['code']}' AND `redeemed` < `quantity` AND `type` = 'discount'")->fetch_object();

                            if($this->code && db()->where('user_id', $this->user->user_id)->where('code_id', $this->code->code_id)->has('redeemed_codes')) {
                                redirect('pay/' . $this->plan_id);
                            }
                        }

                        switch($_POST['payment_processor']) {

                            case 'paypal':

                                $this->paypal_create();

                                break;

                            case 'stripe':

                                $stripe_session = $this->stripe_create();

                                break;

                            case 'offline_payment':

                                $stripe_session = $this->offline_payment_process();

                                break;
                        }

                        break;

                }

            }

        }

        /* Include the detection of paypal callbacks processing */
        $this->paypal_process();

        /* Include the detection of stripe callbacks processing */
        $this->stripe_process();

        /* Prepare the View */
        $data = [
            'plan_id'           => $this->plan_id,
            'plan'              => $this->plan,
            'plan_taxes'        => $this->plan_taxes,
            'stripe_session'    => $stripe_session ?? false
        ];

        $view = new \Altum\Views\View('pay/index', (array) $this);

        $this->add_view_content('content', $view->run($data));

    }

    private function paypal_create() {

        /* Initiate paypal */
        $paypal = new \PayPal\Rest\ApiContext(new \PayPal\Auth\OAuthTokenCredential(settings()->paypal->client_id, settings()->paypal->secret));
        $paypal->setConfig(['mode' => settings()->paypal->mode]);

        /* Payment details */
        $product = $this->plan->name;
        $price = $base_amount = (float) $this->plan->{$_POST['payment_frequency'] . '_price'};
        $shipping = 0;
        $code = '';
        $discount_amount = 0;

        /* Check for code usage */
        if($this->code) {

            /* Discount amount */
            $discount_amount = number_format(($price * $this->code->discount / 100), 2, '.', '');

            /* Calculate the new price */
            $price = $price - $discount_amount;

            $code = $this->code->code;

        }

        /* Taxes */
        $price = $this->calculate_price_with_taxes($price);

        /* Make sure the price is right depending on the currency */
        $price = in_array(settings()->payment->currency, ['JPY', 'TWD', 'HUF']) ? number_format($price, 0, '.', '') : number_format($price, 2, '.', '');

        switch($_POST['payment_type']) {
            case 'one_time':

                /* Payment experience */
                $flowConfig = new FlowConfig();
                $flowConfig->setLandingPageType('Billing');
                $flowConfig->setUserAction('commit');
                $flowConfig->setReturnUriHttpMethod('GET');

                $presentation = new Presentation();
                $presentation->setBrandName(settings()->payment->brand_name);

                $inputFields = new InputFields();
                $inputFields->setAllowNote(true)
                    ->setNoShipping(1)
                    ->setAddressOverride(0);

                $webProfile = new WebProfile();
                $webProfile->setName(settings()->payment->brand_name . uniqid())
                    ->setFlowConfig($flowConfig)
                    ->setPresentation($presentation)
                    ->setInputFields($inputFields)
                    ->setTemporary(true);

                /* Create the experience profile */
                try {
                    $createdProfileResponse = $webProfile->create($paypal);
                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }

                }

                $payer = new Payer();
                $payer->setPaymentMethod('paypal');

                $item = new Item();
                $item->setName($product)
                    ->setCurrency(settings()->payment->currency)
                    ->setQuantity(1)
                    ->setPrice($price);

                $itemList = new ItemList();
                $itemList->setItems([$item]);

                $amount = new Amount();
                $amount->setCurrency(settings()->payment->currency)
                    ->setTotal($price);

                $transaction = new Transaction();
                $transaction->setAmount($amount)
                    ->setItemList($itemList)
                    ->setInvoiceNumber(uniqid());

                $redirectUrls = new RedirectUrls();
                $redirectUrls->setReturnUrl(url('pay/' . $this->plan_id . $this->return_url_parameters('success', $base_amount, $price, $code, $discount_amount)))
                    ->setCancelUrl(url('pay/' . $this->plan_id . $this->return_url_parameters('cancel', $base_amount, $price, $code, $discount_amount)));

                $payment = new Payment();
                $payment->setIntent('sale')
                    ->setPayer($payer)
                    ->setRedirectUrls($redirectUrls)
                    ->setTransactions([$transaction])
                    ->setExperienceProfileId($createdProfileResponse->getId());

                try {
                    $payment->create($paypal);
                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }
                }

                $payment_url = $payment->getApprovalLink();

                header('Location: ' . $payment_url);

                break;

            case 'recurring':

                $plan = new \PayPal\Api\Plan();
                $plan->setName($product)
                    ->setDescription($product)
                    ->setType('fixed');

                /* Set billing plan definitions */
                $payment_definition = new PaymentDefinition();
                $payment_definition->setName('Regular Payments')
                    ->setType('REGULAR')
                    ->setFrequency($_POST['payment_frequency'] == 'monthly' ? 'Month' : 'Year')
                    ->setFrequencyInterval('1')
                    ->setCycles($_POST['payment_frequency'] == 'monthly' ? '60' : '5')
                    ->setAmount(new Currency(['value' => $price, 'currency' => settings()->payment->currency]));


                /* Set merchant preferences */
                $merchant_preferences = new MerchantPreferences();
                $merchant_preferences->setReturnUrl(url('pay/' . $this->plan_id . $this->return_url_parameters('success', $base_amount, $price, $code, $discount_amount)))
                    ->setCancelUrl(url('pay/' . $this->plan_id . $this->return_url_parameters('cancel', $base_amount, $price, $code, $discount_amount)))
                    ->setAutoBillAmount('yes')
                    ->setInitialFailAmountAction('CONTINUE')
                    ->setMaxFailAttempts('0')
                    ->setSetupFee(new Currency(['value' => $price, 'currency' => settings()->payment->currency]));

                $plan->setPaymentDefinitions([$payment_definition]);
                $plan->setMerchantPreferences($merchant_preferences);

                /* Create the plan */
                try {
                    $plan = $plan->create($paypal);
                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo '1';
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }
                }

                /* Make sure to activate the plan */
                try {
                    $patch = new Patch();
                    $value = new PayPalModel('{"state":"ACTIVE"}');
                    $patch->setOp('replace')
                        ->setPath('/')
                        ->setValue($value);
                    $patchRequest = new PatchRequest();
                    $patchRequest->addPatch($patch);
                    $plan->update($patchRequest, $paypal);
                    $plan = Plan::get($plan->getId(), $paypal);
                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo '2';
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }
                }

                /* Start creating the agreement */
                $agreement = new Agreement();
                $agreement->setName($product)
                    ->setDescription(
                        $this->user->user_id
                        . '!!' . $this->plan_id
                        . '!!' . $base_amount
                        . '!!' . $_POST['payment_frequency']
                        . '!!' . $code
                        . '!!' . $discount_amount
                        . '!!' . json_encode($this->applied_taxes_ids)
                    )
                    ->setStartDate((new \DateTime())->modify($_POST['payment_frequency'] == 'monthly' ? '+30 days' : '+1 year')->format(DATE_ISO8601));

                /* Set the plan id to the agreement */
                $agreement_plan = new Plan();
                $agreement_plan->setId($plan->getId());
                $agreement->setPlan($agreement_plan);

                /* Add Payer */
                $payer = new Payer();
                $payer->setPaymentMethod('paypal');
                $agreement->setPayer($payer);

                /* Create the agreement */
                try {
                    $agreement = $agreement->create($paypal);
                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo '3';
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }
                }

                $payment_url = $agreement->getApprovalLink();

                header('Location: ' . $payment_url);

                break;
        }


    }

    private function paypal_process() {

        /* Initiate paypal */
        $paypal = new \PayPal\Rest\ApiContext(new \PayPal\Auth\OAuthTokenCredential(settings()->paypal->client_id, settings()->paypal->secret));
        $paypal->setConfig(['mode' => settings()->paypal->mode]);

        /* Return confirmation processing */
        if($this->return_type && $this->payment_processor && $this->return_type == 'success' && $this->payment_processor = 'paypal' && isset($_GET['payment_frequency'], $_GET['code'])) {
            $payment_frequency = $_GET['payment_frequency'];
            $code = $_GET['code'];
            $discount_amount = $_GET['discount_amount'];
            $base_amount = $_GET['base_amount'];

            /* Return confirmation processing one time payment */
            if(isset($_GET['paymentId'], $_GET['PayerID'])) {

                /* Initiate paypal */
                $paypal = new \PayPal\Rest\ApiContext(new \PayPal\Auth\OAuthTokenCredential(settings()->paypal->client_id, settings()->paypal->secret));
                $paypal->setConfig(['mode' => settings()->paypal->mode]);

                $payment_id = $_GET['paymentId'];
                $payer_id = $_GET['PayerID'];
                $payment_type = 'one_time';
                $subscription_id = '';
                $payment_subscription_id =  '';

                try {
                    $payment = Payment::get($payment_id, $paypal);

                    $payer_info = $payment->getPayer()->getPayerInfo();
                    $payer_email = $payer_info->getEmail();
                    $payer_name = $payer_info->getFirstName() . ' ' . $payer_info->getLastName();

                    $payment_total = $payment->getTransactions()[0]->getAmount()->getTotal();
                    $payment_currency = $payment->getTransactions()[0]->getAmount()->getCurrency();

                    /* Execute the payment */
                    $execute = new PaymentExecution();
                    $execute->setPayerId($payer_id);

                    $result = $payment->execute($execute, $paypal);

                    /* Get status after execution */
                    $payment_status = $payment->getState();

                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }
                }

                /* Make sure the transaction is not already existing */
                if(db()->where('payment_id', $payment_id)->where('processor', 'paypal')->has('payments')) {
                    redirect('pay/' . $this->plan_id);
                }

                /* Make sure the payment is approved */
                if($payment_status != 'approved') {
                    Alerts::add_error(language()->pay->error_message->failed_payment);
                    redirect('pay/' . $this->plan_id);
                }

                /* Make sure the chosen option comply */
                if(!in_array($payment_frequency, ['monthly', 'annual', 'lifetime'])) {
                    redirect('pay/' . $this->plan_id);
                }

                /* Unsubscribe from the previous plan if needed */
                if(!empty($this->user->payment_subscription_id) && $this->user->payment_subscription_id != $payment_subscription_id) {
                    try {
                        (new User(['user' => $this->user]))->cancel_subscription();
                    } catch (\Exception $exception) {

                        /* Output errors properly */
                        if (DEBUG) {
                            echo $exception->getCode() . '-' . $exception->getMessage();

                            die();
                        } else {
                            Alerts::add_error(language()->pay->error_message->failed_payment);
                            redirect('pay/' . $this->plan_id);
                        }

                    }
                }

                /* Make sure the code exists */
                $codes_code = db()->where('code', $code)->where('type', 'discount')->getOne('codes');

                if($codes_code) {
                    $code = $codes_code->code;

                    /* Check if we should insert the usage of the code or not */
                    if(!db()->where('user_id', $this->user->user_id)->where('code_id', $codes_code->code_id)->has('redeemed_codes')) {

                        /* Update the code usage */
                        db()->where('code_id', $codes_code->code_id)->update('codes', ['redeemed' => db()->inc()]);

                        /* Add log for the redeemed code */
                        db()->insert('redeemed_codes', [
                            'code_id'   => $codes_code->code_id,
                            'user_id'   => $this->user->user_id,
                            'date'      => \Altum\Date::$date
                        ]);
                    }
                }

                /* Add a log into the database */
                $payment_id = db()->insert('payments', [
                    'user_id' => $this->user->user_id,
                    'plan_id' => $this->plan_id,
                    'processor' => 'paypal',
                    'type' => $payment_type,
                    'frequency' => $payment_frequency,
                    'code' => $code,
                    'discount_amount' => $discount_amount,
                    'base_amount' => $base_amount,
                    'email' => $payer_email,
                    'payment_id' => $payment_id,
                    'subscription_id' => $subscription_id,
                    'payer_id' => $payer_id,
                    'name' => $payer_name,
                    'billing' => settings()->payment->taxes_and_billing_is_enabled && $this->user->billing ? json_encode($this->user->billing) : null,
                    'taxes_ids' => !empty($this->applied_taxes_ids) ? json_encode($this->applied_taxes_ids) : null,
                    'total_amount' => $payment_total,
                    'currency' => $payment_currency,
                    'date' => \Altum\Date::$date
                ]);

                /* Update the user with the new plan */
                switch($payment_frequency) {
                    case 'monthly':
                        $plan_expiration_date = (new \DateTime())->modify('+30 days')->format('Y-m-d H:i:s');
                        break;

                    case 'annual':
                        $plan_expiration_date = (new \DateTime())->modify('+12 months')->format('Y-m-d H:i:s');
                        break;

                    case 'lifetime':
                        $plan_expiration_date = (new \DateTime())->modify('+100 years')->format('Y-m-d H:i:s');
                        break;
                }

                /* Database query */
                db()->where('user_id', $this->user->user_id)->update('users', [
                    'plan_id' => $this->plan_id,
                    'plan_settings' => json_encode($this->plan->settings),
                    'plan_expiration_date' => $plan_expiration_date
                ]);

                /* Clear the cache */
                \Altum\Cache::$adapter->deleteItemsByTag('user_id=' . $this->user->user_id);

                /* Send notification to the user */
                /* Prepare the email */
                $email_template = get_email_template(
                    [],
                    language()->global->emails->user_payment->subject,
                    [
                        '{{PLAN_EXPIRATION_DATE}}' => Date::get($plan_expiration_date, 2),
                        '{{USER_PLAN_LINK}}' => url('account-plan'),
                        '{{USER_PAYMENTS_LINK}}' => url('account-payments'),
                    ],
                    language()->global->emails->user_payment->body
                );

                send_mail(
                    $this->user->email,
                    $email_template->subject,
                    $email_template->body
                );

                /* Send notification to admin if needed */
                if(settings()->email_notifications->new_payment && !empty(settings()->email_notifications->emails)) {

                    send_mail(
                        explode(',', settings()->email_notifications->emails),
                        sprintf(language()->global->emails->admin_new_payment_notification->subject, 'paypal', $payment_total, $payment_currency),
                        sprintf(language()->global->emails->admin_new_payment_notification->body, $payment_total, $payment_currency)
                    );

                }

                /* Success message and redirect */
                $this->redirect_pay_thank_you();

            }

            /* Return confirmation processing recurring payment */
            if(isset($_GET['token'], $_GET['payment_type'])) {

                /* Initiate paypal */
                $paypal = new \PayPal\Rest\ApiContext(new \PayPal\Auth\OAuthTokenCredential(settings()->paypal->client_id, settings()->paypal->secret));
                $paypal->setConfig(['mode' => settings()->paypal->mode]);

                $token = $_GET['token'];
                $agreement = new \PayPal\Api\Agreement();
                $payment_type = 'recurring';

                try {
                    $agreement->execute($token, $paypal);
                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }
                }

                /* Get details about the executed agreement */
                try {
                    $agreement = \PayPal\Api\Agreement::get($agreement->getId(), $paypal);
                } catch (Exception $exception) {

                    /* Output errors properly */
                    if (DEBUG) {
                        echo $exception->getCode();
                        echo $exception->getData();

                        die();
                    } else {

                        Alerts::add_error(language()->pay->error_message->failed_payment);
                        redirect('pay/' . $this->plan_id);

                    }
                }

                /* Get the needed details from the agreement */
                $agreement_status = $agreement->getState();

                /* Make sure the payment is approved */
                if($agreement_status != 'Active' && $agreement_status != 'Pending') {
                    Alerts::add_error(language()->pay->error_message->failed_payment);
                    redirect('pay/' . $this->plan_id);
                }

                /* Success message and redirect */
                $this->redirect_pay_thank_you();

            }

        }


        /* Return confirmation processing if failed */
        if($this->return_type && $this->payment_processor && $this->return_type == 'cancel' && $this->payment_processor = 'paypal') {
            Alerts::add_error(language()->pay->error_message->canceled_payment);
            redirect('pay/' . $this->plan_id);
        }

    }

    private function stripe_create() {

        /* Initiate Stripe */
        \Stripe\Stripe::setApiKey(settings()->stripe->secret_key);

        /* Payment details */
        $product = $this->plan->name;
        $price = $base_amount = $this->plan->{$_POST['payment_frequency'] . '_price'};
        $shipping = 0;
        $code = '';
        $discount_amount = 0;

        /* Check for code usage */
        if($this->code) {

            /* Discount amount */
            $discount_amount = number_format(($price * $this->code->discount / 100), 2, '.', '');

            /* Calculate the new price */
            $price = $price - $discount_amount;

            $code = $this->code->code;

        }

        /* Taxes */
        $price = $this->calculate_price_with_taxes($price);

        /* Final price */
        $stripe_formatted_price = in_array(settings()->payment->currency, ['MGA', 'BIF', 'CLP', 'PYG', 'DJF', 'RWF', 'GNF', 'UGX', 'JPY', 'VND', 'VUV', 'XAF', 'KMF', 'KRW', 'XOF', 'XPF']) ? number_format($price, 0, '.', '') : number_format($price, 2, '.', '') * 100;

        $price = number_format($price, 2, '.', '');

        switch($_POST['payment_type']) {
            case 'one_time':

                $stripe_session = \Stripe\Checkout\Session::create([
                    'payment_method_types' => ['card'],
                    'line_items' => [[
                        'name' => $product,
                        'description' => $_POST['payment_frequency'],
                        'amount' => $stripe_formatted_price,
                        'currency' => settings()->payment->currency,
                        'quantity' => 1,
                    ]],
                    'metadata' => [
                        'user_id' => $this->user->user_id,
                        'plan_id' => $this->plan_id,
                        'payment_frequency' => $_POST['payment_frequency'],
                        'base_amount' => $base_amount,
                        'code' => $code,
                        'discount_amount' => $discount_amount,
                        'taxes_ids' => json_encode($this->applied_taxes_ids)
                    ],
                    'success_url' => url('pay/' . $this->plan_id . $this->return_url_parameters('success', $base_amount, $price, $code, $discount_amount)),
                    'cancel_url' => url('pay/' . $this->plan_id . $this->return_url_parameters('cancel', $base_amount, $price, $code, $discount_amount)),
                ]);

                break;

            case 'recurring':

                /* Try to get the product related to the plan */
                try {
                    $stripe_product = \Stripe\Product::retrieve($this->plan_id);
                } catch (\Exception $exception) {
                    /* The product probably does not exist */
                }

                if(!isset($stripe_product)) {
                    /* Create the product if not already created */
                    $stripe_product = \Stripe\Product::create([
                        'id'    => $this->plan_id,
                        'name'  => $product,
                        'type'  => 'service',
                    ]);
                }

                /* Generate the plan id with the proper parameters */
                $stripe_plan_id = $this->plan_id . '_' . $_POST['payment_frequency'] . '_' . $stripe_formatted_price . '_' . settings()->payment->currency;

                /* Check if we already have a payment plan created and try to get it */
                try {
                    $stripe_plan = \Stripe\Plan::retrieve($stripe_plan_id);
                } catch (\Exception $exception) {
                    /* The plan probably does not exist */
                }

                /* Create the plan if it doesnt exist already */
                if(!isset($stripe_plan)) {
                    try {
                        $stripe_plan = \Stripe\Plan::create([
                            'amount' => $stripe_formatted_price,
                            'interval' => $_POST['payment_frequency'] == 'monthly' ? 'month' : 'year',
                            'product' => $stripe_product->id,
                            'currency' => settings()->payment->currency,
                            'id' => $stripe_plan_id
                        ]);
                    } catch (\Exception $exception) {
                        Alerts::add_error($exception->getMessage());
                        redirect('pay/' . $this->plan_id);
                    }
                }

                $stripe_session = \Stripe\Checkout\Session::create([
                    'payment_method_types' => ['card'],
                    'subscription_data' => [
                        'items' => [
                            ['plan' => $stripe_plan->id]
                        ],
                        'metadata' => [
                            'user_id' => $this->user->user_id,
                            'plan_id' => $this->plan_id,
                            'payment_frequency' => $_POST['payment_frequency'],
                            'code' => $code,
                            'taxes_ids' => json_encode($this->applied_taxes_ids)
                        ],
                    ],
                    'metadata' => [
                        'user_id' => $this->user->user_id,
                        'plan_id' => $this->plan_id,
                        'payment_frequency' => $_POST['payment_frequency'],
                        'base_amount' => $base_amount,
                        'code' => $code,
                        'discount_amount' => $discount_amount,
                        'taxes_ids' => json_encode($this->applied_taxes_ids)
                    ],
                    'success_url' => url('pay/' . $this->plan_id . $this->return_url_parameters('success', $base_amount, $price, $code, $discount_amount)),
                    'cancel_url' => url('pay/' . $this->plan_id . $this->return_url_parameters('cancel', $base_amount, $price, $code, $discount_amount)),
                ]);

                break;
        }

        return $stripe_session;

    }

    private function stripe_process() {

        /* Return confirmation processing if successfuly */
        if($this->return_type && $this->payment_processor && $this->return_type == 'success' && $this->payment_processor = 'stripe') {

            /* Redirect to the thank you page */
            $this->redirect_pay_thank_you();
        }

        /* Return confirmation processing if failed */
        if($this->return_type && $this->payment_processor && $this->return_type == 'cancel' && $this->payment_processor = 'stripe') {
            Alerts::add_error(language()->pay->error_message->canceled_payment);
            redirect('pay/' . $this->plan_id);
        }

    }

    private function offline_payment_process() {

        /* Return confirmation processing if successfuly */
        if($this->return_type && $this->payment_processor && $this->return_type == 'success' && $this->payment_processor == 'offline_payment') {

            /* Redirect to the thank you page */
            $this->redirect_pay_thank_you();
        }

        /* Payment details */
        $price = $base_amount = $this->plan->{$_POST['payment_frequency'] . '_price'};
        $code = '';
        $discount_amount = 0;

        /* Check for code usage */
        if($this->code) {

            /* Discount amount */
            $discount_amount = number_format(($price * $this->code->discount / 100), 2, '.', '');

            /* Calculate the new price */
            $price = $price - $discount_amount;

            $code = $this->code->code;

        }

        /* Taxes */
        $price = number_format($this->calculate_price_with_taxes($price), 2, '.', '');

        /* Other vars */
        $payment_id = md5($this->user->user_id . $this->plan_id . $_POST['payment_type'] . $_POST['payment_frequency'] . $this->user->email . Date::$date);
        $file_allowed_extensions = ['jpg', 'jpeg', 'png', 'pdf'];
        $offline_payment_proof = (!empty($_FILES['offline_payment_proof']['name']));

        /* Error checks */
        if(!$offline_payment_proof) {
            Alerts::add_error(language()->pay->error_message->offline_payment_proof_missing);
            redirect('pay/' . $this->plan_id);
        }

        $offline_payment_proof_file_name = $_FILES['offline_payment_proof']['name'];
        $offline_payment_proof_file_extension = explode('.', $offline_payment_proof_file_name);
        $offline_payment_proof_file_extension = mb_strtolower(end($offline_payment_proof_file_extension));
        $offline_payment_proof_file_temp = $_FILES['offline_payment_proof']['tmp_name'];

        if(!in_array($offline_payment_proof_file_extension, $file_allowed_extensions)) {
            Alerts::add_error(language()->global->error_message->invalid_file_type);
            redirect('pay/' . $this->plan_id);
        }

        if(!is_writable(UPLOADS_PATH . 'offline_payment_proofs/')) {
            Alerts::add_error(sprintf(language()->global->error_message->directory_not_writable, UPLOADS_PATH . 'offline_payment_proofs/'));
            redirect('pay/' . $this->plan_id);
        }

        /* Generate new name for offline_payment_proof */
        $offline_payment_proof_new_name = $payment_id . '.' . $offline_payment_proof_file_extension;

        /* Upload the original */
        move_uploaded_file($offline_payment_proof_file_temp, UPLOADS_PATH . 'offline_payment_proofs/' . $offline_payment_proof_new_name);

        /* Add a log into the database */
        db()->insert('payments', [
            'user_id' => $this->user->user_id,
            'plan_id' => $this->plan_id,
            'processor' => 'offline_payment',
            'type' => $_POST['payment_type'],
            'frequency' => $_POST['payment_frequency'],
            'code' => $code,
            'discount_amount' => $discount_amount,
            'base_amount' => $base_amount,
            'email' => $this->user->email,
            'payment_id' => $payment_id,
            'subscription_id' => '',
            'payer_id' => $this->user->user_id,
            'name' => $this->user->name,
            'billing' => settings()->payment->taxes_and_billing_is_enabled && $this->user->billing ? json_encode($this->user->billing) : null,
            'taxes_ids' => !empty($this->applied_taxes_ids) ? json_encode($this->applied_taxes_ids) : null,
            'total_amount' => $price,
            'currency' => settings()->payment->currency,
            'payment_proof' => $offline_payment_proof_new_name,
            'status' => 0,
            'date' => Date::$date
        ]);

        /* Send notification to admin if needed */
        if(settings()->email_notifications->new_payment && !empty(settings()->email_notifications->emails)) {

            send_mail(
                explode(',', settings()->email_notifications->emails),
                sprintf(language()->global->emails->admin_new_payment_notification->subject, 'offline_payment', $price, settings()->payment->currency),
                sprintf(language()->global->emails->admin_new_payment_notification->body, $price, settings()->payment->currency)
            );

        }

        redirect('pay/' . $this->plan_id . $this->return_url_parameters('success', $base_amount, $price, $code, $discount_amount));

    }

    /* Ajax to check if discount codes are available */
    public function code() {
        Authentication::guard();

        $_POST = json_decode(file_get_contents('php://input'), true);

        if(!Csrf::check('global_token')) {
            die();
        }

        if(!settings()->payment->is_enabled || !settings()->payment->codes_is_enabled) {
            die();
        }

        if(empty($_POST)) {
            die();
        }

        $_POST['plan_id'] = !$_POST['plan_id'] ? null : (int) $_POST['plan_id'];
        $_POST['code'] = Database::clean_string($_POST['code']);

        /* Make sure the discount code exists */
        $code = database()->query("SELECT * FROM `codes` WHERE (`plan_id` IS NULL OR `plan_id` = '{$_POST['plan_id']}') AND `code` = '{$_POST['code']}' AND `redeemed` < `quantity` AND `type` = 'discount'")->fetch_object();

        if(!$code) {
            Response::json(language()->pay->error_message->code_invalid, 'error');
        }

        if(db()->where('user_id', $this->user->user_id)->where('code_id', $code->code_id)->has('redeemed_codes')) {
            Response::json(language()->pay->error_message->code_used, 'error');
        }


        Response::json(sprintf(language()->pay->success_message->code, '<strong>' . $code->discount . '%</strong>'), 'success', ['discount' => $code->discount]);
    }

    /* Generate the generic return url parameters */
    private function return_url_parameters($return_type, $base_amount, $total_amount, $code, $discount_amount) {
        return
            '&return_type=' . $return_type
            . '&payment_processor=' . $_POST['payment_processor']
            . '&payment_frequency=' . $_POST['payment_frequency']
            . '&payment_type=' . $_POST['payment_type']
            . '&code=' . $code
            . '&discount_amount=' . $discount_amount
            . '&base_amount=' . $base_amount
            . '&total_amount=' . $total_amount;
    }

    /* Simple url generator to return the thank you page */
    private function redirect_pay_thank_you() {
        $thank_you_url_parameters_raw = array_filter($_GET, function($key) {
            return $key != 'altum';
        }, ARRAY_FILTER_USE_KEY);

        $thank_you_url_parameters = '&plan_id=' . $this->plan_id;
        $thank_you_url_parameters .= '&user_id=' . $this->user->user_id;

        foreach($thank_you_url_parameters_raw as $key => $value) {
            $thank_you_url_parameters .= '&' . $key . '=' . $value;
        }

        $thank_you_url_parameters .= '&unique_transaction_identifier=' . md5(\Altum\Date::get('', 4) . $thank_you_url_parameters);

        redirect('pay-thank-you?' . $thank_you_url_parameters);
    }

    private function calculate_price_with_taxes($discounted_price) {

        $price = $discounted_price;

        if($this->plan_taxes) {

            /* Check for the inclusives */
            $inclusive_taxes_array = [];

            foreach($this->plan_taxes as $row) {

                if($row->type == 'exclusive') {
                    continue;
                }

                $inclusive_tax = $price - ($price / (1 + $row->value / 100));

                $inclusive_taxes_array[] = $inclusive_tax;

            }

            $inclusive_taxes = array_sum($inclusive_taxes_array);

            $price_without_inclusive_taxes = $price - $inclusive_taxes;


            /* Check for the exclusives */
            $exclusive_taxes_array = [];

            foreach($this->plan_taxes as $row) {

                if($row->type == 'inclusive') {
                    continue;
                }

                $exclusive_tax = $row->value_type == 'percentage' ? $price_without_inclusive_taxes * ($row->value / 100) : $row->value;

                $exclusive_taxes_array[] = $exclusive_tax;

            }

            $exclusive_taxes = array_sum($exclusive_taxes_array);

            /* Price with all the taxes */
            $price_with_taxes = $price + $exclusive_taxes;

            $price = $price_with_taxes;
        }

        return $price;

    }
}
