$subscription->uuid, ]); return null; } $invoice = $this->issue($subscription, $stripeInvoiceId, $from, $to); if ($invoice === null || $invoice->sent_at !== null) { return $invoice; } $this->send($subscription, $invoice); return $invoice; } private function issue(Subscription $subscription, string $stripeInvoiceId, Carbon $from, Carbon $to): ?Invoice { $existing = Invoice::query()->where('stripe_invoice_id', $stripeInvoiceId)->first(); if ($existing !== null) { return $existing; } try { return app(IssueInvoice::class)->forBilledPeriod($subscription, $from, $to, $stripeInvoiceId); } catch (UniqueConstraintViolationException) { // A concurrent delivery won the race. Its document is the one to // send; this one's invoice number went back into the series when the // transaction rolled back. return Invoice::query()->where('stripe_invoice_id', $stripeInvoiceId)->first(); } catch (Throwable $e) { // Refuses while the company details are incomplete, which is the // correct outcome — see IssueInvoice. The renewal itself stands: the // term has moved and the payment is in the register, and the // document can be issued once the details are there. Log::warning('Could not issue an invoice for this renewal.', [ 'subscription' => $subscription->uuid, 'stripe_invoice' => $stripeInvoiceId, 'exception' => $e->getMessage(), ]); return null; } } private function send(Subscription $subscription, Invoice $invoice): void { $customer = $subscription->customer; $address = $customer?->email; if (! is_string($address) || $address === '') { return; } try { Mail::to($address)->queue(new InvoiceMail($invoice, (string) $customer->name)); // Only once it is actually with the mailer. Stamped so a redelivery // does not send the same invoice a second time, and left unstamped // on a failure so that one still can. $invoice->update(['sent_at' => now()]); } catch (Throwable $e) { Log::warning('Could not queue the invoice mail for this renewal.', [ 'invoice' => $invoice->number, 'exception' => $e->getMessage(), ]); } } }