diff --git a/htdocs/public/stripe/ipn.php b/htdocs/public/stripe/ipn.php index 9b40c0bbaf8..9fbca784257 100644 --- a/htdocs/public/stripe/ipn.php +++ b/htdocs/public/stripe/ipn.php @@ -1,6 +1,7 @@ - * Copyright (C) 2018 Frédéric France +/* Copyright (C) 2018-2020 Thibault FOUCART + * Copyright (C) 2018 Fédéric France + * Copyright (C) 2023 Laurent Destailleur * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -326,30 +327,53 @@ if ($event->type == 'payout.created') { $paymentmethodstripeid = $object->payment_method; $customer_id = $object->customer; $invoice_id = ""; - $paymentTypeId = ""; + $paymentTypeId = ""; // payment type according to Stripe + $paymentTypeIdInDolibarr = ""; // payment type according to Dolibarr $payment_amount = 0; + $payment_amountInDolibarr = 0; - dol_syslog("Try to find the payment in database for the payment_intent id = ".$TRANSACTIONID); + dol_syslog("Try to find a payment in database for the payment_intent id = ".$TRANSACTIONID); - $sql = "SELECT pi.rowid, pi.fk_facture, pi.fk_prelevement_bons, pi.amount, pi.type"; + $sql = "SELECT pi.rowid, pi.fk_facture, pi.fk_prelevement_bons, pi.amount, pi.type, pi.traite"; $sql .= " FROM llx_prelevement_demande as pi"; $sql .= " WHERE pi.ext_payment_id = '".$db->escape($TRANSACTIONID)."'"; - $sql .= " AND pi.traite = '1'"; + //$sql .= " AND pi.type = 'ban' AND pi.traite = '1'"; $sql .= " AND pi.ext_payment_site = '".$db->escape($service)."'"; $result = $db->query($sql); if ($result) { $obj = $db->fetch_object($result); if ($obj) { - $pdid = $obj->rowid; - $invoice_id = $obj->fk_facture; - $directdebitorcreditransfer_id = $obj->fk_prelevement_bons; - $payment_amount = $obj->amount; - $paymentTypeId = $obj->type; + if ($obj->type == 'ban') { + if ($obj->traite == 1) { + // This is a direct-debit with an order (llx_bon_prelevement) ALREADY generated, so + // it means we received here the confirmation that payment request is finished. + $pdid = $obj->rowid; + $invoice_id = $obj->fk_facture; + $directdebitorcreditransfer_id = $obj->fk_prelevement_bons; + $payment_amountInDolibarr = $obj->amount; + $paymentTypeIdInDolibarr = $obj->type; - dol_syslog("Found a request in database to pay with direct debit pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id); + dol_syslog("Found a request in database to pay with direct debit generated (pdid = ".$pdid." directdebitorcreditransfer_id=".$directdebitorcreditransfer_id.")"); + } else { + 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); + } + } + if ($obj->type == 'card' || empty($obj->type)) { + if ($obj->traite == 0) { + // This is a card payment not already flagged as sent to Stripe. + $pdid = $obj->rowid; + $invoice_id = $obj->fk_facture; + $payment_amountInDolibarr = $obj->amount; + $paymentTypeIdInDolibarr = $obj->type; + + dol_syslog("Found a request in database to pay with card (pdid = ".$pdid."). We should fix status traite to 1"); + } else { + dol_syslog("Found a request in database to pay with card (pdid = ".$pdid.") already set to traite=1. Nothing to fix."); + } + } } else { - dol_syslog("Not found"); + dol_syslog("Payment intent not found into database, so ignored."); print "Payment intent not found into database, so ignored."; http_response_code(200); return 1; @@ -360,183 +384,199 @@ if ($event->type == 'payout.created') { return -1; } - // Here a $invoice_id has been found. + if ($paymentTypeIdInDolibarr) { + // Here, we need to do something. A $invoice_id has been found. - $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key']; + $stripeacc = $stripearrayofkeysbyenv[$servicestatus]['secret_key']; - dol_syslog("Get the Stripe payment object for the payment method id = ".json_encode($paymentmethodstripeid)); + dol_syslog("Get the Stripe payment object for the payment method id = ".json_encode($paymentmethodstripeid)); - $s = new \Stripe\StripeClient($stripeacc); + $s = new \Stripe\StripeClient($stripeacc); - $paymentmethodstripe = $s->paymentMethods->retrieve($paymentmethodstripeid); - $paymentTypeId = $paymentmethodstripe->type; - if ($paymentTypeId == "ban" || $paymentTypeId == "sepa_debit") { - $paymentTypeId = "PRE"; - } elseif ($paymentTypeId == "card") { - $paymentTypeId = "CB"; - } - - if ($paymentTypeId == "PRE") { - $paiement = new Paiement($db); - $paiement->datepaye = $now; - $paiement->date = $now; - if ($currencyCodeType == $conf->currency) { - $paiement->amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching with invoice id - } else { - $paiement->multicurrency_amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching - - $postactionmessages[] = 'Payment was done in a different currency than currency expected of company'; - $ispostactionok = -1; - // Not yet supported, so error - $error++; - } - $paiement->paiementid = $paymentTypeId; - $paiement->num_payment = ''; - $paiement->note_public = ''; - $paiement->note_private = 'StripeSepa payment ' . dol_print_date($now, 'standard') . ' using ' . $servicestatus . ($ipaddress ? ' from ip ' . $ipaddress : '') . ' - Transaction ID = ' . $TRANSACTIONID; - $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. - $paiement->ext_payment_site = $service; - - $ispaymentdone = 0; - $sql = "SELECT p.rowid FROM llx_paiement as p"; - $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'"; - $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'"; - $result = $db->query($sql); - if ($result) { - if ($db->num_rows($result)) { - $ispaymentdone = 1; - dol_syslog('* Payment for ext_payment_id '.$paiement->ext_payment_id.' already done. We do not recreate the payment'); - } + $paymentmethodstripe = $s->paymentMethods->retrieve($paymentmethodstripeid); + $paymentTypeId = $paymentmethodstripe->type; + if ($paymentTypeId == "ban" || $paymentTypeId == "sepa_debit") { + $paymentTypeId = "PRE"; + } elseif ($paymentTypeId == "card") { + $paymentTypeId = "CB"; } - $db->begin(); + $payment_amount = $payment_amountInDolibarr; + // TODO Check payment_amount in Stripe (received) is same than the one in Dolibarr - if (!$error && !$ispaymentdone) { - dol_syslog('* Record payment for invoice id ' . $invoice_id . '. It includes closing of invoice and regenerating document'); + if ($paymentTypeId == "CB" && ($paymentTypeIdInDolibarr == 'card' || empty($paymentTypeIdInDolibarr))) { + // Case payment type in Stripe and into prelevement_demande are both CARD. + // For this case, payment should already have been recorded so we just update flag of payment request if not yet 1 - // This include closing invoices to 'paid' (and trigger including unsuspending) and regenerating document - $paiement_id = $paiement->create($user, 1); - if ($paiement_id < 0) { - $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . join("
\n", $paiement->errors); - $ispostactionok = -1; - $error++; + // TODO Set traite to 1 + dol_syslog("TODO update flag traite to 1"); + } elseif ($paymentTypeId == "PRE" && $paymentTypeIdInDolibarr == 'ban') { + // Case payment type in Stripe and into prelevement_demande are both BAN. + // For this case, payment on invoice (not yet recorded) must be done and direct debit order must be closed. - dol_syslog("Failed to create the payment for invoice id " . $invoice_id); + $paiement = new Paiement($db); + $paiement->datepaye = $now; + $paiement->date = $now; + if ($currencyCodeType == $conf->currency) { + $paiement->amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching with invoice id } else { - $postactionmessages[] = 'Payment created'; + $paiement->multicurrency_amounts = [$invoice_id => $payment_amount]; // Array with all payments dispatching - dol_syslog("The payment has been created for invoice id " . $invoice_id); + $postactionmessages[] = 'Payment was done in a different currency than currency expected of company'; + $ispostactionok = -1; + // Not yet supported, so error + $error++; } - } + $paiement->paiementid = $paymentTypeId; + $paiement->num_payment = ''; + $paiement->note_public = ''; + $paiement->note_private = 'StripeSepa payment ' . dol_print_date($now, 'standard') . ' using ' . $servicestatus . ($ipaddress ? ' from ip ' . $ipaddress : '') . ' - Transaction ID = ' . $TRANSACTIONID; + $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. + $paiement->ext_payment_site = $service; - if (!$error && isModEnabled('banque')) { - // Search again the payment to see if it is already linked to a bank payment record (We should always find the payement now we have created before). $ispaymentdone = 0; - $sql = "SELECT p.rowid, p.fk_bank FROM llx_paiement as p"; + $sql = "SELECT p.rowid FROM llx_paiement as p"; $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'"; $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'"; - $sql .= " AND p.fk_bank <> 0"; $result = $db->query($sql); if ($result) { if ($db->num_rows($result)) { $ispaymentdone = 1; - $obj = $db->fetch_object($result); - dol_syslog('* Payment already linked to bank record '.$obj->fk_bank.' . We do not recreate the link'); + dol_syslog('* Payment for ext_payment_id '.$paiement->ext_payment_id.' already done. We do not recreate the payment'); } } - if (!$ispaymentdone) { - dol_syslog('* Add payment to bank'); - // The bank used is the one defined into Stripe setup - $paymentmethod = 'stripe'; - $bankaccountid = getDolGlobalInt("STRIPE_BANK_ACCOUNT_FOR_PAYMENTS"); + $db->begin(); - if ($bankaccountid > 0) { - $label = '(CustomerInvoicePayment)'; - $result = $paiement->addPaymentToBank($user, 'payment', $label, $bankaccountid, $customer_id, ''); - if ($result < 0) { - $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . join("
\n", $paiement->errors); + if (!$error && !$ispaymentdone) { + dol_syslog('* Record payment for invoice id ' . $invoice_id . '. It includes closing of invoice and regenerating document'); + + // This include closing invoices to 'paid' (and trigger including unsuspending) and regenerating document + $paiement_id = $paiement->create($user, 1); + if ($paiement_id < 0) { + $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . join("
\n", $paiement->errors); + $ispostactionok = -1; + $error++; + + dol_syslog("Failed to create the payment for invoice id " . $invoice_id); + } else { + $postactionmessages[] = 'Payment created'; + + dol_syslog("The payment has been created for invoice id " . $invoice_id); + } + } + + if (!$error && isModEnabled('banque')) { + // Search again the payment to see if it is already linked to a bank payment record (We should always find the payement now we have created before). + $ispaymentdone = 0; + $sql = "SELECT p.rowid, p.fk_bank FROM llx_paiement as p"; + $sql .= " WHERE p.ext_payment_id = '".$db->escape($paiement->ext_payment_id)."'"; + $sql .= " AND p.ext_payment_site = '".$db->escape($paiement->ext_payment_site)."'"; + $sql .= " AND p.fk_bank <> 0"; + $result = $db->query($sql); + if ($result) { + if ($db->num_rows($result)) { + $ispaymentdone = 1; + $obj = $db->fetch_object($result); + dol_syslog('* Payment already linked to bank record '.$obj->fk_bank.' . We do not recreate the link'); + } + } + if (!$ispaymentdone) { + dol_syslog('* Add payment to bank'); + + // The bank used is the one defined into Stripe setup + $paymentmethod = 'stripe'; + $bankaccountid = getDolGlobalInt("STRIPE_BANK_ACCOUNT_FOR_PAYMENTS"); + + if ($bankaccountid > 0) { + $label = '(CustomerInvoicePayment)'; + $result = $paiement->addPaymentToBank($user, 'payment', $label, $bankaccountid, $customer_id, ''); + if ($result < 0) { + $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . join("
\n", $paiement->errors); + $ispostactionok = -1; + $error++; + } else { + $postactionmessages[] = 'Bank transaction of payment created (by ipn.php file)'; + } + } else { + $postactionmessages[] = 'Setup of bank account to use in module ' . $paymentmethod . ' was not set. No way to record the payment.'; $ispostactionok = -1; $error++; + } + } + } + + if (!$error && isModEnabled('prelevement')) { + $bon = new BonPrelevement($db); + $idbon = 0; + $sql = "SELECT dp.fk_prelevement_bons as idbon"; + $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as dp"; + $sql .= " JOIN ".MAIN_DB_PREFIX."prelevement_bons as pb"; // Here we join to prevent modification of a prelevement bon already credited + $sql .= " ON pb.rowid = dp.fk_prelevement_bons"; + $sql .= " WHERE dp.fk_facture = ".((int) $invoice_id); + $sql .= " AND dp.sourcetype = 'facture'"; + $sql .= " AND dp.ext_payment_id = '".$db->escape($TRANSACTIONID)."'"; + $sql .= " AND dp.traite = 1"; + $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED); // To be sure that it's not already credited + $result = $db->query($sql); + if ($result) { + if ($db->num_rows($result)) { + $obj = $db->fetch_object($result); + $idbon = $obj->idbon; + dol_syslog('* Set prelevement to credite'); } else { - $postactionmessages[] = 'Bank transaction of payment created (by ipn.php file)'; + dol_syslog('* Prelevement not found or already credited'); } } else { - $postactionmessages[] = 'Setup of bank account to use in module ' . $paymentmethod . ' was not set. No way to record the payment.'; + $postactionmessages[] = $db->lasterror(); $ispostactionok = -1; $error++; } - } - } - if (!$error && isModEnabled('prelevement')) { - $bon = new BonPrelevement($db); - $idbon = 0; - $sql = "SELECT dp.fk_prelevement_bons as idbon"; - $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as dp"; - $sql .= " JOIN ".MAIN_DB_PREFIX."prelevement_bons as pb"; // Here we join to prevent modification of a prelevement bon already credited - $sql .= " ON pb.rowid = dp.fk_prelevement_bons"; - $sql .= " WHERE dp.fk_facture = ".((int) $invoice_id); - $sql .= " AND dp.sourcetype = 'facture'"; - $sql .= " AND dp.ext_payment_id = '".$db->escape($TRANSACTIONID)."'"; - $sql .= " AND dp.traite = 1"; - $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED); // To be sure that it's not already credited - $result = $db->query($sql); - if ($result) { - if ($db->num_rows($result)) { - $obj = $db->fetch_object($result); - $idbon = $obj->idbon; - dol_syslog('* Set prelevement to credite'); - } else { - dol_syslog('* Prelevement not found or already credited'); + if (!$error && !empty($idbon)) { + $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_bons"; + $sql .= " SET fk_user_credit = ".((int) $user->id); + $sql .= ", statut = ".((int) $bon::STATUS_CREDITED); + $sql .= ", date_credit = '".$db->idate($now)."'"; + $sql .= ", credite = 1"; + $sql .= " WHERE rowid = ".((int) $idbon); + $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED); + + $result = $db->query($sql); + if (!$result) { + $postactionmessages[] = $db->lasterror(); + $ispostactionok = -1; + $error++; + } } + + if (!$error && !empty($idbon)) { + $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_lignes"; + $sql .= " SET statut = 2"; + $sql .= " WHERE fk_prelevement_bons = ".((int) $idbon); + $result = $db->query($sql); + if (!$result) { + $postactionmessages[] = $db->lasterror(); + $ispostactionok = -1; + $error++; + } + } + } + + if (!$error) { + $db->commit(); + http_response_code(200); + return 1; } else { - $postactionmessages[] = $db->lasterror(); - $ispostactionok = -1; - $error++; + $db->rollback(); + http_response_code(500); + return -1; } - - if (!$error && !empty($idbon)) { - $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_bons"; - $sql .= " SET fk_user_credit = ".((int) $user->id); - $sql .= ", statut = ".((int) $bon::STATUS_CREDITED); - $sql .= ", date_credit = '".$db->idate($now)."'"; - $sql .= ", credite = 1"; - $sql .= " WHERE rowid = ".((int) $idbon); - $sql .= " AND statut = ".((int) $bon::STATUS_TRANSFERED); - - $result = $db->query($sql); - if (!$result) { - $postactionmessages[] = $db->lasterror(); - $ispostactionok = -1; - $error++; - } - } - - if (!$error && !empty($idbon)) { - $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_lignes"; - $sql .= " SET statut = 2"; - $sql .= " WHERE fk_prelevement_bons = ".((int) $idbon); - $result = $db->query($sql); - if (!$result) { - $postactionmessages[] = $db->lasterror(); - $ispostactionok = -1; - $error++; - } - } - } - - if (!$error) { - $db->commit(); - http_response_code(200); - return 1; } else { - $db->rollback(); - http_response_code(500); - return -1; + dol_syslog("The payment mode of this payment is ".$paymentTypeId." in Stripe and ".$paymentTypeIdInDolibarr." in Dolibarr. This case is not managed by the IPN"); } } else { - dol_syslog("The payment mode of this payment is ".$paymentTypeId.". This payment mode is not managed by the IPN"); + dol_syslog("Nothing to do in database"); } } elseif ($event->type == 'payment_intent.payment_failed') { dol_syslog("A try to make a payment has failed");