stripe.class.php 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404
  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. if ($tokenstring) {
  98. $tmparray = json_decode($tokenstring);
  99. $key = empty($tmparray->stripe_user_id) ? '' : $tmparray->stripe_user_id;
  100. }
  101. } else {
  102. $tokenstring = '';
  103. }
  104. } else {
  105. dol_print_error($this->db);
  106. }
  107. dol_syslog("No dedicated Stripe Connect account available for entity ".$conf->entity);
  108. return $key;
  109. }
  110. /**
  111. * getStripeCustomerAccount
  112. *
  113. * @param int $id Id of third party
  114. * @param int $status Status
  115. * @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.
  116. * @return string Stripe customer ref 'cu_xxxxxxxxxxxxx' or ''
  117. */
  118. public function getStripeCustomerAccount($id, $status = 0, $site_account = '')
  119. {
  120. include_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
  121. $societeaccount = new SocieteAccount($this->db);
  122. return $societeaccount->getCustomerAccount($id, 'stripe', $status, $site_account); // Get thirdparty cus_...
  123. }
  124. /**
  125. * Get the Stripe customer of a thirdparty (with option to create it in Stripe if not linked yet).
  126. * Search on site_account = 0 or = $stripearrayofkeysbyenv[$status]['publishable_key']
  127. *
  128. * @param Societe $object Object thirdparty to check, or create on stripe (create on stripe also update the stripe_account table for current entity)
  129. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  130. * @param int $status Status (0=test, 1=live)
  131. * @param int $createifnotlinkedtostripe 1=Create the stripe customer and the link if the thirdparty is not yet linked to a stripe customer
  132. * @return \Stripe\Customer|null Stripe Customer or null if not found
  133. */
  134. public function customerStripe(Societe $object, $key = '', $status = 0, $createifnotlinkedtostripe = 0)
  135. {
  136. global $conf, $user;
  137. if (empty($object->id)) {
  138. dol_syslog("customerStripe is called with the parameter object that is not loaded");
  139. return null;
  140. }
  141. $customer = null;
  142. // Force to use the correct API key
  143. global $stripearrayofkeysbyenv;
  144. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  145. $sql = "SELECT sa.key_account as key_account, sa.entity"; // key_account is cus_....
  146. $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
  147. $sql .= " WHERE sa.fk_soc = ".((int) $object->id);
  148. $sql .= " AND sa.entity IN (".getEntity('societe').")";
  149. $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
  150. $sql .= " AND (sa.site_account IS NULL OR sa.site_account = '' OR sa.site_account = '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."')";
  151. $sql .= " AND sa.key_account IS NOT NULL AND sa.key_account <> ''";
  152. dol_syslog(get_class($this)."::customerStripe search stripe customer id for thirdparty id=".$object->id, LOG_DEBUG);
  153. $resql = $this->db->query($sql);
  154. if ($resql) {
  155. $num = $this->db->num_rows($resql);
  156. if ($num) {
  157. $obj = $this->db->fetch_object($resql);
  158. $tiers = $obj->key_account;
  159. 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']);
  160. try {
  161. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  162. //$customer = \Stripe\Customer::retrieve("$tiers");
  163. $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'));
  164. } else {
  165. //$customer = \Stripe\Customer::retrieve("$tiers", array("stripe_account" => $key));
  166. $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'), array("stripe_account" => $key));
  167. }
  168. } catch (Exception $e) {
  169. // 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.'
  170. $this->error = $e->getMessage();
  171. }
  172. } elseif ($createifnotlinkedtostripe) {
  173. $ipaddress = getUserRemoteIP();
  174. $dataforcustomer = array(
  175. "email" => $object->email,
  176. "description" => $object->name,
  177. "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
  178. );
  179. $vatcleaned = $object->tva_intra ? $object->tva_intra : null;
  180. /*
  181. $taxinfo = array('type'=>'vat');
  182. if ($vatcleaned)
  183. {
  184. $taxinfo["tax_id"] = $vatcleaned;
  185. }
  186. // We force data to "null" if not defined as expected by Stripe
  187. if (empty($vatcleaned)) $taxinfo=null;
  188. $dataforcustomer["tax_info"] = $taxinfo;
  189. */
  190. //$a = \Stripe\Stripe::getApiKey();
  191. //var_dump($a);var_dump($key);exit;
  192. try {
  193. // Force to use the correct API key
  194. global $stripearrayofkeysbyenv;
  195. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  196. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  197. $customer = \Stripe\Customer::create($dataforcustomer);
  198. } else {
  199. $customer = \Stripe\Customer::create($dataforcustomer, array("stripe_account" => $key));
  200. }
  201. // Create the VAT record in Stripe
  202. if (getDolGlobalString('STRIPE_SAVE_TAX_IDS')) { // We setup to save Tax info on Stripe side. Warning: This may result in error when saving customer
  203. if (!empty($vatcleaned)) {
  204. $isineec = isInEEC($object);
  205. if ($object->country_code && $isineec) {
  206. //$taxids = $customer->allTaxIds($customer->id);
  207. $customer->createTaxId($customer->id, array('type'=>'eu_vat', 'value'=>$vatcleaned));
  208. }
  209. }
  210. }
  211. // Create customer in Dolibarr
  212. $sql = "INSERT INTO ".MAIN_DB_PREFIX."societe_account (fk_soc, login, key_account, site, site_account, status, entity, date_creation, fk_user_creat)";
  213. $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).")";
  214. $resql = $this->db->query($sql);
  215. if (!$resql) {
  216. $this->error = $this->db->lasterror();
  217. }
  218. } catch (Exception $e) {
  219. $this->error = $e->getMessage();
  220. }
  221. }
  222. } else {
  223. dol_print_error($this->db);
  224. }
  225. return $customer;
  226. }
  227. /**
  228. * Get the Stripe payment method Object from its ID
  229. *
  230. * @param string $paymentmethod Payment Method ID
  231. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  232. * @param int $status Status (0=test, 1=live)
  233. * @return \Stripe\PaymentMethod|null Stripe PaymentMethod or null if not found
  234. */
  235. public function getPaymentMethodStripe($paymentmethod, $key = '', $status = 0)
  236. {
  237. $stripepaymentmethod = null;
  238. try {
  239. // Force to use the correct API key
  240. global $stripearrayofkeysbyenv;
  241. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  242. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  243. $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id);
  244. } else {
  245. $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id, array("stripe_account" => $key));
  246. }
  247. } catch (Exception $e) {
  248. $this->error = $e->getMessage();
  249. }
  250. return $stripepaymentmethod;
  251. }
  252. /**
  253. * Get the Stripe reader Object from its ID
  254. *
  255. * @param string $reader Reader ID
  256. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  257. * @param int $status Status (0=test, 1=live)
  258. * @return \Stripe\Terminal\Reader|null Stripe Reader or null if not found
  259. */
  260. public function getSelectedReader($reader, $key = '', $status = 0)
  261. {
  262. $selectedreader = null;
  263. try {
  264. // Force to use the correct API key
  265. global $stripearrayofkeysbyenv;
  266. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  267. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  268. $selectedreader = \Stripe\Terminal\Reader::retrieve((string) $reader);
  269. } else {
  270. $stripepaymentmethod = \Stripe\Terminal\Reader::retrieve((string) $reader, array("stripe_account" => $key));
  271. }
  272. } catch (Exception $e) {
  273. $this->error = $e->getMessage();
  274. }
  275. return $selectedreader;
  276. }
  277. /**
  278. * Get the Stripe payment intent. Create it with confirmnow=false
  279. * Warning. If a payment was tried and failed, a payment intent was created.
  280. * But if we change something on object to pay (amount or other), reusing same payment intent is not allowed by Stripe.
  281. * Recommended solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
  282. * 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)
  283. * 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
  284. * but not when using the STRIPE_USE_NEW_CHECKOUT.
  285. *
  286. * @param double $amount Amount
  287. * @param string $currency_code Currency code
  288. * @param string $tag Tag
  289. * @param string $description Description
  290. * @param mixed $object Object to pay with Stripe
  291. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  292. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  293. * @param int $status Status (0=test, 1=live)
  294. * @param int $usethirdpartyemailforreceiptemail 1=use thirdparty email for receipt
  295. * @param int $mode automatic=automatic confirmation/payment when conditions are ok, manual=need to call confirm() on intent
  296. * @param boolean $confirmnow false=default, true=try to confirm immediatly after create (if conditions are ok)
  297. * @param string $payment_method 'pm_....' (if known)
  298. * @param string $off_session If we use an already known payment method to pay when customer is not available during the checkout flow.
  299. * @param string $noidempotency_key Do not use the idempotency_key when creating the PaymentIntent
  300. * @param int $did ID of an existing line into llx_prelevement_demande (Dolibarr intent). If provided, no new line will be created.
  301. * @return \Stripe\PaymentIntent|null Stripe PaymentIntent or null if not found and failed to create
  302. */
  303. 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, $did = 0)
  304. {
  305. global $conf, $user;
  306. dol_syslog(get_class($this)."::getPaymentIntent", LOG_INFO, 1);
  307. $error = 0;
  308. if (empty($status)) {
  309. $service = 'StripeTest';
  310. } else {
  311. $service = 'StripeLive';
  312. }
  313. $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
  314. if (!in_array($currency_code, $arrayzerounitcurrency)) {
  315. $stripeamount = $amount * 100;
  316. } else {
  317. $stripeamount = $amount;
  318. }
  319. $fee = 0;
  320. if (getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT")) {
  321. $fee = $amount * ((float) getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT", '0') / 100) + (float) getDolGlobalString("STRIPE_APPLICATION_FEE", '0');
  322. }
  323. if ($fee >= (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') && (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') > (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
  324. $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0');
  325. } elseif ($fee < (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
  326. $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0');
  327. }
  328. if (!in_array($currency_code, $arrayzerounitcurrency)) {
  329. $stripefee = round($fee * 100);
  330. } else {
  331. $stripefee = round($fee);
  332. }
  333. $paymentintent = null;
  334. if (is_object($object) && getDolGlobalInt('STRIPE_REUSE_EXISTING_INTENT_IF_FOUND') && !getDolGlobalInt('STRIPE_CARD_PRESENT')) {
  335. // Warning. If a payment was tried and failed, a payment intent was created.
  336. // 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.
  337. // 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
  338. // automatically return the existing payment intent if idempotency is provided when we try to create the new one.
  339. // 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)
  340. $sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
  341. $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
  342. $sql .= " WHERE pi.fk_facture = ".((int) $object->id);
  343. $sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
  344. $sql .= " AND pi.entity IN (".getEntity('societe').")";
  345. $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
  346. dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);
  347. $resql = $this->db->query($sql);
  348. if ($resql) {
  349. $num = $this->db->num_rows($resql);
  350. if ($num) {
  351. $obj = $this->db->fetch_object($resql);
  352. $intent = $obj->ext_payment_id;
  353. dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record");
  354. // Force to use the correct API key
  355. global $stripearrayofkeysbyenv;
  356. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  357. try {
  358. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  359. $paymentintent = \Stripe\PaymentIntent::retrieve($intent);
  360. } else {
  361. $paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
  362. }
  363. } catch (Exception $e) {
  364. $error++;
  365. $this->error = $e->getMessage();
  366. }
  367. }
  368. }
  369. }
  370. if (empty($paymentintent)) {
  371. // Try to create intent. See https://stripe.com/docs/api/payment_intents/create
  372. $ipaddress = getUserRemoteIP();
  373. $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
  374. if (is_object($object)) {
  375. $metadata['dol_type'] = $object->element;
  376. $metadata['dol_id'] = $object->id;
  377. if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
  378. $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
  379. }
  380. }
  381. // list of payment method types
  382. $paymentmethodtypes = array("card");
  383. $descriptor = dol_trunc($tag, 10, 'right', 'UTF-8', 1);
  384. if (getDolGlobalInt('STRIPE_SEPA_DIRECT_DEBIT')) {
  385. $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
  386. //$descriptor = preg_replace('/ref=[^:=]+/', '', $descriptor); // Clean ref
  387. }
  388. if (getDolGlobalInt('STRIPE_KLARNA')) {
  389. $paymentmethodtypes[] = "klarna";
  390. }
  391. if (getDolGlobalInt('STRIPE_BANCONTACT')) {
  392. $paymentmethodtypes[] = "bancontact";
  393. }
  394. if (getDolGlobalInt('STRIPE_IDEAL')) {
  395. $paymentmethodtypes[] = "ideal";
  396. }
  397. if (getDolGlobalInt('STRIPE_GIROPAY')) {
  398. $paymentmethodtypes[] = "giropay";
  399. }
  400. if (getDolGlobalInt('STRIPE_SOFORT')) {
  401. $paymentmethodtypes[] = "sofort";
  402. }
  403. if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
  404. $paymentmethodtypes = array("card_present");
  405. }
  406. global $dolibarr_main_url_root;
  407. $dataforintent = array(
  408. "confirm" => $confirmnow, // try to confirm immediatly after create (if conditions are ok)
  409. "confirmation_method" => $mode,
  410. "amount" => $stripeamount,
  411. "currency" => $currency_code,
  412. "payment_method_types" => $paymentmethodtypes, // When payment_method_types is set, return_url is not required but payment mode can't be managed from dashboard
  413. /*
  414. 'return_url' => $dolibarr_main_url_root.'/public/payment/paymentok.php',
  415. 'automatic_payment_methods' => array(
  416. 'enabled' => true,
  417. 'allow_redirects' => 'never',
  418. ),
  419. */
  420. "description" => $description,
  421. //"save_payment_method" => true,
  422. "setup_future_usage" => "on_session",
  423. "metadata" => $metadata
  424. );
  425. if ($descriptor) {
  426. $dataforintent["statement_descriptor_suffix"] = $descriptor; // For card payment, 22 chars that appears on bank receipt (prefix into stripe setup + this suffix)
  427. $dataforintent["statement_descriptor"] = $descriptor; // For SEPA, it will take only statement_descriptor, not statement_descriptor_suffix
  428. }
  429. if (!is_null($customer)) {
  430. $dataforintent["customer"] = $customer;
  431. }
  432. // payment_method =
  433. // payment_method_types = array('card')
  434. //var_dump($dataforintent);
  435. if ($off_session) {
  436. unset($dataforintent['setup_future_usage']);
  437. // We can't use both "setup_future_usage" = "off_session" and "off_session" = true.
  438. // Because $off_session parameter is dedicated to create paymentintent off_line (and not future payment), we need to use "off_session" = true.
  439. //$dataforintent["setup_future_usage"] = "off_session";
  440. $dataforintent["off_session"] = true;
  441. }
  442. if (getDolGlobalInt('STRIPE_GIROPAY')) {
  443. unset($dataforintent['setup_future_usage']);
  444. }
  445. if (getDolGlobalInt('STRIPE_KLARNA')) {
  446. unset($dataforintent['setup_future_usage']);
  447. }
  448. if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
  449. unset($dataforintent['setup_future_usage']);
  450. $dataforintent["capture_method"] = "manual";
  451. $dataforintent["confirmation_method"] = "manual";
  452. }
  453. if (!is_null($payment_method)) {
  454. $dataforintent["payment_method"] = $payment_method;
  455. $description .= ' - '.$payment_method;
  456. }
  457. if ($conf->entity != getDolGlobalInt('STRIPECONNECT_PRINCIPAL') && $stripefee > 0) {
  458. $dataforintent["application_fee_amount"] = $stripefee;
  459. }
  460. if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
  461. $dataforintent["receipt_email"] = $object->thirdparty->email;
  462. }
  463. try {
  464. // Force to use the correct API key
  465. global $stripearrayofkeysbyenv;
  466. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  467. $arrayofoptions = array();
  468. if (empty($noidempotency_key)) {
  469. $arrayofoptions["idempotency_key"] = $description;
  470. }
  471. // 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.
  472. if (!empty($key)) { // If the Stripe connect account not set, we use common API usage
  473. $arrayofoptions["stripe_account"] = $key;
  474. }
  475. dol_syslog("dataforintent to create paymentintent = ".var_export($dataforintent, true));
  476. $paymentintent = \Stripe\PaymentIntent::create($dataforintent, $arrayofoptions);
  477. // Store the payment intent
  478. if (is_object($object)) {
  479. $paymentintentalreadyexists = 0;
  480. if ($did > 0) {
  481. // If a payment request line provided, we do not need to recreate one, we just update it
  482. dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
  483. $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
  484. $sql .= " ext_payment_site = '".$this->db->escape($service)."',";
  485. $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
  486. $sql .= " WHERE rowid = ".((int) $did);
  487. $resql = $this->db->query($sql);
  488. if ($resql) {
  489. $paymentintentalreadyexists++;
  490. } else {
  491. $error++;
  492. dol_print_error($this->db);
  493. }
  494. } else {
  495. // Check that payment intent $paymentintent->id is not already recorded.
  496. dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
  497. $sql = "SELECT pi.rowid";
  498. $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
  499. $sql .= " WHERE pi.entity IN (".getEntity('societe').")";
  500. $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
  501. $sql .= " AND pi.ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
  502. $resql = $this->db->query($sql);
  503. if ($resql) {
  504. $num = $this->db->num_rows($resql);
  505. if ($num) {
  506. $obj = $this->db->fetch_object($resql);
  507. if ($obj) {
  508. $paymentintentalreadyexists++;
  509. }
  510. }
  511. } else {
  512. $error++;
  513. dol_print_error($this->db);
  514. }
  515. }
  516. // If not, we create it.
  517. if (!$error && !$paymentintentalreadyexists) {
  518. $now = dol_now();
  519. $sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site, amount)";
  520. $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).")";
  521. $resql = $this->db->query($sql);
  522. if (!$resql) {
  523. $error++;
  524. $this->error = $this->db->lasterror();
  525. dol_syslog(get_class($this)."::PaymentIntent failed to insert paymentintent with id=".$paymentintent->id." into database.", LOG_ERR);
  526. }
  527. }
  528. } else {
  529. $_SESSION["stripe_payment_intent"] = $paymentintent;
  530. }
  531. } catch (Stripe\Error\Card $e) {
  532. $error++;
  533. $this->error = $e->getMessage();
  534. $this->code = $e->getStripeCode();
  535. $this->declinecode = $e->getDeclineCode();
  536. } catch (Exception $e) {
  537. //var_dump($dataforintent);
  538. //var_dump($description);
  539. //var_dump($key);
  540. //var_dump($paymentintent);
  541. //var_dump($e->getMessage());
  542. //var_dump($e);
  543. $error++;
  544. $this->error = $e->getMessage();
  545. $this->code = '';
  546. $this->declinecode = '';
  547. }
  548. }
  549. dol_syslog(get_class($this)."::getPaymentIntent return error=".$error." this->error=".$this->error, LOG_INFO, -1);
  550. if (!$error) {
  551. return $paymentintent;
  552. } else {
  553. return null;
  554. }
  555. }
  556. /**
  557. * Get the Stripe payment intent. Create it with confirmnow=false
  558. * Warning. If a payment was tried and failed, a payment intent was created.
  559. * But if we change something on object to pay (amount or other), reusing same payment intent is not allowed.
  560. * Recommanded solution is to recreate a new payment intent each time we need one (old one will be automatically closed after a delay),
  561. * 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)
  562. * 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
  563. * but not when using the STRIPE_USE_NEW_CHECKOUT.
  564. *
  565. * @param string $description Description
  566. * @param Societe $object Object of company to link the Stripe payment mode with
  567. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  568. * @param string $key ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  569. * @param int $status Status (0=test, 1=live)
  570. * @param int $usethirdpartyemailforreceiptemail 1=use thirdparty email for receipt
  571. * @param boolean $confirmnow false=default, true=try to confirm immediatly after create (if conditions are ok)
  572. * @return \Stripe\SetupIntent|null Stripe SetupIntent or null if not found and failed to create
  573. */
  574. public function getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail = 0, $confirmnow = false)
  575. {
  576. global $conf;
  577. dol_syslog("getSetupIntent description=".$description.' confirmnow='.$confirmnow, LOG_INFO, 1);
  578. $error = 0;
  579. if (empty($status)) {
  580. $service = 'StripeTest';
  581. } else {
  582. $service = 'StripeLive';
  583. }
  584. $setupintent = null;
  585. if (empty($setupintent)) {
  586. $ipaddress = getUserRemoteIP();
  587. $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
  588. if (is_object($object)) {
  589. $metadata['dol_type'] = $object->element;
  590. $metadata['dol_id'] = $object->id;
  591. if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
  592. $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
  593. }
  594. }
  595. // list of payment method types
  596. $paymentmethodtypes = array("card");
  597. if (getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
  598. $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
  599. }
  600. if (getDolGlobalString('STRIPE_BANCONTACT')) {
  601. $paymentmethodtypes[] = "bancontact";
  602. }
  603. if (getDolGlobalString('STRIPE_IDEAL')) {
  604. $paymentmethodtypes[] = "ideal";
  605. }
  606. // Giropay not possible for setup intent
  607. if (getDolGlobalString('STRIPE_SOFORT')) {
  608. $paymentmethodtypes[] = "sofort";
  609. }
  610. global $dolibarr_main_url_root;
  611. $dataforintent = array(
  612. "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
  613. "payment_method_types" => $paymentmethodtypes, // When payment_method_types is set, return_url is not required but payment mode can't be managed from dashboard
  614. /*
  615. 'return_url' => $dolibarr_main_url_root.'/public/payment/paymentok.php',
  616. 'automatic_payment_methods' => array(
  617. 'enabled' => true,
  618. 'allow_redirects' => 'never',
  619. ),
  620. */
  621. "usage" => "off_session",
  622. "metadata" => $metadata
  623. );
  624. if (!is_null($customer)) {
  625. $dataforintent["customer"] = $customer;
  626. }
  627. if (!is_null($description)) {
  628. $dataforintent["description"] = $description;
  629. }
  630. // payment_method =
  631. // payment_method_types = array('card')
  632. //var_dump($dataforintent);
  633. if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
  634. $dataforintent["receipt_email"] = $object->thirdparty->email;
  635. }
  636. try {
  637. // Force to use the correct API key
  638. global $stripearrayofkeysbyenv;
  639. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  640. dol_syslog("getSetupIntent ".$stripearrayofkeysbyenv[$status]['publishable_key'], LOG_DEBUG);
  641. // Note: If all data for payment intent are same than a previous one, even if we use 'create', Stripe will return ID of the old existing payment intent.
  642. if (empty($key)) { // If the Stripe connect account not set, we use common API usage
  643. //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description"));
  644. $setupintent = \Stripe\SetupIntent::create($dataforintent, array());
  645. } else {
  646. //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description", "stripe_account" => $key));
  647. $setupintent = \Stripe\SetupIntent::create($dataforintent, array("stripe_account" => $key));
  648. }
  649. //var_dump($setupintent->id);
  650. // Store the setup intent
  651. /*if (is_object($object))
  652. {
  653. $setupintentalreadyexists = 0;
  654. // Check that payment intent $setupintent->id is not already recorded.
  655. $sql = "SELECT pi.rowid";
  656. $sql.= " FROM " . MAIN_DB_PREFIX . "prelevement_demande as pi";
  657. $sql.= " WHERE pi.entity IN (".getEntity('societe').")";
  658. $sql.= " AND pi.ext_payment_site = '" . $this->db->escape($service) . "'";
  659. $sql.= " AND pi.ext_payment_id = '".$this->db->escape($setupintent->id)."'";
  660. dol_syslog(get_class($this) . "::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
  661. $resql = $this->db->query($sql);
  662. if ($resql) {
  663. $num = $this->db->num_rows($resql);
  664. if ($num)
  665. {
  666. $obj = $this->db->fetch_object($resql);
  667. if ($obj) $setupintentalreadyexists++;
  668. }
  669. }
  670. else dol_print_error($this->db);
  671. // If not, we create it.
  672. if (! $setupintentalreadyexists)
  673. {
  674. $now=dol_now();
  675. $sql = "INSERT INTO " . MAIN_DB_PREFIX . "prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site)";
  676. $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).")";
  677. $resql = $this->db->query($sql);
  678. if (! $resql)
  679. {
  680. $error++;
  681. $this->error = $this->db->lasterror();
  682. dol_syslog(get_class($this) . "::PaymentIntent failed to insert paymentintent with id=".$setupintent->id." into database.");
  683. }
  684. }
  685. }
  686. else
  687. {
  688. $_SESSION["stripe_setup_intent"] = $setupintent;
  689. }*/
  690. } catch (Exception $e) {
  691. //var_dump($dataforintent);
  692. //var_dump($description);
  693. //var_dump($key);
  694. //var_dump($setupintent);
  695. //var_dump($e->getMessage());
  696. $error++;
  697. $this->error = $e->getMessage();
  698. }
  699. }
  700. if (!$error) {
  701. dol_syslog("getSetupIntent ".(is_object($setupintent) ? $setupintent->id : ''), LOG_INFO, -1);
  702. return $setupintent;
  703. } else {
  704. dol_syslog("getSetupIntent return error=".$error, LOG_INFO, -1);
  705. return null;
  706. }
  707. }
  708. /**
  709. * 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)
  710. *
  711. * @param \Stripe\Customer $cu Object stripe customer.
  712. * @param CompanyPaymentMode $object Object companypaymentmode to check, or create on stripe (create on stripe also update the societe_rib table for current entity)
  713. * @param string $stripeacc ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  714. * @param int $status Status (0=test, 1=live)
  715. * @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.
  716. * @return \Stripe\Card|\Stripe\PaymentMethod|null Stripe Card or null if not found
  717. */
  718. public function cardStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
  719. {
  720. global $conf, $user, $langs;
  721. $card = null;
  722. $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_....
  723. $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
  724. $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
  725. $sql .= " AND sa.type = 'card'";
  726. dol_syslog(get_class($this)."::cardStripe search stripe card id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
  727. $resql = $this->db->query($sql);
  728. if ($resql) {
  729. $num = $this->db->num_rows($resql);
  730. if ($num) {
  731. $obj = $this->db->fetch_object($resql);
  732. $cardref = $obj->stripe_card_ref;
  733. dol_syslog(get_class($this)."::cardStripe cardref=".$cardref);
  734. if ($cardref) {
  735. try {
  736. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  737. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  738. $card = $cu->sources->retrieve($cardref);
  739. } else {
  740. $card = \Stripe\PaymentMethod::retrieve($cardref);
  741. }
  742. } else {
  743. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  744. //$card = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
  745. $card = $cu->sources->retrieve($cardref);
  746. } else {
  747. //$card = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
  748. $card = \Stripe\PaymentMethod::retrieve($cardref);
  749. }
  750. }
  751. } catch (Exception $e) {
  752. $this->error = $e->getMessage();
  753. dol_syslog($this->error, LOG_WARNING);
  754. }
  755. } elseif ($createifnotlinkedtostripe) {
  756. // Deprecated with new Stripe API and SCA. We should not use anymore this part of code now.
  757. $exp_date_month = $obj->exp_date_month;
  758. $exp_date_year = $obj->exp_date_year;
  759. $number = $obj->number;
  760. $cvc = $obj->cvn; // cvn in database, cvc for stripe
  761. $cardholdername = $obj->proprio;
  762. $ipaddress = getUserRemoteIP();
  763. $dataforcard = array(
  764. "source" => array(
  765. 'object'=>'card',
  766. 'exp_month'=>$exp_date_month,
  767. 'exp_year'=>$exp_date_year,
  768. 'number'=>$number,
  769. 'cvc'=>$cvc,
  770. 'name'=>$cardholdername
  771. ),
  772. "metadata" => array(
  773. 'dol_type'=>$object->element,
  774. 'dol_id'=>$object->id,
  775. 'dol_version'=>DOL_VERSION,
  776. 'dol_entity'=>$conf->entity,
  777. 'ipaddress'=>$ipaddress
  778. )
  779. );
  780. //$a = \Stripe\Stripe::getApiKey();
  781. //var_dump($a);
  782. //var_dump($stripeacc);exit;
  783. try {
  784. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  785. if (!getDolGlobalString('STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION')) {
  786. dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
  787. $card = $cu->sources->create($dataforcard);
  788. if (!$card) {
  789. $this->error = 'Creation of card on Stripe has failed';
  790. }
  791. } else {
  792. $connect = '';
  793. if (!empty($stripeacc)) {
  794. $connect = $stripeacc.'/';
  795. }
  796. $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
  797. if ($status) {
  798. $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
  799. }
  800. $urtoswitchonstripe = '<a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
  801. //dol_syslog("Error: This case is not supported", LOG_ERR);
  802. $this->error = str_replace('{s1}', $urtoswitchonstripe, $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', '{s1}'));
  803. }
  804. } else {
  805. if (!getDolGlobalString('STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION')) {
  806. dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
  807. $card = $cu->sources->create($dataforcard, array("stripe_account" => $stripeacc));
  808. if (!$card) {
  809. $this->error = 'Creation of card on Stripe has failed';
  810. }
  811. } else {
  812. $connect = '';
  813. if (!empty($stripeacc)) {
  814. $connect = $stripeacc.'/';
  815. }
  816. $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
  817. if ($status) {
  818. $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
  819. }
  820. $urtoswitchonstripe = '<a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
  821. //dol_syslog("Error: This case is not supported", LOG_ERR);
  822. $this->error = str_replace('{s1}', $urtoswitchonstripe, $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', '{s1}'));
  823. }
  824. }
  825. if ($card) {
  826. $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
  827. $sql .= " SET stripe_card_ref = '".$this->db->escape($card->id)."', card_type = '".$this->db->escape($card->brand)."',";
  828. $sql .= " country_code = '".$this->db->escape($card->country)."',";
  829. $sql .= " approved = ".($card->cvc_check == 'pass' ? 1 : 0);
  830. $sql .= " WHERE rowid = ".((int) $object->id);
  831. $sql .= " AND type = 'card'";
  832. $resql = $this->db->query($sql);
  833. if (!$resql) {
  834. $this->error = $this->db->lasterror();
  835. }
  836. }
  837. } catch (Exception $e) {
  838. $this->error = $e->getMessage();
  839. dol_syslog($this->error, LOG_WARNING);
  840. }
  841. }
  842. }
  843. } else {
  844. dol_print_error($this->db);
  845. }
  846. return $card;
  847. }
  848. /**
  849. * Get the Stripe SEPA of a company payment mode (create it if it doesn't exists and $createifnotlinkedtostripe is set)
  850. *
  851. * @param \Stripe\Customer $cu Object stripe customer.
  852. * @param CompanyPaymentMode $object Object companypaymentmode to check, or create on stripe (create on stripe also update the societe_rib table for current entity)
  853. * @param string $stripeacc ''=Use common API. If not '', it is the Stripe connect account 'acc_....' to use Stripe connect
  854. * @param int $status Status (0=test, 1=live)
  855. * @param int $createifnotlinkedtostripe 1=Create the stripe sepa and the link if the sepa is not yet linked to a stripe sepa. Used by the "Create bank to Stripe" feature.
  856. * @return \Stripe\PaymentMethod|null Stripe SEPA or null if not found
  857. */
  858. public function sepaStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
  859. {
  860. global $conf;
  861. $sepa = null;
  862. $sql = "SELECT sa.stripe_card_ref, sa.proprio, sa.iban_prefix as iban, sa.rum"; // stripe_card_ref is 'src_...' for Stripe SEPA
  863. $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
  864. $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
  865. $sql .= " AND sa.type = 'ban'"; //type ban to get normal bank account of customer (prelevement)
  866. $soc = new Societe($this->db);
  867. $soc->fetch($object->fk_soc);
  868. dol_syslog(get_class($this)."::sepaStripe search stripe ban id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
  869. $resql = $this->db->query($sql);
  870. if ($resql) {
  871. $num = $this->db->num_rows($resql);
  872. if ($num) {
  873. $obj = $this->db->fetch_object($resql);
  874. $cardref = $obj->stripe_card_ref;
  875. dol_syslog(get_class($this)."::sepaStripe paymentmode=".$cardref);
  876. if ($cardref) {
  877. try {
  878. if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
  879. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  880. $sepa = $cu->sources->retrieve($cardref);
  881. } else {
  882. $sepa = \Stripe\PaymentMethod::retrieve($cardref);
  883. }
  884. } else {
  885. if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
  886. //$sepa = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
  887. $sepa = $cu->sources->retrieve($cardref);
  888. } else {
  889. //$sepa = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
  890. $sepa = \Stripe\PaymentMethod::retrieve($cardref);
  891. }
  892. }
  893. } catch (Exception $e) {
  894. $this->error = $e->getMessage();
  895. dol_syslog($this->error, LOG_WARNING);
  896. }
  897. } elseif ($createifnotlinkedtostripe) {
  898. $iban = $obj->iban;
  899. $ipaddress = getUserRemoteIP();
  900. $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
  901. if (is_object($object)) {
  902. $metadata['dol_type'] = $object->element;
  903. $metadata['dol_id'] = $object->id;
  904. $metadata['dol_thirdparty_id'] = $soc->id;
  905. }
  906. $description = 'SEPA for IBAN '.$iban;
  907. $dataforcard = array(
  908. 'type'=>'sepa_debit',
  909. "sepa_debit" => array('iban' => $iban),
  910. 'billing_details' => array(
  911. 'name' => $soc->name,
  912. 'email' => !empty($soc->email) ? $soc->email : "",
  913. ),
  914. "metadata" => $metadata
  915. );
  916. // Complete owner name
  917. if (!empty($soc->town)) {
  918. $dataforcard['billing_details']['address']['city']=$soc->town;
  919. }
  920. if (!empty($soc->country_code)) {
  921. $dataforcard['billing_details']['address']['country']=$soc->country_code;
  922. }
  923. if (!empty($soc->address)) {
  924. $dataforcard['billing_details']['address']['line1']=$soc->address;
  925. }
  926. if (!empty($soc->zip)) {
  927. $dataforcard['billing_details']['address']['postal_code']=$soc->zip;
  928. }
  929. if (!empty($soc->state)) {
  930. $dataforcard['billing_details']['address']['state']=$soc->state;
  931. }
  932. //$a = \Stripe\Stripe::getApiKey();
  933. //var_dump($a);var_dump($stripeacc);exit;
  934. try {
  935. dol_syslog("Try to create sepa_debit");
  936. $service = 'StripeTest';
  937. $servicestatus = 0;
  938. if (getDolGlobalString('STRIPE_LIVE') && !GETPOST('forcesandbox', 'alpha')) {
  939. $service = 'StripeLive';
  940. $servicestatus = 1;
  941. }
  942. // Force to use the correct API key
  943. global $stripearrayofkeysbyenv;
  944. $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];
  945. dol_syslog("Try to create sepa_debit with data = ".json_encode($dataforcard));
  946. $s = new \Stripe\StripeClient($stripeacc);
  947. //var_dump($dataforcard);exit;
  948. $sepa = $s->paymentMethods->create($dataforcard);
  949. if (!$sepa) {
  950. $this->error = 'Creation of payment method sepa_debit on Stripe has failed';
  951. } else {
  952. // link customer and src
  953. //$cs = $this->getSetupIntent($description, $soc, $cu, '', $status);
  954. $dataforintent = array(['description'=> $description, 'payment_method_types' => ['sepa_debit'], 'customer' => $cu->id, 'payment_method' => $sepa->id], 'metadata'=>$metadata);
  955. $cs = $s->setupIntents->create($dataforintent);
  956. //$cs = $s->setupIntents->update($cs->id, ['payment_method' => $sepa->id]);
  957. $cs = $s->setupIntents->confirm($cs->id, ['mandate_data' => ['customer_acceptance' => ['type' => 'offline']]]);
  958. // note: $cs->mandate contians ID of mandate on Stripe side
  959. if (!$cs) {
  960. $this->error = 'Link SEPA <-> Customer failed';
  961. } else {
  962. dol_syslog("Update the payment mode of the customer");
  963. // print json_encode($sepa);
  964. // Save the Stripe payment mode ID into the Dolibarr database
  965. $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
  966. $sql .= " SET stripe_card_ref = '".$this->db->escape($sepa->id)."',";
  967. $sql .= " card_type = 'sepa_debit',";
  968. $sql .= " stripe_account= '" . $this->db->escape($cu->id . "@" . $stripeacc) . "',";
  969. $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
  970. if (!empty($cs->mandate)) {
  971. $mandateservice = new \Stripe\Mandate($stripeacc);
  972. $mandate = $mandateservice->retrieve($cs->mandate);
  973. if (is_object($mandate) && is_object($mandate->payment_method_details) && is_object($mandate->payment_method_details->sepa_debit)) {
  974. $refmandate = $mandate->payment_method_details->sepa_debit->reference;
  975. //$urlmandate = $mandate->payment_method_details->sepa_debit->url;
  976. $sql .= ", rum = '".$this->db->escape($refmandate)."'";
  977. }
  978. $sql .= ", comment = '".$this->db->escape($cs->mandate)."'";
  979. $sql .= ", date_rum = '".$this->db->idate(dol_now())."'";
  980. }
  981. $sql .= " WHERE rowid = ".((int) $object->id);
  982. $sql .= " AND type = 'ban'";
  983. $resql = $this->db->query($sql);
  984. if (!$resql) {
  985. $this->error = $this->db->lasterror();
  986. }
  987. }
  988. }
  989. } catch (Exception $e) {
  990. $sepa = null;
  991. $this->error = 'Stripe error: '.$e->getMessage().'. Check the BAN information.';
  992. dol_syslog($this->error, LOG_WARNING); // Error from Stripe, so a warning on Dolibarr
  993. }
  994. }
  995. }
  996. } else {
  997. dol_print_error($this->db);
  998. }
  999. return $sepa;
  1000. }
  1001. /**
  1002. * Create charge.
  1003. * This is called by page htdocs/stripe/payment.php and may be deprecated.
  1004. *
  1005. * @param int $amount Amount to pay
  1006. * @param string $currency EUR, GPB...
  1007. * @param string $origin Object type to pay (order, invoice, contract...)
  1008. * @param int $item Object id to pay
  1009. * @param string $source src_xxxxx or card_xxxxx or pm_xxxxx
  1010. * @param string $customer Stripe customer ref 'cus_xxxxxxxxxxxxx' via customerStripe()
  1011. * @param string $account Stripe account ref 'acc_xxxxxxxxxxxxx' via getStripeAccount()
  1012. * @param int $status Status (0=test, 1=live)
  1013. * @param int $usethirdpartyemailforreceiptemail Use thirdparty email as receipt email
  1014. * @param boolean $capture Set capture flag to true (take payment) or false (wait)
  1015. * @return Stripe
  1016. */
  1017. public function createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status = 0, $usethirdpartyemailforreceiptemail = 0, $capture = true)
  1018. {
  1019. global $conf;
  1020. $error = 0;
  1021. if (empty($status)) {
  1022. $service = 'StripeTest';
  1023. } else {
  1024. $service = 'StripeLive';
  1025. }
  1026. $sql = "SELECT sa.key_account as key_account, sa.fk_soc, sa.entity";
  1027. $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
  1028. $sql .= " WHERE sa.key_account = '".$this->db->escape($customer)."'";
  1029. //$sql.= " AND sa.entity IN (".getEntity('societe').")";
  1030. $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
  1031. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  1032. $result = $this->db->query($sql);
  1033. if ($result) {
  1034. if ($this->db->num_rows($result)) {
  1035. $obj = $this->db->fetch_object($result);
  1036. $key = $obj->fk_soc;
  1037. } else {
  1038. $key = null;
  1039. }
  1040. } else {
  1041. $key = null;
  1042. }
  1043. $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
  1044. if (!in_array($currency, $arrayzerounitcurrency)) {
  1045. $stripeamount = $amount * 100;
  1046. } else {
  1047. $stripeamount = $amount;
  1048. }
  1049. $societe = new Societe($this->db);
  1050. if ($key > 0) {
  1051. $societe->fetch($key);
  1052. }
  1053. $description = "";
  1054. $ref = "";
  1055. if ($origin == 'order') {
  1056. $order = new Commande($this->db);
  1057. $order->fetch($item);
  1058. $ref = $order->ref;
  1059. $description = "ORD=".$ref.".CUS=".$societe->id.".PM=stripe";
  1060. } elseif ($origin == 'invoice') {
  1061. $invoice = new Facture($this->db);
  1062. $invoice->fetch($item);
  1063. $ref = $invoice->ref;
  1064. $description = "INV=".$ref.".CUS=".$societe->id.".PM=stripe";
  1065. }
  1066. $ipaddress = getUserRemoteIP();
  1067. $metadata = array(
  1068. "dol_id" => (string) $item,
  1069. "dol_type" => (string) $origin,
  1070. "dol_thirdparty_id" => (string) $societe->id,
  1071. 'dol_thirdparty_name' => $societe->name,
  1072. 'dol_version' => DOL_VERSION,
  1073. 'dol_entity' => $conf->entity,
  1074. 'ipaddress' => $ipaddress
  1075. );
  1076. $return = new Stripe($this->db);
  1077. try {
  1078. // Force to use the correct API key
  1079. global $stripearrayofkeysbyenv;
  1080. \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
  1081. if (empty($conf->stripeconnect->enabled)) { // With a common Stripe account
  1082. if (preg_match('/pm_/i', $source)) {
  1083. $stripecard = $source;
  1084. $amountstripe = $stripeamount;
  1085. $FULLTAG = 'PFBO'; // Payment From Back Office
  1086. $stripe = $return;
  1087. $amounttopay = $amount;
  1088. $servicestatus = $status;
  1089. dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
  1090. $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
  1091. dol_syslog("* createPaymentStripe Create payment for customer ".$customer->id." on source card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
  1092. // Create payment intent and charge payment (confirmnow = true)
  1093. $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
  1094. $charge = new stdClass();
  1095. if ($paymentintent->status == 'succeeded') {
  1096. $charge->status = 'ok';
  1097. } else {
  1098. $charge->status = 'failed';
  1099. $charge->failure_code = $stripe->code;
  1100. $charge->failure_message = $stripe->error;
  1101. $charge->failure_declinecode = $stripe->declinecode;
  1102. $stripefailurecode = $stripe->code;
  1103. $stripefailuremessage = $stripe->error;
  1104. $stripefailuredeclinecode = $stripe->declinecode;
  1105. }
  1106. } elseif (preg_match('/acct_/i', $source)) {
  1107. $charge = \Stripe\Charge::create(array(
  1108. "amount" => "$stripeamount",
  1109. "currency" => "$currency",
  1110. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  1111. "description" => "Stripe payment: ".$description,
  1112. "capture" => $capture,
  1113. "metadata" => $metadata,
  1114. "source" => "$source"
  1115. ));
  1116. } else {
  1117. $paymentarray = array(
  1118. "amount" => "$stripeamount",
  1119. "currency" => "$currency",
  1120. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  1121. "description" => "Stripe payment: ".$description,
  1122. "capture" => $capture,
  1123. "metadata" => $metadata,
  1124. "source" => "$source",
  1125. "customer" => "$customer"
  1126. );
  1127. if ($societe->email && $usethirdpartyemailforreceiptemail) {
  1128. $paymentarray["receipt_email"] = $societe->email;
  1129. }
  1130. $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description"));
  1131. }
  1132. } else {
  1133. // With Stripe Connect
  1134. $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
  1135. if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  1136. $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
  1137. } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
  1138. $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
  1139. }
  1140. if (!in_array($currency, $arrayzerounitcurrency)) {
  1141. $stripefee = round($fee * 100);
  1142. } else {
  1143. $stripefee = round($fee);
  1144. }
  1145. $paymentarray = array(
  1146. "amount" => "$stripeamount",
  1147. "currency" => "$currency",
  1148. "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
  1149. "description" => "Stripe payment: ".$description,
  1150. "capture" => $capture,
  1151. "metadata" => $metadata,
  1152. "source" => "$source",
  1153. "customer" => "$customer"
  1154. );
  1155. if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0) {
  1156. $paymentarray["application_fee_amount"] = $stripefee;
  1157. }
  1158. if ($societe->email && $usethirdpartyemailforreceiptemail) {
  1159. $paymentarray["receipt_email"] = $societe->email;
  1160. }
  1161. if (preg_match('/pm_/i', $source)) {
  1162. $stripecard = $source;
  1163. $amountstripe = $stripeamount;
  1164. $FULLTAG = 'PFBO'; // Payment From Back Office
  1165. $stripe = $return;
  1166. $amounttopay = $amount;
  1167. $servicestatus = $status;
  1168. dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
  1169. $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
  1170. dol_syslog("* createPaymentStripe Create payment on card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
  1171. // Create payment intent and charge payment (confirmnow = true)
  1172. $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
  1173. $charge = new stdClass();
  1174. if ($paymentintent->status == 'succeeded') {
  1175. $charge->status = 'ok';
  1176. $charge->id = $paymentintent->id;
  1177. } else {
  1178. $charge->status = 'failed';
  1179. $charge->failure_code = $stripe->code;
  1180. $charge->failure_message = $stripe->error;
  1181. $charge->failure_declinecode = $stripe->declinecode;
  1182. }
  1183. } else {
  1184. $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description", "stripe_account" => "$account"));
  1185. }
  1186. }
  1187. if (isset($charge->id)) {
  1188. }
  1189. $return->statut = 'success';
  1190. $return->id = $charge->id;
  1191. if (preg_match('/pm_/i', $source)) {
  1192. $return->message = 'Payment retrieved by card status = '.$charge->status;
  1193. } else {
  1194. if ($charge->source->type == 'card') {
  1195. $return->message = $charge->source->card->brand." ....".$charge->source->card->last4;
  1196. } elseif ($charge->source->type == 'three_d_secure') {
  1197. $stripe = new Stripe($this->db);
  1198. $src = \Stripe\Source::retrieve("".$charge->source->three_d_secure->card, array(
  1199. "stripe_account" => $stripe->getStripeAccount($service)
  1200. ));
  1201. $return->message = $src->card->brand." ....".$src->card->last4;
  1202. } else {
  1203. $return->message = $charge->id;
  1204. }
  1205. }
  1206. } catch (\Stripe\Error\Card $e) {
  1207. include DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
  1208. // Since it's a decline, \Stripe\Error\Card will be caught
  1209. $body = $e->getJsonBody();
  1210. $err = $body['error'];
  1211. $return->statut = 'error';
  1212. $return->id = $err['charge'];
  1213. $return->type = $err['type'];
  1214. $return->code = $err['code'];
  1215. $return->message = $err['message'];
  1216. $body = "Error: <br>".$return->id." ".$return->message." ";
  1217. $subject = '[Alert] Payment error using Stripe';
  1218. $cmailfile = new CMailFile($subject, $conf->global->ONLINE_PAYMENT_SENDEMAIL, $conf->global->MAIN_INFO_SOCIETE_MAIL, $body);
  1219. $cmailfile->sendfile();
  1220. $error++;
  1221. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1222. } catch (\Stripe\Error\RateLimit $e) {
  1223. // Too many requests made to the API too quickly
  1224. $error++;
  1225. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1226. } catch (\Stripe\Error\InvalidRequest $e) {
  1227. // Invalid parameters were supplied to Stripe's API
  1228. $error++;
  1229. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1230. } catch (\Stripe\Error\Authentication $e) {
  1231. // Authentication with Stripe's API failed
  1232. // (maybe you changed API keys recently)
  1233. $error++;
  1234. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1235. } catch (\Stripe\Error\ApiConnection $e) {
  1236. // Network communication with Stripe failed
  1237. $error++;
  1238. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1239. } catch (\Stripe\Error\Base $e) {
  1240. // Display a very generic error to the user, and maybe send
  1241. // yourself an email
  1242. $error++;
  1243. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1244. } catch (Exception $e) {
  1245. // Something else happened, completely unrelated to Stripe
  1246. $error++;
  1247. dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
  1248. }
  1249. return $return;
  1250. }
  1251. }