/home/edulekha/crm.edulekha.com/modules/ideal/libraries/Ideal_gateway.php
<?php

use Stripe\Checkout\Session;
use Stripe\Exception\ApiErrorException;
use Stripe\Exception\SignatureVerificationException;
use Stripe\PaymentIntent;
use Stripe\StripeClient;
use Stripe\Webhook;
use Stripe\WebhookEndpoint;

defined('BASEPATH') or exit('No direct script access allowed');

class Ideal_gateway extends App_gateway
{
    private static StripeClient $stripeClient;
    public bool $processingFees = true;
    private StripeClient $stripe;
    private string $webhookEndPoint;

    public function __construct()
    {
        parent::__construct();
        $this->setId(IDEAL_MODULE_GATEWAY_ID);
        $this->setName('Stripe iDEAL V2');
        $this->setSettings([
            [
                'name'  => 'api_publishable_key',
                'label' => 'ideal_api_publishable_key',
                'type'  => 'input',
            ],
            [
                'name'      => 'api_secret_key',
                'encrypted' => true,
                'label'     => 'ideal_api_secret_key',
                'type'      => 'input',
            ],
            [
                'name'             => 'currencies',
                'label'            => 'settings_paymentmethod_currencies',
                'default_value'    => 'EUR',
                'field_attributes' => ['disabled' => true],
            ],
            [
                'name'          => 'description_dashboard',
                'label'         => 'settings_paymentmethod_description',
                'type'          => 'textarea',
                'default_value' => 'Payment for Invoice {invoice_number}',
            ],
        ]);

        $this->webhookEndPoint = site_url('ideal/webhook');
        hooks()->add_action('before_render_payment_gateway_settings', 'idealModuleWebhookCheck');
    }

    public function isGatewayKeyConfigured(): bool
    {
        if (empty($this->getSetting('api_secret_key'))) {
            return true;
        }

        return $this->decryptSetting('api_secret_key') !== '' && $this->getSetting('api_publishable_key') !== '';
    }

    public function getClient(): StripeClient
    {
        if (! isset(self::$stripeClient)) {
            self::$stripeClient = new StripeClient([
                'api_key' => $this->decryptSetting('api_secret_key'),
            ]);
        }

        return self::$stripeClient;
    }

    /**
     * @throws ApiErrorException
     */
    public function process_payment(array $data): void
    {
        if (! $this->isGatewayKeyConfigured()) {
            $this->markAsInactive();
            set_alert('danger', _l('ideal_gateway_keys_not_configured'));
            redirect(site_url('invoice/' . $data['invoice']->id . '/' . $data['invoice']->hash));
        }

        if ($this->processingFees) {
            $this->ci->session->set_userdata([
                'attempt_fee'    => $data['payment_attempt']->fee,
                'attempt_amount' => $data['payment_attempt']->amount,
            ]);
        }

        $sessionData = [
            'payment_method_types' => ['ideal'],
            'line_items'           => [[
                'price_data' => [
                    'currency'     => $this->getSetting('currencies'),
                    'product_data' => [
                        'name' => $this->getDescription($data['invoiceid']),
                    ],
                    'unit_amount' => $data['amount'] * 100,
                ],
                'quantity' => 1,
            ]],
            'payment_intent_data' => [
                'capture_method' => 'automatic',
                'metadata'       => [
                    'invoice_id'        => $data['invoice']->id,
                    'attempt_reference' => $data['payment_attempt']->reference,
                    'attempt_fee'       => $data['payment_attempt']->fee,
                ],
            ],
            'mode'       => 'payment',
            'ui_mode'    => 'embedded',
            'return_url' => site_url("/ideal/callback/{$data['invoiceid']}/{$data['hash']}?session_id={CHECKOUT_SESSION_ID}"),
        ];
        if ($data['invoice']->client->stripe_id) {
            $sessionData['customer']                     = $data['invoice']->client->stripe_id;
            $sessionData['saved_payment_method_options'] = ['payment_method_save' => 'enabled'];
        }

        $session = $this->getClient()->checkout->sessions->create($sessionData);

        $this->ci->session->set_userdata([
            'total_amount'        => $data['amount'],
            'ideal_client_secret' => $session->client_secret,
        ]);

        redirect(site_url('/ideal/make_payment/' . $data['invoice']->id . '/' . $data['invoice']->hash));
    }

    public function getPublishableKey(): string
    {
        return $this->getSetting('api_publishable_key');
    }

    public function getDescription($invoiceId): string
    {
        $invoiceNumber = format_invoice_number($invoiceId);

        return str_replace('{invoice_number}', $invoiceNumber, $this->getSetting('description_dashboard'));
    }

    /**
     * @param mixed $sessionId
     *
     * @throws ApiErrorException
     */
    public function retrieveSession($sessionId): Session
    {
        return $this->getClient()->checkout->sessions->retrieve($sessionId);
    }

    /**
     * @param mixed $intentId
     *
     * @throws ApiErrorException
     */
    public function retrievePaymentIntent($intentId): PaymentIntent
    {
        return $this->getClient()->paymentIntents->retrieve($intentId);
    }

    public function hasSecretKey(): bool
    {
        return ! empty($this->decryptSetting('api_secret_key'));
    }

    public function getWebhookEndPoint(): string
    {
        return $this->webhookEndPoint;
    }

    /**
     * Determine the Stripe environment based on the keys
     */
    public function environment(): string
    {
        $environment = 'production';
        $apiKey      = $this->decryptSetting('api_secret_key');

        if (str_contains($apiKey, 'sk_test')) {
            $environment = 'test';
        }

        return $environment;
    }

    /**
     * @throws ApiErrorException
     */
    public function getCurrentWebhookObject(): ?WebhookEndpoint
    {
        $webhook = null;

        foreach ($this->getAllWebhookObjects() as $endpoint) {
            if ($endpoint->url == $this->getWebhookEndPoint()) {
                $webhook = $endpoint;
                break;
            }
        }

        return $webhook;
    }

    /**
     * @return WebhookEndpoint[]
     *
     * @throws ApiErrorException
     */
    public function getAllWebhookObjects(): array
    {
        return $this->getClient()->webhookEndpoints->all()->data;
    }

    /**
     * @throws ApiErrorException
     */
    public function deleteWebhook(WebhookEndpoint $webhookEndpoint): void
    {
        $this->getClient()->webhookEndpoints->delete($webhookEndpoint->id);
    }

    public function getIdentificationKey(): string
    {
        return 'ideal-gateway-v2-' . get_option('identification_key');
    }

    public function createWebhook(): WebhookEndpoint
    {
        $webhook = $this->getClient()->webhookEndpoints->create([
            'url'            => $this->webhookEndPoint,
            'enabled_events' => idealModuleWebhookEvents(),
            'metadata'       => ['identification_key' => $this->getIdentificationKey()],
        ]);
        update_option('ideal_module_stripe_webhook_id', $webhook->id);
        update_option('ideal_module_stripe_webhook_signing_secret', $webhook->secret);

        return $webhook;
    }

    /**
     * @throws ApiErrorException
     */
    public function enableCurrentWebhookEndpoint(): void
    {
        $this->getClient()->webhookEndpoints->update(
            $this->getCurrentWebhookObject()?->id,
            ['disabled' => false]
        );
    }

    /**
     * @throws SignatureVerificationException
     */
    public function validateAndReturnWebhookEvent(bool|string $payload): Stripe\Event
    {
        $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
        $secret     = get_option('ideal_module_stripe_webhook_signing_secret');

        return Webhook::constructEvent($payload, $sig_header, $secret);
    }
}