dolibarr  20.0.0-beta
ipn.php
1 <?php
2 /* Copyright (C) 2018-2020 Thibault FOUCART <support@ptibogxiv.net>
3  * Copyright (C) 2018-2024 Frédéric France <frederic.france@free.fr>
4  * Copyright (C) 2023 Laurent Destailleur <eldy@users.sourceforge.net>
5  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 if (!defined('NOLOGIN')) {
22  define("NOLOGIN", 1); // This means this output page does not require to be logged.
23 }
24 if (!defined('NOCSRFCHECK')) {
25  define("NOCSRFCHECK", 1); // We accept to go on this page from external web site.
26 }
27 if (!defined('NOIPCHECK')) {
28  define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip
29 }
30 if (!defined('NOBROWSERNOTIF')) {
31  define('NOBROWSERNOTIF', '1');
32 }
33 
34 // Because 2 entities can have the same ref.
35 $entity = (!empty($_GET['entity']) ? (int) $_GET['entity'] : (!empty($_POST['entity']) ? (int) $_POST['entity'] : 1));
36 if (is_numeric($entity)) {
37  define("DOLENTITY", $entity);
38 }
39 
40 // So log file will have a suffix
41 if (!defined('USESUFFIXINLOG')) {
42  define('USESUFFIXINLOG', '_stripeipn');
43 }
44 
45 // Load Dolibarr environment
46 require '../../main.inc.php';
47 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
48 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
49 require_once DOL_DOCUMENT_ROOT.'/core/class/ccountry.class.php';
50 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
51 require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
52 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
53 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
54 require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
55 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
56 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
57 require_once DOL_DOCUMENT_ROOT.'/includes/stripe/stripe-php/init.php';
58 require_once DOL_DOCUMENT_ROOT.'/stripe/class/stripe.class.php';
59 
60 
61 // You can find your endpoint's secret in your webhook settings
62 if (GETPOSTISSET('connect')) {
63  if (GETPOSTISSET('test')) {
64  $endpoint_secret = getDolGlobalString('STRIPE_TEST_WEBHOOK_CONNECT_KEY');
65  $service = 'StripeTest';
66  $servicestatus = 0;
67  } else {
68  $endpoint_secret = getDolGlobalString('STRIPE_LIVE_WEBHOOK_CONNECT_KEY');
69  $service = 'StripeLive';
70  $servicestatus = 1;
71  }
72 } else {
73  if (GETPOSTISSET('test')) {
74  $endpoint_secret = getDolGlobalString('STRIPE_TEST_WEBHOOK_KEY');
75  $service = 'StripeTest';
76  $servicestatus = 0;
77  } else {
78  $endpoint_secret = getDolGlobalString('STRIPE_LIVE_WEBHOOK_KEY');
79  $service = 'StripeLive';
80  $servicestatus = 1;
81  }
82 }
83 
84 if (!isModEnabled('stripe')) {
85  httponly_accessforbidden('Module Stripe not enabled');
86 }
87 
88 if (empty($endpoint_secret)) {
89  httponly_accessforbidden('Error: Setup of module Stripe not complete for mode '.dol_escape_htmltag($service).'. The WEBHOOK_KEY is not defined.', 400, 1);
90 }
91 
92 if (getDolGlobalString('STRIPE_USER_ACCOUNT_FOR_ACTIONS')) {
93  // We set the user to use for all ipn actions in Dolibarr
94  $user = new User($db);
95  $user->fetch(getDolGlobalString('STRIPE_USER_ACCOUNT_FOR_ACTIONS'));
96  $user->getrights();
97 } else {
98  httponly_accessforbidden('Error: Setup of module Stripe not complete for mode '.dol_escape_htmltag($service).'. The STRIPE_USER_ACCOUNT_FOR_ACTIONS is not defined.', 400, 1);
99 }
100 
101 $now = dol_now();
102 
103 // Security
104 // The test on security key is done later into constructEvent() method.
105 
106 
107 /*
108  * Actions
109  */
110 
111 $payload = @file_get_contents("php://input");
112 $sig_header = empty($_SERVER["HTTP_STRIPE_SIGNATURE"]) ? '' : $_SERVER["HTTP_STRIPE_SIGNATURE"];
113 $event = null;
114 
115 if (getDolGlobalString('STRIPE_DEBUG')) {
116  $fh = fopen(DOL_DATA_ROOT.'/dolibarr_stripeipn_payload.log', 'w+');
117  if ($fh) {
118  fwrite($fh, dol_print_date(dol_now('gmt'), 'standard').' IPN Called. service='.$service.' HTTP_STRIPE_SIGNATURE='.$sig_header."\n");
119  fwrite($fh, $payload);
120  fclose($fh);
121  dolChmod(DOL_DATA_ROOT.'/dolibarr_stripeipn_payload.log');
122  }
123 }
124 
125 $error = 0;
126 
127 try {
128  $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $endpoint_secret);
129 } catch (UnexpectedValueException $e) {
130  // Invalid payload
131  httponly_accessforbidden('Invalid payload', 400);
132 } catch (\Stripe\Exception\SignatureVerificationException $e) {
133  httponly_accessforbidden('Invalid signature. May be a hook for an event created by another Stripe env ? Check setup of your keys whsec_...', 400);
134 } catch (Exception $e) {
135  httponly_accessforbidden('Error '.$e->getMessage(), 400);
136 }
137 
138 // Do something with $event
139 
140 $langs->load("main");
141 
142 
143 if (isModEnabled('multicompany') && !empty($conf->stripeconnect->enabled) && is_object($mc)) {
144  $sql = "SELECT entity";
145  $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
146  $sql .= " WHERE service = '".$db->escape($service)."' and tokenstring LIKE '%".$db->escape($db->escapeforlike($event->account))."%'";
147 
148  dol_syslog(get_class($db)."::fetch", LOG_DEBUG);
149  $result = $db->query($sql);
150  if ($result) {
151  if ($db->num_rows($result)) {
152  $obj = $db->fetch_object($result);
153  $key = $obj->entity;
154  } else {
155  $key = 1;
156  }
157  } else {
158  $key = 1;
159  }
160  $ret = $mc->switchEntity($key);
161 }
162 
163 // list of action
164 $stripe = new Stripe($db);
165 
166 // Subject
167 $societeName = getDolGlobalString('MAIN_INFO_SOCIETE_NOM');
168 if (getDolGlobalString('MAIN_APPLICATION_TITLE')) {
169  $societeName = getDolGlobalString('MAIN_APPLICATION_TITLE');
170 }
171 
172 top_httphead();
173 
174 dol_syslog("***** Stripe IPN was called with event->type=".$event->type." service=".$service);
175 
176 
177 if ($event->type == 'payout.created') {
178  $error = 0;
179 
180  $result = dolibarr_set_const($db, $service."_NEXTPAYOUT", date('Y-m-d H:i:s', $event->data->object->arrival_date), 'chaine', 0, '', $conf->entity);
181 
182  if ($result > 0) {
183  $subject = $societeName.' - [NOTIFICATION] Stripe payout scheduled';
184  if (!empty($user->email)) {
185  $sendto = dolGetFirstLastname($user->firstname, $user->lastname)." <".$user->email.">";
186  } else {
187  $sendto = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL') . '" <' . getDolGlobalString('MAIN_INFO_SOCIETE_MAIL').'>';
188  }
189  $replyto = $sendto;
190  $sendtocc = '';
191  if (getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL')) {
192  $sendtocc = getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL') . '" <' . getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL').'>';
193  }
194 
195  $message = "A bank transfer of ".price2num($event->data->object->amount / 100)." ".$event->data->object->currency." should arrive in your account the ".dol_print_date($event->data->object->arrival_date, 'dayhour');
196 
197  $mailfile = new CMailFile(
198  $subject,
199  $sendto,
200  $replyto,
201  $message,
202  array(),
203  array(),
204  array(),
205  $sendtocc,
206  '',
207  0,
208  -1
209  );
210 
211  $ret = $mailfile->sendfile();
212 
213  return 1;
214  } else {
215  $error++;
216  http_response_code(500);
217  return -1;
218  }
219 } elseif ($event->type == 'payout.paid') {
220  $error = 0;
221  $result = dolibarr_set_const($db, $service."_NEXTPAYOUT", null, 'chaine', 0, '', $conf->entity);
222  if ($result) {
223  $langs->load("errors");
224 
225  $dateo = dol_now();
226  $label = $event->data->object->description;
227  $amount = $event->data->object->amount / 100;
228  $amount_to = $event->data->object->amount / 100;
229  require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
230 
231  $accountfrom = new Account($db);
232  $accountfrom->fetch(getDolGlobalString('STRIPE_BANK_ACCOUNT_FOR_PAYMENTS'));
233 
234  $accountto = new Account($db);
235  $accountto->fetch(getDolGlobalString('STRIPE_BANK_ACCOUNT_FOR_BANKTRANSFERS'));
236 
237  if (($accountto->id != $accountfrom->id) && empty($error)) {
238  $bank_line_id_from = 0;
239  $bank_line_id_to = 0;
240  $result = 0;
241 
242  // By default, electronic transfer from bank to bank
243  $typefrom = 'PRE';
244  $typeto = 'VIR';
245 
246  if (!$error) {
247  $bank_line_id_from = $accountfrom->addline($dateo, $typefrom, $label, -1 * (float) price2num($amount), '', '', $user);
248  }
249  if (!($bank_line_id_from > 0)) {
250  $error++;
251  }
252  if (!$error) {
253  $bank_line_id_to = $accountto->addline($dateo, $typeto, $label, price2num($amount), '', '', $user);
254  }
255  if (!($bank_line_id_to > 0)) {
256  $error++;
257  }
258 
259  if (!$error) {
260  $result = $accountfrom->add_url_line($bank_line_id_from, $bank_line_id_to, DOL_URL_ROOT.'/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert');
261  }
262  if (!($result > 0)) {
263  $error++;
264  }
265  if (!$error) {
266  $result = $accountto->add_url_line($bank_line_id_to, $bank_line_id_from, DOL_URL_ROOT.'/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert');
267  }
268  if (!($result > 0)) {
269  $error++;
270  }
271  }
272 
273  $subject = $societeName.' - [NOTIFICATION] Stripe payout done';
274  if (!empty($user->email)) {
275  $sendto = dolGetFirstLastname($user->firstname, $user->lastname)." <".$user->email.">";
276  } else {
277  $sendto = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL') . '" <' . getDolGlobalString('MAIN_INFO_SOCIETE_MAIL').'>';
278  }
279  $replyto = $sendto;
280  $sendtocc = '';
281  if (getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL')) {
282  $sendtocc = getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL') . '" <' . getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL').'>';
283  }
284 
285  $message = "A bank transfer of ".price2num($event->data->object->amount / 100)." ".$event->data->object->currency." has been done to your account the ".dol_print_date($event->data->object->arrival_date, 'dayhour');
286 
287  $mailfile = new CMailFile(
288  $subject,
289  $sendto,
290  $replyto,
291  $message,
292  array(),
293  array(),
294  array(),
295  $sendtocc,
296  '',
297  0,
298  -1
299  );
300 
301  $ret = $mailfile->sendfile();
302 
303  return 1;
304  } else {
305  $error++;
306  http_response_code(500);
307  return -1;
308  }
309 } elseif ($event->type == 'customer.source.created') {
310  //TODO: save customer's source
311 } elseif ($event->type == 'customer.source.updated') {
312  //TODO: update customer's source
313 } elseif ($event->type == 'customer.source.delete') {
314  //TODO: delete customer's source
315 } elseif ($event->type == 'customer.deleted') {
316  $db->begin();
317  $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_account WHERE key_account = '".$db->escape($event->data->object->id)."' and site='stripe'";
318  $db->query($sql);
319  $db->commit();
320 } elseif ($event->type == 'payment_intent.succeeded') { // Called when making payment with PaymentIntent method ($conf->global->STRIPE_USE_NEW_CHECKOUT is on).
321  //dol_syslog("object = ".var_export($event->data, true));
322  include_once DOL_DOCUMENT_ROOT . '/compta/paiement/class/paiement.class.php';
323  global $stripearrayofkeysbyenv;
324  $error = 0;
325  $object = $event->data->object;
326  $TRANSACTIONID = $object->id; // Example pi_123456789...
327  $ipaddress = $object->metadata->ipaddress;
328  $now = dol_now();
329  $currencyCodeType = strtoupper($object->currency);
330  $paymentmethodstripeid = $object->payment_method;
331  $customer_id = $object->customer;
332  $invoice_id = "";
333  $paymentTypeCode = ""; // payment type according to Stripe
334  $paymentTypeCodeInDolibarr = ""; // payment type according to Dolibarr
335  $payment_amount = 0;
336  $payment_amountInDolibarr = 0;
337 
338  dol_syslog("Try to find a payment in database for the payment_intent id = ".$TRANSACTIONID);
339 
340  $sql = "SELECT pi.rowid, pi.fk_facture, pi.fk_prelevement_bons, pi.amount, pi.type, pi.traite";
341  $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
342  $sql .= " WHERE pi.ext_payment_id = '".$db->escape($TRANSACTIONID)."'";
343  $sql .= " AND pi.ext_payment_site = '".$db->escape($service)."'";
344 
345  $result = $db->query($sql);
346  if ($result) {
347  $obj = $db->fetch_object($result);
348  if ($obj) {
349  if ($obj->type == 'ban') {
350  if ($obj->traite == 1) {
351  // This is a direct-debit with an order (llx_bon_prelevement) ALREADY generated, so
352  // it means we received here the confirmation that payment request is finished.
353  $pdid = $obj->rowid;
354  $invoice_id = $obj->fk_facture;
355  $directdebitorcreditransfer_id = $obj->fk_prelevement_bons;
356  $payment_amountInDolibarr = $obj->amount;
357  $paymentTypeCodeInDolibarr = $obj->type;
358 
359  dol_syslog("Found a request in database to pay with direct debit generated (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")");
360  } else {
361  dol_syslog("Found a request in database not yet generated (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id."). Was the order deleted after being sent ?", LOG_WARNING);
362  }
363  }
364  if ($obj->type == 'card' || empty($obj->type)) {
365  if ($obj->traite == 0) {
366  // This is a card payment not already flagged as sent to Stripe.
367  $pdid = $obj->rowid;
368  $invoice_id = $obj->fk_facture;
369  $payment_amountInDolibarr = $obj->amount;
370  $paymentTypeCodeInDolibarr = empty($obj->type) ? 'card' : $obj->type;
371 
372  dol_syslog("Found a request in database to pay with card (pdid = ".$pdid."). We should fix status traite to 1");
373  } else {
374  dol_syslog("Found a request in database to pay with card (pdid = ".$pdid.") already set to traite=1. Nothing to fix.");
375  }
376  }
377  } else {
378  dol_syslog("Payment intent ".$TRANSACTIONID." not found into database, so ignored.");
379  http_response_code(200);
380  print "Payment intent ".$TRANSACTIONID." not found into database, so ignored.";
381  return 1;
382  }
383  } else {
384  http_response_code(500);
385  print $db->lasterror();
386  return -1;
387  }
388 
389  if ($paymentTypeCodeInDolibarr) {
390  // Here, we need to do something. A $invoice_id has been found.
391 
392  $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];
393 
394  dol_syslog("Get the Stripe payment object for the payment method id = ".json_encode($paymentmethodstripeid));
395 
396  $s = new \Stripe\StripeClient($stripeacc);
397 
398  $paymentmethodstripe = $s->paymentMethods->retrieve($paymentmethodstripeid);
399  $paymentTypeCode = $paymentmethodstripe->type;
400  if ($paymentTypeCode == "ban" || $paymentTypeCode == "sepa_debit") {
401  $paymentTypeCode = "PRE";
402  } elseif ($paymentTypeCode == "card") {
403  $paymentTypeCode = "CB";
404  }
405 
406  $payment_amount = $payment_amountInDolibarr;
407  // TODO Check payment_amount in Stripe (received) is same than the one in Dolibarr
408 
409  $postactionmessages = array();
410 
411  if ($paymentTypeCode == "CB" && ($paymentTypeCodeInDolibarr == 'card' || empty($paymentTypeCodeInDolibarr))) {
412  // Case payment type in Stripe and into prelevement_demande are both CARD.
413  // For this case, payment should already have been recorded so we just update flag of payment request if not yet 1
414 
415  // TODO Set traite to 1
416  dol_syslog("TODO update flag traite to 1");
417  } elseif ($paymentTypeCode == "PRE" && $paymentTypeCodeInDolibarr == 'ban') {
418  // Case payment type in Stripe and into prelevement_demande are both BAN.
419  // For this case, payment on invoice (not yet recorded) must be done and direct debit order must be closed.
420 
421  $paiement = new Paiement($db);
422  $paiement->datepaye = $now;
423  $paiement->date = $now;
424  if ($currencyCodeType == $conf->currency) {
425  $paiement->amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching with invoice id
426  } else {
427  $paiement->multicurrency_amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching
428 
429  $postactionmessages[] = 'Payment was done in a currency ('.$currencyCodeType.') other than the expected currency of company ('.$conf->currency.')';
430  $ispostactionok = -1;
431  // Not yet supported, so error
432  $error++;
433  }
434  $paiement->paiementcode = $paymentTypeCode;
435  $paiement->num_payment = '';
436  $paiement->note_public = '';
437  $paiement->note_private = 'StripeSepa payment ' . dol_print_date($now, 'standard') . ' using ' . $servicestatus . ($ipaddress ? ' from ip ' . $ipaddress : '') . ' - Transaction ID = ' . $TRANSACTIONID;
438  $paiement->ext_payment_id = $TRANSACTIONID.':'.$customer_id.'@'.$stripearrayofkeysbyenv[$servicestatus]['publishable_key']; // May be we should store py_... instead of pi_... but we started with pi_... so we continue.
439  $paiement->ext_payment_site = $service;
440 
441  $ispaymentdone = 0;
442  $sql = "SELECT p.rowid FROM ".MAIN_DB_PREFIX."paiement as p";
443  $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'";
444  $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'";
445  $result = $db->query($sql);
446  if ($result) {
447  if ($db->num_rows($result)) {
448  $ispaymentdone = 1;
449  dol_syslog('* Payment for ext_payment_id '.$paiement->ext_payment_id.' already done. We do not recreate the payment');
450  }
451  }
452 
453  $db->begin();
454 
455  if (!$error && !$ispaymentdone) {
456  dol_syslog('* Record payment for invoice id ' . $invoice_id . '. It includes closing of invoice and regenerating document');
457 
458  // This include closing invoices to 'paid' (and trigger including unsuspending) and regenerating document
459  $paiement_id = $paiement->create($user, 1);
460  if ($paiement_id < 0) {
461  $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . implode("<br>\n", $paiement->errors);
462  $ispostactionok = -1;
463  $error++;
464 
465  dol_syslog("Failed to create the payment for invoice id " . $invoice_id);
466  } else {
467  $postactionmessages[] = 'Payment created';
468 
469  dol_syslog("The payment has been created for invoice id " . $invoice_id);
470  }
471  }
472 
473  if (!$error && isModEnabled('bank')) {
474  // Search again the payment to see if it is already linked to a bank payment record (We should always find the payment that was created before).
475  $ispaymentdone = 0;
476  $sql = "SELECT p.rowid, p.fk_bank FROM ".MAIN_DB_PREFIX."paiement as p";
477  $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'";
478  $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'";
479  $sql .= " AND p.fk_bank <> 0";
480  $result = $db->query($sql);
481  if ($result) {
482  if ($db->num_rows($result)) {
483  $ispaymentdone = 1;
484  $obj = $db->fetch_object($result);
485  dol_syslog('* Payment already linked to bank record '.$obj->fk_bank.' . We do not recreate the link');
486  }
487  }
488  if (!$ispaymentdone) {
489  dol_syslog('* Add payment to bank');
490 
491  // The bank used is the one defined into Stripe setup
492  $paymentmethod = 'stripe';
493  $bankaccountid = getDolGlobalInt("STRIPE_BANK_ACCOUNT_FOR_PAYMENTS");
494 
495  if ($bankaccountid > 0) {
496  $label = '(CustomerInvoicePayment)';
497  $result = $paiement->addPaymentToBank($user, 'payment', $label, $bankaccountid, $customer_id, '');
498  if ($result < 0) {
499  $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . implode("<br>\n", $paiement->errors);
500  $ispostactionok = -1;
501  $error++;
502  } else {
503  $postactionmessages[] = 'Bank transaction of payment created (by ipn.php file)';
504  }
505  } else {
506  $postactionmessages[] = 'Setup of bank account to use in module ' . $paymentmethod . ' was not set. No way to record the payment.';
507  $ispostactionok = -1;
508  $error++;
509  }
510  }
511  }
512 
513  if (!$error && isModEnabled('prelevement')) {
514  $bon = new BonPrelevement($db);
515  $idbon = 0;
516  $sql = "SELECT dp.fk_prelevement_bons as idbon";
517  $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as dp";
518  $sql .= " JOIN ".MAIN_DB_PREFIX."prelevement_bons as pb"; // Here we join to prevent modification of a prelevement bon already credited
519  $sql .= " ON pb.rowid = dp.fk_prelevement_bons";
520  $sql .= " WHERE dp.fk_facture = ".((int) $invoice_id);
521  $sql .= " AND dp.sourcetype = 'facture'";
522  $sql .= " AND dp.ext_payment_id = '".$db->escape($TRANSACTIONID)."'";
523  $sql .= " AND dp.traite = 1";
524  $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED); // To be sure that it's not already credited
525  $result = $db->query($sql);
526  if ($result) {
527  if ($db->num_rows($result)) {
528  $obj = $db->fetch_object($result);
529  $idbon = $obj->idbon;
530  dol_syslog('* Prelevement must be set to credited');
531  } else {
532  dol_syslog('* Prelevement not found or already credited');
533  }
534  } else {
535  $postactionmessages[] = $db->lasterror();
536  $ispostactionok = -1;
537  $error++;
538  }
539 
540  if (!$error && !empty($idbon)) {
541  $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_bons";
542  $sql .= " SET fk_user_credit = ".((int) $user->id);
543  $sql .= ", statut = ".((int) $bon::STATUS_CREDITED);
544  $sql .= ", date_credit = '".$db->idate($now)."'";
545  $sql .= ", credite = 1";
546  $sql .= " WHERE rowid = ".((int) $idbon);
547  $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED);
548 
549  $result = $db->query($sql);
550  if (!$result) {
551  $postactionmessages[] = $db->lasterror();
552  $ispostactionok = -1;
553  $error++;
554  }
555  }
556 
557  if (!$error && !empty($idbon)) {
558  $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_lignes";
559  $sql .= " SET statut = 2";
560  $sql .= " WHERE fk_prelevement_bons = ".((int) $idbon);
561  $result = $db->query($sql);
562  if (!$result) {
563  $postactionmessages[] = $db->lasterror();
564  $ispostactionok = -1;
565  $error++;
566  }
567  }
568  }
569 
570  if (!$error) {
571  $db->commit();
572  http_response_code(200);
573  return 1;
574  } else {
575  $db->rollback();
576  http_response_code(500);
577  return -1;
578  }
579  } else {
580  dol_syslog("The payment mode of this payment is ".$paymentTypeCode." in Stripe and ".$paymentTypeCodeInDolibarr." in Dolibarr. This case is not managed by the IPN");
581  }
582  } else {
583  dol_syslog("Nothing to do in database because we don't know paymentTypeIdInDolibarr");
584  }
585 } elseif ($event->type == 'payment_intent.payment_failed') {
586  dol_syslog("A try to make a payment has failed");
587 
588  $object = $event->data->object;
589  $ipaddress = $object->metadata->ipaddress;
590  $currencyCodeType = strtoupper($object->currency);
591  $paymentmethodstripeid = $object->payment_method;
592  $customer_id = $object->customer;
593 
594  $chargesdataarray = array();
595  $objpayid = '';
596  $objpaydesc = '';
597  $objinvoiceid = 0;
598  $objerrcode = '';
599  $objerrmessage = '';
600  $objpaymentmodetype = '';
601  if (!empty($object->charges)) { // Old format
602  $chargesdataarray = $object->charges->data;
603  foreach ($chargesdataarray as $chargesdata) {
604  $objpayid = $chargesdata->id;
605  $objpaydesc = $chargesdata->description;
606  $objinvoiceid = 0;
607  if ($chargesdata->metadata->dol_type == 'facture') {
608  $objinvoiceid = $chargesdata->metadata->dol_id;
609  }
610  $objerrcode = $chargesdata->outcome->reason;
611  $objerrmessage = $chargesdata->outcome->seller_message;
612 
613  $objpaymentmodetype = $chargesdata->payment_method_details->type;
614  break;
615  }
616  }
617  if (!empty($object->last_payment_error)) { // New format 2023-10-16
618  // $object is probably an object of type Stripe\PaymentIntent
619  $objpayid = $object->latest_charge;
620  $objpaydesc = $object->description;
621  $objinvoiceid = 0;
622  if ($object->metadata->dol_type == 'facture') {
623  $objinvoiceid = $object->metadata->dol_id;
624  }
625  $objerrcode = empty($object->last_payment_error->code) ? $object->last_payment_error->decline_code : $object->last_payment_error->code;
626  $objerrmessage = $object->last_payment_error->message;
627 
628  $objpaymentmodetype = $object->last_payment_error->payment_method->type;
629  }
630 
631  dol_syslog("objpayid=".$objpayid." objpaymentmodetype=".$objpaymentmodetype." objerrcode=".$objerrcode);
632 
633  // If this is a differed payment for SEPA, add a line into agenda events
634  if ($objpaymentmodetype == 'sepa_debit') {
635  $db->begin();
636 
637  require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
638  $actioncomm = new ActionComm($db);
639 
640  if ($objinvoiceid > 0) {
641  require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
642  $invoice = new Facture($db);
643  $invoice->fetch($objinvoiceid);
644 
645  $actioncomm->userownerid = 0;
646  $actioncomm->percentage = -1;
647 
648  $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
649  $actioncomm->code = 'AC_IPN';
650 
651  $actioncomm->datep = $now;
652  $actioncomm->datef = $now;
653 
654  $actioncomm->socid = $invoice->socid;
655  $actioncomm->fk_project = $invoice->fk_project;
656  $actioncomm->fk_element = $invoice->id;
657  $actioncomm->elementtype = 'invoice';
658  $actioncomm->ip = getUserRemoteIP();
659  }
660 
661  $actioncomm->note_private = 'Error returned on payment id '.$objpayid.' after SEPA payment request '.$objpaydesc.'<br>Error code is: '.$objerrcode.'<br>Error message is: '.$objerrmessage;
662  $actioncomm->label = 'Payment error (SEPA Stripe)';
663 
664  $result = $actioncomm->create($user);
665  if ($result <= 0) {
666  dol_syslog($actioncomm->error, LOG_ERR);
667  $error++;
668  }
669 
670  if (! $error) {
671  $db->commit();
672  } else {
673  $db->rollback();
674  http_response_code(500);
675  return -1;
676  }
677  }
678 } elseif ($event->type == 'checkout.session.completed') { // Called when making payment with new Checkout method ($conf->global->STRIPE_USE_NEW_CHECKOUT is on).
679  // TODO: create fees
680 } elseif ($event->type == 'payment_method.attached') {
681  require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
682  require_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
683  $societeaccount = new SocieteAccount($db);
684 
685  $companypaymentmode = new CompanyPaymentMode($db);
686 
687  $idthirdparty = $societeaccount->getThirdPartyID($db->escape($event->data->object->customer), 'stripe', $servicestatus);
688  if ($idthirdparty > 0) {
689  // If the payment mode attached is to a stripe account owned by an external customer in societe_account (so a thirdparty that has a Stripe account),
690  // we can create the payment mode
691  $companypaymentmode->stripe_card_ref = $db->escape($event->data->object->id);
692  $companypaymentmode->fk_soc = $idthirdparty;
693  $companypaymentmode->bank = null;
694  $companypaymentmode->label = '';
695  $companypaymentmode->number = $db->escape($event->data->object->id);
696  $companypaymentmode->last_four = $db->escape($event->data->object->card->last4);
697  $companypaymentmode->card_type = $db->escape($event->data->object->card->branding);
698  $companypaymentmode->proprio = $db->escape($event->data->object->billing_details->name);
699  $companypaymentmode->exp_date_month = $db->escape($event->data->object->card->exp_month);
700  $companypaymentmode->exp_date_year = $db->escape($event->data->object->card->exp_year);
701  $companypaymentmode->cvn = null;
702  $companypaymentmode->datec = $db->escape($event->data->object->created);
703  $companypaymentmode->default_rib = 0;
704  $companypaymentmode->type = $db->escape($event->data->object->type);
705  $companypaymentmode->country_code = $db->escape($event->data->object->card->country);
706  $companypaymentmode->status = $servicestatus;
707 
708  // TODO Check that a payment mode $companypaymentmode->stripe_card_ref does not exists yet to avoid to create duplicates
709  // so we can remove the test on STRIPE_NO_DUPLICATE_CHECK
710  if (getDolGlobalString('STRIPE_NO_DUPLICATE_CHECK')) {
711  $db->begin();
712  $result = $companypaymentmode->create($user);
713  if ($result < 0) {
714  $error++;
715  }
716  if (!$error) {
717  $db->commit();
718  } else {
719  $db->rollback();
720  }
721  }
722  }
723 } elseif ($event->type == 'payment_method.updated') {
724  require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
725  $companypaymentmode = new CompanyPaymentMode($db);
726  $companypaymentmode->fetch(0, '', 0, '', " AND stripe_card_ref = '".$db->escape($event->data->object->id)."'");
727  if ($companypaymentmode->id > 0) {
728  // If we found a payment mode with the ID
729  $companypaymentmode->bank = null;
730  $companypaymentmode->label = '';
731  $companypaymentmode->number = $db->escape($event->data->object->id);
732  $companypaymentmode->last_four = $db->escape($event->data->object->card->last4);
733  $companypaymentmode->proprio = $db->escape($event->data->object->billing_details->name);
734  $companypaymentmode->exp_date_month = $db->escape($event->data->object->card->exp_month);
735  $companypaymentmode->exp_date_year = $db->escape($event->data->object->card->exp_year);
736  $companypaymentmode->cvn = null;
737  $companypaymentmode->datec = $db->escape($event->data->object->created);
738  $companypaymentmode->default_rib = 0;
739  $companypaymentmode->type = $db->escape($event->data->object->type);
740  $companypaymentmode->country_code = $db->escape($event->data->object->card->country);
741  $companypaymentmode->status = $servicestatus;
742 
743  $db->begin();
744  if (!$error) {
745  $result = $companypaymentmode->update($user);
746  if ($result < 0) {
747  $error++;
748  }
749  }
750  if (!$error) {
751  $db->commit();
752  } else {
753  $db->rollback();
754  }
755  }
756 } elseif ($event->type == 'payment_method.detached') {
757  $db->begin();
758  $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_rib WHERE number = '".$db->escape($event->data->object->id)."' and status = ".((int) $servicestatus);
759  $db->query($sql);
760  $db->commit();
761 } elseif ($event->type == 'charge.succeeded') {
762  // Deprecated. TODO: create fees and redirect to paymentok.php
763 } elseif ($event->type == 'charge.failed') {
764  // Deprecated. TODO: Redirect to paymentko.php
765 } elseif (($event->type == 'source.chargeable') && ($event->data->object->type == 'three_d_secure') && ($event->data->object->three_d_secure->authenticated == true)) {
766  // Deprecated.
767 }
768 
769 // End of page. Default return HTTP code will be 200
if($user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition: card.php:58
dolibarr_set_const($db, $name, $value, $type='chaine', $visible=0, $note='', $entity=1)
Insert a parameter (key,value) into database (delete old key then insert it again).
Definition: admin.lib.php:656
Class to manage bank accounts.
Class to manage agenda events (actions)
Class to manage withdrawal receipts.
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Class for CompanyPaymentMode.
Class to manage invoices.
Class to manage payments of customer invoices.
Class for SocieteAccount.
Stripe class @TODO No reason to extends CommonObject.
Class to manage Dolibarr users.
Definition: user.class.php:50
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:745
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dolChmod($filepath, $newmask='')
Change mod of a file.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
getUserRemoteIP()
Return the IP of remote user.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
if(!defined('NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
Definition: main.inc.php:1648
httponly_accessforbidden($message='1', $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.