dolibarr 20.0.0
stripe.class.php
1<?php
2/* Copyright (C) 2018-2021 Thibault FOUCART <support@ptibogxiv.net>
3 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20// Put here all includes required by your class file
21require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
22require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
23require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
24require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
25require_once DOL_DOCUMENT_ROOT.'/stripe/config.php'; // This set stripe global env
26
27
32class Stripe extends CommonObject
33{
37 public $rowid;
38
42 public $fk_soc;
43
47 public $fk_key;
48
52 public $id;
53
57 public $mode;
58
62 public $entity;
63
68 public $result;
69
73 public $type;
74
78 public $code;
79
83 public $declinecode;
84
88 public $message;
89
95 public function __construct($db)
96 {
97 $this->db = $db;
98 }
99
100
109 public function getStripeAccount($mode = 'StripeTest', $fk_soc = 0, $entity = -1)
110 {
111 global $conf;
112
113 $key = '';
114 if ($entity < 0) {
115 $entity = $conf->entity;
116 }
117
118 $sql = "SELECT tokenstring";
119 $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
120 $sql .= " WHERE service = '".$this->db->escape($mode)."'";
121 $sql .= " AND entity = ".((int) $entity);
122 if ($fk_soc > 0) {
123 $sql .= " AND fk_soc = ".((int) $fk_soc);
124 } else {
125 $sql .= " AND fk_soc IS NULL";
126 }
127 $sql .= " AND fk_user IS NULL AND fk_adherent IS NULL";
128
129 dol_syslog(get_class($this)."::getStripeAccount", LOG_DEBUG);
130
131 $result = $this->db->query($sql);
132 if ($result) {
133 if ($this->db->num_rows($result)) {
134 $obj = $this->db->fetch_object($result);
135 $tokenstring = $obj->tokenstring;
136
137 if ($tokenstring) {
138 $tmparray = json_decode($tokenstring);
139 $key = empty($tmparray->stripe_user_id) ? '' : $tmparray->stripe_user_id;
140 }
141 } else {
142 $tokenstring = '';
143 }
144 } else {
145 dol_print_error($this->db);
146 }
147
148 dol_syslog("No dedicated Stripe Connect account available for entity ".$conf->entity);
149
150 return $key;
151 }
152
161 public function getStripeCustomerAccount($id, $status = 0, $site_account = '')
162 {
163 include_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
164 $societeaccount = new SocieteAccount($this->db);
165 return $societeaccount->getCustomerAccount($id, 'stripe', $status, $site_account); // Get thirdparty cus_...
166 }
167
168
179 public function customerStripe(CommonObject $object, $key = '', $status = 0, $createifnotlinkedtostripe = 0)
180 {
181 global $conf, $user;
182
183 if (empty($object->id)) {
184 dol_syslog("customerStripe is called with the parameter object that is not loaded");
185 return null;
186 }
187
188 $customer = null;
189
190 // Force to use the correct API key
191 global $stripearrayofkeysbyenv;
192 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
193
194 $sql = "SELECT sa.key_account as key_account, sa.entity"; // key_account is cus_....
195 $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
196 $sql .= " WHERE sa.fk_soc = ".((int) $object->id);
197 $sql .= " AND sa.entity IN (".getEntity('societe').")";
198 $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
199 $sql .= " AND (sa.site_account IS NULL OR sa.site_account = '' OR sa.site_account = '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."')";
200 $sql .= " AND sa.key_account IS NOT NULL AND sa.key_account <> ''";
201
202 dol_syslog(get_class($this)."::customerStripe search stripe customer id for thirdparty id=".$object->id, LOG_DEBUG);
203 $resql = $this->db->query($sql);
204 if ($resql) {
205 $num = $this->db->num_rows($resql);
206 if ($num) {
207 $obj = $this->db->fetch_object($resql);
208 $tiers = $obj->key_account;
209
210 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']);
211
212 try {
213 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
214 //$customer = \Stripe\Customer::retrieve("$tiers");
215 $customer = \Stripe\Customer::retrieve(array('id' => "$tiers", 'expand[]' => 'sources'));
216 } else {
217 //$customer = \Stripe\Customer::retrieve("$tiers", array("stripe_account" => $key));
218 $customer = \Stripe\Customer::retrieve(array('id' => "$tiers", 'expand[]' => 'sources'), array("stripe_account" => $key));
219 }
220 } catch (Exception $e) {
221 // For example, 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.'
222 $this->error = $e->getMessage();
223 }
224 } elseif ($createifnotlinkedtostripe) {
225 $ipaddress = getUserRemoteIP();
226
227 $dataforcustomer = array(
228 "email" => $object->email,
229 "description" => $object->name,
230 "metadata" => array('dol_id' => $object->id, 'dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress)
231 );
232
233 $vatcleaned = $object->tva_intra ? $object->tva_intra : null;
234
235 /*
236 $taxinfo = array('type'=>'vat');
237 if ($vatcleaned)
238 {
239 $taxinfo["tax_id"] = $vatcleaned;
240 }
241 // We force data to "null" if not defined as expected by Stripe
242 if (empty($vatcleaned)) $taxinfo=null;
243 $dataforcustomer["tax_info"] = $taxinfo;
244 */
245
246 //$a = \Stripe\Stripe::getApiKey();
247 //var_dump($a);var_dump($key);exit;
248 try {
249 // Force to use the correct API key
250 global $stripearrayofkeysbyenv;
251 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
252
253 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
254 $customer = \Stripe\Customer::create($dataforcustomer);
255 } else {
256 $customer = \Stripe\Customer::create($dataforcustomer, array("stripe_account" => $key));
257 }
258
259 // Create the VAT record in Stripe
260 if (getDolGlobalString('STRIPE_SAVE_TAX_IDS')) { // We setup to save Tax info on Stripe side. Warning: This may result in error when saving customer
261 if (!empty($vatcleaned)) {
262 $isineec = isInEEC($object);
263 if ($object->country_code && $isineec) {
264 //$taxids = $customer->allTaxIds($customer->id);
265 $customer->createTaxId($customer->id, array('type' => 'eu_vat', 'value' => $vatcleaned));
266 }
267 }
268 }
269
270 // Create customer in Dolibarr
271 $sql = "INSERT INTO ".MAIN_DB_PREFIX."societe_account (fk_soc, login, key_account, site, site_account, status, entity, date_creation, fk_user_creat)";
272 $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).")";
273 $resql = $this->db->query($sql);
274 if (!$resql) {
275 $this->error = $this->db->lasterror();
276 }
277 } catch (Exception $e) {
278 $this->error = $e->getMessage();
279 }
280 }
281 } else {
282 dol_print_error($this->db);
283 }
284
285 return $customer;
286 }
287
296 public function getPaymentMethodStripe($paymentmethod, $key = '', $status = 0)
297 {
298 $stripepaymentmethod = null;
299
300 try {
301 // Force to use the correct API key
302 global $stripearrayofkeysbyenv;
303 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
304 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
305 $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id);
306 } else {
307 $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id, array("stripe_account" => $key));
308 }
309 } catch (Exception $e) {
310 $this->error = $e->getMessage();
311 }
312
313 return $stripepaymentmethod;
314 }
315
324 public function getSelectedReader($reader, $key = '', $status = 0)
325 {
326 $selectedreader = null;
327
328 try {
329 // Force to use the correct API key
330 global $stripearrayofkeysbyenv;
331 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
332 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
333 $selectedreader = \Stripe\Terminal\Reader::retrieve((string) $reader);
334 } else {
335 $stripepaymentmethod = \Stripe\Terminal\Reader::retrieve((string) $reader, array("stripe_account" => $key));
336 }
337 } catch (Exception $e) {
338 $this->error = $e->getMessage();
339 }
340
341 return $selectedreader;
342 }
343
370 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)
371 {
372 global $conf, $user;
373
374 dol_syslog(get_class($this)."::getPaymentIntent description=".$description, LOG_INFO, 1);
375
376 $error = 0;
377
378 if (empty($status)) {
379 $service = 'StripeTest';
380 } else {
381 $service = 'StripeLive';
382 }
383
384 $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
385 if (!in_array($currency_code, $arrayzerounitcurrency)) {
386 $stripeamount = $amount * 100;
387 } else {
388 $stripeamount = $amount;
389 }
390
391 $fee = 0;
392 if (getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT")) {
393 $fee = $amount * ((float) getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT", '0') / 100) + (float) getDolGlobalString("STRIPE_APPLICATION_FEE", '0');
394 }
395 if ($fee >= (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') && (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') > (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
396 $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0');
397 } elseif ($fee < (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
398 $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0');
399 }
400 if (!in_array($currency_code, $arrayzerounitcurrency)) {
401 $stripefee = round($fee * 100);
402 } else {
403 $stripefee = round($fee);
404 }
405
406 $paymentintent = null;
407
408 if (is_object($object) && getDolGlobalInt('STRIPE_REUSE_EXISTING_INTENT_IF_FOUND') && !getDolGlobalInt('STRIPE_CARD_PRESENT')) {
409 // Warning. If a payment was tried and failed, a payment intent was created.
410 // 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.
411 // 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
412 // automatically return the existing payment intent if idempotency is provided when we try to create the new one.
413 // 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)
414
415 $sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
416 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
417 $sql .= " WHERE pi.fk_facture = ".((int) $object->id);
418 $sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
419 $sql .= " AND pi.entity IN (".getEntity('societe').")";
420 $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
421
422 dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);
423 $resql = $this->db->query($sql);
424 if ($resql) {
425 $num = $this->db->num_rows($resql);
426 if ($num) {
427 $obj = $this->db->fetch_object($resql);
428 $intent = $obj->ext_payment_id;
429
430 dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record");
431
432 // Force to use the correct API key
433 global $stripearrayofkeysbyenv;
434 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
435
436 try {
437 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
438 $paymentintent = \Stripe\PaymentIntent::retrieve($intent);
439 } else {
440 $paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
441 }
442 } catch (Exception $e) {
443 $error++;
444 $this->error = $e->getMessage();
445 }
446 }
447 }
448 }
449
450 if (empty($paymentintent)) {
451 // Try to create intent. See https://stripe.com/docs/api/payment_intents/create
452 $ipaddress = getUserRemoteIP();
453 $metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress, 'dol_noidempotency' => (int) $noidempotency_key);
454 if (is_object($object)) {
455 $metadata['dol_type'] = $object->element;
456 $metadata['dol_id'] = $object->id;
457 if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
458 $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
459 }
460 }
461
462 // list of payment method types
463 $paymentmethodtypes = array("card");
464 $descriptor = dol_trunc($tag, 10, 'right', 'UTF-8', 1);
465 if (getDolGlobalInt('STRIPE_SEPA_DIRECT_DEBIT')) {
466 $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
467 //$descriptor = preg_replace('/ref=[^:=]+/', '', $descriptor); // Clean ref
468 }
469 if (getDolGlobalInt('STRIPE_KLARNA')) {
470 $paymentmethodtypes[] = "klarna";
471 }
472 if (getDolGlobalInt('STRIPE_BANCONTACT')) {
473 $paymentmethodtypes[] = "bancontact";
474 }
475 if (getDolGlobalInt('STRIPE_IDEAL')) {
476 $paymentmethodtypes[] = "ideal";
477 }
478 if (getDolGlobalInt('STRIPE_GIROPAY')) {
479 $paymentmethodtypes[] = "giropay";
480 }
481 if (getDolGlobalInt('STRIPE_SOFORT')) {
482 $paymentmethodtypes[] = "sofort";
483 }
484 if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
485 $paymentmethodtypes = array("card_present");
486 }
487
488 global $dolibarr_main_url_root;
489
490 $descriptioninpaymentintent = $description;
491
492 $dataforintent = array(
493 "confirm" => $confirmnow, // try to confirm immediately after create (if conditions are ok)
494 "confirmation_method" => $mode,
495 "amount" => $stripeamount,
496 "currency" => $currency_code,
497 "payment_method_types" => $paymentmethodtypes, // When payment_method_types is set, return_url is not required but payment mode can't be managed from dashboard
498 /*
499 'return_url' => $dolibarr_main_url_root.'/public/payment/paymentok.php',
500 'automatic_payment_methods' => array(
501 'enabled' => true,
502 'allow_redirects' => 'never',
503 ),
504 */
505 "description" => $descriptioninpaymentintent,
506 //"save_payment_method" => true,
507 "setup_future_usage" => "on_session",
508 "metadata" => $metadata
509 );
510 if ($descriptor) {
511 $dataforintent["statement_descriptor_suffix"] = $descriptor; // For card payment, 22 chars that appears on bank receipt (prefix into stripe setup + this suffix)
512 $dataforintent["statement_descriptor"] = $descriptor; // For SEPA, it will take only statement_descriptor, not statement_descriptor_suffix
513 }
514 if (!is_null($customer)) {
515 $dataforintent["customer"] = $customer;
516 }
517 // payment_method =
518 // payment_method_types = array('card')
519 //var_dump($dataforintent);
520 if ($off_session) {
521 unset($dataforintent['setup_future_usage']);
522 // We can't use both "setup_future_usage" = "off_session" and "off_session" = true.
523 // Because $off_session parameter is dedicated to create paymentintent off_line (and not future payment), we need to use "off_session" = true.
524 //$dataforintent["setup_future_usage"] = "off_session";
525 $dataforintent["off_session"] = true;
526 }
527 if (getDolGlobalInt('STRIPE_GIROPAY')) {
528 unset($dataforintent['setup_future_usage']);
529 }
530 if (getDolGlobalInt('STRIPE_KLARNA')) {
531 unset($dataforintent['setup_future_usage']);
532 }
533 if (getDolGlobalInt('STRIPE_CARD_PRESENT') && $mode == 'terminal') {
534 unset($dataforintent['setup_future_usage']);
535 $dataforintent["capture_method"] = "manual";
536 $dataforintent["confirmation_method"] = "manual";
537 }
538 if (!is_null($payment_method)) {
539 $dataforintent["payment_method"] = $payment_method;
540 $description .= ' - '.$payment_method;
541 }
542
543 if ($conf->entity != getDolGlobalInt('STRIPECONNECT_PRINCIPAL') && $stripefee > 0) {
544 $dataforintent["application_fee_amount"] = $stripefee;
545 }
546 if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
547 $dataforintent["receipt_email"] = $object->thirdparty->email;
548 }
549
550 try {
551 // Force to use the correct API key
552 global $stripearrayofkeysbyenv;
553 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
554
555 $arrayofoptions = array();
556 if (empty($noidempotency_key)) {
557 $arrayofoptions["idempotency_key"] = $descriptioninpaymentintent;
558 }
559 // 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.
560 if (!empty($key)) { // If the Stripe connect account not set, we use common API usage
561 $arrayofoptions["stripe_account"] = $key;
562 }
563
564 dol_syslog(get_class($this)."::getPaymentIntent ".$stripearrayofkeysbyenv[$status]['publishable_key'], LOG_DEBUG);
565 dol_syslog(get_class($this)."::getPaymentIntent dataforintent to create paymentintent = ".var_export($dataforintent, true));
566
567 $paymentintent = \Stripe\PaymentIntent::create($dataforintent, $arrayofoptions);
568
569 // Store the payment intent
570 if (is_object($object)) {
571 $paymentintentalreadyexists = 0;
572
573 if ($did > 0) {
574 // If a payment request line provided, we do not need to recreate one, we just update it
575 dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
576
577 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
578 $sql .= " ext_payment_site = '".$this->db->escape($service)."',";
579 $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
580 $sql .= " WHERE rowid = ".((int) $did);
581
582 $resql = $this->db->query($sql);
583 if ($resql) {
584 $paymentintentalreadyexists++;
585 } else {
586 $error++;
587 dol_print_error($this->db);
588 }
589 } else {
590 // Check that payment intent $paymentintent->id is not already recorded.
591 dol_syslog(get_class($this)."::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
592
593 $sql = "SELECT pi.rowid";
594 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
595 $sql .= " WHERE pi.entity IN (".getEntity('societe').")";
596 $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
597 $sql .= " AND pi.ext_payment_id = '".$this->db->escape($paymentintent->id)."'";
598
599 $resql = $this->db->query($sql);
600 if ($resql) {
601 $num = $this->db->num_rows($resql);
602 if ($num) {
603 $obj = $this->db->fetch_object($resql);
604 if ($obj) {
605 $paymentintentalreadyexists++;
606 }
607 }
608 } else {
609 $error++;
610 dol_print_error($this->db);
611 }
612 }
613
614 // If not, we create it.
615 if (!$error && !$paymentintentalreadyexists) {
616 $now = dol_now();
617 $sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site, amount)";
618 $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).")";
619 $resql = $this->db->query($sql);
620 if (!$resql) {
621 $error++;
622 $this->error = $this->db->lasterror();
623 dol_syslog(get_class($this)."::PaymentIntent failed to insert paymentintent with id=".$paymentintent->id." into database.", LOG_ERR);
624 }
625 }
626 } else {
627 $_SESSION["stripe_payment_intent"] = $paymentintent;
628 }
629 } catch (Stripe\Exception\CardException $e) {
630 $error++;
631 $this->error = $e->getMessage();
632 $this->code = $e->getStripeCode();
633 $this->declinecode = $e->getDeclineCode();
634 } catch (Exception $e) {
635 //var_dump($dataforintent);
636 //var_dump($description);
637 //var_dump($key);
638 //var_dump($paymentintent);
639 //var_dump($e->getMessage());
640 //var_dump($e);
641 $error++;
642 $this->error = $e->getMessage();
643 $this->code = '';
644 $this->declinecode = '';
645 }
646 }
647
648 dol_syslog(get_class($this)."::getPaymentIntent return error=".$error." this->error=".$this->error, LOG_INFO, -1);
649
650 if (!$error) {
651 return $paymentintent;
652 } else {
653 return null;
654 }
655 }
656
675 public function getSetupIntent($description, $object, $customer, $key, $status, $usethirdpartyemailforreceiptemail = 0, $confirmnow = false)
676 {
677 global $conf;
678
679 $noidempotency_key = 1;
680
681 dol_syslog("getSetupIntent description=".$description.' confirmnow='.json_encode($confirmnow), LOG_INFO, 1);
682
683 $error = 0;
684
685 if (empty($status)) {
686 $service = 'StripeTest';
687 } else {
688 $service = 'StripeLive';
689 }
690
691 $setupintent = null;
692
693 if (empty($setupintent)) {
694 $ipaddress = getUserRemoteIP();
695 $metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress, 'dol_noidempotency' => (int) $noidempotency_key);
696 if (is_object($object)) {
697 $metadata['dol_type'] = $object->element;
698 $metadata['dol_id'] = $object->id;
699 if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
700 $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
701 }
702 }
703
704 // list of payment method types
705 $paymentmethodtypes = array("card");
706 if (getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
707 $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
708 }
709 if (getDolGlobalString('STRIPE_BANCONTACT')) {
710 $paymentmethodtypes[] = "bancontact";
711 }
712 if (getDolGlobalString('STRIPE_IDEAL')) {
713 $paymentmethodtypes[] = "ideal";
714 }
715 // Giropay not possible for setup intent
716 if (getDolGlobalString('STRIPE_SOFORT')) {
717 $paymentmethodtypes[] = "sofort";
718 }
719
720 global $dolibarr_main_url_root;
721
722 $descriptioninsetupintent = $description;
723
724 $dataforintent = array(
725 "confirm" => $confirmnow, // Do not confirm immediately during creation of intent
726 "payment_method_types" => $paymentmethodtypes, // When payment_method_types is set, return_url is not required but payment mode can't be managed from dashboard
727 /*
728 'return_url' => $dolibarr_main_url_root.'/public/payment/paymentok.php',
729 'automatic_payment_methods' => array(
730 'enabled' => true,
731 'allow_redirects' => 'never',
732 ),
733 */
734 "usage" => "off_session",
735 "metadata" => $metadata
736 );
737 if (!is_null($customer)) {
738 $dataforintent["customer"] = $customer;
739 }
740 if (!is_null($description)) {
741 $dataforintent["description"] = $descriptioninsetupintent;
742 }
743 // payment_method =
744 // payment_method_types = array('card')
745 //var_dump($dataforintent);
746
747 if ($usethirdpartyemailforreceiptemail && is_object($object) && $object->thirdparty->email) {
748 $dataforintent["receipt_email"] = $object->thirdparty->email;
749 }
750
751 try {
752 // Force to use the correct API key
753 global $stripearrayofkeysbyenv;
754 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
755
756 dol_syslog(get_class($this)."::getSetupIntent ".$stripearrayofkeysbyenv[$status]['publishable_key'], LOG_DEBUG);
757 dol_syslog(get_class($this)."::getSetupIntent dataforintent to create setupintent = ".var_export($dataforintent, true));
758
759 // Note: If all data for payment intent are same than a previous one, even if we use 'create', Stripe will return ID of the old existing payment intent.
760 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
761 //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description"));
762 $setupintent = \Stripe\SetupIntent::create($dataforintent, array());
763 } else {
764 //$setupintent = \Stripe\SetupIntent::create($dataforintent, array("idempotency_key" => "$description", "stripe_account" => $key));
765 $setupintent = \Stripe\SetupIntent::create($dataforintent, array("stripe_account" => $key));
766 }
767 //var_dump($setupintent->id);
768
769 // Store the setup intent
770 /*if (is_object($object))
771 {
772 $setupintentalreadyexists = 0;
773 // Check that payment intent $setupintent->id is not already recorded.
774 $sql = "SELECT pi.rowid";
775 $sql.= " FROM " . MAIN_DB_PREFIX . "prelevement_demande as pi";
776 $sql.= " WHERE pi.entity IN (".getEntity('societe').")";
777 $sql.= " AND pi.ext_payment_site = '" . $this->db->escape($service) . "'";
778 $sql.= " AND pi.ext_payment_id = '".$this->db->escape($setupintent->id)."'";
779
780 dol_syslog(get_class($this) . "::getPaymentIntent search if payment intent already in prelevement_demande", LOG_DEBUG);
781 $resql = $this->db->query($sql);
782 if ($resql) {
783 $num = $this->db->num_rows($resql);
784 if ($num)
785 {
786 $obj = $this->db->fetch_object($resql);
787 if ($obj) $setupintentalreadyexists++;
788 }
789 }
790 else dol_print_error($this->db);
791
792 // If not, we create it.
793 if (! $setupintentalreadyexists)
794 {
795 $now=dol_now();
796 $sql = "INSERT INTO " . MAIN_DB_PREFIX . "prelevement_demande (date_demande, fk_user_demande, ext_payment_id, fk_facture, sourcetype, entity, ext_payment_site)";
797 $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).")";
798 $resql = $this->db->query($sql);
799 if (! $resql)
800 {
801 $error++;
802 $this->error = $this->db->lasterror();
803 dol_syslog(get_class($this) . "::PaymentIntent failed to insert paymentintent with id=".$setupintent->id." into database.");
804 }
805 }
806 }
807 else
808 {
809 $_SESSION["stripe_setup_intent"] = $setupintent;
810 }*/
811 } catch (Exception $e) {
812 //var_dump($dataforintent);
813 //var_dump($description);
814 //var_dump($key);
815 //var_dump($setupintent);
816 //var_dump($e->getMessage());
817 $error++;
818 $this->error = $e->getMessage();
819 }
820 }
821
822 if (!$error) {
823 dol_syslog("getSetupIntent ".(is_object($setupintent) ? $setupintent->id : ''), LOG_INFO, -1);
824 return $setupintent;
825 } else {
826 dol_syslog("getSetupIntent return error=".$error, LOG_INFO, -1);
827 return null;
828 }
829 }
830
831
842 public function cardStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
843 {
844 global $conf, $user, $langs;
845
846 $card = null;
847
848 $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_....
849 $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
850 $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
851 $sql .= " AND sa.type = 'card'";
852
853 dol_syslog(get_class($this)."::cardStripe search stripe card id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
854 $resql = $this->db->query($sql);
855 if ($resql) {
856 $num = $this->db->num_rows($resql);
857 if ($num) {
858 $obj = $this->db->fetch_object($resql);
859 $cardref = $obj->stripe_card_ref;
860 dol_syslog(get_class($this)."::cardStripe cardref=".$cardref);
861 if ($cardref) {
862 try {
863 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
864 if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
865 $card = $cu->sources->retrieve($cardref);
866 } else {
867 $card = \Stripe\PaymentMethod::retrieve($cardref);
868 }
869 } else {
870 if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
871 //$card = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
872 $card = $cu->sources->retrieve($cardref);
873 } else {
874 //$card = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
875 $card = \Stripe\PaymentMethod::retrieve($cardref);
876 }
877 }
878 } catch (Exception $e) {
879 $this->error = $e->getMessage();
880 dol_syslog($this->error, LOG_WARNING);
881 }
882 } elseif ($createifnotlinkedtostripe) {
883 // Deprecated with new Stripe API and SCA. We should not use anymore this part of code now.
884 $exp_date_month = $obj->exp_date_month;
885 $exp_date_year = $obj->exp_date_year;
886 $number = $obj->number;
887 $cvc = $obj->cvn; // cvn in database, cvc for stripe
888 $cardholdername = $obj->proprio;
889
890 $ipaddress = getUserRemoteIP();
891
892 $dataforcard = array(
893 "source" => array(
894 'object' => 'card',
895 'exp_month' => $exp_date_month,
896 'exp_year' => $exp_date_year,
897 'number' => $number,
898 'cvc' => $cvc,
899 'name' => $cardholdername
900 ),
901 "metadata" => array(
902 'dol_type' => $object->element,
903 'dol_id' => $object->id,
904 'dol_version' => DOL_VERSION,
905 'dol_entity' => $conf->entity,
906 'ipaddress' => $ipaddress
907 )
908 );
909
910 //$a = \Stripe\Stripe::getApiKey();
911 //var_dump($a);
912 //var_dump($stripeacc);exit;
913 try {
914 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
915 if (!getDolGlobalString('STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION')) {
916 dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
917 $card = $cu->sources->create($dataforcard);
918 if (!$card) {
919 $this->error = 'Creation of card on Stripe has failed';
920 }
921 } else {
922 $connect = '';
923 if (!empty($stripeacc)) {
924 $connect = $stripeacc.'/';
925 }
926 $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
927 if ($status) {
928 $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
929 }
930 $urtoswitchonstripe = '<a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
931
932 //dol_syslog("Error: This case is not supported", LOG_ERR);
933 $this->error = str_replace('{s1}', $urtoswitchonstripe, $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', '{s1}'));
934 }
935 } else {
936 if (!getDolGlobalString('STRIPE_USE_INTENT_WITH_AUTOMATIC_CONFIRMATION')) {
937 dol_syslog("Try to create card with dataforcard = ".json_encode($dataforcard));
938 $card = $cu->sources->create($dataforcard, array("stripe_account" => $stripeacc));
939 if (!$card) {
940 $this->error = 'Creation of card on Stripe has failed';
941 }
942 } else {
943 $connect = '';
944 if (!empty($stripeacc)) {
945 $connect = $stripeacc.'/';
946 }
947 $url = 'https://dashboard.stripe.com/'.$connect.'test/customers/'.$cu->id;
948 if ($status) {
949 $url = 'https://dashboard.stripe.com/'.$connect.'customers/'.$cu->id;
950 }
951 $urtoswitchonstripe = '<a href="'.$url.'" target="_stripe">'.img_picto($langs->trans('ShowInStripe'), 'globe').'</a>';
952
953 //dol_syslog("Error: This case is not supported", LOG_ERR);
954 $this->error = str_replace('{s1}', $urtoswitchonstripe, $langs->trans('CreationOfPaymentModeMustBeDoneFromStripeInterface', '{s1}'));
955 }
956 }
957
958 if ($card) {
959 $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
960 $sql .= " SET stripe_card_ref = '".$this->db->escape($card->id)."', card_type = '".$this->db->escape($card->brand)."',";
961 $sql .= " country_code = '".$this->db->escape($card->country)."',";
962 $sql .= " approved = ".($card->cvc_check == 'pass' ? 1 : 0);
963 $sql .= " WHERE rowid = ".((int) $object->id);
964 $sql .= " AND type = 'card'";
965 $resql = $this->db->query($sql);
966 if (!$resql) {
967 $this->error = $this->db->lasterror();
968 }
969 }
970 } catch (Exception $e) {
971 $this->error = $e->getMessage();
972 dol_syslog($this->error, LOG_WARNING);
973 }
974 }
975 }
976 } else {
977 dol_print_error($this->db);
978 }
979
980 return $card;
981 }
982
983
994 public function sepaStripe($cu, CompanyPaymentMode $object, $stripeacc = '', $status = 0, $createifnotlinkedtostripe = 0)
995 {
996 global $conf;
997 $sepa = null;
998
999 $sql = "SELECT sa.stripe_card_ref, sa.proprio, sa.iban_prefix as iban, sa.rum"; // stripe_card_ref is 'src_...' for Stripe SEPA
1000 $sql .= " FROM ".MAIN_DB_PREFIX."societe_rib as sa";
1001 $sql .= " WHERE sa.rowid = ".((int) $object->id); // We get record from ID, no need for filter on entity
1002 $sql .= " AND sa.type = 'ban'"; //type ban to get normal bank account of customer (prelevement)
1003
1004 $soc = new Societe($this->db);
1005 $soc->fetch($object->fk_soc);
1006
1007 dol_syslog(get_class($this)."::sepaStripe search stripe ban id for paymentmode id=".$object->id.", stripeacc=".$stripeacc.", status=".$status.", createifnotlinkedtostripe=".$createifnotlinkedtostripe, LOG_DEBUG);
1008 $resql = $this->db->query($sql);
1009 if ($resql) {
1010 $num = $this->db->num_rows($resql);
1011 if ($num) {
1012 $obj = $this->db->fetch_object($resql);
1013 $cardref = $obj->stripe_card_ref;
1014 dol_syslog(get_class($this)."::sepaStripe paymentmode=".$cardref);
1015 if ($cardref) {
1016 try {
1017 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1018 if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
1019 $sepa = $cu->sources->retrieve($cardref);
1020 } else {
1021 $sepa = \Stripe\PaymentMethod::retrieve($cardref);
1022 }
1023 } else {
1024 if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
1025 //$sepa = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
1026 $sepa = $cu->sources->retrieve($cardref);
1027 } else {
1028 //$sepa = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
1029 $sepa = \Stripe\PaymentMethod::retrieve($cardref);
1030 }
1031 }
1032 } catch (Exception $e) {
1033 $this->error = $e->getMessage();
1034 dol_syslog($this->error, LOG_WARNING);
1035 }
1036 } elseif ($createifnotlinkedtostripe) {
1037 $iban = $obj->iban;
1038 $ipaddress = getUserRemoteIP();
1039 $metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress);
1040 if (is_object($object)) {
1041 $metadata['dol_type'] = $object->element;
1042 $metadata['dol_id'] = $object->id;
1043 $metadata['dol_thirdparty_id'] = $soc->id;
1044 }
1045
1046 $description = 'SEPA for IBAN '.$iban;
1047
1048 $dataforcard = array(
1049 'type' => 'sepa_debit',
1050 "sepa_debit" => array('iban' => $iban),
1051 'billing_details' => array(
1052 'name' => $soc->name,
1053 'email' => !empty($soc->email) ? $soc->email : "",
1054 ),
1055 "metadata" => $metadata
1056 );
1057 // Complete owner name
1058 if (!empty($soc->town)) {
1059 $dataforcard['billing_details']['address']['city'] = $soc->town;
1060 }
1061 if (!empty($soc->country_code)) {
1062 $dataforcard['billing_details']['address']['country'] = $soc->country_code;
1063 }
1064 if (!empty($soc->address)) {
1065 $dataforcard['billing_details']['address']['line1'] = $soc->address;
1066 }
1067 if (!empty($soc->zip)) {
1068 $dataforcard['billing_details']['address']['postal_code'] = $soc->zip;
1069 }
1070 if (!empty($soc->state)) {
1071 $dataforcard['billing_details']['address']['state'] = $soc->state;
1072 }
1073
1074 //$a = \Stripe\Stripe::getApiKey();
1075 //var_dump($a);var_dump($stripeacc);exit;
1076 try {
1077 dol_syslog("Try to create sepa_debit");
1078
1079 $service = 'StripeTest';
1080 $servicestatus = 0;
1081 if (getDolGlobalString('STRIPE_LIVE') && !GETPOST('forcesandbox', 'alpha')) {
1082 $service = 'StripeLive';
1083 $servicestatus = 1;
1084 }
1085 // Force to use the correct API key
1086 global $stripearrayofkeysbyenv;
1087 $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];
1088
1089 dol_syslog("Try to create sepa_debit with data = ".json_encode($dataforcard));
1090
1091 $s = new \Stripe\StripeClient($stripeacc);
1092
1093 //var_dump($dataforcard);exit;
1094
1095 $sepa = $s->paymentMethods->create($dataforcard);
1096 if (!$sepa) {
1097 $this->error = 'Creation of payment method sepa_debit on Stripe has failed';
1098 } else {
1099 // link customer and src
1100 //$cs = $this->getSetupIntent($description, $soc, $cu, '', $status);
1101 $dataforintent = array(0 => ['description' => $description, 'payment_method_types' => ['sepa_debit'], 'customer' => $cu->id, 'payment_method' => $sepa->id], 'metadata' => $metadata);
1102
1103 $cs = $s->setupIntents->create($dataforintent);
1104 //$cs = $s->setupIntents->update($cs->id, ['payment_method' => $sepa->id]);
1105 $cs = $s->setupIntents->confirm($cs->id, ['mandate_data' => ['customer_acceptance' => ['type' => 'offline']]]);
1106 // note: $cs->mandate contains ID of mandate on Stripe side
1107
1108 if (!$cs) {
1109 $this->error = 'Link SEPA <-> Customer failed';
1110 } else {
1111 dol_syslog("Update the payment mode of the customer");
1112
1113 // print json_encode($sepa);
1114
1115 // Save the Stripe payment mode ID into the Dolibarr database
1116 $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
1117 $sql .= " SET stripe_card_ref = '".$this->db->escape($sepa->id)."',";
1118 $sql .= " card_type = 'sepa_debit',";
1119 $sql .= " stripe_account= '" . $this->db->escape($cu->id . "@" . $stripeacc) . "',";
1120 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1121 if (!empty($cs->mandate)) {
1122 $mandateservice = new \Stripe\Mandate($stripeacc);
1123 $mandate = $mandateservice->retrieve($cs->mandate);
1124 if (is_object($mandate) && is_object($mandate->payment_method_details) && is_object($mandate->payment_method_details->sepa_debit)) {
1125 $refmandate = $mandate->payment_method_details->sepa_debit->reference;
1126 //$urlmandate = $mandate->payment_method_details->sepa_debit->url;
1127 $sql .= ", rum = '".$this->db->escape($refmandate)."'";
1128 }
1129 $sql .= ", comment = '".$this->db->escape($cs->mandate)."'";
1130 $sql .= ", date_rum = '".$this->db->idate(dol_now())."'";
1131 }
1132 $sql .= " WHERE rowid = ".((int) $object->id);
1133 $sql .= " AND type = 'ban'";
1134 $resql = $this->db->query($sql);
1135 if (!$resql) {
1136 $this->error = $this->db->lasterror();
1137 }
1138 }
1139 }
1140 } catch (Exception $e) {
1141 $sepa = null;
1142 $this->error = 'Stripe error: '.$e->getMessage().'. Check the BAN information.';
1143 dol_syslog($this->error, LOG_WARNING); // Error from Stripe, so a warning on Dolibarr
1144 }
1145 }
1146 }
1147 } else {
1148 dol_print_error($this->db);
1149 }
1150
1151 return $sepa;
1152 }
1153
1154
1172 public function createPaymentStripe($amount, $currency, $origin, $item, $source, $customer, $account, $status = 0, $usethirdpartyemailforreceiptemail = 0, $capture = true)
1173 {
1174 global $conf;
1175
1176 $error = 0;
1177
1178 if (empty($status)) {
1179 $service = 'StripeTest';
1180 } else {
1181 $service = 'StripeLive';
1182 }
1183
1184 $sql = "SELECT sa.key_account as key_account, sa.fk_soc, sa.entity";
1185 $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
1186 $sql .= " WHERE sa.key_account = '".$this->db->escape($customer)."'";
1187 //$sql.= " AND sa.entity IN (".getEntity('societe').")";
1188 $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
1189
1190 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1191 $result = $this->db->query($sql);
1192 if ($result) {
1193 if ($this->db->num_rows($result)) {
1194 $obj = $this->db->fetch_object($result);
1195 $key = $obj->fk_soc;
1196 } else {
1197 $key = null;
1198 }
1199 } else {
1200 $key = null;
1201 }
1202
1203 $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
1204 if (!in_array($currency, $arrayzerounitcurrency)) {
1205 $stripeamount = $amount * 100;
1206 } else {
1207 $stripeamount = $amount;
1208 }
1209
1210 $societe = new Societe($this->db);
1211 if ($key > 0) {
1212 $societe->fetch($key);
1213 }
1214
1215 $description = "";
1216 $ref = "";
1217 if ($origin == 'order') {
1218 $order = new Commande($this->db);
1219 $order->fetch($item);
1220 $ref = $order->ref;
1221 $description = "ORD=".$ref.".CUS=".$societe->id.".PM=stripe";
1222 } elseif ($origin == 'invoice') {
1223 $invoice = new Facture($this->db);
1224 $invoice->fetch($item);
1225 $ref = $invoice->ref;
1226 $description = "INV=".$ref.".CUS=".$societe->id.".PM=stripe";
1227 }
1228
1229 $ipaddress = getUserRemoteIP();
1230
1231 $metadata = array(
1232 "dol_id" => (string) $item,
1233 "dol_type" => (string) $origin,
1234 "dol_thirdparty_id" => (string) $societe->id,
1235 'dol_thirdparty_name' => $societe->name,
1236 'dol_version' => DOL_VERSION,
1237 'dol_entity' => $conf->entity,
1238 'ipaddress' => $ipaddress
1239 );
1240 $return = new Stripe($this->db);
1241 try {
1242 // Force to use the correct API key
1243 global $stripearrayofkeysbyenv;
1244 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
1245
1246 if (empty($conf->stripeconnect->enabled)) { // With a common Stripe account
1247 if (preg_match('/pm_/i', $source)) {
1248 $stripecard = $source;
1249 $amountstripe = $stripeamount;
1250 $FULLTAG = 'PFBO'; // Payment From Back Office
1251 $stripe = $return;
1252 $amounttopay = $amount;
1253 $servicestatus = $status;
1254
1255 dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
1256 $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
1257
1258 dol_syslog("* createPaymentStripe Create payment for customer ".$customer->id." on source card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
1259
1260 // Create payment intent and charge payment (confirmnow = true)
1261 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
1262
1263 $charge = new stdClass();
1264 if ($paymentintent->status == 'succeeded') {
1265 $charge->status = 'ok';
1266 } else {
1267 $charge->status = 'failed';
1268 $charge->failure_code = $stripe->code;
1269 $charge->failure_message = $stripe->error;
1270 $charge->failure_declinecode = $stripe->declinecode;
1271 $stripefailurecode = $stripe->code;
1272 $stripefailuremessage = $stripe->error;
1273 $stripefailuredeclinecode = $stripe->declinecode;
1274 }
1275 } elseif (preg_match('/acct_/i', $source)) {
1276 $charge = \Stripe\Charge::create(array(
1277 "amount" => "$stripeamount",
1278 "currency" => "$currency",
1279 "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
1280 "description" => "Stripe payment: ".$description,
1281 "capture" => $capture,
1282 "metadata" => $metadata,
1283 "source" => "$source"
1284 ));
1285 } else {
1286 $paymentarray = array(
1287 "amount" => "$stripeamount",
1288 "currency" => "$currency",
1289 "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
1290 "description" => "Stripe payment: ".$description,
1291 "capture" => $capture,
1292 "metadata" => $metadata,
1293 "source" => "$source",
1294 "customer" => "$customer"
1295 );
1296
1297 if ($societe->email && $usethirdpartyemailforreceiptemail) {
1298 $paymentarray["receipt_email"] = $societe->email;
1299 }
1300
1301 $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description"));
1302 }
1303 } else {
1304 // With Stripe Connect
1305 $fee = $amount * ($conf->global->STRIPE_APPLICATION_FEE_PERCENT / 100) + $conf->global->STRIPE_APPLICATION_FEE;
1306 if ($fee >= $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL && $conf->global->STRIPE_APPLICATION_FEE_MAXIMAL > $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
1307 $fee = getDolGlobalString('STRIPE_APPLICATION_FEE_MAXIMAL');
1308 } elseif ($fee < $conf->global->STRIPE_APPLICATION_FEE_MINIMAL) {
1309 $fee = getDolGlobalString('STRIPE_APPLICATION_FEE_MINIMAL');
1310 }
1311
1312 if (!in_array($currency, $arrayzerounitcurrency)) {
1313 $stripefee = round($fee * 100);
1314 } else {
1315 $stripefee = round($fee);
1316 }
1317
1318 $paymentarray = array(
1319 "amount" => "$stripeamount",
1320 "currency" => "$currency",
1321 "statement_descriptor_suffix" => dol_trunc($description, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
1322 "description" => "Stripe payment: ".$description,
1323 "capture" => $capture,
1324 "metadata" => $metadata,
1325 "source" => "$source",
1326 "customer" => "$customer"
1327 );
1328 if ($conf->entity != $conf->global->STRIPECONNECT_PRINCIPAL && $stripefee > 0) {
1329 $paymentarray["application_fee_amount"] = $stripefee;
1330 }
1331 if ($societe->email && $usethirdpartyemailforreceiptemail) {
1332 $paymentarray["receipt_email"] = $societe->email;
1333 }
1334
1335 if (preg_match('/pm_/i', $source)) {
1336 $stripecard = $source;
1337 $amountstripe = $stripeamount;
1338 $FULLTAG = 'PFBO'; // Payment From Back Office
1339 $stripe = $return;
1340 $amounttopay = $amount;
1341 $servicestatus = $status;
1342
1343 dol_syslog("* createPaymentStripe get stripeacc", LOG_DEBUG);
1344 $stripeacc = $stripe->getStripeAccount($service); // Get Stripe OAuth connect account if it exists (no network access here)
1345
1346 dol_syslog("* createPaymentStripe Create payment on card ".$stripecard->id.", amounttopay=".$amounttopay.", amountstripe=".$amountstripe.", FULLTAG=".$FULLTAG, LOG_DEBUG);
1347
1348 // Create payment intent and charge payment (confirmnow = true)
1349 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
1350
1351 $charge = new stdClass();
1352 if ($paymentintent->status == 'succeeded') {
1353 $charge->status = 'ok';
1354 $charge->id = $paymentintent->id;
1355 } else {
1356 $charge->status = 'failed';
1357 $charge->failure_code = $stripe->code;
1358 $charge->failure_message = $stripe->error;
1359 $charge->failure_declinecode = $stripe->declinecode;
1360 }
1361 } else {
1362 $charge = \Stripe\Charge::create($paymentarray, array("idempotency_key" => "$description", "stripe_account" => "$account"));
1363 }
1364 }
1365 if (isset($charge->id)) {
1366 }
1367
1368 $return->result = 'success';
1369 $return->id = $charge->id;
1370
1371 if (preg_match('/pm_/i', $source)) {
1372 $return->message = 'Payment retrieved by card status = '.$charge->status;
1373 } else {
1374 if ($charge->source->type == 'card') {
1375 $return->message = $charge->source->card->brand." ....".$charge->source->card->last4;
1376 } elseif ($charge->source->type == 'three_d_secure') {
1377 $stripe = new Stripe($this->db);
1378 $src = \Stripe\Source::retrieve("".$charge->source->three_d_secure->card, array(
1379 "stripe_account" => $stripe->getStripeAccount($service)
1380 ));
1381 $return->message = $src->card->brand." ....".$src->card->last4;
1382 } else {
1383 $return->message = $charge->id;
1384 }
1385 }
1386 } catch (\Stripe\Exception\CardException $e) {
1387 include DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
1388 // Since it's a decline, \Stripe\Exception\Card will be caught
1389 $body = $e->getJsonBody();
1390 $err = $body['error'];
1391
1392 $return->result = 'error';
1393 $return->id = $err['charge'];
1394 $return->type = $err['type'];
1395 $return->code = $err['code'];
1396 $return->message = $err['message'];
1397 $body = "Error: <br>".$return->id." ".$return->message." ";
1398 $subject = '[Alert] Payment error using Stripe';
1399 $cmailfile = new CMailFile($subject, $conf->global->ONLINE_PAYMENT_SENDEMAIL, $conf->global->MAIN_INFO_SOCIETE_MAIL, $body);
1400 $cmailfile->sendfile();
1401
1402 $error++;
1403 dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1404 } catch (\Stripe\Exception\RateLimitException $e) {
1405 // Too many requests made to the API too quickly
1406 $error++;
1407 dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1408 } catch (\Stripe\Exception\InvalidRequestException $e) {
1409 // Invalid parameters were supplied to Stripe's API
1410 $error++;
1411 dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1412 } catch (\Stripe\Exception\AuthenticationException $e) {
1413 // Authentication with Stripe's API failed
1414 // (maybe you changed API keys recently)
1415 $error++;
1416 dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1417 } catch (\Stripe\Exception\ApiConnectionException $e) {
1418 // Network communication with Stripe failed
1419 $error++;
1420 dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1421 } catch (\Stripe\Exception\ExceptionInterface $e) {
1422 // Display a very generic error to the user, and maybe send
1423 // yourself an email
1424 $error++;
1425 dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1426 } catch (Exception $e) {
1427 // Something else happened, completely unrelated to Stripe
1428 $error++;
1429 dol_syslog($e->getMessage(), LOG_WARNING, 0, '_stripe');
1430 }
1431 return $return;
1432 }
1433}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:58
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 @TODO No reason to extends CommonObject.
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 (create it if it doesn't exists and $createifnotlinkedt...
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
customerStripe(CommonObject $object, $key='', $status=0, $createifnotlinkedtostripe=0)
Get the Stripe customer of a thirdparty (with option to create it in Stripe if not linked yet).
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.
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)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_print_error($db=null, $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 '…' 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.