dolibarr  17.0.4
ipn.php
1 <?php
2 /* Copyright (C) 2018-2020 Thibault FOUCART <support@ptibogxiv.net>
3  * Copyright (C) 2018 Frédéric France <frederic.france@netlogic.fr>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <https://www.gnu.org/licenses/>.
17  */
18 
19 if (!defined('NOLOGIN')) {
20  define("NOLOGIN", 1); // This means this output page does not require to be logged.
21 }
22 if (!defined('NOCSRFCHECK')) {
23  define("NOCSRFCHECK", 1); // We accept to go on this page from external web site.
24 }
25 if (!defined('NOIPCHECK')) {
26  define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip
27 }
28 if (!defined('NOBROWSERNOTIF')) {
29  define('NOBROWSERNOTIF', '1');
30 }
31 
32 $entity = (!empty($_GET['entity']) ? (int) $_GET['entity'] : (!empty($_POST['entity']) ? (int) $_POST['entity'] : 1));
33 if (is_numeric($entity)) {
34  define("DOLENTITY", $entity);
35 }
36 
37 // Load Dolibarr environment
38 require '../../main.inc.php';
39 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
40 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
41 require_once DOL_DOCUMENT_ROOT.'/core/class/ccountry.class.php';
42 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
43 require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
44 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
45 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
46 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
47 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
48 
49 require_once DOL_DOCUMENT_ROOT.'/includes/stripe/stripe-php/init.php';
50 require_once DOL_DOCUMENT_ROOT.'/stripe/class/stripe.class.php';
51 
52 
53 // You can find your endpoint's secret in your webhook settings
54 if (isset($_GET['connect'])) {
55  if (isset($_GET['test'])) {
56  $endpoint_secret = $conf->global->STRIPE_TEST_WEBHOOK_CONNECT_KEY;
57  $service = 'StripeTest';
58  $servicestatus = 0;
59  } else {
60  $endpoint_secret = $conf->global->STRIPE_LIVE_WEBHOOK_CONNECT_KEY;
61  $service = 'StripeLive';
62  $servicestatus = 1;
63  }
64 } else {
65  if (isset($_GET['test'])) {
66  $endpoint_secret = $conf->global->STRIPE_TEST_WEBHOOK_KEY;
67  $service = 'StripeTest';
68  $servicestatus = 0;
69  } else {
70  $endpoint_secret = $conf->global->STRIPE_LIVE_WEBHOOK_KEY;
71  $service = 'StripeLive';
72  $servicestatus = 1;
73  }
74 }
75 
76 if (!isModEnabled('stripe')) {
77  httponly_accessforbidden('Module Stripe not enabled');
78 }
79 
80 if (empty($endpoint_secret)) {
81  httponly_accessforbidden('Error: Setup of module Stripe not complete for mode '.dol_escape_htmltag($service).'. The WEBHOOK_KEY is not defined.', 400, 1);
82 }
83 
84 if (!empty($conf->global->STRIPE_USER_ACCOUNT_FOR_ACTIONS)) {
85  // We set the user to use for all ipn actions in Dolibarr
86  $user = new User($db);
87  $user->fetch($conf->global->STRIPE_USER_ACCOUNT_FOR_ACTIONS);
88  $user->getrights();
89 } else {
90  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);
91 }
92 
93 
94 // TODO Add a check on a security key
95 
96 
97 
98 /*
99  * Actions
100  */
101 
102 $payload = @file_get_contents("php://input");
103 $sig_header = $_SERVER["HTTP_STRIPE_SIGNATURE"];
104 $event = null;
105 
106 $error = 0;
107 
108 try {
109  $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $endpoint_secret);
110 } catch (\UnexpectedValueException $e) {
111  // Invalid payload
112  httponly_accessforbidden('Invalid payload', 400);
113 } catch (\Stripe\Error\SignatureVerification $e) {
114  httponly_accessforbidden('Invalid signature', 400);
115 }
116 
117 // Do something with $event
118 
119 $langs->load("main");
120 
121 
122 if (isModEnabled('multicompany') && !empty($conf->stripeconnect->enabled) && is_object($mc)) {
123  $sql = "SELECT entity";
124  $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token";
125  $sql .= " WHERE service = '".$db->escape($service)."' and tokenstring LIKE '%".$db->escape($event->account)."%'";
126 
127  dol_syslog(get_class($db)."::fetch", LOG_DEBUG);
128  $result = $db->query($sql);
129  if ($result) {
130  if ($db->num_rows($result)) {
131  $obj = $db->fetch_object($result);
132  $key = $obj->entity;
133  } else {
134  $key = 1;
135  }
136  } else {
137  $key = 1;
138  }
139  $ret = $mc->switchEntity($key);
140 }
141 
142 // list of action
143 $stripe = new Stripe($db);
144 
145 // Subject
146 $societeName = $conf->global->MAIN_INFO_SOCIETE_NOM;
147 if (!empty($conf->global->MAIN_APPLICATION_TITLE)) {
148  $societeName = $conf->global->MAIN_APPLICATION_TITLE;
149 }
150 
151 top_httphead();
152 
153 dol_syslog("***** Stripe IPN was called with event->type = ".$event->type);
154 
155 
156 if ($event->type == 'payout.created') {
157  $error = 0;
158 
159  $result = dolibarr_set_const($db, $service."_NEXTPAYOUT", date('Y-m-d H:i:s', $event->data->object->arrival_date), 'chaine', 0, '', $conf->entity);
160 
161  if ($result > 0) {
162  $subject = $societeName.' - [NOTIFICATION] Stripe payout scheduled';
163  if (!empty($user->email)) {
164  $sendto = dolGetFirstLastname($user->firstname, $user->lastname)." <".$user->email.">";
165  } else {
166  $sendto = $conf->global->MAIN_INFO_SOCIETE_MAIL.'" <'.$conf->global->MAIN_INFO_SOCIETE_MAIL.'>';
167  }
168  $replyto = $sendto;
169  $sendtocc = '';
170  if (!empty($conf->global->ONLINE_PAYMENT_SENDEMAIL)) {
171  $sendtocc = $conf->global->ONLINE_PAYMENT_SENDEMAIL.'" <'.$conf->global->ONLINE_PAYMENT_SENDEMAIL.'>';
172  }
173 
174  $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');
175 
176  $mailfile = new CMailFile(
177  $subject,
178  $sendto,
179  $replyto,
180  $message,
181  array(),
182  array(),
183  array(),
184  $sendtocc,
185  '',
186  0,
187  -1
188  );
189 
190  $ret = $mailfile->sendfile();
191 
192  return 1;
193  } else {
194  $error++;
195  http_response_code(500);
196  return -1;
197  }
198 } elseif ($event->type == 'payout.paid') {
199  global $conf;
200  $error = 0;
201  $result = dolibarr_set_const($db, $service."_NEXTPAYOUT", null, 'chaine', 0, '', $conf->entity);
202  if ($result) {
203  $langs->load("errors");
204 
205  $dateo = dol_now();
206  $label = $event->data->object->description;
207  $amount = $event->data->object->amount / 100;
208  $amount_to = $event->data->object->amount / 100;
209  require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
210 
211  $accountfrom = new Account($db);
212  $accountfrom->fetch($conf->global->STRIPE_BANK_ACCOUNT_FOR_PAYMENTS);
213 
214  $accountto = new Account($db);
215  $accountto->fetch($conf->global->STRIPE_BANK_ACCOUNT_FOR_BANKTRANSFERS);
216 
217  if (($accountto->id != $accountfrom->id) && empty($error)) {
218  $bank_line_id_from = 0;
219  $bank_line_id_to = 0;
220  $result = 0;
221 
222  // By default, electronic transfert from bank to bank
223  $typefrom = 'PRE';
224  $typeto = 'VIR';
225 
226  if (!$error) {
227  $bank_line_id_from = $accountfrom->addline($dateo, $typefrom, $label, -1 * price2num($amount), '', '', $user);
228  }
229  if (!($bank_line_id_from > 0)) {
230  $error++;
231  }
232  if (!$error) {
233  $bank_line_id_to = $accountto->addline($dateo, $typeto, $label, price2num($amount), '', '', $user);
234  }
235  if (!($bank_line_id_to > 0)) {
236  $error++;
237  }
238 
239  if (!$error) {
240  $result = $accountfrom->add_url_line($bank_line_id_from, $bank_line_id_to, DOL_URL_ROOT.'/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert');
241  }
242  if (!($result > 0)) {
243  $error++;
244  }
245  if (!$error) {
246  $result = $accountto->add_url_line($bank_line_id_to, $bank_line_id_from, DOL_URL_ROOT.'/compta/bank/line.php?rowid=', '(banktransfert)', 'banktransfert');
247  }
248  if (!($result > 0)) {
249  $error++;
250  }
251  }
252 
253  $subject = $societeName.' - [NOTIFICATION] Stripe payout done';
254  if (!empty($user->email)) {
255  $sendto = dolGetFirstLastname($user->firstname, $user->lastname)." <".$user->email.">";
256  } else {
257  $sendto = $conf->global->MAIN_INFO_SOCIETE_MAIL.'" <'.$conf->global->MAIN_INFO_SOCIETE_MAIL.'>';
258  }
259  $replyto = $sendto;
260  $sendtocc = '';
261  if (!empty($conf->global->ONLINE_PAYMENT_SENDEMAIL)) {
262  $sendtocc = $conf->global->ONLINE_PAYMENT_SENDEMAIL.'" <'.$conf->global->ONLINE_PAYMENT_SENDEMAIL.'>';
263  }
264 
265  $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');
266 
267  $mailfile = new CMailFile(
268  $subject,
269  $sendto,
270  $replyto,
271  $message,
272  array(),
273  array(),
274  array(),
275  $sendtocc,
276  '',
277  0,
278  -1
279  );
280 
281  $ret = $mailfile->sendfile();
282 
283  return 1;
284  } else {
285  $error++;
286  http_response_code(500);
287  return -1;
288  }
289 } elseif ($event->type == 'customer.source.created') {
290  //TODO: save customer's source
291 } elseif ($event->type == 'customer.source.updated') {
292  //TODO: update customer's source
293 } elseif ($event->type == 'customer.source.delete') {
294  //TODO: delete customer's source
295 } elseif ($event->type == 'customer.deleted') {
296  $db->begin();
297  $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_account WHERE key_account = '".$db->escape($event->data->object->id)."' and site='stripe'";
298  $db->query($sql);
299  $db->commit();
300 } elseif ($event->type == 'payment_intent.succeeded') { // Called when making payment with PaymentIntent method ($conf->global->STRIPE_USE_NEW_CHECKOUT is on).
301  // TODO: create fees
302  // TODO: Redirect to paymentok.php
303 } elseif ($event->type == 'payment_intent.payment_failed') {
304  // TODO: Redirect to paymentko.php
305 } elseif ($event->type == 'checkout.session.completed') { // Called when making payment with new Checkout method ($conf->global->STRIPE_USE_NEW_CHECKOUT is on).
306  // TODO: create fees
307  // TODO: Redirect to paymentok.php
308 } elseif ($event->type == 'payment_method.attached') {
309  require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
310  require_once DOL_DOCUMENT_ROOT.'/societe/class/societeaccount.class.php';
311  $societeaccount = new SocieteAccount($db);
312 
313  $companypaymentmode = new CompanyPaymentMode($db);
314 
315  $idthirdparty = $societeaccount->getThirdPartyID($db->escape($event->data->object->customer), 'stripe', $servicestatus);
316  if ($idthirdparty > 0) { // If the payment mode is on an external customer that is known in societeaccount, we can create the payment mode
317  $companypaymentmode->stripe_card_ref = $db->escape($event->data->object->id);
318  $companypaymentmode->fk_soc = $idthirdparty;
319  $companypaymentmode->bank = null;
320  $companypaymentmode->label = null;
321  $companypaymentmode->number = $db->escape($event->data->object->id);
322  $companypaymentmode->last_four = $db->escape($event->data->object->card->last4);
323  $companypaymentmode->card_type = $db->escape($event->data->object->card->branding);
324  $companypaymentmode->proprio = $db->escape($event->data->object->billing_details->name);
325  $companypaymentmode->exp_date_month = $db->escape($event->data->object->card->exp_month);
326  $companypaymentmode->exp_date_year = $db->escape($event->data->object->card->exp_year);
327  $companypaymentmode->cvn = null;
328  $companypaymentmode->datec = $db->escape($event->data->object->created);
329  $companypaymentmode->default_rib = 0;
330  $companypaymentmode->type = $db->escape($event->data->object->type);
331  $companypaymentmode->country_code = $db->escape($event->data->object->card->country);
332  $companypaymentmode->status = $servicestatus;
333 
334  $db->begin();
335  if (!$error) {
336  $result = $companypaymentmode->create($user);
337  if ($result < 0) {
338  $error++;
339  }
340  }
341  if (!$error) {
342  $db->commit();
343  } else {
344  $db->rollback();
345  }
346  }
347 } elseif ($event->type == 'payment_method.updated') {
348  require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
349  $companypaymentmode = new CompanyPaymentMode($db);
350  $companypaymentmode->fetch(0, '', 0, '', " AND stripe_card_ref = '".$db->escape($event->data->object->id)."'");
351  $companypaymentmode->bank = null;
352  $companypaymentmode->label = null;
353  $companypaymentmode->number = $db->escape($event->data->object->id);
354  $companypaymentmode->last_four = $db->escape($event->data->object->card->last4);
355  $companypaymentmode->proprio = $db->escape($event->data->object->billing_details->name);
356  $companypaymentmode->exp_date_month = $db->escape($event->data->object->card->exp_month);
357  $companypaymentmode->exp_date_year = $db->escape($event->data->object->card->exp_year);
358  $companypaymentmode->cvn = null;
359  $companypaymentmode->datec = $db->escape($event->data->object->created);
360  $companypaymentmode->default_rib = 0;
361  $companypaymentmode->type = $db->escape($event->data->object->type);
362  $companypaymentmode->country_code = $db->escape($event->data->object->card->country);
363  $companypaymentmode->status = $servicestatus;
364 
365  $db->begin();
366  if (!$error) {
367  $result = $companypaymentmode->update($user);
368  if ($result < 0) {
369  $error++;
370  }
371  }
372  if (!$error) {
373  $db->commit();
374  } else {
375  $db->rollback();
376  }
377 } elseif ($event->type == 'payment_method.detached') {
378  $db->begin();
379  $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_rib WHERE number = '".$db->escape($event->data->object->id)."' and status = ".((int) $servicestatus);
380  $db->query($sql);
381  $db->commit();
382 } elseif ($event->type == 'charge.succeeded') {
383  // TODO: create fees
384  // TODO: Redirect to paymentok.php
385 } elseif ($event->type == 'charge.failed') {
386  // TODO: Redirect to paymentko.php
387 } elseif (($event->type == 'source.chargeable') && ($event->data->object->type == 'three_d_secure') && ($event->data->object->three_d_secure->authenticated == true)) {
388  // This event is deprecated.
389 }
390 
391 // End of page. Default return HTTP code will be 200
dolibarr_set_const($db, $name, $value, $type='chaine', $visible=0, $note='', $entity=1)
Insert a parameter (key,value) into database (delete old key then insert it again).
Definition: admin.lib.php:632
Class to manage bank accounts.
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Class for CompanyPaymentMode.
Class for SocieteAccount.
Stripe class.
Class to manage Dolibarr users.
Definition: user.class.php:47
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0)
Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
if(!defined('NOREQUIREMENU')) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
Definition: main.inc.php:1440
httponly_accessforbidden($message=1, $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.