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