dolibarr 24.0.0-beta
ipn.php
1<?php
2/* Copyright (C) 2018-2020 Thibault FOUCART <support@ptibogxiv.net>
3 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2023 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2024-2026 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';
54require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
55require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
56require_once DOL_DOCUMENT_ROOT.'/core/class/ccountry.class.php';
57require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
58require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
59require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
60require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
61require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
62require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
63require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
64require_once DOL_DOCUMENT_ROOT.'/includes/stripe/stripe-php/init.php';
65require_once DOL_DOCUMENT_ROOT.'/stripe/class/stripe.class.php';
66
67// You can find your endpoint's secret in your webhook settings
68if (GETPOSTISSET('connect')) {
69 if (GETPOSTISSET('test')) {
70 $endpoint_secret = getDolGlobalString('STRIPE_TEST_WEBHOOK_CONNECT_KEY');
71 $service = 'StripeTest';
72 $servicestatus = 0;
73 } else {
74 $endpoint_secret = getDolGlobalString('STRIPE_LIVE_WEBHOOK_CONNECT_KEY');
75 $service = 'StripeLive';
76 $servicestatus = 1;
77 }
78} else {
79 if (GETPOSTISSET('test')) {
80 $endpoint_secret = getDolGlobalString('STRIPE_TEST_WEBHOOK_KEY');
81 $service = 'StripeTest';
82 $servicestatus = 0;
83 } else {
84 $endpoint_secret = getDolGlobalString('STRIPE_LIVE_WEBHOOK_KEY');
85 $service = 'StripeLive';
86 $servicestatus = 1;
87 }
88}
89
90if (!isModEnabled('stripe')) {
91 httponly_accessforbidden('Module Stripe not enabled');
92}
93
94if (empty($endpoint_secret)) {
95 httponly_accessforbidden('Error: Setup of module Stripe not complete for mode '.dol_escape_htmltag($service).'. The WEBHOOK_KEY is not defined.', 400, 1);
96}
97
98if (getDolGlobalString('STRIPE_USER_ACCOUNT_FOR_ACTIONS')) {
99 // We set the user to use for all ipn actions in Dolibarr
100 $user = new User($db);
101 $user->fetch(getDolGlobalInt('STRIPE_USER_ACCOUNT_FOR_ACTIONS'));
102 $user->loadRights();
103} else {
104 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);
105}
106
107$now = dol_now();
108
109// Security
110// The test on security key is done later into constructEvent() method.
111
112
113/*
114 * Actions
115 */
116
117$payload = @file_get_contents("php://input");
118$sig_header = empty($_SERVER["HTTP_STRIPE_SIGNATURE"]) ? '' : $_SERVER["HTTP_STRIPE_SIGNATURE"];
119$event = null;
120
121if (getDolGlobalString('STRIPE_DEBUG')) {
122 $fh = fopen(DOL_DATA_ROOT.'/dolibarr_stripeipn_payload.log', 'w+');
123 if ($fh) {
124 fwrite($fh, dol_print_date(dol_now('gmt'), 'standard').' IPN Called. service='.$service.' HTTP_STRIPE_SIGNATURE='.$sig_header."\n");
125 fwrite($fh, $payload);
126 fclose($fh);
127 dolChmod(DOL_DATA_ROOT.'/dolibarr_stripeipn_payload.log');
128 }
129}
130
131$error = 0;
132
133try {
134 $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $endpoint_secret);
135} catch (UnexpectedValueException $e) {
136 // Invalid payload
137 dol_syslog("***** Stripe IPN was called with UnexpectedValueException (invalid payload) service=".$service);
138 dol_syslog("***** Stripe IPN was called with UnexpectedValueException (invalid payload) service=".$service, LOG_DEBUG, 0, '_payment');
139 httponly_accessforbidden('Invalid payload', 400);
140} catch (\Stripe\Exception\SignatureVerificationException $e) {
141 dol_syslog("***** Stripe IPN was called with SignatureVerificationException service=".$service);
142 dol_syslog("***** Stripe IPN was called with SignatureVerificationException service=".$service, LOG_DEBUG, 0, '_payment');
143 httponly_accessforbidden('Invalid signature. May be a hook for an event created by another Stripe env or a hack attempt ? Check setup of your keys whsec_...', 400);
144} catch (Exception $e) { // @phpstan-ignore catch.neverThrown
145 dol_syslog("***** Stripe IPN was called with Exception (".$e->getMessage().") service=".$service);
146 dol_syslog("***** Stripe IPN was called with Exception (".$e->getMessage().") service=".$service, LOG_DEBUG, 0, '_payment');
147 httponly_accessforbidden('Error '.$e->getMessage(), 400);
148}
149
150// Do something with $event
151
152$langs->load("main");
153
154
155if (isModEnabled('multicompany') && !empty($conf->stripeconnect->enabled) && isset($mc) && is_object($mc)) {
156 $sql = "SELECT entity";
157 $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
158 $sql .= " WHERE service = '".$db->escape($service)."' and tokenstring LIKE '%".$db->escape($db->escapeforlike($event->account))."%'";
159
160 dol_syslog(get_class($db)."::fetch", LOG_DEBUG, 0, '_payment');
161
162 $result = $db->query($sql);
163 if ($result) {
164 if ($db->num_rows($result)) {
165 $obj = $db->fetch_object($result);
166 $key = $obj->entity;
167 } else {
168 $key = 1;
169 }
170 } else {
171 $key = 1;
172 }
173 $ret = $mc->switchEntity($key);
174}
175
176$stripe = new Stripe($db);
177
178// Subject
179$societeName = getDolGlobalString('MAIN_INFO_SOCIETE_NOM');
180if (getDolGlobalString('MAIN_APPLICATION_TITLE')) {
181 $societeName = getDolGlobalString('MAIN_APPLICATION_TITLE');
182}
183
184
185// Add a delay to be sure that any Stripe action from webhooks are executed after interactive actions that also trigger a webhook
186sleep(2);
187
188
190
191dol_syslog("***** Stripe IPN was called with event->type=".$event->type." service=".$service);
192dol_syslog("***** Stripe IPN was called with event->type=".$event->type." service=".$service, LOG_DEBUG, 0, '_payment');
193
194
195// Hook to allow external modules to handle Stripe webhook events
196$hookmanager = new HookManager($db);
197$hookmanager->initHooks(array('stripeipn'));
198$parameters = array('event' => $event, 'servicestatus' => $servicestatus, 'service' => $service);
199$reshook = $hookmanager->executeHooks('stripeWebhookEvent', $parameters, $event->data->object, $event->type);
200if ($reshook > 0) {
201 // A hook handled the event entirely, stop processing
202 http_response_code(200);
203 return;
204}
205
206
207if ($event->type == 'payout.created' && getDolGlobalString('STRIPE_AUTO_RECORD_PAYOUT')) {
208 // When a payout is created by Stripe to transfer money to your account
209 dol_syslog("object = ".formatLogObject($event->data));
210 dol_syslog("object = ".formatLogObject($event->data), LOG_DEBUG, 0, '_payment');
211
212 $error = 0;
213
214 $result = dolibarr_set_const($db, $service."_NEXTPAYOUT", date('Y-m-d H:i:s', $event->data->object->arrival_date), 'chaine', 0, '', $conf->entity);
215
216 if ($result > 0) {
217 $subject = '['.$societeName.'] Notification - Stripe payout scheduled';
218 if (!empty($user->email)) {
219 $sendto = dolGetFirstLastname($user->firstname, $user->lastname)." <".$user->email.">";
220 } else {
221 $sendto = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL') . '" <' . getDolGlobalString('MAIN_INFO_SOCIETE_MAIL').'>';
222 }
223 $replyto = $sendto;
224 $sendtocc = '';
225 if (getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL')) {
226 $sendtocc = getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL') . '" <' . getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL').'>';
227 }
228
229 $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');
230
231 $mailfile = new CMailFile(
232 $subject,
233 $sendto,
234 $replyto,
235 $message,
236 array(),
237 array(),
238 array(),
239 $sendtocc,
240 '',
241 0,
242 -1
243 );
244
245 $ret = $mailfile->sendfile();
246
247 return 1;
248 } else {
249 $error++;
250 http_response_code(500);
251 return -1;
252 }
253} elseif ($event->type == 'payout.paid' && getDolGlobalString('STRIPE_AUTO_RECORD_PAYOUT')) {
254 // When a payout to transfer money to your account is completely done
255 dol_syslog("object = ".formatLogObject($event->data));
256 dol_syslog("object = ".formatLogObject($event->data), LOG_DEBUG, 0, '_payment');
257
258 $error = 0;
259 $result = dolibarr_set_const($db, $service."_NEXTPAYOUT", 0, 'chaine', 0, '', $conf->entity);
260 if ($result) {
261 $langs->load("errors");
262
263 $currency_code = getDolCurrency();
264
265 $dateo = dol_now();
266 $label = $event->data->object->description.' - '.getDolGlobalString('STRIPE_AUTO_RECORD_PAYOUT_LABEL', 'autorecord by IPN, see Stripe setup');
267 $amount = $stripe->convertAmount($event->data->object->amount, $currency_code, 1);
268
269 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
270
271 $accountfrom = new Account($db);
272 $accountfrom->fetch(getDolGlobalInt('STRIPE_BANK_ACCOUNT_FOR_PAYMENTS'));
273
274 $accountto = new Account($db);
275 $accountto->fetch(getDolGlobalInt('STRIPE_BANK_ACCOUNT_FOR_BANKTRANSFERS'));
276
277 if (($accountto->id != $accountfrom->id) && empty($error)) {
278 $bank_line_id_from = 0;
279 $bank_line_id_to = 0;
280 $result = 0;
281
282 // By default, electronic transfer from bank to bank
283 $typefrom = 'PRE';
284 $typeto = 'VIR';
285
286 $numChqOrOpe = ''; // TODO Store the po ref from $event->data
287
288 $db->begin();
289
290 // Add entry into table llx_bank
291 $bank_line_id_from = $accountfrom->addline($dateo, $typefrom, $label, -1 * (float) price2num($amount), $numChqOrOpe, 0, $user, '', '', '', null, '', null, 'Record payout from public/stripe/ipn.php');
292
293 if (!($bank_line_id_from > 0)) {
294 $error++;
295 }
296 if (!$error) {
297 $bank_line_id_to = $accountto->addline($dateo, $typeto, $label, (float) price2num($amount), $numChqOrOpe, 0, $user, '', '', '', null, '', null, 'Record payout from public/stripe/ipn.php');
298 }
299 if (!($bank_line_id_to > 0)) {
300 $error++;
301 }
302
303 // Now add links of detail into llx_bank_url
304 if (!$error) {
305 $result = $accountfrom->add_url_line($bank_line_id_from, $bank_line_id_to, DOL_URL_ROOT.'/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert');
306 }
307 if (!($result > 0)) {
308 $error++;
309 }
310 if (!$error) {
311 $result = $accountto->add_url_line($bank_line_id_to, $bank_line_id_from, DOL_URL_ROOT.'/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert');
312 }
313 if (!($result > 0)) {
314 $error++;
315 }
316
317 if (!$error) {
318 $db->commit();
319 } else {
320 $db->rollback();
321 }
322
323 // Send email
324 if (!$error) {
325 $subject = '['.$societeName.'] Notification - Stripe payout done';
326 if (!empty($user->email)) {
327 $sendto = dolGetFirstLastname($user->firstname, $user->lastname)." <".$user->email.">";
328 } else {
329 $sendto = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL');
330 }
331 $replyto = $sendto;
332 $sendtocc = '';
333 if (getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL')) {
334 $sendtocc = getDolGlobalString('ONLINE_PAYMENT_SENDEMAIL');
335 }
336
337 $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');
338
339 $mailfile = new CMailFile(
340 $subject,
341 $sendto,
342 $replyto,
343 $message,
344 array(),
345 array(),
346 array(),
347 $sendtocc,
348 '',
349 0,
350 -1
351 );
352
353 $ret = $mailfile->sendfile();
354 }
355 }
356
357 return 1;
358 } else {
359 $error++;
360 http_response_code(500);
361 return -1;
362 }
363} elseif ($event->type == 'customer.source.created') {
364 //TODO: save customer's source
365} elseif ($event->type == 'customer.source.updated') {
366 //TODO: update customer's source
367} elseif ($event->type == 'customer.source.delete') {
368 //TODO: delete customer's source
369} elseif ($event->type == 'customer.deleted') {
370 // When a customer account is delete on Stripe side
371 $db->begin();
372 $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_account WHERE key_account = '".$db->escape($event->data->object->id)."' AND site = 'stripe'";
373 $db->query($sql);
374 $db->commit();
375} elseif ($event->type == 'payment_intent.succeeded') {
376 // Called when making payment with PaymentIntent method.
377 dol_syslog("object = ".formatLogObject($event->data));
378 dol_syslog("object = ".formatLogObject($event->data), LOG_DEBUG, 0, '_payment');
379
380 include_once DOL_DOCUMENT_ROOT . '/compta/paiement/class/paiement.class.php';
381 global $stripearrayofkeysbyenv;
382 $error = 0;
383 $object = $event->data->object;
384 $objectType = $object->metadata->dol_type;
385 $TRANSACTIONID = $object->id; // Example 'pi_123456789...'
386 $ipaddress = $object->metadata->ipaddress;
387 $remoteipaddress = getUserRemoteIP();
388 $now = dol_now();
389 $currencyCodeType = strtoupper($object->currency);
390 $paymentmethodstripeid = $object->payment_method;
391 $customer_id = $object->customer;
392 $invoice_id = 0;
393 $supplierinvoice_id = 0;
394 $salary_id = "";
395 $paymentTypeCode = ""; // payment type according to Stripe
396 $paymentTypeCodeInDolibarr = ""; // payment type according to Dolibarr
397 $payment_amount = 0;
398 $payment_amountInDolibarr = 0;
399
400 dol_syslog("Try to find a payment in database for the payment_intent id = ".$TRANSACTIONID);
401 dol_syslog("Try to find a payment in database for the payment_intent id = ".$TRANSACTIONID, LOG_DEBUG, 0, '_payment');
402
403 $sql = "SELECT pi.rowid, pi.fk_facture, fk_facture_fourn, fk_salary, pi.fk_prelevement_bons, pi.amount, pi.type, pi.traite";
404 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
405 $sql .= " WHERE pi.ext_payment_id = '".$db->escape($TRANSACTIONID)."'";
406 $sql .= " AND pi.ext_payment_site = '".$db->escape($service)."'";
407
408 $result = $db->query($sql);
409 if ($result) {
410 $obj = $db->fetch_object($result);
411 if ($obj) {
412 if ($obj->type == 'ban') {
413 $pdid = $obj->rowid;
414 $directdebitorcreditransfer_id = $obj->fk_prelevement_bons;
415
416 if ($obj->traite == 1) {
417 // This is a direct-debit with an order (llx_bon_prelevement) ALREADY generated, so
418 // it means we received here the confirmation that payment request is finished.
419 $invoice_id = $obj->fk_facture;
420 $supplierinvoice_id = $obj->fk_facture_fourn;
421 $salary_id = $obj->fk_salary;
422 $payment_amountInDolibarr = $obj->amount;
423 $paymentTypeCodeInDolibarr = $obj->type;
424
425 dol_syslog("Found a request in database to pay with direct debit generated (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")");
426 dol_syslog("Found a request in database to pay with direct debit generated (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")", LOG_DEBUG, 0, '_payment');
427 } else {
428 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);
429 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, 0, '_payment');
430 }
431 }
432 if ($obj->type == 'card' || empty($obj->type)) {
433 $pdid = $obj->rowid;
434 if ($obj->traite == 0) {
435 // This is a card payment not already flagged as sent to Stripe.
436 $invoice_id = $obj->fk_facture;
437 $payment_amountInDolibarr = $obj->amount;
438 $paymentTypeCodeInDolibarr = empty($obj->type) ? 'card' : $obj->type;
439
440 dol_syslog("Found a request in database to pay with card (pdid = ".$pdid."). We should fix status traite to 1");
441 dol_syslog("Found a request in database to pay with card (pdid = ".$pdid."). We should fix status traite to 1", LOG_DEBUG, 0, '_payment');
442 } else {
443 dol_syslog("Found a request in database to pay with card (pdid = ".$pdid.") already set to traite=1. Nothing to fix.");
444 dol_syslog("Found a request in database to pay with card (pdid = ".$pdid.") already set to traite=1. Nothing to fix.", LOG_DEBUG, 0, '_payment');
445 }
446 }
447 } else {
448 dol_syslog("Payment intent ".$TRANSACTIONID." not found into database, so ignored.");
449 dol_syslog("Payment intent ".$TRANSACTIONID." not found into database, so ignored.", LOG_DEBUG, 0, '_payment');
450 http_response_code(200);
451 print "Payment intent ".$TRANSACTIONID." not found into database, so ignored.";
452 return 1;
453 }
454 } else {
455 http_response_code(500);
456 print $db->lasterror();
457 return -1;
458 }
459
460 if ($paymentTypeCodeInDolibarr) {
461 // Here, we need to do something. A $invoice_id has been found.
462
463 $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key'];
464
465 dol_syslog("Get the Stripe payment object for the payment method id = ".formatLogObject($paymentmethodstripeid));
466 dol_syslog("Get the Stripe payment object for the payment method id = ".formatLogObject($paymentmethodstripeid), LOG_DEBUG, 0, '_payment');
467
468 $s = new \Stripe\StripeClient($stripeacc);
469
470 $paymentmethodstripe = $s->paymentMethods->retrieve($paymentmethodstripeid);
471 $paymentTypeCode = $paymentmethodstripe->type;
472 if ($paymentTypeCode == "ban" || $paymentTypeCode == "sepa_debit") {
473 $paymentTypeCode = "PRE";
474 } elseif ($paymentTypeCode == "card") {
475 $paymentTypeCode = "CB";
476 }
477
478 $payment_amount = $payment_amountInDolibarr;
479
480 // TODO Add this checks ? May not be required because the message is already decoded with $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $endpoint_secret);
481 // - Check payment_amount in Stripe (received) is same than the one in Dolibarr
482 // - Check that payment intent is succeed (to avoid forged json webhook sent by malicious users)
483
484 $postactionmessages = array();
485
486 if ($paymentTypeCode == "CB" && ($paymentTypeCodeInDolibarr == 'card' || empty($paymentTypeCodeInDolibarr))) {
487 // Case payment type at Stripe side and into prelevement_demande are both CARD.
488 // For this case, payment should already have been recorded so we just update flag of payment request if not yet 1
489
490 // May be we should store py_... instead of pi_... but we started with pi_... so we continue.
491 $paiement_ext_payment_id = $TRANSACTIONID.':'.$customer_id.'@'.$stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
492 $paiement_ext_payment_idold = $TRANSACTIONID;
493 $paiement_ext_payment_site = $service;
494
495 // Update the payment request to pay by credit card
496 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET traite = 1 WHERE traite = 0";
497 $sql .= " AND (type = '' OR type = 'card')";
498 $sql .= " AND (ext_payment_id = '".$db->escape($paiement_ext_payment_id)."' OR ext_payment_id = '".$db->escape($paiement_ext_payment_idold)."')";
499 $sql .= " AND ext_payment_site = '".$db->escape($paiement_ext_payment_site)."'";
500 $sql .= " AND fk_facture = ".((int) $invoice_id);
501 $sql .= " AND sourcetype = 'facture'";
502
503 dol_syslog("TODO update flag traite to 1 sql=".$sql);
504 dol_syslog("TODO update flag traite to 1 sql=".$sql, LOG_DEBUG, 0, '_payment');
505 //$db->query($sql);
506 } elseif ($paymentTypeCode == "PRE" && $paymentTypeCodeInDolibarr == 'ban') {
507 // Case payment type is Direct Debit and into prelevement_demande is also BAN.
508 // For this case, payment on invoice (not yet recorded) must be recorded and direct debit order must be closed.
509
510 $paiement = new Paiement($db);
511
512 $paiement->datepaye = $now;
513 $paiement->date = $now;
514 if ($currencyCodeType == getDolCurrency()) {
515 $paiement->amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching with invoice id
516 } else {
517 $paiement->multicurrency_amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching
518
519 $postactionmessages[] = 'Payment was done in a currency ('.$currencyCodeType.') other than the expected currency of company ('.getDolCurrency().')';
520 $ispostactionok = -1;
521 // Not yet supported, so error
522 $error++;
523 }
524
525 // Get ID of payment PRE
526 $paiement->paiementcode = $paymentTypeCode;
527 $sql = "SELECT id FROM ".MAIN_DB_PREFIX."c_paiement";
528 $sql .= " WHERE code = '".$db->escape($paymentTypeCode)."'";
529 $sql .= " AND entity IN (".getEntity('c_paiement').")";
530 $resql = $db->query($sql);
531 if ($resql) {
532 $obj = $db->fetch_object($resql);
533 $paiement->paiementid = $obj->id;
534 } else {
535 $error++;
536 }
537
538 $paiement->num_payment = '';
539 $paiement->note_public = '';
540 $paiement->note_private = 'Stripe Sepa payment received by IPN service listening webhooks - ' . dol_print_date($now, 'standard') . ' (TZ server) using servicestatus=' . $servicestatus . ($remoteipaddress ? ' remote ip ' . $remoteipaddress : '').($ipaddress ? ' user ip ' . $ipaddress : '') . ' - Transaction ID = ' . $TRANSACTIONID;
541
542 $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.
543 $paiement->ext_payment_site = $service;
544
545 $ispaymentdone = 0;
546 $sql = "SELECT p.rowid FROM ".MAIN_DB_PREFIX."paiement as p";
547 $sql .= " WHERE (p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."' OR p.ext_payment_id = '".$db->escape($TRANSACTIONID)."')";
548 $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'";
549 $result = $db->query($sql);
550 if ($result) {
551 if ($db->num_rows($result)) {
552 $ispaymentdone = 1;
553 dol_syslog('* Payment for ext_payment_id '.$paiement->ext_payment_id.' already done. We do not recreate the payment');
554 dol_syslog('* Payment for ext_payment_id '.$paiement->ext_payment_id.' already done. We do not recreate the payment', LOG_DEBUG, 0, '_payment');
555 }
556 }
557
558 $db->begin();
559
560 if (!$error && !$ispaymentdone) {
561 dol_syslog('* Record payment type PRE for invoice id ' . $invoice_id . '. It includes closing of invoice and regenerating document.');
562 dol_syslog('* Record payment type PRE for invoice id ' . $invoice_id . '. It includes closing of invoice and regenerating document.', LOG_DEBUG, 0, '_payment');
563
564 // This include closing invoices to 'paid' (and trigger including unsuspending) and regenerating document
565 $thirdpartyofpayment = null; // TODO Load thirdparty from $invoice_id
566
567 $paiement_id = $paiement->create($user, 1, $thirdpartyofpayment);
568 if ($paiement_id < 0) {
569 $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . implode("<br>\n", $paiement->errors);
570 $ispostactionok = -1;
571 $error++;
572
573 dol_syslog("Failed to create the payment for invoice id " . $invoice_id);
574 dol_syslog("Failed to create the payment for invoice id " . $invoice_id, LOG_DEBUG, 0, '_payment');
575 } else {
576 $postactionmessages[] = 'Payment created';
577
578 dol_syslog("The payment has been created for invoice id " . $invoice_id);
579 dol_syslog("The payment has been created for invoice id " . $invoice_id, LOG_DEBUG, 0, '_payment');
580 }
581 }
582
583 if (!$error && isModEnabled('bank')) {
584 // 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).
585 $ispaymentdone = 0;
586 $sql = "SELECT p.rowid, p.fk_bank FROM ".MAIN_DB_PREFIX."paiement as p";
587 $sql .= " WHERE (p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."' OR p.ext_payment_id = '".$db->escape($TRANSACTIONID)."')";
588 $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'";
589 $sql .= " AND p.fk_bank <> 0";
590 $result = $db->query($sql);
591 if ($result) {
592 if ($db->num_rows($result)) {
593 $ispaymentdone = 1;
594 $obj = $db->fetch_object($result);
595 dol_syslog('* Payment already linked to bank record '.$obj->fk_bank.' . We do not recreate the link');
596 dol_syslog('* Payment already linked to bank record '.$obj->fk_bank.' . We do not recreate the link', LOG_DEBUG, 0, '_payment');
597 }
598 }
599 if (!$ispaymentdone) {
600 dol_syslog('* Add payment to bank');
601 dol_syslog('* Add payment to bank', LOG_DEBUG, 0, '_payment');
602
603 // The bank used is the one defined into Stripe setup
604 $paymentmethod = 'stripe';
605 $bankaccountid = getDolGlobalInt("STRIPE_BANK_ACCOUNT_FOR_PAYMENTS");
606
607 if ($bankaccountid > 0) {
608 $label = '(CustomerInvoicePayment)';
609 $result = $paiement->addPaymentToBank($user, 'payment', $label, $bankaccountid, $customer_id, '');
610 if ($result < 0) {
611 $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . implode("<br>\n", $paiement->errors);
612 $ispostactionok = -1;
613 $error++;
614 } else {
615 $postactionmessages[] = 'Bank transaction of payment created (by ipn.php file)';
616 }
617 } else {
618 $postactionmessages[] = 'Setup of bank account to use in module ' . $paymentmethod . ' was not set. No way to record the payment.';
619 $ispostactionok = -1;
620 $error++;
621 }
622 }
623 }
624
625 if (!$error && isModEnabled('prelevement')) {
626 $bon = new BonPrelevement($db);
627 $idbon = 0;
628 $sql = "SELECT dp.fk_prelevement_bons as idbon";
629 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as dp";
630 $sql .= " JOIN ".MAIN_DB_PREFIX."prelevement_bons as pb"; // Here we join to prevent modification of a prelevement bon already credited
631 $sql .= " ON pb.rowid = dp.fk_prelevement_bons";
632 $sql .= " WHERE dp.fk_facture = ".((int) $invoice_id);
633 $sql .= " AND dp.sourcetype = 'facture'";
634 $sql .= " AND (dp.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."' OR dp.ext_payment_id = '".$db->escape($TRANSACTIONID)."')";
635 $sql .= " AND dp.traite = 1";
636 $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED); // To be sure that it's not already credited
637 $result = $db->query($sql);
638 if ($result) {
639 if ($db->num_rows($result)) {
640 $obj = $db->fetch_object($result);
641 $idbon = $obj->idbon;
642 dol_syslog('* Prelevement must be set to credited');
643 dol_syslog('* Prelevement must be set to credited', LOG_DEBUG, 0, '_payment');
644 } else {
645 dol_syslog('* Prelevement not found or already credited');
646 dol_syslog('* Prelevement not found or already credited', LOG_DEBUG, 0, '_payment');
647 }
648 } else {
649 $postactionmessages[] = $db->lasterror();
650 $ispostactionok = -1;
651 $error++;
652 }
653
654 if (!$error && !empty($idbon)) {
655 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_bons";
656 $sql .= " SET fk_user_credit = ".((int) $user->id);
657 $sql .= ", statut = ".((int) $bon::STATUS_CREDITED);
658 $sql .= ", date_credit = '".$db->idate($now)."'";
659 $sql .= ", credite = 1";
660 $sql .= " WHERE rowid = ".((int) $idbon);
661 $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED);
662
663 $result = $db->query($sql);
664 if (!$result) {
665 $postactionmessages[] = $db->lasterror();
666 $ispostactionok = -1;
667 $error++;
668 }
669 }
670
671 if (!$error && !empty($idbon)) {
672 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_lignes";
673 $sql .= " SET statut = 2";
674 $sql .= " WHERE fk_prelevement_bons = ".((int) $idbon);
675 $result = $db->query($sql);
676 if (!$result) {
677 $postactionmessages[] = $db->lasterror();
678 $ispostactionok = -1;
679 $error++;
680 }
681 }
682 }
683
684 if (!$error) {
685 $db->commit();
686 } else {
687 $db->rollback();
688 http_response_code(500);
689 return -1;
690 }
691
692 if (getDolGlobalString('STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION')) {
693 $db->begin();
694
695 // If option to send email after confirmation of direct debit is on, we send the email (template must exists
696 $labeltouse = getDolGlobalString('STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION');
697 // Example: $labeltouse = 'InvoicePaymentSuccess'
698
699 $invoice = new Facture($db);
700 $invoice->fetch($invoice_id);
701 $invoice->fetch_thirdparty();
702
703 // Set output language
704 $outputlangs = new Translate('', $conf);
705 $outputlangs->setDefaultLang(empty($invoice->thirdparty->default_lang) ? $mysoc->default_lang : $invoice->thirdparty->default_lang);
706 $outputlangs->loadLangs(array("main", "members", "bills"));
707
708 // Get email content from template
709 $arraydefaultmessage = null;
710
711 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
712 $formmail = new FormMail($db);
713
714 $arraydefaultmessage = $formmail->getEMailTemplate($db, 'facture_send', $user, $outputlangs, 0, 1, $labeltouse);
715
716 $appli = $mysoc->name;
717
718 $subject = '['.$appli.'] Invoice direct debit payment recevied';
719 $msg = 'An invoice direct debit payment for invoice '.$invoice->ref.' has been recevied';
720 if (is_object($arraydefaultmessage) && $arraydefaultmessage->id > 0) {
721 $subject = $arraydefaultmessage->topic;
722 $msg = $arraydefaultmessage->content;
723 }
724
725 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $invoice);
726
727 complete_substitutions_array($substitutionarray, $outputlangs, $object);
728
729 // Set the property ->ref_customer with ref_customer of contract so __REF_CLIENT__ will be replaced in email content
730 // Search contract linked to invoice
731 $foundcontract = null;
732 $invoice->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 1);
733
734 if (is_array($invoice->linkedObjects['contrat']) && count($invoice->linkedObjects['contrat']) > 0) {
735 //dol_sort_array($object->linkedObjects['facture'], 'date');
736 foreach ($invoice->linkedObjects['contrat'] as $contract) {
738 '@phan-var-force Contrat $contract';
739 $substitutionarray['__CONTRACT_REF__'] = $contract->ref_customer;
740 $substitutionarray['__REFCLIENT__'] = $contract->ref_customer; // For backward compatibility
741 $substitutionarray['__REF_CLIENT__'] = $contract->ref_customer;
742 $substitutionarray['__REF_CUSTOMER__'] = $contract->ref_customer;
743 $foundcontract = $contract;
744 break;
745 }
746 }
747
748 dol_syslog('__DIRECTDOWNLOAD_URL_INVOICE__='.$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__']);
749
750 $subjecttosend = make_substitutions($subject, $substitutionarray, $outputlangs);
751 $texttosend = make_substitutions($msg, $substitutionarray, $outputlangs);
752
753 // Attach a file ?
754 $listofpaths = array();
755 $listofnames = array();
756 $listofmimes = array();
757
758 /*
759 $invoicediroutput = $conf->invoice->dir_output;
760 $fileparams = dol_most_recent_file($invoicediroutput . '/' . $invoice->ref, preg_quote($invoice->ref, '/').'[^\-]+');
761 $file = $fileparams['fullname'];
762 $file = ''; // Disable attachment of invoice in emails
763
764 if ($file) {
765 $listofpaths=array($file);
766 $listofnames=array(basename($file));
767 $listofmimes=array(dol_mimetype($file));
768 }
769 */
770
771 $from = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL');
772
773 $trackid = 'inv'.$invoice->id;
774 $moreinheader = 'X-Dolibarr-Info: public stripe ipn.php'."\r\n";
775 $addr_cc = '';
776 if (!empty($invoice->thirdparty->array_options['options_emailccinvoice'])) {
777 dol_syslog("We add the recipient ".$invoice->thirdparty->array_options['options_emailccinvoice']." as CC", LOG_DEBUG);
778 $addr_cc = $invoice->thirdparty->array_options['options_emailccinvoice'];
779 }
780
781 // Send email (substitutionarray must be done just before this)
782 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
783 $mailfile = new CMailFile($subjecttosend, $invoice->thirdparty->email, $from, $texttosend, $listofpaths, $listofmimes, $listofnames, $addr_cc, '', 0, -1, '', '', $trackid, $moreinheader);
784 if (empty($mailfile->error) && $mailfile->sendfile()) {
785 $result = 1;
786
787 dol_syslog("Option STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION: Email sent");
788 dol_syslog("Option STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION: Email sent", LOG_DEBUG, 0, '_payment');
789 } else {
790 $errmsg = 'Option STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION: '.$langs->trans("ErrorFailedToSendMail", $from, $invoice->thirdparty->email).'. '.$mailfile->error;
791
792 dol_syslog($errmsg);
793 dol_syslog($errmsg, LOG_WARNING, 0, '_payment');
794 }
795
796 $db->commit();
797 } else {
798 dol_syslog("Option STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION not set to the template label. No email sent.");
799 dol_syslog("Option STRIPE_IPN_SEND_EMAIL_ON_DIRECT_DEBIT_CONFIRMATION not set to the template label. No email sent.", LOG_DEBUG, 0, '_payment');
800 }
801 } else {
802 dol_syslog("The payment mode of this payment is ".$paymentTypeCode." in Stripe and ".$paymentTypeCodeInDolibarr." in Dolibarr. This case is not managed by the IPN");
803 dol_syslog("The payment mode of this payment is ".$paymentTypeCode." in Stripe and ".$paymentTypeCodeInDolibarr." in Dolibarr. This case is not managed by the IPN", LOG_DEBUG, 0, '_payment');
804 }
805 } else {
806 dol_syslog("Nothing to do in database because we don't know paymentTypeIdInDolibarr");
807 dol_syslog("Nothing to do in database because we don't know paymentTypeIdInDolibarr", LOG_DEBUG, 0, '_payment');
808 }
809} elseif ($event->type == 'payment_intent.payment_failed') {
810 // When a try to take payment has failed. Useful for asynchronous SEPA payment that fails.
811 dol_syslog("A try to make a payment has failed");
812 dol_syslog("A try to make a payment has failed", LOG_DEBUG, 0, '_payment');
813
814 $object = $event->data->object;
815 $ipaddress = $object->metadata->ipaddress;
816 $remoteipaddress = getUserRemoteIP();
817 $currencyCodeType = strtoupper($object->currency);
818 $paymentmethodstripeid = $object->payment_method;
819 $customer_id = $object->customer;
820
821 $chargesdataarray = array();
822 $objpayid = '';
823 $objpaydesc = '';
824 $objinvoiceid = 0;
825 $objerrcode = '';
826 $objerrmessage = '';
827 $objpaymentmodetype = '';
828 if (!empty($object->charges)) { // Old format
829 $chargesdataarray = $object->charges->data;
830 foreach ($chargesdataarray as $chargesdata) {
831 $objpayid = $chargesdata->id;
832 $objpaydesc = $chargesdata->description;
833 $objinvoiceid = 0;
834 if ($chargesdata->metadata->dol_type == 'facture') {
835 $objinvoiceid = $chargesdata->metadata->dol_id;
836 }
837 $objerrcode = $chargesdata->outcome->reason;
838 $objerrmessage = $chargesdata->outcome->seller_message;
839
840 $objpaymentmodetype = $chargesdata->payment_method_details->type;
841 break;
842 }
843 }
844 if (!empty($object->last_payment_error)) { // New format 2023-10-16
845 // $object is probably an object of type Stripe\PaymentIntent
846 $objpayid = $object->latest_charge;
847 $objpaydesc = $object->description;
848 $objinvoiceid = 0;
849 if ($object->metadata->dol_type == 'facture') {
850 $objinvoiceid = $object->metadata->dol_id;
851 }
852 $objerrcode = empty($object->last_payment_error->code) ? $object->last_payment_error->decline_code : $object->last_payment_error->code;
853 $objerrmessage = $object->last_payment_error->message;
854
855 $objpaymentmodetype = $object->last_payment_error->payment_method->type;
856 }
857
858 dol_syslog("objpayid=".$objpayid." objpaymentmodetype=".$objpaymentmodetype." objerrcode=".$objerrcode);
859 dol_syslog("objpayid=".$objpayid." objpaymentmodetype=".$objpaymentmodetype." objerrcode=".$objerrcode, LOG_DEBUG, 0, '_payment');
860
861 // If this is a differed payment for SEPA, add a line into agenda events
862 if ($objpaymentmodetype == 'sepa_debit') {
863 $db->begin();
864
865 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
866 $actioncomm = new ActionComm($db);
867
868 if ($objinvoiceid > 0) {
869 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
870 $invoice = new Facture($db);
871 $invoice->fetch($objinvoiceid);
872
873 $actioncomm->userownerid = 0;
874 $actioncomm->percentage = -1;
875
876 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
877 $actioncomm->code = 'AC_PAYMENT_STRIPE_IPN_SEPA_KO';
878
879 $actioncomm->datep = $now;
880 $actioncomm->datef = $now;
881
882 $actioncomm->socid = $invoice->socid;
883 $actioncomm->fk_project = $invoice->fk_project;
884 $actioncomm->elementid = $invoice->id;
885 $actioncomm->elementtype = 'invoice';
886 $actioncomm->ip = getUserRemoteIP();
887 }
888
889 $actioncomm->note_private = 'Stripe Sepa payment error received by IPN service listening webhooks - ' . dol_print_date($now, 'standard') . ' (TZ server) using servicestatus=' . $servicestatus . ($remoteipaddress ? ' remote ip ' . $remoteipaddress : '').($ipaddress ? ' user ip ' . $ipaddress : '').' - Payment id '.$objpayid.' after SEPA payment request '.$objpaydesc.'<br>Error code is: '.$objerrcode.'<br>Error message is: '.$objerrmessage;
890 $actioncomm->label = 'Payment error (SEPA Stripe)';
891
892 $result = $actioncomm->create($user);
893 if ($result <= 0) {
894 dol_syslog($actioncomm->error, LOG_ERR);
895 dol_syslog($actioncomm->error, LOG_ERR, 0, '_payment');
896 $error++;
897 }
898
899 if (! $error) {
900 // Option: Force status of invoice to "dispute on"
901 //$invoice->dispute_status = 1;
902 //$invoice->update($user);
903
904 // Option: Sen email to customer
905 // TODO
906
907 $db->commit();
908 } else {
909 $db->rollback();
910 http_response_code(500);
911 return -1;
912 }
913 }
914} elseif ($event->type == 'checkout.session.completed') { // Called when making payment with new Checkout method (getDolGlobalString('STRIPE_USE_NEW_CHECKOUT') is on).
915 // TODO: create fees
916} elseif ($event->type == 'payment_method.attached') {
917 dol_syslog("object = ".formatLogObject($event->data));
918 dol_syslog("object = ".formatLogObject($event->data), LOG_DEBUG, 0, '_payment');
919
920 // When we link a payment method with a customer on Stripe side
921 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
922 require_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
923 $societeaccount = new SocieteAccount($db);
924
925 $companypaymentmode = new CompanyPaymentMode($db);
926
927 $idthirdparty = $societeaccount->getThirdPartyID($db->escape($event->data->object->customer), 'stripe', $servicestatus);
928 if ($idthirdparty > 0) {
929 // 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),
930 // we can create the payment mode
931 $companypaymentmode->stripe_card_ref = $event->data->object->id;
932 $companypaymentmode->fk_soc = $idthirdparty;
933 $companypaymentmode->bank = null;
934 $companypaymentmode->label = '';
935 $companypaymentmode->number = $event->data->object->id;
936 $companypaymentmode->last_four = $event->data->object->card->last4;
937 $companypaymentmode->card_type = $event->data->object->card->branding;
938
939 $companypaymentmode->owner_name = $event->data->object->billing_details->name;
940 $companypaymentmode->proprio = $companypaymentmode->owner_name; // We may still need this for modulebuilder code because name of field is "proprio"
941
942 $companypaymentmode->exp_date_month = (int) $event->data->object->card->exp_month;
943 $companypaymentmode->exp_date_year = (int) $event->data->object->card->exp_year;
944 $companypaymentmode->cvn = null;
945 $companypaymentmode->datec = $event->data->object->created;
946 $companypaymentmode->default_rib = 0;
947 $companypaymentmode->type = $event->data->object->type;
948 $companypaymentmode->country_code = $event->data->object->card->country;
949 $companypaymentmode->status = $servicestatus;
950
951 // TODO Check that a payment mode $companypaymentmode->stripe_card_ref does not exists yet to avoid to create duplicates
952 // so we can remove the test on STRIPE_NO_DUPLICATE_CHECK
953 if (getDolGlobalString('STRIPE_NO_DUPLICATE_CHECK')) {
954 $db->begin();
955 $result = $companypaymentmode->create($user);
956 if ($result < 0) {
957 $error++;
958 }
959 if (!$error) {
960 $db->commit();
961 } else {
962 $db->rollback();
963 http_response_code(500);
964 return -1;
965 }
966 }
967 }
968} elseif ($event->type == 'payment_method.updated') {
969 dol_syslog("object = ".formatLogObject($event->data));
970 dol_syslog("object = ".formatLogObject($event->data), LOG_DEBUG, 0, '_payment');
971
972 // When we update a payment method on Stripe side
973 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
974 $companypaymentmode = new CompanyPaymentMode($db);
975 $companypaymentmode->fetch(0, '', 0, '', " AND stripe_card_ref = '".$db->escape($event->data->object->id)."'");
976 if ($companypaymentmode->id > 0) {
977 // If we found a payment mode with the ID
978 $companypaymentmode->bank = null;
979 $companypaymentmode->label = '';
980 $companypaymentmode->number = $db->escape($event->data->object->id);
981 $companypaymentmode->last_four = $db->escape($event->data->object->card->last4);
982 $companypaymentmode->proprio = $db->escape($event->data->object->billing_details->name); // deprecated but still needed
983 $companypaymentmode->owner_name = $db->escape($event->data->object->billing_details->name);
984 $companypaymentmode->exp_date_month = (int) $event->data->object->card->exp_month;
985 $companypaymentmode->exp_date_year = (int) $event->data->object->card->exp_year;
986 $companypaymentmode->cvn = null;
987 $companypaymentmode->datec = (int) $event->data->object->created;
988 $companypaymentmode->default_rib = 0;
989 $companypaymentmode->type = $db->escape($event->data->object->type);
990 $companypaymentmode->country_code = $db->escape($event->data->object->card->country);
991 $companypaymentmode->status = $servicestatus;
992
993 $db->begin();
994
995 $result = $companypaymentmode->update($user);
996 if ($result < 0) {
997 $error++;
998 }
999
1000 if (!$error) {
1001 $db->commit();
1002 } else {
1003 $db->rollback();
1004
1005 http_response_code(500);
1006 return -1;
1007 }
1008 }
1009} elseif ($event->type == 'payment_method.detached') {
1010 // When we remove a payment method on Stripe side
1011 $db->begin();
1012 $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_rib WHERE number = '".$db->escape($event->data->object->id)."' and status = ".((int) $servicestatus);
1013 $db->query($sql);
1014 $db->commit();
1015} elseif ($event->type == 'charge.succeeded') {
1016 // Deprecated. TODO: create fees and redirect to paymentok.php
1017} elseif ($event->type == 'charge.failed') {
1018 // Deprecated. TODO: Redirect to paymentko.php
1019} elseif (($event->type == 'source.chargeable') && ($event->data->object->type == 'three_d_secure') && ($event->data->object->three_d_secure->authenticated == true)) {
1020 // Deprecated.
1021} elseif ($event->type == 'charge.dispute.closed') {
1022 // When a dispute to cancel a SEPA payment is finished
1023 dol_syslog("object = ".formatLogObject($event->data));
1024 dol_syslog("object = ".formatLogObject($event->data), LOG_DEBUG, 0, '_payment');
1025} elseif ($event->type == 'charge.dispute.funds_withdrawn') {
1026 // When a dispute/withdraw to cancel a payment (card or SEPA) is done
1027 dol_syslog("object = ".formatLogObject($event->data));
1028 dol_syslog("object = ".formatLogObject($event->data), LOG_DEBUG, 0, '_payment');
1029
1030 global $stripearrayofkeysbyenv;
1031 $error = 0;
1032 $errormsg = '';
1033 $object = $event->data->object;
1034 $TRANSACTIONID = $object->payment_intent;
1035 $ipaddress = $object->metadata->ipaddress;
1036 $remoteipaddress = getUserRemoteIP();
1037 $now = dol_now();
1038 $currencyCodeType = strtoupper($object->currency);
1039 $paymentmethodstripeid = $object->payment_method;
1040 $customer_id = $object->customer;
1041 $reason = $object->reason;
1042 $amountdisputestripe = $object->amount; // In stripe format
1043 $amountdispute = $stripe->convertAmount($amountdisputestripe, $currencyCodeType, 1); // In real currency format
1044 $statusdispute = $object->status;
1045
1046 $pkey = '';
1047 if (isset($stripearrayofkeysbyenv[$servicestatus]['publishable_key'])) {
1048 $pkey = $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1049 }
1050 $LONGTRANSACTIONID = $TRANSACTIONID.':'.$customer_id.'@'.$pkey;
1051
1052 // Get the amount of fees for the dispute
1053 $balance_transactions_array = $object->balance_transactions;
1054 $feesstripe = 0;
1055 if (!empty($balance_transactions_array) && is_array($balance_transactions_array)) {
1056 foreach ($balance_transactions_array as $tmpval) {
1057 if (isset($tmpval['fee'])) {
1058 $feesstripe += (int) $tmpval['fee']; // In stripe format
1059 }
1060 }
1061 }
1062 $fees = $stripe->convertAmount($feesstripe, $currencyCodeType, 1); // In real currency format
1063
1064 $invoice_id = 0;
1065 $paymentTypeCode = ""; // payment type according to Stripe
1066 $paymentTypeCodeInDolibarr = ""; // payment type according to Dolibarr
1067 $payment_amount = 0;
1068 $payment_amountInDolibarr = 0;
1069
1070 dol_syslog("Try to find the payment in database for the payment_intent id = ".$TRANSACTIONID);
1071 dol_syslog("Try to find the payment in database for the payment_intent id = ".$TRANSACTIONID, LOG_DEBUG, 0, '_payment');
1072
1073 $sql = "SELECT pi.rowid, pi.fk_facture, pi.fk_prelevement_bons, pi.amount, pi.type, pi.traite";
1074 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pi";
1075 //$sql .= " WHERE pi.ext_payment_id = '".$db->escape($TRANSACTIONID)."'";
1076 $sql .= " WHERE (pi.ext_payment_id = '".$db->escape($LONGTRANSACTIONID)."' OR pi.ext_payment_id = '".$db->escape($TRANSACTIONID)."')";
1077 $sql .= " AND pi.ext_payment_site = '".$db->escape($service)."'";
1078
1079 $result = $db->query($sql);
1080 if ($result) {
1081 $obj = $db->fetch_object($result);
1082 if ($obj) {
1083 if ($obj->type == 'ban') {
1084 // This is a direct-debit with an order (llx_bon_prelevement).
1085 $pdid = $obj->rowid;
1086 $invoice_id = $obj->fk_facture;
1087 $directdebitorcreditransfer_id = $obj->fk_prelevement_bons;
1088 $payment_amountInDolibarr = $obj->amount;
1089 $paymentTypeCodeInDolibarr = $obj->type;
1090
1091 dol_syslog("Found the payment intent for ban in database (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")");
1092 dol_syslog("Found the payment intent for ban in database (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")", LOG_DEBUG, 0, '_payment');
1093 }
1094 if ($obj->type == 'card' || empty($obj->type)) {
1095 // This is a card payment.
1096 $pdid = $obj->rowid;
1097 $invoice_id = $obj->fk_facture;
1098 $directdebitorcreditransfer_id = 0;
1099 $payment_amountInDolibarr = $obj->amount;
1100 $paymentTypeCodeInDolibarr = empty($obj->type) ? 'card' : $obj->type;
1101
1102 dol_syslog("Found the payment intent for card in database (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")");
1103 dol_syslog("Found the payment intent for card in database (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")", LOG_DEBUG, 0, '_payment');
1104 }
1105 } else {
1106 dol_syslog("Payment intent ".$TRANSACTIONID." not found into database, so ignored.");
1107 dol_syslog("Payment intent ".$TRANSACTIONID." not found into database, so ignored.", LOG_DEBUG, 0, '_payment');
1108 http_response_code(200);
1109 print "Payment intent ".$TRANSACTIONID." not found into database, so ignored.";
1110 return 1;
1111 }
1112 } else {
1113 http_response_code(500);
1114 print $db->lasterror();
1115 return -1;
1116 }
1117
1118 dol_syslog("objinvoiceid=".$invoice_id);
1119 dol_syslog("objinvoiceid=".$invoice_id, LOG_DEBUG, 0, '_payment');
1120 $tmpinvoice = new Facture($db);
1121 $tmpinvoice->fetch($invoice_id);
1122 $tmpinvoice->fetch_thirdparty();
1123
1124 dol_syslog("The payment disputed has the amount ".$amountdispute.", fees of ".$fees." and the invoice has ".$payment_amountInDolibarr);
1125 dol_syslog("The payment disputed has the amount ".$amountdispute.", fees of ".$fees." and the invoice has ".$payment_amountInDolibarr, LOG_DEBUG, 0, '_payment');
1126
1127 // Amount may differ: sometimes amount for chargback is higher or lower than initial amount. No explanation (may be currencyrate ?)
1128 // So we disable this protection
1129 /*
1130 if ($amountdispute != $payment_amountInDolibarr) {
1131 http_response_code(500);
1132 print "The payment disputed has the amount ".$amountdispute." and the invoice has ".$payment_amountInDolibarr.". Amount is too different, we don't know what to do.";
1133 dol_syslog("Amount differs, we don't know what to do - Return HTTP 500.", LOG_WARNING, 0, '_payment');
1134 http_response_code(500);
1135 return -1;
1136 }
1137 */
1138
1139 if ($statusdispute == 'needs_response') {
1140 // Payment is disputed, but not yet refunded.
1141 $db->begin();
1142
1143 // If invoice was closed, we reopen it
1144 if ($tmpinvoice->status == Facture::STATUS_CLOSED) {
1145 // Switch back the invoice to status validated
1146 $result = $tmpinvoice->setStatut(Facture::STATUS_VALIDATED, null, '', 'none'); // Trigger will be run later
1147 if ($result < 0) {
1148 $errormsg = $tmpinvoice->error.implode(', ', $tmpinvoice->errors);
1149 $error++;
1150 }
1151 }
1152
1153 /* disabled, a record should already be done with the invoice update
1154 $actioncomm = new ActionComm($db);
1155 $actioncode = 'OTHER';
1156
1157 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1158 $actioncomm->code = 'AC_'.$actioncode;
1159 $actioncomm->label = 'Payment dispute has been received by Stripe';
1160 $actioncomm->note_private = 'Payment dispute has been received by Stripe';
1161 $actioncomm->fk_project = 0;
1162 $actioncomm->datep = $now;
1163 $actioncomm->datef = $now;
1164 $actioncomm->percentage = -1; // Not applicable
1165 $actioncomm->socid = $tmpinvoice->thirdparty->id;
1166 $actioncomm->contact_id = 0;
1167 $actioncomm->authorid = $user->id; // User saving action
1168 $actioncomm->userownerid = $user->id; // Owner of action
1169
1170 $actioncomm->elementid = $tmpinvoice->id;
1171 $actioncomm->elementtype = $tmpinvoice->element;
1172
1173 $actioncomm->create($user);
1174 */
1175
1176 // Add a flag "dispute_status" in invoice table to Dispute Open
1177 $result = $tmpinvoice->setStatut(Facture::STATUS_VALIDATED, null, '', 'FACTURE_MODIFY', 'dispute_status');
1178 if ($result < 0) {
1179 $errormsg = $tmpinvoice->error.implode(', ', $tmpinvoice->errors);
1180 $error++;
1181 }
1182
1183 if (!$error) {
1184 $db->commit();
1185
1186 dol_syslog("The dispute_status of invoice ".$tmpinvoice->ref." has been modified to 1");
1187 dol_syslog("The dispute_status of invoice ".$tmpinvoice->ref." has been modified to 1", LOG_DEBUG, 0, '_payment');
1188
1189 http_response_code(200);
1190 print "Payment dispute received for ".$TRANSACTIONID.". We have changed the status of dispute_status to 1 for invoice ".$tmpinvoice->ref;
1191 return 1;
1192 } else {
1193 $db->rollback();
1194
1195 dol_syslog("Technicalerror ".$db->lasterror()." - ".$errormsg, LOG_ERR);
1196 dol_syslog("Technicalerror ".$db->lasterror()." - ".$errormsg, LOG_ERR, 0, '_payment');
1197
1198 http_response_code(500);
1199 print $db->lasterror();
1200 return -1;
1201 }
1202 } else {
1203 // Payment dispute is confirmed and refunded.
1204 $accountfrom = new Account($db);
1205 $accountfrom->fetch(getDolGlobalInt('STRIPE_BANK_ACCOUNT_FOR_PAYMENTS'));
1206
1207 // Now we add a negative payment
1208 $paiement = new Paiement($db);
1209
1210 $amounts = array();
1211 $amounts[$tmpinvoice->id] = -1 * $payment_amountInDolibarr;
1212
1213 $paiement->datepaye = dol_now();
1214 $paiement->amounts = $amounts; // Array with all payments dispatching with invoice id
1215 /*$paiement->multicurrency_amounts = $multicurrency_amounts; // Array with all payments dispatching
1216 $paiement->multicurrency_code = $multicurrency_code; // Array with all currency of payments dispatching
1217 $paiement->multicurrency_tx = $multicurrency_tx; // Array with all currency tx of payments dispatching
1218 */
1219 $paiement->paiementid = dol_getIdFromCode($db, 'PRE', 'c_paiement', 'code', 'id', 1);
1220 $paiement->num_payment = $object->id; // A string like 'du_...'
1221
1222 $paiement->note_private = 'Stripe fund withdrawn message received by IPN service listening webhooks - ' . dol_print_date($now, 'standard') . ' (TZ server) using servicestatus=' . $servicestatus . ($remoteipaddress ? ' remote ip ' . $remoteipaddress : '').($ipaddress ? ' user ip ' . $ipaddress : '');
1223 $paiement->note_private .= ' - Fund withdrawn by bank with id='.$object->id.'. Reason: '.$reason.'. A fee of '.$fees.' may have been charged by Stripe.';
1224
1225 $paiement->fk_account = $accountfrom->id;
1226
1227 $paiement->ext_payment_id = $object->payment_intent;
1228 $paiement->ext_payment_site = $service;
1229
1230 $db->begin();
1231
1232 $alreadytransferedinaccounting = $tmpinvoice->getVentilExportCompta();
1233
1234 dol_syslog("The invoice has alreadytransferedinaccounting=".$alreadytransferedinaccounting);
1235 dol_syslog("The invoice has alreadytransferedinaccounting=".$alreadytransferedinaccounting, LOG_DEBUG, 0, '_payment');
1236
1237 /*
1238 if ($alreadytransferedinaccounting) {
1239 // TODO Test if invoice already in accountancy.
1240 // If yes, what to do ?
1241 $errormsg = 'Error: the invoice '.$tmpinvoice->id.' is already transferred into accounting. Don\'t know what to do.';
1242 $error++;
1243 }
1244 */
1245
1246 if (!$error && !$alreadytransferedinaccounting && $tmpinvoice->status == Facture::STATUS_CLOSED) {
1247 // Switch back the invoice to status validated
1248 $result = $tmpinvoice->setStatut(Facture::STATUS_VALIDATED, null, '', 'none');
1249 if ($result < 0) {
1250 $errormsg = $tmpinvoice->error.implode(', ', $tmpinvoice->errors);
1251 $error++;
1252 }
1253 }
1254
1255 if (!$error) {
1256 // Add status dispute_status to Dispute Open
1257 $result = $tmpinvoice->setStatut(Facture::STATUS_VALIDATED, null, '', 'FACTURE_MODIFY', 'dispute_status');
1258 if ($result < 0) {
1259 $errormsg = $tmpinvoice->error.implode(', ', $tmpinvoice->errors);
1260 $error++;
1261 }
1262
1263 if (!$error) {
1264 dol_syslog("The dispute_status of invoice ".$tmpinvoice->ref." has been modified to 1");
1265 dol_syslog("The dispute_status of invoice ".$tmpinvoice->ref." has been modified to 1", LOG_DEBUG, 0, '_payment');
1266 }
1267 }
1268
1269 // Check that a withdrawn payment does not already exists for the withdrawn (if IPN is sent twice by Stripe)
1270 $withdrawn_payment_already_exists = true; // By default, we assume that it exists
1271 $sql = "SELECT p.rowid, p.ref";
1272 $sql .= " FROM ".MAIN_DB_PREFIX."paiement as p";
1273 $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'";
1274 $sql .= " AND p.ext_payment_site = '".$db->escape($service)."'";
1275
1276 $tmpresql = $db->query($sql);
1277 if ($tmpresql) {
1278 $obj = $db->fetch_object($tmpresql);
1279 if (empty($obj)) {
1280 $withdrawn_payment_already_exists = false;
1281 dol_syslog("No withdraw payment already exists", LOG_DEBUG);
1282 } else {
1283 dol_syslog("A withdraw payment already exists", LOG_DEBUG);
1284 }
1285 }
1286
1287 if (!$error && !$alreadytransferedinaccounting && !$withdrawn_payment_already_exists) {
1288 if ($paiement->fk_account > 0) {
1289 // If not yet in accountnacy, we can record the negative payment, otherwise, only the dispute status will be set and user
1290 // will have to make manual correction like a credit note.
1291 dol_syslog("We try to record the payment");
1292 dol_syslog("We try to record the payment", LOG_DEBUG, 0, '_payment');
1293
1294 $paiement_id = $paiement->create($user, 0, $tmpinvoice->thirdparty); // This include regenerating documents
1295 if ($paiement_id < 0) {
1296 $errormsg = $paiement->error.implode(', ', $paiement->errors);
1297 $error++;
1298 } else {
1299 $banklineid = $paiement->addPaymentToBank($user, 'payment', 'IPN Stripe dispute funds withdrawn', $paiement->fk_account, '', '', 1, '', '');
1300 if ($banklineid < 0) {
1301 $errormsg = $paiement->error.implode(', ', $paiement->errors);
1302 $error++;
1303 }
1304 }
1305 } else {
1306 dol_syslog("No bank account defined to record payment so no payment recorded");
1307 dol_syslog("No bank account defined to record payment so no payment recorded", LOG_DEBUG, 0, '_payment');
1308 }
1309 }
1310
1311 if (!$error) {
1312 // TODO
1313 // Record a payment for Stripe fees ?
1314 }
1315
1316 if (!$error) {
1317 $db->commit();
1318 //$db->rollback();
1319 //http_response_code(500);
1320
1321 dol_syslog("Invoice status updated and/or Revert payment created", LOG_WARNING);
1322 dol_syslog("Invoice status updated and/or Revert payment created", LOG_WARNING, 0, '_payment');
1323 } else {
1324 $db->rollback();
1325
1326 dol_syslog("Error - Return HTTP 500 - ".$errormsg, LOG_ERR);
1327 dol_syslog("Error - Return HTTP 500 - ".$errormsg, LOG_ERR, 0, '_payment');
1328
1329 http_response_code(500);
1330
1331 print $errormsg;
1332 return -1;
1333 }
1334 }
1335}
1336
1337
1338// End of page. Default return HTTP code will be 200
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
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.
const STATUS_VALIDATED
Validated (need to be paid)
const STATUS_CLOSED
Classified paid.
Class to manage a HTML form to send a unitary email Usage: $formail = new FormMail($db) $formmail->pr...
Class to manage hooks.
Class to manage payments of customer invoices.
Class for SocieteAccount.
Stripe class @TODO No reason to extend CommonObject.
Class to manage translations.
Class to manage Dolibarr users.
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_now($mode='gmt')
Return date for now.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
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.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
getDolCurrency()
Return the main currency ('EUR', 'USD', ...)
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getUserRemoteIP($trusted=0)
Return the real IP of remote user.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
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.
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:487
httponly_accessforbidden($message='1', $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.