new: send alert email when Paybox payment arrives on cancelled order

When a Paybox callback confirms payment on an order with status 4
(Annulé), the payment is still recorded but the order status is no
longer forced to « Préparation ». Instead, an alert email is sent to
``commande@jardinenvie.com`` warning that a refund is likely needed.

New ``AlertePaiementAnnule`` mailable with DB template providing order
ref, amount, customer info and payment reference. New method
``OrderMails::sendCancelledOrderPaymentAlert()`` handles the dispatch.
This commit is contained in:
Valentin Lab
2026-02-20 11:52:03 +01:00
parent 5c10645af7
commit 5325fa1f06
4 changed files with 178 additions and 6 deletions

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Mail;
use App\Models\Core\Mail\MailTemplate;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Spatie\MailTemplates\TemplateMailable;
class AlertePaiementAnnule extends TemplateMailable
{
use Queueable, SerializesModels;
public $subject;
public $numero_commande;
public $date_commande;
public $montant;
public $client_nom;
public $client_email;
public $reference_paiement;
protected static $templateModelClass = MailTemplate::class;
public function __construct($order, $reference)
{
$this->numero_commande = $order->ref;
$this->date_commande = $order->created_at->format('d/m/Y H:i');
$this->montant = number_format($order->total_shipped, 2, ',', ' ').' €';
$this->client_nom = $order->customer
? $order->customer->last_name.' '.$order->customer->first_name
: 'Client supprimé';
$this->client_email = $order->customer->email ?? 'inconnu';
$this->reference_paiement = $reference;
}
public function envelope()
{
return new Envelope(
from: new Address('boutique@jardinenvie.com', 'Jardin\'en\'Vie'),
subject: $this->subject,
);
}
}

View File

@@ -3,9 +3,11 @@
namespace App\Repositories\Shop;
use App\Mail\Acheminement;
use App\Mail\AlertePaiementAnnule;
use App\Mail\ConfirmationCommande;
use App\Mail\Preparation;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
class OrderMails
{
@@ -37,4 +39,25 @@ class OrderMails
return Mail::to($order->customer->email)->send($mail);
}
public static function sendCancelledOrderPaymentAlert($orderId, $reference)
{
$order = Orders::get($orderId, ['customer']);
try {
Mail::to('commande@jardinenvie.com')
->send(new AlertePaiementAnnule($order, $reference));
Log::info('Cancelled order payment alert sent', [
'order_id' => $orderId,
'order_ref' => $order->ref,
'reference' => $reference,
]);
} catch (\Throwable $e) {
Log::error('Failed to send cancelled order payment alert', [
'order_id' => $orderId,
'order_ref' => $order->ref,
'exception' => $e->getMessage(),
]);
}
}
}

View File

@@ -104,7 +104,9 @@ class Paybox
return true;
}
DB::transaction(function () use ($invoice, $order, $reference, $payload, $existingPayment, &$shouldNotify) {
$isCancelled = (int) $order->status === 4;
DB::transaction(function () use ($invoice, $order, $reference, $payload, $existingPayment, &$shouldNotify, $isCancelled) {
$attributes = [
'payment_type' => 1,
'amount' => $invoice->total_shipped,
@@ -134,14 +136,24 @@ class Paybox
Invoices::checkPayments($invoice->id);
$paidStatus = Orders::getStatusByName('Préparation');
if ($paidStatus !== '' && (int) $order->status !== (int) $paidStatus) {
$order->status = $paidStatus;
$order->save();
if (! $isCancelled) {
$paidStatus = Orders::getStatusByName('Préparation');
if ($paidStatus !== '' && (int) $order->status !== (int) $paidStatus) {
$order->status = $paidStatus;
$order->save();
}
}
});
if ($shouldNotify) {
if ($isCancelled && $shouldNotify) {
Log::warning('Paybox payment received on cancelled order', [
'order_id' => $order->id,
'order_ref' => $order->ref,
'invoice_id' => $invoice->id,
'reference' => $reference,
]);
OrderMails::sendCancelledOrderPaymentAlert($order->id, $reference);
} elseif ($shouldNotify) {
try {
OrderMails::sendOrderConfirmed($order->id);
} catch (\Throwable $exception) {

View File

@@ -0,0 +1,86 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('mail_templates')->insert([
'mailable' => 'App\\Mail\\AlertePaiementAnnule',
'subject' => json_encode(['fr' => '[URGENT] Paiement reçu sur commande annulée {{numero_commande}}'], JSON_UNESCAPED_UNICODE),
'html_template' => json_encode(['fr' => $this->getHtmlTemplate()], JSON_UNESCAPED_UNICODE),
'text_template' => json_encode(['fr' => $this->getTextTemplate()], JSON_UNESCAPED_UNICODE),
'created_at' => now(),
'updated_at' => now(),
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::table('mail_templates')
->where('mailable', 'App\\Mail\\AlertePaiementAnnule')
->delete();
}
private function getHtmlTemplate(): string
{
return '<table style="min-width: 100%;" width="100%" cellspacing="0" cellpadding="0">'
.'<tbody><tr><td>'
.'<table style="border-collapse: collapse; background-color: #ffffff; margin: 0 auto; width: 600px;" cellspacing="0" cellpadding="0" border="0" align="center">'
.'<tbody><tr>'
.'<td style="margin: 0px auto; padding: 32px 0px 24px 4px; vertical-align: top;" align="center">'
.'<a href="https://www.jardinenvie.com/" style="text-decoration: none; color: #000000;">'
.'<img alt="Jardin\'enVie" src="https://boutique.jardinenvie.com/img/logo.png" style="margin: 0px auto; display: block;" width="300" height="138" border="0" /></a></td>'
.'</tr></tbody></table>'
.'<table style="border-collapse: collapse; background-color: #ffffff; margin: 0 auto; width: 600px;" cellspacing="0" cellpadding="0" border="0" align="center">'
.'<tbody><tr><td style="margin: 0 auto; padding: 0px; vertical-align: top;">'
.'<table style="border-collapse: collapse; margin: 0 auto; width: 512px;" cellspacing="0" cellpadding="0" border="0" align="center">'
.'<tbody>'
.'<tr><td style="font-family: \'Nunito Sans\', Arial, sans-serif; line-height: 40px; color: #c0392b; font-size: 28px; padding: 20px 0px 10px 0px; font-weight: 800; text-align: center;">'
.'&#9888; Paiement sur commande annul&eacute;e</td></tr>'
.'<tr><td style="font-family: \'Nunito Sans\', Arial, sans-serif; line-height: 28px; color: #000000; font-size: 16px; text-align: center; padding: 10px 0 30px 0;">'
.'<p>Un paiement Paybox a &eacute;t&eacute; re&ccedil;u sur une commande <strong>annul&eacute;e</strong>.</p>'
.'<p style="font-size: 20px; font-weight: bold; color: #c0392b; padding: 10px 0;">Un remboursement est probablement n&eacute;cessaire.</p>'
.'<table style="margin: 15px auto; text-align: left; border-collapse: collapse;" cellpadding="8">'
.'<tr><td style="font-weight: bold; padding-right: 15px;">Commande :</td><td>{{numero_commande}}</td></tr>'
.'<tr><td style="font-weight: bold; padding-right: 15px;">Date :</td><td>{{date_commande}}</td></tr>'
.'<tr><td style="font-weight: bold; padding-right: 15px;">Montant :</td><td style="font-size: 18px; font-weight: bold; color: #c0392b;">{{montant}}</td></tr>'
.'<tr><td style="font-weight: bold; padding-right: 15px;">Client :</td><td>{{client_nom}}</td></tr>'
.'<tr><td style="font-weight: bold; padding-right: 15px;">Email :</td><td>{{client_email}}</td></tr>'
.'<tr><td style="font-weight: bold; padding-right: 15px;">R&eacute;f. paiement :</td><td>{{reference_paiement}}</td></tr>'
.'</table>'
.'<p style="padding-top: 15px; color: #666;">Veuillez proc&eacute;der au remboursement du client dans les plus brefs d&eacute;lais.</p>'
.'</td></tr>'
.'</tbody></table>'
.'</td></tr></tbody></table>'
.'<table style="border-collapse: collapse; background-color: #ffffff; margin: 0 auto; width: 600px; text-align: center;" cellspacing="0" cellpadding="0" border="0" align="center">'
.'<tbody><tr><td style="font-family: \'Nunito Sans\', Arial, sans-serif; font-size: 12px; font-weight: 600; line-height: 20px; padding: 16px; color: #999999;">'
.'Jardin\'enVie Artisan Semencier<br />429 route des chaux, 26500 Bourg les Valence - Dr&ocirc;me'
.'</td></tr></tbody></table>'
.'</td></tr></tbody></table>';
}
private function getTextTemplate(): string
{
return "⚠ PAIEMENT SUR COMMANDE ANNULÉE\n\n"
."Un paiement Paybox a été reçu sur une commande annulée.\n"
."Un remboursement est probablement nécessaire.\n\n"
."Commande : {{numero_commande}}\n"
."Date : {{date_commande}}\n"
."Montant : {{montant}}\n"
."Client : {{client_nom}}\n"
."Email : {{client_email}}\n"
."Réf. paiement : {{reference_paiement}}\n\n"
."Veuillez procéder au remboursement du client dans les plus brefs délais.\n\n"
."Jardin'enVie Artisan Semencier\n"
.'429 route des chaux, 26500 Bourg les Valence - Drôme';
}
};