dolibarr 21.0.0-alpha
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
21if (!defined('NOLOGIN')) {
22 define("NOLOGIN", 1); // This means this output page does not require to be logged.
23}
24if (!defined('NOCSRFCHECK')) {
25 define("NOCSRFCHECK", 1); // We accept to go on this page from external web site.
26}
27if (!defined('NOIPCHECK')) {
28 define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip
29}
30if (!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));
36if (is_numeric($entity)) {
37 define("DOLENTITY", $entity);
38}
39
40// So log file will have a suffix
41if (!defined('USESUFFIXINLOG')) {
42 define('USESUFFIXINLOG', '_stripeipn');
43}
44
45// Load Dolibarr environment
46require '../../main.inc.php';
47require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
48require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
49require_once DOL_DOCUMENT_ROOT.'/core/class/ccountry.class.php';
50require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
51require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
52require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
53require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
54require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
55require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
56require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
57require_once DOL_DOCUMENT_ROOT.'/includes/stripe/stripe-php/init.php';
58require_once DOL_DOCUMENT_ROOT.'/stripe/class/stripe.class.php';
59
60
61// You can find your endpoint's secret in your webhook settings
62if (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
84if (!isModEnabled('stripe')) {
85 httponly_accessforbidden('Module Stripe not enabled');
86}
87
88if (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
92if (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->loadRights();
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
115if (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
127try {
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
143if (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');
168if (getDolGlobalString('MAIN_APPLICATION_TITLE')) {
169 $societeName = getDolGlobalString('MAIN_APPLICATION_TITLE');
170}
171
173
174dol_syslog("***** Stripe IPN was called with event->type=".$event->type." service=".$service);
175
176
177if ($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
435 // Get ID of payment PRE
436 $paiement->paiementcode = $paymentTypeCode;
437 $sql = "SELECT id FROM ".MAIN_DB_PREFIX."c_paiement";
438 $sql .= " WHERE code = '".$db->escape($paymentTypeCode)."'";
439 $sql .= " AND entity IN (".getEntity('c_paiement').")";
440 $resql = $db->query($sql);
441 if ($resql) {
442 $obj = $db->fetch_object($resql);
443 $paiement->paiementid = $obj->id;
444 } else {
445 $error++;
446 }
447
448 $paiement->num_payment = '';
449 $paiement->note_public = '';
450 $paiement->note_private = 'StripeSepa payment received by IPN webhook - ' . dol_print_date($now, 'standard') . ' (TZ server) using servicestatus=' . $servicestatus . ($ipaddress ? ' from ip ' . $ipaddress : '') . ' - Transaction ID = ' . $TRANSACTIONID;
451 $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.
452 $paiement->ext_payment_site = $service;
453
454 $ispaymentdone = 0;
455 $sql = "SELECT p.rowid FROM ".MAIN_DB_PREFIX."paiement as p";
456 $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'";
457 $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'";
458 $result = $db->query($sql);
459 if ($result) {
460 if ($db->num_rows($result)) {
461 $ispaymentdone = 1;
462 dol_syslog('* Payment for ext_payment_id '.$paiement->ext_payment_id.' already done. We do not recreate the payment');
463 }
464 }
465
466 $db->begin();
467
468 if (!$error && !$ispaymentdone) {
469 dol_syslog('* Record payment type PRE for invoice id ' . $invoice_id . '. It includes closing of invoice and regenerating document.');
470
471 // This include closing invoices to 'paid' (and trigger including unsuspending) and regenerating document
472 $paiement_id = $paiement->create($user, 1);
473 if ($paiement_id < 0) {
474 $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . implode("<br>\n", $paiement->errors);
475 $ispostactionok = -1;
476 $error++;
477
478 dol_syslog("Failed to create the payment for invoice id " . $invoice_id);
479 } else {
480 $postactionmessages[] = 'Payment created';
481
482 dol_syslog("The payment has been created for invoice id " . $invoice_id);
483 }
484 }
485
486 if (!$error && isModEnabled('bank')) {
487 // 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).
488 $ispaymentdone = 0;
489 $sql = "SELECT p.rowid, p.fk_bank FROM ".MAIN_DB_PREFIX."paiement as p";
490 $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'";
491 $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'";
492 $sql .= " AND p.fk_bank <> 0";
493 $result = $db->query($sql);
494 if ($result) {
495 if ($db->num_rows($result)) {
496 $ispaymentdone = 1;
497 $obj = $db->fetch_object($result);
498 dol_syslog('* Payment already linked to bank record '.$obj->fk_bank.' . We do not recreate the link');
499 }
500 }
501 if (!$ispaymentdone) {
502 dol_syslog('* Add payment to bank');
503
504 // The bank used is the one defined into Stripe setup
505 $paymentmethod = 'stripe';
506 $bankaccountid = getDolGlobalInt("STRIPE_BANK_ACCOUNT_FOR_PAYMENTS");
507
508 if ($bankaccountid > 0) {
509 $label = '(CustomerInvoicePayment)';
510 $result = $paiement->addPaymentToBank($user, 'payment', $label, $bankaccountid, $customer_id, '');
511 if ($result < 0) {
512 $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . implode("<br>\n", $paiement->errors);
513 $ispostactionok = -1;
514 $error++;
515 } else {
516 $postactionmessages[] = 'Bank transaction of payment created (by ipn.php file)';
517 }
518 } else {
519 $postactionmessages[] = 'Setup of bank account to use in module ' . $paymentmethod . ' was not set. No way to record the payment.';
520 $ispostactionok = -1;
521 $error++;
522 }
523 }
524 }
525
526 if (!$error && isModEnabled('prelevement')) {
527 $bon = new BonPrelevement($db);
528 $idbon = 0;
529 $sql = "SELECT dp.fk_prelevement_bons as idbon";
530 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as dp";
531 $sql .= " JOIN ".MAIN_DB_PREFIX."prelevement_bons as pb"; // Here we join to prevent modification of a prelevement bon already credited
532 $sql .= " ON pb.rowid = dp.fk_prelevement_bons";
533 $sql .= " WHERE dp.fk_facture = ".((int) $invoice_id);
534 $sql .= " AND dp.sourcetype = 'facture'";
535 $sql .= " AND dp.ext_payment_id = '".$db->escape($TRANSACTIONID)."'";
536 $sql .= " AND dp.traite = 1";
537 $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED); // To be sure that it's not already credited
538 $result = $db->query($sql);
539 if ($result) {
540 if ($db->num_rows($result)) {
541 $obj = $db->fetch_object($result);
542 $idbon = $obj->idbon;
543 dol_syslog('* Prelevement must be set to credited');
544 } else {
545 dol_syslog('* Prelevement not found or already credited');
546 }
547 } else {
548 $postactionmessages[] = $db->lasterror();
549 $ispostactionok = -1;
550 $error++;
551 }
552
553 if (!$error && !empty($idbon)) {
554 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_bons";
555 $sql .= " SET fk_user_credit = ".((int) $user->id);
556 $sql .= ", statut = ".((int) $bon::STATUS_CREDITED);
557 $sql .= ", date_credit = '".$db->idate($now)."'";
558 $sql .= ", credite = 1";
559 $sql .= " WHERE rowid = ".((int) $idbon);
560 $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED);
561
562 $result = $db->query($sql);
563 if (!$result) {
564 $postactionmessages[] = $db->lasterror();
565 $ispostactionok = -1;
566 $error++;
567 }
568 }
569
570 if (!$error && !empty($idbon)) {
571 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_lignes";
572 $sql .= " SET statut = 2";
573 $sql .= " WHERE fk_prelevement_bons = ".((int) $idbon);
574 $result = $db->query($sql);
575 if (!$result) {
576 $postactionmessages[] = $db->lasterror();
577 $ispostactionok = -1;
578 $error++;
579 }
580 }
581 }
582
583 if (!$error) {
584 $db->commit();
585 http_response_code(200);
586 return 1;
587 } else {
588 $db->rollback();
589 http_response_code(500);
590 return -1;
591 }
592 } else {
593 dol_syslog("The payment mode of this payment is ".$paymentTypeCode." in Stripe and ".$paymentTypeCodeInDolibarr." in Dolibarr. This case is not managed by the IPN");
594 }
595 } else {
596 dol_syslog("Nothing to do in database because we don't know paymentTypeIdInDolibarr");
597 }
598} elseif ($event->type == 'payment_intent.payment_failed') {
599 dol_syslog("A try to make a payment has failed");
600
601 $object = $event->data->object;
602 $ipaddress = $object->metadata->ipaddress;
603 $currencyCodeType = strtoupper($object->currency);
604 $paymentmethodstripeid = $object->payment_method;
605 $customer_id = $object->customer;
606
607 $chargesdataarray = array();
608 $objpayid = '';
609 $objpaydesc = '';
610 $objinvoiceid = 0;
611 $objerrcode = '';
612 $objerrmessage = '';
613 $objpaymentmodetype = '';
614 if (!empty($object->charges)) { // Old format
615 $chargesdataarray = $object->charges->data;
616 foreach ($chargesdataarray as $chargesdata) {
617 $objpayid = $chargesdata->id;
618 $objpaydesc = $chargesdata->description;
619 $objinvoiceid = 0;
620 if ($chargesdata->metadata->dol_type == 'facture') {
621 $objinvoiceid = $chargesdata->metadata->dol_id;
622 }
623 $objerrcode = $chargesdata->outcome->reason;
624 $objerrmessage = $chargesdata->outcome->seller_message;
625
626 $objpaymentmodetype = $chargesdata->payment_method_details->type;
627 break;
628 }
629 }
630 if (!empty($object->last_payment_error)) { // New format 2023-10-16
631 // $object is probably an object of type Stripe\PaymentIntent
632 $objpayid = $object->latest_charge;
633 $objpaydesc = $object->description;
634 $objinvoiceid = 0;
635 if ($object->metadata->dol_type == 'facture') {
636 $objinvoiceid = $object->metadata->dol_id;
637 }
638 $objerrcode = empty($object->last_payment_error->code) ? $object->last_payment_error->decline_code : $object->last_payment_error->code;
639 $objerrmessage = $object->last_payment_error->message;
640
641 $objpaymentmodetype = $object->last_payment_error->payment_method->type;
642 }
643
644 dol_syslog("objpayid=".$objpayid." objpaymentmodetype=".$objpaymentmodetype." objerrcode=".$objerrcode);
645
646 // If this is a differed payment for SEPA, add a line into agenda events
647 if ($objpaymentmodetype == 'sepa_debit') {
648 $db->begin();
649
650 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
651 $actioncomm = new ActionComm($db);
652
653 if ($objinvoiceid > 0) {
654 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
655 $invoice = new Facture($db);
656 $invoice->fetch($objinvoiceid);
657
658 $actioncomm->userownerid = 0;
659 $actioncomm->percentage = -1;
660
661 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
662 $actioncomm->code = 'AC_IPN';
663
664 $actioncomm->datep = $now;
665 $actioncomm->datef = $now;
666
667 $actioncomm->socid = $invoice->socid;
668 $actioncomm->fk_project = $invoice->fk_project;
669 $actioncomm->fk_element = $invoice->id;
670 $actioncomm->elementtype = 'invoice';
671 $actioncomm->ip = getUserRemoteIP();
672 }
673
674 $actioncomm->note_private = 'Error returned on payment id '.$objpayid.' after SEPA payment request '.$objpaydesc.'<br>Error code is: '.$objerrcode.'<br>Error message is: '.$objerrmessage;
675 $actioncomm->label = 'Payment error (SEPA Stripe)';
676
677 $result = $actioncomm->create($user);
678 if ($result <= 0) {
679 dol_syslog($actioncomm->error, LOG_ERR);
680 $error++;
681 }
682
683 if (! $error) {
684 $db->commit();
685 } else {
686 $db->rollback();
687 http_response_code(500);
688 return -1;
689 }
690 }
691} elseif ($event->type == 'checkout.session.completed') { // Called when making payment with new Checkout method ($conf->global->STRIPE_USE_NEW_CHECKOUT is on).
692 // TODO: create fees
693} elseif ($event->type == 'payment_method.attached') {
694 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
695 require_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
696 $societeaccount = new SocieteAccount($db);
697
698 $companypaymentmode = new CompanyPaymentMode($db);
699
700 $idthirdparty = $societeaccount->getThirdPartyID($db->escape($event->data->object->customer), 'stripe', $servicestatus);
701 if ($idthirdparty > 0) {
702 // 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),
703 // we can create the payment mode
704 $companypaymentmode->stripe_card_ref = $db->escape($event->data->object->id);
705 $companypaymentmode->fk_soc = $idthirdparty;
706 $companypaymentmode->bank = null;
707 $companypaymentmode->label = '';
708 $companypaymentmode->number = $db->escape($event->data->object->id);
709 $companypaymentmode->last_four = $db->escape($event->data->object->card->last4);
710 $companypaymentmode->card_type = $db->escape($event->data->object->card->branding);
711 $companypaymentmode->proprio = $db->escape($event->data->object->billing_details->name);
712 $companypaymentmode->exp_date_month = $db->escape($event->data->object->card->exp_month);
713 $companypaymentmode->exp_date_year = $db->escape($event->data->object->card->exp_year);
714 $companypaymentmode->cvn = null;
715 $companypaymentmode->datec = $db->escape($event->data->object->created);
716 $companypaymentmode->default_rib = 0;
717 $companypaymentmode->type = $db->escape($event->data->object->type);
718 $companypaymentmode->country_code = $db->escape($event->data->object->card->country);
719 $companypaymentmode->status = $servicestatus;
720
721 // TODO Check that a payment mode $companypaymentmode->stripe_card_ref does not exists yet to avoid to create duplicates
722 // so we can remove the test on STRIPE_NO_DUPLICATE_CHECK
723 if (getDolGlobalString('STRIPE_NO_DUPLICATE_CHECK')) {
724 $db->begin();
725 $result = $companypaymentmode->create($user);
726 if ($result < 0) {
727 $error++;
728 }
729 if (!$error) {
730 $db->commit();
731 } else {
732 $db->rollback();
733 }
734 }
735 }
736} elseif ($event->type == 'payment_method.updated') {
737 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
738 $companypaymentmode = new CompanyPaymentMode($db);
739 $companypaymentmode->fetch(0, '', 0, '', " AND stripe_card_ref = '".$db->escape($event->data->object->id)."'");
740 if ($companypaymentmode->id > 0) {
741 // If we found a payment mode with the ID
742 $companypaymentmode->bank = null;
743 $companypaymentmode->label = '';
744 $companypaymentmode->number = $db->escape($event->data->object->id);
745 $companypaymentmode->last_four = $db->escape($event->data->object->card->last4);
746 $companypaymentmode->proprio = $db->escape($event->data->object->billing_details->name);
747 $companypaymentmode->exp_date_month = $db->escape($event->data->object->card->exp_month);
748 $companypaymentmode->exp_date_year = $db->escape($event->data->object->card->exp_year);
749 $companypaymentmode->cvn = null;
750 $companypaymentmode->datec = $db->escape($event->data->object->created);
751 $companypaymentmode->default_rib = 0;
752 $companypaymentmode->type = $db->escape($event->data->object->type);
753 $companypaymentmode->country_code = $db->escape($event->data->object->card->country);
754 $companypaymentmode->status = $servicestatus;
755
756 $db->begin();
757 if (!$error) {
758 $result = $companypaymentmode->update($user);
759 if ($result < 0) {
760 $error++;
761 }
762 }
763 if (!$error) {
764 $db->commit();
765 } else {
766 $db->rollback();
767 }
768 }
769} elseif ($event->type == 'payment_method.detached') {
770 $db->begin();
771 $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_rib WHERE number = '".$db->escape($event->data->object->id)."' and status = ".((int) $servicestatus);
772 $db->query($sql);
773 $db->commit();
774} elseif ($event->type == 'charge.succeeded') {
775 // Deprecated. TODO: create fees and redirect to paymentok.php
776} elseif ($event->type == 'charge.failed') {
777 // Deprecated. TODO: Redirect to paymentko.php
778} elseif (($event->type == 'source.chargeable') && ($event->data->object->type == 'three_d_secure') && ($event->data->object->three_d_secure->authenticated == true)) {
779 // Deprecated.
780}
781
782// 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).
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.
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.
getDolGlobalString($key, $default='')
Return a 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.
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.
httponly_accessforbidden($message='1', $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.