feat(billing,landing): plan Free 2 factures + scaffold preuves sociales/SEO
Suite des chantiers structurants de landing-optimisations.md. #5 — Plan Free : 5 → 2 factures actives (cf. ADR-023) - PLAN_CAPS.free.activeInvoicesLimit dans apps/api/app/services/billing.ts - Tests unitaires alignés (4 → 1, 5 → 2 cap, delta 3 → delta 2) - billing:scenario command : commentaires + valeur par défaut - PlanLimitBanner : copy dynamique via {limit} au lieu de "5" hardcodé - /parametres/abonnement : H1 + tile Free (3 mois → 14 jours, 5 → 2) - billing.test.tsx (fixtures + cas test) - landing copy : hero feature pill, Pricing tile, FinalCTA, CGV §5 - CLAUDE.md pricing table #7 — Scaffold <TrustedBy /> (preuve sociale) - Composant qui render null tant que copy.trustedBy.{logos,testimonials} sont vides — pas de placeholder bidon. - Structure data dans copy.ts avec commentaires sur les prérequis avant d'ajouter une entrée (accord signé, photo, citation chiffrée). - Section insérée juste avant <Pricing /> (cf. doc §4). #8 — Plan articles SEO + brouillon article 1 - docs/marketing/seo-articles.md : 5 articles ciblés, mots-clés, structure type, lead magnet, calendrier 5 semaines. - Article 1 ("Modèle d'email de relance facture impayée") en brouillon complet, prêt à valider via l'admin blog (apps/api). #6 — Plan détaillé migration Stripe trial 14 j (code reporté) - docs/tech/stripe-trial-with-card.md : état actuel vs cible, architecture (Stripe Checkout + trial_period_days), modifs DB (trial_ends_at), API (start-trial + webhook trial_will_end), SPA (onboarding/billing), 3 emails transactionnels avec contenu intégral, risques + mitigations, plan d'exécution 2,5 j. - Implémentation reportée à une session focus avec accès Stripe test mode (cartes 3DS, webhook signing secret). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
7c0767f45e
commit
f9cba50b5e
@ -97,7 +97,7 @@ Direct, concret, chaleureux, précis, empathique. *On parle comme un bon associ
|
||||
|
||||
| Plan | Prix | Limite |
|
||||
|---|---|---|
|
||||
| **Free** | 0 € | 5 factures actives en relance, 1 utilisateur |
|
||||
| **Free** | 0 € | 2 factures actives en relance, 1 utilisateur |
|
||||
| **Pro** | 19 €/mois | Factures illimitées, OCR illimité, 1 utilisateur |
|
||||
| **Business** | 49 €/mois | + multi-utilisateurs, + branding email, + SMS (V2) |
|
||||
|
||||
|
||||
@ -5,15 +5,26 @@ import Organization from '#models/organization'
|
||||
/**
|
||||
* Politique de plans Rubis V1 :
|
||||
*
|
||||
* - Free : 5 factures actives en relance, 1 user
|
||||
* - Free : 2 factures actives en relance, 1 user
|
||||
* - Pro : factures illimitées, 1 user
|
||||
* - Business : factures illimitées, 5 users (V2 multi-users), réponses
|
||||
* par l'adresse mail user (V2 enhancement)
|
||||
*
|
||||
* Période de grâce : à l'inscription, l'org démarre en `free` avec un
|
||||
* `gracePeriodEndsAt = createdAt + 3 mois`. Pendant cette fenêtre, AUCUNE
|
||||
* limite n'est appliquée — l'user peut tester full power. Au-delà des 3
|
||||
* mois, si `activeInvoicesCount > 5` → import bloqué jusqu'à upgrade.
|
||||
* Période de grâce historique : les orgs créées avant le passage à 2
|
||||
* factures bénéficient toujours d'un `gracePeriodEndsAt = createdAt +
|
||||
* 3 mois` posé par la migration `1778157876956_alter_organizations_table`.
|
||||
* Pendant cette fenêtre, AUCUNE limite n'est appliquée. Au-delà, si
|
||||
* `activeInvoicesCount > limit` → import bloqué jusqu'à upgrade.
|
||||
*
|
||||
* Pour les nouvelles orgs, `gracePeriodEndsAt = null` par défaut : la
|
||||
* limite Free s'applique immédiatement. L'essai 14 jours Pro (CB à
|
||||
* l'inscription via Stripe Setup Intent) viendra remplacer cette grâce
|
||||
* historique — cf. roadmap landing-optimisations.md §3-6.
|
||||
*
|
||||
* Choix Free 2 factures (vs 5 initialement) : cf. ADR-022. Le segment
|
||||
* cœur (freelance/artisan/kiné) émet < 5 factures/mois et restait donc
|
||||
* en Free perpétuel — 2 permet de tester sans rendre le produit utilisable
|
||||
* en production solo.
|
||||
*/
|
||||
|
||||
export type PlanKey = 'free' | 'pro' | 'business'
|
||||
@ -33,7 +44,7 @@ export type PlanCaps = {
|
||||
|
||||
export const PLAN_CAPS: Record<PlanKey, PlanCaps> = {
|
||||
free: {
|
||||
activeInvoicesLimit: 5,
|
||||
activeInvoicesLimit: 2,
|
||||
seatsLimit: 1,
|
||||
multiUsers: false,
|
||||
replyFromUserEmail: false,
|
||||
|
||||
@ -25,9 +25,9 @@ import { canCreateInvoices, countActiveInvoices, PLAN_CAPS } from '#services/bil
|
||||
*
|
||||
* Scénarios :
|
||||
* - status : ne touche rien, affiche juste l'état courant
|
||||
* - fresh : reset à un signup neuf (free + grace 3 mois)
|
||||
* - grace-expired : free, grace terminée, ≤ 5 factures actives → OK
|
||||
* - limit-reached : free, grace terminée, 5 factures actives forcées → bloqué
|
||||
* - fresh : reset à un signup neuf (free + grace 14 jours)
|
||||
* - grace-expired : free, grace terminée, ≤ 2 factures actives → OK
|
||||
* - limit-reached : free, grace terminée, 2 factures actives forcées → bloqué
|
||||
* - pro : pro mensuel actif, fake Stripe IDs
|
||||
* - pro-cancelling : pro mais annulation programmée à period_end
|
||||
* - pro-past-due : pro mais paiement échoué (status past_due)
|
||||
@ -129,7 +129,7 @@ export default class BillingScenario extends BaseCommand {
|
||||
org.currentPeriodEnd = null
|
||||
org.cancelAtPeriodEnd = false
|
||||
await org.save()
|
||||
this.logger.info('→ Free, grace period 3 mois fresh, aucun Stripe customer')
|
||||
this.logger.info('→ Free, grace period 14 jours fresh, aucun Stripe customer')
|
||||
}
|
||||
|
||||
private async applyGraceExpired(org: Organization) {
|
||||
@ -143,16 +143,16 @@ export default class BillingScenario extends BaseCommand {
|
||||
org.cancelAtPeriodEnd = false
|
||||
await org.save()
|
||||
this.logger.info(
|
||||
'→ Free, grace period expirée. Si actives ≤ 5 : OK. Si > 5 : bloqué.'
|
||||
'→ Free, grace period expirée. Si actives ≤ 2 : OK. Si > 2 : bloqué.'
|
||||
)
|
||||
}
|
||||
|
||||
private async applyLimitReached(org: Organization) {
|
||||
await this.applyGraceExpired(org)
|
||||
// S'assurer qu'il y a au moins 5 factures actives. Si moins, on en crée
|
||||
// de quoi passer à 5 minimum.
|
||||
// S'assurer qu'il y a au moins `limit` factures actives. Si moins, on en
|
||||
// crée pour atteindre la limite et déclencher le blocage import.
|
||||
const current = await countActiveInvoices(org.id)
|
||||
const limit = PLAN_CAPS.free.activeInvoicesLimit ?? 5
|
||||
const limit = PLAN_CAPS.free.activeInvoicesLimit ?? 2
|
||||
if (current >= limit) {
|
||||
this.logger.info(`→ Déjà ${current} factures actives (≥ ${limit}), OK`)
|
||||
return
|
||||
|
||||
@ -64,8 +64,8 @@ async function makeInvoice(
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test.group('billing — PLAN_CAPS', () => {
|
||||
test('Free : 5 factures max, 1 user, pas de multi-users', ({ assert }) => {
|
||||
assert.equal(PLAN_CAPS.free.activeInvoicesLimit, 5)
|
||||
test('Free : 2 factures max, 1 user, pas de multi-users', ({ assert }) => {
|
||||
assert.equal(PLAN_CAPS.free.activeInvoicesLimit, 2)
|
||||
assert.equal(PLAN_CAPS.free.seatsLimit, 1)
|
||||
assert.isFalse(PLAN_CAPS.free.multiUsers)
|
||||
assert.isFalse(PLAN_CAPS.free.replyFromUserEmail)
|
||||
@ -142,41 +142,41 @@ test.group('billing — canCreateInvoices', (group) => {
|
||||
assert.isTrue(result.allowed)
|
||||
})
|
||||
|
||||
test('Free post-grace + 4 actives → on peut en ajouter 1 (4+1 ≤ 5)', async ({ assert }) => {
|
||||
test('Free post-grace + 1 active → on peut en ajouter 1 (1+1 ≤ 2)', async ({ assert }) => {
|
||||
const { org } = await createTestUser()
|
||||
org.plan = 'free'
|
||||
org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
|
||||
await org.save()
|
||||
const client = await makeClientFor(org)
|
||||
for (let i = 0; i < 4; i++) await makeInvoice(org, client, 'pending')
|
||||
await makeInvoice(org, client, 'pending')
|
||||
const result = await canCreateInvoices(org.id, 1)
|
||||
assert.isTrue(result.allowed)
|
||||
})
|
||||
|
||||
test('Free post-grace + 5 actives → bloqué (5+1 > 5)', async ({ assert }) => {
|
||||
test('Free post-grace + 2 actives → bloqué (2+1 > 2)', async ({ assert }) => {
|
||||
const { org } = await createTestUser()
|
||||
org.plan = 'free'
|
||||
org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
|
||||
await org.save()
|
||||
const client = await makeClientFor(org)
|
||||
for (let i = 0; i < 5; i++) await makeInvoice(org, client, 'in_relance')
|
||||
for (let i = 0; i < 2; i++) await makeInvoice(org, client, 'in_relance')
|
||||
const result = await canCreateInvoices(org.id, 1)
|
||||
assert.isFalse(result.allowed)
|
||||
if (!result.allowed) {
|
||||
assert.equal(result.reason, 'free_limit_active_invoices')
|
||||
assert.equal(result.limit, 5)
|
||||
assert.equal(result.current, 5)
|
||||
assert.equal(result.limit, 2)
|
||||
assert.equal(result.current, 2)
|
||||
}
|
||||
})
|
||||
|
||||
test('Free post-grace + 3 actives + delta=3 → bloqué (3+3 > 5)', async ({ assert }) => {
|
||||
test('Free post-grace + 1 active + delta=2 → bloqué (1+2 > 2)', async ({ assert }) => {
|
||||
const { org } = await createTestUser()
|
||||
org.plan = 'free'
|
||||
org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
|
||||
await org.save()
|
||||
const client = await makeClientFor(org)
|
||||
for (let i = 0; i < 3; i++) await makeInvoice(org, client, 'pending')
|
||||
const result = await canCreateInvoices(org.id, 3)
|
||||
await makeInvoice(org, client, 'pending')
|
||||
const result = await canCreateInvoices(org.id, 2)
|
||||
assert.isFalse(result.allowed)
|
||||
})
|
||||
|
||||
@ -207,9 +207,9 @@ test.group('billing — canCreateInvoices', (group) => {
|
||||
org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
|
||||
await org.save()
|
||||
const client = await makeClientFor(org)
|
||||
// 5 paid + 0 actives = encore 5 slots dispos
|
||||
for (let i = 0; i < 5; i++) await makeInvoice(org, client, 'paid')
|
||||
const result = await canCreateInvoices(org.id, 5)
|
||||
// 10 paid + 0 actives = encore 2 slots dispos
|
||||
for (let i = 0; i < 10; i++) await makeInvoice(org, client, 'paid')
|
||||
const result = await canCreateInvoices(org.id, 2)
|
||||
assert.isTrue(result.allowed)
|
||||
})
|
||||
})
|
||||
@ -230,7 +230,7 @@ test.group('billing — getOrgSubscriptionState', (group) => {
|
||||
assert.equal(state.plan, 'free')
|
||||
assert.isTrue(state.inGracePeriod)
|
||||
assert.equal(state.activeInvoicesCount, 0)
|
||||
assert.equal(state.caps.activeInvoicesLimit, 5)
|
||||
assert.equal(state.caps.activeInvoicesLimit, 2)
|
||||
assert.isFalse(state.hasStripeCustomer)
|
||||
})
|
||||
|
||||
|
||||
110
apps/landing/src/components/sections/TrustedBy.tsx
Normal file
110
apps/landing/src/components/sections/TrustedBy.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { Eyebrow } from "@rubis/ui";
|
||||
import { copy } from "../../copy";
|
||||
|
||||
/**
|
||||
* Bande "Ils utilisent Rubis" — preuve sociale.
|
||||
*
|
||||
* Stratégie : render **null** tant qu'on n'a aucun témoignage / logo signé.
|
||||
* Un placeholder bidon en attendant ferait plus de mal que de bien (signal
|
||||
* "SaaS désert", cf. landing-optimisations.md §4). On laisse le composant
|
||||
* en place dans le flow de la page : dès qu'on remplit `trustedBy.logos`
|
||||
* ou `trustedBy.testimonials` dans `copy.ts`, la section apparaît.
|
||||
*
|
||||
* Trois niveaux d'affichage, dans cet ordre du léger au lourd :
|
||||
* 1. Bande logos clients seule (dès le 1er logo signé)
|
||||
* 2. + Compteur "X relances envoyées ce mois" (dès qu'on a > 50 relances
|
||||
* réelles pour pas faire pitié)
|
||||
* 3. + Témoignages signés (idéalement 3 minimum pour la vraie page)
|
||||
*
|
||||
* Voir le template d'email "Demande de témoignage en échange d'accès à vie"
|
||||
* pour recruter les premiers bêta-testeurs (cf. roadmap acquisition S1).
|
||||
*/
|
||||
export function TrustedBy() {
|
||||
const t = copy.trustedBy;
|
||||
const hasLogos = t.logos.length > 0;
|
||||
const hasTestimonials = t.testimonials.length > 0;
|
||||
const hasCounter = typeof t.counter.value === "number" && t.counter.value > 0;
|
||||
|
||||
// Nothing to show → ne pas rendre du tout. Mieux qu'un placeholder honteux.
|
||||
if (!hasLogos && !hasTestimonials && !hasCounter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="bg-cream-2 border-y border-line">
|
||||
<div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-16 lg:py-20">
|
||||
<div className="text-center max-w-[640px] mx-auto mb-10">
|
||||
<Eyebrow>{t.eyebrow}</Eyebrow>
|
||||
{hasTestimonials && (
|
||||
<>
|
||||
<h2 className="mt-4 font-display font-bold text-ink leading-[1.1] tracking-[-0.025em] text-[30px] sm:text-[38px]">
|
||||
{t.title}
|
||||
</h2>
|
||||
<p className="mt-4 text-[16px] text-ink-2 leading-relaxed">{t.subtitle}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasLogos && (
|
||||
<ul className="flex flex-wrap items-center justify-center gap-x-10 gap-y-6 opacity-80">
|
||||
{t.logos.map((logo) => (
|
||||
<li key={logo.name} className="flex items-center">
|
||||
<img
|
||||
src={logo.logoSrc}
|
||||
alt={logo.name}
|
||||
className="h-8 w-auto grayscale opacity-90 hover:opacity-100 transition-opacity"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{hasCounter && (
|
||||
<p className="mt-10 text-center text-[15px] text-ink-2">
|
||||
<span className="font-display font-extrabold text-rubis text-[28px] tracking-[-0.02em]">
|
||||
{t.counter.value!.toLocaleString("fr-FR")}
|
||||
</span>{" "}
|
||||
<span className="text-ink-3">{t.counter.label}</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{hasTestimonials && (
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{t.testimonials.map((tt) => (
|
||||
<figure
|
||||
key={`${tt.firstName}-${tt.lastName}`}
|
||||
className="bg-white border border-line rounded-card p-6 shadow-soft flex flex-col gap-4"
|
||||
>
|
||||
<blockquote className="text-[15.5px] leading-relaxed text-ink-2">
|
||||
« {tt.quote} »
|
||||
</blockquote>
|
||||
<figcaption className="flex items-center gap-3 mt-auto pt-4 border-t border-line">
|
||||
{tt.avatarSrc && (
|
||||
<img
|
||||
src={tt.avatarSrc}
|
||||
alt={`${tt.firstName} ${tt.lastName}`}
|
||||
className="size-10 rounded-full object-cover"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
)}
|
||||
<div className="text-[13px] leading-tight">
|
||||
<div className="font-semibold text-ink">
|
||||
{tt.firstName} {tt.lastName}
|
||||
</div>
|
||||
<div className="text-ink-3">
|
||||
{tt.role ? `${tt.role} · ` : ""}
|
||||
{tt.company} · {tt.city}
|
||||
</div>
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -59,7 +59,7 @@ export const copy = {
|
||||
ctaPrimary: "Démarrer mon essai 14 jours →",
|
||||
ctaPrimaryHint: "Première relance envoyée en 5 minutes · 14 jours sans engagement",
|
||||
ctaSecondary: "Voir les tarifs",
|
||||
feature1: "14 jours gratuits puis Free 5 factures",
|
||||
feature1: "14 jours gratuits puis Free 2 factures",
|
||||
feature2: "Hébergement souverain",
|
||||
feature3: "Made in France 🇫🇷",
|
||||
mockDashboard: "Tableau de bord",
|
||||
@ -257,7 +257,7 @@ export const copy = {
|
||||
freePrice: "0 €",
|
||||
freeQualifier: "Pour tester ou démarrer en freelance",
|
||||
freeBody_a: "Le plan Free fait tourner Rubis sur ",
|
||||
freeBody_bold: "5 factures actives",
|
||||
freeBody_bold: "2 factures actives",
|
||||
freeBody_b: " en permanence. Gratuit, pour de bon. Notre façon de prouver que la promesse tient.",
|
||||
businessName: "Plan Business",
|
||||
businessPrice: "49 €",
|
||||
@ -323,7 +323,7 @@ export const copy = {
|
||||
},
|
||||
finalCta: {
|
||||
title: "Récupérez vos premières heures dès aujourd'hui.",
|
||||
body: "14 jours gratuits, puis le plan Free continue avec 5 factures actives. Pas de carte demandée pour démarrer.",
|
||||
body: "14 jours gratuits, puis le plan Free continue avec 2 factures actives. Pas de carte demandée pour démarrer.",
|
||||
cta: "Démarrer mon essai 14 jours →",
|
||||
ctaHint: "Première relance envoyée en 5 minutes · 14 jours sans engagement",
|
||||
hint: "Inscription en 30 secondes. Annulation 1-clic à tout moment.",
|
||||
@ -347,6 +347,52 @@ export const copy = {
|
||||
backToBlog: "← Retour au blog",
|
||||
minutesRead: "min de lecture",
|
||||
},
|
||||
trustedBy: {
|
||||
eyebrow: "Ils utilisent Rubis",
|
||||
title: "Premiers retours du terrain.",
|
||||
subtitle: "Témoignages signés, vérifiables. Pas de logo sans accord, pas de citation sans nom complet.",
|
||||
/**
|
||||
* Logos clients en bande. Ajouter un objet ici dès qu'un bêta-testeur
|
||||
* a signé l'accord d'usage marque. `logoSrc` est un chemin servi depuis
|
||||
* `apps/landing/public/trusted/`. Garder le format SVG monochrome pour
|
||||
* un rendu cohérent (cf. /docs/marque.md).
|
||||
*/
|
||||
logos: [] as Array<{
|
||||
name: string;
|
||||
logoSrc: string;
|
||||
city?: string;
|
||||
sector?: string;
|
||||
}>,
|
||||
/**
|
||||
* Témoignages texte signés. Ajouter une entrée dès qu'on a :
|
||||
* - Prénom + nom complet (vrais)
|
||||
* - Société + ville
|
||||
* - URL d'une photo (idéalement portrait carré 200×200, dans
|
||||
* `apps/landing/public/trusted/avatars/<slug>.jpg`)
|
||||
* - Une phrase concrète sur le bénéfice mesuré ("j'ai récupéré
|
||||
* X heures par mois", "j'ai relancé Y factures impayées", pas
|
||||
* "j'adore Rubis")
|
||||
* - Accord d'usage signé (cf. template d'email beta-testeurs)
|
||||
*/
|
||||
testimonials: [] as Array<{
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
company: string;
|
||||
city: string;
|
||||
role?: string;
|
||||
avatarSrc?: string;
|
||||
quote: string;
|
||||
}>,
|
||||
/**
|
||||
* Compteur public optionnel — à brancher sur une route agrégée
|
||||
* `apps/api` (à créer en parallèle de la première vague d'usage réelle).
|
||||
* Tant que `value` est `null`, le bandeau compteur reste masqué.
|
||||
*/
|
||||
counter: {
|
||||
label: "Relances envoyées via Rubis ce mois-ci",
|
||||
value: null as number | null,
|
||||
},
|
||||
},
|
||||
changelog: {
|
||||
title: "Changelog",
|
||||
subtitle: "L'historique des nouveautés, améliorations et corrections déployées sur Rubis.",
|
||||
|
||||
@ -68,7 +68,7 @@ import LegalLayout from "../layouts/LegalLayout.astro";
|
||||
<tr><th>Plan</th><th>Périmètre principal</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><strong>Free</strong></td><td>Jusqu'à 5 factures actives en relance simultanément. Plans de relance standards. OCR limité. Un seul utilisateur.</td></tr>
|
||||
<tr><td><strong>Free</strong></td><td>Jusqu'à 2 factures actives en relance simultanément. Plans de relance standards. OCR limité. Un seul utilisateur.</td></tr>
|
||||
<tr><td><strong>Pro</strong></td><td>Factures et OCR illimités, plans personnalisables, statistiques détaillées, relances signées au nom de l'utilisateur (sous-domaine Rubis), support prioritaire. Un seul utilisateur.</td></tr>
|
||||
<tr><td><strong>Business</strong></td><td>Tout du Pro, plus jusqu'à 5 collaborateurs, envoi des relances depuis le domaine propre de l'utilisateur (configuration DKIM/SPF), onboarding personnel.</td></tr>
|
||||
</tbody>
|
||||
@ -81,7 +81,7 @@ import LegalLayout from "../layouts/LegalLayout.astro";
|
||||
<p>Les tarifs en vigueur sont indiqués sur la page tarifs du site, en euros et hors taxes. La TVA, lorsque applicable, est ajoutée selon le taux en vigueur (20 % en France métropolitaine).</p>
|
||||
|
||||
<h3>6.2 Période d'essai gratuite</h3>
|
||||
<p>Tout nouvel utilisateur bénéficie d'une période d'essai de <strong>14 jours</strong> permettant l'accès au plan Pro sans engagement et sans carte bancaire requise. À l'issue de cette période, l'utilisateur peut souscrire à un plan payant ou poursuivre gratuitement avec le plan Free (5 factures actives).</p>
|
||||
<p>Tout nouvel utilisateur bénéficie d'une période d'essai de <strong>14 jours</strong> permettant l'accès au plan Pro sans engagement et sans carte bancaire requise. À l'issue de cette période, l'utilisateur peut souscrire à un plan payant ou poursuivre gratuitement avec le plan Free (2 factures actives).</p>
|
||||
|
||||
<h3>6.3 Modalités de paiement</h3>
|
||||
<p>Le paiement des plans payants s'effectue en ligne via notre prestataire de paiement <strong>Stripe</strong>, par carte bancaire ou prélèvement SEPA. Aucune donnée bancaire n'est stockée sur les serveurs de l'éditeur.</p>
|
||||
|
||||
@ -13,6 +13,7 @@ import { HowItWorks } from "../components/sections/HowItWorks";
|
||||
import { Gamification } from "../components/sections/Gamification";
|
||||
import { AutoBanking } from "../components/sections/AutoBanking";
|
||||
import { Legal } from "../components/sections/Legal";
|
||||
import { TrustedBy } from "../components/sections/TrustedBy";
|
||||
import { Pricing } from "../components/sections/Pricing";
|
||||
import { FAQ } from "../components/sections/FAQ";
|
||||
import { FinalCTA } from "../components/sections/FinalCTA";
|
||||
@ -45,6 +46,7 @@ const jsonLd = {
|
||||
<Gamification />
|
||||
<AutoBanking />
|
||||
<Legal />
|
||||
<TrustedBy />
|
||||
<Pricing />
|
||||
<FAQ />
|
||||
<FinalCTA />
|
||||
|
||||
@ -9,8 +9,8 @@ import { formatDate } from "@/lib/format";
|
||||
* Banner d'enforcement plan Free.
|
||||
*
|
||||
* - Hidden : plan Pro/Business OU période de grâce active
|
||||
* - "Approche" : 4-5 factures sur 5 → ton conseil
|
||||
* - "Atteinte" : ≥ 5 factures sur 5 → ton blocant + CTA upgrade
|
||||
* - "Approche" : ratio ≥ 80 % du quota → ton conseil
|
||||
* - "Atteinte" : ratio ≥ 100 % du quota → ton blocant + CTA upgrade
|
||||
*
|
||||
* Posé en haut de /factures et /factures/import. Pas dans le dashboard
|
||||
* pour ne pas polluer la lecture des KPIs.
|
||||
@ -76,7 +76,7 @@ export function PlanLimitBanner({ className }: { className?: string }) {
|
||||
</p>
|
||||
<p className="mt-1 text-[12.5px] text-ink-2 leading-snug">
|
||||
{reached
|
||||
? "Vous avez utilisé vos 5 factures actives gratuites. Passez Pro pour continuer à importer et relancer sans contrainte."
|
||||
? `Vous avez utilisé vos ${limit} factures actives gratuites. Passez Pro pour continuer à importer et relancer sans contrainte.`
|
||||
: "Vous approchez de la limite Free. Passer Pro maintenant évite l'interruption."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -33,7 +33,7 @@ function fakeState(overrides: Partial<SubscriptionState> = {}): SubscriptionStat
|
||||
return {
|
||||
plan: "free",
|
||||
caps: {
|
||||
activeInvoicesLimit: 5,
|
||||
activeInvoicesLimit: 2,
|
||||
seatsLimit: 1,
|
||||
multiUsers: false,
|
||||
replyFromUserEmail: false,
|
||||
@ -124,7 +124,7 @@ describe("useIsAtFreeLimit", () => {
|
||||
vi.spyOn(api, "get").mockResolvedValue(
|
||||
fakeState({
|
||||
plan: "free",
|
||||
activeInvoicesCount: 5,
|
||||
activeInvoicesCount: 2,
|
||||
inGracePeriod: false,
|
||||
}),
|
||||
);
|
||||
@ -150,11 +150,11 @@ describe("useIsAtFreeLimit", () => {
|
||||
await waitFor(() => expect(result.current).toBe(true));
|
||||
});
|
||||
|
||||
it("retourne false sur Free post-grace mais activeCount < limit (4 < 5)", async () => {
|
||||
it("retourne false sur Free post-grace mais activeCount < limit (1 < 2)", async () => {
|
||||
vi.spyOn(api, "get").mockResolvedValue(
|
||||
fakeState({
|
||||
plan: "free",
|
||||
activeInvoicesCount: 4,
|
||||
activeInvoicesCount: 1,
|
||||
inGracePeriod: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -91,12 +91,12 @@ function AbonnementPage() {
|
||||
<header className="max-w-2xl">
|
||||
<Eyebrow>Abonnement</Eyebrow>
|
||||
<h1 className="mt-2 font-display text-[30px] font-bold tracking-[-0.024em] text-ink lg:text-[36px] leading-[1.05]">
|
||||
Trois mois pour voir.
|
||||
14 jours pour voir.
|
||||
<br />
|
||||
Puis vous <em className="text-rubis">décidez</em>.
|
||||
</h1>
|
||||
<p className="mt-3 text-[14.5px] text-ink-2 leading-relaxed">
|
||||
Pas de carte demandée pour démarrer. Au-delà de 5 factures actives,
|
||||
Pas de carte demandée pour démarrer. Au-delà de 2 factures actives,
|
||||
vous passez Pro pour continuer — quand vous êtes prêt, pas avant.
|
||||
</p>
|
||||
</header>
|
||||
@ -370,18 +370,18 @@ function PlanFree({ current }: { current: boolean }) {
|
||||
<p className="font-display text-[28px] font-bold leading-none text-ink mb-1">
|
||||
0 €
|
||||
</p>
|
||||
<p className="text-[11.5px] text-ink-3 mb-5">3 mois illimités, puis 5 factures actives</p>
|
||||
<p className="text-[11.5px] text-ink-3 mb-5">14 jours illimités, puis 2 factures actives</p>
|
||||
|
||||
<ul className="flex flex-col gap-2.5 text-[12.5px] text-ink-2 leading-snug">
|
||||
<FeatureLine>Tester sans poser sa CB</FeatureLine>
|
||||
<FeatureLine>Plans de relance fournis</FeatureLine>
|
||||
<FeatureLine>OCR + automatisations 3 mois</FeatureLine>
|
||||
<FeatureLine>Au-delà : 5 factures à la fois</FeatureLine>
|
||||
<FeatureLine>OCR + automatisations 14 jours</FeatureLine>
|
||||
<FeatureLine>Au-delà : 2 factures à la fois</FeatureLine>
|
||||
</ul>
|
||||
|
||||
<p className="mt-auto pt-6 text-[11px] italic text-ink-3 leading-snug">
|
||||
Tout ce qu'il faut pour comprendre l'outil. Vous prendrez Pro
|
||||
quand votre volume mensuel dépasse 5 factures à relancer.
|
||||
dès que votre activité de relance démarre vraiment.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -4,6 +4,26 @@
|
||||
|
||||
---
|
||||
|
||||
## ADR-023 · Plan Free : 2 factures actives (vs 5 initialement)
|
||||
|
||||
- **Date** : 2026-05-18
|
||||
- **Statut** : ✅ Validée
|
||||
- **Contexte** : le plan Free initial autorisait 5 factures actives en relance — un seuil défini *avant* d'avoir une vraie lecture du marché. À l'arrivée des premiers signaux qualitatifs (cf. audit `docs/tech/landing-optimisations.md`) il apparaît que **le segment cœur de Rubis** (freelance solo, artisan, consultant indépendant, kiné en libéral) émet **typiquement moins de 5 factures par mois**. Conséquence : on offre gratuitement le produit aux gens qui devraient être nos premiers payeurs, et on ne capture en payant que les boîtes plus grosses (déjà servies par Pennylane, Sellsy, Qonto recouvrement intégré, etc.).
|
||||
- **Décision** : abaisser la limite Free à **2 factures actives**. Le ratio est calibré pour permettre de *tester* l'outil (créer une facture, voir une relance partir, recevoir un check-in) sans le rendre utilisable en production solo. La bascule Pro devient incontournable dès que l'usage est réel.
|
||||
- **Rationale** :
|
||||
- **2 vs 0** : on garde une vraie expérience Free, indispensable pour permettre aux prospects méfiants de tester sans engagement. Free vide = friction d'inscription, on perd le funnel d'évangélisation.
|
||||
- **2 vs 3-4** : 3 factures par mois reste le bas du segment cœur (kiné, freelance ultra-petit) — laisser 3 maintient le piège. 2 force la décision dès que l'activité de relance devient récurrente.
|
||||
- **Source de vérité unique** : `PLAN_CAPS.free.activeInvoicesLimit` dans `apps/api/app/services/billing.ts`. L'UI et la copy l'utilisent en lecture (PlanLimitBanner) ou la dupliquent statiquement (CGV §5, landing Pricing, /parametres/abonnement).
|
||||
- **Alternatives écartées** :
|
||||
- **Supprimer le plan Free** (essai 14 j uniquement) : plus radical, mais coupe le canal d'évangélisation. Risque trop fort tant qu'on n'a pas validé le funnel payant.
|
||||
- **Free dégradé en lecture seule après 14 jours** (création/relances coupées, historique lisible) : plus subtil mais plus complexe à implémenter UX + back, et ne règle pas le fond — les utilisateurs habitués à un Free actif vont s'énerver à la bascule.
|
||||
- **Conséquences** :
|
||||
- Les orgs créées avant cette décision conservent leur grace period historique (3 mois post-signup, posée par la migration `1778157876956_alter_organizations_table`) — *pendant* la grâce aucun cap, *après* elles tombent sur la nouvelle limite à 2 (et non plus à 5).
|
||||
- La transition vers un vrai "essai 14 j Pro avec CB à l'inscription" (Stripe Setup Intent) est tracée séparément — cf. `docs/tech/landing-optimisations.md` §3 et §6, ADR à venir au moment de l'implémentation.
|
||||
- **À mesurer 30 jours après déploiement** : taux Free → Pro, churn signup à T+1 jour (les ultra-réticents abandonnent-ils plus vite ?).
|
||||
|
||||
---
|
||||
|
||||
## ADR-022 · Changelog en Markdown versionné (pas en DB)
|
||||
|
||||
- **Date** : 2026-05-11
|
||||
|
||||
353
docs/marketing/seo-articles.md
Normal file
353
docs/marketing/seo-articles.md
Normal file
@ -0,0 +1,353 @@
|
||||
# Articles SEO — plan & brouillons
|
||||
|
||||
> Version : 0.1 · Dernière maj : 2026-05-18
|
||||
> Référence d'arbitrage : `docs/tech/landing-optimisations.md` §6
|
||||
|
||||
Plan de production des **5 articles SEO ciblés** à publier dans les 30 prochains jours. Ce sont nos seuls leviers SEO long-terme — ils capturent le trafic des prospects qui cherchent **avant** de connaître l'existence d'un outil comme Rubis.
|
||||
|
||||
L'infrastructure technique est déjà en place :
|
||||
|
||||
- Blog rendu par `apps/landing` à `rubis.pro/blog/<slug>` (SSR + cache HTTP).
|
||||
- Posts en DB `posts` côté `apps/api`, validés via `app.rubis.pro/admin/blog`.
|
||||
- `[slug].astro` injecte déjà du JSON-LD `Article` + `BreadcrumbList`, `inLanguage: fr-FR`, `keywords` via les tags du post, `og:image` depuis `ogImageUrl`.
|
||||
- Cron IA hebdomadaire (Sonnet 4.6) génère des brouillons en attente de review humaine.
|
||||
|
||||
Ce qui manque : le contenu lui-même, **ciblé sur les bons mots-clés et structuré pour la SERP**.
|
||||
|
||||
---
|
||||
|
||||
## Stratégie
|
||||
|
||||
### Pattern d'article SEO Rubis
|
||||
|
||||
Tous les articles suivent la même structure pour rester reconnaissables et performer en SERP. Aucun de ces 5 articles ne doit ressembler à un article corporate générique — la voix Rubis (directe, concrète, chaleureuse — *bon associé, pas DAF*) reste l'actif différenciant.
|
||||
|
||||
1. **Titre H1** — exactement la query cible quand possible (« Comment relancer un client qui ne paie pas »). Pas de baroque.
|
||||
2. **Lede de 2-3 phrases** — pose le problème, qualifie le lecteur (« si vous êtes freelance et que… »), promet la résolution.
|
||||
3. **TL;DR encadré** — 3 bullets de réponse directe pour que Google puisse remonter un *featured snippet*.
|
||||
4. **3-5 sections H2** — chacune adresse un aspect concret. Pas de fluff intro/conclusion redondants.
|
||||
5. **Tableau ou checklist** au moins une fois — booste les chances de rich snippet (Google adore les tableaux dans la SERP).
|
||||
6. **Modèle prêt à copier-coller** quand le sujet le permet (email, courrier, calcul). Le copier-coller est l'unité de valeur SEO B2B la plus puissante.
|
||||
7. **CTA Rubis en fin uniquement** — discret, en aside encadré. **Jamais en intro** pour ne pas casser la valeur perçue.
|
||||
8. **3-5 liens internes** vers d'autres articles Rubis (à brancher au fur et à mesure que la collection grossit).
|
||||
|
||||
### Mots-clés cibles
|
||||
|
||||
Les 5 articles couvrent 5 intentions distinctes du funnel, du plus en amont (« je découvre que j'ai un problème ») au plus en aval (« je veux passer à l'acte »).
|
||||
|
||||
| # | Article | Query principale | Volume mensuel FR (estimé) | Intention |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Modèle d'email de relance facture impayée | `modèle email relance facture impayée` | ~2 400 | Acte (cherche un template) |
|
||||
| 2 | Que faire quand un client ne paie pas | `que faire quand un client ne paie pas` | ~1 800 | Recherche solutions |
|
||||
| 3 | Délai de paiement légal en France (LME) | `délai de paiement légal entreprise` | ~1 600 | Information (cadre légal) |
|
||||
| 4 | Mise en demeure : quand l'envoyer | `mise en demeure impayé délai` | ~880 | Acte (cherche le bon moment) |
|
||||
| 5 | Modèle de lettre de mise en demeure | `lettre mise en demeure modèle gratuit` | ~3 600 | Acte (cherche un template) |
|
||||
|
||||
Volumes à valider via Ahrefs / SEMrush avant publication. Articles 1 et 5 sont les plus forts pour le funnel « acte » — à publier en priorité.
|
||||
|
||||
### Lead magnet
|
||||
|
||||
Sur les articles 1 et 5 (les modèles prêts à copier-coller), proposer un **téléchargement PDF/Word** du modèle en échange de l'email. Liste de remarketing utilisable pour relancer ces leads avec une séquence email courte (J+1 « comment ça s'est passé ? », J+7 « envie d'automatiser ? »).
|
||||
|
||||
Implémentation : route `/api/v1/leads/seo-template` côté `apps/api` qui prend `{ email, templateSlug }` + le PDF servi depuis MinIO. À chantier dans une PR séparée.
|
||||
|
||||
---
|
||||
|
||||
## Article 1 — Modèle d'email de relance facture impayée (gratuit, à copier-coller)
|
||||
|
||||
**Status** : brouillon prêt à valider en admin.
|
||||
**Slug** : `modele-email-relance-facture-impayee`
|
||||
**Mot-clé principal** : `modèle email relance facture impayée`
|
||||
**Mots-clés secondaires** : `email relance client qui ne paie pas`, `relance facture impayée modèle`, `lettre relance facture word`
|
||||
**Excerpt** : "Trois modèles d'emails de relance pour facture impayée — du rappel courtois (J+3) à la mise en demeure ferme (J+30). Prêts à copier-coller, adaptés au droit français (loi LME)."
|
||||
**Hero image** : à shooter / illustrer — *suggestion* : un mockup de boîte mail avec une facture dans le sujet, palette rubis.
|
||||
**Reading time estimé** : 6 min.
|
||||
|
||||
### Contenu
|
||||
|
||||
```markdown
|
||||
**TL;DR** — Trois modèles d'emails de relance, du courtois au ferme :
|
||||
- **J+3** (rappel doux) — partir du principe d'un oubli, pas d'un problème.
|
||||
- **J+10** (relance ferme mais respectueuse) — rappeler le délai légal LME (60 jours max B2B).
|
||||
- **J+30** (mise en demeure) — préavis avant procédure, pas la procédure elle-même.
|
||||
|
||||
Tous les modèles ci-dessous sont gratuits, sous licence libre — copiez,
|
||||
adaptez, envoyez. Pas de mail à renseigner pour les lire.
|
||||
|
||||
## Avant le premier email : ce que dit la loi
|
||||
|
||||
Trois choses utiles à savoir avant d'envoyer la première relance, parce
|
||||
qu'elles vont structurer le ton et les enjeux.
|
||||
|
||||
1. **Le délai de paiement maximum entre entreprises est fixé par la loi
|
||||
LME à 60 jours** (ou 45 jours fin de mois). Au-delà, vous êtes dans
|
||||
votre droit le plus strict — ce n'est ni de la pression ni du
|
||||
harcèlement, c'est l'application du contrat et de la loi.
|
||||
2. **Les pénalités de retard sont dues automatiquement** dès le premier
|
||||
jour de retard, **même si elles ne sont pas mentionnées sur la
|
||||
facture** (art. L441-10 du Code de commerce). Le taux légal mini :
|
||||
3 fois le taux d'intérêt légal. Plus une indemnité forfaitaire de
|
||||
**40 €** par facture impayée pour frais de recouvrement.
|
||||
3. **La DGCCRF a prononcé 47 M€ d'amendes en 2025** contre les mauvais
|
||||
payeurs B2B français. Vous n'êtes pas seul, ce n'est pas une
|
||||
exception.
|
||||
|
||||
Cela posé, **personne ne veut envoyer un commissaire de justice à son
|
||||
meilleur client.** L'objectif des trois emails ci-dessous est de monter
|
||||
le ton progressivement, pas de faire un coup de pression d'entrée.
|
||||
|
||||
## Modèle 1 — Relance courtoise (J+3 après l'échéance)
|
||||
|
||||
À envoyer 3 jours après la date d'échéance, jamais avant. Posture : on
|
||||
part du principe d'un oubli administratif, pas d'un problème.
|
||||
|
||||
> **Objet** : Facture {{numero}} — petit rappel
|
||||
>
|
||||
> Bonjour {{prenom_client}},
|
||||
>
|
||||
> Petit message pour faire suivre la facture {{numero}} datée du
|
||||
> {{date_emission}}, dont l'échéance était fixée au {{date_echeance}}.
|
||||
> Elle ne nous est pas encore parvenue.
|
||||
>
|
||||
> Je me dis qu'elle a peut-être glissé entre deux mails — auquel cas
|
||||
> pas de souci, voici de nouveau le détail :
|
||||
>
|
||||
> - Montant : {{montant_ttc}}
|
||||
> - Échéance initiale : {{date_echeance}}
|
||||
> - IBAN pour virement : {{iban}}
|
||||
>
|
||||
> Si vous l'avez déjà réglée ces derniers jours, ignorez ce mail — nos
|
||||
> banques mettent parfois 48 h à se synchroniser.
|
||||
>
|
||||
> Belle journée,
|
||||
> {{signature}}
|
||||
|
||||
**Pourquoi ça marche** : pas d'accusation, pas de menace, juste un
|
||||
rappel des faits. 70 % des factures se débloquent sur cette première
|
||||
relance — souvent parce que la pièce comptable n'a effectivement pas
|
||||
été traitée à temps.
|
||||
|
||||
## Modèle 2 — Relance ferme (J+10)
|
||||
|
||||
À envoyer 10 jours après l'échéance si la première relance est restée
|
||||
sans réponse. On change de registre : on rappelle le cadre, on demande
|
||||
une date.
|
||||
|
||||
> **Objet** : Facture {{numero}} — toujours en attente de règlement
|
||||
>
|
||||
> Bonjour {{prenom_client}},
|
||||
>
|
||||
> Je reviens vers vous concernant la facture {{numero}}, échue depuis
|
||||
> {{jours_retard}} jours. Mon premier rappel du {{date_premiere_relance}}
|
||||
> est resté sans retour de votre part.
|
||||
>
|
||||
> Je souhaiterais comprendre si un point de friction empêche le
|
||||
> règlement : litige, pièce manquante de notre côté, période de
|
||||
> congés ? Si oui, dites-le moi, on trouve une solution.
|
||||
>
|
||||
> Sinon, je vous demande de **bien vouloir procéder au règlement sous
|
||||
> 8 jours**, soit au plus tard le {{date_limite_relance_2}}, par virement
|
||||
> à l'IBAN {{iban}}.
|
||||
>
|
||||
> Pour rappel, les pénalités de retard sont dues automatiquement au
|
||||
> taux légal majoré (art. L441-10 Code de commerce), plus une indemnité
|
||||
> forfaitaire de 40 € pour frais de recouvrement.
|
||||
>
|
||||
> Je reste à votre disposition,
|
||||
> {{signature}}
|
||||
|
||||
**Pourquoi ça marche** : on a posé une date butoir précise, on a
|
||||
rappelé le cadre légal (sans le brandir agressivement), on a laissé
|
||||
une porte ouverte au dialogue. La majorité des dossiers se résolvent
|
||||
ici.
|
||||
|
||||
## Modèle 3 — Mise en demeure (J+30)
|
||||
|
||||
À envoyer 30 jours après l'échéance, **par lettre recommandée AR doublée
|
||||
d'un email**. C'est le préavis avant procédure, pas la procédure
|
||||
elle-même. Le ton est ferme, factuel, sans agressivité.
|
||||
|
||||
> **Objet** : Mise en demeure — facture {{numero}} impayée
|
||||
>
|
||||
> Madame, Monsieur,
|
||||
>
|
||||
> Malgré nos précédentes relances datées du {{date_premiere_relance}}
|
||||
> et du {{date_seconde_relance}}, la facture {{numero}} d'un montant
|
||||
> de {{montant_ttc}}, échue le {{date_echeance}}, demeure impayée.
|
||||
>
|
||||
> Par la présente, **je vous mets en demeure de procéder au règlement
|
||||
> sous 8 jours**, soit au plus tard le {{date_limite_mise_en_demeure}}.
|
||||
>
|
||||
> À défaut de règlement à cette date, je me verrai contraint, sans
|
||||
> autre préavis :
|
||||
>
|
||||
> 1. D'engager une procédure de recouvrement judiciaire (injonction de
|
||||
> payer auprès du Tribunal de commerce).
|
||||
> 2. De facturer les pénalités de retard au taux légal majoré et
|
||||
> l'indemnité forfaitaire de 40 € (art. L441-10 du Code de commerce).
|
||||
> 3. De vous facturer les frais de recouvrement engagés au-delà du
|
||||
> forfait, sur présentation de justificatifs.
|
||||
>
|
||||
> Restant dans l'attente de votre règlement,
|
||||
>
|
||||
> {{signature_complete}}
|
||||
> {{adresse_emetteur}}
|
||||
> {{siren_emetteur}}
|
||||
|
||||
**Pourquoi ça marche** : ce mail est un acte juridique. Doublé d'une
|
||||
LRAR, il est recevable au Tribunal de commerce comme preuve de mise en
|
||||
demeure. À l'inverse, son ton calme et précis évite le piège du « ton
|
||||
qui monte d'un coup » — qui peut braquer un client qui aurait fini par
|
||||
payer.
|
||||
|
||||
## Quand ces modèles ne suffisent pas
|
||||
|
||||
Si la facture n'est toujours pas réglée 15 jours après la mise en
|
||||
demeure, vous avez deux options :
|
||||
|
||||
1. **Injonction de payer** (procédure rapide, ~50 €, Tribunal de
|
||||
commerce) — fonctionne bien pour les créances non contestées.
|
||||
2. **Mandat à un cabinet de recouvrement** — plus cher (forfait + %
|
||||
variable), mais ils gèrent toute la procédure.
|
||||
|
||||
Avant d'en arriver là, vérifiez que vous avez :
|
||||
- ✓ La facture émise dans les délais légaux
|
||||
- ✓ Les CGV signées par le client (idéalement)
|
||||
- ✓ Le bon de commande ou le devis accepté
|
||||
- ✓ La preuve de livraison ou de prestation rendue
|
||||
```
|
||||
|
||||
### CTA fin d'article (aside encadré)
|
||||
|
||||
> ### Et si vous automatisiez tout ça ?
|
||||
>
|
||||
> Si vous envoyez plus de 5 relances par mois manuellement, vous êtes
|
||||
> probablement en train de perdre **5 heures par semaine** à écrire les
|
||||
> mêmes mails. Rubis automatise les trois cadences ci-dessus — vous
|
||||
> importez la facture, choisissez le plan de relance, et la machine fait
|
||||
> le reste. La mise en demeure reste sous validation manuelle (parce
|
||||
> qu'elle a des conséquences).
|
||||
>
|
||||
> [Démarrer mon essai 14 jours →](https://app.rubis.pro)
|
||||
|
||||
---
|
||||
|
||||
## Article 2 — Que faire quand un client ne paie pas ? Guide complet 2026
|
||||
|
||||
**Status** : outline détaillé.
|
||||
**Slug** : `que-faire-quand-client-ne-paie-pas`
|
||||
**Mot-clé principal** : `que faire quand un client ne paie pas`
|
||||
**Mots-clés secondaires** : `client mauvais payeur que faire`, `facture impayée recours`, `recouvrement amiable B2B`
|
||||
|
||||
### Structure
|
||||
|
||||
1. **TL;DR** — Les 4 étapes : (1) prévention en amont, (2) relance amiable J+3/J+10/J+30, (3) mise en demeure formelle, (4) procédure judiciaire ou cabinet de recouvrement. Pas plus.
|
||||
2. **H2 — Avant que ça arrive : 3 réflexes de prévention** (CGV signées, acompte 30 %, scoring client via Pappers/Société.com).
|
||||
3. **H2 — Étape 1 : relances amiables échelonnées** (lien vers article 1 sur les modèles d'email).
|
||||
4. **H2 — Étape 2 : mise en demeure** (lien vers article 4 sur le timing, article 5 sur le modèle).
|
||||
5. **H2 — Étape 3 : injonction de payer au Tribunal de commerce** (procédure détaillée, coût ~50 €, délai 2-3 semaines).
|
||||
6. **H2 — Étape 4 : cabinet de recouvrement ou commissaire de justice** (différence, coûts, quand choisir lequel).
|
||||
7. **H2 — Tableau récap** : étape / délai / coût / efficacité estimée.
|
||||
8. **CTA Rubis** — automatisation des étapes 1-2 (relances + brouillon mise en demeure).
|
||||
|
||||
### Notes
|
||||
|
||||
- Ne pas se planquer sur l'aspect « émotionnel » de la situation (relation client compromise, perte de récurrence) — c'est ce qui distingue le contenu Rubis du contenu LegalStart.
|
||||
- Citer la DGCCRF (47 M€ d'amendes 2025) pour rassurer sur la légitimité.
|
||||
|
||||
---
|
||||
|
||||
## Article 3 — Délai de paiement légal en France : ce que dit la loi LME
|
||||
|
||||
**Status** : outline détaillé.
|
||||
**Slug** : `delai-paiement-legal-france-lme`
|
||||
**Mot-clé principal** : `délai de paiement légal entreprise`
|
||||
**Mots-clés secondaires** : `loi LME délai paiement`, `délai paiement B2B France`, `60 jours fin de mois facture`
|
||||
|
||||
### Structure
|
||||
|
||||
1. **TL;DR** — 30 jours par défaut, **60 jours plafond** entre entreprises (ou 45 jours fin de mois). Au-delà, sanctions DGCCRF jusqu'à 2 M€.
|
||||
2. **H2 — Le délai par défaut : 30 jours** (art. L441-10 Code de commerce).
|
||||
3. **H2 — Le délai plafond : 60 jours nets ou 45 jours fin de mois** (différence + comment calculer).
|
||||
4. **H2 — Les exceptions sectorielles** (agroalimentaire 30 j, transport, BTP, livre, etc. — tableau).
|
||||
5. **H2 — Sanctions en cas de dépassement** (DGCCRF, amendes 2 M€, name & shame trimestriel).
|
||||
6. **H2 — Comment se mettre en conformité** côté payeur ET côté créancier.
|
||||
7. **CTA Rubis** — Rubis applique le délai légal automatiquement dans les plans de relance par défaut.
|
||||
|
||||
### Notes
|
||||
|
||||
- C'est l'article le plus juridique des 5. Style sobre, citations précises (articles du Code), pas d'effet de manche.
|
||||
- Citer les amendes récentes Fnac Darty (3,9 M€), Cdiscount (2,1 M€), Sanofi (1,65 M€), LCL (1,5 M€) — mêmes chiffres que la section Legal du hero.
|
||||
|
||||
---
|
||||
|
||||
## Article 4 — Mise en demeure : quand l'envoyer ? Le bon timing en 2026
|
||||
|
||||
**Status** : outline détaillé.
|
||||
**Slug** : `mise-en-demeure-quand-envoyer-bon-timing`
|
||||
**Mot-clé principal** : `mise en demeure impayé délai`
|
||||
**Mots-clés secondaires** : `quand envoyer mise en demeure`, `mise en demeure après combien de jours`, `timing mise en demeure facture`
|
||||
|
||||
### Structure
|
||||
|
||||
1. **TL;DR** — Pas avant J+30, idéalement J+30 à J+45. Avant : trop tôt, casse la relation. Après J+60 : perte de pénalités automatiques.
|
||||
2. **H2 — Ce que la loi exige formellement** (il n'y a pas de délai imposé, mais des conséquences au timing choisi).
|
||||
3. **H2 — Pourquoi J+30 est le sweet spot** (cadre LME, espérance des juges, perception client).
|
||||
4. **H2 — Les 3 prérequis avant d'envoyer** (preuve d'envoi des relances précédentes, montant exact, CGV à portée).
|
||||
5. **H2 — Le format légal** : LRAR + email doublon. Modèle (lien vers article 5).
|
||||
6. **H2 — Et après ?** Délai de 8 jours obligatoire, puis injonction de payer.
|
||||
7. **CTA Rubis** — Rubis prépare le brouillon à la bonne étape de votre plan, vous validez manuellement (décision produit forte).
|
||||
|
||||
---
|
||||
|
||||
## Article 5 — Lettre de mise en demeure : modèle Word à télécharger gratuitement
|
||||
|
||||
**Status** : outline détaillé.
|
||||
**Slug** : `lettre-mise-en-demeure-modele-word-gratuit`
|
||||
**Mot-clé principal** : `lettre mise en demeure modèle gratuit`
|
||||
**Mots-clés secondaires** : `modèle mise en demeure word`, `modèle lettre mise en demeure facture`, `mise en demeure pdf gratuit`
|
||||
|
||||
### Structure
|
||||
|
||||
1. **TL;DR** — Modèle Word + PDF prêts à télécharger. Adapté au droit français. Compatible LRAR.
|
||||
2. **H2 — Téléchargement** (lead magnet — email contre PDF, cf. section dédiée plus haut).
|
||||
3. **H2 — Les mentions obligatoires** (5 éléments à vérifier avant envoi).
|
||||
4. **H2 — Le modèle dans le texte** (copier-coller direct pour les utilisateurs qui ne veulent pas donner leur email).
|
||||
5. **H2 — Erreurs à éviter** (ton trop agressif, oubli de la date butoir, montant flou).
|
||||
6. **H2 — Et si la mise en demeure ne suffit pas** ? Injonction de payer (lien vers article 2).
|
||||
7. **CTA Rubis** — Rubis génère la mise en demeure pré-remplie quand votre plan de relance arrive à l'étape correspondante.
|
||||
|
||||
### Notes
|
||||
|
||||
- Article le plus « commercial » SEO — pure intention d'acte. À publier en dernier quand on a déjà 2-3 articles indexés (besoin de signaux d'autorité).
|
||||
- Lead magnet : indispensable. C'est l'article qui doit construire la liste de remarketing.
|
||||
|
||||
---
|
||||
|
||||
## Calendrier de publication suggéré
|
||||
|
||||
| Semaine | Article | Status |
|
||||
|---|---|---|
|
||||
| S1 (publication immédiate) | #1 Modèles d'email de relance | Brouillon prêt, à valider admin |
|
||||
| S2 | #3 Délai légal LME | À rédiger |
|
||||
| S3 | #2 Que faire quand un client ne paie pas | À rédiger |
|
||||
| S4 | #4 Mise en demeure : quand l'envoyer | À rédiger |
|
||||
| S5 | #5 Lettre de mise en demeure : modèle | À rédiger + lead magnet PDF |
|
||||
|
||||
Rythme : 1 article / semaine = 5 semaines. Couplé au cron IA hebdomadaire (Sonnet 4.6 → review humaine), on peut tenir ce rythme tant que le pipeline review est fluide.
|
||||
|
||||
---
|
||||
|
||||
## À mesurer
|
||||
|
||||
À 30 jours après la publication du dernier article :
|
||||
|
||||
- **Position SEO** sur les 5 queries principales (objectif : top 10 sur au moins 2 d'entre elles).
|
||||
- **Trafic organique** vers `/blog/*` (Plausible / Umami — outil à trancher, cf. landing-optimisations.md §3).
|
||||
- **CTR depuis SERP** (Search Console).
|
||||
- **Conversion blog → signup** (PostHog : event `signup_cta_clicked` avec `location: "blog_article"`).
|
||||
- **Téléchargements lead magnet** (articles 1 et 5).
|
||||
|
||||
---
|
||||
|
||||
*Document maintenu en parallèle de `docs/tech/landing-optimisations.md`. Toute modification de stratégie SEO doit s'aligner sur les arbitrages produit consignés dans `docs/decisions.md`.*
|
||||
292
docs/tech/stripe-trial-with-card.md
Normal file
292
docs/tech/stripe-trial-with-card.md
Normal file
@ -0,0 +1,292 @@
|
||||
# Migration billing : essai 14 j Pro avec CB à l'inscription
|
||||
|
||||
> Version : 0.1 · Dernière maj : 2026-05-18
|
||||
> Référence d'arbitrage : `docs/tech/landing-optimisations.md` §3 + §6
|
||||
> Statut : **À implémenter** — pas encore en code. Ce doc décrit l'archi cible + plan d'exécution.
|
||||
|
||||
Document de chantier pour passer le funnel signup d'**aucun engagement** (Free 2 factures par défaut, Pro via Checkout au moment où l'usage dépasse la limite) à **essai 14 jours Pro avec carte bancaire à l'inscription** (Stripe `trial_period_days`, prélèvement automatique au J+14, possibilité d'annuler en un clic).
|
||||
|
||||
**Pourquoi** : un essai sans CB convertit 2 à 3 fois moins qu'un essai avec CB demandée (cf. études Profitwell, ChartMogul). Le segment cœur de Rubis (TPE-PME françaises sans crédit manager) a besoin d'être confronté à la décision payante dès qu'il a senti la valeur — sinon il oublie.
|
||||
|
||||
---
|
||||
|
||||
## État actuel (avant migration)
|
||||
|
||||
### Flow signup courant
|
||||
|
||||
1. `POST /api/v1/auth/signup` — `NewAccountController.store` crée Organization vide + provision les plans par défaut + crée User.
|
||||
2. Émet une `AuthSession` (Bearer + refresh cookie).
|
||||
3. **Aucun appel Stripe** à l'inscription. `org.plan = 'free'`, `stripeCustomerId = null`, `stripeSubscriptionId = null`, `gracePeriodEndsAt = null` (nouvelles orgs).
|
||||
4. User est redirigé vers `/onboarding/compte` → `/onboarding/entreprise` → `/onboarding/signature`.
|
||||
|
||||
### Upgrade Pro plus tard
|
||||
|
||||
1. User va sur `/parametres/abonnement`, clique « Passer Pro ».
|
||||
2. SPA appelle `POST /api/v1/billing/checkout` → Stripe Checkout `mode: 'subscription'`.
|
||||
3. User paye via Checkout, est ramené sur `/parametres/abonnement?checkout=success`.
|
||||
4. Webhook `customer.subscription.created` → `org.plan = 'pro'`, `stripeSubscriptionId` posé.
|
||||
|
||||
### Limites du modèle actuel
|
||||
|
||||
- Le segment cœur ne paye jamais : 2 factures Free (cf. ADR-023) suffisent pour beaucoup, et la friction d'aller chercher Checkout dans les Settings est forte.
|
||||
- Pas de tunnel forcé : on perd les indécis qui se disent « j'essaie plus tard » et ne reviennent pas.
|
||||
- L'orga historique (3 mois grace illimitée) habituait les users à un comportement opposé.
|
||||
|
||||
---
|
||||
|
||||
## État cible
|
||||
|
||||
### Flow signup cible
|
||||
|
||||
1. `POST /api/v1/auth/signup` — Organization + User créés comme aujourd'hui.
|
||||
2. **Nouveau** : `POST /api/v1/billing/start-trial` (auth, appelé juste après signup) → crée Stripe Customer + Checkout Session avec `subscription_data.trial_period_days: 14` + `payment_method_collection: 'always'`.
|
||||
3. SPA redirige vers l'URL Stripe Checkout.
|
||||
4. User remplit sa CB sur Checkout, est ramené sur `/onboarding/compte`. La subscription est créée en `status: 'trialing'`, `org.plan = 'pro'`, `subscriptionStatus = 'trialing'`.
|
||||
5. Onboarding standard se poursuit (compte → entreprise → signature).
|
||||
6. À J+14, Stripe prélève automatiquement et passe la subscription en `status: 'active'`.
|
||||
|
||||
### Fallback « pas de CB »
|
||||
|
||||
Bouton secondaire sur le Checkout-redirect screen (apps/web) :
|
||||
|
||||
> **Pas de CB ? Démarrer en Free (2 factures)**
|
||||
|
||||
→ skip le Checkout, l'org reste `free` avec `gracePeriodEndsAt: null`. Cap immédiat à 2 factures. Récupère les ultra-réticents sans casser le funnel principal.
|
||||
|
||||
### Email J+12 (recap avant prélèvement)
|
||||
|
||||
Déclenché par le webhook Stripe `customer.subscription.trial_will_end` (Stripe l'émet automatiquement 3 jours avant `trial_end` = J+11 dans notre cas — assez proche du J+12 proposé par le doc). Si on veut exactement J+12, basculer sur un scheduler interne (BullMQ / cron node-cron) qui lit `trial_end - 2 days`.
|
||||
|
||||
Contenu : récap usage des 12 premiers jours (factures relancées, € récupérés, rubis gagnés) + rappel date prélèvement + lien annulation 1-clic vers Customer Portal.
|
||||
|
||||
---
|
||||
|
||||
## Architecture cible
|
||||
|
||||
### Modifications DB
|
||||
|
||||
| Champ | Avant | Après | Notes |
|
||||
|---|---|---|---|
|
||||
| `organizations.plan` | `'free'\|'pro'\|'business'` | `'free'\|'pro'\|'business'` | Inchangé. `'pro'` couvre trial + paid. |
|
||||
| `organizations.subscription_status` | `'active'\|'past_due'\|'canceled'\|...` | + `'trialing'` | Stripe l'émet déjà, on le persiste tel quel. |
|
||||
| `organizations.trial_ends_at` | n'existe pas | `timestamp with timezone NULL` | Date de fin d'essai (= Stripe `subscription.trial_end`). Sert au teaser UI sans rappel Stripe à chaque page. |
|
||||
| `organizations.grace_period_ends_at` | conservé | conservé | Pour les orgs historiques. Nouvelles orgs : `null`. |
|
||||
|
||||
Migration : ajouter `trial_ends_at` ; backfill à null pour les orgs existantes (elles ne sont pas en trial).
|
||||
|
||||
### Modifications API
|
||||
|
||||
| Endpoint | État | Action |
|
||||
|---|---|---|
|
||||
| `POST /api/v1/billing/start-trial` | Nouveau | Crée Customer + Checkout Session avec `trial_period_days: 14`, return URL. |
|
||||
| `POST /api/v1/billing/checkout` | Existant | Conserver — pour les users qui sont passés Free (fallback) et veulent upgrader plus tard. **Sans trial dans ce cas** (déjà eu son essai). |
|
||||
| `POST /api/v1/billing/webhook` | Existant | Étendre pour gérer `customer.subscription.trial_will_end` → trigger email J+12. |
|
||||
| `services/billing.ts` `canCreateInvoices` | Existant | Bypass quota si `subscription_status === 'trialing'` (Pro illimité pendant l'essai). |
|
||||
|
||||
### Modifications Frontend (apps/web)
|
||||
|
||||
1. **Nouveau** : `src/routes/onboarding/billing.tsx` — étape entre `/signup` et `/onboarding/compte`.
|
||||
- Bouton primaire : « Démarrer l'essai 14 jours » → appelle `start-trial` → redirige vers Stripe Checkout.
|
||||
- Bouton secondaire : « Pas de carte ? Commencer en Free (2 factures) » → skip, redirige `/onboarding/compte`.
|
||||
2. **Modifier** : `src/main.tsx` ou le guard `_app` → forcer le passage par `/onboarding/billing` tant que `org.stripeCustomerId` est null **et** que l'org ne s'est pas explicitement déclarée Free (cookie ou flag DB `org.skippedTrial`).
|
||||
3. **Modifier** : `src/components/billing/PlanLimitBanner.tsx` → afficher un mini-rappel pendant le trialing (« Essai Pro · X jours restants »).
|
||||
4. **Modifier** : copy signup (`src/routes/signup.tsx`) → préciser « Carte demandée à l'étape suivante, non prélevée avant J+14 ».
|
||||
5. **Modifier** : landing `Hero.tsx` / `FinalCTA.tsx` — pouvoir réactiver le sous-texte CTA « CB demandée, non prélevée avant J+14 » (actuellement masqué — cf. cecbddc).
|
||||
|
||||
### Modifications Emails (apps/api)
|
||||
|
||||
1. **Nouveau template React Email** : `emails/trial_recap.tsx` — recap J+12.
|
||||
2. **Nouveau template** : `emails/trial_ended_success.tsx` — confirme le passage en payant.
|
||||
3. **Nouveau template** : `emails/trial_ended_failed.tsx` — paiement échoué, fallback Free + lien CB.
|
||||
|
||||
Contenu détaillé des trois emails en annexe ↓.
|
||||
|
||||
---
|
||||
|
||||
## Risques et mitigations
|
||||
|
||||
| Risque | Impact | Mitigation |
|
||||
|---|---|---|
|
||||
| Le Stripe Checkout est hosté → on perd le contrôle UX à l'étape la plus stressante du tunnel | Conversion -10 à -20 % vs Elements custom | Accepter en V1, on rebascule sur Payment Element custom en V2 si la conversion plafonne. Checkout est plus rapide à shipper et fiable côté compliance. |
|
||||
| Webhook `trial_will_end` peut être manqué (réseau, redéploiement) | Email J+12 raté → user surpris par le prélèvement | Doubler avec un job cron quotidien (`trial-recap-cron`) qui scanne les orgs avec `subscription_status = 'trialing'` ET `trial_ends_at` dans 1-3 jours et envoie l'email idempotemment (table `email_log` avec dédoublonnage). |
|
||||
| Org historique (3 mois grace) avec orgs récentes mêlées dans la table | Migration backfill incorrecte | Le champ `grace_period_ends_at` reste intouché. Le nouveau flow ne pose `trial_ends_at` que sur les orgs qui passent par `start-trial`. Pas de conflit. |
|
||||
| Stripe locale FR + 3D Secure | Friction supplémentaire sur certaines cartes | Locale `fr` déjà passée au Checkout, 3DS géré nativement par Stripe. Tester en mode test avec cartes 3DS `4000 0027 6000 3184`. |
|
||||
| User ferme Stripe Checkout sans valider → state incohérent | Compte créé sans CB ni redirect onboarding | Côté webhook `checkout.session.expired` → ne rien faire. Côté UI, gérer le cas `?checkout=cancel` sur `/onboarding/billing` : afficher le fallback Free + bouton « réessayer avec CB ». |
|
||||
| Migration des orgs en grace period (ils ne devraient pas voir l'écran billing) | UX confuse pour les early users | Le guard `_app` ne force `/onboarding/billing` que si `org.gracePeriodEndsAt` est null. Les orgs historiques continuent leur vie sans changement. |
|
||||
|
||||
---
|
||||
|
||||
## Plan d'exécution
|
||||
|
||||
### PR 1 — Backend (1 j)
|
||||
|
||||
1. Migration `add_trial_ends_at_to_organizations.ts`.
|
||||
2. Endpoint `POST /api/v1/billing/start-trial` dans `billing_controller.ts`.
|
||||
3. Étendre le webhook handler :
|
||||
- `checkout.session.completed` → persister `trialEndsAt` depuis `subscription.trial_end`.
|
||||
- `customer.subscription.trial_will_end` → trigger email recap (nouveau service).
|
||||
- `customer.subscription.updated` (passage `trialing → active`) → email confirmation.
|
||||
- `customer.subscription.updated` (passage `trialing → past_due`) → email fallback.
|
||||
4. Bypass `canCreateInvoices` quand `subscription_status === 'trialing'`.
|
||||
5. Tests unitaires (`tests/unit/billing.spec.ts`).
|
||||
6. Cron de redondance `trial-recap-cron` (BullMQ ou node-cron quotidien).
|
||||
|
||||
### PR 2 — Frontend tunnel (0,5 j)
|
||||
|
||||
1. Route `src/routes/onboarding/billing.tsx`.
|
||||
2. Guard `_app` : forcer `/onboarding/billing` si trial pas encore offert.
|
||||
3. Banner « Essai Pro · X jours restants » dans `PlanLimitBanner.tsx`.
|
||||
4. Copy signup + landing (réactiver le sous-texte CTA « CB demandée, non prélevée avant J+14 »).
|
||||
|
||||
### PR 3 — Emails (0,5 j)
|
||||
|
||||
1. Template `emails/trial_recap.tsx`.
|
||||
2. Template `emails/trial_ended_success.tsx`.
|
||||
3. Template `emails/trial_ended_failed.tsx`.
|
||||
4. Tester via Resend / mode dev.
|
||||
|
||||
### PR 4 — Tests end-to-end (0,5 j)
|
||||
|
||||
1. Tester en mode test Stripe :
|
||||
- Carte normale `4242 4242 4242 4242` → trial OK → prélèvement à J+14 OK.
|
||||
- Carte 3DS `4000 0027 6000 3184` → 3DS OK → trial OK.
|
||||
- Carte déclinée à J+14 `4000 0000 0000 0341` → email fallback Free OK.
|
||||
2. Vérifier idempotence webhook (renvoyer 2× le même event).
|
||||
|
||||
Total : **2,5 j de dev**, alignés sur l'estimation initiale du doc landing-optimisations.md §6.
|
||||
|
||||
---
|
||||
|
||||
## Annexe — Contenu des emails transactionnels
|
||||
|
||||
### Email J+12 — Recap d'essai
|
||||
|
||||
**Sujet** : Plus que 2 jours d'essai — voilà ce que Rubis a déjà fait pour vous.
|
||||
|
||||
**Preheader** : X factures relancées, Y € récupérés, Z minutes libérées. Récap avant le prélèvement.
|
||||
|
||||
**Body** :
|
||||
|
||||
```
|
||||
Bonjour {{prenom}},
|
||||
|
||||
12 jours que vous testez Rubis. Petit récap avant le prélèvement de
|
||||
votre abonnement Pro dans 2 jours (le {{date_prelevement}}).
|
||||
|
||||
Ce que Rubis a fait pour vous depuis le {{date_signup}} :
|
||||
|
||||
◆ {{nb_factures_importees}} factures importées
|
||||
◆ {{nb_relances_envoyees}} relances envoyées automatiquement
|
||||
◆ {{euros_recuperes}} € encaissés
|
||||
◆ {{nb_rubis_gagnes}} rubis ≈ {{heures_liberees}} de votre temps
|
||||
que vous n'avez pas passé à relancer
|
||||
|
||||
[ICI un bloc visuel avec les 4 chiffres, gros, rubis sur crème]
|
||||
|
||||
Si tout est OK, vous n'avez rien à faire — votre essai bascule en Pro
|
||||
mensuel à {{prix_pro_ttc}} TTC le {{date_prelevement}}. Vous gardez
|
||||
toutes vos données, tous vos plans de relance, toutes vos relances en
|
||||
cours.
|
||||
|
||||
Si vous voulez annuler, c'est en un clic depuis vos paramètres :
|
||||
|
||||
[Annuler mon essai →]
|
||||
|
||||
Vos relances en cours s'arrêteront le {{date_prelevement}} et vous
|
||||
basculerez automatiquement sur le plan Free (2 factures actives).
|
||||
|
||||
Une question ? Répondez à ce mail, je le lis personnellement.
|
||||
|
||||
Belle journée,
|
||||
Arthur — fondateur de Rubis
|
||||
```
|
||||
|
||||
**Variables à interpoler** :
|
||||
|
||||
- `{{prenom}}` — `user.fullName.split(' ')[0]`
|
||||
- `{{date_signup}}` — `org.createdAt` formaté FR
|
||||
- `{{date_prelevement}}` — `org.trialEndsAt` formaté FR
|
||||
- `{{prix_pro_ttc}}` — `19 €` ou `49 €` selon plan
|
||||
- `{{nb_factures_importees}}` — count(invoices where org_id=… and created_at >= signup_date)
|
||||
- `{{nb_relances_envoyees}}` — count(reminder_logs where status='sent' and org_id=…)
|
||||
- `{{euros_recuperes}}` — sum(amount_cents) where status='paid' and paid_at >= signup_date
|
||||
- `{{nb_rubis_gagnes}}` — org.rubisCount
|
||||
- `{{heures_liberees}}` — formatRubisToHours(rubisCount)
|
||||
|
||||
### Email J+14 — Trial → active (succès)
|
||||
|
||||
**Sujet** : Bienvenue dans Rubis Pro — votre paiement est passé.
|
||||
|
||||
**Body** :
|
||||
|
||||
```
|
||||
Bonjour {{prenom}},
|
||||
|
||||
Votre essai est terminé et le prélèvement de {{prix_pro_ttc}} a été
|
||||
effectué avec succès. Vous êtes officiellement en Pro.
|
||||
|
||||
Pas de changement de votre côté — toutes vos factures, plans de
|
||||
relance et automatisations continuent comme avant, sans aucune
|
||||
interruption.
|
||||
|
||||
Trois choses utiles à connaître maintenant :
|
||||
|
||||
◆ Votre facture Stripe est disponible dans vos paramètres
|
||||
[Voir mes factures Rubis →]
|
||||
|
||||
◆ Vous pouvez changer de carte ou de cycle (annuel = 2 mois
|
||||
gratuits) en un clic
|
||||
[Gérer mon abonnement →]
|
||||
|
||||
◆ Vous pouvez annuler à tout moment depuis le même écran. Aucune
|
||||
question posée.
|
||||
|
||||
Merci de votre confiance.
|
||||
|
||||
Arthur — fondateur de Rubis
|
||||
```
|
||||
|
||||
### Email J+14 — Trial → past_due (échec paiement)
|
||||
|
||||
**Sujet** : Votre essai Rubis a expiré — paiement à régulariser.
|
||||
|
||||
**Body** :
|
||||
|
||||
```
|
||||
Bonjour {{prenom}},
|
||||
|
||||
Votre essai 14 jours est arrivé à son terme, mais le prélèvement de
|
||||
{{prix_pro_ttc}} sur votre carte n'a pas pu aboutir
|
||||
({{stripe_decline_code_humain}}).
|
||||
|
||||
Pas de panique : pendant 7 jours, votre compte reste en Pro et toutes
|
||||
vos relances continuent comme avant. Il vous suffit de mettre à jour
|
||||
votre carte pour rester sur Pro :
|
||||
|
||||
[Mettre à jour ma carte →]
|
||||
|
||||
Au-delà de 7 jours sans paiement valide, votre compte bascule
|
||||
automatiquement sur le plan Free (2 factures actives). Vos données
|
||||
restent intactes, mais les relances au-delà de 2 factures simultanées
|
||||
sont mises en pause.
|
||||
|
||||
Une question ? Répondez à ce mail, je vous réponds dans la journée.
|
||||
|
||||
Belle journée,
|
||||
Arthur — fondateur de Rubis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Décisions ouvertes
|
||||
|
||||
À documenter en ADR au moment de l'implémentation :
|
||||
|
||||
1. **Tunnel forcé vs onboarding optionnel** : l'écran `/onboarding/billing` doit-il être bloquant (pas d'accès au reste sans choix CB ou Free) ou skippable (« plus tard ») ? Recommandation : bloquant, sinon on perd le bénéfice principal du tunnel.
|
||||
2. **Stripe Checkout vs Payment Element custom** : tradeoff UX vs time-to-ship. Recommandation V1 : Checkout (compliance gratuite, locale FR native). V2 si la conversion plafonne : Payment Element intégré.
|
||||
3. **Email J+12 timing exact** : Stripe émet `trial_will_end` à J+11 par défaut. On peut soit accepter (1 jour d'écart sans importance), soit overrider via cron interne pour avoir J+12 pile. Recommandation : accepter le J+11 Stripe (moins de complexité), reformulater le copy "Plus que 3 jours" au lieu de "Plus que 2 jours" si besoin.
|
||||
4. **Org historique en grace** : on les laisse purger naturellement leurs 3 mois, ou on les force à passer par l'écran trial dès leur prochain login ? Recommandation : laisser purger — moins de churn brutal.
|
||||
|
||||
---
|
||||
|
||||
*Document maintenu en parallèle de `docs/tech/landing-optimisations.md`. Implémentation à faire dans une session dédiée avec accès Stripe test mode + clé webhook signing secret de test.*
|
||||
Loading…
x
Reference in New Issue
Block a user