dolibarr  13.0.2
stripe.class.php
1 <?php
2 /* Copyright (C) 2018-2019 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) $entity = $conf->entity;
95 
96  $sql = "SELECT tokenstring";
97  $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
98  $sql .= " WHERE service = '".$this->db->escape($mode)."'";
99  $sql .= " AND entity = ".((int) $entity);
100  if ($fk_soc > 0) {
101  $sql .= " AND fk_soc = ".$fk_soc;
102  } else {
103  $sql .= " AND fk_soc IS NULL";
104  }
105  $sql .= " AND fk_user IS NULL AND fk_adherent IS NULL";
106 
107  dol_syslog(get_class($this)."::getStripeAccount", LOG_DEBUG);
108 
109  $result = $this->db->query($sql);
110  if ($result) {
111  if ($this->db->num_rows($result)) {
112  $obj = $this->db->fetch_object($result);
113  $tokenstring = $obj->tokenstring;
114 
115  $tmparray = json_decode($tokenstring);
116  $key = $tmparray->stripe_user_id;
117  } else {
118  $tokenstring = '';
119  }
120  } else {
121  dol_print_error($this->db);
122  }
123 
124  dol_syslog("No dedicated Stripe Connect account available for entity ".$conf->entity);
125  return $key;
126  }
127 
136  public function getStripeCustomerAccount($id, $status = 0, $site_account = '')
137  {
138  include_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
139  $societeaccount = new SocieteAccount($this->db);
140  return $societeaccount->getCustomerAccount($id, 'stripe', $status, $site_account); // Get thirdparty cus_...
141  }
142 
143 
154  public function customerStripe(Societe $object, $key = '', $status = 0, $createifnotlinkedtostripe = 0)
155  {
156  global $conf, $user;
157 
158  if (empty($object->id))
159  {
160  dol_syslog("customerStripe is called with the parameter object that is not loaded");
161  return null;
162  }
163 
164  $customer = null;
165 
166  // Force to use the correct API key
167  global $stripearrayofkeysbyenv;
168  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
169 
170  $sql = "SELECT sa.key_account as key_account, sa.entity"; // key_account is cus_....
171  $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
172  $sql .= " WHERE sa.fk_soc = ".$object->id;
173  $sql .= " AND sa.entity IN (".getEntity('societe').")";
174  $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
175  $sql .= " AND (sa.site_account IS NULL OR sa.site_account = '' OR sa.site_account = '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."')";
176  $sql .= " AND sa.key_account IS NOT NULL AND sa.key_account <> ''";
177 
178  dol_syslog(get_class($this)."::customerStripe search stripe customer id for thirdparty id=".$object->id, LOG_DEBUG);
179  $resql = $this->db->query($sql);
180  if ($resql) {
181  $num = $this->db->num_rows($resql);
182  if ($num) {
183  $obj = $this->db->fetch_object($resql);
184  $tiers = $obj->key_account;
185 
186  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']);
187 
188  try {
189  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
190  //$customer = \Stripe\Customer::retrieve("$tiers");
191  $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'));
192  } else {
193  //$customer = \Stripe\Customer::retrieve("$tiers", array("stripe_account" => $key));
194  $customer = \Stripe\Customer::retrieve(array('id'=>"$tiers", 'expand[]'=>'sources'), array("stripe_account" => $key));
195  }
196  } catch (Exception $e)
197  {
198  // 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.'
199  $this->error = $e->getMessage();
200  }
201  } elseif ($createifnotlinkedtostripe) {
202  $ipaddress = getUserRemoteIP();
203 
204  $dataforcustomer = array(
205  "email" => $object->email,
206  "description" => $object->name,
207  "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
208  );
209 
210  $vatcleaned = $object->tva_intra ? $object->tva_intra : null;
211 
212  /*
213  $taxinfo = array('type'=>'vat');
214  if ($vatcleaned)
215  {
216  $taxinfo["tax_id"] = $vatcleaned;
217  }
218  // We force data to "null" if not defined as expected by Stripe
219  if (empty($vatcleaned)) $taxinfo=null;
220  $dataforcustomer["tax_info"] = $taxinfo;
221  */
222 
223  //$a = \Stripe\Stripe::getApiKey();
224  //var_dump($a);var_dump($key);exit;
225  try {
226  // Force to use the correct API key
227  global $stripearrayofkeysbyenv;
228  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
229 
230  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
231  $customer = \Stripe\Customer::create($dataforcustomer);
232  } else {
233  $customer = \Stripe\Customer::create($dataforcustomer, array("stripe_account" => $key));
234  }
235 
236  // Create the VAT record in Stripe
237  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
238  {
239  if (!empty($vatcleaned))
240  {
241  $isineec = isInEEC($object);
242  if ($object->country_code && $isineec)
243  {
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 (".$object->id.", '', '".$this->db->escape($customer->id)."', 'stripe', '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."', ".$status.", ".$conf->entity.", '".$this->db->idate(dol_now())."', ".$user->id.")";
253  $resql = $this->db->query($sql);
254  if (!$resql)
255  {
256  $this->error = $this->db->lasterror();
257  }
258  } catch (Exception $e)
259  {
260  $this->error = $e->getMessage();
261  }
262  }
263  } else {
264  dol_print_error($this->db);
265  }
266 
267  return $customer;
268  }
269 
278  public function getPaymentMethodStripe($paymentmethod, $key = '', $status = 0)
279  {
280  $stripepaymentmethod = null;
281 
282  try {
283  // Force to use the correct API key
284  global $stripearrayofkeysbyenv;
285  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
286  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
287  $stripepaymentmethod = \Stripe\PaymentMethod::retrieve(''.$paymentmethod->id.'');
288  } else {
289  $stripepaymentmethod = \Stripe\PaymentMethod::retrieve(''.$paymentmethod->id.'', array("stripe_account" => $key));
290  }
291  } catch (Exception $e)
292  {
293  $this->error = $e->getMessage();
294  }
295 
296  return $stripepaymentmethod;
297  }
298 
324  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)
325  {
326  global $conf, $user;
327 
328  dol_syslog("getPaymentIntent", LOG_INFO, 1);
329 
330  $error = 0;
331 
332  if (empty($status)) $service = 'StripeTest';
333  else $service = 'StripeLive';
334 
335  $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
336  if (!in_array($currency_code, $arrayzerounitcurrency)) $stripeamount = $amount * 100;
337  else $stripeamount = $amount;
338 
339  $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
340  if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
341  $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
342  } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
343  $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
344  }
345  if (!in_array($currency_code, $arrayzerounitcurrency)) {
346  $stripefee = round($fee * 100);
347  } else {
348  $stripefee = round($fee);
349  }
350 
351  $paymentintent = null;
352 
353  if (is_object($object) && !empty($conf->global->STRIPE_REUSE_EXISTING_INTENT_IF_FOUND))
354  {
355  // Warning. If a payment was tried and failed, a payment intent was created.
356  // 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.
357  // 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
358  // automatically return the existing payment intent if idempotency is provided when we try to create the new one.
359  // 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)
360 
361  $sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
362  $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_facture_demande as pi";
363  $sql .= " WHERE pi.fk_facture = ".$object->id;
364  $sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
365  $sql .= " AND pi.entity IN (".getEntity('societe').")";
366  $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
367 
368  dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);
369  $resql = $this->db->query($sql);
370  if ($resql) {
371  $num = $this->db->num_rows($resql);
372  if ($num)
373  {
374  $obj = $this->db->fetch_object($resql);
375  $intent = $obj->ext_payment_id;
376 
377  dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record");
378 
379  // Force to use the correct API key
380  global $stripearrayofkeysbyenv;
381  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
382 
383  try {
384  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
385  $paymentintent = \Stripe\PaymentIntent::retrieve($intent);
386  } else {
387  $paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
388  }
389  }
390  catch (Exception $e) {
391  $error++;
392  $this->error = $e->getMessage();
393  }
394  }
395  }
396  }
397 
398  if (empty($paymentintent))
399  {
400  $ipaddress = getUserRemoteIP();
401  $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
402  if (is_object($object))
403  {
404  $metadata['dol_type'] = $object->element;
405  $metadata['dol_id'] = $object->id;
406  if (is_object($object->thirdparty) && $object->thirdparty->id > 0) $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
407  }
408 
409  // list of payment method types
410  $paymentmethodtypes = array("card");
411  if (!empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
412  if (!empty($conf->global->STRIPE_IDEAL)) $paymentmethodtypes[] = "ideal"; //&& ($object->thirdparty->isInEEC())
413 
414  $dataforintent = array(
415  "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
416  "confirmation_method" => $mode,
417  "amount" => $stripeamount,
418  "currency" => $currency_code,
419  "payment_method_types" => $paymentmethodtypes,
420  "description" => $description,
421  "statement_descriptor_suffix" => dol_trunc($tag, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
422  //"save_payment_method" => true,
423  "setup_future_usage" => "on_session",
424  "metadata" => $metadata
425  );
426  if (!is_null($customer)) $dataforintent["customer"] = $customer;
427  // payment_method =
428  // payment_method_types = array('card')
429  //var_dump($dataforintent);
430  if ($off_session)
431  {
432  unset($dataforintent['setup_future_usage']);
433  //$dataforintent["setup_future_usage"] = "off_session";
434  $dataforintent["off_session"] = true;
435  }
436  if (!is_null($payment_method))
437  {
438  $dataforintent["payment_method"] = $payment_method;
439  $description .= ' - '.$payment_method;
440  }
441 
442  if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0)
443  {
444  $dataforintent["application_fee_amount"] = $stripefee;
445  }
446  if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email)
447  {
448  $dataforintent["receipt_email"] = $object->thirdparty->email;
449  }
450 
451  try {
452  // Force to use the correct API key
453  global $stripearrayofkeysbyenv;
454  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
455 
456  $arrayofoptions = array();
457  if (empty($noidempotency_key)) {
458  $arrayofoptions["idempotency_key"] = $description;
459  }
460  // 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.
461  if (!empty($key)) { // If the Stripe connect account not set, we use common API usage
462  $arrayofoptions["stripe_account"] = $key;
463  }
464  $paymentintent = \Stripe\PaymentIntent::create($dataforintent, $arrayofoptions);
465 
466  // Store the payment intent
467  if (is_object($object))
468  {
469  $paymentintentalreadyexists = 0;
470  // Check that payment intent $paymentintent->id is not already recorded.
471  $sql = "SELECT pi.rowid";
472  $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_facture_demande as pi";
473  $sql .= " WHERE pi.entity IN (".getEntity('societe').")";
474  $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
475  $sql .= " AND pi.ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
476 
477  dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_facture_demande", LOG_DEBUG);
478  $resql = $this->db->query($sql);
479  if ($resql) {
480  $num = $this->db->num_rows($resql);
481  if ($num)
482  {
483  $obj = $this->db->fetch_object($resql);
484  if ($obj) $paymentintentalreadyexists++;
485  }
486  } else dol_print_error($this->db);
487 
488  // If not, we create it.
489  if (!$paymentintentalreadyexists)
490  {
491  $now = dol_now();
492  $sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_facture_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site, amount)";
493  $sql .= " VALUES ('".$this->db->idate($now)."', ".$user->id.", '".$this->db->escape($paymentintent->id)."', ".$object->id.", '".$this->db->escape($object->element)."', ".$conf->entity.", '".$this->db->escape($service)."', ".$amount.")";
494  $resql = $this->db->query($sql);
495  if (!$resql)
496  {
497  $error++;
498  $this->error = $this->db->lasterror();
499  dol_syslog(get_class($this)."::PaymentIntent failed to insert paymentintent with id=".$paymentintent->id." into database.");
500  }
501  }
502  } else {
503  $_SESSION["stripe_payment_intent"] = $paymentintent;
504  }
505  } catch (Stripe\Error\Card $e)
506  {
507  $error++;
508  $this->error = $e->getMessage();
509  $this->code = $e->getStripeCode();
510  $this->declinecode = $e->getDeclineCode();
511  } catch (Exception $e)
512  {
513  /*var_dump($dataforintent);
514  var_dump($description);
515  var_dump($key);
516  var_dump($paymentintent);
517  var_dump($e->getMessage());
518  var_dump($e);*/
519  $error++;
520  $this->error = $e->getMessage();
521  $this->code = '';
522  $this->declinecode = '';
523  }
524  }
525 
526  dol_syslog("getPaymentIntent return error=".$error." this->error=".$this->error, LOG_INFO, -1);
527 
528  if (!$error)
529  {
530  return $paymentintent;
531  } else {
532  return null;
533  }
534  }
535 
536 
555  public function getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail = 0, $confirmnow = false)
556  {
557  global $conf;
558 
559  dol_syslog("getSetupIntent description=".$description.' confirmnow='.$confirmnow, LOG_INFO, 1);
560 
561  $error = 0;
562 
563  if (empty($status)) $service = 'StripeTest';
564  else $service = 'StripeLive';
565 
566  $setupintent = null;
567 
568  if (empty($setupintent))
569  {
570  $ipaddress = getUserRemoteIP();
571  $metadata = array('dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress);
572  if (is_object($object))
573  {
574  $metadata['dol_type'] = $object->element;
575  $metadata['dol_id'] = $object->id;
576  if (is_object($object->thirdparty) && $object->thirdparty->id > 0) $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
577  }
578 
579  // list of payment method types
580  $paymentmethodtypes = array("card");
581  if (!empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
582  // iDEAL not supported with setupIntent
583 
584  $dataforintent = array(
585  "confirm" => $confirmnow, // Do not confirm immediatly during creation of intent
586  "payment_method_types" => $paymentmethodtypes,
587  "usage" => "off_session",
588  "metadata" => $metadata
589  );
590  if (!is_null($customer)) $dataforintent["customer"] = $customer;
591  if (!is_null($description)) $dataforintent["description"] = $description;
592  // payment_method =
593  // payment_method_types = array('card')
594  //var_dump($dataforintent);
595 
596  if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email)
597  {
598  $dataforintent["receipt_email"] = $object->thirdparty->email;
599  }
600 
601  try {
602  // Force to use the correct API key
603  global $stripearrayofkeysbyenv;
604  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
605 
606  dol_syslog("getSetupIntent ".$stripearrayofkeysbyenv[$status]['publishable_key'], LOG_DEBUG);
607 
608  // 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.
609  if (empty($key)) { // If the Stripe connect account not set, we use common API usage
610  //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description"));
611  $setupintent = \Stripe\SetupIntent::create($dataforintent, array());
612  } else {
613  //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description", "stripe_account" => $key));
614  $setupintent = \Stripe\SetupIntent::create($dataforintent, array("stripe_account" => $key));
615  }
616  //var_dump($setupintent->id);
617 
618  // Store the setup intent
619  /*if (is_object($object))
620  {
621  $setupintentalreadyexists = 0;
622  // Check that payment intent $setupintent->id is not already recorded.
623  $sql = "SELECT pi.rowid";
624  $sql.= " FROM " . MAIN_DB_PREFIX . "prelevement_facture_demande as pi";
625  $sql.= " WHERE pi.entity IN (".getEntity('societe').")";
626  $sql.= " AND pi.ext_payment_site = '" . $this->db->escape($service) . "'";
627  $sql.= " AND pi.ext_payment_id = '".$this->db->escape($setupintent->id)."'";
628 
629  dol_syslog(get_class($this) . "::getPaymentIntent search if payment intent already in prelevement_facture_demande", LOG_DEBUG);
630  $resql = $this->db->query($sql);
631  if ($resql) {
632  $num = $this->db->num_rows($resql);
633  if ($num)
634  {
635  $obj = $this->db->fetch_object($resql);
636  if ($obj) $setupintentalreadyexists++;
637  }
638  }
639  else dol_print_error($this->db);
640 
641  // If not, we create it.
642  if (! $setupintentalreadyexists)
643  {
644  $now=dol_now();
645  $sql = "INSERT INTO " . MAIN_DB_PREFIX . "prelevement_facture_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site)";
646  $sql .= " VALUES ('".$this->db->idate($now)."', ".$user->id.", '".$this->db->escape($setupintent->id)."', ".$object->id.", '".$this->db->escape($object->element)."', " . $conf->entity . ", '" . $this->db->escape($service) . "', ".$amount.")";
647  $resql = $this->db->query($sql);
648  if (! $resql)
649  {
650  $error++;
651  $this->error = $this->db->lasterror();
652  dol_syslog(get_class($this) . "::PaymentIntent failed to insert paymentintent with id=".$setupintent->id." into database.");
653  }
654  }
655  }
656  else
657  {
658  $_SESSION["stripe_setup_intent"] = $setupintent;
659  }*/
660  } catch (Exception $e)
661  {
662  /*var_dump($dataforintent);
663  var_dump($description);
664  var_dump($key);
665  var_dump($setupintent);
666  var_dump($e->getMessage());*/
667  $error++;
668  $this->error = $e->getMessage();
669  }
670  }
671 
672  if (!$error)
673  {
674  dol_syslog("getSetupIntent ".(is_object($setupintent) ? $setupintent->id : ''), LOG_INFO, -1);
675  return $setupintent;
676  } else {
677  dol_syslog("getSetupIntent return error=".$error, LOG_INFO, -1);
678  return null;
679  }
680  }
681 
682 
693  public function cardStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
694  {
695  global $conf, $user, $langs;
696 
697  $card = null;
698 
699  $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_....
700  $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
701  $sql .= " WHERE sa.rowid = ".$object->id; // We get record from ID, no need for filter on entity
702  $sql .= " AND sa.type = 'card'";
703 
704  dol_syslog(get_class($this)."::fetch search stripe card id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
705  $resql = $this->db->query($sql);
706  if ($resql) {
707  $num = $this->db->num_rows($resql);
708  if ($num)
709  {
710  $obj = $this->db->fetch_object($resql);
711  $cardref = $obj->stripe_card_ref;
712  dol_syslog(get_class($this)."::cardStripe cardref=".$cardref);
713  if ($cardref)
714  {
715  try {
716  if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
717  if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources))
718  {
719  $card = $cu->sources->retrieve($cardref);
720  } else {
721  $card = \Stripe\PaymentMethod::retrieve($cardref);
722  }
723  } else {
724  if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources))
725  {
726  //$card = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
727  $card = $cu->sources->retrieve($cardref);
728  } else {
729  //$card = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
730  $card = \Stripe\PaymentMethod::retrieve($cardref);
731  }
732  }
733  } catch (Exception $e)
734  {
735  $this->error = $e->getMessage();
736  dol_syslog($this->error, LOG_WARNING);
737  }
738  } elseif ($createifnotlinkedtostripe)
739  {
740  $exp_date_month = $obj->exp_date_month;
741  $exp_date_year = $obj->exp_date_year;
742  $number = $obj->number;
743  $cvc = $obj->cvn; // cvn in database, cvc for stripe
744  $cardholdername = $obj->proprio;
745 
746  $ipaddress = getUserRemoteIP();
747 
748  $dataforcard = array(
749  "source" => array('object'=>'card', 'exp_month'=>$exp_date_month, 'exp_year'=>$exp_date_year, 'number'=>$number, 'cvc'=>$cvc, 'name'=>$cardholdername),
750  "metadata" => array('dol_id'=>$object->id, 'dol_version'=>DOL_VERSION, 'dol_entity'=>$conf->entity, 'ipaddress'=>$ipaddress)
751  );
752 
753  //$a = \Stripe\Stripe::getApiKey();
754  //var_dump($a);var_dump($stripeacc);exit;
755  try {
756  if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
757  if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION))
758  {
759  dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
760  $card = $cu->sources->create($dataforcard);
761  if (!$card)
762  {
763  $this->error = 'Creation of card on Stripe has failed';
764  }
765  } else {
766  $connect = '';
767  if (!empty($stripeacc)) $connect = $stripeacc.'/';
768  $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
769  if ($status)
770  {
771  $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
772  }
773  $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
774 
775  //dol_syslog("Error: This case is not supported", LOG_ERR);
776  $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
777  }
778  } else {
779  if (empty($conf->global->STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION))
780  {
781  dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
782  $card = $cu->sources->create($dataforcard, array("stripe_account" => $stripeacc));
783  if (!$card)
784  {
785  $this->error = 'Creation of card on Stripe has failed';
786  }
787  } else {
788  $connect = '';
789  if (!empty($stripeacc)) $connect = $stripeacc.'/';
790  $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
791  if ($status)
792  {
793  $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
794  }
795  $urtoswitchonstripe = ' <a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
796 
797  //dol_syslog("Error: This case is not supported", LOG_ERR);
798  $this->error = $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', $urtoswitchonstripe);
799  }
800  }
801 
802  if ($card)
803  {
804  $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
805  $sql .= " SET stripe_card_ref = '".$this->db->escape($card->id)."', card_type = '".$this->db->escape($card->brand)."',";
806  $sql .= " country_code = '".$this->db->escape($card->country)."',";
807  $sql .= " approved = ".($card->cvc_check == 'pass' ? 1 : 0);
808  $sql .= " WHERE rowid = ".$object->id;
809  $sql .= " AND type = 'card'";
810  $resql = $this->db->query($sql);
811  if (!$resql)
812  {
813  $this->error = $this->db->lasterror();
814  }
815  }
816  } catch (Exception $e)
817  {
818  $this->error = $e->getMessage();
819  dol_syslog($this->error, LOG_WARNING);
820  }
821  }
822  }
823  } else {
824  dol_print_error($this->db);
825  }
826 
827  return $card;
828  }
829 
846  public function createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status = 0, $usethirdpartyemailforreceiptemail = 0, $capture = true)
847  {
848  global $conf;
849 
850  $error = 0;
851 
852  if (empty($status)) $service = 'StripeTest';
853  else $service = 'StripeLive';
854 
855  $sql = "SELECT sa.key_account as key_account, sa.fk_soc, sa.entity";
856  $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
857  $sql .= " WHERE sa.key_account = '".$this->db->escape($customer)."'";
858  //$sql.= " AND sa.entity IN (".getEntity('societe').")";
859  $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
860 
861  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
862  $result = $this->db->query($sql);
863  if ($result) {
864  if ($this->db->num_rows($result)) {
865  $obj = $this->db->fetch_object($result);
866  $key = $obj->fk_soc;
867  } else {
868  $key = null;
869  }
870  } else {
871  $key = null;
872  }
873 
874  $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
875  if (!in_array($currency, $arrayzerounitcurrency)) $stripeamount = $amount * 100;
876  else $stripeamount = $amount;
877 
878  $societe = new Societe($this->db);
879  if ($key > 0) $societe->fetch($key);
880 
881  $description = "";
882  $ref = "";
883  if ($origin == 'order') {
884  $order = new Commande($this->db);
885  $order->fetch($item);
886  $ref = $order->ref;
887  $description = "ORD=".$ref.".CUS=".$societe->id.".PM=stripe";
888  } elseif ($origin == 'invoice') {
889  $invoice = new Facture($this->db);
890  $invoice->fetch($item);
891  $ref = $invoice->ref;
892  $description = "INV=".$ref.".CUS=".$societe->id.".PM=stripe";
893  }
894 
895  $ipaddress = getUserRemoteIP();
896 
897  $metadata = array(
898  "dol_id" => "".$item."",
899  "dol_type" => "".$origin."",
900  "dol_thirdparty_id" => "".$societe->id."",
901  'dol_thirdparty_name' => $societe->name,
902  'dol_version'=>DOL_VERSION,
903  'dol_entity'=>$conf->entity,
904  'ipaddress'=>$ipaddress
905  );
906  $return = new Stripe($this->db);
907  try {
908  // Force to use the correct API key
909  global $stripearrayofkeysbyenv;
910  \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
911 
912  if (empty($conf->stripeconnect->enabled)) // With a common Stripe account
913  {
914  if (preg_match('/pm_/i', $source))
915  {
916  $stripecard = $source;
917  $amountstripe = $stripeamount;
918  $FULLTAG = 'PFBO'; // Payment From Back Office
919  $stripe = $return;
920  $amounttopay = $amount;
921  $servicestatus = $status;
922 
923  dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
924  $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
925 
926  dol_syslog("* createPaymentStripe Create payment for customer ".$customer->id." on source card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
927 
928  // Create payment intent and charge payment (confirmnow = true)
929  $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
930 
931  $charge = new stdClass();
932  if ($paymentintent->status == 'succeeded')
933  {
934  $charge->status = 'ok';
935  } else {
936  $charge->status = 'failed';
937  $charge->failure_code = $stripe->code;
938  $charge->failure_message = $stripe->error;
939  $charge->failure_declinecode = $stripe->declinecode;
940  $stripefailurecode = $stripe->code;
941  $stripefailuremessage = $stripe->error;
942  $stripefailuredeclinecode = $stripe->declinecode;
943  }
944  } elseif (preg_match('/acct_/i', $source))
945  {
946  $charge = \Stripe\Charge::create(array(
947  "amount" => "$stripeamount",
948  "currency" => "$currency",
949  "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
950  "description" => "Stripe payment: ".$description,
951  "capture" => $capture,
952  "metadata" => $metadata,
953  "source" => "$source"
954  ));
955  } else {
956  $paymentarray = array(
957  "amount" => "$stripeamount",
958  "currency" => "$currency",
959  "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
960  "description" => "Stripe payment: ".$description,
961  "capture" => $capture,
962  "metadata" => $metadata,
963  "source" => "$source",
964  "customer" => "$customer"
965  );
966 
967  if ($societe->email && $usethirdpartyemailforreceiptemail)
968  {
969  $paymentarray["receipt_email"] = $societe->email;
970  }
971 
972  $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description"));
973  }
974  } else {
975  // With Stripe Connect
976  $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
977  if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
978  $fee = $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL;
979  } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
980  $fee = $conf->global->STRIPE_APPLICATION_FEE_MINIMAL;
981  }
982 
983  if (!in_array($currency, $arrayzerounitcurrency)) $stripefee = round($fee * 100);
984  else $stripefee = round($fee);
985 
986  $paymentarray = array(
987  "amount" => "$stripeamount",
988  "currency" => "$currency",
989  "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
990  "description" => "Stripe payment: ".$description,
991  "capture" => $capture,
992  "metadata" => $metadata,
993  "source" => "$source",
994  "customer" => "$customer"
995  );
996  if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0)
997  {
998  $paymentarray["application_fee_amount"] = $stripefee;
999  }
1000  if ($societe->email && $usethirdpartyemailforreceiptemail)
1001  {
1002  $paymentarray["receipt_email"] = $societe->email;
1003  }
1004 
1005  if (preg_match('/pm_/i', $source))
1006  {
1007  $stripecard = $source;
1008  $amountstripe = $stripeamount;
1009  $FULLTAG = 'PFBO'; // Payment From Back Office
1010  $stripe = $return;
1011  $amounttopay = $amount;
1012  $servicestatus = $status;
1013 
1014  dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
1015  $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
1016 
1017  dol_syslog("* createPaymentStripe Create payment on card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
1018 
1019  // Create payment intent and charge payment (confirmnow = true)
1020  $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
1021 
1022  $charge = new stdClass();
1023  if ($paymentintent->status == 'succeeded')
1024  {
1025  $charge->status = 'ok';
1026  $charge->id = $paymentintent->id;
1027  } else {
1028  $charge->status = 'failed';
1029  $charge->failure_code = $stripe->code;
1030  $charge->failure_message = $stripe->error;
1031  $charge->failure_declinecode = $stripe->declinecode;
1032  }
1033  } else {
1034  $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description", "stripe_account" => "$account"));
1035  }
1036  }
1037  if (isset($charge->id)) {}
1038 
1039  $return->statut = 'success';
1040  $return->id = $charge->id;
1041 
1042  if (preg_match('/pm_/i', $source))
1043  {
1044  $return->message = 'Payment retrieved by card status = '.$charge->status;
1045  } else {
1046  if ($charge->source->type == 'card') {
1047  $return->message = $charge->source->card->brand." ....".$charge->source->card->last4;
1048  } elseif ($charge->source->type == 'three_d_secure') {
1049  $stripe = new Stripe($this->db);
1050  $src = \Stripe\Source::retrieve("".$charge->source->three_d_secure->card."", array(
1051  "stripe_account" => $stripe->getStripeAccount($service)
1052  ));
1053  $return->message = $src->card->brand." ....".$src->card->last4;
1054  } else {
1055  $return->message = $charge->id;
1056  }
1057  }
1058  } catch (\Stripe\Error\Card $e) {
1059  include DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
1060  // Since it's a decline, \Stripe\Error\Card will be caught
1061  $body = $e->getJsonBody();
1062  $err = $body['error'];
1063 
1064  $return->statut = 'error';
1065  $return->id = $err['charge'];
1066  $return->type = $err['type'];
1067  $return->code = $err['code'];
1068  $return->message = $err['message'];
1069  $body = "Error: <br>".$return->id." ".$return->message." ";
1070  $subject = '[Alert] Payment error using Stripe';
1071  $cmailfile = new CMailFile($subject, $conf->global->ONLINE_PAYMENT_SENDEMAIL, $conf->global->MAIN_INFO_SOCIETE_MAIL, $body);
1072  $cmailfile->sendfile();
1073 
1074  $error++;
1075  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1076  } catch (\Stripe\Error\RateLimit $e) {
1077  // Too many requests made to the API too quickly
1078  $error++;
1079  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1080  } catch (\Stripe\Error\InvalidRequest $e) {
1081  // Invalid parameters were supplied to Stripe's API
1082  $error++;
1083  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1084  } catch (\Stripe\Error\Authentication $e) {
1085  // Authentication with Stripe's API failed
1086  // (maybe you changed API keys recently)
1087  $error++;
1088  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1089  } catch (\Stripe\Error\ApiConnection $e) {
1090  // Network communication with Stripe failed
1091  $error++;
1092  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1093  } catch (\Stripe\Error\Base $e) {
1094  // Display a very generic error to the user, and maybe send
1095  // yourself an email
1096  $error++;
1097  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1098  } catch (Exception $e) {
1099  // Something else happened, completely unrelated to Stripe
1100  $error++;
1101  dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1102  }
1103  return $return;
1104  }
1105 }
getUserRemoteIP()
Return the IP of remote user.
getStripeCustomerAccount($id, $status=0, $site_account= '')
getStripeCustomerAccount
getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail=0, $confirmnow=false)
Get the Stripe payment intent.
if(!empty($arrayfields['country.code_iso']['checked'])) print_liste_field_titre($arrayfields['country.code_iso']['label'] country if(!empty($arrayfields['typent.code']['checked'])) print_liste_field_titre($arrayfields['typent.code']['label'] typent code
Definition: list.php:566
dol_now($mode= 'auto')
Return date for now.
__construct($db)
Constructor.
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)
Get the Stripe payment intent.
Class for SocieteAccount.
Stripe class.
$conf db
API class for accounts.
Definition: inc.php:54
createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status=0, $usethirdpartyemailforreceiptemail=0, $capture=true)
Create charge.
Class to manage third parties objects (customers, suppliers, prospects...)
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)...
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,$sendto,$replyto,$message,$filepath,$mimetype,$filename,$cc,$ccc,$deliveryreceipt,$msgishtml,$errors_to,$css,$trackid,$moreinheader,$sendcontext,$replyto); $mailfile-&gt;sendfile();.
img_picto($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt= '', $morecss= '', $marginleftonlyshort=2)
Show picto whatever it&#39;s its name (generic function)
Class to manage customers orders.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename= '', $restricttologhandler= '', $logcontext=null)
Write log message into outputs.
Class for CompanyPaymentMode.
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 ...
if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) if(!empty($conf->don->enabled)&&$user->rights->don->lire) if(!empty($conf->tax->enabled)&&$user->rights->tax->charges->lire) if(!empty($conf->facture->enabled)&&!empty($conf->commande->enabled)&&$user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) $resql
Social contributions to pay.
Definition: index.php:1232
dol_print_error($db= '', $error= '', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc= 'right', $stringencoding= 'UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding &#39;...&#39; if string larger than length.
Class to manage invoices.
if(!defined('CSRFCHECK_WITH_TOKEN')) define('CSRFCHECK_WITH_TOKEN'
Draft customers invoices.
getPaymentMethodStripe($paymentmethod, $key= '', $status=0)
Get the Stripe payment method Object from its ID.
Parent class of all other business classes (invoices, contracts, proposals, orders, ...)
isInEEC($object)
Return if a country of an object is inside the EEC (European Economic Community)