Email workspacePayment request contract
Implementation: App\Mail\SendLinkEmail, app/Mail/SendLinkEmail.php. Original Markdown body: resources/views/emails/SendLinkEmail.blade.php using mail::message, mail::panel and optional mail::promotion. Caller: app/Http/Controllers/General/emailsController.php:344–371, sendLinkToEmails. Static references are usage evidence, not proof of current production delivery.
Subject: Payment request from {{wallet_business_name}} (existing exact pattern).
Suggested preheader: Payment request from {{wallet_business_name}}: {{request_title}}.
Read docs/integration/shared-footer.md for the common PNG/CID assets and medium action policy.
Portable placeholders
| Token | Existing source / derivation | Handling |
|---|---|---|
| {{logo_url}} | Original approved Built app_name PNG | Optional deployment override. Concrete HTTPS logo already exists; use approved HTTPS or an actual matching CID. |
| {{preheader}} | Suggested existing wallet-label/title summary above | Required escaped text; not a deadline or fixed-charge promise. |
| {{wallet_business_name}} | $wallet->business_name | Required escaped body, subject and Regards sign-off identity. It is separate from Business.name used for From/Reply-To display. |
| {{request_title}} | $link->title | Required escaped source payment-request title; not an invoice number or customer name. |
| {{requested_amount}} | $wallet->currency . number_format($link->amount, 2) | Required complete existing formatted display, retaining prefix, two decimals, zero and sign. Do not substitute business.currency, fx_currency, transaction amount, minimum/maximum or a fee-inclusive total. |
| {{request_description}} | $link->description | Optional escaped multiline text under the original PHP truthiness predicate. Original Blade already escapes this field; preserve literal markup if present rather than treating it as raw HTML or applying a lossy rich-text converter. |
| {{payment_url}} | $link->short_link | Required complete stored HTTPS payment link. Validate the approved short-link/direct secure hosts and paths separately, then escape attributes. Button and visible fallback use this exact one value. Do not rebuild from invoice UUID or an unused variable. |
Trigger, routing and headers
routes/api.php:1775 POST link/{uuid}/send/email calls sendLinkToEmails inside the existing wallet routing. The method requires emails, splits request.emails by comma, finds PaymentLink by uuid and Wallet by link.wallet_id, then loops Mail::to(email)->send(new SendLinkEmail(link, wallet)). Preserve the current lookup/routing and individual sends. It does not establish a customer-only recipient, per-address validation, deduplication or trimming inside this method. Do not add/remove security or validation behavior through HTML; assess the target project’s authorization separately during integration.
The Mailable receives the existing PaymentLink and Wallet. It loads Business through wallet.business_id and User through business.owner_id without events. From uses config(mail.from.address) with business.name. Reply-To uses business.email ?: owner.email with business.name display. Body/subject/sign-off use wallet.business_name instead, which can differ from business.name. Do not collapse these sources or use wallet.user_id for this request’s owner fallback.
The caller explicitly uses send(), not queue(). Queueable/SerializesModels alone do not change the native call. Preserve this behavior; the configured transport may have asynchronous outbox behavior underneath Laravel, so this is not proof of immediate provider delivery. No source business attachment/PDF is configured. Shared footer PNGs remain separate inline-image requirements.
Payment destination and action meaning
WalletController::createLink (around :2554–2567) builds config(app.secure_URL) + /bw/pay/{link.uuid}. Production uses ShortURLFacade with redirectStatusCode(302), stores config(app.short_url) + /p/{url_key}, and non-production stores the direct secure URL. The WhatsApp link-creation path uses the same destination scheme, but it is a separate channel. Preserve whichever full value is stored in short_link, including its key/UUID and any existing parameters; do not rewrite to a guessed checkout-provider URL.
The source email prints Pay Now followed by short_link. The draft turns that existing destination into one Open payment link action, explains that it opens the merchant’s payment page and keeps a visible fallback. It does not review invoice details, automatically charge the recipient, confirm payment or promise a particular external provider. There are no separate legacy duplicate email link fields.
The original local $base_url = config(app.url) + /viewinvoice/ is unused and is not an action to preserve or invent. Keep application/invoice review separate from payment-page navigation.
Conditions and amount semantics
The complete marked description table is present in the ready extract. Retain it only when the original link.description is PHP-truthy; null, empty string and literal string 0 omit it. Remove its entire wrapper/spacing in HTML and the corresponding text block. No HTML normalization is needed for this already-escaped field, unlike raw merchant-message Mailables. Preserve all nonempty literal text and line breaks.
The source displays link.amount regardless of can_change_amount. Link models also store min_amount, max_amount, limit, expires_at, one_time, is_active and archived, but none is rendered as an instruction in this email. Keep those application controls in their current checkout/lifecycle paths. Do not add an expiry, deadline, fixed-charge promise, free-payment claim or guessed min/max. Requested amount is the stored display, not proof of the eventual amount paid. Missing/invalid required link/title/amount/URL needs an explicit target-project decision rather than an invented value.
Six states cover description, absence, literal-0 absence, editable zero amount, direct versus short destination, and long values. Zero/editable fixtures are rendering/source-data examples, not a claim that checkout is free or every zero-amount request can complete.
Native integration and safety
Portable {{placeholder_name}} tokens are not automatically Laravel Blade expressions. Prefer the existing Mailable with a complete HTML view and separate text view, mapped explicitly to its existing objects. Do not nest the complete document in mail::message or introduce a new framework. Keep native subject, sender/Reply-To, recipient iteration and send() behavior.
Escape every dynamic title, wallet label, formatted amount, description and attribute. Validate payment_url separately for approved HTTPS short-link or secure payment-page destinations, preserving complete values. If using string replacement, use one pass so inserted token-looking text remains literal and is never re-evaluated as markup. Image hosting/CID overrides require the shared contract’s separate validation and actual embedded assets. Relative local preview URLs are not production endpoints.
Files: public/ready/payment-request.html and .txt; public/previews/payment-request-*.html/.txt. No separate project-owned plain-text file was found for this Markdown source; the supplied text is proposed equivalent content. No real request, link, checkout, charge or email is created/opened/sent here. Browser rendering does not establish Gmail/Outlook/iOS Mail, actual CID delivery, checkout availability or production delivery.