dolibarr 22.0.5
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-2025 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; // @phpstan-ignore-line
53
57 public $mode;
58
62 public $entity;
63
67 public $type;
68
72 public $code;
73
77 public $declinecode;
78
82 public $message;
83
89 public function __construct($db)
90 {
91 $this->db = $db;
92 }
93
94
103 public function getStripeAccount($mode = 'StripeTest', $fk_soc = 0, $entity = -1)
104 {
105 global $conf;
106
107 $key = '';
108 if ($entity < 0) {
109 $entity = $conf->entity;
110 }
111
112 $sql = "SELECT tokenstring";
113 $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
114 $sql .= " WHERE service = '".$this->db->escape($mode)."'";
115 $sql .= " AND entity = ".((int) $entity);
116 if ($fk_soc > 0) {
117 $sql .= " AND fk_soc = ".((int) $fk_soc);
118 } else {
119 $sql .= " AND fk_soc IS NULL";
120 }
121 $sql .= " AND fk_user IS NULL AND fk_adherent IS NULL";
122
123 dol_syslog(get_class($this)."::getStripeAccount", LOG_DEBUG);
124
125 $result = $this->db->query($sql);
126 if ($result) {
127 if ($this->db->num_rows($result)) {
128 $obj = $this->db->fetch_object($result);
129 $tokenstring = $obj->tokenstring;
130
131 if ($tokenstring) {
132 $tmparray = json_decode($tokenstring);
133 $key = empty($tmparray->stripe_user_id) ? '' : $tmparray->stripe_user_id;
134 }
135 } else {
136 $tokenstring = '';
137 }
138 } else {
139 dol_print_error($this->db);
140 }
141
142 dol_syslog("No dedicated Stripe Connect account available for entity ".$conf->entity);
143
144 return $key;
145 }
146
155 public function getStripeCustomerAccount($id, $status = 0, $site_account = '')
156 {
157 include_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
158 $societeaccount = new SocieteAccount($this->db);
159 return $societeaccount->getCustomerAccount($id, 'stripe', $status, $site_account); // Get thirdparty cus_...
160 }
161
162
173 public function customerStripe($object, $key = '', $status = 0, $createifnotlinkedtostripe = 0)
174 {
175 global $conf, $user;
176
177 if (empty($object->id)) {
178 dol_syslog("customerStripe is called with the parameter object that is not loaded");
179 return null;
180 }
181
182 $customer = null;
183
184 // Force to use the correct API key
185 global $stripearrayofkeysbyenv;
186 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
187
188 $sql = "SELECT sa.key_account as key_account, sa.entity"; // key_account is cus_....
189 $sql .= " FROM ".MAIN_DB_PREFIX."societe_account as sa";
190 $sql .= " WHERE sa.fk_soc = ".((int) $object->id);
191 $sql .= " AND sa.entity IN (".getEntity('societe').")";
192 $sql .= " AND sa.site = 'stripe' AND sa.status = ".((int) $status);
193 $sql .= " AND (sa.site_account IS NULL OR sa.site_account = '' OR sa.site_account = '".$this->db->escape($stripearrayofkeysbyenv[$status]['publishable_key'])."')";
194 $sql .= " AND sa.key_account IS NOT NULL AND sa.key_account <> ''";
195 $sql .= " ORDER BY sa.site_account DESC, sa.rowid DESC"; // To get the entry with a site_account defined in priority
196
197 dol_syslog(get_class($this)."::customerStripe search stripe customer id for thirdparty id=".$object->id, LOG_DEBUG);
198 $resql = $this->db->query($sql);
199 if ($resql) {
200 $num = $this->db->num_rows($resql);
201 if ($num) {
202 $obj = $this->db->fetch_object($resql);
203 $tiers = $obj->key_account;
204
205 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']);
206
207 try {
208 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
209 //$customer = \Stripe\Customer::retrieve("$tiers");
210 $customer = \Stripe\Customer::retrieve(array('id' => "$tiers", 'expand[]' => 'sources'));
211 } else {
212 //$customer = \Stripe\Customer::retrieve("$tiers", array("stripe_account" => $key));
213 $customer = \Stripe\Customer::retrieve(array('id' => "$tiers", 'expand[]' => 'sources'), array("stripe_account" => $key));
214 }
215 } catch (Exception $e) {
216 // 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.'
217 $this->error = $e->getMessage();
218 }
219 } elseif ($createifnotlinkedtostripe) {
220 $ipaddress = getUserRemoteIP();
221
222 $dataforcustomer = array(
223 "email" => $object->email,
224 "description" => $object->name,
225 "metadata" => array('dol_id' => $object->id, 'dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress)
226 );
227
228 $vatcleaned = $object->tva_intra ? $object->tva_intra : null;
229
230 /*
231 $taxinfo = array('type'=>'vat');
232 if ($vatcleaned)
233 {
234 $taxinfo["tax_id"] = $vatcleaned;
235 }
236 // We force data to "null" if not defined as expected by Stripe
237 if (empty($vatcleaned)) $taxinfo=null;
238 $dataforcustomer["tax_info"] = $taxinfo;
239 */
240
241 //$a = \Stripe\Stripe::getApiKey();
242 //var_dump($a);var_dump($key);exit;
243 try {
244 // Force to use the correct API key
245 global $stripearrayofkeysbyenv;
246 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
247
248 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
249 $customer = \Stripe\Customer::create($dataforcustomer);
250 } else {
251 $customer = \Stripe\Customer::create($dataforcustomer, array("stripe_account" => $key));
252 }
253
254 // Create the VAT record in Stripe
255 if (getDolGlobalString('STRIPE_SAVE_TAX_IDS')) { // We setup to save Tax info on Stripe side. Warning: This may result in error when saving customer
256 if (!empty($vatcleaned)) {
257 $isineec = isInEEC($object);
258 if ($object->country_code && $isineec) {
259 //$taxids = $customer->allTaxIds($customer);
260 $customer->createTaxId($customer->id, array('type' => 'eu_vat', 'value' => $vatcleaned));
261 }
262 }
263 }
264
265 // Create customer in Dolibarr
266 $sql = "INSERT INTO ".MAIN_DB_PREFIX."societe_account (fk_soc, login, key_account, site, site_account, status, entity, date_creation, fk_user_creat)";
267 $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).")";
268 $resql = $this->db->query($sql);
269 if (!$resql) {
270 $this->error = $this->db->lasterror();
271 }
272 } catch (Exception $e) {
273 $this->error = $e->getMessage();
274 }
275 }
276 } else {
277 dol_print_error($this->db);
278 }
279
280 return $customer;
281 }
282
291 public function getPaymentMethodStripe($paymentmethod, $key = '', $status = 0)
292 {
293 $stripepaymentmethod = null;
294
295 try {
296 // Force to use the correct API key
297 global $stripearrayofkeysbyenv;
298 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
299 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
300 $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id);
301 } else {
302 $stripepaymentmethod = \Stripe\PaymentMethod::retrieve((string) $paymentmethod->id, array("stripe_account" => $key));
303 }
304 } catch (Exception $e) {
305 $this->error = $e->getMessage();
306 }
307
308 return $stripepaymentmethod;
309 }
310
319 public function getSelectedReader($reader, $key = '', $status = 0)
320 {
321 $selectedreader = null;
322
323 try {
324 // Force to use the correct API key
325 global $stripearrayofkeysbyenv;
326 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
327 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
328 $selectedreader = \Stripe\Terminal\Reader::retrieve((string) $reader);
329 } else {
330 $stripepaymentmethod = \Stripe\Terminal\Reader::retrieve((string) $reader, array("stripe_account" => $key));
331 }
332 } catch (Exception $e) {
333 $this->error = $e->getMessage();
334 }
335
336 return $selectedreader;
337 }
338
365 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)
366 {
367 global $conf, $user;
368
369 dol_syslog(get_class($this)."::getPaymentIntent description=".$description, LOG_INFO, 1);
370
371 $error = 0;
372
373 if (empty($status)) {
374 $service = 'StripeTest';
375 } else {
376 $service = 'StripeLive';
377 }
378
379 $arrayzerounitcurrency = array('BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF');
380 if (!in_array($currency_code, $arrayzerounitcurrency)) {
381 $stripeamount = $amount * 100;
382 } else {
383 $stripeamount = $amount;
384 }
385
386 $fee = 0;
387 if (getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT")) {
388 $fee = $amount * ((float) getDolGlobalString("STRIPE_APPLICATION_FEE_PERCENT", '0') / 100) + (float) getDolGlobalString("STRIPE_APPLICATION_FEE", '0');
389 }
390 if ($fee >= (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') && (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0') > (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
391 $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MAXIMAL", '0');
392 } elseif ($fee < (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0')) {
393 $fee = (float) getDolGlobalString("STRIPE_APPLICATION_FEE_MINIMAL", '0');
394 }
395 if (!in_array($currency_code, $arrayzerounitcurrency)) {
396 $stripefee = round($fee * 100);
397 } else {
398 $stripefee = round($fee);
399 }
400
401 $paymentintent = null;
402
403 if (is_object($object) && getDolGlobalInt('STRIPE_REUSE_EXISTING_INTENT_IF_FOUND') && !getDolGlobalInt('STRIPE_CARD_PRESENT')) {
404 // Warning. If a payment was tried and failed, a payment intent was created.
405 // 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.
406 // 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
407 // automatically return the existing payment intent if idempotency is provided when we try to create the new one.
408 // 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)
409
410 $sql = "SELECT pi.ext_payment_id, pi.entity, pi.fk_facture, pi.sourcetype, pi.ext_payment_site";
411 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
412 $sql .= " WHERE pi.fk_facture = ".((int) $object->id);
413 $sql .= " AND pi.sourcetype = '".$this->db->escape($object->element)."'";
414 $sql .= " AND pi.entity IN (".getEntity('societe').")";
415 $sql .= " AND pi.ext_payment_site = '".$this->db->escape($service)."'";
416
417 dol_syslog(get_class($this)."::getPaymentIntent search stripe payment intent for object id = ".$object->id, LOG_DEBUG);
418 $resql = $this->db->query($sql);
419 if ($resql) {
420 $num = $this->db->num_rows($resql);
421 if ($num) {
422 $obj = $this->db->fetch_object($resql);
423 $intent = $obj->ext_payment_id;
424
425 dol_syslog(get_class($this)."::getPaymentIntent found existing payment intent record");
426
427 // Force to use the correct API key
428 global $stripearrayofkeysbyenv;
429 \Stripe\Stripe::setApiKey($stripearrayofkeysbyenv[$status]['secret_key']);
430
431 try {
432 if (empty($key)) { // If the Stripe connect account not set, we use common API usage
433 $paymentintent = \Stripe\PaymentIntent::retrieve($intent);
434 } else {
435 $paymentintent = \Stripe\PaymentIntent::retrieve($intent, array("stripe_account" => $key));
436 }
437 } catch (Exception $e) {
438 $error++;
439 $this->error = $e->getMessage();
440 }
441 }
442 }
443 }
444
445 if (empty($paymentintent)) {
446 // Try to create intent. See https://stripe.com/docs/api/payment_intents/create
447 $ipaddress = getUserRemoteIP();
448 $metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress, 'dol_noidempotency' => (int) $noidempotency_key);
449 if (is_object($object)) {
450 $metadata['dol_type'] = $object->element;
451 $metadata['dol_id'] = $object->id;
452 if (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
453 $metadata['dol_thirdparty_id'] = $object->thirdparty->id;
454 }
455 }
456
457 $stripemode = $mode;
458
459 // list of payment method types
460 $paymentmethodtypes = array("card");
461 $descriptor = dol_trunc($tag, 10, 'right', 'UTF-8', 1);
462 if (getDolGlobalInt('STRIPE_SEPA_DIRECT_DEBIT')) {
463 $paymentmethodtypes[] = "sepa_debit"; //&& ($object->thirdparty->isInEEC())
464 //$descriptor = preg_replace('/ref=[^:=]+/', '', $descriptor); // Clean ref
465 }
466 if (getDolGlobalInt('STRIPE_KLARNA')) {
467 $paymentmethodtypes[] = "klarna";
468 }
469 if (getDolGlobalInt('STRIPE_BANCONTACT')) {
470 $paymentmethodtypes[] = "bancontact";
471 }
472 if (getDolGlobalInt('STRIPE_IDEAL')) {
473 $paymentmethodtypes[] = "ideal";
474 }
475 if (getDolGlobalInt('STRIPE_GIROPAY')) {
476 $paymentmethodtypes[] = "giropay";
477 }
478 if (getDolGlobalInt('STRIPE_SOFORT')) {
479 $paymentmethodtypes[] = "sofort";
480 }
481 if ($mode == 'terminal') {
482 if (getDolGlobalInt('STRIPE_CARD_PRESENT')) {
483 $paymentmethodtypes = array("card_present");
484 }
485 $stripemode = 'manual';
486 }
487
489
490 $descriptioninpaymentintent = $description;
491
492 $dataforintent = array(
493 "confirm" => $confirmnow, // try to confirm immediately after create (if conditions are ok)
494 "confirmation_method" => $stripemode,
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)) { // @phan-suppress-current-line PhanPluginConstantVariableNull
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
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 as owner_name, 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->owner_name;
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
1015 dol_syslog(get_class($this)."::sepaStripe paymentmode=".$cardref);
1016
1017 if ($cardref) {
1018 try {
1019 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1020 if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
1021 $sepa = $cu->sources->retrieve($cardref);
1022 } else {
1023 $sepa = \Stripe\PaymentMethod::retrieve($cardref);
1024 }
1025 } else {
1026 if (!preg_match('/^pm_/', $cardref) && !empty($cu->sources)) {
1027 //$sepa = $cu->sources->retrieve($cardref, array("stripe_account" => $stripeacc)); // this API fails when array stripe_account is provided
1028 $sepa = $cu->sources->retrieve($cardref);
1029 } else {
1030 //$sepa = \Stripe\PaymentMethod::retrieve($cardref, array("stripe_account" => $stripeacc)); // Don't know if this works
1031 $sepa = \Stripe\PaymentMethod::retrieve($cardref);
1032 }
1033 }
1034 } catch (Exception $e) {
1035 $this->error = $e->getMessage();
1036 dol_syslog($this->error, LOG_WARNING);
1037 }
1038 } elseif ($createifnotlinkedtostripe) {
1039 $iban = dolDecrypt($obj->iban);
1040 $ipaddress = getUserRemoteIP();
1041 $metadata = array('dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress);
1042 if (is_object($object)) {
1043 $metadata['dol_type'] = $object->element;
1044 $metadata['dol_id'] = $object->id;
1045 $metadata['dol_thirdparty_id'] = $soc->id;
1046 }
1047
1048 $description = 'SEPA for IBAN '.$iban;
1049
1050 $dataforcard = array(
1051 'type' => 'sepa_debit',
1052 "sepa_debit" => array('iban' => $iban),
1053 'billing_details' => array(
1054 'name' => $soc->name,
1055 'email' => !empty($soc->email) ? $soc->email : "",
1056 ),
1057 "metadata" => $metadata
1058 );
1059 // Complete owner name
1060 if (!empty($soc->town)) {
1061 $dataforcard['billing_details']['address']['city'] = $soc->town;
1062 }
1063 if (!empty($soc->country_code)) {
1064 $dataforcard['billing_details']['address']['country'] = $soc->country_code;
1065 }
1066 if (!empty($soc->address)) {
1067 $dataforcard['billing_details']['address']['line1'] = $soc->address;
1068 }
1069 if (!empty($soc->zip)) {
1070 $dataforcard['billing_details']['address']['postal_code'] = $soc->zip;
1071 }
1072 if (!empty($soc->state)) {
1073 $dataforcard['billing_details']['address']['state'] = $soc->state;
1074 }
1075
1076 //$a = \Stripe\Stripe::getApiKey();
1077 //var_dump($a);var_dump($stripeacc);exit;
1078 try {
1079 dol_syslog("Try to create sepa_debit");
1080
1081 $service = 'StripeTest';
1082 $servicestatus = 0;
1083 if (getDolGlobalString('STRIPE_LIVE')/* && !GETPOST('forcesandbox', 'alpha') */) {
1084 $service = 'StripeLive';
1085 $servicestatus = 1;
1086 }
1087 // Force to use the correct API key
1088 global $stripearrayofkeysbyenv;
1089 $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];
1090
1091 dol_syslog("Try to create sepa_debit with data = ".json_encode($dataforcard));
1092
1093 $s = new \Stripe\StripeClient($stripeacc);
1094
1095 //var_dump($dataforcard);exit;
1096
1097 $sepa = $s->paymentMethods->create($dataforcard);
1098 if (!$sepa) {
1099 $this->error = 'Creation of payment method sepa_debit on Stripe has failed';
1100 dol_syslog($this->error, LOG_ERR);
1101 } else {
1102 // link customer and src
1103 //$cs = $this->getSetupIntent($description, $soc, $cu, '', $status);
1104 $dataforintent = array(0 => ['description' => $description, 'payment_method_types' => ['sepa_debit'], 'customer' => $cu->id, 'payment_method' => $sepa->id], 'metadata' => $metadata);
1105
1106 $cs = $s->setupIntents->create($dataforintent);
1107 //$cs = $s->setupIntents->update($cs->id, ['payment_method' => $sepa->id]);
1108 $cs = $s->setupIntents->confirm($cs->id, ['mandate_data' => ['customer_acceptance' => ['type' => 'offline']]]);
1109 // note: $cs->mandate contains ID of mandate on Stripe side
1110
1111 if (!$cs) {
1112 $this->error = 'Link SEPA <-> Customer failed';
1113 dol_syslog($this->error, LOG_ERR);
1114 } else {
1115 dol_syslog("Update the payment mode of the customer");
1116
1117 // print json_encode($sepa);
1118
1119 // Save the Stripe payment mode ID into the Dolibarr database
1120 $sql = "UPDATE ".MAIN_DB_PREFIX."societe_rib";
1121 $sql .= " SET stripe_card_ref = '".$this->db->escape($sepa->id)."',";
1122 $sql .= " card_type = 'sepa_debit',";
1123 $sql .= " stripe_account= '" . $this->db->escape($cu->id . "@" . $stripeacc) . "',";
1124 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1125 if (!empty($cs->mandate)) {
1126 $mandateservice = new \Stripe\Mandate($stripeacc);
1127 $mandate = $mandateservice->retrieve($cs->mandate);
1128 if (is_object($mandate) && is_object($mandate->payment_method_details) && is_object($mandate->payment_method_details->sepa_debit)) {
1129 $refmandate = $mandate->payment_method_details->sepa_debit->reference;
1130 //$urlmandate = $mandate->payment_method_details->sepa_debit->url;
1131 $sql .= ", rum = '".$this->db->escape($refmandate)."'";
1132 }
1133 $sql .= ", comment = '".$this->db->escape($cs->mandate)."'";
1134 $sql .= ", date_rum = '".$this->db->idate(dol_now())."'";
1135 }
1136 $sql .= " WHERE rowid = ".((int) $object->id);
1137 $sql .= " AND type = 'ban'";
1138 $resql = $this->db->query($sql);
1139 if (!$resql) {
1140 $this->error = $this->db->lasterror();
1141 }
1142 }
1143 }
1144 } catch (Exception $e) {
1145 $sepa = null;
1146 $this->error = 'Stripe error: '.$e->getMessage().'. Check the BAN information.';
1147 dol_syslog($this->error, LOG_WARNING); // Error from Stripe, so a warning on Dolibarr
1148 }
1149 }
1150 }
1151 } else {
1152 dol_print_error($this->db);
1153 }
1154
1155 return $sepa;
1156 }
1157}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
global $dolibarr_main_url_root
Parent class of all other business classes (invoices, contracts, proposals, orders,...
Class for CompanyPaymentMode.
Class for SocieteAccount.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class @TODO No reason to extend 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...
customerStripe($object, $key='', $status=0, $createifnotlinkedtostripe=0)
Get the Stripe customer of a thirdparty (with option to create it in Stripe if not linked yet).
getPaymentIntent($amount, $currency_code, $tag, $description='', $object=null, $customer=null, $key=null, $status=0, $usethirdpartyemailforreceiptemail=0, $mode='automatic', $confirmnow=false, $payment_method=null, $off_session=0, $noidempotency_key=1, $did=0)
Get the Stripe payment intent.
getStripeCustomerAccount($id, $status=0, $site_account='')
getStripeCustomerAccount
cardStripe($cu, CompanyPaymentMode $object, $stripeacc='', $status=0, $createifnotlinkedtostripe=0)
Get the Stripe card of a company payment mode (option to create it on Stripe if not linked yet is no ...
__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, $allowothertags=array())
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.
getUserRemoteIP($trusted=0)
Return the real IP of remote user.
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 a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
dolDecrypt($chain, $key='')
Decode a string with a symmetric encryption.