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