88 lines
3.1 KiB
PHP
88 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Mail;
|
|
|
|
use App\Mail\Concerns\SendsFromMailbox;
|
|
use App\Models\Order;
|
|
use App\Services\Mail\MailPurpose;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Mail\Mailable;
|
|
use Illuminate\Mail\Mailables\Content;
|
|
use Illuminate\Mail\Mailables\Envelope;
|
|
use Illuminate\Queue\SerializesModels;
|
|
|
|
/**
|
|
* "We have your order, and here is what happens next."
|
|
*
|
|
* Sent when the payment lands, not when the machine is ready — those are
|
|
* minutes apart at best and can be much longer, and a customer who has just
|
|
* been charged and heard nothing assumes the worst about both.
|
|
*
|
|
* From the BILLING mailbox: it is a record of a payment, and a reply to it is
|
|
* a question about money rather than about a server.
|
|
*
|
|
* Deliberately says nothing about when the cloud will be ready. Provisioning
|
|
* takes as long as it takes, CloudReady announces the end of it, and a
|
|
* promised time that slips is worse than no promise at all.
|
|
*/
|
|
class OrderConfirmationMail extends Mailable implements ShouldQueue
|
|
{
|
|
use Queueable, SendsFromMailbox, SerializesModels;
|
|
|
|
public function __construct(public Order $order, 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,
|
|
__('orders.mail_subject', ['number' => $this->order->uuid ?? $this->order->id]),
|
|
);
|
|
}
|
|
|
|
public function content(): Content
|
|
{
|
|
return new Content(view: 'mail.order-confirmation', with: [
|
|
'name' => $this->name,
|
|
'order' => $this->order,
|
|
// Through the model, so the cart, the invoice list and this mail
|
|
// cannot each invent their own wording for the same row.
|
|
'label' => $this->order->label(),
|
|
'amount' => $this->amount(),
|
|
'location' => $this->location(),
|
|
'invoicesUrl' => route('invoices'),
|
|
]);
|
|
}
|
|
|
|
/** The charge, formatted where the currency is known rather than in a view. */
|
|
private function amount(): string
|
|
{
|
|
return number_format($this->order->amount_cents / 100, 2, ',', '.')
|
|
.' '.strtoupper((string) $this->order->currency);
|
|
}
|
|
|
|
private function location(): string
|
|
{
|
|
$datacenter = \App\Models\Datacenter::query()
|
|
->where('code', $this->order->datacenter)
|
|
->first();
|
|
|
|
if ($datacenter === null) {
|
|
return (string) $this->order->datacenter;
|
|
}
|
|
|
|
return $datacenter->facility
|
|
? $datacenter->name.' · '.$datacenter->facility
|
|
: $datacenter->name;
|
|
}
|
|
}
|