79 lines
2.9 KiB
PHP
79 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace App\Mail;
|
|
|
|
use App\Mail\Concerns\SendsFromMailbox;
|
|
use App\Models\Invoice;
|
|
use App\Services\Billing\InvoiceRenderer;
|
|
use App\Services\Mail\MailPurpose;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Mail\Mailable;
|
|
use Illuminate\Mail\Mailables\Attachment;
|
|
use Illuminate\Mail\Mailables\Content;
|
|
use Illuminate\Mail\Mailables\Envelope;
|
|
use Illuminate\Queue\SerializesModels;
|
|
|
|
/**
|
|
* The invoice, with the invoice attached.
|
|
*
|
|
* The PDF is rendered when the mail is sent rather than fetched from anywhere,
|
|
* because nothing stores it — and rendering from the frozen snapshot means the
|
|
* attachment is the document as issued no matter when the queue gets round to
|
|
* it, or how often it is retried.
|
|
*
|
|
* From the BILLING mailbox: a reply to this is a question about money.
|
|
*/
|
|
class InvoiceMail extends Mailable implements ShouldQueue
|
|
{
|
|
use Queueable, SendsFromMailbox, SerializesModels;
|
|
|
|
public function __construct(public Invoice $invoice, public string $name)
|
|
{
|
|
// The mailer, alongside the From. A purpose mailbox authenticates with
|
|
// its OWN account, and a mail server lets an account send only from the
|
|
// address it owns — so a From of billing@ over the default mailer's
|
|
// no-reply@ login is answered "553 Sender address rejected: not owned by
|
|
// user". Setting the From from a mailbox and leaving the mailer at the
|
|
// default is a mail that renders perfectly and never arrives.
|
|
$this->mailer('cp_'.MailPurpose::BILLING);
|
|
}
|
|
|
|
public function envelope(): Envelope
|
|
{
|
|
return $this->mailboxEnvelope(
|
|
MailPurpose::BILLING,
|
|
__('invoice_mail.subject', ['number' => $this->invoice->number]),
|
|
);
|
|
}
|
|
|
|
public function content(): Content
|
|
{
|
|
$meta = (array) ($this->invoice->snapshot['meta'] ?? []);
|
|
|
|
return new Content(view: 'mail.invoice', with: [
|
|
'name' => $this->name,
|
|
'number' => $this->invoice->number,
|
|
'issuedOn' => $this->invoice->issued_on?->local()->format('d.m.Y'),
|
|
'dueOn' => $this->invoice->due_on?->local()->format('d.m.Y'),
|
|
'gross' => \App\Services\Billing\InvoiceMath::money((int) $this->invoice->gross_cents),
|
|
'currency' => $this->invoice->currency,
|
|
'paymentTerms' => (string) ($meta['payment_terms'] ?? ''),
|
|
'invoicesUrl' => route('invoices'),
|
|
]);
|
|
}
|
|
|
|
/** @return array<int, Attachment> */
|
|
public function attachments(): array
|
|
{
|
|
return [
|
|
// fromData, not fromPath: there is no path. The document exists as
|
|
// a row and becomes a file only for as long as this mail needs one.
|
|
Attachment::fromData(
|
|
fn () => app(InvoiceRenderer::class)->forInvoice($this->invoice),
|
|
$this->invoice->number.'.pdf',
|
|
)->withMime('application/pdf'),
|
|
];
|
|
}
|
|
}
|