<?php

namespace ProfilePress\Core\Membership\PaymentMethods\Stripe;

use ProfilePress\Core\Membership\Controllers\CheckoutResponse;
use ProfilePress\Core\Membership\Models\Customer\CustomerEntity;
use ProfilePress\Core\Membership\Models\Customer\CustomerFactory;
use ProfilePress\Core\Membership\Models\Order\CartEntity;
use ProfilePress\Core\Membership\Models\Order\OrderEntity;
use ProfilePress\Core\Membership\Models\Order\OrderFactory;
use ProfilePress\Core\Membership\Models\Plan\PlanEntity;
use ProfilePress\Core\Membership\Models\Plan\PlanFactory;
use ProfilePress\Core\Membership\Models\Subscription\SubscriptionEntity;
use ProfilePress\Core\Membership\Models\Subscription\SubscriptionFactory;
use ProfilePress\Core\Membership\PaymentMethods\AbstractPaymentMethod;
use ProfilePress\Core\Membership\PaymentMethods\PaymentMethods;
use ProfilePress\Core\Membership\PaymentMethods\WebhookHandlerInterface;
use ProfilePress\Core\Membership\Services\Calculator;
use ProfilePress\Core\Membership\Services\TaxService;
use ProfilePress\Core\RegisterScripts;
use ProfilePress\Core\ShortcodeParser\MyAccount\MyAccountTag;
use ProfilePressVendor\Stripe\Webhook as StripeWebhook;
use WP_Error;

class Stripe extends AbstractPaymentMethod
{
    public function __construct()
    {
        parent::__construct();

        $this->id          = 'stripe';
        $this->title       = 'Credit Card (Stripe)';
        $this->description = esc_html__('Pay with your credit card via Stripe', 'wp-user-avatar');

        $this->method_title       = 'Stripe';
        $this->method_description = esc_html__('Accept various payment methods including Credit Card, Apple & Google Pay via Stripe.', 'wp-user-avatar');
        $this->method_description .= ( ! isset($_GET['method']) && true === PaymentHelpers::has_application_fee()) ?
            ' ' . sprintf(
                esc_html__('NOTE: The free version of ProfilePress includes an additional 2%% fee for processing payments. Remove the fee by %supgrading to premium%s.', 'wp-user-avatar'),
                '<a target="_blank" href="https://profilepress.com/pricing/?utm_source=wp_dashboard&utm_medium=upgrade&utm_campaign=stripe-gateway-method">', '</a>'
            )
            : '';

        $this->icon = PPRESS_ASSETS_URL . '/images/cards-icon.svg';

        $this->supports = [
            self::SUBSCRIPTIONS,
            self::REFUNDS,
        ];

        add_action('admin_init', [$this, 'save_stripe_connect']);
        add_action('admin_init', [$this, 'disconnect_stripe_account']);
        add_action('admin_init', [$this, 'maybe_update_webhook']);
        add_action('ppress_admin_notices', [$this, 'output_connection_error']);

        add_filter('ppress_update_order_review_response', [$this, 'filter_update_order_review_response'], 10, 2);

        add_filter('ppress_myaccount_subscription_actions', [$this, 'manage_subscription_button'], 10, 3);
        add_action('ppress_handle_subscription_actions', [$this, 'handle_manage_subscription_action'], 10, 2);
    }

    public function has_fields()
    {
        return ! $this->is_offsite_checkout_style();
    }

    protected function is_offsite_checkout_style()
    {
        return $this->get_value('checkout_style', 'onsite') == 'offsite';
    }

    protected function is_billing_fields_removed()
    {
        return $this->get_value('remove_billing_fields') == 'true';
    }

    public function output_connection_error()
    {
        if (ppressGET_var('section') == 'payment-methods' && ppressGET_var('method') == 'stripe' && ! empty($_GET['oauth-error'])) {

            echo '<div class="notice notice-error is-dismissible"><p>';
            echo '<strong>' . esc_html__('Stripe Connect Error:', 'wp-user-avatar') . '</strong> ' . esc_html(ppressGET_var('oauth-error'));
            echo '</p></div>';
        }
    }

    public function save_stripe_connect()
    {
        if (ppressGET_var('pp-save-auth') == 'stripe' && ! empty($_GET['access_token'])) {

            if (current_user_can('manage_options') && wp_verify_nonce($_GET['ppnonce'], 'ppress_stripe_auth') !== false) {
                $mode                                        = ppressGET_var('livemode') === false ? 'test' : 'live';
                $old                                         = get_option(PPRESS_PAYMENT_METHODS_OPTION_NAME, []);
                $old['stripe_' . $mode . '_publishable_key'] = sanitize_text_field($_GET['stripe_publishable_key']);
                $old['stripe_' . $mode . '_secret_key']      = sanitize_text_field($_GET['access_token']);
                $old['stripe_' . $mode . '_user_id']         = sanitize_text_field($_GET['stripe_user_id']);
                $old['stripe_connect_mode']                  = $mode;
                update_option(PPRESS_PAYMENT_METHODS_OPTION_NAME, $old);

                WebhookHelpers::add_update_endpoint();
            }

            wp_safe_redirect($this->get_admin_page_url());
            exit;
        }
    }

    public function maybe_update_webhook()
    {
        if (wp_doing_ajax() || apply_filters('ppress_stripe_disable_maybe_update_webhook', false)) return;

        try {

            // bail if stripe is not connected
            if (empty(ppress_get_payment_method_setting('stripe_connect_mode', ''))) return;

            $mode = ppress_is_test_mode() ? 'test' : 'live';

            $endpoint_id = ppress_get_payment_method_setting('stripe_' . $mode . '_webhook_endpoint_id', '');

            if ( ! empty($endpoint_id) && PPRESS_STRIPE_API_VERSION !== ppress_get_payment_method_setting("stripe_{$mode}_webhook_api_version")) {
                WebhookHelpers::delete($endpoint_id);
                $endpoint_id = '';
            }

            if (empty($endpoint_id)) {
                WebhookHelpers::create();

                return;
            }

            $endpoint_url = ppress_get_payment_method_setting("stripe_{$mode}_webhook_endpoint_url", '');

            if (empty($endpoint_url) || $endpoint_url !== WebhookHelpers::webhook_url()) {
                $endpoint = WebhookHelpers::get($endpoint_id);
                WebhookHelpers::update($endpoint);

                return;
            }

            $endpoint_events = ppress_get_payment_method_setting("stripe_{$mode}_webhook_endpoint_events", []);

            if (empty($endpoint_events) || (
                    // Not all events, and saved is fewer than our current whitelist.
                    ! in_array('*', $endpoint_events, true) &&
                    count($endpoint_events) < count(WebhookHelpers::get_event_whitelist())
                )
            ) {
                $endpoint = WebhookHelpers::get($endpoint_id);
                WebhookHelpers::update($endpoint);

                return;
            }

        } catch (\Exception $e) {
            // Fail silently.
        }
    }

    public function disconnect_stripe_account()
    {
        if (ppressGET_var('section') == 'payment-methods' && ppressGET_var('method') == 'stripe' && ppressGET_var('ppress-stripe-disconnect') == 'true') {

            if (current_user_can('manage_options') && wp_verify_nonce($_GET['ppnonce'], 'ppress_stripe_disconnect') !== false) {

                $data                     = get_option(PPRESS_PAYMENT_METHODS_OPTION_NAME, []);
                $test_webhook_endpoint_id = ppress_get_payment_method_setting('stripe_test_webhook_endpoint_id', '');
                $live_webhook_endpoint_id = ppress_get_payment_method_setting('stripe_live_webhook_endpoint_id', '');

                try {
                    WebhookHelpers::delete($test_webhook_endpoint_id, true);
                    WebhookHelpers::delete($live_webhook_endpoint_id, true);
                } catch (\Exception $e) {
                }

                unset($data['stripe_live_user_id']);
                unset($data['stripe_test_user_id']);

                unset($data['stripe_test_publishable_key']);
                unset($data['stripe_live_publishable_key']);

                unset($data['stripe_test_secret_key']);
                unset($data['stripe_live_secret_key']);

                unset($data['stripe_connect_mode']);

                unset($data['stripe_connect_account_country']);

                unset($data['stripe_test_webhook_secret']);
                unset($data['stripe_live_webhook_secret']);

                unset($data['stripe_test_webhook_endpoint_id']);
                unset($data['stripe_live_webhook_endpoint_id']);

                unset($data['stripe_test_webhook_endpoint_events']);
                unset($data['stripe_live_webhook_endpoint_events']);

                update_option(PPRESS_PAYMENT_METHODS_OPTION_NAME, $data);
            }

            wp_safe_redirect($this->get_admin_page_url());
            exit;
        }
    }

    public function admin_connection_status_block()
    {
        $html = '';

        $mode = ppress_is_test_mode() ? __('test', 'wp-user-avatar') : __('live', 'wp-user-avatar');

        if ( ! Helpers::get_account_user_id() || ! Helpers::check_keys_exist()) {
            $html .= Helpers::get_connect_button($this->get_admin_page_url());
        } else {
            $html .= '<div class="pp-alert-notice pp-alert-notice-info"><p>';
            $html .= Helpers::get_account_information($this->get_admin_page_url());
            $html .= '</p></div>';

            $html .= '<p id="ppress-stripe-activated-account-actions">' . sprintf(
                    __('Your Stripe account is connected in %1$s mode. %2$sDisconnect this account%3$s.', 'wp-user-avatar'),
                    '<strong>' . $mode . '</strong>',
                    '<a class="pp-confirm-delete" href="' . esc_url(Helpers::get_disconnect_url($this->get_admin_page_url())) . '" class="ppress-disconnect-link">',
                    '</a>'
                ) . '</p>';
        }

        return $html;
    }

    public function admin_settings()
    {
        $settings = [
            'connection_status' => [
                'type'  => 'custom_field_block',
                'label' => esc_html__('Connection Status', 'wp-user-avatar'),
                'data'  => $this->admin_connection_status_block(),
            ]
        ];

        $fee_message = ( ! Helpers::check_keys_exist() && true === PaymentHelpers::has_application_fee()) ?
            sprintf(
                esc_html__('Connect now to start accepting payments instantly. This includes an additional 2%% payment processing fee. Remove the fee by %supgrading to premium%s.', 'wp-user-avatar'),
                '<a target="_blank" href="https://profilepress.com/pricing/?utm_source=wp_dashboard&utm_medium=upgrade&utm_campaign=stripe-inner-gateway-method">', '</a>'
            )
            : '';

        if (ppress_is_test_mode() && ! empty($fee_message)) {
            $settings['connection_status']['description'] = $fee_message;
        }

        $settings = array_merge($settings, parent::admin_settings());

        $connect_mode       = ppress_get_payment_method_setting('stripe_connect_mode', ppress_is_test_mode() ? 'test' : 'live');
        $connect_mode_label = $connect_mode == 'test' ? esc_html__('Test', 'wp-user-avatar') : esc_html__('Live', 'wp-user-avatar');
        $stripe_webhook_url = $connect_mode == 'test' ? 'https://dashboard.stripe.com/test/webhooks' : 'https://dashboard.stripe.com/webhooks';

        if (empty(Helpers::get_webhook_secret())) {

            $settings['webhook_info'] = [
                'label' => esc_html__('Webhook Setup', 'wp-user-avatar'),
                'type'  => 'custom_field_block',
                'data'  => sprintf(
                    __('In order for Stripe to function well, ensure the webhook endpoint %1$s is present in the %2$sStripe webhooks settings%3$s. Learn more from our %4$swebhook documentation%3$s', 'wp-user-avatar'),
                    '<code>' . esc_url($this->get_webhook_url()) . '</code>',
                    '<a target="_blank" href="' . esc_url($stripe_webhook_url) . '">',
                    '</a>',
                    '<a target="_blank" href="https://profilepress.com/article/setting-up-stripe/#Webhooks">'
                ),
            ];
        }

        $secret_connect_mode_label                   = ($connect_mode == 'test') ? esc_html__('test mode', 'wp-user-avatar') : esc_html__('live mode', 'wp-user-avatar');
        $settings[$connect_mode . '_webhook_secret'] = [
            'label'       => $connect_mode_label . ' ' . esc_html__('Webhook Secret', 'wp-user-avatar'),
            'type'        => 'password',
            'description' => sprintf(
                __(
                    'Retrieve your %3$s "Signing secret" from your %1$sStripe webhook settings%2$s. Select the endpoint then click "Reveal".',
                    'wp-user-avatar'
                ),
                '<a href="' . $stripe_webhook_url . '" target="_blank" rel="noopener noreferrer">',
                '</a>',
                '<strong>' . $secret_connect_mode_label . '</strong>'
            ),
        ];

        $settings['statement_descriptor'] = [
            'label'       => esc_html__('Statement Descriptor', 'wp-user-avatar'),
            'type'        => 'text',
            'description' => sprintf(
                esc_html__('The text that appears on your customer\'s bank or credit card statements. Choose something they will recognise to help prevent disputes, typically your business name. Must be limited to 22 characters, no special characters %1$s<%2$s, %1$s>%2$s, %1$s\'%2$s, or %1$s"%2$s.', 'wp-user-avatar'),
                '<code>', '</code>'
            )
        ];

        $settings['checkout_style'] = [
            'label'       => esc_html__('Payment Collection Method', 'wp-user-avatar'),
            'type'        => 'select',
            'options'     => [
                'onsite'  => esc_html__('Stripe Credit Card Field (On-site)', 'wp-user-avatar'),
                'offsite' => esc_html__('Stripe Payment Page (Off-site)', 'wp-user-avatar')
            ],
            'description' => esc_html__('Select how payment information will be collected. Could be right on your site with Stripe card fields or off-site through Stripe hosted payment page.', 'wp-user-avatar')
        ];

        $settings['tax_settings'] = [
            'label'          => esc_html__('Enable Stripe Tax', 'wp-user-avatar'),
            'type'           => 'checkbox',
            'checkbox_label' => esc_html__('Check to automatically calculate and charge taxes via Stripe Tax.', 'wp-user-avatar'),
            'description'    => sprintf(
                esc_html__('This is only available when using Stripe Payment Page (Off-site) and have %sStripe Tax%s enabled in your Stripe account.', 'wp-user-avatar'),
                '<a target="_blank" href="https://dashboard.stripe.com/settings/tax/activate">', '</a>'
            )
        ];

        $settings['remove_billing_fields'] = [
            'label'          => esc_html__('Remove Billing Address', 'wp-user-avatar'),
            'type'           => 'checkbox',
            'checkbox_label' => esc_html__('Check to remove billing address fields from the checkout page.', 'wp-user-avatar'),
            'description'    => esc_html__('If you do not want the billing address fields displayed on the checkout page, use this setting to remove it.', 'wp-user-avatar')
        ];

        $settings['restrict_assets'] = [
            'label'          => esc_html__('Restrict Stripe Assets', 'wp-user-avatar'),
            'type'           => 'checkbox',
            'checkbox_label' => esc_html__('Only load Stripe.com hosted assets on pages that specifically utilize Stripe functionality.', 'wp-user-avatar'),
            'description'    => sprintf(
                esc_html__('Stripe advises that their Javascript library be loaded on every page to take advantage of their advanced fraud detection rules. If you are not concerned with this, enable this setting to only load the Javascript when necessary. %sLearn more%s', 'wp-user-avatar'),
                '<a target="_blank" href="https://stripe.com/docs/js/including">', '</a>'
            )
        ];

        return $settings;
    }

    /**
     * @return bool|WP_Error
     */
    public function validate_fields()
    {
        if ( ! $this->is_offsite_checkout_style() && ! $this->is_billing_fields_removed()) {

            if ( ! isset($_POST['stripe-card_name']) || strlen(trim($_POST['stripe-card_name'])) === 0) {

                return new WP_Error(
                    'no_card_name',
                    esc_html__('Please enter a name for the credit card.', 'wp-user-avatar')
                );
            }
        }

        return true;
    }

    /**
     * Disable billing validation.
     *
     * @param $val
     *
     * @return bool
     */
    public function should_validate_billing_details($val)
    {
        if ($this->is_offsite_checkout_style() || $this->is_billing_fields_removed()) {
            $val = false;
        }

        return $val;
    }

    public function get_statement_descriptor()
    {
        $statement_descriptor = $this->get_value('statement_descriptor');

        if (empty($statement_descriptor)) return '';

        $unsupported_characters = apply_filters('ppress_stripe_statement_descriptor_unsupported_characters', [
            '<',
            '>',
            '"',
            '\'',
            '\\',
            '*',
        ]);

        $statement_descriptor = trim(str_replace($unsupported_characters, '', $statement_descriptor));

        return substr($statement_descriptor, 0, 22);
    }

    public function link_transaction_id($transaction_id, $order)
    {
        if ( ! empty($transaction_id)) {
            return sprintf('<a target="_blank" href="https://dashboard.stripe.com/payments/%1$s">%1$s</a>', $transaction_id);
        }

        return $transaction_id;
    }

    public function link_profile_id($profile_id, $subscription)
    {
        if ( ! empty($profile_id)) {
            return sprintf('<a target="_blank" href="https://dashboard.stripe.com/subscriptions/%1$s">%1$s</a>', $profile_id);
        }

        return $profile_id;
    }

    /**
     * Determines if the subscription can be cancelled
     *
     * @param $ret
     *
     * @param SubscriptionEntity $subscription
     *
     * @return      bool
     */
    public function can_cancel($ret, $subscription)
    {
        if ($subscription->get_payment_method() === $this->id && ! empty($subscription->profile_id) && in_array($subscription->status, $this->get_cancellable_statuses())) {
            return true;
        }

        return $ret;
    }

    /**
     * @param SubscriptionEntity $subscription
     *
     * @return bool
     *
     */
    private function stripe_cancel($subscription)
    {
        try {

            // Before we cancel, lets make sure this subscription exists at Stripe.
            $stripeSubObj = APIClass::stripeClient()->subscriptions->retrieve($subscription->profile_id);

            if ('canceled' === $stripeSubObj->status || empty($subscription->profile_id)) return false;

            APIClass::stripeClient()->subscriptions->cancel($subscription->profile_id);

        } catch (\Exception $e) {

            $subscription->add_note(
                sprintf(
                    esc_html__('Attempted cancellation but was unable. Message was "%s".', 'wp-user-avatar'),
                    wp_json_encode($e->getMessage())
                )
            );

            return false;
        }

        return true;
    }

    /**
     * Cancels a subscription at period end, unless the status of the subscription is failing. If failing, cancel immediately.
     *
     * @param SubscriptionEntity $subscription
     *
     * @return bool
     */
    public function cancel($subscription)
    {
        return $this->stripe_cancel($subscription);
    }

    public function enqueue_frontend_assets()
    {
        if ( ! PaymentMethods::get_instance()->get_by_id('stripe')->is_enabled()) return;

        wp_register_script(
            'ppress-stripe-v3',
            'https://js.stripe.com/v3/',
            [],
            null
        );

        if ('false' === $this->get_value('restrict_assets', 'false')) {
            wp_enqueue_script('ppress-stripe-v3');
        }

        if ($this->is_offsite_checkout_style()) return;

        if ( ! function_exists('ppress_is_checkout')) return;

        $publishable_key = Helpers::get_publishable_key();

        $suffix = RegisterScripts::asset_suffix();

        wp_register_script(
            'ppress-stripe-js',
            PPRESS_ASSETS_URL . "/js/stripe/stripe{$suffix}.js",
            array(
                'jquery',
                'ppress-stripe-v3',
                'ppress-frontend-script'
            ),
            PPRESS_VERSION_NUMBER,
            true
        );

        $is_checkout = ppress_is_checkout();

        if ($is_checkout || 'false' === $this->get_value('restrict_assets', 'false')) {
            wp_enqueue_script('ppress-stripe-v3');
        }

        if ($is_checkout) {

            wp_enqueue_script('ppress-stripe-js');

            $stripe_vars = [
                'publishable_key'   => trim($publishable_key),
                'hideBillingFields' => 'true',
                'locale'            => apply_filters('ppress_stripe_checkout_locale', 'auto')
            ];

            if ($this->is_billing_fields_removed()) {
                $stripe_vars['hideBillingFields'] = 'false';
            }

            $stripe_vars = apply_filters('ppress_stripe_js_vars', $stripe_vars);

            wp_localize_script('ppress-stripe-js', 'ppress_stripe_vars', $stripe_vars);
        }
    }

    protected function billing_address_form()
    {
        if ($this->is_offsite_checkout_style() && ! TaxService::init()->is_tax_enabled()) return;

        if ($this->is_billing_fields_removed()) return;

        parent::billing_address_form();
    }

    public function credit_card_form()
    {
        if ($this->is_offsite_checkout_style()) return;

        if ( ! $this->is_billing_fields_removed()) :
            ?>
            <div class="ppress-main-checkout-form__block__item">
                <label for="<?= esc_attr($this->id . '-' . 'card_name') ?>">
                    <?php esc_html_e('Name on card', 'wp-user-avatar') ?>
                    <span class="ppress-required">*</span> </label>
                <input id="<?= esc_attr($this->id . '-' . 'card_name') ?>" name="<?= esc_attr($this->id . '-' . 'card_name') ?>" class="ppress-checkout-field__input" type="text" autocomplete="cc-name">
            </div>
        <?php endif; ?>

        <div id="ppress-stripe-card-element-wrapper" class="ppress-main-checkout-form__block__item">
            <div id="ppress-stripe-card-element"></div>
        </div>
        <?php
    }

    /**
     * @param OrderEntity $order
     * @param SubscriptionEntity $subscription
     *
     * @return array
     */
    public function get_order_metadata($order, $subscription)
    {
        return apply_filters('ppress_stripe_order_metadata', [
            'order_id'        => $order->id,
            'order_key'       => $order->order_key,
            'customer_id'     => $order->customer_id,
            'subscription_id' => $subscription->id,
            'caller'          => __CLASS__ . '|' . __METHOD__ . '|' . __LINE__ . '|' . PPRESS_VERSION_NUMBER,
        ], $order, $subscription);
    }

    /**
     * @param $response
     * @param CartEntity $cart_vars
     *
     * @return void
     */
    public function filter_update_order_review_response($response, $cart_vars)
    {
        $plan = ppress_get_plan($cart_vars->plan_id);

        $response['stripe_args'] = apply_filters('ppress_stripe_js_args', [
            'mode'     => $plan->is_auto_renew() ? 'subscription' : 'payment',
            'currency' => strtolower(ppress_get_currency()),
            'amount'   => absint(PaymentHelpers::process_amount($cart_vars->total))
        ], $cart_vars, $response);

        return $response;
    }

    /**
     * @param array $actions
     * @param SubscriptionEntity $sub
     * @param AbstractPaymentMethod|false $payment_method
     *
     * @return mixed
     */
    public function manage_subscription_button($actions, $sub, $payment_method)
    {
        if ( ! empty($sub->profile_id) && is_object($payment_method) && $payment_method->id == $this->id) {
            $actions['stripe_manage_subscription'] = esc_html__('Manage Subscription', 'wp-user-avatar');
        }

        return $actions;
    }

    /**
     * @param string $action
     * @param SubscriptionEntity $sub
     *
     * @return void
     */
    public function handle_manage_subscription_action($action, $sub)
    {
        if ($action == 'stripe_manage_subscription') {

            try {
                $response = APIClass::stripeClient()->billingPortal->sessions->create([
                    'customer'   => $sub->get_customer()->get_meta('stripe_customer_id'),
                    'return_url' => add_query_arg(['sub_id' => $sub->id], MyAccountTag::get_endpoint_url('list-subscriptions'))
                ]);

                if (isset($response->url)) {
                    wp_redirect($response->url);
                    exit;
                }

            } catch (\Exception $e) {
                ppress_log_error($e->getMessage());
            }

            wp_die(esc_html__('Unable to generate Stripe customer portal URL. Please try again', 'wp-user-avatar'));
        }
    }

    /**
     * @param OrderEntity $order
     * @param CustomerEntity $customer
     * @param SubscriptionEntity $subscription
     * @param PlanEntity $plan
     *
     * @return CheckoutResponse
     */
    public function process_offsite_payment($order, $customer, $subscription, $plan)
    {
        try {

            PaymentHelpers::empty_coupon_bucket();

            $product_price = PaymentHelpers::get_product_price($subscription, $this->get_statement_descriptor());

            if (is_wp_error($product_price)) {
                throw new \Exception($product_price->get_error_message());
            }

            $price_id = ppress_var($product_price, 'stripe_price_id');

            $stripe_customer_id = PaymentHelpers::get_stripe_customer_id($customer);

            $create_session_args_metadata = $this->get_order_metadata($order, $subscription);

            $create_session_args = [
                'success_url'         => add_query_arg(['session_id' => '{CHECKOUT_SESSION_ID}'], $this->get_success_url($order->order_key)),
                'cancel_url'          => ppress_plan_checkout_url($plan->id),
                'line_items'          => [
                    [
                        'price'    => $price_id,
                        'quantity' => 1,
                    ],
                ],
                'mode'                => $plan->is_auto_renew() ? 'subscription' : 'payment',
                'client_reference_id' => $order->order_key,
                'customer'            => $stripe_customer_id,
                'customer_update'     => ['address' => 'auto', 'name' => 'auto'],
                'metadata'            => $create_session_args_metadata
            ];

            if ($this->get_value('tax_settings') == 'true') {
                $create_session_args['automatic_tax'] = ['enabled' => true];
            }

            if ( ! $plan->is_auto_renew()) {
                $create_session_args['payment_intent_data']['metadata'] = $create_session_args_metadata;
            } else {
                $create_session_args['subscription_data']['metadata'] = $create_session_args_metadata;
            }

            if ($plan->has_signup_fee()) {

                $signup_fee = $plan->has_free_trial() ? $order->total : '0';

                if (
                    ! $plan->has_free_trial() &&
                    Calculator::init($order->total)->isGreaterThan($subscription->recurring_amount)) {
                    $signup_fee = Calculator::init($order->total)->minus($subscription->recurring_amount)->val();
                }

                if (Calculator::init($signup_fee)->isGreaterThanZero()) {

                    $create_session_args['line_items'][] = [
                        'quantity'   => 1,
                        'price_data' => [
                            'unit_amount'  => PaymentHelpers::process_amount($signup_fee),
                            'currency'     => ppress_get_currency(),
                            'product_data' => [
                                'name' => PaymentHelpers::get_signup_fee_label(),
                            ],
                        ]
                    ];
                }
            }

            if ($plan->is_auto_renew() && $plan->has_free_trial()) {

                $trial_period_days = PaymentHelpers::free_trial_days_count($plan->free_trial);

                if ($trial_period_days > 0) {
                    $create_session_args['subscription_data']['trial_period_days'] = $trial_period_days;
                }
            }

            if (
                $plan->is_auto_renew() &&
                ! $plan->has_free_trial() &&
                Calculator::init($order->total)->isLessThan($subscription->recurring_amount)
            ) {

                $discount_amount = Calculator::init($subscription->recurring_amount)->minus($order->total)->val();

                $stripe_coupon = APIClass::stripeClient()->coupons->create([
                    'amount_off' => PaymentHelpers::process_amount($discount_amount),
                    'currency'   => ppress_get_currency(),
                    'duration'   => 'once'
                ])->toArray();

                $create_session_args['discounts'][] = ['coupon' => $stripe_coupon['id']];

                PaymentHelpers::add_coupon_to_bucket($stripe_coupon['id']);
            }

            $create_session_args = apply_filters('ppress_stripe_create_session_args', $create_session_args, $this, $customer, $order, $subscription);

            if (PaymentHelpers::has_application_fee()) {

                if ($plan->is_auto_renew()) {
                    $create_session_args['subscription_data']['application_fee_percent'] = PaymentHelpers::application_fee_percent();
                } else {
                    $create_session_args['payment_intent_data']['application_fee_amount'] = PaymentHelpers::application_fee_amount($order->total);
                }
            }

            $session_response = APIClass::stripeClient()->checkout->sessions->create($create_session_args)->toArray();

            $checkoutResponse = new CheckoutResponse();

            if (isset($session_response['url'])) {

                return $checkoutResponse
                    ->set_is_success(true)
                    ->set_redirect_url($session_response['url'])
                    ->set_gateway_response($session_response);
            }

            throw new \Exception($checkoutResponse->get_generic_error_message());

        } catch (\Exception $e) {

            ppress_log_error(__METHOD__ . '(): ' . $e->getMessage());

            return (new CheckoutResponse())
                ->set_is_success(false)
                ->set_error_message($e->getMessage());
        }
    }

    /**
     * @param $customer_id
     * @param $checkout_metadata
     *
     * @return array
     *
     * @throws \Exception
     */
    public function create_setup_intent($customer_id, $checkout_metadata)
    {
        $create_setup_intent_args = [
            'customer' => $customer_id,
            'metadata' => $checkout_metadata
        ];

        $create_setup_intent_args = apply_filters('ppress_stripe_create_setup_intent_args', $create_setup_intent_args, $this);

        return APIClass::stripeClient()->setupIntents->create($create_setup_intent_args)->toArray();
    }

    public function process_payment($order_id, $subscription_id, $customer_id)
    {
        $order        = OrderFactory::fromId($order_id);
        $subscription = SubscriptionFactory::fromId($subscription_id);
        $customer     = CustomerFactory::fromId($customer_id);

        $plan = PlanFactory::fromId($order->plan_id);

        if ($this->is_offsite_checkout_style()) {
            return $this->process_offsite_payment($order, $customer, $subscription, $plan);
        }

        try {

            $stripe_coupon = false;

            $customer_id = PaymentHelpers::get_stripe_customer_id($customer);

            $product_price = PaymentHelpers::get_product_price($subscription, $this->get_statement_descriptor());

            $checkout_metadata = $this->get_order_metadata($order, $subscription);

            if ($plan->is_auto_renew()) {

                $create_subscription_args = [
                    'customer'         => $customer_id,
                    'items'            => [
                        [
                            'price'    => ppress_var($product_price, 'stripe_price_id'),
                            'quantity' => 1,
                            'metadata' => $checkout_metadata
                        ]
                    ],
                    'metadata'         => $checkout_metadata,
                    'payment_behavior' => 'default_incomplete',
                    'payment_settings' => ['save_default_payment_method' => 'on_subscription'],
                    'expand'           => ['latest_invoice.payment_intent']
                ];

                $signup_fee = Calculator::init($order->total)->minus($subscription->recurring_amount)->val();

                // if there is a free trial, no amount is charged hence order total is 0
                // however if the plan has a signup fee, it becomes the order total hence the below code.
                if ($plan->has_free_trial()) {

                    $signup_fee = $order->total;

                    $trial_period_days = PaymentHelpers::free_trial_days_count($plan->free_trial);

                    if ($trial_period_days > 0) {
                        $create_subscription_args['trial_period_days'] = $trial_period_days;
                    }
                }

                if (Calculator::init($signup_fee)->isGreaterThanZero()) {

                    $create_subscription_args['add_invoice_items'][] = [
                        'quantity'   => 1,
                        'price_data' => [
                            'product'     => $product_price['stripe_product_id'],
                            'unit_amount' => PaymentHelpers::process_amount($signup_fee),
                            'currency'    => ppress_get_currency(),
                        ]
                    ];
                }

                if (
                    ! $plan->has_free_trial() &&
                    Calculator::init($order->total)->isLessThan($subscription->recurring_amount)
                ) {

                    $discount_amount = Calculator::init($subscription->recurring_amount)->minus($order->total)->val();

                    $stripe_coupon = APIClass::stripeClient()->coupons->create([
                        'amount_off' => PaymentHelpers::process_amount($discount_amount),
                        'currency'   => ppress_get_currency(),
                        'duration'   => 'once'
                    ])->toArray();

                    $create_subscription_args['coupon'] = $stripe_coupon['id'];
                }

                $create_subscription_args = apply_filters('ppress_stripe_create_subscription_args', $create_subscription_args, $this, $customer, $order, $subscription);

                if (PaymentHelpers::has_application_fee()) {
                    $create_subscription_args['application_fee_percent'] = PaymentHelpers::application_fee_percent();
                }

                $response = APIClass::stripeClient()->subscriptions->create($create_subscription_args)->toArray();

                if (isset($response['latest_invoice'])) {
                    $subscription->update_profile_id($response['id']);
                }

                if (false !== $stripe_coupon) {
                    PaymentHelpers::delete_coupon($stripe_coupon['id']);
                }

                // if order total is $0 and not signup fee (total amount charged is $0), create a setup intent to save customer
                // payment method so when trial ends, they can be charged.
                if (Calculator::init($order->total)->isNegativeOrZero() && Calculator::init($signup_fee)->isNegativeOrZero()) {

                    $setup_intent_response = $this->create_setup_intent($customer_id, $checkout_metadata);

                    if (is_array($response) && isset($setup_intent_response['id'])) {
                        $order->update_meta('stripe_setup_intent', $setup_intent_response['id']);
                        $response['setup_intent_response'] = $setup_intent_response;
                    }
                }

                return (new CheckoutResponse())
                    ->set_is_success(true)
                    ->set_gateway_response($response);
            }

            $create_payment_intent_args = [
                'amount'                    => PaymentHelpers::process_amount($order->total),
                'currency'                  => ppress_get_currency(),
                'customer'                  => $customer_id,
                'description'               => $plan->name,
                'metadata'                  => $checkout_metadata,
                'automatic_payment_methods' => ['enabled' => true]
            ];

            $statement_descriptor = $this->get_statement_descriptor();

            if ( ! empty($statement_descriptor)) {
                $create_payment_intent_args['statement_descriptor'] = $statement_descriptor;
            }

            $create_payment_intent_args = apply_filters('ppress_stripe_create_payment_intent_args', $create_payment_intent_args, $this, $customer, $order, $subscription);

            if (PaymentHelpers::has_application_fee()) {
                $create_payment_intent_args['application_fee_amount'] = PaymentHelpers::application_fee_amount($order->total);
            }

            $response = APIClass::stripeClient()->paymentIntents->create($create_payment_intent_args)->toArray();

            if (isset($response['id'])) {
                $order->update_transaction_id($response['id']);
            }

            return (new CheckoutResponse())
                ->set_is_success(true)
                ->set_gateway_response($response);

        } catch (\Exception $e) {

            ppress_log_error(__METHOD__ . '(): ' . $e->getMessage());

            return (new CheckoutResponse())
                ->set_is_success(false)
                ->set_error_message($e->getMessage());
        }
    }

    public function process_refund($order_id, $amount = null, $reason = '')
    {
        try {

            $order = OrderFactory::fromId($order_id);

            $response = APIClass::stripeClient()->refunds->create([
                'payment_intent' => $order->transaction_id,
            ]);

            switch ($response->status) {
                case 'succeeded':
                    return true;
                case 'pending':
                    $order->add_note(esc_html__('Refund request is pending', 'wp-user-avatar'));
                    break;
                case 'failed':
                    $order->add_note(esc_html__('Refund request failed', 'wp-user-avatar'));
                    break;
                default:
                    $order->add_note(sprintf(esc_html__('Refund request failed. Status: %s', 'wp-user-avatar'), $response->status));
                    break;
            }

            return false;

        } catch (\Exception $e) {
            ppress_log_error($e->getMessage() . '; OrderID:' . $order_id);

            return false;
        }
    }

    public function process_webhook()
    {
        APIClass::_setup();

        $endpoint_secret = Helpers::get_webhook_secret();
        if (defined('PPRESS_STRIPE_WEBHOOK_SECRET')) {
            $endpoint_secret = PPRESS_STRIPE_WEBHOOK_SECRET;
        }

        // If there is no webhook secret configured, we can't verify the webhook, so we bail early.
        if (empty($endpoint_secret)) {
            http_response_code(400);
            exit();
        }

        $payload    = @file_get_contents('php://input');
        $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];

        try {

            $event = StripeWebhook::constructEvent($payload, $sig_header, $endpoint_secret);

        } catch (\UnexpectedValueException $e) {
            // Invalid payload
            http_response_code(400);
            exit();
        } catch (\Exception $e) {
            // Invalid signature
            http_response_code(400);
            exit();
        }

        $event = $event->toArray();

        $webhooks = WebhookHelpers::valid_events();

        if (in_array($event['type'], array_keys($webhooks))) {
            /** @var WebhookHandlerInterface $callable */
            $callable = $webhooks[$event['type']];

            call_user_func([$callable, 'handle'], $event['data']['object']);

            do_action('ppress_stripe_webhook_event', $event['type'], $event);
        }

        http_response_code(200);
    }
}
