stripe.class.php 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293
  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 = empty($tmparray->stripe_user_id) ? '' : $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 reader Object from its ID
  252. *
  253. * @param string $reader Reader ID
  254. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  255. * @param int $status Status (0=test, 1=live)
  256. * @return \Stripe\Terminal\Reader|null Stripe Reader or null if not found
  257. */
  258. public function getSelectedReader($reader, $key = '', $status = 0)
  259. {
  260. $selectedreader = null;
  261. try {
  262. // Force to use the correct API key
  263. global $stripearrayofkeysbyenv;
  264. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  265. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  266. $selectedreader = \Stripe\Terminal\Reader::retrieve(''.$reader.'');
  267. } else {
  268. $stripepaymentmethod = \Stripe\Terminal\Reader::retrieve(''.$reader.'', array("stripe_account" => $key));
  269. }
  270. } catch (Exception $e) {
  271. $this->error = $e->getMessage();
  272. }
  273. return $selectedreader;
  274. }
  275. /**
  276. * Get the Stripe payment intent. Create it with confirmnow=false
  277. * Warning. If a payment was tried and failed, a payment intent was created.
  278. * But if we change something on object to pay (amount or other), reusing same payment intent is not allowed by Stripe.
  279. * Recommended solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
  280. * 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)
  281. * 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
  282. * but not when using the STRIPE_USE_NEW_CHECKOUT.
  283. *
  284. * @param double $amount Amount
  285. * @param string $currency_code Currency code
  286. * @param string $tag Tag
  287. * @param string $description Description
  288. * @param mixed $object Object to pay with Stripe
  289. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  290. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  291. * @param int $status Status (0=test, 1=live)
  292. * @param int $usethirdpartyemailforreceiptemail 1=use thirdparty email for receipt
  293. * @param int $mode automatic=automatic confirmation/payment when conditions are ok, manual=need to call confirm() on intent
  294. * @param boolean $confirmnow false=default, true=try to confirm immediatly after create (if conditions are ok)
  295. * @param string $payment_method 'pm_....' (if known)
  296. * @param string $off_session If we use an already known payment method to pay when customer is not available during the checkout flow.
  297. * @param string $noidempotency_key Do not use the idempotency_key when creating the PaymentIntent
  298. * @return \Stripe\PaymentIntent|null Stripe PaymentIntent or null if not found and failed to create
  299. */
  300. 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)
  301. {
  302. global $conf, $user;
  303. dol_syslog(get_class($this)."::getPaymentIntent", LOG_INFO, 1);
  304. $error = 0;
  305. if (empty($status)) {
  306. $service = 'StripeTest';
  307. } else {
  308. $service = 'StripeLive';
  309. }
  310. $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
  311. if (!in_array($currency_code, $arrayzerounitcurrency)) {
  312. $stripeamount = $amount * 100;
  313. } else {
  314. $stripeamount = $amount;
  315. }
  316. $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
  317. if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  318. $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
  319. } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  320. $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
  321. }
  322. if (!in_array($currency_code, $arrayzerounitcurrency)) {
  323. $stripefee = round($fee * 100);
  324. } else {
  325. $stripefee = round($fee);
  326. }
  327. $paymentintent = null;
  328. if (is_object($object) && getDolGlobalInt('STRIPE_REUSE_EXISTING_INTENT_IF_FOUND') && !getDolGlobalInt('STRIPE_CARD_PRESENT')) {
  329. // Warning. If a payment was tried and failed, a payment intent was created.
  330. // 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.
  331. // 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
  332. // automatically return the existing payment intent if idempotency is provided when we try to create the new one.
  333. // 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)
  334. $sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
  335. $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
  336. $sql .= " WHERE pi.fk_facture = ".((int) $object->id);
  337. $sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
  338. $sql .= " AND pi.entity IN (".getEntity('societe').")";
  339. $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
  340. dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);
  341. $resql = $this->db->query($sql);
  342. if ($resql) {
  343. $num = $this->db->num_rows($resql);
  344. if ($num) {
  345. $obj = $this->db->fetch_object($resql);
  346. $intent = $obj->ext_payment_id;
  347. dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record");
  348. // Force to use the correct API key
  349. global $stripearrayofkeysbyenv;
  350. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  351. try {
  352. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  353. $paymentintent = \Stripe\PaymentIntent::retrieve($intent);
  354. } else {
  355. $paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
  356. }
  357. } catch (Exception $e) {
  358. $error++;
  359. $this->error = $e->getMessage();
  360. }
  361. }
  362. }
  363. }
  364. if (empty($paymentintent)) {
  365. // Try to create intent. See https://stripe.com/docs/api/payment_intents/create
  366. $ipaddress = getUserRemoteIP();
  367. $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
  368. if (is_object($object)) {
  369. $metadata['dol_type'] = $object->element;
  370. $metadata['dol_id'] = $object->id;
  371. if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
  372. $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
  373. }
  374. }
  375. // list of payment method types
  376. $paymentmethodtypes = array("card");
  377. $descriptor = dol_trunc($tag, 10, 'right', 'UTF-8', 1);
  378. if (getDolGlobalInt('STRIPE_SEPA_DIRECT_DEBIT')) {
  379. $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
  380. //$descriptor = preg_replace('/ref=[^:=]+/', '', $descriptor); // Clean ref
  381. }
  382. if (getDolGlobalInt('STRIPE_KLARNA')) {
  383. $paymentmethodtypes[] = "klarna";
  384. }
  385. if (getDolGlobalInt('STRIPE_BANCONTACT')) {
  386. $paymentmethodtypes[] = "bancontact";
  387. }
  388. if (getDolGlobalInt('STRIPE_IDEAL')) {
  389. $paymentmethodtypes[] = "ideal";
  390. }
  391. if (getDolGlobalInt('STRIPE_GIROPAY')) {
  392. $paymentmethodtypes[] = "giropay";
  393. }
  394. if (getDolGlobalInt('STRIPE_SOFORT')) {
  395. $paymentmethodtypes[] = "sofort";
  396. }
  397. if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
  398. $paymentmethodtypes = array("card_present");
  399. }
  400. $dataforintent = array(
  401. "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
  402. "confirmation_method" => $mode,
  403. "amount" => $stripeamount,
  404. "currency" => $currency_code,
  405. "payment_method_types" => $paymentmethodtypes,
  406. "description" => $description,
  407. //"save_payment_method" => true,
  408. "setup_future_usage" => "on_session",
  409. "metadata" => $metadata
  410. );
  411. if ($descriptor) {
  412. $dataforintent["statement_descriptor_suffix"] = $descriptor; // For card payment, 22 chars that appears on bank receipt (prefix into stripe setup + this suffix)
  413. $dataforintent["statement_descriptor"] = $descriptor; // For SEPA, it will take only statement_descriptor, not statement_descriptor_suffix
  414. }
  415. if (!is_null($customer)) {
  416. $dataforintent["customer"] = $customer;
  417. }
  418. // payment_method =
  419. // payment_method_types = array('card')
  420. //var_dump($dataforintent);
  421. if ($off_session) {
  422. unset($dataforintent['setup_future_usage']);
  423. // We can't use both "setup_future_usage" = "off_session" and "off_session" = true.
  424. // Because $off_session parameter is dedicated to create paymentintent off_line (and not future payment), we need to use "off_session" = true.
  425. //$dataforintent["setup_future_usage"] = "off_session";
  426. $dataforintent["off_session"] = true;
  427. }
  428. if (getDolGlobalInt('STRIPE_GIROPAY')) {
  429. unset($dataforintent['setup_future_usage']);
  430. }
  431. if (getDolGlobalInt('STRIPE_KLARNA')) {
  432. unset($dataforintent['setup_future_usage']);
  433. }
  434. if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
  435. unset($dataforintent['setup_future_usage']);
  436. $dataforintent["capture_method"] = "manual";
  437. $dataforintent["confirmation_method"] = "manual";
  438. }
  439. if (!is_null($payment_method)) {
  440. $dataforintent["payment_method"] = $payment_method;
  441. $description .= ' - '.$payment_method;
  442. }
  443. if ($conf->entity != getDolGlobalInt('STRIPECONNECT_PRINCIPAL') && $stripefee > 0) {
  444. $dataforintent["application_fee_amount"] = $stripefee;
  445. }
  446. if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
  447. $dataforintent["receipt_email"] = $object->thirdparty->email;
  448. }
  449. try {
  450. // Force to use the correct API key
  451. global $stripearrayofkeysbyenv;
  452. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  453. $arrayofoptions = array();
  454. if (empty($noidempotency_key)) {
  455. $arrayofoptions["idempotency_key"] = $description;
  456. }
  457. // 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.
  458. if (!empty($key)) { // If the Stripe connect account not set, we use common API usage
  459. $arrayofoptions["stripe_account"] = $key;
  460. }
  461. dol_syslog("dataforintent to create paymentintent = ".var_export($dataforintent, true));
  462. $paymentintent = \Stripe\PaymentIntent::create($dataforintent, $arrayofoptions);
  463. // Store the payment intent
  464. if (is_object($object)) {
  465. $paymentintentalreadyexists = 0;
  466. // Check that payment intent $paymentintent->id is not already recorded.
  467. $sql = "SELECT pi.rowid";
  468. $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
  469. $sql .= " WHERE pi.entity IN (".getEntity('societe').")";
  470. $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
  471. $sql .= " AND pi.ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
  472. dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
  473. $resql = $this->db->query($sql);
  474. if ($resql) {
  475. $num = $this->db->num_rows($resql);
  476. if ($num) {
  477. $obj = $this->db->fetch_object($resql);
  478. if ($obj) {
  479. $paymentintentalreadyexists++;
  480. }
  481. }
  482. } else {
  483. dol_print_error($this->db);
  484. }
  485. // If not, we create it.
  486. if (!$paymentintentalreadyexists) {
  487. $now = dol_now();
  488. $sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site, amount)";
  489. $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).")";
  490. $resql = $this->db->query($sql);
  491. if (!$resql) {
  492. $error++;
  493. $this->error = $this->db->lasterror();
  494. dol_syslog(get_class($this)."::PaymentIntent failed to insert paymentintent with id=".$paymentintent->id." into database.", LOG_ERR);
  495. }
  496. }
  497. } else {
  498. $_SESSION["stripe_payment_intent"] = $paymentintent;
  499. }
  500. } catch (Stripe\Error\Card $e) {
  501. $error++;
  502. $this->error = $e->getMessage();
  503. $this->code = $e->getStripeCode();
  504. $this->declinecode = $e->getDeclineCode();
  505. } catch (Exception $e) {
  506. //var_dump($dataforintent);
  507. //var_dump($description);
  508. //var_dump($key);
  509. //var_dump($paymentintent);
  510. //var_dump($e->getMessage());
  511. //var_dump($e);
  512. $error++;
  513. $this->error = $e->getMessage();
  514. $this->code = '';
  515. $this->declinecode = '';
  516. }
  517. }
  518. dol_syslog(get_class($this)."::getPaymentIntent return error=".$error." this->error=".$this->error, LOG_INFO, -1);
  519. if (!$error) {
  520. return $paymentintent;
  521. } else {
  522. return null;
  523. }
  524. }
  525. /**
  526. * Get the Stripe payment intent. Create it with confirmnow=false
  527. * Warning. If a payment was tried and failed, a payment intent was created.
  528. * But if we change something on object to pay (amount or other), reusing same payment intent is not allowed.
  529. * Recommanded solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
  530. * 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)
  531. * 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
  532. * but not when using the STRIPE_USE_NEW_CHECKOUT.
  533. *
  534. * @param string $description Description
  535. * @param Societe $object Object to pay with Stripe
  536. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  537. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  538. * @param int $status Status (0=test, 1=live)
  539. * @param int $usethirdpartyemailforreceiptemail 1=use thirdparty email for receipt
  540. * @param boolean $confirmnow false=default, true=try to confirm immediatly after create (if conditions are ok)
  541. * @return \Stripe\SetupIntent|null Stripe SetupIntent or null if not found and failed to create
  542. */
  543. public function getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail = 0, $confirmnow = false)
  544. {
  545. global $conf;
  546. dol_syslog("getSetupIntent description=".$description.' confirmnow='.$confirmnow, LOG_INFO, 1);
  547. $error = 0;
  548. if (empty($status)) {
  549. $service = 'StripeTest';
  550. } else {
  551. $service = 'StripeLive';
  552. }
  553. $setupintent = null;
  554. if (empty($setupintent)) {
  555. $ipaddress = getUserRemoteIP();
  556. $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
  557. if (is_object($object)) {
  558. $metadata['dol_type'] = $object->element;
  559. $metadata['dol_id'] = $object->id;
  560. if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
  561. $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
  562. }
  563. }
  564. // list of payment method types
  565. $paymentmethodtypes = array("card");
  566. if (!empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) {
  567. $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
  568. }
  569. if (!empty($conf->global->STRIPE_BANCONTACT)) {
  570. $paymentmethodtypes[] = "bancontact";
  571. }
  572. if (!empty($conf->global->STRIPE_IDEAL)) {
  573. $paymentmethodtypes[] = "ideal";
  574. }
  575. // Giropay not possible for setup intent
  576. if (!empty($conf->global->STRIPE_SOFORT)) {
  577. $paymentmethodtypes[] = "sofort";
  578. }
  579. $dataforintent = array(
  580. "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
  581. "payment_method_types" => $paymentmethodtypes,
  582. "usage" => "off_session",
  583. "metadata" => $metadata
  584. );
  585. if (!is_null($customer)) {
  586. $dataforintent["customer"] = $customer;
  587. }
  588. if (!is_null($description)) {
  589. $dataforintent["description"] = $description;
  590. }
  591. // payment_method =
  592. // payment_method_types = array('card')
  593. //var_dump($dataforintent);
  594. if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
  595. $dataforintent["receipt_email"] = $object->thirdparty->email;
  596. }
  597. try {
  598. // Force to use the correct API key
  599. global $stripearrayofkeysbyenv;
  600. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  601. dol_syslog("getSetupIntent ".$stripearrayofkeysbyenv[$status]['publishable_key'], LOG_DEBUG);
  602. // 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.
  603. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  604. //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description"));
  605. $setupintent = \Stripe\SetupIntent::create($dataforintent, array());
  606. } else {
  607. //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description", "stripe_account" => $key));
  608. $setupintent = \Stripe\SetupIntent::create($dataforintent, array("stripe_account" => $key));
  609. }
  610. //var_dump($setupintent->id);
  611. // Store the setup intent
  612. /*if (is_object($object))
  613. {
  614. $setupintentalreadyexists = 0;
  615. // Check that payment intent $setupintent->id is not already recorded.
  616. $sql = "SELECT pi.rowid";
  617. $sql.= " FROM " . MAIN_DB_PREFIX . "prelevement_demande as pi";
  618. $sql.= " WHERE pi.entity IN (".getEntity('societe').")";
  619. $sql.= " AND pi.ext_payment_site = '" . $this->db->escape($service) . "'";
  620. $sql.= " AND pi.ext_payment_id = '".$this->db->escape($setupintent->id)."'";
  621. dol_syslog(get_class($this) . "::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
  622. $resql = $this->db->query($sql);
  623. if ($resql) {
  624. $num = $this->db->num_rows($resql);
  625. if ($num)
  626. {
  627. $obj = $this->db->fetch_object($resql);
  628. if ($obj) $setupintentalreadyexists++;
  629. }
  630. }
  631. else dol_print_error($this->db);
  632. // If not, we create it.
  633. if (! $setupintentalreadyexists)
  634. {
  635. $now=dol_now();
  636. $sql = "INSERT INTO " . MAIN_DB_PREFIX . "prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site)";
  637. $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).")";
  638. $resql = $this->db->query($sql);
  639. if (! $resql)
  640. {
  641. $error++;
  642. $this->error = $this->db->lasterror();
  643. dol_syslog(get_class($this) . "::PaymentIntent failed to insert paymentintent with id=".$setupintent->id." into database.");
  644. }
  645. }
  646. }
  647. else
  648. {
  649. $_SESSION["stripe_setup_intent"] = $setupintent;
  650. }*/
  651. } catch (Exception $e) {
  652. //var_dump($dataforintent);
  653. //var_dump($description);
  654. //var_dump($key);
  655. //var_dump($setupintent);
  656. //var_dump($e->getMessage());
  657. $error++;
  658. $this->error = $e->getMessage();
  659. }
  660. }
  661. if (!$error) {
  662. dol_syslog("getSetupIntent ".(is_object($setupintent) ? $setupintent->id : ''), LOG_INFO, -1);
  663. return $setupintent;
  664. } else {
  665. dol_syslog("getSetupIntent return error=".$error, LOG_INFO, -1);
  666. return null;
  667. }
  668. }
  669. /**
  670. * 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)
  671. *
  672. * @param \Stripe\StripeCustomer $cu Object stripe customer.
  673. * @param CompanyPaymentMode $object Object companypaymentmode to check, or create on stripe (create on stripe also update the societe_rib table for current entity)
  674. * @param string $stripeacc ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  675. * @param int $status Status (0=test, 1=live)
  676. * @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.
  677. * @return \Stripe\StripeCard|\Stripe\PaymentMethod|null Stripe Card or null if not found
  678. */
  679. public function cardStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
  680. {
  681. global $conf, $user, $langs;
  682. $card = null;
  683. $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_....
  684. $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
  685. $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
  686. $sql .= " AND sa.type = 'card'";
  687. dol_syslog(get_class($this)."::cardStripe search stripe card id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
  688. $resql = $this->db->query($sql);
  689. if ($resql) {
  690. $num = $this->db->num_rows($resql);
  691. if ($num) {
  692. $obj = $this->db->fetch_object($resql);
  693. $cardref = $obj->stripe_card_ref;
  694. dol_syslog(get_class($this)."::cardStripe cardref=".$cardref);
  695. if ($cardref) {
  696. try {
  697. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  698. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  699. $card = $cu->sources->retrieve($cardref);
  700. } else {
  701. $card = \Stripe\PaymentMethod::retrieve($cardref);
  702. }
  703. } else {
  704. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  705. //$card = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
  706. $card = $cu->sources->retrieve($cardref);
  707. } else {
  708. //$card = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
  709. $card = \Stripe\PaymentMethod::retrieve($cardref);
  710. }
  711. }
  712. } catch (Exception $e) {
  713. $this->error = $e->getMessage();
  714. dol_syslog($this->error, LOG_WARNING);
  715. }
  716. } elseif ($createifnotlinkedtostripe) {
  717. $exp_date_month = $obj->exp_date_month;
  718. $exp_date_year = $obj->exp_date_year;
  719. $number = $obj->number;
  720. $cvc = $obj->cvn; // cvn in database, cvc for stripe
  721. $cardholdername = $obj->proprio;
  722. $ipaddress = getUserRemoteIP();
  723. $dataforcard = array(
  724. "source" => array('object'=>'card', 'exp_month'=>$exp_date_month, 'exp_year'=>$exp_date_year, 'number'=>$number, 'cvc'=>$cvc, 'name'=>$cardholdername),
  725. "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
  726. );
  727. //$a = \Stripe\Stripe::getApiKey();
  728. //var_dump($a);
  729. //var_dump($stripeacc);exit;
  730. try {
  731. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  732. if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION)) {
  733. dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
  734. $card = $cu->sources->create($dataforcard);
  735. if (!$card) {
  736. $this->error = 'Creation of card on Stripe has failed';
  737. }
  738. } else {
  739. $connect = '';
  740. if (!empty($stripeacc)) {
  741. $connect = $stripeacc.'/';
  742. }
  743. $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
  744. if ($status) {
  745. $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
  746. }
  747. $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
  748. //dol_syslog("Error: This case is not supported", LOG_ERR);
  749. $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
  750. }
  751. } else {
  752. if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION)) {
  753. dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
  754. $card = $cu->sources->create($dataforcard, array("stripe_account" => $stripeacc));
  755. if (!$card) {
  756. $this->error = 'Creation of card on Stripe has failed';
  757. }
  758. } else {
  759. $connect = '';
  760. if (!empty($stripeacc)) {
  761. $connect = $stripeacc.'/';
  762. }
  763. $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
  764. if ($status) {
  765. $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
  766. }
  767. $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
  768. //dol_syslog("Error: This case is not supported", LOG_ERR);
  769. $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
  770. }
  771. }
  772. if ($card) {
  773. $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
  774. $sql .= " SET stripe_card_ref = '".$this->db->escape($card->id)."', card_type = '".$this->db->escape($card->brand)."',";
  775. $sql .= " country_code = '".$this->db->escape($card->country)."',";
  776. $sql .= " approved = ".($card->cvc_check == 'pass' ? 1 : 0);
  777. $sql .= " WHERE rowid = ".((int) $object->id);
  778. $sql .= " AND type = 'card'";
  779. $resql = $this->db->query($sql);
  780. if (!$resql) {
  781. $this->error = $this->db->lasterror();
  782. }
  783. }
  784. } catch (Exception $e) {
  785. $this->error = $e->getMessage();
  786. dol_syslog($this->error, LOG_WARNING);
  787. }
  788. }
  789. }
  790. } else {
  791. dol_print_error($this->db);
  792. }
  793. return $card;
  794. }
  795. /**
  796. * Get the Stripe SEPA of a company payment mode
  797. *
  798. * @param \Stripe\StripeCustomer $cu Object stripe customer.
  799. * @param CompanyPaymentMode $object Object companypaymentmode to check, or create on stripe (create on stripe also update the societe_rib table for current entity)
  800. * @param string $stripeacc ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  801. * @param int $status Status (0=test, 1=live)
  802. * @param int $createifnotlinkedtostripe 1=Create the stripe sepa and the link if the sepa is not yet linked to a stripe sepa. Deprecated with new Stripe API and SCA.
  803. * @return \Stripe\PaymentMethod|null Stripe SEPA or null if not found
  804. */
  805. public function sepaStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
  806. {
  807. global $conf, $user, $langs;
  808. $sepa = null;
  809. $sql = "SELECT sa.stripe_card_ref, sa.proprio, sa.iban_prefix"; // stripe_card_ref is src_ for sepa
  810. $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
  811. $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
  812. $sql .= " AND sa.type = 'ban'"; //type ban to get normal bank account of customer (prelevement)
  813. $soc = new Societe($this->db);
  814. $soc->fetch($object->fk_soc);
  815. dol_syslog(get_class($this)."::sepaStripe search stripe ban id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
  816. $resql = $this->db->query($sql);
  817. if ($resql) {
  818. $num = $this->db->num_rows($resql);
  819. if ($num) {
  820. $obj = $this->db->fetch_object($resql);
  821. $cardref = $obj->stripe_card_ref;
  822. dol_syslog(get_class($this)."::sepaStripe cardref=".$cardref);
  823. if ($cardref) {
  824. try {
  825. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  826. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  827. $sepa = $cu->sources->retrieve($cardref);
  828. } else {
  829. $sepa = \Stripe\PaymentMethod::retrieve($cardref);
  830. }
  831. } else {
  832. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  833. //$sepa = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
  834. $sepa = $cu->sources->retrieve($cardref);
  835. } else {
  836. //$sepa = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
  837. $sepa = \Stripe\PaymentMethod::retrieve($cardref);
  838. }
  839. }
  840. } catch (Exception $e) {
  841. $this->error = $e->getMessage();
  842. dol_syslog($this->error, LOG_WARNING);
  843. }
  844. } elseif ($createifnotlinkedtostripe) {
  845. $iban = $obj->iban_prefix; //prefix ?
  846. $ipaddress = getUserRemoteIP();
  847. $dataforcard = array(
  848. 'type'=>'sepa_debit',
  849. "sepa_debit" => array('iban' => $iban),
  850. 'currency' => 'eur',
  851. 'usage' => 'reusable',
  852. 'owner' => array(
  853. 'name' => $soc->name,
  854. ),
  855. "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
  856. );
  857. //$a = \Stripe\Stripe::getApiKey();
  858. //var_dump($a);var_dump($stripeacc);exit;
  859. try {
  860. dol_syslog("Try to create sepa_debit 0");
  861. $service = 'StripeTest';
  862. $servicestatus = 0;
  863. if (!empty($conf->global->STRIPE_LIVE) && !GETPOST('forcesandbox', 'alpha')) {
  864. $service = 'StripeLive';
  865. $servicestatus = 1;
  866. }
  867. // Force to use the correct API key
  868. global $stripearrayofkeysbyenv;
  869. $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];
  870. dol_syslog("Try to create sepa_debit with data = ".json_encode($dataforcard));
  871. $s = new \Stripe\StripeClient($stripeacc);
  872. $sepa = $s->sources->create($dataforcard);
  873. if (!$sepa) {
  874. $this->error = 'Creation of sepa_debit on Stripe has failed';
  875. } else {
  876. // association du client avec cette source de paimeent
  877. $cs = $cu->createSource($cu->id, array('source' => $sepa->id));
  878. if (!$cs) {
  879. $this->error = 'Link SEPA <-> Customer failed';
  880. } else {
  881. dol_syslog("Try to create sepa_debit 3");
  882. // print json_encode($sepa);
  883. $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
  884. $sql .= " SET stripe_card_ref = '".$this->db->escape($sepa->id)."', card_type = 'sepa_debit',";
  885. $sql .= " stripe_account= '" . $this->db->escape($cu->id . "@" . $stripeacc) . "'";
  886. $sql .= " WHERE rowid = ".((int) $object->id);
  887. $sql .= " AND type = 'ban'";
  888. $resql = $this->db->query($sql);
  889. if (!$resql) {
  890. $this->error = $this->db->lasterror();
  891. }
  892. }
  893. }
  894. } catch (Exception $e) {
  895. $this->error = $e->getMessage();
  896. dol_syslog($this->error, LOG_WARNING);
  897. }
  898. }
  899. }
  900. } else {
  901. dol_print_error($this->db);
  902. }
  903. return $sepa;
  904. }
  905. /**
  906. * Create charge.
  907. * This is called by page htdocs/stripe/payment.php and may be deprecated.
  908. *
  909. * @param int $amount Amount to pay
  910. * @param string $currency EUR, GPB...
  911. * @param string $origin Object type to pay (order, invoice, contract...)
  912. * @param int $item Object id to pay
  913. * @param string $source src_xxxxx or card_xxxxx or pm_xxxxx
  914. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  915. * @param string $account Stripe account ref 'acc_xxxxxxxxxxxxx' via getStripeAccount()
  916. * @param int $status Status (0=test, 1=live)
  917. * @param int $usethirdpartyemailforreceiptemail Use thirdparty email as receipt email
  918. * @param boolean $capture Set capture flag to true (take payment) or false (wait)
  919. * @return Stripe
  920. */
  921. public function createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status = 0, $usethirdpartyemailforreceiptemail = 0, $capture = true)
  922. {
  923. global $conf;
  924. $error = 0;
  925. if (empty($status)) {
  926. $service = 'StripeTest';
  927. } else {
  928. $service = 'StripeLive';
  929. }
  930. $sql = "SELECT sa.key_account as key_account, sa.fk_soc, sa.entity";
  931. $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
  932. $sql .= " WHERE sa.key_account = '".$this->db->escape($customer)."'";
  933. //$sql.= " AND sa.entity IN (".getEntity('societe').")";
  934. $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
  935. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  936. $result = $this->db->query($sql);
  937. if ($result) {
  938. if ($this->db->num_rows($result)) {
  939. $obj = $this->db->fetch_object($result);
  940. $key = $obj->fk_soc;
  941. } else {
  942. $key = null;
  943. }
  944. } else {
  945. $key = null;
  946. }
  947. $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
  948. if (!in_array($currency, $arrayzerounitcurrency)) {
  949. $stripeamount = $amount * 100;
  950. } else {
  951. $stripeamount = $amount;
  952. }
  953. $societe = new Societe($this->db);
  954. if ($key > 0) {
  955. $societe->fetch($key);
  956. }
  957. $description = "";
  958. $ref = "";
  959. if ($origin == 'order') {
  960. $order = new Commande($this->db);
  961. $order->fetch($item);
  962. $ref = $order->ref;
  963. $description = "ORD=".$ref.".CUS=".$societe->id.".PM=stripe";
  964. } elseif ($origin == 'invoice') {
  965. $invoice = new Facture($this->db);
  966. $invoice->fetch($item);
  967. $ref = $invoice->ref;
  968. $description = "INV=".$ref.".CUS=".$societe->id.".PM=stripe";
  969. }
  970. $ipaddress = getUserRemoteIP();
  971. $metadata = array(
  972. "dol_id" => "".$item."",
  973. "dol_type" => "".$origin."",
  974. "dol_thirdparty_id" => "".$societe->id."",
  975. 'dol_thirdparty_name' => $societe->name,
  976. 'dol_version'=>DOL_VERSION,
  977. 'dol_entity'=>$conf->entity,
  978. 'ipaddress'=>$ipaddress
  979. );
  980. $return = new Stripe($this->db);
  981. try {
  982. // Force to use the correct API key
  983. global $stripearrayofkeysbyenv;
  984. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  985. if (empty($conf->stripeconnect->enabled)) { // With a common Stripe account
  986. if (preg_match('/pm_/i', $source)) {
  987. $stripecard = $source;
  988. $amountstripe = $stripeamount;
  989. $FULLTAG = 'PFBO'; // Payment From Back Office
  990. $stripe = $return;
  991. $amounttopay = $amount;
  992. $servicestatus = $status;
  993. dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
  994. $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
  995. dol_syslog("* createPaymentStripe Create payment for customer ".$customer->id." on source card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
  996. // Create payment intent and charge payment (confirmnow = true)
  997. $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
  998. $charge = new stdClass();
  999. if ($paymentintent->status == 'succeeded') {
  1000. $charge->status = 'ok';
  1001. } else {
  1002. $charge->status = 'failed';
  1003. $charge->failure_code = $stripe->code;
  1004. $charge->failure_message = $stripe->error;
  1005. $charge->failure_declinecode = $stripe->declinecode;
  1006. $stripefailurecode = $stripe->code;
  1007. $stripefailuremessage = $stripe->error;
  1008. $stripefailuredeclinecode = $stripe->declinecode;
  1009. }
  1010. } elseif (preg_match('/acct_/i', $source)) {
  1011. $charge = \Stripe\Charge::create(array(
  1012. "amount" => "$stripeamount",
  1013. "currency" => "$currency",
  1014. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  1015. "description" => "Stripe payment: ".$description,
  1016. "capture" => $capture,
  1017. "metadata" => $metadata,
  1018. "source" => "$source"
  1019. ));
  1020. } else {
  1021. $paymentarray = array(
  1022. "amount" => "$stripeamount",
  1023. "currency" => "$currency",
  1024. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  1025. "description" => "Stripe payment: ".$description,
  1026. "capture" => $capture,
  1027. "metadata" => $metadata,
  1028. "source" => "$source",
  1029. "customer" => "$customer"
  1030. );
  1031. if ($societe->email && $usethirdpartyemailforreceiptemail) {
  1032. $paymentarray["receipt_email"] = $societe->email;
  1033. }
  1034. $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description"));
  1035. }
  1036. } else {
  1037. // With Stripe Connect
  1038. $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
  1039. if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  1040. $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
  1041. } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  1042. $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
  1043. }
  1044. if (!in_array($currency, $arrayzerounitcurrency)) {
  1045. $stripefee = round($fee * 100);
  1046. } else {
  1047. $stripefee = round($fee);
  1048. }
  1049. $paymentarray = array(
  1050. "amount" => "$stripeamount",
  1051. "currency" => "$currency",
  1052. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  1053. "description" => "Stripe payment: ".$description,
  1054. "capture" => $capture,
  1055. "metadata" => $metadata,
  1056. "source" => "$source",
  1057. "customer" => "$customer"
  1058. );
  1059. if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0) {
  1060. $paymentarray["application_fee_amount"] = $stripefee;
  1061. }
  1062. if ($societe->email && $usethirdpartyemailforreceiptemail) {
  1063. $paymentarray["receipt_email"] = $societe->email;
  1064. }
  1065. if (preg_match('/pm_/i', $source)) {
  1066. $stripecard = $source;
  1067. $amountstripe = $stripeamount;
  1068. $FULLTAG = 'PFBO'; // Payment From Back Office
  1069. $stripe = $return;
  1070. $amounttopay = $amount;
  1071. $servicestatus = $status;
  1072. dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
  1073. $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
  1074. dol_syslog("* createPaymentStripe Create payment on card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
  1075. // Create payment intent and charge payment (confirmnow = true)
  1076. $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
  1077. $charge = new stdClass();
  1078. if ($paymentintent->status == 'succeeded') {
  1079. $charge->status = 'ok';
  1080. $charge->id = $paymentintent->id;
  1081. } else {
  1082. $charge->status = 'failed';
  1083. $charge->failure_code = $stripe->code;
  1084. $charge->failure_message = $stripe->error;
  1085. $charge->failure_declinecode = $stripe->declinecode;
  1086. }
  1087. } else {
  1088. $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description", "stripe_account" => "$account"));
  1089. }
  1090. }
  1091. if (isset($charge->id)) {
  1092. }
  1093. $return->statut = 'success';
  1094. $return->id = $charge->id;
  1095. if (preg_match('/pm_/i', $source)) {
  1096. $return->message = 'Payment retrieved by card status = '.$charge->status;
  1097. } else {
  1098. if ($charge->source->type == 'card') {
  1099. $return->message = $charge->source->card->brand." ....".$charge->source->card->last4;
  1100. } elseif ($charge->source->type == 'three_d_secure') {
  1101. $stripe = new Stripe($this->db);
  1102. $src = \Stripe\Source::retrieve("".$charge->source->three_d_secure->card."", array(
  1103. "stripe_account" => $stripe->getStripeAccount($service)
  1104. ));
  1105. $return->message = $src->card->brand." ....".$src->card->last4;
  1106. } else {
  1107. $return->message = $charge->id;
  1108. }
  1109. }
  1110. } catch (\Stripe\Error\Card $e) {
  1111. include DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
  1112. // Since it's a decline, \Stripe\Error\Card will be caught
  1113. $body = $e->getJsonBody();
  1114. $err = $body['error'];
  1115. $return->statut = 'error';
  1116. $return->id = $err['charge'];
  1117. $return->type = $err['type'];
  1118. $return->code = $err['code'];
  1119. $return->message = $err['message'];
  1120. $body = "Error: <br>".$return->id." ".$return->message." ";
  1121. $subject = '[Alert] Payment error using Stripe';
  1122. $cmailfile = new CMailFile($subject, $conf->global->ONLINE_PAYMENT_SENDEMAIL, $conf->global->MAIN_INFO_SOCIETE_MAIL, $body);
  1123. $cmailfile->sendfile();
  1124. $error++;
  1125. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1126. } catch (\Stripe\Error\RateLimit $e) {
  1127. // Too many requests made to the API too quickly
  1128. $error++;
  1129. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1130. } catch (\Stripe\Error\InvalidRequest $e) {
  1131. // Invalid parameters were supplied to Stripe's API
  1132. $error++;
  1133. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1134. } catch (\Stripe\Error\Authentication $e) {
  1135. // Authentication with Stripe's API failed
  1136. // (maybe you changed API keys recently)
  1137. $error++;
  1138. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1139. } catch (\Stripe\Error\ApiConnection $e) {
  1140. // Network communication with Stripe failed
  1141. $error++;
  1142. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1143. } catch (\Stripe\Error\Base $e) {
  1144. // Display a very generic error to the user, and maybe send
  1145. // yourself an email
  1146. $error++;
  1147. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1148. } catch (Exception $e) {
  1149. // Something else happened, completely unrelated to Stripe
  1150. $error++;
  1151. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1152. }
  1153. return $return;
  1154. }
  1155. }