Files
pcrt-legal-return/app/app/lib/recesso.copy.ts
tommaso 8ca4de6c0b MVP core recesso: App Proxy flow + ricevuta durevole + design storefront
- A1: SPEC-MVP-RECESSO.md (criteri + copy IT)
- A3: App Proxy /apps/recesso — lookup guest ordine+email (anti-leak), form 2-step,
  funzione dedicata Conferma recesso, persist WithdrawalRequest + transmittedAt + AuditLog
- A4: ricevuta durevole via nodemailer (mailer.server.ts) + receiptSentAt + AuditLog
- Design: recesso.server.ts restyle token CSS light/dark, coerente Shopify
- Fix: @shopify/shopify-api pinnato 13.1.0 (dedupe) -> tsc clean
- PLAN: A5 esteso a motore di stile a 3 livelli

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mv83a29B4eFv5ixoj6PoE1
2026-07-07 09:52:47 +02:00

124 lines
5.5 KiB
TypeScript

/**
* Copy deck IT — stringhe ESATTE da SPEC-MVP-RECESSO.md §2.
*
* Non modificare i testi: sono vincolanti (contratto di build) e usati sia dal
* flusso storefront (route proxy) sia — per il template email — da A4.
* Placeholder con {{doppie graffe}} vengono sostituiti a runtime.
*
* i18n multi-lingua = fase A7. In MVP tutto IT hardcoded.
*/
// §2.1 — Etichetta pulsante di avvio (usata dalla Theme App Extension / A2).
export const BUTTON_LABEL = "Recedere dal contratto qui";
// §2.2 — Etichetta funzione di conferma (step 3), funzione dedicata anti-dark-pattern.
export const CONFIRM_LABEL = "Conferma recesso";
// §2.3 — Label dei campi del form + placeholder/hint.
export const FIELD = {
name: { label: "Nome e cognome", placeholder: "Mario Rossi" },
orderName: { label: "Numero dell'ordine", placeholder: "es. #1234" },
email: { label: "Email", placeholder: "La tua email dell'ordine" },
statement: { label: "Dichiarazione di recesso" },
} as const;
// §2.3 — Testo precompilato (editabile) della dichiarazione (deriva dall'Allegato I-B).
export function statementTemplate(orderName: string): string {
return `Con la presente comunico il recesso dal contratto di vendita relativo all'ordine ${orderName}.`;
}
// §2.4 — Testo informativo sul diritto di recesso (Art. 49), mostrato PRIMA dell'invio.
export const ART49_INFO =
"Hai diritto di recedere da questo contratto entro 14 giorni senza dover fornire alcuna motivazione.\n" +
"Il termine decorre dalla consegna del bene (o dalla conclusione del contratto per i servizi).\n" +
"Compilando e confermando questo modulo eserciti il recesso: riceverai via email una ricevuta con\n" +
"la data e l'ora di trasmissione della tua dichiarazione.";
// §2.9 — Nota di coesistenza (il pulsante è aggiuntivo, non sostitutivo).
export const COEXISTENCE_NOTE =
"Questa funzione è un modo aggiuntivo per esercitare il recesso. Puoi comunque usare il modulo\n" +
"tipo (Allegato I, parte B) o inviare qualsiasi dichiarazione esplicita, anche via email.";
// §2.7 — Messaggi di errore.
export const ERROR = {
// Anti-enumeration: ordine inesistente ED email non combaciante DEVONO
// mostrare lo stesso identico messaggio (stesso testo, stesso status 200).
lookupNoMatch:
"Non abbiamo trovato un ordine con questi dati. Verifica il numero dell'ordine e l'email usata per l'acquisto.",
missingField: "Compila tutti i campi obbligatori per continuare.",
invalidEmail: "Inserisci un indirizzo email valido.",
windowClosed:
"Il termine di 14 giorni per il recesso su questo ordine è terminato. Puoi comunque contattarci per altre richieste.",
generic:
"Si è verificato un problema. Riprova tra qualche istante; se persiste, contattaci.",
} as const;
// §2.6 — Schermata finale (dopo "Conferma recesso").
export function successMessage(
orderName: string,
transmittedAt: string,
email: string,
): { line1: string; line2: string; line3: string } {
return {
line1: "Recesso trasmesso correttamente.",
line2: `Abbiamo registrato la tua dichiarazione di recesso per l'ordine ${orderName} in data ${transmittedAt}.`,
line3: `Ti abbiamo inviato una ricevuta all'indirizzo ${email}.`,
};
}
// §2.5 — Template email ricevuta su supporto durevole.
// Usato da A4 (invio reale). In MVP il flusso lascia receiptSentAt = null.
export function receiptEmailSubject(orderName: string): string {
return `Ricevuta della tua richiesta di recesso — Ordine ${orderName}`;
}
export function receiptEmailBody(params: {
customerName: string;
orderName: string;
transmittedAt: string; // formato leggibile con fuso orario esplicito (Europe/Rome)
statementText: string;
shopName: string;
}): string {
const { customerName, orderName, transmittedAt, statementText, shopName } =
params;
return `Gentile ${customerName},
confermiamo di aver ricevuto la Sua dichiarazione di recesso relativa all'ordine ${orderName},
trasmessa tramite la funzione di recesso presente sul nostro sito.
Dettagli della richiesta:
- Data e ora di trasmissione: ${transmittedAt}
- Ordine: ${orderName}
- Nome del consumatore: ${customerName}
Testo integrale della dichiarazione di recesso trasmessa:
"${statementText}"
Questa comunicazione costituisce la ricevuta su supporto durevole della Sua dichiarazione di
recesso, ai sensi dell'art. 54-bis del Codice del Consumo. La data e l'ora sopra indicate
attestano il momento della trasmissione della dichiarazione.
Le invieremo separatamente le istruzioni per l'eventuale restituzione dei beni e i tempi di rimborso.
Restano comunque validi anche gli altri mezzi per esercitare il recesso (modulo tipo di cui
all'Allegato I, parte B, o qualsiasi altra dichiarazione esplicita, anche via email): questa
funzione è aggiuntiva e non sostituisce tali strumenti.
${shopName}`;
}
// §2.8 — Messaggio prodotto escluso (Art. 59). Copy MVP-ready; la LOGICA è in A6.
export const EXCLUSION_REASON = {
CUSTOM: "prodotto realizzato su misura o personalizzato",
PERISHABLE: "prodotto deperibile o a rapida scadenza",
HYGIENE:
"prodotto sigillato, aperto dopo la consegna, non restituibile per motivi igienici o di salute",
} as const;
export function exclusionMessage(motivoEsclusione: string): string {
return `Per questo prodotto il diritto di recesso non è previsto (${motivoEsclusione}, ai sensi dell'art. 59 del Codice del Consumo). Per informazioni o altre richieste, contattaci.`;
}
// Titolo pagina / intestazione del flusso (non normato: label neutra, coerente col verbo di legge).
export const PAGE_TITLE = "Recesso dal contratto";