diff --git a/src/Mail/OrderMailer.php b/src/Mail/OrderMailer.php new file mode 100644 index 0000000..a000ee2 --- /dev/null +++ b/src/Mail/OrderMailer.php @@ -0,0 +1,71 @@ +mailer->send(new Email( + $recipient, + sprintf('%s order #%d confirmed', $this->appName, (int) $order['id']), + $this->confirmationBody($order, $items) + )); + } + + public function statusUpdate(string $recipient, array $order): void + { + $this->mailer->send(new Email( + $recipient, + sprintf('%s order #%d is now %s', $this->appName, (int) $order['id'], $order['status']), + $this->statusBody($order) + )); + } + + private function confirmationBody(array $order, array $items): string + { + $lines = [ + 'Thank you for your order.', + '', + 'Order #' . (int) $order['id'], + '', + ]; + foreach ($items as $item) { + $label = $item['productName']; + if (($item['variantLabel'] ?? '') !== '') { + $label .= ' (' . $item['variantLabel'] . ')'; + } + $lines[] = sprintf( + ' %d x %s - %s', + (int) $item['quantity'], + $label, + formatMoney((int) $item['unitPriceCents'] * (int) $item['quantity'], $order['currency']) + ); + } + $lines[] = ''; + if ((int) ($order['discountCents'] ?? 0) > 0) { + $lines[] = 'Discount (' . ($order['couponCode'] ?? '') . '): -' + . formatMoney((int) $order['discountCents'], $order['currency']); + } + $lines[] = 'Total: ' . formatMoney((int) $order['totalCents'], $order['currency']); + + return implode("\n", $lines); + } + + private function statusBody(array $order): string + { + return implode("\n", [ + 'Your order status has changed.', + '', + 'Order #' . (int) $order['id'], + 'New status: ' . $order['status'], + ]); + } +} diff --git a/src/Mail/SmtpMailer.php b/src/Mail/SmtpMailer.php new file mode 100644 index 0000000..869b666 --- /dev/null +++ b/src/Mail/SmtpMailer.php @@ -0,0 +1,73 @@ +host, $this->port, $errno, $error, $this->timeout); + if ($socket === false) { + throw new \RuntimeException("SMTP connection failed: {$error} ({$errno})"); + } + + $this->expect($socket, 220); + $this->command($socket, 'EHLO bazaar', 250); + + if ($this->username !== null && $this->password !== null) { + $this->command($socket, 'AUTH LOGIN', 334); + $this->command($socket, base64_encode($this->username), 334); + $this->command($socket, base64_encode($this->password), 235); + } + + $this->command($socket, 'MAIL FROM:<' . $email->from . '>', 250); + $this->command($socket, 'RCPT TO:<' . $email->to . '>', 250); + $this->command($socket, 'DATA', 354); + + $headers = implode("\r\n", [ + 'From: ' . $email->from, + 'To: ' . $email->to, + 'Subject: ' . $email->subject, + 'MIME-Version: 1.0', + 'Content-Type: text/plain; charset=UTF-8', + ]); + fwrite($socket, $headers . "\r\n\r\n" . $email->body . "\r\n.\r\n"); + $this->expect($socket, 250); + + $this->command($socket, 'QUIT', 221); + fclose($socket); + + $this->sent[] = $email; + } + + public function sent(): array + { + return $this->sent; + } + + private function command($socket, string $line, int $expected): void + { + fwrite($socket, $line . "\r\n"); + $this->expect($socket, $expected); + } + + private function expect($socket, int $expected): void + { + $response = (string) fgets($socket, 512); + $code = (int) substr($response, 0, 3); + if ($code !== $expected) { + throw new \RuntimeException("SMTP expected {$expected}, got: " . trim($response)); + } + } +}