dolibarr  19.0.0-dev
stripe.class.php
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 
18 // Put here all includes required by your class file
19 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
20 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
21 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
22 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
23 require_once DOL_DOCUMENT_ROOT.'/stripe/config.php'; // This set stripe global env
24 
25 
29 class Stripe extends CommonObject
30 {
34  public $rowid;
35 
39  public $fk_soc;
40 
44  public $fk_key;
45 
49  public $id;
50 
51  public $mode;
52 
56  public $entity;
57 
58  public $statut;
59 
60  public $type;
61 
62  public $code;
63  public $declinecode;
64 
68  public $message;
69 
75  public function __construct($db)
76  {
77  $this->db = $db;
78  }
79 
80 
89  public function getStripeAccount($mode = 'StripeTest', $fk_soc = 0, $entity = -1)
90  {
91  global $conf;
92 
93  $key = '';
94  if ($entity < 0) {
95  $entity = $conf->entity;
96  }
97 
98  $sql = "SELECT tokenstring";
99  $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
100  $sql .= " WHERE service = '".$this->db->escape($mode)."'";
101  $sql .= " AND entity = ".((int) $entity);
102  if ($fk_soc > 0) {
103  $sql .= " AND fk_soc = ".((int) $fk_soc);
104  } else {
105  $sql .= " AND fk_soc IS NULL";
106  }
107  $sql .= " AND fk_user IS NULL AND fk_adherent IS NULL";
108 
109  dol_syslog(get_class($this)."::getStripeAccount", LOG_DEBUG);
110 
111  $result = $this->db->query($sql);
112  if ($result) {
113  if ($this->db->num_rows($result)) {
114  $obj = $this->db->fetch_object($result);
115  $tokenstring = $obj->tokenstring;
116 
117  if ($tokenstring) {
118  $tmparray = json_decode($tokenstring);
119  $key = empty($tmparray->stripe_user_id) ? '' : $tmparray->stripe_user_id;
120  }
121  } else {
122  $tokenstring = '';
123  }
124  } else {
125  dol_print_error($this->db);
126  }
127 
128  dol_syslog("No dedicated Stripe Connect account available for entity ".$conf->entity);
129 
130  return $key;
131  }
132 
141  public function getStripeCustomerAccount($id, $status = 0, $site_account = '')
142  {
143  include_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
144  $societeaccount = new SocieteAccount($this->db);
145  return $societeaccount->getCustomerAccount($id, 'stripe', $status, $site_account); // Get thirdparty cus_...
146  }
147 
148 
159  public function customerStripe(Societe $object, $key = '', $status = 0, $createifnotlinkedtostripe = 0)
160  {
161  global $conf, $user;
162 
163  if (empty($object->id)) {
164  dol_syslog("customerStripe is called with the parameter object that is not loaded");
165  return null;
166  }
167 
168  $customer = null;
169 
170  // Force to use the correct API key
171  global $stripearrayofkeysbyenv;
172  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
173 
174  $sql = "SELECT sa.key_account as key_account, sa.entity"; // key_account is cus_....
175  $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
176  $sql .= " WHERE sa.fk_soc = ".((int) $object->id);
177  $sql .= " AND sa.entity IN (".getEntity('societe').")";
178  $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
179  $sql .= " AND (sa.site_account IS NULL OR sa.site_account = '' OR sa.site_account = '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."')";
180  $sql .= " AND sa.key_account IS NOT NULL AND sa.key_account <> ''";
181 
182  dol_syslog(get_class($this)."::customerStripe search stripe customer id for thirdparty id=".$object->id, LOG_DEBUG);
183  $resql = $this->db->query($sql);
184  if ($resql) {
185  $num = $this->db->num_rows($resql);
186  if ($num) {
187  $obj = $this->db->fetch_object($resql);
188  $tiers = $obj->key_account;
189 
190  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']);
191 
192  try {
193  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
194  //$customer = \Stripe\Customer::retrieve("$tiers");
195  $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'));
196  } else {
197  //$customer = \Stripe\Customer::retrieve("$tiers", array("stripe_account" => $key));
198  $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'), array("stripe_account" => $key));
199  }
200  } catch (Exception $e) {
201  // 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.'
202  $this->error = $e->getMessage();
203  }
204  } elseif ($createifnotlinkedtostripe) {
205  $ipaddress = getUserRemoteIP();
206 
207  $dataforcustomer = array(
208  "email" => $object->email,
209  "description" => $object->name,
210  "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
211  );
212 
213  $vatcleaned = $object->tva_intra ? $object->tva_intra : null;
214 
215  /*
216  $taxinfo = array('type'=>'vat');
217  if ($vatcleaned)
218  {
219  $taxinfo["tax_id"] = $vatcleaned;
220  }
221  // We force data to "null" if not defined as expected by Stripe
222  if (empty($vatcleaned)) $taxinfo=null;
223  $dataforcustomer["tax_info"] = $taxinfo;
224  */
225 
226  //$a = \Stripe\Stripe::getApiKey();
227  //var_dump($a);var_dump($key);exit;
228  try {
229  // Force to use the correct API key
230  global $stripearrayofkeysbyenv;
231  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
232 
233  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
234  $customer = \Stripe\Customer::create($dataforcustomer);
235  } else {
236  $customer = \Stripe\Customer::create($dataforcustomer, array("stripe_account" => $key));
237  }
238 
239  // Create the VAT record in Stripe
240  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
241  if (!empty($vatcleaned)) {
242  $isineec = isInEEC($object);
243  if ($object->country_code && $isineec) {
244  //$taxids = $customer->allTaxIds($customer->id);
245  $customer->createTaxId($customer->id, array('type'=>'eu_vat', 'value'=>$vatcleaned));
246  }
247  }
248  }
249 
250  // Create customer in Dolibarr
251  $sql = "INSERT INTO ".MAIN_DB_PREFIX."societe_account (fk_soc, login, key_account, site, site_account, status, entity, date_creation, fk_user_creat)";
252  $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).")";
253  $resql = $this->db->query($sql);
254  if (!$resql) {
255  $this->error = $this->db->lasterror();
256  }
257  } catch (Exception $e) {
258  $this->error = $e->getMessage();
259  }
260  }
261  } else {
262  dol_print_error($this->db);
263  }
264 
265  return $customer;
266  }
267 
276  public function getPaymentMethodStripe($paymentmethod, $key = '', $status = 0)
277  {
278  $stripepaymentmethod = null;
279 
280  try {
281  // Force to use the correct API key
282  global $stripearrayofkeysbyenv;
283  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
284  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
285  $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id);
286  } else {
287  $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id, array("stripe_account" => $key));
288  }
289  } catch (Exception $e) {
290  $this->error = $e->getMessage();
291  }
292 
293  return $stripepaymentmethod;
294  }
295 
304  public function getSelectedReader($reader, $key = '', $status = 0)
305  {
306  $selectedreader = null;
307 
308  try {
309  // Force to use the correct API key
310  global $stripearrayofkeysbyenv;
311  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
312  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
313  $selectedreader = \Stripe\Terminal\Reader::retrieve((string) $reader);
314  } else {
315  $stripepaymentmethod = \Stripe\Terminal\Reader::retrieve((string) $reader, array("stripe_account" => $key));
316  }
317  } catch (Exception $e) {
318  $this->error = $e->getMessage();
319  }
320 
321  return $selectedreader;
322  }
323 
350  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)
351  {
352  global $conf, $user;
353 
354  dol_syslog(get_class($this)."::getPaymentIntent", LOG_INFO, 1);
355 
356  $error = 0;
357 
358  if (empty($status)) {
359  $service = 'StripeTest';
360  } else {
361  $service = 'StripeLive';
362  }
363 
364  $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
365  if (!in_array($currency_code, $arrayzerounitcurrency)) {
366  $stripeamount = $amount * 100;
367  } else {
368  $stripeamount = $amount;
369  }
370 
371  $fee = 0;
372  if (getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT")) {
373  $fee = $amount * ((float) getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT", '0') / 100) + (float) getDolGlobalString("STRIPE_APPLICATION_FEE", '0');
374  }
375  if ($fee >= (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') && (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') > (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
376  $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0');
377  } elseif ($fee < (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
378  $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0');
379  }
380  if (!in_array($currency_code, $arrayzerounitcurrency)) {
381  $stripefee = round($fee * 100);
382  } else {
383  $stripefee = round($fee);
384  }
385 
386  $paymentintent = null;
387 
388  if (is_object($object) && getDolGlobalInt('STRIPE_REUSE_EXISTING_INTENT_IF_FOUND') && !getDolGlobalInt('STRIPE_CARD_PRESENT')) {
389  // Warning. If a payment was tried and failed, a payment intent was created.
390  // 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.
391  // 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
392  // automatically return the existing payment intent if idempotency is provided when we try to create the new one.
393  // 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)
394 
395  $sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
396  $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
397  $sql .= " WHERE pi.fk_facture = ".((int) $object->id);
398  $sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
399  $sql .= " AND pi.entity IN (".getEntity('societe').")";
400  $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
401 
402  dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);
403  $resql = $this->db->query($sql);
404  if ($resql) {
405  $num = $this->db->num_rows($resql);
406  if ($num) {
407  $obj = $this->db->fetch_object($resql);
408  $intent = $obj->ext_payment_id;
409 
410  dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record");
411 
412  // Force to use the correct API key
413  global $stripearrayofkeysbyenv;
414  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
415 
416  try {
417  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
418  $paymentintent = \Stripe\PaymentIntent::retrieve($intent);
419  } else {
420  $paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
421  }
422  } catch (Exception $e) {
423  $error++;
424  $this->error = $e->getMessage();
425  }
426  }
427  }
428  }
429 
430  if (empty($paymentintent)) {
431  // Try to create intent. See https://stripe.com/docs/api/payment_intents/create
432  $ipaddress = getUserRemoteIP();
433  $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
434  if (is_object($object)) {
435  $metadata['dol_type'] = $object->element;
436  $metadata['dol_id'] = $object->id;
437  if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
438  $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
439  }
440  }
441 
442  // list of payment method types
443  $paymentmethodtypes = array("card");
444  $descriptor = dol_trunc($tag, 10, 'right', 'UTF-8', 1);
445  if (getDolGlobalInt('STRIPE_SEPA_DIRECT_DEBIT')) {
446  $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
447  //$descriptor = preg_replace('/ref=[^:=]+/', '', $descriptor); // Clean ref
448  }
449  if (getDolGlobalInt('STRIPE_KLARNA')) {
450  $paymentmethodtypes[] = "klarna";
451  }
452  if (getDolGlobalInt('STRIPE_BANCONTACT')) {
453  $paymentmethodtypes[] = "bancontact";
454  }
455  if (getDolGlobalInt('STRIPE_IDEAL')) {
456  $paymentmethodtypes[] = "ideal";
457  }
458  if (getDolGlobalInt('STRIPE_GIROPAY')) {
459  $paymentmethodtypes[] = "giropay";
460  }
461  if (getDolGlobalInt('STRIPE_SOFORT')) {
462  $paymentmethodtypes[] = "sofort";
463  }
464  if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
465  $paymentmethodtypes = array("card_present");
466  }
467 
468  $dataforintent = array(
469  "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
470  "confirmation_method" => $mode,
471  "amount" => $stripeamount,
472  "currency" => $currency_code,
473  "payment_method_types" => $paymentmethodtypes,
474  "description" => $description,
475  //"save_payment_method" => true,
476  "setup_future_usage" => "on_session",
477  "metadata" => $metadata
478  );
479  if ($descriptor) {
480  $dataforintent["statement_descriptor_suffix"] = $descriptor; // For card payment, 22 chars that appears on bank receipt (prefix into stripe setup + this suffix)
481  $dataforintent["statement_descriptor"] = $descriptor; // For SEPA, it will take only statement_descriptor, not statement_descriptor_suffix
482  }
483  if (!is_null($customer)) {
484  $dataforintent["customer"] = $customer;
485  }
486  // payment_method =
487  // payment_method_types = array('card')
488  //var_dump($dataforintent);
489  if ($off_session) {
490  unset($dataforintent['setup_future_usage']);
491  // We can't use both "setup_future_usage" = "off_session" and "off_session" = true.
492  // Because $off_session parameter is dedicated to create paymentintent off_line (and not future payment), we need to use "off_session" = true.
493  //$dataforintent["setup_future_usage"] = "off_session";
494  $dataforintent["off_session"] = true;
495  }
496  if (getDolGlobalInt('STRIPE_GIROPAY')) {
497  unset($dataforintent['setup_future_usage']);
498  }
499  if (getDolGlobalInt('STRIPE_KLARNA')) {
500  unset($dataforintent['setup_future_usage']);
501  }
502  if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
503  unset($dataforintent['setup_future_usage']);
504  $dataforintent["capture_method"] = "manual";
505  $dataforintent["confirmation_method"] = "manual";
506  }
507  if (!is_null($payment_method)) {
508  $dataforintent["payment_method"] = $payment_method;
509  $description .= ' - '.$payment_method;
510  }
511 
512  if ($conf->entity != getDolGlobalInt('STRIPECONNECT_PRINCIPAL') && $stripefee > 0) {
513  $dataforintent["application_fee_amount"] = $stripefee;
514  }
515  if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
516  $dataforintent["receipt_email"] = $object->thirdparty->email;
517  }
518 
519  try {
520  // Force to use the correct API key
521  global $stripearrayofkeysbyenv;
522  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
523 
524  $arrayofoptions = array();
525  if (empty($noidempotency_key)) {
526  $arrayofoptions["idempotency_key"] = $description;
527  }
528  // 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.
529  if (!empty($key)) { // If the Stripe connect account not set, we use common API usage
530  $arrayofoptions["stripe_account"] = $key;
531  }
532 
533  dol_syslog("dataforintent to create paymentintent = ".var_export($dataforintent, true));
534 
535  $paymentintent = \Stripe\PaymentIntent::create($dataforintent, $arrayofoptions);
536 
537  // Store the payment intent
538  if (is_object($object)) {
539  $paymentintentalreadyexists = 0;
540 
541  if ($did > 0) {
542  // If a payment request line provided, we do not need to recreate one, we just update it
543  dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
544 
545  $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
546  $sql .= " ext_payment_site = '".$this->db->escape($service)."',";
547  $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
548  $sql .= " WHERE rowid = ".((int) $did);
549 
550  $resql = $this->db->query($sql);
551  if ($resql) {
552  $paymentintentalreadyexists++;
553  } else {
554  $error++;
555  dol_print_error($this->db);
556  }
557  } else {
558  // Check that payment intent $paymentintent->id is not already recorded.
559  dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
560 
561  $sql = "SELECT pi.rowid";
562  $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
563  $sql .= " WHERE pi.entity IN (".getEntity('societe').")";
564  $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
565  $sql .= " AND pi.ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
566 
567  $resql = $this->db->query($sql);
568  if ($resql) {
569  $num = $this->db->num_rows($resql);
570  if ($num) {
571  $obj = $this->db->fetch_object($resql);
572  if ($obj) {
573  $paymentintentalreadyexists++;
574  }
575  }
576  } else {
577  $error++;
578  dol_print_error($this->db);
579  }
580  }
581 
582  // If not, we create it.
583  if (!$error && !$paymentintentalreadyexists) {
584  $now = dol_now();
585  $sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site, amount)";
586  $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).")";
587  $resql = $this->db->query($sql);
588  if (!$resql) {
589  $error++;
590  $this->error = $this->db->lasterror();
591  dol_syslog(get_class($this)."::PaymentIntent failed to insert paymentintent with id=".$paymentintent->id." into database.", LOG_ERR);
592  }
593  }
594  } else {
595  $_SESSION["stripe_payment_intent"] = $paymentintent;
596  }
597  } catch (Stripe\Error\Card $e) {
598  $error++;
599  $this->error = $e->getMessage();
600  $this->code = $e->getStripeCode();
601  $this->declinecode = $e->getDeclineCode();
602  } catch (Exception $e) {
603  //var_dump($dataforintent);
604  //var_dump($description);
605  //var_dump($key);
606  //var_dump($paymentintent);
607  //var_dump($e->getMessage());
608  //var_dump($e);
609  $error++;
610  $this->error = $e->getMessage();
611  $this->code = '';
612  $this->declinecode = '';
613  }
614  }
615 
616  dol_syslog(get_class($this)."::getPaymentIntent return error=".$error." this->error=".$this->error, LOG_INFO, -1);
617 
618  if (!$error) {
619  return $paymentintent;
620  } else {
621  return null;
622  }
623  }
624 
643  public function getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail = 0, $confirmnow = false)
644  {
645  global $conf;
646 
647  dol_syslog("getSetupIntent description=".$description.' confirmnow='.$confirmnow, LOG_INFO, 1);
648 
649  $error = 0;
650 
651  if (empty($status)) {
652  $service = 'StripeTest';
653  } else {
654  $service = 'StripeLive';
655  }
656 
657  $setupintent = null;
658 
659  if (empty($setupintent)) {
660  $ipaddress = getUserRemoteIP();
661  $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
662  if (is_object($object)) {
663  $metadata['dol_type'] = $object->element;
664  $metadata['dol_id'] = $object->id;
665  if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
666  $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
667  }
668  }
669 
670  // list of payment method types
671  $paymentmethodtypes = array("card");
672  if (!empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) {
673  $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
674  }
675  if (!empty($conf->global->STRIPE_BANCONTACT)) {
676  $paymentmethodtypes[] = "bancontact";
677  }
678  if (!empty($conf->global->STRIPE_IDEAL)) {
679  $paymentmethodtypes[] = "ideal";
680  }
681  // Giropay not possible for setup intent
682  if (!empty($conf->global->STRIPE_SOFORT)) {
683  $paymentmethodtypes[] = "sofort";
684  }
685 
686  $dataforintent = array(
687  "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
688  "payment_method_types" => $paymentmethodtypes,
689  "usage" => "off_session",
690  "metadata" => $metadata
691  );
692  if (!is_null($customer)) {
693  $dataforintent["customer"] = $customer;
694  }
695  if (!is_null($description)) {
696  $dataforintent["description"] = $description;
697  }
698  // payment_method =
699  // payment_method_types = array('card')
700  //var_dump($dataforintent);
701 
702  if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
703  $dataforintent["receipt_email"] = $object->thirdparty->email;
704  }
705 
706  try {
707  // Force to use the correct API key
708  global $stripearrayofkeysbyenv;
709  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
710 
711  dol_syslog("getSetupIntent ".$stripearrayofkeysbyenv[$status]['publishable_key'], LOG_DEBUG);
712 
713  // 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.
714  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
715  //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description"));
716  $setupintent = \Stripe\SetupIntent::create($dataforintent, array());
717  } else {
718  //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description", "stripe_account" => $key));
719  $setupintent = \Stripe\SetupIntent::create($dataforintent, array("stripe_account" => $key));
720  }
721  //var_dump($setupintent->id);
722 
723  // Store the setup intent
724  /*if (is_object($object))
725  {
726  $setupintentalreadyexists = 0;
727  // Check that payment intent $setupintent->id is not already recorded.
728  $sql = "SELECT pi.rowid";
729  $sql.= " FROM " . MAIN_DB_PREFIX . "prelevement_demande as pi";
730  $sql.= " WHERE pi.entity IN (".getEntity('societe').")";
731  $sql.= " AND pi.ext_payment_site = '" . $this->db->escape($service) . "'";
732  $sql.= " AND pi.ext_payment_id = '".$this->db->escape($setupintent->id)."'";
733 
734  dol_syslog(get_class($this) . "::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
735  $resql = $this->db->query($sql);
736  if ($resql) {
737  $num = $this->db->num_rows($resql);
738  if ($num)
739  {
740  $obj = $this->db->fetch_object($resql);
741  if ($obj) $setupintentalreadyexists++;
742  }
743  }
744  else dol_print_error($this->db);
745 
746  // If not, we create it.
747  if (! $setupintentalreadyexists)
748  {
749  $now=dol_now();
750  $sql = "INSERT INTO " . MAIN_DB_PREFIX . "prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site)";
751  $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).")";
752  $resql = $this->db->query($sql);
753  if (! $resql)
754  {
755  $error++;
756  $this->error = $this->db->lasterror();
757  dol_syslog(get_class($this) . "::PaymentIntent failed to insert paymentintent with id=".$setupintent->id." into database.");
758  }
759  }
760  }
761  else
762  {
763  $_SESSION["stripe_setup_intent"] = $setupintent;
764  }*/
765  } catch (Exception $e) {
766  //var_dump($dataforintent);
767  //var_dump($description);
768  //var_dump($key);
769  //var_dump($setupintent);
770  //var_dump($e->getMessage());
771  $error++;
772  $this->error = $e->getMessage();
773  }
774  }
775 
776  if (!$error) {
777  dol_syslog("getSetupIntent ".(is_object($setupintent) ? $setupintent->id : ''), LOG_INFO, -1);
778  return $setupintent;
779  } else {
780  dol_syslog("getSetupIntent return error=".$error, LOG_INFO, -1);
781  return null;
782  }
783  }
784 
785 
796  public function cardStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
797  {
798  global $conf, $user, $langs;
799 
800  $card = null;
801 
802  $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_....
803  $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
804  $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
805  $sql .= " AND sa.type = 'card'";
806 
807  dol_syslog(get_class($this)."::cardStripe search stripe card id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
808  $resql = $this->db->query($sql);
809  if ($resql) {
810  $num = $this->db->num_rows($resql);
811  if ($num) {
812  $obj = $this->db->fetch_object($resql);
813  $cardref = $obj->stripe_card_ref;
814  dol_syslog(get_class($this)."::cardStripe cardref=".$cardref);
815  if ($cardref) {
816  try {
817  if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
818  if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
819  $card = $cu->sources->retrieve($cardref);
820  } else {
821  $card = \Stripe\PaymentMethod::retrieve($cardref);
822  }
823  } else {
824  if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
825  //$card = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
826  $card = $cu->sources->retrieve($cardref);
827  } else {
828  //$card = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
829  $card = \Stripe\PaymentMethod::retrieve($cardref);
830  }
831  }
832  } catch (Exception $e) {
833  $this->error = $e->getMessage();
834  dol_syslog($this->error, LOG_WARNING);
835  }
836  } elseif ($createifnotlinkedtostripe) {
837  // Deprecated with new Stripe API and SCA. We should not use anymore this part of code now.
838  $exp_date_month = $obj->exp_date_month;
839  $exp_date_year = $obj->exp_date_year;
840  $number = $obj->number;
841  $cvc = $obj->cvn; // cvn in database, cvc for stripe
842  $cardholdername = $obj->proprio;
843 
844  $ipaddress = getUserRemoteIP();
845 
846  $dataforcard = array(
847  "source" => array(
848  'object'=>'card',
849  'exp_month'=>$exp_date_month,
850  'exp_year'=>$exp_date_year,
851  'number'=>$number,
852  'cvc'=>$cvc,
853  'name'=>$cardholdername
854  ),
855  "metadata" => array(
856  'dol_type'=>$object->element,
857  'dol_id'=>$object->id,
858  'dol_version'=>DOL_VERSION,
859  'dol_entity'=>$conf->entity,
860  'ipaddress'=>$ipaddress
861  )
862  );
863 
864  //$a = \Stripe\Stripe::getApiKey();
865  //var_dump($a);
866  //var_dump($stripeacc);exit;
867  try {
868  if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
869  if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION)) {
870  dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
871  $card = $cu->sources->create($dataforcard);
872  if (!$card) {
873  $this->error = 'Creation of card on Stripe has failed';
874  }
875  } else {
876  $connect = '';
877  if (!empty($stripeacc)) {
878  $connect = $stripeacc.'/';
879  }
880  $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
881  if ($status) {
882  $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
883  }
884  $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
885 
886  //dol_syslog("Error: This case is not supported", LOG_ERR);
887  $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
888  }
889  } else {
890  if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION)) {
891  dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
892  $card = $cu->sources->create($dataforcard, array("stripe_account" => $stripeacc));
893  if (!$card) {
894  $this->error = 'Creation of card on Stripe has failed';
895  }
896  } else {
897  $connect = '';
898  if (!empty($stripeacc)) {
899  $connect = $stripeacc.'/';
900  }
901  $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
902  if ($status) {
903  $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
904  }
905  $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
906 
907  //dol_syslog("Error: This case is not supported", LOG_ERR);
908  $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
909  }
910  }
911 
912  if ($card) {
913  $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
914  $sql .= " SET stripe_card_ref = '".$this->db->escape($card->id)."', card_type = '".$this->db->escape($card->brand)."',";
915  $sql .= " country_code = '".$this->db->escape($card->country)."',";
916  $sql .= " approved = ".($card->cvc_check == 'pass' ? 1 : 0);
917  $sql .= " WHERE rowid = ".((int) $object->id);
918  $sql .= " AND type = 'card'";
919  $resql = $this->db->query($sql);
920  if (!$resql) {
921  $this->error = $this->db->lasterror();
922  }
923  }
924  } catch (Exception $e) {
925  $this->error = $e->getMessage();
926  dol_syslog($this->error, LOG_WARNING);
927  }
928  }
929  }
930  } else {
931  dol_print_error($this->db);
932  }
933 
934  return $card;
935  }
936 
937 
948  public function sepaStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
949  {
950  global $conf, $user, $langs;
951  $sepa = null;
952 
953  $sql = "SELECT sa.stripe_card_ref, sa.proprio, sa.iban_prefix as iban, sa.rum"; // stripe_card_ref is 'src_...' for Stripe SEPA
954  $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
955  $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
956  $sql .= " AND sa.type = 'ban'"; //type ban to get normal bank account of customer (prelevement)
957 
958  $soc = new Societe($this->db);
959  $soc->fetch($object->fk_soc);
960 
961  dol_syslog(get_class($this)."::sepaStripe search stripe ban id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
962  $resql = $this->db->query($sql);
963  if ($resql) {
964  $num = $this->db->num_rows($resql);
965  if ($num) {
966  $obj = $this->db->fetch_object($resql);
967  $cardref = $obj->stripe_card_ref;
968  dol_syslog(get_class($this)."::sepaStripe paymentmode=".$cardref);
969  if ($cardref) {
970  try {
971  if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
972  if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
973  $sepa = $cu->sources->retrieve($cardref);
974  } else {
975  $sepa = \Stripe\PaymentMethod::retrieve($cardref);
976  }
977  } else {
978  if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
979  //$sepa = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
980  $sepa = $cu->sources->retrieve($cardref);
981  } else {
982  //$sepa = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
983  $sepa = \Stripe\PaymentMethod::retrieve($cardref);
984  }
985  }
986  } catch (Exception $e) {
987  $this->error = $e->getMessage();
988  dol_syslog($this->error, LOG_WARNING);
989  }
990  } elseif ($createifnotlinkedtostripe) {
991  $iban = $obj->iban;
992  $ipaddress = getUserRemoteIP();
993  $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
994  if (is_object($object)) {
995  $metadata['dol_type'] = $object->element;
996  $metadata['dol_id'] = $object->id;
997  $metadata['dol_thirdparty_id'] = $soc->id;
998  }
999 
1000  $description = 'SEPA for IBAN '.$iban;
1001 
1002  $dataforcard = array(
1003  'type'=>'sepa_debit',
1004  "sepa_debit" => array('iban' => $iban),
1005  'billing_details' => array(
1006  'name' => $soc->name,
1007  'email' => !empty($soc->email) ? $soc->email : "",
1008  ),
1009  "metadata" => $metadata
1010  );
1011  // Complete owner name
1012  if (!empty($soc->town)) {
1013  $dataforcard['billing_details']['address']['city']=$soc->town;
1014  }
1015  if (!empty($soc->country_code)) {
1016  $dataforcard['billing_details']['address']['country']=$soc->country_code;
1017  }
1018  if (!empty($soc->address)) {
1019  $dataforcard['billing_details']['address']['line1']=$soc->address;
1020  }
1021  if (!empty($soc->zip)) {
1022  $dataforcard['billing_details']['address']['postal_code']=$soc->zip;
1023  }
1024  if (!empty($soc->state)) {
1025  $dataforcard['billing_details']['address']['state']=$soc->state;
1026  }
1027 
1028  //$a = \Stripe\Stripe::getApiKey();
1029  //var_dump($a);var_dump($stripeacc);exit;
1030  try {
1031  dol_syslog("Try to create sepa_debit 0");
1032 
1033  $service = 'StripeTest';
1034  $servicestatus = 0;
1035  if (!empty($conf->global->STRIPE_LIVE) && !GETPOST('forcesandbox', 'alpha')) {
1036  $service = 'StripeLive';
1037  $servicestatus = 1;
1038  }
1039  // Force to use the correct API key
1040  global $stripearrayofkeysbyenv;
1041  $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];
1042 
1043  dol_syslog("Try to create sepa_debit with data = ".json_encode($dataforcard));
1044 
1045  $s = new \Stripe\StripeClient($stripeacc);
1046 
1047  //var_dump($dataforcard);exit;
1048 
1049  $sepa = $s->paymentMethods->create($dataforcard);
1050  if (!$sepa) {
1051  $this->error = 'Creation of payment method sepa_debit on Stripe has failed';
1052  } else {
1053  // link customer and src
1054  //$cs = $this->getSetupIntent($description, $soc, $cu, '', $status);
1055  $dataforintent = array(['description'=> $description, 'payment_method_types' => ['sepa_debit'], 'customer' => $cu->id, 'payment_method' => $sepa->id], 'metadata'=>$metadata);
1056  $cs = $s->setupIntents->create($dataforintent);
1057  //$cs = $s->setupIntents->update($cs->id, ['payment_method' => $sepa->id]);
1058  $cs = $s->setupIntents->confirm($cs->id, ['mandate_data' => ['customer_acceptance' => ['type' => 'offline']]]);
1059  if (!$cs) {
1060  $this->error = 'Link SEPA <-> Customer failed';
1061  } else {
1062  dol_syslog("Update the payment mode of the customer");
1063  // print json_encode($sepa);
1064 
1065  // Save the Stripe payment mode ID into the Dolibarr database
1066  $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
1067  $sql .= " SET stripe_card_ref = '".$this->db->escape($sepa->id)."', card_type = 'sepa_debit',";
1068  $sql .= " stripe_account= '" . $this->db->escape($cu->id . "@" . $stripeacc) . "'";
1069  $sql .= " WHERE rowid = ".((int) $object->id);
1070  $sql .= " AND type = 'ban'";
1071  $resql = $this->db->query($sql);
1072  if (!$resql) {
1073  $this->error = $this->db->lasterror();
1074  }
1075  }
1076  }
1077  } catch (Exception $e) {
1078  $sepa = null;
1079  $this->error = $e->getMessage();
1080  dol_syslog($this->error, LOG_WARNING);
1081  }
1082  }
1083  }
1084  } else {
1085  dol_print_error($this->db);
1086  }
1087 
1088  return $sepa;
1089  }
1090 
1091 
1108  public function createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status = 0, $usethirdpartyemailforreceiptemail = 0, $capture = true)
1109  {
1110  global $conf;
1111 
1112  $error = 0;
1113 
1114  if (empty($status)) {
1115  $service = 'StripeTest';
1116  } else {
1117  $service = 'StripeLive';
1118  }
1119 
1120  $sql = "SELECT sa.key_account as key_account, sa.fk_soc, sa.entity";
1121  $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
1122  $sql .= " WHERE sa.key_account = '".$this->db->escape($customer)."'";
1123  //$sql.= " AND sa.entity IN (".getEntity('societe').")";
1124  $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
1125 
1126  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1127  $result = $this->db->query($sql);
1128  if ($result) {
1129  if ($this->db->num_rows($result)) {
1130  $obj = $this->db->fetch_object($result);
1131  $key = $obj->fk_soc;
1132  } else {
1133  $key = null;
1134  }
1135  } else {
1136  $key = null;
1137  }
1138 
1139  $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
1140  if (!in_array($currency, $arrayzerounitcurrency)) {
1141  $stripeamount = $amount * 100;
1142  } else {
1143  $stripeamount = $amount;
1144  }
1145 
1146  $societe = new Societe($this->db);
1147  if ($key > 0) {
1148  $societe->fetch($key);
1149  }
1150 
1151  $description = "";
1152  $ref = "";
1153  if ($origin == 'order') {
1154  $order = new Commande($this->db);
1155  $order->fetch($item);
1156  $ref = $order->ref;
1157  $description = "ORD=".$ref.".CUS=".$societe->id.".PM=stripe";
1158  } elseif ($origin == 'invoice') {
1159  $invoice = new Facture($this->db);
1160  $invoice->fetch($item);
1161  $ref = $invoice->ref;
1162  $description = "INV=".$ref.".CUS=".$societe->id.".PM=stripe";
1163  }
1164 
1165  $ipaddress = getUserRemoteIP();
1166 
1167  $metadata = array(
1168  "dol_id" => (string) $item,
1169  "dol_type" => (string) $origin,
1170  "dol_thirdparty_id" => (string) $societe->id,
1171  'dol_thirdparty_name' => $societe->name,
1172  'dol_version' => DOL_VERSION,
1173  'dol_entity' => $conf->entity,
1174  'ipaddress' => $ipaddress
1175  );
1176  $return = new Stripe($this->db);
1177  try {
1178  // Force to use the correct API key
1179  global $stripearrayofkeysbyenv;
1180  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
1181 
1182  if (empty($conf->stripeconnect->enabled)) { // With a common Stripe account
1183  if (preg_match('/pm_/i', $source)) {
1184  $stripecard = $source;
1185  $amountstripe = $stripeamount;
1186  $FULLTAG = 'PFBO'; // Payment From Back Office
1187  $stripe = $return;
1188  $amounttopay = $amount;
1189  $servicestatus = $status;
1190 
1191  dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
1192  $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
1193 
1194  dol_syslog("* createPaymentStripe Create payment for customer ".$customer->id." on source card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
1195 
1196  // Create payment intent and charge payment (confirmnow = true)
1197  $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
1198 
1199  $charge = new stdClass();
1200  if ($paymentintent->status == 'succeeded') {
1201  $charge->status = 'ok';
1202  } else {
1203  $charge->status = 'failed';
1204  $charge->failure_code = $stripe->code;
1205  $charge->failure_message = $stripe->error;
1206  $charge->failure_declinecode = $stripe->declinecode;
1207  $stripefailurecode = $stripe->code;
1208  $stripefailuremessage = $stripe->error;
1209  $stripefailuredeclinecode = $stripe->declinecode;
1210  }
1211  } elseif (preg_match('/acct_/i', $source)) {
1212  $charge = \Stripe\Charge::create(array(
1213  "amount" => "$stripeamount",
1214  "currency" => "$currency",
1215  "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
1216  "description" => "Stripe payment: ".$description,
1217  "capture" => $capture,
1218  "metadata" => $metadata,
1219  "source" => "$source"
1220  ));
1221  } else {
1222  $paymentarray = array(
1223  "amount" => "$stripeamount",
1224  "currency" => "$currency",
1225  "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
1226  "description" => "Stripe payment: ".$description,
1227  "capture" => $capture,
1228  "metadata" => $metadata,
1229  "source" => "$source",
1230  "customer" => "$customer"
1231  );
1232 
1233  if ($societe->email && $usethirdpartyemailforreceiptemail) {
1234  $paymentarray["receipt_email"] = $societe->email;
1235  }
1236 
1237  $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description"));
1238  }
1239  } else {
1240  // With Stripe Connect
1241  $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
1242  if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
1243  $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
1244  } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
1245  $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
1246  }
1247 
1248  if (!in_array($currency, $arrayzerounitcurrency)) {
1249  $stripefee = round($fee * 100);
1250  } else {
1251  $stripefee = round($fee);
1252  }
1253 
1254  $paymentarray = array(
1255  "amount" => "$stripeamount",
1256  "currency" => "$currency",
1257  "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
1258  "description" => "Stripe payment: ".$description,
1259  "capture" => $capture,
1260  "metadata" => $metadata,
1261  "source" => "$source",
1262  "customer" => "$customer"
1263  );
1264  if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0) {
1265  $paymentarray["application_fee_amount"] = $stripefee;
1266  }
1267  if ($societe->email && $usethirdpartyemailforreceiptemail) {
1268  $paymentarray["receipt_email"] = $societe->email;
1269  }
1270 
1271  if (preg_match('/pm_/i', $source)) {
1272  $stripecard = $source;
1273  $amountstripe = $stripeamount;
1274  $FULLTAG = 'PFBO'; // Payment From Back Office
1275  $stripe = $return;
1276  $amounttopay = $amount;
1277  $servicestatus = $status;
1278 
1279  dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
1280  $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
1281 
1282  dol_syslog("* createPaymentStripe Create payment on card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
1283 
1284  // Create payment intent and charge payment (confirmnow = true)
1285  $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
1286 
1287  $charge = new stdClass();
1288  if ($paymentintent->status == 'succeeded') {
1289  $charge->status = 'ok';
1290  $charge->id = $paymentintent->id;
1291  } else {
1292  $charge->status = 'failed';
1293  $charge->failure_code = $stripe->code;
1294  $charge->failure_message = $stripe->error;
1295  $charge->failure_declinecode = $stripe->declinecode;
1296  }
1297  } else {
1298  $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description", "stripe_account" => "$account"));
1299  }
1300  }
1301  if (isset($charge->id)) {
1302  }
1303 
1304  $return->statut = 'success';
1305  $return->id = $charge->id;
1306 
1307  if (preg_match('/pm_/i', $source)) {
1308  $return->message = 'Payment retrieved by card status = '.$charge->status;
1309  } else {
1310  if ($charge->source->type == 'card') {
1311  $return->message = $charge->source->card->brand." ....".$charge->source->card->last4;
1312  } elseif ($charge->source->type == 'three_d_secure') {
1313  $stripe = new Stripe($this->db);
1314  $src = \Stripe\Source::retrieve("".$charge->source->three_d_secure->card, array(
1315  "stripe_account" => $stripe->getStripeAccount($service)
1316  ));
1317  $return->message = $src->card->brand." ....".$src->card->last4;
1318  } else {
1319  $return->message = $charge->id;
1320  }
1321  }
1322  } catch (\Stripe\Error\Card $e) {
1323  include DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
1324  // Since it's a decline, \Stripe\Error\Card will be caught
1325  $body = $e->getJsonBody();
1326  $err = $body['error'];
1327 
1328  $return->statut = 'error';
1329  $return->id = $err['charge'];
1330  $return->type = $err['type'];
1331  $return->code = $err['code'];
1332  $return->message = $err['message'];
1333  $body = "Error: <br>".$return->id." ".$return->message." ";
1334  $subject = '[Alert] Payment error using Stripe';
1335  $cmailfile = new CMailFile($subject, $conf->global->ONLINE_PAYMENT_SENDEMAIL, $conf->global->MAIN_INFO_SOCIETE_MAIL, $body);
1336  $cmailfile->sendfile();
1337 
1338  $error++;
1339  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1340  } catch (\Stripe\Error\RateLimit $e) {
1341  // Too many requests made to the API too quickly
1342  $error++;
1343  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1344  } catch (\Stripe\Error\InvalidRequest $e) {
1345  // Invalid parameters were supplied to Stripe's API
1346  $error++;
1347  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1348  } catch (\Stripe\Error\Authentication $e) {
1349  // Authentication with Stripe's API failed
1350  // (maybe you changed API keys recently)
1351  $error++;
1352  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1353  } catch (\Stripe\Error\ApiConnection $e) {
1354  // Network communication with Stripe failed
1355  $error++;
1356  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1357  } catch (\Stripe\Error\Base $e) {
1358  // Display a very generic error to the user, and maybe send
1359  // yourself an email
1360  $error++;
1361  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1362  } catch (Exception $e) {
1363  // Something else happened, completely unrelated to Stripe
1364  $error++;
1365  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1366  }
1367  return $return;
1368  }
1369 }
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Class to manage customers orders.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
Class for CompanyPaymentMode.
Class to manage invoices.
Class for SocieteAccount.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class.
getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail=0, $confirmnow=false)
Get the Stripe payment intent.
getPaymentMethodStripe($paymentmethod, $key='', $status=0)
Get the Stripe payment method Object from its ID.
sepaStripe($cu, CompanyPaymentMode $object, $stripeacc='', $status=0, $createifnotlinkedtostripe=0)
Get the Stripe SEPA of a company payment mode.
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)
Get the Stripe payment intent.
getStripeCustomerAccount($id, $status=0, $site_account='')
getStripeCustomerAccount
cardStripe($cu, CompanyPaymentMode $object, $stripeacc='', $status=0, $createifnotlinkedtostripe=0)
Get the Stripe card of a company payment mode (option to create it on Stripe if not linked yet is no ...
createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status=0, $usethirdpartyemailforreceiptemail=0, $capture=true)
Create charge.
__construct($db)
Constructor.
getStripeAccount($mode='StripeTest', $fk_soc=0, $entity=-1)
Return main company OAuth Connect stripe account.
customerStripe(Societe $object, $key='', $status=0, $createifnotlinkedtostripe=0)
Get the Stripe customer of a thirdparty (with option to create it in Stripe if not linked yet).
getSelectedReader($reader, $key='', $status=0)
Get the Stripe reader Object from its ID.
isInEEC($object)
Return if a country of an object is inside the EEC (European Economic Community)
if(isModEnabled('facture') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $sql
Social contributions to pay.
Definition: index.php:746
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
getUserRemoteIP()
Return the IP of remote user.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
if(!defined( 'CSRFCHECK_WITH_TOKEN'))
div float
Buy price without taxes.
Definition: style.css.php:921
print *****$script_file(".$version.") pid code
! Closing after partial payment: discount_vat, badcustomer or badsupplier, bankcharge,...