<?php
/* Copyright (C) 2018-2021 	Thibault FOUCART       <support@ptibogxiv.net>
 * Copyright (C) 2024       Frédéric France             <frederic.france@free.fr>
 * Copyright (C) 2024-2025	MDW							<mdeweerd@users.noreply.github.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

// Put here all includes required by your class file
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/stripe/config.php'; // This set stripe global $stripearrayofkeys and $stripearrayofkeysbyenv

/**
 *	Stripe class
 *  @TODO No reason to extend CommonObject
 */
class Stripe extends CommonObject
{
	/**
	 * @var int ID
	 */
	public $rowid;

	/**
	 * @var int Thirdparty ID
	 */
	public $fk_soc;

	/**
	 * @var int ID
	 */
	public $fk_key;

	/**
	 * @var string Stripe ID (Note: Conflict with CommonObject)
	 */
	public $id;  // @phpstan-ignore-line

	/**
	 * @var string
	 */
	public $mode;

	/**
	 * @var int Entity
	 */
	public $entity;

	/**
	 * @var string
	 */
	public $type;

	/**
	 * @var string
	 */
	public $code;

	/**
	 * @var string
	 */
	public $declinecode;

	/**
	 * @var string Message
	 */
	public $message;

	/**
	 * 	Constructor
	 *
	 * 	@param	DoliDB		$db			Database handler
	 */
	public function __construct($db)
	{
		$this->db = $db;
	}


	/**
	 * Return main company OAuth Connect stripe account
	 *
	 * @param 	'StripeTest'|'StripeLive'	$mode		'StripeTest' or 'StripeLive'
	 * @param	int							$fk_soc		Id of third party
	 * @param	int							$entity		Id of entity (-1 = current environment)
	 * @return 	string									Stripe account 'acc_....' or '' if no OAuth token found
	 */
	public function getStripeAccount($mode = 'StripeTest', $fk_soc = 0, $entity = -1)
	{
		global $conf;

		$key = '';
		if ($entity < 0) {
			$entity = $conf->entity;
		}

		$sql = "SELECT tokenstring";
		$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
		$sql .= " WHERE service = '".$this->db->escape($mode)."'";
		$sql .= " AND entity = ".((int) $entity);
		if ($fk_soc > 0) {
			$sql .= " AND fk_soc = ".((int) $fk_soc);
		} else {
			$sql .= " AND fk_soc IS NULL";
		}
		$sql .= " AND fk_user IS NULL AND fk_adherent IS NULL";

		dol_syslog(get_class($this)."::getStripeAccount", LOG_DEBUG);

		$result = $this->db->query($sql);
		if ($result) {
			if ($this->db->num_rows($result)) {
				$obj = $this->db->fetch_object($result);
				$tokenstring = $obj->tokenstring;

				if ($tokenstring) {
					$tmparray = json_decode($tokenstring);
					$key = empty($tmparray->stripe_user_id) ? '' : $tmparray->stripe_user_id;
				}
			} else {
				$tokenstring = '';
			}
		} else {
			dol_print_error($this->db);
		}

		dol_syslog("No dedicated Stripe Connect account available for entity ".$conf->entity);

		return $key;
	}

	/**
	 * getStripeCustomerAccount
	 *
	 * @param	int			$id				Id of third party
	 * @param	int<0,1>	$status			Status
	 * @param	string		$site_account 	Value to use to identify with account to use on site when site can offer several accounts. For example: 'pk_live_123456' when using Stripe service.
	 * @return	string						Stripe customer ref 'cu_xxxxxxxxxxxxx' or ''
	 */
	public function getStripeCustomerAccount($id, $status = 0, $site_account = '')
	{
		include_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
		$societeaccount = new SocieteAccount($this->db);
		return $societeaccount->getCustomerAccount($id, 'stripe', $status, $site_account); // Get thirdparty cus_...
	}


	/**
	 * Get the Stripe customer of a thirdparty (with option to create it in Stripe if not linked yet).
	 * Search on site_account = 0 or = $stripearrayofkeysbyenv[$status]['publishable_key']
	 *
	 * @param	Societe|Adherent	$object				Object thirdparty to check, or create on stripe (create on stripe also update the stripe_account table for current entity).  Used for Adherent and Societe.
	 * @param	string		$key							''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
	 * @param	int<0,1>	$status							Status (0=test, 1=live)
	 * @param	int<0,1>	$createifnotlinkedtostripe		1=Create the stripe customer and the link if the thirdparty is not yet linked to a stripe customer
	 * @return 	\Stripe\Customer|null 					Stripe Customer or null if not found
	 */
	public function customerStripe($object, $key = '', $status = 0, $createifnotlinkedtostripe = 0)
	{
		global $conf, $user;

		if (empty($object->id)) {
			dol_syslog("customerStripe is called with the parameter object that is not loaded");
			return null;
		}

		$customer = null;

		// Force to use the correct API key
		global $stripearrayofkeysbyenv;
		\Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);

		$sql = "SELECT sa.key_account as key_account, sa.entity"; // key_account is cus_....
		$sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
		$sql .= " WHERE sa.fk_soc = ".((int) $object->id);
		$sql .= " AND sa.entity IN (".getEntity('societe').")";
		$sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
		$sql .= " AND (sa.site_account IS NULL OR sa.site_account = '' OR sa.site_account = '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."')";
		$sql .= " AND sa.key_account IS NOT NULL AND sa.key_account <> ''";
		$sql .= " ORDER BY sa.site_account DESC, sa.rowid DESC"; // To get the entry with a site_account defined in priority

		dol_syslog(get_class($this)."::customerStripe search stripe customer id for thirdparty id=".$object->id, LOG_DEBUG);
		$resql = $this->db->query($sql);
		if ($resql) {
			$num = $this->db->num_rows($resql);
			if ($num) {
				$obj = $this->db->fetch_object($resql);
				$tiers = $obj->key_account;

				dol_syslog(get_class($this)."::customerStripe found stripe customer key_account = ".$tiers.". We will try to read it on Stripe with publishable_key = ".$stripearrayofkeysbyenv[$status]['publishable_key']);

				try {
					if (empty($key)) {				// If the Stripe connect account not set, we use common API usage
						//$customer = \Stripe\Customer::retrieve("$tiers");
						$customer = \Stripe\Customer::retrieve(array('id' => "$tiers", 'expand[]' => 'sources'));
					} else {
						//$customer = \Stripe\Customer::retrieve("$tiers", array("stripe_account" => $key));
						$customer = \Stripe\Customer::retrieve(array('id' => "$tiers", 'expand[]' => 'sources'), array("stripe_account" => $key));
					}
				} catch (Exception $e) {
					// For example, we may have error: 'No such customer: cus_XXXXX; a similar object exists in live mode, but a test mode key was used to make this request.'
					$this->error = $e->getMessage();
				}
			} elseif ($createifnotlinkedtostripe) {
				$ipaddress = getUserRemoteIP();

				$dataforcustomer = array(
					"email" => $object->email,
					"description" => $object->name,
					"metadata" => array('dol_id' => $object->id, 'dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress)
				);

				$vatcleaned = $object->tva_intra ? $object->tva_intra : null;

				/*
				$taxinfo = array('type'=>'vat');
				if ($vatcleaned)
				{
					$taxinfo["tax_id"] = $vatcleaned;
				}
				// We force data to "null" if not defined as expected by Stripe
				if (empty($vatcleaned)) $taxinfo=null;
				$dataforcustomer["tax_info"] = $taxinfo;
				*/

				//$a = \Stripe\Stripe::getApiKey();
				//var_dump($a);var_dump($key);exit;
				try {
					// Force to use the correct API key
					global $stripearrayofkeysbyenv;
					\Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);

					if (empty($key)) {				// If the Stripe connect account not set, we use common API usage
						$customer = \Stripe\Customer::create($dataforcustomer);
					} else {
						$customer = \Stripe\Customer::create($dataforcustomer, array("stripe_account" => $key));
					}

					// Create the VAT record in Stripe
					if (getDolGlobalString('STRIPE_SAVE_TAX_IDS')) {	// We setup to save Tax info on Stripe side. Warning: This may result in error when saving customer
						if (!empty($vatcleaned)) {
							$isineec = isInEEC($object);
							if ($object->country_code && $isineec) {
								//$taxids = $customer->allTaxIds($customer);
								$customer->createTaxId($customer->id, array('type' => 'eu_vat', 'value' => $vatcleaned));
							}
						}
					}

					// Create customer in Dolibarr
					$sql = "INSERT INTO ".MAIN_DB_PREFIX."societe_account (fk_soc, login, key_account, site, site_account, status, entity, date_creation, fk_user_creat)";
					$sql .= " VALUES (".((int) $object->id).", '', '".$this->db->escape($customer->id)."', 'stripe', '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."', ".((int) $status).", ".((int) $conf->entity).", '".$this->db->idate(dol_now())."', ".((int) $user->id).")";
					$resql = $this->db->query($sql);
					if (!$resql) {
						$this->error = $this->db->lasterror();
					}
				} catch (Exception $e) {
					$this->error = $e->getMessage();
				}
			}
		} else {
			dol_print_error($this->db);
		}

		return $customer;
	}

	/**
	 * Get the Stripe payment method Object from its ID
	 *
	 * @param	Stripe		$paymentmethod			Payment Method ID
	 * @param	string		$key					''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
	 * @param	int<0,1>	$status					Status (0=test, 1=live)
	 * @return 	\Stripe\PaymentMethod|null 			Stripe PaymentMethod or null if not found
	 */
	public function getPaymentMethodStripe($paymentmethod, $key = '', $status = 0)
	{
		$stripepaymentmethod = null;

		try {
			// Force to use the correct API key
			global $stripearrayofkeysbyenv;
			\Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
			if (empty($key)) {				// If the Stripe connect account not set, we use common API usage
				$stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id);
			} else {
				$stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id, array("stripe_account" => $key));
			}
		} catch (Exception $e) {
			$this->error = $e->getMessage();
		}

		return $stripepaymentmethod;
	}

	/**
	 * Get the Stripe reader Object from its ID
	 *
	 * @param	string		$reader				Reader ID
	 * @param	string		$key				''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
	 * @param	int<0,1>	$status				Status (0=test, 1=live)
	 * @return 	\Stripe\Terminal\Reader|null	Stripe Reader or null if not found
	 */
	public function getSelectedReader($reader, $key = '', $status = 0)
	{
		$selectedreader = null;

		try {
			// Force to use the correct API key
			global $stripearrayofkeysbyenv;
			\Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
			if (empty($key)) {				// If the Stripe connect account not set, we use common API usage
				$selectedreader = \Stripe\Terminal\Reader::retrieve((string) $reader);
			} else {
				$stripepaymentmethod = \Stripe\Terminal\Reader::retrieve((string) $reader, array("stripe_account" => $key));
			}
		} catch (Exception $e) {
			$this->error = $e->getMessage();
		}

		return $selectedreader;
	}


	/**
	 * Convert an amount in Stripe format into an amount into standard amount
	 *
	 * @param 	int|float	$amount				Amount in Stripe format (For example 1234 for 12.34 euros)
	 * @param	string		$currency_code		Currency code (Example 'EUR')
	 * @param	int			$direction			0=From standard to Stripe amount, 1=From Stripe to standard amount
	 * @return	int|float						Standard float amount (For example 12.34)
	 */
	public function convertAmount($amount, $currency_code, $direction = 0)
	{
		$arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
		if (!in_array($currency_code, $arrayzerounitcurrency)) {
			if (empty($direction)) {
				$newamount = (int) round($amount * 100);		// If $amount is 79.99, doing 79.99 * 100 returns float 7998.999999999999, and "int" do a truncation into 7998 so we must first use round to get nearest integer value
			} else {
				$newamount = (float) ($amount / 100);
			}
		} else {
			$newamount = $amount;
		}

		return $newamount;
	}


	/**
	 * Get the Stripe payment intent. Create it with confirmnow=false
	 * Warning. If a payment was tried and failed, a payment intent was created.
	 * But if we change something on object to pay (amount or other), reusing same payment intent, is not allowed by Stripe.
	 * Recommended solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
	 * that's why i comment the part of code to retrieve a payment intent with object id (never mind if we cumulate payment intent with old ones that will not be used)
	 * Note: This is used when option STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION is on when making a payment from the public/payment/newpayment.php page
	 * but not when using the STRIPE_USE_NEW_CHECKOUT.
	 *
	 * @param   float			$amount                             Amount
	 * @param   string			$currency_code                      Currency code
	 * @param   string			$tag                                Tag
	 * @param   string			$description                        Description
	 * @param	?CommonObject	$object						    	Object to pay with Stripe
	 * @param	?string			$customer							Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
	 * @param	?string			$key							    ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
	 * @param	int<0,1>		$servicestatus					    Status (0=test, 1=live)
	 * @param	int<0,1>		$usethirdpartyemailforreceiptemail	1=use thirdparty email for receipt
	 * @param	'automatic'|'manual'|'terminal'		$mode			Automatic=automatic confirmation/payment when conditions are ok, manual=need to call confirm() on intent, terminal=manual
	 * @param   bool			$confirmnow                     	False=default, true=try to confirm immediately after create (if conditions are ok)
	 * @param   ?string			$payment_method                 	'pm_....' (if known)
	 * @param   int<0,1>		$off_session                    	If we use an already known payment method to pay when customer is not available during the checkout flow.
	 * @param	int<0,1>		$noidempotency_key					Do not use the idempotency_key when creating the PaymentIntent
	 * @param	int				$did								ID of an existing line into llx_prelevement_demande (Dolibarr intent). If provided, no new line will be created.
	 * @return 	?\Stripe\PaymentIntent				        		Stripe PaymentIntent or null if not found and failed to create
	 */
	public function getPaymentIntent($amount, $currency_code, $tag, $description = '', $object = null, $customer = null, $key = null, $servicestatus = 0, $usethirdpartyemailforreceiptemail = 0, $mode = 'automatic', $confirmnow = false, $payment_method = null, $off_session = 0, $noidempotency_key = 1, $did = 0)
	{
		global $conf, $user, $hookmanager;

		dol_syslog(get_class($this)."::getPaymentIntent description=".$description, LOG_INFO, 1);

		$error = 0;

		if (empty($servicestatus)) {
			$service = 'StripeTest';
		} else {
			$service = 'StripeLive';
		}

		$stripeamount = $this->convertAmount($amount, $currency_code, 0);

		$fee = 0;
		if (getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT")) {
			$fee = $amount * ((float) getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT", '0') / 100) + (float) getDolGlobalString("STRIPE_APPLICATION_FEE", '0');
		}
		if ($fee >= (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') && (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') > (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
			$fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0');
		} elseif ($fee < (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
			$fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0');
		}
		$stripefee = round($this->convertAmount($fee, $currency_code));

		$paymentintent = null;

		if (is_object($object) && getDolGlobalInt('STRIPE_REUSE_EXISTING_INTENT_IF_FOUND') && !getDolGlobalInt('STRIPE_CARD_PRESENT')) {
			// Warning. If a payment was tried and failed, a payment intent was created.
			// But if we change something on object to pay (amount or other that does not change the idempotency key), reusing same payment intent, is not allowed by Stripe.
			// Recommended solution is to recreate a new payment intent each time we need one (old one will be automatically closed by Stripe after a delay), Stripe will
			// automatically return the existing payment intent if idempotency is provided when we try to create the new one.
			// That's why we can comment the part of code to retrieve a payment intent with object id (never mind if we cumulate payment intent with old ones that will not be used)

			// Try to retrieve the last paymentintent for invoice, but if it fails, never mind.
			$sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
			$sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
			$sql .= " WHERE pi.fk_facture = ".((int) $object->id);
			$sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
			$sql .= " AND pi.entity IN (".getEntity('societe').")";
			$sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
			$sql .= " ORDER BY rowid DESC";

			dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);

			$resql = $this->db->query($sql);
			if ($resql) {
				$num = $this->db->num_rows($resql);
				if ($num) {
					$obj = $this->db->fetch_object($resql);

					$ext_payment_intent = $obj->ext_payment_id;
					$ext_payment_intent_array = preg_split('/[:@]/', $ext_payment_intent);

					$intent = $ext_payment_intent_array[0];
					$customerindb = (isset($ext_payment_intent_array[1]) ? $ext_payment_intent_array[1] : '');
					$pkeyindb = (isset($ext_payment_intent_array[2]) ? $ext_payment_intent_array[2] : '');
					// TODO Test that $pkeyindb and $customerindb match

					dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record with intent=".$intent);

					// Force to use the correct API key
					global $stripearrayofkeysbyenv;
					\Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$servicestatus]['secret_key']);

					try {
						if (empty($key)) {				// If the Stripe connect account not set, we use common API usage
							$paymentintent = \Stripe\PaymentIntent::retrieve($intent);
						} else {
							$paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
						}
					} catch (Exception $e) {
						$error++;
						$this->error = $e->getMessage();
					}
				}
			}
		}

		if (empty($paymentintent)) {
			// Try to create intent. See https://stripe.com/docs/api/payment_intents/create
			$ipaddress = getUserRemoteIP();
			$metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress, 'dol_noidempotency' => (int) $noidempotency_key);
			if (is_object($object)) {
				$metadata['dol_type'] = $object->element;
				$metadata['dol_id'] = $object->id;
				if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
					$metadata['dol_thirdparty_id'] = $object->thirdparty->id;
				}
			}

			$stripemode = $mode;

			// list of payment method types
			$paymentmethodtypes = array("card");
			$descriptor = dol_trunc($tag, 10, 'right', 'UTF-8', 1);
			if (getDolGlobalInt('STRIPE_SEPA_DIRECT_DEBIT')) {
				$paymentmethodtypes[] = "sepa_debit";
			}
			if (getDolGlobalInt('STRIPE_KLARNA')) {
				$paymentmethodtypes[] = "klarna";
			}
			if (getDolGlobalInt('STRIPE_BANCONTACT')) {
				$paymentmethodtypes[] = "bancontact";
			}
			if (getDolGlobalInt('STRIPE_IDEAL')) {
				$paymentmethodtypes[] = "ideal";
			}
			if (getDolGlobalInt('STRIPE_GIROPAY')) {
				$paymentmethodtypes[] = "giropay";
			}
			if (getDolGlobalInt('STRIPE_SOFORT')) {
				$paymentmethodtypes[] = "sofort";
			}
			if ($mode == 'terminal') {
				if (getDolGlobalInt('STRIPE_CARD_PRESENT')) {
					$paymentmethodtypes = array("card_present");
				}
				$stripemode = 'manual';
			}
			global $dolibarr_main_url_root;
			$descriptioninpaymentintent = $description;
			// When STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION=2, use automatic_payment_methods
			// so Stripe Dashboard controls active methods (Klarna, Bancontact, Link, etc.)
			// and return_url redirect flow works correctly.
			// In terminal mode, automatic methods are not supported — fallback to manual list.
			$useautomaticmethods = (getDolGlobalInt('STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION') == 2 && $mode != 'terminal');
			$dataforintent = array_merge(
				array(
					"confirm"     => $confirmnow,
					"amount"      => $stripeamount,
					"currency"    => $currency_code,
					"description" => $descriptioninpaymentintent,
					"metadata"    => $metadata,
				),
				$useautomaticmethods ? array(
					'automatic_payment_methods' => array(
						'enabled' => true,
					),
				) : array(
					'confirmation_method'  => $stripemode,
					'payment_method_types' => $paymentmethodtypes,
					'setup_future_usage'   => 'on_session',
				)
			);
			if ($tag) {
				$dataforintent["statement_descriptor_suffix"] = dol_trunc($tag, 12, 'right', 'UTF-8', 1); 	// For card payment, 22 chars that appears on bank receipt (prefix into stripe setup + this suffix)
				$dataforintent["statement_descriptor"] = dol_trunc($tag, 22, 'right', 'UTF-8', 1); 			// For SEPA, 22 chars, it will take only statement_descriptor, not statement_descriptor_suffix
			}
			if (!is_null($customer)) {
				$dataforintent["customer"] = $customer;
			}
			// payment_method =
			// payment_method_types = array('card')
			//var_dump($dataforintent);
			if ($off_session) {
				unset($dataforintent['setup_future_usage']);
				// We can't use both "setup_future_usage" = "off_session" and "off_session" = true.
				// Because $off_session parameter is dedicated to create paymentintent off_line (and not future payment), we need to use "off_session" = true.
				//$dataforintent["setup_future_usage"] = "off_session";
				$dataforintent["off_session"] = true;
			}
			if (getDolGlobalInt('STRIPE_GIROPAY')) {
				unset($dataforintent['setup_future_usage']);
			}
			if (getDolGlobalInt('STRIPE_KLARNA')) {
				unset($dataforintent['setup_future_usage']);
			}
			if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
				unset($dataforintent['setup_future_usage']);
				$dataforintent["capture_method"] = "manual";
				$dataforintent["confirmation_method"] = "manual";
			}
			if (!is_null($payment_method)) {
				$dataforintent["payment_method"] = $payment_method;
				$description .= ' - '.$payment_method;
			}

			if ($conf->entity != getDolGlobalInt('STRIPECONNECT_PRINCIPAL') && $stripefee > 0) {
				$dataforintent["application_fee_amount"] = $stripefee;
			}
			if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
				$dataforintent["receipt_email"] = $object->thirdparty->email;
			}

			try {
				// Force to use the correct API key
				global $stripearrayofkeysbyenv;
				\Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$servicestatus]['secret_key']);

				$arrayofoptions = array();
				if (empty($noidempotency_key)) {
					$arrayofoptions["idempotency_key"] = $descriptioninpaymentintent;
				}
				// Note: If all data for payment intent are same than a previous on, even if we use 'create', Stripe will return ID of the old existing payment intent.
				if (!empty($key)) {				// If the Stripe connect account not set, we use common API usage
					$arrayofoptions["stripe_account"] = $key;
				}

				// Hook to allow external modules to modify Stripe PaymentIntent data before API call.
				// Can be used to customize statement_descriptor (e.g. structured communication for SEPA),
				// add metadata, modify description, etc.
				// Note: $arrayofoptions is not passed for security reasons (contains stripe_account and idempotency_key).
				$parameters = array(
					'dataforintent' => $dataforintent,
					'object' => $object,
					'tag' => $tag,
					'amount' => $amount,
					'currency_code' => $currency_code,
					'customer' => $customer,
					'servicestatus' => $servicestatus,
				);
				$reshook = $hookmanager->executeHooks('beforeCreateStripePaymentIntent', $parameters, $this);
				if (!empty($hookmanager->resArray['dataforintent'])) {
					$dataforintent = $hookmanager->resArray['dataforintent'];
				}

				dol_syslog(get_class($this)."::getPaymentIntent ".$stripearrayofkeysbyenv[$servicestatus]['publishable_key'], LOG_DEBUG);
				dol_syslog(get_class($this)."::getPaymentIntent dataforintent to create paymentintent = ".formatLogObject($dataforintent));

				$paymentintent = \Stripe\PaymentIntent::create($dataforintent, $arrayofoptions);

				if ($paymentintent instanceof \Stripe\PaymentIntent) {
					dol_syslog(get_class($this)."::getPaymentIntent paymentintent is a defined object");

					// Store the payment intent
					if (is_object($object)) {
						$paymentintentalreadyexists = 0;

						// Get $customerid and $pkey
						$customerid = $paymentintent->customer;
						$pkey = '';
						if (isset($stripearrayofkeysbyenv[$servicestatus]['publishable_key'])) {
							$pkey = $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
						}

						$LONGTRANSACTIONID = $paymentintent->id.':'.$customerid.'@'.$pkey;


						if ($did > 0) {
							// If a payment request line provided, we do not need to recreate one, we just update it
							dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);

							$sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
							$sql .= " ext_payment_site = '".$this->db->escape($service)."',";
							$sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."'";	// TODO Save the long transaction id
							$sql .= " WHERE rowid = ".((int) $did);

							$resql = $this->db->query($sql);
							if ($resql) {
								$paymentintentalreadyexists++;
							} else {
								$error++;
								dol_print_error($this->db);
							}
						} else {
							// Check that payment intent $paymentintent->id is not already recorded.
							dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);

							$sql = "SELECT pi.rowid";
							$sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
							$sql .= " WHERE pi.entity IN (".getEntity('societe').")";
							$sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
							$sql .= " AND (pi.ext_payment_id = '".$this->db->escape($paymentintent->id)."' OR pi.ext_payment_id = '".$this->db->escape($LONGTRANSACTIONID)."')";

							$resql = $this->db->query($sql);
							if ($resql) {
								$num = $this->db->num_rows($resql);
								if ($num) {
									$obj = $this->db->fetch_object($resql);
									if ($obj) {
										$paymentintentalreadyexists++;
									}
								}
							} else {
								$error++;
								dol_print_error($this->db);
							}
						}

						// If not, we create it.
						if (!$error && !$paymentintentalreadyexists) {
							$now = dol_now();
							$sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site, amount)";
							// TODO Save the long transaction id in ext_payment_id
							$sql .= " VALUES ('".$this->db->idate($now)."', ".((int) $user->id).", '".$this->db->escape($paymentintent->id)."', ".((int) $object->id).", '".$this->db->escape($object->element)."', ".((int) $conf->entity).", '".$this->db->escape($service)."', ".((float) $amount).")";
							$resql = $this->db->query($sql);
							if (!$resql) {
								$error++;
								$this->error = $this->db->lasterror();
								dol_syslog(get_class($this)."::PaymentIntent failed to insert paymentintent with id=".$paymentintent->id." into database.", LOG_ERR);
							}
						}
					} else {
						$_SESSION["stripe_payment_intent"] = $paymentintent;
					}
				} else {
					dol_syslog(get_class($this)."::getPaymentIntent create paymentintent did not returned a Stripe\PaymentIntent object", LOG_ERR);
				}
			} catch (Stripe\Exception\CardException $e) {
				$error++;
				$this->error = $e->getMessage();
				$this->code = $e->getStripeCode();
				$this->declinecode = $e->getDeclineCode();
			} catch (Exception $e) {
				//var_dump($dataforintent);
				//var_dump($description);
				//var_dump($key);
				//var_dump($paymentintent);
				//var_dump($e->getMessage());
				//var_dump($e);
				$error++;
				$this->error = $e->getMessage();
				$this->code = '';
				$this->declinecode = '';
			}
		}

		dol_syslog(get_class($this)."::getPaymentIntent return error=".$error." this->error=".$this->error, LOG_INFO, -1);

		if (!$error) {
			return $paymentintent;
		} else {
			return null;
		}
	}

	/**
	 * Get the Stripe payment intent. Create it with confirmnow=false
	 * Warning. If a payment was tried and failed, a payment intent was created.
	 * But if we change something on object to pay (amount or other), reusing same payment intent is not allowed.
	 * Recommended solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
	 * that's why i comment the part of code to retrieve a payment intent with object id (never mind if we cumulate payment intent with old ones that will not be used)
	 * Note: This is used when option STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION is on when making a payment from the public/payment/newpayment.php page
	 * but not when using the STRIPE_USE_NEW_CHECKOUT.
	 *
	 * @param   string		$description                        Description
	 * @param	Societe		$object							    Object of company to link the Stripe payment mode with
	 * @param	string		$customer							Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
	 * @param	string		$key							    ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
	 * @param	int<0,1>	$servicestatus					    Service status (0=test, 1=live)
	 * @param	int<0,1>	$usethirdpartyemailforreceiptemail	1=use thirdparty email for receipt
	 * @param   bool		$confirmnow                         false=default, true=try to confirm immediately after create (if conditions are ok)
	 * @return 	\Stripe\SetupIntent|null				        Stripe SetupIntent or null if not found and failed to create
	 */
	public function getSetupIntent($description, $object, $customer, $key, $servicestatus, $usethirdpartyemailforreceiptemail = 0, $confirmnow = false)
	{
		global $conf;

		$noidempotency_key = 1;

		dol_syslog("getSetupIntent description=".$description.' confirmnow='.formatLogObject($confirmnow), LOG_INFO, 1);

		$error = 0;

		if (empty($servicestatus)) {
			$service = 'StripeTest';
		} else {
			$service = 'StripeLive';
		}

		$setupintent = null;

		if (empty($setupintent)) {  // @phan-suppress-current-line PhanPluginConstantVariableNull
			$ipaddress = getUserRemoteIP();
			$metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress, 'dol_noidempotency' => (int) $noidempotency_key);
			if (is_object($object)) {
				$metadata['dol_type'] = $object->element;
				$metadata['dol_id'] = $object->id;
				if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
					$metadata['dol_thirdparty_id'] = $object->thirdparty->id;
				}
			}

			// list of payment method types
			$paymentmethodtypes = array("card");
			if (getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
				$paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
			}
			if (getDolGlobalString('STRIPE_BANCONTACT')) {
				$paymentmethodtypes[] = "bancontact";
			}
			if (getDolGlobalString('STRIPE_IDEAL')) {
				$paymentmethodtypes[] = "ideal";
			}
			// Giropay not possible for setup intent
			if (getDolGlobalString('STRIPE_SOFORT')) {
				$paymentmethodtypes[] = "sofort";
			}

			global $dolibarr_main_url_root;

			$descriptioninsetupintent = $description;

			$dataforintent = array(
				"confirm" => $confirmnow, // Do not confirm immediately during creation of intent
				"payment_method_types" => $paymentmethodtypes,	// When payment_method_types is set, return_url is not required but payment mode can't be managed from dashboard
				/*
				 'return_url' => $dolibarr_main_url_root.'/public/payment/paymentok.php',
				 'automatic_payment_methods' => array(
				 'enabled' => true,
				 'allow_redirects' => 'never',
				 ),
				 */
				"usage" => "off_session",
				"metadata" => $metadata
			);
			if (!is_null($customer)) {
				$dataforintent["customer"] = $customer;
			}
			if (!is_null($description)) {
				$dataforintent["description"] = $descriptioninsetupintent;
			}
			// payment_method =
			// payment_method_types = array('card')
			//var_dump($dataforintent);

			if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
				$dataforintent["receipt_email"] = $object->thirdparty->email;
			}

			try {
				// Force to use the correct API key
				global $stripearrayofkeysbyenv;
				\Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$servicestatus]['secret_key']);

				dol_syslog(get_class($this)."::getSetupIntent ".$stripearrayofkeysbyenv[$servicestatus]['publishable_key'], LOG_DEBUG);
				dol_syslog(get_class($this)."::getSetupIntent dataforintent to create setupintent = ".formatLogObject($dataforintent));

				// Note: If all data for payment intent are same than a previous one, even if we use 'create', Stripe will return ID of the old existing payment intent.
				if (empty($key)) {				// If the Stripe connect account not set, we use common API usage
					//$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description"));
					$setupintent = \Stripe\SetupIntent::create($dataforintent, array());
				} else {
					//$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description", "stripe_account" => $key));
					$setupintent = \Stripe\SetupIntent::create($dataforintent, array("stripe_account" => $key));
				}
				//var_dump($setupintent->id);

				// Store the setup intent
				/*if (is_object($object))
				{
					$setupintentalreadyexists = 0;
					// Check that payment intent $setupintent->id is not already recorded.
					$sql = "SELECT pi.rowid";
					$sql.= " FROM " . MAIN_DB_PREFIX . "prelevement_demande as pi";
					$sql.= " WHERE pi.entity IN (".getEntity('societe').")";
					$sql.= " AND pi.ext_payment_site = '" . $this->db->escape($service) . "'";
					$sql.= " AND pi.ext_payment_id = '".$this->db->escape($setupintent->id)."'";

					dol_syslog(get_class($this) . "::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
					$resql = $this->db->query($sql);
					if ($resql) {
						$num = $this->db->num_rows($resql);
						if ($num)
						{
							$obj = $this->db->fetch_object($resql);
							if ($obj) $setupintentalreadyexists++;
						}
					}
					else dol_print_error($this->db);

					// If not, we create it.
					if (! $setupintentalreadyexists)
					{
						$now=dol_now();
						$sql = "INSERT INTO " . MAIN_DB_PREFIX . "prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site)";
						$sql .= " VALUES ('".$this->db->idate($now)."', ".((int) $user->id).", '".$this->db->escape($setupintent->id)."', ".((int) $object->id).", '".$this->db->escape($object->element)."', " . ((int) $conf->entity) . ", '" . $this->db->escape($service) . "', ".((float) $amount).")";
						$resql = $this->db->query($sql);
						if (! $resql)
						{
							$error++;
							$this->error = $this->db->lasterror();
							dol_syslog(get_class($this) . "::PaymentIntent failed to insert paymentintent with id=".$setupintent->id." into database.");
						}
					}
				}
				else
				{
					$_SESSION["stripe_setup_intent"] = $setupintent;
				}*/
			} catch (Exception $e) {
				//var_dump($dataforintent);
				//var_dump($description);
				//var_dump($key);
				//var_dump($setupintent);
				//var_dump($e->getMessage());
				$error++;
				$this->error = $e->getMessage();
			}
		}

		if (!$error) {
			dol_syslog("getSetupIntent ".(is_object($setupintent) ? $setupintent->id : ''), LOG_INFO, -1);
			return $setupintent;
		} else {
			dol_syslog("getSetupIntent return error=".$error, LOG_INFO, -1);
			return null;
		}
	}


	/**
	 * Get the Stripe card of a company payment mode (option to create it on Stripe if not linked yet is no more available on new Stripe API)
	 *
	 * @param	\Stripe\Customer		$cu								Object stripe customer.
	 * @param	CompanyPaymentMode		$object							Object companypaymentmode to check, or create on stripe (create on stripe also update the societe_rib table for current entity)
	 * @param	string					$stripeacc						''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
	 * @param	int<0,1>				$status							Status (0=test, 1=live)
	 * @param	int<0,1>				$createifnotlinkedtostripe		1=Create the stripe card and the link if the card is not yet linked to a stripe card. Deprecated with new Stripe API and SCA.
	 * @return 	\Stripe\Card|\Stripe\PaymentMethod|null 				Stripe Card or null if not found
	 */
	public function cardStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
	{
		global $conf, $langs;

		$card = null;

		$sql = "SELECT sa.stripe_card_ref, sa.proprio as owner_name, sa.exp_date_month, sa.exp_date_year, sa.number, sa.cvn"; // stripe_card_ref is card_....
		$sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
		$sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
		$sql .= " AND sa.type = 'card'";

		dol_syslog(get_class($this)."::cardStripe search stripe card id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
		$resql = $this->db->query($sql);
		if ($resql) {
			$num = $this->db->num_rows($resql);
			if ($num) {
				$obj = $this->db->fetch_object($resql);
				$cardref = $obj->stripe_card_ref;
				dol_syslog(get_class($this)."::cardStripe cardref=".$cardref);
				if ($cardref) {
					try {
						if (empty($stripeacc)) {				// If the Stripe connect account not set, we use common API usage
							if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
								$card = $cu->sources->retrieve($cardref);
							} else {
								$card = \Stripe\PaymentMethod::retrieve($cardref);
							}
						} else {
							if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
								//$card = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc));		// this API fails when array stripe_account is provided
								$card = $cu->sources->retrieve($cardref);
							} else {
								//$card = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc));		// Don't know if this works
								$card = \Stripe\PaymentMethod::retrieve($cardref);
							}
						}
					} catch (Exception $e) {
						$this->error = $e->getMessage();
						dol_syslog($this->error, LOG_WARNING);
					}
				} elseif ($createifnotlinkedtostripe) {
					// Deprecated with new Stripe API and SCA. We should not use anymore this part of code now.
					$exp_date_month = $obj->exp_date_month;
					$exp_date_year = $obj->exp_date_year;
					$number = $obj->number;
					$cvc = $obj->cvn; // cvn in database, cvc for stripe
					$cardholdername = $obj->owner_name;

					$ipaddress = getUserRemoteIP();

					$dataforcard = array(
						"source" => array(
							'object' => 'card',
							'exp_month' => $exp_date_month,
							'exp_year' => $exp_date_year,
							'number' => $number,
							'cvc' => $cvc,
							'name' => $cardholdername
						),
						"metadata" => array(
							'dol_type' => $object->element,
							'dol_id' => $object->id,
							'dol_version' => DOL_VERSION,
							'dol_entity' => $conf->entity,
							'ipaddress' => $ipaddress
						)
					);

					//$a = \Stripe\Stripe::getApiKey();
					//var_dump($a);
					//var_dump($stripeacc);exit;
					try {
						if (empty($stripeacc)) {				// If the Stripe connect account not set, we use common API usage
							if (!getDolGlobalString('STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION')) {
								dol_syslog("Try to create card with dataforcard = ".formatLogObject($dataforcard));
								$card = $cu->sources->create($dataforcard);
								if (!$card) {
									$this->error = 'Creation of card on Stripe has failed';
								}
							} else {
								$connect = '';
								if (!empty($stripeacc)) {
									$connect = $stripeacc.'/';
								}
								$url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
								if ($status) {
									$url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
								}
								$urtoswitchonstripe = '<a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';

								//dol_syslog("Error: This case is not supported", LOG_ERR);
								$this->error = str_replace('{s1}', $urtoswitchonstripe, $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', '{s1}'));
							}
						} else {
							if (!getDolGlobalString('STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION')) {
								dol_syslog("Try to create card with dataforcard = ".formatLogObject($dataforcard));
								$card = $cu->sources->create($dataforcard, array("stripe_account" => $stripeacc));
								if (!$card) {
									$this->error = 'Creation of card on Stripe has failed';
								}
							} else {
								$connect = '';
								if (!empty($stripeacc)) {
									$connect = $stripeacc.'/';
								}
								$url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
								if ($status) {
									$url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
								}
								$urtoswitchonstripe = '<a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';

								//dol_syslog("Error: This case is not supported", LOG_ERR);
								$this->error = str_replace('{s1}', $urtoswitchonstripe, $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', '{s1}'));
							}
						}

						if ($card) {
							$sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
							$sql .= " SET stripe_card_ref = '".$this->db->escape($card->id)."', card_type = '".$this->db->escape($card->brand)."',";
							$sql .= " country_code = '".$this->db->escape($card->country)."',";
							$sql .= " approved = ".($card->cvc_check == 'pass' ? 1 : 0);
							$sql .= " WHERE rowid = ".((int) $object->id);
							$sql .= " AND type = 'card'";
							$resql = $this->db->query($sql);
							if (!$resql) {
								$this->error = $this->db->lasterror();
							}
						}
					} catch (Exception $e) {
						$this->error = $e->getMessage();
						dol_syslog($this->error, LOG_WARNING);
					}
				}
			}
		} else {
			dol_print_error($this->db);
		}

		return $card;
	}


	/**
	 * Get the Stripe SEPA of a company payment mode (create it if it doesn't exists and $createifnotlinkedtostripe is set)
	 *
	 * @param	\Stripe\Customer		$cu								Object stripe customer.
	 * @param	CompanyPaymentMode		$object							Object companypaymentmode to check, or create on stripe (create on stripe also update the societe_rib table for current entity)
	 * @param	string					$stripeacc						''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
	 * @param	int<0,1>				$status							Status (0=test, 1=live)
	 * @param	int<0,1>				$createifnotlinkedtostripe		1=Create the stripe sepa and the link if the sepa is not yet linked to a stripe sepa. Used by the "Create bank to Stripe" feature.
	 * @return 	\Stripe\PaymentMethod|null 								Stripe SEPA or null if not found
	 */
	public function sepaStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
	{
		global $conf;
		$sepa = null;

		$sql = "SELECT sa.stripe_card_ref, sa.proprio, sa.iban_prefix as iban, sa.rum"; // stripe_card_ref is 'src_...' for Stripe SEPA
		$sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
		$sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
		$sql .= " AND sa.type = 'ban'"; //type ban to get normal bank account of customer (prelevement)

		$soc = new Societe($this->db);
		$soc->fetch($object->fk_soc);

		dol_syslog(get_class($this)."::sepaStripe search stripe ban id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
		$resql = $this->db->query($sql);
		if ($resql) {
			$num = $this->db->num_rows($resql);
			if ($num) {
				$obj = $this->db->fetch_object($resql);
				$cardref = $obj->stripe_card_ref;

				dol_syslog(get_class($this)."::sepaStripe paymentmode=".$cardref);

				if ($cardref) {
					try {
						if (empty($stripeacc)) {				// If the Stripe connect account not set, we use common API usage
							if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
								$sepa = $cu->sources->retrieve($cardref);
							} else {
								$sepa = \Stripe\PaymentMethod::retrieve($cardref);
							}
						} else {
							if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
								//$sepa = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc));		// this API fails when array stripe_account is provided
								$sepa = $cu->sources->retrieve($cardref);
							} else {
								//$sepa = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc));		// Don't know if this works
								$sepa = \Stripe\PaymentMethod::retrieve($cardref);
							}
						}
					} catch (Exception $e) {
						$this->error = $e->getMessage();
						dol_syslog($this->error, LOG_WARNING);
					}
				} elseif ($createifnotlinkedtostripe) {
					$iban = dolDecrypt($obj->iban);
					$ipaddress = getUserRemoteIP();
					$metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress);
					if (is_object($object)) {
						$metadata['dol_type'] = $object->element;
						$metadata['dol_id'] = $object->id;
						$metadata['dol_thirdparty_id'] = $soc->id;
					}

					$description = 'SEPA for IBAN '.$iban;

					$dataforcard = array(
						'type' => 'sepa_debit',
						"sepa_debit" => array('iban' => $iban),
						'billing_details' => array(
							'name' => $soc->name,
							'email' => !empty($soc->email) ? $soc->email : "",
						),
						"metadata" => $metadata
					);
					// Complete owner name
					if (!empty($soc->town)) {
						$dataforcard['billing_details']['address']['city'] = $soc->town;
					}
					if (!empty($soc->country_code)) {
						$dataforcard['billing_details']['address']['country'] = $soc->country_code;
					}
					if (!empty($soc->address)) {
						$dataforcard['billing_details']['address']['line1'] = $soc->address;
					}
					if (!empty($soc->zip)) {
						$dataforcard['billing_details']['address']['postal_code'] = $soc->zip;
					}
					if (!empty($soc->state)) {
						$dataforcard['billing_details']['address']['state'] = $soc->state;
					}

					//$a = \Stripe\Stripe::getApiKey();
					//var_dump($a);var_dump($stripeacc);exit;
					try {
						dol_syslog("Try to create sepa_debit");

						$service = 'StripeTest';
						$servicestatus = 0;
						if (getDolGlobalString('STRIPE_LIVE')/* && !GETPOST('forcesandbox', 'alpha') */) {
							$service = 'StripeLive';
							$servicestatus = 1;
						}
						// Force to use the correct API key
						global $stripearrayofkeysbyenv;
						$stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];

						dol_syslog("Try to create sepa_debit with data = ".formatLogObject($dataforcard));

						$s = new \Stripe\StripeClient($stripeacc);

						//var_dump($dataforcard);exit;

						$sepa = $s->paymentMethods->create($dataforcard);
						if (!$sepa) {
							$this->error = 'Creation of payment method sepa_debit on Stripe has failed';
							dol_syslog($this->error, LOG_ERR);
						} else {
							// link customer and src
							//$cs = $this->getSetupIntent($description, $soc, $cu, '', $status);
							$dataforintent = array(0 => ['description' => $description, 'payment_method_types' => ['sepa_debit'], 'customer' => $cu->id, 'payment_method' => $sepa->id], 'metadata' => $metadata);

							$cs = $s->setupIntents->create($dataforintent);
							//$cs = $s->setupIntents->update($cs->id, ['payment_method' => $sepa->id]);

							if (!$cs) {
								$this->error = 'Link SEPA <-> setupIntent->create failed';
								dol_syslog($this->error, LOG_ERR);
								$cs2 = null;
							} else {
								// note: $cs->mandate contains ID of mandate on Stripe side
								$cs2 = $s->setupIntents->confirm($cs->id, ['mandate_data' => ['customer_acceptance' => ['type' => 'offline']]]);
								if (!$cs2) {
									$this->error = 'Link SEPA <-> setupIntent->confirm failed';
									dol_syslog($this->error, LOG_ERR);
								}
							}

							if ($cs && $cs2) {
								dol_syslog("Update the payment mode of the customer");

								// print json_encode($sepa);

								// Save the Stripe payment mode ID into the Dolibarr database
								$sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
								$sql .= " SET stripe_card_ref = '".$this->db->escape($sepa->id)."',";
								$sql .= " card_type = 'sepa_debit',";
								$sql .= " stripe_account= '" . $this->db->escape($cu->id . "@" . $stripeacc) . "',";
								$sql .= " ext_payment_site = '".$this->db->escape($service)."'";
								if (!empty($cs->mandate)) {
									$mandateservice = new \Stripe\Mandate($stripeacc);
									$mandate = $mandateservice->retrieve($cs->mandate);
									if (is_object($mandate) && is_object($mandate->payment_method_details) && is_object($mandate->payment_method_details->sepa_debit)) {
										$refmandate = $mandate->payment_method_details->sepa_debit->reference;
										//$urlmandate = $mandate->payment_method_details->sepa_debit->url;
										$sql .= ", rum = '".$this->db->escape($refmandate)."'";
									}
									$sql .= ", comment = '".$this->db->escape($cs->mandate)."'";
									$sql .= ", date_rum = '".$this->db->idate(dol_now())."'";
								}
								$sql .= " WHERE rowid = ".((int) $object->id);
								$sql .= " AND type = 'ban'";
								$resql = $this->db->query($sql);
								if (!$resql) {
									$this->error = $this->db->lasterror();
								}
							}
						}
					} catch (Exception $e) {
						$sepa = null;
						$this->error = 'Stripe error: '.$e->getMessage().'. Check the BAN information.';
						dol_syslog($this->error, LOG_WARNING);	// Error from Stripe, so a warning on Dolibarr
					}
				}
			}
		} else {
			dol_print_error($this->db);
		}

		return $sepa;
	}
}
