stripe.class.php 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. <?php
  2. /* Copyright (C) 2018-2021 Thibault FOUCART <support@ptibogxiv.net>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. // Put here all includes required by your class file
  18. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
  19. require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
  20. require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
  21. require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
  22. require_once DOL_DOCUMENT_ROOT.'/stripe/config.php'; // This set stripe global env
  23. /**
  24. * Stripe class
  25. */
  26. class Stripe extends CommonObject
  27. {
  28. /**
  29. * @var int ID
  30. */
  31. public $rowid;
  32. /**
  33. * @var int Thirdparty ID
  34. */
  35. public $fk_soc;
  36. /**
  37. * @var int ID
  38. */
  39. public $fk_key;
  40. /**
  41. * @var int ID
  42. */
  43. public $id;
  44. public $mode;
  45. /**
  46. * @var int Entity
  47. */
  48. public $entity;
  49. public $statut;
  50. public $type;
  51. public $code;
  52. public $declinecode;
  53. /**
  54. * @var string Message
  55. */
  56. public $message;
  57. /**
  58. * Constructor
  59. *
  60. * @param DoliDB $db Database handler
  61. */
  62. public function __construct($db)
  63. {
  64. $this->db = $db;
  65. }
  66. /**
  67. * Return main company OAuth Connect stripe account
  68. *
  69. * @param string $mode 'StripeTest' or 'StripeLive'
  70. * @param int $fk_soc Id of thirdparty
  71. * @param int $entity Id of entity (-1 = current environment)
  72. * @return string Stripe account 'acc_....' or '' if no OAuth token found
  73. */
  74. public function getStripeAccount($mode = 'StripeTest', $fk_soc = 0, $entity = -1)
  75. {
  76. global $conf;
  77. $key = '';
  78. if ($entity < 0) {
  79. $entity = $conf->entity;
  80. }
  81. $sql = "SELECT tokenstring";
  82. $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
  83. $sql .= " WHERE service = '".$this->db->escape($mode)."'";
  84. $sql .= " AND entity = ".((int) $entity);
  85. if ($fk_soc > 0) {
  86. $sql .= " AND fk_soc = ".((int) $fk_soc);
  87. } else {
  88. $sql .= " AND fk_soc IS NULL";
  89. }
  90. $sql .= " AND fk_user IS NULL AND fk_adherent IS NULL";
  91. dol_syslog(get_class($this)."::getStripeAccount", LOG_DEBUG);
  92. $result = $this->db->query($sql);
  93. if ($result) {
  94. if ($this->db->num_rows($result)) {
  95. $obj = $this->db->fetch_object($result);
  96. $tokenstring = $obj->tokenstring;
  97. $tmparray = json_decode($tokenstring);
  98. $key = $tmparray->stripe_user_id;
  99. } else {
  100. $tokenstring = '';
  101. }
  102. } else {
  103. dol_print_error($this->db);
  104. }
  105. dol_syslog("No dedicated Stripe Connect account available for entity ".$conf->entity);
  106. return $key;
  107. }
  108. /**
  109. * getStripeCustomerAccount
  110. *
  111. * @param int $id Id of third party
  112. * @param int $status Status
  113. * @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.
  114. * @return string Stripe customer ref 'cu_xxxxxxxxxxxxx' or ''
  115. */
  116. public function getStripeCustomerAccount($id, $status = 0, $site_account = '')
  117. {
  118. include_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
  119. $societeaccount = new SocieteAccount($this->db);
  120. return $societeaccount->getCustomerAccount($id, 'stripe', $status, $site_account); // Get thirdparty cus_...
  121. }
  122. /**
  123. * Get the Stripe customer of a thirdparty (with option to create it in Stripe if not linked yet).
  124. * Search on site_account = 0 or = $stripearrayofkeysbyenv[$status]['publishable_key']
  125. *
  126. * @param Societe $object Object thirdparty to check, or create on stripe (create on stripe also update the stripe_account table for current entity)
  127. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  128. * @param int $status Status (0=test, 1=live)
  129. * @param int $createifnotlinkedtostripe 1=Create the stripe customer and the link if the thirdparty is not yet linked to a stripe customer
  130. * @return \Stripe\StripeCustomer|null Stripe Customer or null if not found
  131. */
  132. public function customerStripe(Societe $object, $key = '', $status = 0, $createifnotlinkedtostripe = 0)
  133. {
  134. global $conf, $user;
  135. if (empty($object->id)) {
  136. dol_syslog("customerStripe is called with the parameter object that is not loaded");
  137. return null;
  138. }
  139. $customer = null;
  140. // Force to use the correct API key
  141. global $stripearrayofkeysbyenv;
  142. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  143. $sql = "SELECT sa.key_account as key_account, sa.entity"; // key_account is cus_....
  144. $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
  145. $sql .= " WHERE sa.fk_soc = ".((int) $object->id);
  146. $sql .= " AND sa.entity IN (".getEntity('societe').")";
  147. $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
  148. $sql .= " AND (sa.site_account IS NULL OR sa.site_account = '' OR sa.site_account = '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."')";
  149. $sql .= " AND sa.key_account IS NOT NULL AND sa.key_account <> ''";
  150. dol_syslog(get_class($this)."::customerStripe search stripe customer id for thirdparty id=".$object->id, LOG_DEBUG);
  151. $resql = $this->db->query($sql);
  152. if ($resql) {
  153. $num = $this->db->num_rows($resql);
  154. if ($num) {
  155. $obj = $this->db->fetch_object($resql);
  156. $tiers = $obj->key_account;
  157. 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']);
  158. try {
  159. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  160. //$customer = \Stripe\Customer::retrieve("$tiers");
  161. $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'));
  162. } else {
  163. //$customer = \Stripe\Customer::retrieve("$tiers", array("stripe_account" => $key));
  164. $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'), array("stripe_account" => $key));
  165. }
  166. } catch (Exception $e) {
  167. // For exemple, 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.'
  168. $this->error = $e->getMessage();
  169. }
  170. } elseif ($createifnotlinkedtostripe) {
  171. $ipaddress = getUserRemoteIP();
  172. $dataforcustomer = array(
  173. "email" => $object->email,
  174. "description" => $object->name,
  175. "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
  176. );
  177. $vatcleaned = $object->tva_intra ? $object->tva_intra : null;
  178. /*
  179. $taxinfo = array('type'=>'vat');
  180. if ($vatcleaned)
  181. {
  182. $taxinfo["tax_id"] = $vatcleaned;
  183. }
  184. // We force data to "null" if not defined as expected by Stripe
  185. if (empty($vatcleaned)) $taxinfo=null;
  186. $dataforcustomer["tax_info"] = $taxinfo;
  187. */
  188. //$a = \Stripe\Stripe::getApiKey();
  189. //var_dump($a);var_dump($key);exit;
  190. try {
  191. // Force to use the correct API key
  192. global $stripearrayofkeysbyenv;
  193. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  194. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  195. $customer = \Stripe\Customer::create($dataforcustomer);
  196. } else {
  197. $customer = \Stripe\Customer::create($dataforcustomer, array("stripe_account" => $key));
  198. }
  199. // Create the VAT record in Stripe
  200. if (!empty($conf->global->STRIPE_SAVE_TAX_IDS)) { // We setup to save Tax info on Stripe side. Warning: This may result in error when saving customer
  201. if (!empty($vatcleaned)) {
  202. $isineec = isInEEC($object);
  203. if ($object->country_code && $isineec) {
  204. //$taxids = $customer->allTaxIds($customer->id);
  205. $customer->createTaxId($customer->id, array('type'=>'eu_vat', 'value'=>$vatcleaned));
  206. }
  207. }
  208. }
  209. // Create customer in Dolibarr
  210. $sql = "INSERT INTO ".MAIN_DB_PREFIX."societe_account (fk_soc, login, key_account, site, site_account, status, entity, date_creation, fk_user_creat)";
  211. $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).")";
  212. $resql = $this->db->query($sql);
  213. if (!$resql) {
  214. $this->error = $this->db->lasterror();
  215. }
  216. } catch (Exception $e) {
  217. $this->error = $e->getMessage();
  218. }
  219. }
  220. } else {
  221. dol_print_error($this->db);
  222. }
  223. return $customer;
  224. }
  225. /**
  226. * Get the Stripe payment method Object from its ID
  227. *
  228. * @param string $paymentmethod Payment Method ID
  229. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  230. * @param int $status Status (0=test, 1=live)
  231. * @return \Stripe\PaymentMethod|null Stripe PaymentMethod or null if not found
  232. */
  233. public function getPaymentMethodStripe($paymentmethod, $key = '', $status = 0)
  234. {
  235. $stripepaymentmethod = null;
  236. try {
  237. // Force to use the correct API key
  238. global $stripearrayofkeysbyenv;
  239. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  240. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  241. $stripepaymentmethod = \Stripe\PaymentMethod::retrieve(''.$paymentmethod->id.'');
  242. } else {
  243. $stripepaymentmethod = \Stripe\PaymentMethod::retrieve(''.$paymentmethod->id.'', array("stripe_account" => $key));
  244. }
  245. } catch (Exception $e) {
  246. $this->error = $e->getMessage();
  247. }
  248. return $stripepaymentmethod;
  249. }
  250. /**
  251. * Get the Stripe payment intent. Create it with confirmnow=false
  252. * Warning. If a payment was tried and failed, a payment intent was created.
  253. * But if we change something on object to pay (amount or other), reusing same payment intent is not allowed by Stripe.
  254. * Recommended solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
  255. * 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)
  256. * 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
  257. * but not when using the STRIPE_USE_NEW_CHECKOUT.
  258. *
  259. * @param double $amount Amount
  260. * @param string $currency_code Currency code
  261. * @param string $tag Tag
  262. * @param string $description Description
  263. * @param mixed $object Object to pay with Stripe
  264. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  265. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  266. * @param int $status Status (0=test, 1=live)
  267. * @param int $usethirdpartyemailforreceiptemail 1=use thirdparty email for receipt
  268. * @param int $mode automatic=automatic confirmation/payment when conditions are ok, manual=need to call confirm() on intent
  269. * @param boolean $confirmnow false=default, true=try to confirm immediatly after create (if conditions are ok)
  270. * @param string $payment_method 'pm_....' (if known)
  271. * @param string $off_session If we use an already known payment method to pay when customer is not available during the checkout flow.
  272. * @param string $noidempotency_key Do not use the idempotency_key when creating the PaymentIntent
  273. * @return \Stripe\PaymentIntent|null Stripe PaymentIntent or null if not found and failed to create
  274. */
  275. public function getPaymentIntent($amount, $currency_code, $tag, $description = '', $object = null, $customer = null, $key = null, $status = 0, $usethirdpartyemailforreceiptemail = 0, $mode = 'automatic', $confirmnow = false, $payment_method = null, $off_session = 0, $noidempotency_key = 1)
  276. {
  277. global $conf, $user;
  278. dol_syslog(get_class($this)."::getPaymentIntent", LOG_INFO, 1);
  279. $error = 0;
  280. if (empty($status)) {
  281. $service = 'StripeTest';
  282. } else {
  283. $service = 'StripeLive';
  284. }
  285. $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
  286. if (!in_array($currency_code, $arrayzerounitcurrency)) {
  287. $stripeamount = $amount * 100;
  288. } else {
  289. $stripeamount = $amount;
  290. }
  291. $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
  292. if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  293. $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
  294. } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  295. $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
  296. }
  297. if (!in_array($currency_code, $arrayzerounitcurrency)) {
  298. $stripefee = round($fee * 100);
  299. } else {
  300. $stripefee = round($fee);
  301. }
  302. $paymentintent = null;
  303. if (is_object($object) && !empty($conf->global->STRIPE_REUSE_EXISTING_INTENT_IF_FOUND)) {
  304. // Warning. If a payment was tried and failed, a payment intent was created.
  305. // 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.
  306. // 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
  307. // automatically return the existing payment intent if idempotency is provided when we try to create the new one.
  308. // 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)
  309. $sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
  310. $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_facture_demande as pi";
  311. $sql .= " WHERE pi.fk_facture = ".((int) $object->id);
  312. $sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
  313. $sql .= " AND pi.entity IN (".getEntity('societe').")";
  314. $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
  315. dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);
  316. $resql = $this->db->query($sql);
  317. if ($resql) {
  318. $num = $this->db->num_rows($resql);
  319. if ($num) {
  320. $obj = $this->db->fetch_object($resql);
  321. $intent = $obj->ext_payment_id;
  322. dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record");
  323. // Force to use the correct API key
  324. global $stripearrayofkeysbyenv;
  325. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  326. try {
  327. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  328. $paymentintent = \Stripe\PaymentIntent::retrieve($intent);
  329. } else {
  330. $paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
  331. }
  332. } catch (Exception $e) {
  333. $error++;
  334. $this->error = $e->getMessage();
  335. }
  336. }
  337. }
  338. }
  339. if (empty($paymentintent)) {
  340. // Try to create intent. See https://stripe.com/docs/api/payment_intents/create
  341. $ipaddress = getUserRemoteIP();
  342. $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
  343. if (is_object($object)) {
  344. $metadata['dol_type'] = $object->element;
  345. $metadata['dol_id'] = $object->id;
  346. if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
  347. $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
  348. }
  349. }
  350. // list of payment method types
  351. $paymentmethodtypes = array("card");
  352. if (!empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) {
  353. $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
  354. }
  355. if (!empty($conf->global->STRIPE_KLARNA)) {
  356. $paymentmethodtypes[] = "klarna";
  357. }
  358. if (!empty($conf->global->STRIPE_BANCONTACT)) {
  359. $paymentmethodtypes[] = "bancontact";
  360. }
  361. if (!empty($conf->global->STRIPE_IDEAL)) {
  362. $paymentmethodtypes[] = "ideal";
  363. }
  364. if (!empty($conf->global->STRIPE_GIROPAY)) {
  365. $paymentmethodtypes[] = "giropay";
  366. }
  367. if (!empty($conf->global->STRIPE_SOFORT)) {
  368. $paymentmethodtypes[] = "sofort";
  369. }
  370. $dataforintent = array(
  371. "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
  372. "confirmation_method" => $mode,
  373. "amount" => $stripeamount,
  374. "currency" => $currency_code,
  375. "payment_method_types" => $paymentmethodtypes,
  376. "description" => $description,
  377. "statement_descriptor_suffix" => dol_trunc($tag, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  378. //"save_payment_method" => true,
  379. "setup_future_usage" => "on_session",
  380. "metadata" => $metadata
  381. );
  382. if (!is_null($customer)) {
  383. $dataforintent["customer"] = $customer;
  384. }
  385. // payment_method =
  386. // payment_method_types = array('card')
  387. //var_dump($dataforintent);
  388. if ($off_session) {
  389. unset($dataforintent['setup_future_usage']);
  390. // We can't use both "setup_future_usage" = "off_session" and "off_session" = true.
  391. // Because $off_session parameter is dedicated to create paymentintent off_line (and not future payment), we need to use "off_session" = true.
  392. //$dataforintent["setup_future_usage"] = "off_session";
  393. $dataforintent["off_session"] = true;
  394. }
  395. if (!empty($conf->global->STRIPE_GIROPAY)) {
  396. unset($dataforintent['setup_future_usage']);
  397. }
  398. if (!empty($conf->global->STRIPE_KLARNA)) {
  399. unset($dataforintent['setup_future_usage']);
  400. }
  401. if (!is_null($payment_method)) {
  402. $dataforintent["payment_method"] = $payment_method;
  403. $description .= ' - '.$payment_method;
  404. }
  405. if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0) {
  406. $dataforintent["application_fee_amount"] = $stripefee;
  407. }
  408. if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
  409. $dataforintent["receipt_email"] = $object->thirdparty->email;
  410. }
  411. try {
  412. // Force to use the correct API key
  413. global $stripearrayofkeysbyenv;
  414. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  415. $arrayofoptions = array();
  416. if (empty($noidempotency_key)) {
  417. $arrayofoptions["idempotency_key"] = $description;
  418. }
  419. // 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.
  420. if (!empty($key)) { // If the Stripe connect account not set, we use common API usage
  421. $arrayofoptions["stripe_account"] = $key;
  422. }
  423. dol_syslog("dataforintent to create paymentintent = ".var_export($dataforintent, true));
  424. $paymentintent = \Stripe\PaymentIntent::create($dataforintent, $arrayofoptions);
  425. // Store the payment intent
  426. if (is_object($object)) {
  427. $paymentintentalreadyexists = 0;
  428. // Check that payment intent $paymentintent->id is not already recorded.
  429. $sql = "SELECT pi.rowid";
  430. $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_facture_demande as pi";
  431. $sql .= " WHERE pi.entity IN (".getEntity('societe').")";
  432. $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
  433. $sql .= " AND pi.ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
  434. dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_facture_demande", LOG_DEBUG);
  435. $resql = $this->db->query($sql);
  436. if ($resql) {
  437. $num = $this->db->num_rows($resql);
  438. if ($num) {
  439. $obj = $this->db->fetch_object($resql);
  440. if ($obj) {
  441. $paymentintentalreadyexists++;
  442. }
  443. }
  444. } else {
  445. dol_print_error($this->db);
  446. }
  447. // If not, we create it.
  448. if (!$paymentintentalreadyexists) {
  449. $now = dol_now();
  450. $sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_facture_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site, amount)";
  451. $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).")";
  452. $resql = $this->db->query($sql);
  453. if (!$resql) {
  454. $error++;
  455. $this->error = $this->db->lasterror();
  456. dol_syslog(get_class($this)."::PaymentIntent failed to insert paymentintent with id=".$paymentintent->id." into database.", LOG_ERR);
  457. }
  458. }
  459. } else {
  460. $_SESSION["stripe_payment_intent"] = $paymentintent;
  461. }
  462. } catch (Stripe\Error\Card $e) {
  463. $error++;
  464. $this->error = $e->getMessage();
  465. $this->code = $e->getStripeCode();
  466. $this->declinecode = $e->getDeclineCode();
  467. } catch (Exception $e) {
  468. //var_dump($dataforintent);
  469. //var_dump($description);
  470. //var_dump($key);
  471. //var_dump($paymentintent);
  472. //var_dump($e->getMessage());
  473. //var_dump($e);
  474. $error++;
  475. $this->error = $e->getMessage();
  476. $this->code = '';
  477. $this->declinecode = '';
  478. }
  479. }
  480. dol_syslog(get_class($this)."::getPaymentIntent return error=".$error." this->error=".$this->error, LOG_INFO, -1);
  481. if (!$error) {
  482. return $paymentintent;
  483. } else {
  484. return null;
  485. }
  486. }
  487. /**
  488. * Get the Stripe payment intent. Create it with confirmnow=false
  489. * Warning. If a payment was tried and failed, a payment intent was created.
  490. * But if we change something on object to pay (amount or other), reusing same payment intent is not allowed.
  491. * Recommanded solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
  492. * 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)
  493. * 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
  494. * but not when using the STRIPE_USE_NEW_CHECKOUT.
  495. *
  496. * @param string $description Description
  497. * @param Societe $object Object to pay with Stripe
  498. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  499. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  500. * @param int $status Status (0=test, 1=live)
  501. * @param int $usethirdpartyemailforreceiptemail 1=use thirdparty email for receipt
  502. * @param boolean $confirmnow false=default, true=try to confirm immediatly after create (if conditions are ok)
  503. * @return \Stripe\SetupIntent|null Stripe SetupIntent or null if not found and failed to create
  504. */
  505. public function getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail = 0, $confirmnow = false)
  506. {
  507. global $conf;
  508. dol_syslog("getSetupIntent description=".$description.' confirmnow='.$confirmnow, LOG_INFO, 1);
  509. $error = 0;
  510. if (empty($status)) {
  511. $service = 'StripeTest';
  512. } else {
  513. $service = 'StripeLive';
  514. }
  515. $setupintent = null;
  516. if (empty($setupintent)) {
  517. $ipaddress = getUserRemoteIP();
  518. $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
  519. if (is_object($object)) {
  520. $metadata['dol_type'] = $object->element;
  521. $metadata['dol_id'] = $object->id;
  522. if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
  523. $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
  524. }
  525. }
  526. // list of payment method types
  527. $paymentmethodtypes = array("card");
  528. if (!empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) {
  529. $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
  530. }
  531. if (!empty($conf->global->STRIPE_BANCONTACT)) {
  532. $paymentmethodtypes[] = "bancontact";
  533. }
  534. if (!empty($conf->global->STRIPE_IDEAL)) {
  535. $paymentmethodtypes[] = "ideal";
  536. }
  537. // Giropay not possible for setup intent
  538. if (!empty($conf->global->STRIPE_SOFORT)) {
  539. $paymentmethodtypes[] = "sofort";
  540. }
  541. $dataforintent = array(
  542. "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
  543. "payment_method_types" => $paymentmethodtypes,
  544. "usage" => "off_session",
  545. "metadata" => $metadata
  546. );
  547. if (!is_null($customer)) {
  548. $dataforintent["customer"] = $customer;
  549. }
  550. if (!is_null($description)) {
  551. $dataforintent["description"] = $description;
  552. }
  553. // payment_method =
  554. // payment_method_types = array('card')
  555. //var_dump($dataforintent);
  556. if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
  557. $dataforintent["receipt_email"] = $object->thirdparty->email;
  558. }
  559. try {
  560. // Force to use the correct API key
  561. global $stripearrayofkeysbyenv;
  562. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  563. dol_syslog("getSetupIntent ".$stripearrayofkeysbyenv[$status]['publishable_key'], LOG_DEBUG);
  564. // 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.
  565. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  566. //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description"));
  567. $setupintent = \Stripe\SetupIntent::create($dataforintent, array());
  568. } else {
  569. //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description", "stripe_account" => $key));
  570. $setupintent = \Stripe\SetupIntent::create($dataforintent, array("stripe_account" => $key));
  571. }
  572. //var_dump($setupintent->id);
  573. // Store the setup intent
  574. /*if (is_object($object))
  575. {
  576. $setupintentalreadyexists = 0;
  577. // Check that payment intent $setupintent->id is not already recorded.
  578. $sql = "SELECT pi.rowid";
  579. $sql.= " FROM " . MAIN_DB_PREFIX . "prelevement_facture_demande as pi";
  580. $sql.= " WHERE pi.entity IN (".getEntity('societe').")";
  581. $sql.= " AND pi.ext_payment_site = '" . $this->db->escape($service) . "'";
  582. $sql.= " AND pi.ext_payment_id = '".$this->db->escape($setupintent->id)."'";
  583. dol_syslog(get_class($this) . "::getPaymentIntent search if payment intent already in prelevement_facture_demande", LOG_DEBUG);
  584. $resql = $this->db->query($sql);
  585. if ($resql) {
  586. $num = $this->db->num_rows($resql);
  587. if ($num)
  588. {
  589. $obj = $this->db->fetch_object($resql);
  590. if ($obj) $setupintentalreadyexists++;
  591. }
  592. }
  593. else dol_print_error($this->db);
  594. // If not, we create it.
  595. if (! $setupintentalreadyexists)
  596. {
  597. $now=dol_now();
  598. $sql = "INSERT INTO " . MAIN_DB_PREFIX . "prelevement_facture_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site)";
  599. $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).")";
  600. $resql = $this->db->query($sql);
  601. if (! $resql)
  602. {
  603. $error++;
  604. $this->error = $this->db->lasterror();
  605. dol_syslog(get_class($this) . "::PaymentIntent failed to insert paymentintent with id=".$setupintent->id." into database.");
  606. }
  607. }
  608. }
  609. else
  610. {
  611. $_SESSION["stripe_setup_intent"] = $setupintent;
  612. }*/
  613. } catch (Exception $e) {
  614. //var_dump($dataforintent);
  615. //var_dump($description);
  616. //var_dump($key);
  617. //var_dump($setupintent);
  618. //var_dump($e->getMessage());
  619. $error++;
  620. $this->error = $e->getMessage();
  621. }
  622. }
  623. if (!$error) {
  624. dol_syslog("getSetupIntent ".(is_object($setupintent) ? $setupintent->id : ''), LOG_INFO, -1);
  625. return $setupintent;
  626. } else {
  627. dol_syslog("getSetupIntent return error=".$error, LOG_INFO, -1);
  628. return null;
  629. }
  630. }
  631. /**
  632. * 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)
  633. *
  634. * @param \Stripe\StripeCustomer $cu Object stripe customer.
  635. * @param CompanyPaymentMode $object Object companypaymentmode to check, or create on stripe (create on stripe also update the societe_rib table for current entity)
  636. * @param string $stripeacc ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  637. * @param int $status Status (0=test, 1=live)
  638. * @param int $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.
  639. * @return \Stripe\StripeCard|\Stripe\PaymentMethod|null Stripe Card or null if not found
  640. */
  641. public function cardStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
  642. {
  643. global $conf, $user, $langs;
  644. $card = null;
  645. $sql = "SELECT sa.stripe_card_ref, sa.proprio, sa.exp_date_month, sa.exp_date_year, sa.number, sa.cvn"; // stripe_card_ref is card_....
  646. $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
  647. $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
  648. $sql .= " AND sa.type = 'card'";
  649. dol_syslog(get_class($this)."::fetch search stripe card id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
  650. $resql = $this->db->query($sql);
  651. if ($resql) {
  652. $num = $this->db->num_rows($resql);
  653. if ($num) {
  654. $obj = $this->db->fetch_object($resql);
  655. $cardref = $obj->stripe_card_ref;
  656. dol_syslog(get_class($this)."::cardStripe cardref=".$cardref);
  657. if ($cardref) {
  658. try {
  659. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  660. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  661. $card = $cu->sources->retrieve($cardref);
  662. } else {
  663. $card = \Stripe\PaymentMethod::retrieve($cardref);
  664. }
  665. } else {
  666. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  667. //$card = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
  668. $card = $cu->sources->retrieve($cardref);
  669. } else {
  670. //$card = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
  671. $card = \Stripe\PaymentMethod::retrieve($cardref);
  672. }
  673. }
  674. } catch (Exception $e) {
  675. $this->error = $e->getMessage();
  676. dol_syslog($this->error, LOG_WARNING);
  677. }
  678. } elseif ($createifnotlinkedtostripe) {
  679. $exp_date_month = $obj->exp_date_month;
  680. $exp_date_year = $obj->exp_date_year;
  681. $number = $obj->number;
  682. $cvc = $obj->cvn; // cvn in database, cvc for stripe
  683. $cardholdername = $obj->proprio;
  684. $ipaddress = getUserRemoteIP();
  685. $dataforcard = array(
  686. "source" => array('object'=>'card', 'exp_month'=>$exp_date_month, 'exp_year'=>$exp_date_year, 'number'=>$number, 'cvc'=>$cvc, 'name'=>$cardholdername),
  687. "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
  688. );
  689. //$a = \Stripe\Stripe::getApiKey();
  690. //var_dump($a);
  691. //var_dump($stripeacc);exit;
  692. try {
  693. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  694. if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION)) {
  695. dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
  696. $card = $cu->sources->create($dataforcard);
  697. if (!$card) {
  698. $this->error = 'Creation of card on Stripe has failed';
  699. }
  700. } else {
  701. $connect = '';
  702. if (!empty($stripeacc)) {
  703. $connect = $stripeacc.'/';
  704. }
  705. $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
  706. if ($status) {
  707. $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
  708. }
  709. $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
  710. //dol_syslog("Error: This case is not supported", LOG_ERR);
  711. $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
  712. }
  713. } else {
  714. if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION)) {
  715. dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
  716. $card = $cu->sources->create($dataforcard, array("stripe_account" => $stripeacc));
  717. if (!$card) {
  718. $this->error = 'Creation of card on Stripe has failed';
  719. }
  720. } else {
  721. $connect = '';
  722. if (!empty($stripeacc)) {
  723. $connect = $stripeacc.'/';
  724. }
  725. $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
  726. if ($status) {
  727. $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
  728. }
  729. $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
  730. //dol_syslog("Error: This case is not supported", LOG_ERR);
  731. $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
  732. }
  733. }
  734. if ($card) {
  735. $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
  736. $sql .= " SET stripe_card_ref = '".$this->db->escape($card->id)."', card_type = '".$this->db->escape($card->brand)."',";
  737. $sql .= " country_code = '".$this->db->escape($card->country)."',";
  738. $sql .= " approved = ".($card->cvc_check == 'pass' ? 1 : 0);
  739. $sql .= " WHERE rowid = ".((int) $object->id);
  740. $sql .= " AND type = 'card'";
  741. $resql = $this->db->query($sql);
  742. if (!$resql) {
  743. $this->error = $this->db->lasterror();
  744. }
  745. }
  746. } catch (Exception $e) {
  747. $this->error = $e->getMessage();
  748. dol_syslog($this->error, LOG_WARNING);
  749. }
  750. }
  751. }
  752. } else {
  753. dol_print_error($this->db);
  754. }
  755. return $card;
  756. }
  757. /**
  758. * Create charge.
  759. * This is called by page htdocs/stripe/payment.php and may be deprecated.
  760. *
  761. * @param int $amount Amount to pay
  762. * @param string $currency EUR, GPB...
  763. * @param string $origin Object type to pay (order, invoice, contract...)
  764. * @param int $item Object id to pay
  765. * @param string $source src_xxxxx or card_xxxxx or pm_xxxxx
  766. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  767. * @param string $account Stripe account ref 'acc_xxxxxxxxxxxxx' via getStripeAccount()
  768. * @param int $status Status (0=test, 1=live)
  769. * @param int $usethirdpartyemailforreceiptemail Use thirdparty email as receipt email
  770. * @param boolean $capture Set capture flag to true (take payment) or false (wait)
  771. * @return Stripe
  772. */
  773. public function createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status = 0, $usethirdpartyemailforreceiptemail = 0, $capture = true)
  774. {
  775. global $conf;
  776. $error = 0;
  777. if (empty($status)) {
  778. $service = 'StripeTest';
  779. } else {
  780. $service = 'StripeLive';
  781. }
  782. $sql = "SELECT sa.key_account as key_account, sa.fk_soc, sa.entity";
  783. $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
  784. $sql .= " WHERE sa.key_account = '".$this->db->escape($customer)."'";
  785. //$sql.= " AND sa.entity IN (".getEntity('societe').")";
  786. $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
  787. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  788. $result = $this->db->query($sql);
  789. if ($result) {
  790. if ($this->db->num_rows($result)) {
  791. $obj = $this->db->fetch_object($result);
  792. $key = $obj->fk_soc;
  793. } else {
  794. $key = null;
  795. }
  796. } else {
  797. $key = null;
  798. }
  799. $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
  800. if (!in_array($currency, $arrayzerounitcurrency)) {
  801. $stripeamount = $amount * 100;
  802. } else {
  803. $stripeamount = $amount;
  804. }
  805. $societe = new Societe($this->db);
  806. if ($key > 0) {
  807. $societe->fetch($key);
  808. }
  809. $description = "";
  810. $ref = "";
  811. if ($origin == 'order') {
  812. $order = new Commande($this->db);
  813. $order->fetch($item);
  814. $ref = $order->ref;
  815. $description = "ORD=".$ref.".CUS=".$societe->id.".PM=stripe";
  816. } elseif ($origin == 'invoice') {
  817. $invoice = new Facture($this->db);
  818. $invoice->fetch($item);
  819. $ref = $invoice->ref;
  820. $description = "INV=".$ref.".CUS=".$societe->id.".PM=stripe";
  821. }
  822. $ipaddress = getUserRemoteIP();
  823. $metadata = array(
  824. "dol_id" => "".$item."",
  825. "dol_type" => "".$origin."",
  826. "dol_thirdparty_id" => "".$societe->id."",
  827. 'dol_thirdparty_name' => $societe->name,
  828. 'dol_version'=>DOL_VERSION,
  829. 'dol_entity'=>$conf->entity,
  830. 'ipaddress'=>$ipaddress
  831. );
  832. $return = new Stripe($this->db);
  833. try {
  834. // Force to use the correct API key
  835. global $stripearrayofkeysbyenv;
  836. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  837. if (empty($conf->stripeconnect->enabled)) { // With a common Stripe account
  838. if (preg_match('/pm_/i', $source)) {
  839. $stripecard = $source;
  840. $amountstripe = $stripeamount;
  841. $FULLTAG = 'PFBO'; // Payment From Back Office
  842. $stripe = $return;
  843. $amounttopay = $amount;
  844. $servicestatus = $status;
  845. dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
  846. $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
  847. dol_syslog("* createPaymentStripe Create payment for customer ".$customer->id." on source card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
  848. // Create payment intent and charge payment (confirmnow = true)
  849. $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
  850. $charge = new stdClass();
  851. if ($paymentintent->status == 'succeeded') {
  852. $charge->status = 'ok';
  853. } else {
  854. $charge->status = 'failed';
  855. $charge->failure_code = $stripe->code;
  856. $charge->failure_message = $stripe->error;
  857. $charge->failure_declinecode = $stripe->declinecode;
  858. $stripefailurecode = $stripe->code;
  859. $stripefailuremessage = $stripe->error;
  860. $stripefailuredeclinecode = $stripe->declinecode;
  861. }
  862. } elseif (preg_match('/acct_/i', $source)) {
  863. $charge = \Stripe\Charge::create(array(
  864. "amount" => "$stripeamount",
  865. "currency" => "$currency",
  866. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  867. "description" => "Stripe payment: ".$description,
  868. "capture" => $capture,
  869. "metadata" => $metadata,
  870. "source" => "$source"
  871. ));
  872. } else {
  873. $paymentarray = array(
  874. "amount" => "$stripeamount",
  875. "currency" => "$currency",
  876. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  877. "description" => "Stripe payment: ".$description,
  878. "capture" => $capture,
  879. "metadata" => $metadata,
  880. "source" => "$source",
  881. "customer" => "$customer"
  882. );
  883. if ($societe->email && $usethirdpartyemailforreceiptemail) {
  884. $paymentarray["receipt_email"] = $societe->email;
  885. }
  886. $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description"));
  887. }
  888. } else {
  889. // With Stripe Connect
  890. $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
  891. if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  892. $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
  893. } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  894. $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
  895. }
  896. if (!in_array($currency, $arrayzerounitcurrency)) {
  897. $stripefee = round($fee * 100);
  898. } else {
  899. $stripefee = round($fee);
  900. }
  901. $paymentarray = array(
  902. "amount" => "$stripeamount",
  903. "currency" => "$currency",
  904. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  905. "description" => "Stripe payment: ".$description,
  906. "capture" => $capture,
  907. "metadata" => $metadata,
  908. "source" => "$source",
  909. "customer" => "$customer"
  910. );
  911. if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0) {
  912. $paymentarray["application_fee_amount"] = $stripefee;
  913. }
  914. if ($societe->email && $usethirdpartyemailforreceiptemail) {
  915. $paymentarray["receipt_email"] = $societe->email;
  916. }
  917. if (preg_match('/pm_/i', $source)) {
  918. $stripecard = $source;
  919. $amountstripe = $stripeamount;
  920. $FULLTAG = 'PFBO'; // Payment From Back Office
  921. $stripe = $return;
  922. $amounttopay = $amount;
  923. $servicestatus = $status;
  924. dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
  925. $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
  926. dol_syslog("* createPaymentStripe Create payment on card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
  927. // Create payment intent and charge payment (confirmnow = true)
  928. $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
  929. $charge = new stdClass();
  930. if ($paymentintent->status == 'succeeded') {
  931. $charge->status = 'ok';
  932. $charge->id = $paymentintent->id;
  933. } else {
  934. $charge->status = 'failed';
  935. $charge->failure_code = $stripe->code;
  936. $charge->failure_message = $stripe->error;
  937. $charge->failure_declinecode = $stripe->declinecode;
  938. }
  939. } else {
  940. $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description", "stripe_account" => "$account"));
  941. }
  942. }
  943. if (isset($charge->id)) {
  944. }
  945. $return->statut = 'success';
  946. $return->id = $charge->id;
  947. if (preg_match('/pm_/i', $source)) {
  948. $return->message = 'Payment retrieved by card status = '.$charge->status;
  949. } else {
  950. if ($charge->source->type == 'card') {
  951. $return->message = $charge->source->card->brand." ....".$charge->source->card->last4;
  952. } elseif ($charge->source->type == 'three_d_secure') {
  953. $stripe = new Stripe($this->db);
  954. $src = \Stripe\Source::retrieve("".$charge->source->three_d_secure->card."", array(
  955. "stripe_account" => $stripe->getStripeAccount($service)
  956. ));
  957. $return->message = $src->card->brand." ....".$src->card->last4;
  958. } else {
  959. $return->message = $charge->id;
  960. }
  961. }
  962. } catch (\Stripe\Error\Card $e) {
  963. include DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
  964. // Since it's a decline, \Stripe\Error\Card will be caught
  965. $body = $e->getJsonBody();
  966. $err = $body['error'];
  967. $return->statut = 'error';
  968. $return->id = $err['charge'];
  969. $return->type = $err['type'];
  970. $return->code = $err['code'];
  971. $return->message = $err['message'];
  972. $body = "Error: <br>".$return->id." ".$return->message." ";
  973. $subject = '[Alert] Payment error using Stripe';
  974. $cmailfile = new CMailFile($subject, $conf->global->ONLINE_PAYMENT_SENDEMAIL, $conf->global->MAIN_INFO_SOCIETE_MAIL, $body);
  975. $cmailfile->sendfile();
  976. $error++;
  977. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  978. } catch (\Stripe\Error\RateLimit $e) {
  979. // Too many requests made to the API too quickly
  980. $error++;
  981. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  982. } catch (\Stripe\Error\InvalidRequest $e) {
  983. // Invalid parameters were supplied to Stripe's API
  984. $error++;
  985. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  986. } catch (\Stripe\Error\Authentication $e) {
  987. // Authentication with Stripe's API failed
  988. // (maybe you changed API keys recently)
  989. $error++;
  990. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  991. } catch (\Stripe\Error\ApiConnection $e) {
  992. // Network communication with Stripe failed
  993. $error++;
  994. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  995. } catch (\Stripe\Error\Base $e) {
  996. // Display a very generic error to the user, and maybe send
  997. // yourself an email
  998. $error++;
  999. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1000. } catch (Exception $e) {
  1001. // Something else happened, completely unrelated to Stripe
  1002. $error++;
  1003. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1004. }
  1005. return $return;
  1006. }
  1007. }